SlideShare a Scribd company logo
1 of 54
이더리움 스마트계약
보안지침 가이드
2. 솔리디티 권고안
(주)지앤클라우드 송상욱
여기서는 스마트계약을 작성할때 일반적으로 따라야 하는 몇가지 패턴들을 보여줍니다.
개요
이더리움 스마트계약
프로토콜 권고안
외부호출 (1)
외부호출을 할때 주의하라.
신뢰할 수없는 계약서를 요청하면 예상치 못한 위험이나 오류가 발생할 수 있다. 외부 호출은 그 계약 또는
그 계약에 의존하는 다른 계약에서 악의적인 코드를 실행할 수 있다. 따라서 모든 외부 호출은 잠재적인 보
안 위험으로 간주되어야 한다. 외부 호출을 제거 할 수 없거나 바람직하지 않은 경우에는, 이 단원의 나머지
부분에있는 권장 사항을 사용하여 위험을 최소화한다.
외부호출 (2)
신뢰할수 없는 계약을 표시하라.
외부 계약과 상호 작용할 때 변수, 방법 및 계약 인터페이스의 이름을 이들과 상호 작용하는 것이 잠재적으
로 위험할 수 있다. 이것은 외부 계약을 호출하는 함수에 적용된다.
// bad
Bank.withdraw(100); // Unclear whether trusted or untrusted
function makeWithdrawal(uint amount) { // Isn't clear that this function is potentially unsafe
Bank.withdraw(amount);
}
// good
UntrustedBank.withdraw(100); // untrusted external call
TrustedBank.withdraw(100); // external but trusted bank contract maintained by XYZ Corp
function makeUntrustedWithdrawal(uint amount) {
UntrustedBank.withdraw(amount);
}
외부호출 (3)
외부 호출 후에는 상태변경을 피한다.
Raw 호출 사용 (someAddress.call() 방식) 또는 컨트랙트 호출 (ExternalContract.someMethod() 방식) 을
사용할때 악성 코드가 실행될 수 있다고 가정한다. 비록 ExternalContract 계약이 악성코드가 아니라 할지
라도 그 코드가 호출하는 어떤 계약에 의해서도 악성 코드는 실행될 수 있다.
한가지 특별한 위험은 악성 코드가 제어 흐름을 방해하여 재진입으로 인한 취약성으로 이어질 수 있다는 것
이다.
신뢰할 수 없는 외부 계약을 호출하는 경우 호출 후의 상태 변경을 피하십시오. 이 패턴은 때로는 체크 효과
상호 작용 패턴(checks-effects-interactions pattern) 으로 알려져 있다.
외부호출 (4)
send(), transfer(), call.value()() 사이의 트레이드 오프관계를 인식한다.
이더리움을 전송할때는 send(), transfer(), call.value()() 사이의 트레이드 오프관계를 인식해야 한다.
● someAddress.send() 와 someAddress.transfer() 는 재진입 문제에 대해 안전하다. 이러한 방법은 여
전히 코드 실행을 유발하지만, 이른바 계약은 현재 이벤트를 기록하기에 충분할 정도로 2,300개의 가
스만 공급 받는다.
● x.transfer(y) 는 require(x.send(y)) 과 같으며 전송에 실패하면 자동으로 복구(revert) 된다.
● someAddress.call.value(y)() 는 이더리움을 전송하고 코드 실행을 트리거한다. 실행된 코드에는 사용
할 수 있는 모든 가스가 제공되므로 이러한 유형의 값 전송은 재진입에 대해 안전하지 않다.
send () 또는 transfer ()를 사용하면 재진입을 방지 할 수 있지만 폴백함수가 2,300 개가 넘는 가스를 요구
하는 계약과 호환되지 않는 비용으로 처리한다. 또한 가스의 양을 지정하려면
someAddress.call.value(ethAmount) .gas(gasAmount)() 와 같이 사용해도 된다.
이 트레이드 오프의 균형을 맞출수 있는 한가지 패턴은 Push 컴포넌트에서는 send() 와 transfer()를 사용하
고, Pull 컴포넌트에서는 call.value()()를 사용하는 것이다.
값 전송을 위해 send () 또는 transfer ()를 독점적으로 사용한다고해서 계약을 재진입에 대한 안전하게 만들
지는 않는다. 하지만 해당 값의 전송은 재진입에 대해 안전하게 만들어준다.
외부호출 (4) 이어서
외부 호출의 오류 처리
솔리디티는 raw 주소에서 동작하는 낮은 수준의 호출방법을 제공한다. address.call(), address.callcode(),
address.delegatecall(), address.send() 이 낮은 수준의 방법들은 예외를 던지지 않지만, 예외상황 발생시
False값을 리턴한다. 반면에, 컨트랙트 호출(예: ExternalContract.doSomething() )는 자동으로 예외를 전
파는데 예를들어, ExternalContract.doSomething() 는 doSomething()에서 예외가 발생하면, 예외를 던지게
된다.
만약 로우 레벨 호출방법을 사용하도록 선택한 경우 반환 값을 확인하여 호출에 실패할 경우를 처리해야 한
다.
외부호출 (5)
외부호출 (5) 이어서
// bad
someAddress.send(55);
someAddress.call.value(55)(); // this is doubly dangerous, as it will forward all remaining gas and
doesn't check for result
someAddress.call.value(100)(bytes4(sha3("deposit()"))); // if deposit throws an exception, the raw call()
will only return false and transaction will NOT be reverted
// good
if(!someAddress.send(55)) {
// handle failure code
}
ExternalContract(someAddress).deposit.value(100);
외부 호출에 대한 Push Over Pull
외부 통화가 실수로 또는 의도적으로 실패 할 수 있다. 이러한 실패로 인한 피해를 최소화하려면 각 외부 호
출을 호출 수신자가 시작할 수있는 자체 트랜잭션으로 격리하는 것이 좋다. 이것은 특히 지급과 관련이 있
다. 사용자가 자동으로 자금을 밀어 넣는 대신 사용자가 자금을 인출 할 수 있도록하는 것이 더 좋으며 이렇
게 하면 “가스 제한 문제”의 발생을 줄일 수 있다. 단일 트랜잭션에서 여러 send () 호출을 결합하지 말라.
외부호출 (6)
외부호출 (6) 이어서
// 틀린예
contract auction {
address highestBidder;
uint highestBid;
function bid() payable {
require(msg.value >= highestBid);
if (highestBidder != address(0)) {
highestBidder.transfer(highestBid); // if this call consistently fails, no one else can bid
}
highestBidder = msg.sender;
highestBid = msg.value;
}
}
외부호출 (6) 이어서
// 좋은예
contract auction {
address highestBidder;
uint highestBid;
mapping(address => uint) refunds;
function bid() payable external {
require(msg.value >= highestBid);
if (highestBidder != address(0)) {
refunds[highestBidder] += highestBid; // record the refund that this user can claim
}
highestBidder = msg.sender;
highestBid = msg.value;
}
function withdrawRefund() external {
uint refund = refunds[msg.sender];
refunds[msg.sender] = 0;
msg.sender.transfer(refund);
}
}
신뢰할수 없는 코드에 delegatecall을 하지 말라.
delegatecall 함수는 호출자 계약에 속한 것처럼 다른 계약의 함수를 호출하는 데 사용된다. 따라서 피 호출
자는 발신 주소의 상태를 변경할 수 있으며 이것은 안전하지 않을 수 있다. 아래 예제는 delegatecall을 사용
하면 계약이 파기되고 잔고가 없어질 수 있음을 보여준다.
배포 된 Destructor 계약의 주소를 인수로 사용하여 Worker.doWork ()를 호출하면 Worker 계약이 자체 파
괴된다. 신뢰할 수있는 계약에만 실행 위임하며 사용자가 제공 한 주소에는 절대 위임하지 않도록 한다.
외부호출 (7)
외부호출 (7) 이어서
contract Destructor
{
function doWork() external
{
selfdestruct(0);
}
}
contract Worker
{
function doWork(address _internalWorker) public
{
// unsafe
_internalWorker.delegatecall(bytes4(keccak256("doWork()")));
}
}
이더는 계좌에 강제로 보내 질 수 있다.
엄격하게 계약의 잔고를 체크하는 코드를 주의한다.
공격자는 임의의 계정에 강제로 이더를 보낼 수 있으며, 이를 막을 수는 없다. (revert ()를 수행하는 폴백
(fallback) 기능조차도)
공격자는 계약을 생성하고 1 wei로 자금을 조달하고 selfdestruct (victimAddress)를 호출하여 이를 수행 할
수 있다. victimAddress에서 코드가 호출되지 않으므로 이를 막을 수 없다.
블록체인 데이터는 공개되어 있다.
많은 응용 프로그램은 제출 된 데이터가 작동하기 위해 어느 시점까지 비공개로 있어야 한다. 게임 (예 : 가
위바위보)과 경매 메커니즘이 예시에 해당하는 두 가지 주요 카테고리이다. 개인 정보가 문제가 되는 응용
프로그램을 만드는 경우 사용자가 정보를 너무 일찍 게시하지 않도록 하라. 가장 좋은 전략은 커밋 계획을
별도의 단계로 사용하는 것이다. 먼저 값의 해시를 사용하고 나중에 단계에서 값을 나타낸다.
● 가위바위보 게임에서 두 플레이어가 먼저 의도한 동작의 해시를 제출하도록 요구 한 다음, 양 플레이
어가 자신의 동작을 제출한다. 제출 된 동작이 해시와 일치하지 않으면 이를 버린다.
● 경매에서 플레이어는 초기 단계에서 입찰가 값의 해시를 제출하고 (입찰 값보다 큰 입금액과 함께) 두
번째 단계에서 경매 입찰가를 제출해야 한다.
● 난수 생성기에 따라 응용 프로그램을 개발할 때 순서는 항상 (1) 플레이어가 이동을 제출하고, (2) 생
성 된 난수를 계산하고, (3) 플레이어가 지불해야 한다. 난수 생성 방법은 그 자체로 활발한 연구 영역
이다. 현재 동급 최강의 솔루션으로는 Bitcoin 블록 헤더 (http://btcrelay.org를 통해 검증 됨), hash-
commit-reveal 방식 (한 당사자가 숫자를 생성하고 해당 해시를 값에 "커밋"하도록 게시하며 나중에
값을 공개) 그리고 RANDAO가 있다. Ethereum은 결정론적 프로토콜이기 때문에 프로토콜 내의 변수
를 예측할 수 없는 난수로 사용할 수 없다. 또한 채굴자가 block.blockhash () 값 *을 어느 정도 제어하
고 있음을 알아 두어야 한다.
"오프라인 상태"로 전환하여 복귀하지 않을 가능성
특정 조치를 수행하는 특정 당사자에 따라 자금을 환원하지 않고 환불 또는 청구 프로세스를 수행하지 말라.
예를 들어, 가위바위보 게임에서 한 가지 공통적인 실수는 두 플레이어가 자신의 동작을 제출할 때까지 지
불금을 지불하지 않는 것이다. 그러나 악의적인 플레이어는 자신의 동작을 단순히 제출하지 않음으로써 상
대방을 힘들게 할 수 있다. 실제로 다른 플레이어가 공개한 동작을 보고 자신이 진 것을 알게되면 자신의 동
작을 제출할 이유가 전혀 없다. 그러한 상황이 문제가 된다면, (1) 제한 시간을 통해 참여자를 움직이게 하
는 방법을 제공하고, (2) 참여자에게는 추가적인 경제적 인센티브를 주어, 자신이 있는 모든 상황에서 정보
를 제출하도록 유도한다.
음의 정수의 부정을 주의한다.
Solidity는 부호있는 정수로 작업 할 수 있는 몇 가지 유형을 제공한다. 대부분의 프로그래밍 언어와 마찬가
지로 Solidity에서는 N 비트의 부호있는 정수는 -2 ^ (N-1)에서 2 ^ (N-1) -1까지의 값을 나타낼 수 있다. 즉,
MIN_INT에는 양수 값이 없다. 부정(Negation) 은 숫자 2의 보수를 찾는 것으로 구현되므로 가장 큰 숫자의
부정은 같은 숫자가 된다.
이러한 속성은 Solidity (int8, int16, ..., int256)의 모든 부호있는 정수 유형에 해당된다.
이를 처리하는 한 가지 방법은 부정 이전에 변수의 값을 확인하고 MIN_INT와 같으면 던져 넣는 것이다. 또
다른 옵션은 용량이 더 큰 유형을 사용하여 가장 큰 숫자에 도달하지 않도록 하는 것이다. (예 : int16 대신
int32)
MIN_INT가 -1로 나누거나 곱해질 때 int 타입과 비슷한 문제가 발생한다.
음의 정수의 부정을 주의한다.
contract Negation {
function negate8(int8 _i) public pure returns(int8) {
return -_i;
}
function negate16(int16 _i) public pure returns(int16) {
return -_i;
}
int8 public a = negate8(-128); // -128
int16 public b = negate16(-128); // 128
int16 public c = negate16(-32768); // -32768
}
솔리디티 권고안
다음 권장 사항은 Solidity에만 적용되지만 다른 언어로 된 스마트 계약 개발에 도움이 될수도 있다.
불변값은 Assert()를 강제한다.
불변의 속성이 변경되는 경우와 같은 assert 이 실패하면 assert 가드가 트리거 된다. 예를 들어, 토큰 발행
계약에서 토큰 대 이더 발행 비율은 고정시킬 수 있다. 이러한 케이스는 항상 assert ()로 입증할 수 있다.
Assert 가드는 종종 계약 일시 중지나 업그레이드 허용과 같은 다른 기술과 결합되어야 한다. (그렇지 않으
면 Assert가 항상 실패해서 진행이 막힐수가 있다.)
contract Token {
mapping(address => uint) public balanceOf;
uint public totalSupply;
function deposit() public payable {
balanceOf[msg.sender] += msg.value;
totalSupply += msg.value;
assert(this.balance >= totalSupply);
}
}
assert (), require (), revert ()를 올바르게 사용한다.
편의 함수 assert와 require는 조건을 검사하고 조건이 충족되지 않으면 예외를 throw하는 데 사용할 수 있
다.
● assert 함수는 내부 오류를 테스트하고 불변성을 검사하는 데에만 사용해야 한다.
● require 함수는 입력 또는 계약 상태 변수와 같은 유효한 조건을 확인하거나 외부 계약에 대한 호출에
서 반환 값의 유효성을 검사하는 데 사용해야 한다.
이 패러다임을 따르면 분석 도구를 통해 유효하지 않은 opcode에 도달 할 수 없음을 확인할 수 있다. 즉, 코
드의 불변 변수가 위반되지 않고 코드가 정식으로 검증됨을 의미한다.
assert (), require (), revert ()를 올바르게 사용한다.
pragma solidity ^0.5.0;
contract Sharer {
function sendHalf(address payable addr) public payable returns (uint balance) {
require(msg.value % 2 == 0, "Even value required."); //Require() can have an optional message string
uint balanceBeforeTransfer = address(this).balance;
addr.transfer(msg.value / 2);
// Since transfer throws an exception on failure and
// cannot call back here, there should be no way for us to
// still have half of the money.
assert(address(this).balance == balanceBeforeTransfer - msg.value / 2); // used for internal error
checking
return address(this).balance;
}
}
modifier는 assertion 용도로만 사용한다.
modifier 내부의 코드는 일반적으로 함수 본문 전에 실행되므로 상태 변경이나 외부 호출이 Checks-
Effects- Interactions 패턴을 위반하게 된다. 또한 수정 자에 대한 코드가 함수 선언과 멀리 떨어져있을 수
있기 때문에 이러한 문장은 개발자가 알아 채지 못할 수도 있다. 예를 들어, modifier에 외부 호출을 하면 재
진입 공격이 발생할 수 있다.
아래 코드와 같은 레지스트리 계약은 isVoter () 내부에서 Election.vote ()를 호출하여 재진입 공격을 할 수
있다.
modifier를 사용하면 isOwner(), require 또는 revert 와 같은 여러 함수에서 중복 조건 검사를 대체할 수 있
다. 이렇게 하면 스마트 계약 코드가 읽기 쉽고 감사하기 쉬워진다.
modifier는 assertion 용도로만 사용한다.
contract Registry {
address owner;
function isVoter(address _addr) external returns(bool) {
// Code
}
}
contract Election {
Registry registry;
modifier isEligible(address _addr) {
require(registry.isVoter(_addr));
_;
}
function vote() isEligible(msg.sender) public {
// Code
}
}
정수 나누기에 주의한다.
모든 정수 나누기는 가장 가까운 정수로 내림한다. 더 많은 정밀도가 필요하면 승수를 사용해서 값을 크게
하거나 분자와 분모를 모두 저장하는 것이 좋다.
// bad
uint x = 5 / 2; // Result is 2, all integer divison rounds DOWN to the nearest integer
승수를 사용하면 반올림을 방지 할 수 있다.하지만 나중에 x값을 사용할때 곱해진 승수를 고려해야 한다.
// good
uint multiplier = 10;
uint x = (5 * multiplier) / 2;
분자와 분모를 저장한다는 것은 분자 / 분모 오프 체인 (off-chain)의 결과를 계산할 수 있음을 의미한다.
// good
uint numerator = 5;
uint denominator = 2;
abstract와 interface는 트레이드오프다.
interface와 abstract 는 모두 스마트 계약을 위한 커스터마이징과 재사용이 가능한 접근 방식을 제공한다.
Solidity 0.4.11에서 소개 된 인터페이스는 추상인 계약과 유사하지만 함수를 가질 수는 없다. 인터페이스는
스토리지에 액세스 할 수 없고 다른 인터페이스를 상속할수 없다는 제약이 있어, 일반적으로 abstract
contract 가 좀더 실용적으로 보인다. 하지만 인터페이스는 구현하기 전에 contract를 설계하는 데 확실히
유용하다. 또한 abstract 계약을 상속하는 경우 구현되지 않은 모든 기능을 구현해야 하며 그렇지 않다면 그
계약도 abstract contract 가 된다.
폴백(Fallback) 함수
폴백함수는 심플하게 유지한다.
Fallback 함수는 계약서에 인수가 없는 메시지가 전달되거나 함수가 일치하지 않을 때 호출되며 .send () 또
는 .transfer ()에서 호출 될 때 2,300의 가스만 소모할 수 있다. .send () 또는 .transfer ()에서 이더를 수신 할
수 있게 하려면 fallback 함수에서 할 수 있는 일은 이벤트를 기록하는 것이다. 더 많은 가스를 계산해야하는
경우 그에 적합한 함수를 사용한다.
// bad
function() payable { balances[msg.sender] += msg.value; }
// good
function deposit() payable external { balances[msg.sender] += msg.value; }
function() payable { require(msg.data.length == 0); emit LogDepositReceived(msg.sender); }
폴백(Fallback) 함수
폴백함수내에서 데이터 길이를 체크한다.
폴백함수는 일반 이더 전송(데이터가 없음)뿐만 아니라 다른 함수에 매칭되지 않은 경우에도 사용되므로,
수신되는 이더를 로깅할 목적으로만 폴백함수를 사용하려는 경우 데이터가 비어 있는지 확인해야 한다. 그
렇지 않으면 호출자가 계약이 잘못 사용되는지와 존재하지 않는 함수가 호출되었는지 여부를 알 수 없다.
// bad
function() payable { emit LogDepositReceived(msg.sender); }
// good
function() payable { require(msg.data.length == 0); emit LogDepositReceived(msg.sender); }
지불 함수 및 상태 변수를 명시한다.
Solidity 0.4.0부터 이더를 받는 모든 함수는 payable 수식어를 사용해야 한다. 그렇지 않으면 msg.value> 0
인 트랜잭션이 revert 된다. (강제 실행은 제외)
transfer()를 호출하려면, 변수와 함수 인수는 address payable 로 선언해야 한다. address payable에
는 .transfer (..) 와 .send (..)를 호출할수가 있으나, 그냥 address에는 사용할 수 없다. 낮은 수준의 호출인
call() 은 value가 있더라도 address와 address payable에 모두 사용할 수 있으나 이는 권장 사항이 아니다.
주의사항 : payable modifier는 외부 계약의 호출에만 적용된다. 만약 동일한 계약의 payable 함수에서 non-
payable 함수를 호출하면 msg.value가 설정되어 있어도 non-payable 함수는 실패하지 않는다.
함수와 상태 변수의 가시성을 명시한다.
함수 및 상태 변수의 가시성을 명시적으로 지정한다. 기능은 external, public, internal, private 으로 지정할
수 있다. 그들 사이의 차이점을 이해해야 하는데, 예를 들면, public이 아닌 external로 충분할 수 있다. 상태
변수의 경우 external은 불가능하다. 가시성을 명시적으로 지정하면 함수를 호출하거나 변수에 액세스 하는
사용자가 잘못 접근하는 경우를 쉽게 찾을 수 있다.
● External 함수는 계약 인터페이스의 일부이다. 외부 함수 f 를 내부적으로 호출 할 수 없다. 즉 f ()가 작
동하지 않지만 this.f ()가 작동한다. 외부 함수는 때로는 대용량 데이터 배열을 수신 할 때 더 효율적이
다.
● Public 함수는 계약 인터페이스의 일부이며 내부적으로 또는 메시지를 통해 호출 할 수 있다. public
상태 변수의 경우 자동 getter 함수가 생성된다.
● Internal 함수 및 상태 변수는 this를 사용하지 않고 내부적으로만 액세스 할 수 있다.
● Private 함수와 상태 변수는 정의된 계약에서만 볼 수 있고 파생된 계약에서는 보이지 않는다. 참고 :
Contract 내부에있는 모든 내용은 그 변수가 Private라 할지라도 누구나 블록체인을 통해 전부 볼 수
있다.
함수와 상태 변수의 가시성을 명시한다.
// bad
uint x; // the default is internal for state variables, but it should be made explicit
function buy() { // the default is public
// public code
}
// good
uint private y;
function buy() external {
// only callable externally or using this.buy()
}
function utility() public {
// callable externally, as well as internally: changing this code requires thinking about both cases.
}
function internalAction() internal {
// internal code
}
특정 컴파일러 버전으로 pragma 설정
계약서는 가장 많이 테스트된 것과 동일한 컴파일러 버전과 플래그를 사용하여 배포해야 한다. pragma 를
고정하면 계약이 실수로 최신 컴파일러를 사용하여 배포되는 걸 막을 수 있는데, 최신 컴파일러는 발견되지
않은 버그의 잠재적 위험이 높기 때문이다. 계약은 다른 사람이 배포 할 수도 있으나 pragma는 원본 작성자
가 의도한 컴파일러 버전을 나타내준다.
참고 : ^로 시작되는 floating pragma 버전 (예 : ^0.4.25)은 0.4.26-nightly.2018.9.25 로 컴파일이 잘 되지만
nightly 빌드는 프로덕션을 위한 코드를 컴파일하는 데 사용해서는 안된다.
경고: 프라그마 진술은 도서관이나 EthPM 패키지 계약의 경우와 같이 다른 개발자가 계약을 맺을 의도가있
을 때 떠 다니게 할 수 있다. 그렇지 않으면 개발자는 로컬에서 컴파일하기 위해 pragma를 수동으로 업데이
트 해야 한다.
// bad
pragma solidity ^0.4.4;
// good
pragma solidity 0.4.4;
Contract 활동 모니터링에 Event를 사용한다.
계약이 디플로이된 후에 계약 활동을 모니터하는 것은 매우 유용하다. 이를 위한 방법으로 계약의 모든 트
랜잭션을 살펴 보는 것이 있지만 계약 간의 메시지 호출은 블록 체인에 기록되지 않으므로 충분하지 않다.
또한 입력 매개변수만 표시하며 실제 변경 사항은 상태에 적용되지 않는다. 또한 이벤트를 사용하여 사용자
인터페이스의 기능을 트리거 할 수 있다.
contract Charity {
mapping(address => uint) balances;
function donate() payable public {
balances[msg.sender] += msg.value;
}
}
contract Game {
function buyCoins() payable public {
// 5% goes to charity
charity.donate.value(msg.value / 20)();
}
}
Contract 활동 모니터링에 Event를 사용한다.
여기서 Game 계약은 Charity.donate ()에 내부적으로 호출을 한다. 이 거래는 Charity의 외부 트랜잭션 목록
에는 나타나지 않으며, 내부 트랜잭션에서만 볼 수 있다.
이벤트는 contract 에서 발생한 일을 기록하는 편리한 방법이다. 발생된 이벤트는 다른 contract 데이터와
함께 블록 체인에 남아 있으며 향후 감사를 위해 사용할 수 있다. 아래 코드는 이벤트를 통해 Charity의 기부
히스토리를 제공하는 것을 보여준다.
Contract 활동 모니터링에 Event를 사용한다.
contract Charity {
// define event
event LogDonate(uint _amount);
mapping(address => uint) balances;
function donate() payable public {
balances[msg.sender] += msg.value;
// emit event
emit LogDonate(msg.value);
}
}
contract Game {
function buyCoins() payable public {
// 5% goes to charity
charity.donate.value(msg.value / 20)();
}
}
여기서 Charity 계약을 직접 또는 간접적으로 거치는 모든 트랜잭션은, 기부 금액과 함께 해당 계약의 이벤
트 목록에 표시된다.
'Built-ins'은 덮어쓸 수 있다.
현재 Solidity에서 기본제공 되는 전역 변수 및 함수들은 덮어쓸 수 있다. 이를 통해 계약은 msg 및 revert()
같은 내장 함수의 기능을 대체 할 수 있다. 이것은 의도 된 것이지만 계약의 실제 행동과 관련하여 계약 사
용자를 헷갈리게 할 수 있다.
계약 사용자 (및 감사자)는 자신이 사용하려는 프로그램의 모든 계약 소스코드를 파악하고 있어야 한다.
contract PretendingToRevert {
function revert() internal constant {}
}
contract ExampleContract is PretendingToRevert {
function somethingBad() public {
revert();
}
}
tx.origin 은 사용하지 말라.
다른 계약서에서 나의 계약을 호출 할때 tx.origin을 권한용으로 사용하면 안된다. 이럴경우 나의 주소가
tx.origin 인 경우 계약에서 해당 거래를 승인하게 된다.
contract MyContract {
address owner;
function MyContract() public {
owner = msg.sender;
}
function sendTo(address receiver, uint amount) public {
require(tx.origin == owner);
receiver.transfer(amount);
}
}
경고: 권한 문제와 더불어 향후에 tx.origin은 이더리움 프로토콜에서 제거 될 가능성이 있으므로 tx.origin을
사용하는 코드는 향후 릴리스와 호환되지 않는다. 비탈릭은 “tx.origin을 앞으로 계속 사용할 수 있거나 의미
가 있다고 생각하지 말라”고 언급했다.
tx.origin 은 사용하지 말라.
권환확인에는 msg.sender를 사용해야 한다. 다른 계약에서 이 계약을 호출하는 경우 msg.sender가 계약의
주소이며 계약을 호출한 사용자의 주소는 아니다.
또한 tx.origin을 사용하면 계약 간의 상호 운용성이 제한되는데, tx.origin을 사용하는 계약은 tx.origin가 되
지 않으면, 다른 계약에서 사용할 수 없기 때문이다.
contract AttackingContract {
MyContract myContract;
address attacker;
function AttackingContract(address myContractAddress) public {
myContract = MyContract(myContractAddress);
attacker = msg.sender;
}
function() public {
myContract.sendTo(attacker, msg.sender.balance);
}
}
타임 스탬프 의존성
계약에서 중요한 기능을 실행하기 위해 타임 스탬프를 사용할 때, 특히 작업에 자금 이체가 포함될 때 고려
해야 할 세 가지 중요한 사항이 있다.
타임 스탬프 조작
블록의 타임 스탬프는 채굴자가 조작 할 수 있다. 아래 계약을 확인한다.
uint256 constant private salt = block.timestamp;
function random(uint Max) constant private returns (uint256 result){
//get the best seed for randomness
uint256 x = salt * 100/Max;
uint256 y = salt * block.number/(salt % 5) ;
uint256 seed = block.number/3 + (salt % 300) + Last_Payout + y;
uint256 h = uint256(block.blockhash(seed));
return uint256((h / x)) % Max + 1; //random number between 1 and Max
}
타임 스탬프 의존성
계약에 타임 스탬프를 사용하여 임의의 숫자를 입력하면 채굴자는 블록 유효 기간의 15 초 내에 타임 스탬
프를 기록할 수 있으므로 채굴자가 추첨에서 자신의 기회에 더 유리한 옵션을 미리 계산할 수 있다. 타임 스
탬프는 랜덤이 아니므로 해당 상황에서 사용하면 안된다.
타임 스탬프 의존성
15초 룰
이더리움 황서 (이더리움 레퍼런스 스펙)는 블록이 어느 정도의 시간간격으로 떨어질 수 있는지는 제한하
고 있지는 않지만 각 타임스탬프는 부모 블록의 타임스탬프보다 커야 함을 지정하고 있다. 널리 사용되는
이더리움 프로토콜 구현체인 Geth와 Parity는 15 초 이상 떨어져 있는 타임 스탬프가 있는 블록은 거부한다.
노트: 시간에 의존성 있는 이벤트의 스케일이 15초마다 달라질 수 있고 무결성을 유지한다면
block.timestamp를 사용해도 안전하다.
block.number를 타임 스탬프로 사용하지 말라.
block.number 속성과 평균 블록 시간을 사용하여 시간 델타를 추정하는 것이 가능하지만, 블록 시간이 변경
될 수 있으므로 미래의 증거가 될수는 없다. (예 : 포크 재구성와 난이도 폭탄). 연속된 일정기간 내에서는
15초 규칙으로 안정적인 시간 추정을 할 수 있다.
다중 상속주의
Solidity에서 다중 상속을 사용할 때는 컴파일러가 상속 그래프를 어떻게 만드는지 이해할 필요가 있다.
contract Final {
uint public a;
function Final(uint f) public {
a = f;
}
}
contract B is Final {
int public fee;
function B(uint f) Final(f) public {
}
function setFee() public {
fee = 3;
}
}
다중 상속주의
계약이 배포되면, 컴파일러는 상속을 오른쪽에서 왼쪽으로 선형화한다. 계약 A의 선형화는 다음과 같다.
Final <- B <- C <- A
contract C is Final {
int public fee;
function C(uint f) Final(f) public {
}
function setFee() public {
fee = 5;
}
}
contract A is B, C {
function A() public B(3) C(5) {
setFee();
}
}
다중 상속주의
C가 가장 많이 파생 된 계약이므로 선형화의 결과로 fee 값은 5가 된다. 이것은 명백해 보일지 모르지만, C
가 중요한 기능을 숨기고 부울 절을 재정렬하고 개발자가 악용 가능한 계약을 작성하게하는 시나리오를 상
상해보라. 정적 분석은 현재 Shadow가 있는 함수의 문제를 제기하지 않으므로 수동으로 검사해야만 한다.
타입안전을 위해 주소 대신 인터페이스 사용
함수가 계약 주소를 파라미터로 사용하는 경우 address가 아닌 interface나 contract 타입을 전달하는 것이
좋다. 함수가 소스 코드 내의 다른 곳에서 호출되면 컴파일러는 추가 타입안전을 보장한다.
contract Validator {
function validate(uint) external returns(bool);
}
contract TypeSafeAuction {
// good
function validateBet(Validator _validator, uint _value) internal returns(bool) {
bool valid = _validator.validate(_value);
return valid;
}
}
contract TypeUnsafeAuction {
// bad
function validateBet(address _addr, uint _value) internal returns(bool) {
Validator validator = Validator(_addr);
bool valid = validator.validate(_value);
return valid;
}
}
타입안전을 위해 주소 대신 인터페이스 사용
위에서 TypeSafeAuction 계약을 사용하면 얻을 수 있는 이점을 다음 예제에서 볼 수 있다. validateBet ()이
Address 인수로 호출되거나 Validator 이외의 계약 유형으로 호출되면 컴파일러에서 오류를 발생한다.
contract NonValidator{}
contract Auction is TypeSafeAuction {
NonValidator nonValidator;
function bet(uint _value) {
bool valid = validateBet(nonValidator, _value); // TypeError: Invalid type for argument
}
}
EOA 체크를 위해 extcodesize를 사용하지 말라.
다음 modifier는 외부 소유 계정 (EOA) 또는 contract 계정에서 호출이 이루어 졌는지 확인하는 데 자주 사
용된다.
// bad
modifier isNotContract(address _a) {
uint size;
assembly {
size := extcodesize(_a)
}
require(size == 0);
_;
}
EOA 체크를 위해 extcodesize를 사용하지 말라.
아이디어는 간단하다. 주소에 코드가 포함되어 있으면 EOA가 아니라 계약 계정이다. 그러나 계약은 생성
중에는 소스 코드를 가지고 있지 않다. 즉, 생성자가 실행 중일 때 다른 계약을 호출 할 수 있지만 해당 주소
에 대한 extcodesize는 0을 반환한다. 아래는 이 방법이 어떻게 우회 될 수 있는지 보여주는 간단한 예시다.
contract OnlyForEOA {
uint public flag;
// bad
modifier isNotContract(address _a){
uint len;
assembly { len := extcodesize(_a) }
require(len == 0);
_;
}
function setFlag(uint i) public isNotContract(msg.sender){
flag = i;
}
}
계약 주소는 사전에 계산될 수 있기 때문에 블록 n에서 비어 있다면 이 방법도 실패할 수 있지만 n보다 큰
블록에서 배포된 계약을 가지고 있을 수 있다.
경고: 이건 미묘한 문제다. 만약 목표가 다른 계약이 나의 계약을 호출할 수 없게 하려는 경우, extcodesize
로도 충분하다. 다른 방법으로는 단점은 있지만 (tx.origin == msg.sender)의 값을 확인하는 것이다.
extcodesize 검사가 목적을 충족시키는 다른 상황이 있을 수 있다. 그것들을 여기서 모두를 논하는 것은 이
범위를 벗어난다. 기본적으로 EVM의 기본 동작을 이해하고 판단하는 것이 필요하다.
EOA 체크를 위해 extcodesize를 사용하지 말라.
contract FakeEOA {
constructor(address _a) public {
OnlyForEOA c = OnlyForEOA(_a);
c.setFlag(1);
}
}
사용되지 않는 오래된 권고사항
이는 프로토콜 변경이나 기능향상으로 인해 더 이상 적합하지 않은 권장 사항이며 확인차 기록한다.
0으로 나누기주의 (Solidity <0.4)
버전 0.4 이전에는 Solidity가 0을 반환하고 숫자를 0으로 나눌 때 예외가 발생하지 않는다. 버전 0.4 이상을
실행하고 있는지 확인한다.
함수와 이벤트 구분(Solidity <0.4.21)
함수와 이벤트가 혼동될 위험을 방지하기 위해 이벤트는 대문자로 시작한다. 함수의 경우 항상 생성자를 제
외하고 소문자로 시작한다.
참고자료
● https://github.com/ConsenSys/smart-contract-best-practices
● https://ethereum.stackexchange.com/
● https://solidity.readthedocs.io/en/latest/security-considerations.html
협업
지앤클라우드는 클라우드 서비스 및 블록체인 개발회사로서 다음과 같은 서비스를
제공합니다.
1. Smart Contract 보안감사 및 리포트 제공
2. Smart Contract 개발
3. Dapp 개발 (Smart Contract + Front-End + Back-End)
4. 기타 블록체인 개발 컨설팅
hello@gncloud.kr
02-508-1151
서울시 서초구 신반포로45길 18 주일빌딩 502호
https://gncloud.kr

More Related Content

What's hot

What's hot (20)

Understanding blockchain
Understanding blockchainUnderstanding blockchain
Understanding blockchain
 
Ibm blockchain - Hyperledger 15.02.18
Ibm blockchain - Hyperledger 15.02.18Ibm blockchain - Hyperledger 15.02.18
Ibm blockchain - Hyperledger 15.02.18
 
[AWSKRUG] Lambda로컬테스트부터 배포까지의 여정-박태성
[AWSKRUG] Lambda로컬테스트부터 배포까지의 여정-박태성[AWSKRUG] Lambda로컬테스트부터 배포까지의 여정-박태성
[AWSKRUG] Lambda로컬테스트부터 배포까지의 여정-박태성
 
Istio Service Mesh for Developers and Platform Engineers
Istio Service Mesh for Developers and Platform EngineersIstio Service Mesh for Developers and Platform Engineers
Istio Service Mesh for Developers and Platform Engineers
 
Secure Credential Management with CredHub - DaShaun Carter & Sharath Sahadevan
Secure Credential Management with CredHub - DaShaun Carter & Sharath Sahadevan Secure Credential Management with CredHub - DaShaun Carter & Sharath Sahadevan
Secure Credential Management with CredHub - DaShaun Carter & Sharath Sahadevan
 
IPFS introduction
IPFS introductionIPFS introduction
IPFS introduction
 
JS authentication with auth0
JS authentication with auth0JS authentication with auth0
JS authentication with auth0
 
Building Event Driven Architectures with Kafka and Cloud Events (Dan Rosanova...
Building Event Driven Architectures with Kafka and Cloud Events (Dan Rosanova...Building Event Driven Architectures with Kafka and Cloud Events (Dan Rosanova...
Building Event Driven Architectures with Kafka and Cloud Events (Dan Rosanova...
 
AATC - Gamifying DevOps with Lego and Chocolate Game
AATC - Gamifying DevOps with Lego and Chocolate GameAATC - Gamifying DevOps with Lego and Chocolate Game
AATC - Gamifying DevOps with Lego and Chocolate Game
 
Journey to the Cloud with Red Hat
Journey to the Cloud with Red HatJourney to the Cloud with Red Hat
Journey to the Cloud with Red Hat
 
Types of interfaces in a Cisco Router
Types of interfaces in a Cisco RouterTypes of interfaces in a Cisco Router
Types of interfaces in a Cisco Router
 
InterPlanetary File System (IPFS)
InterPlanetary File System (IPFS)InterPlanetary File System (IPFS)
InterPlanetary File System (IPFS)
 
Blockchain Fundamentals for Beginners - 101 Blockchains
Blockchain Fundamentals for Beginners - 101 BlockchainsBlockchain Fundamentals for Beginners - 101 Blockchains
Blockchain Fundamentals for Beginners - 101 Blockchains
 
SRE Conference 2022 - How to Build a Healthy On-Call Culture
SRE Conference 2022 - How to Build a Healthy On-Call CultureSRE Conference 2022 - How to Build a Healthy On-Call Culture
SRE Conference 2022 - How to Build a Healthy On-Call Culture
 
Introduction to Blockchain and Smart Contracts
Introduction to Blockchain and Smart ContractsIntroduction to Blockchain and Smart Contracts
Introduction to Blockchain and Smart Contracts
 
Introduction to Self-Sovereign Identity
Introduction to Self-Sovereign IdentityIntroduction to Self-Sovereign Identity
Introduction to Self-Sovereign Identity
 
Crypto currencies The internet of Money
Crypto currencies The internet of MoneyCrypto currencies The internet of Money
Crypto currencies The internet of Money
 
EVOLVE'13 | Keynote | Roy Fielding
EVOLVE'13 | Keynote | Roy FieldingEVOLVE'13 | Keynote | Roy Fielding
EVOLVE'13 | Keynote | Roy Fielding
 
What is a blockchain?
What is a blockchain?What is a blockchain?
What is a blockchain?
 
Scaling Ethereum using Zero-Knowledge Proofs
Scaling Ethereum using Zero-Knowledge ProofsScaling Ethereum using Zero-Knowledge Proofs
Scaling Ethereum using Zero-Knowledge Proofs
 

Similar to 이더리움 스마트계약 보안지침 가이드 2. 솔리디티 권고안

Effective c++(chapter 5,6)
Effective c++(chapter 5,6)Effective c++(chapter 5,6)
Effective c++(chapter 5,6)
문익 장
 
Modern C++ 프로그래머를 위한 CPP11/14 핵심
Modern C++ 프로그래머를 위한 CPP11/14 핵심Modern C++ 프로그래머를 위한 CPP11/14 핵심
Modern C++ 프로그래머를 위한 CPP11/14 핵심
흥배 최
 
NDC 2011, 네트워크 비동기 통신, 합의점의 길목에서
NDC 2011, 네트워크 비동기 통신, 합의점의 길목에서NDC 2011, 네트워크 비동기 통신, 합의점의 길목에서
NDC 2011, 네트워크 비동기 통신, 합의점의 길목에서
tcaesvk
 
중급 소켓프로그래밍
중급 소켓프로그래밍중급 소켓프로그래밍
중급 소켓프로그래밍
quxn6
 

Similar to 이더리움 스마트계약 보안지침 가이드 2. 솔리디티 권고안 (14)

Blockchain Study(5) - Smart Contract(스마트 계약)
Blockchain Study(5) - Smart Contract(스마트 계약)Blockchain Study(5) - Smart Contract(스마트 계약)
Blockchain Study(5) - Smart Contract(스마트 계약)
 
Blockchain 3rd smart contract programming
Blockchain 3rd smart contract programmingBlockchain 3rd smart contract programming
Blockchain 3rd smart contract programming
 
Ethereum 스마트 컨트랙트 보안
Ethereum 스마트 컨트랙트 보안Ethereum 스마트 컨트랙트 보안
Ethereum 스마트 컨트랙트 보안
 
Blockchain 4th dapp programming
Blockchain 4th dapp programmingBlockchain 4th dapp programming
Blockchain 4th dapp programming
 
Ethereum Basics Part1
Ethereum Basics Part1Ethereum Basics Part1
Ethereum Basics Part1
 
Effective c++(chapter 5,6)
Effective c++(chapter 5,6)Effective c++(chapter 5,6)
Effective c++(chapter 5,6)
 
세션5. web3.js와 Node.js 를 사용한 dApp 개발
세션5. web3.js와 Node.js 를 사용한 dApp 개발세션5. web3.js와 Node.js 를 사용한 dApp 개발
세션5. web3.js와 Node.js 를 사용한 dApp 개발
 
신규 트레이더를 위한 MCS 이용 가이드
신규 트레이더를 위한 MCS 이용 가이드신규 트레이더를 위한 MCS 이용 가이드
신규 트레이더를 위한 MCS 이용 가이드
 
코어 이더리움
코어 이더리움 코어 이더리움
코어 이더리움
 
Bitcoin satoshi kor_chewers
Bitcoin satoshi kor_chewersBitcoin satoshi kor_chewers
Bitcoin satoshi kor_chewers
 
세션1. block chain as a platform
세션1. block chain as a platform세션1. block chain as a platform
세션1. block chain as a platform
 
Modern C++ 프로그래머를 위한 CPP11/14 핵심
Modern C++ 프로그래머를 위한 CPP11/14 핵심Modern C++ 프로그래머를 위한 CPP11/14 핵심
Modern C++ 프로그래머를 위한 CPP11/14 핵심
 
NDC 2011, 네트워크 비동기 통신, 합의점의 길목에서
NDC 2011, 네트워크 비동기 통신, 합의점의 길목에서NDC 2011, 네트워크 비동기 통신, 합의점의 길목에서
NDC 2011, 네트워크 비동기 통신, 합의점의 길목에서
 
중급 소켓프로그래밍
중급 소켓프로그래밍중급 소켓프로그래밍
중급 소켓프로그래밍
 

More from 상욱 송

SaaS Flatform metering and billing
SaaS Flatform metering and billingSaaS Flatform metering and billing
SaaS Flatform metering and billing
상욱 송
 
Realtimestream and realtime fastcatsearch
Realtimestream and realtime fastcatsearchRealtimestream and realtime fastcatsearch
Realtimestream and realtime fastcatsearch
상욱 송
 
네이버 지식쇼핑과 아마존의 검색결과 페이지네비게이션 유형분석
네이버 지식쇼핑과 아마존의 검색결과 페이지네비게이션 유형분석네이버 지식쇼핑과 아마존의 검색결과 페이지네비게이션 유형분석
네이버 지식쇼핑과 아마존의 검색결과 페이지네비게이션 유형분석
상욱 송
 

More from 상욱 송 (17)

클레이튼 BApp 서비스 현황
클레이튼 BApp 서비스 현황클레이튼 BApp 서비스 현황
클레이튼 BApp 서비스 현황
 
쿠버네티스의 이해 #2
쿠버네티스의 이해 #2쿠버네티스의 이해 #2
쿠버네티스의 이해 #2
 
쿠버네티스의 이해 #1
쿠버네티스의 이해 #1쿠버네티스의 이해 #1
쿠버네티스의 이해 #1
 
Go 언어 성공사례 및 강점
Go 언어 성공사례 및 강점Go 언어 성공사례 및 강점
Go 언어 성공사례 및 강점
 
Java 어플리케이션 성능튜닝 Part3
Java 어플리케이션 성능튜닝 Part3Java 어플리케이션 성능튜닝 Part3
Java 어플리케이션 성능튜닝 Part3
 
Java 어플리케이션 성능튜닝 Part1
Java 어플리케이션 성능튜닝 Part1Java 어플리케이션 성능튜닝 Part1
Java 어플리케이션 성능튜닝 Part1
 
Java 어플리케이션 성능튜닝 Part2
Java 어플리케이션 성능튜닝 Part2Java 어플리케이션 성능튜닝 Part2
Java 어플리케이션 성능튜닝 Part2
 
Fastcat 검색구축사례
Fastcat 검색구축사례Fastcat 검색구축사례
Fastcat 검색구축사례
 
가상화폐 개념 및 거래 기초개발
가상화폐 개념 및 거래 기초개발가상화폐 개념 및 거래 기초개발
가상화폐 개념 및 거래 기초개발
 
클라우드 서비스운영 플랫폼 가루다
클라우드 서비스운영 플랫폼 가루다클라우드 서비스운영 플랫폼 가루다
클라우드 서비스운영 플랫폼 가루다
 
범용 PaaS 플랫폼 mesos(mesosphere)
범용 PaaS 플랫폼 mesos(mesosphere)범용 PaaS 플랫폼 mesos(mesosphere)
범용 PaaS 플랫폼 mesos(mesosphere)
 
빌링:미터링 Bss platform구현
빌링:미터링 Bss platform구현빌링:미터링 Bss platform구현
빌링:미터링 Bss platform구현
 
Let's Go (golang)
Let's Go (golang)Let's Go (golang)
Let's Go (golang)
 
SaaS Flatform metering and billing
SaaS Flatform metering and billingSaaS Flatform metering and billing
SaaS Flatform metering and billing
 
Realtimestream and realtime fastcatsearch
Realtimestream and realtime fastcatsearchRealtimestream and realtime fastcatsearch
Realtimestream and realtime fastcatsearch
 
Realtime search engine concept
Realtime search engine conceptRealtime search engine concept
Realtime search engine concept
 
네이버 지식쇼핑과 아마존의 검색결과 페이지네비게이션 유형분석
네이버 지식쇼핑과 아마존의 검색결과 페이지네비게이션 유형분석네이버 지식쇼핑과 아마존의 검색결과 페이지네비게이션 유형분석
네이버 지식쇼핑과 아마존의 검색결과 페이지네비게이션 유형분석
 

이더리움 스마트계약 보안지침 가이드 2. 솔리디티 권고안

  • 1. 이더리움 스마트계약 보안지침 가이드 2. 솔리디티 권고안 (주)지앤클라우드 송상욱
  • 2. 여기서는 스마트계약을 작성할때 일반적으로 따라야 하는 몇가지 패턴들을 보여줍니다. 개요
  • 4. 외부호출 (1) 외부호출을 할때 주의하라. 신뢰할 수없는 계약서를 요청하면 예상치 못한 위험이나 오류가 발생할 수 있다. 외부 호출은 그 계약 또는 그 계약에 의존하는 다른 계약에서 악의적인 코드를 실행할 수 있다. 따라서 모든 외부 호출은 잠재적인 보 안 위험으로 간주되어야 한다. 외부 호출을 제거 할 수 없거나 바람직하지 않은 경우에는, 이 단원의 나머지 부분에있는 권장 사항을 사용하여 위험을 최소화한다.
  • 5. 외부호출 (2) 신뢰할수 없는 계약을 표시하라. 외부 계약과 상호 작용할 때 변수, 방법 및 계약 인터페이스의 이름을 이들과 상호 작용하는 것이 잠재적으 로 위험할 수 있다. 이것은 외부 계약을 호출하는 함수에 적용된다. // bad Bank.withdraw(100); // Unclear whether trusted or untrusted function makeWithdrawal(uint amount) { // Isn't clear that this function is potentially unsafe Bank.withdraw(amount); } // good UntrustedBank.withdraw(100); // untrusted external call TrustedBank.withdraw(100); // external but trusted bank contract maintained by XYZ Corp function makeUntrustedWithdrawal(uint amount) { UntrustedBank.withdraw(amount); }
  • 6. 외부호출 (3) 외부 호출 후에는 상태변경을 피한다. Raw 호출 사용 (someAddress.call() 방식) 또는 컨트랙트 호출 (ExternalContract.someMethod() 방식) 을 사용할때 악성 코드가 실행될 수 있다고 가정한다. 비록 ExternalContract 계약이 악성코드가 아니라 할지 라도 그 코드가 호출하는 어떤 계약에 의해서도 악성 코드는 실행될 수 있다. 한가지 특별한 위험은 악성 코드가 제어 흐름을 방해하여 재진입으로 인한 취약성으로 이어질 수 있다는 것 이다. 신뢰할 수 없는 외부 계약을 호출하는 경우 호출 후의 상태 변경을 피하십시오. 이 패턴은 때로는 체크 효과 상호 작용 패턴(checks-effects-interactions pattern) 으로 알려져 있다.
  • 7. 외부호출 (4) send(), transfer(), call.value()() 사이의 트레이드 오프관계를 인식한다. 이더리움을 전송할때는 send(), transfer(), call.value()() 사이의 트레이드 오프관계를 인식해야 한다. ● someAddress.send() 와 someAddress.transfer() 는 재진입 문제에 대해 안전하다. 이러한 방법은 여 전히 코드 실행을 유발하지만, 이른바 계약은 현재 이벤트를 기록하기에 충분할 정도로 2,300개의 가 스만 공급 받는다. ● x.transfer(y) 는 require(x.send(y)) 과 같으며 전송에 실패하면 자동으로 복구(revert) 된다. ● someAddress.call.value(y)() 는 이더리움을 전송하고 코드 실행을 트리거한다. 실행된 코드에는 사용 할 수 있는 모든 가스가 제공되므로 이러한 유형의 값 전송은 재진입에 대해 안전하지 않다.
  • 8. send () 또는 transfer ()를 사용하면 재진입을 방지 할 수 있지만 폴백함수가 2,300 개가 넘는 가스를 요구 하는 계약과 호환되지 않는 비용으로 처리한다. 또한 가스의 양을 지정하려면 someAddress.call.value(ethAmount) .gas(gasAmount)() 와 같이 사용해도 된다. 이 트레이드 오프의 균형을 맞출수 있는 한가지 패턴은 Push 컴포넌트에서는 send() 와 transfer()를 사용하 고, Pull 컴포넌트에서는 call.value()()를 사용하는 것이다. 값 전송을 위해 send () 또는 transfer ()를 독점적으로 사용한다고해서 계약을 재진입에 대한 안전하게 만들 지는 않는다. 하지만 해당 값의 전송은 재진입에 대해 안전하게 만들어준다. 외부호출 (4) 이어서
  • 9. 외부 호출의 오류 처리 솔리디티는 raw 주소에서 동작하는 낮은 수준의 호출방법을 제공한다. address.call(), address.callcode(), address.delegatecall(), address.send() 이 낮은 수준의 방법들은 예외를 던지지 않지만, 예외상황 발생시 False값을 리턴한다. 반면에, 컨트랙트 호출(예: ExternalContract.doSomething() )는 자동으로 예외를 전 파는데 예를들어, ExternalContract.doSomething() 는 doSomething()에서 예외가 발생하면, 예외를 던지게 된다. 만약 로우 레벨 호출방법을 사용하도록 선택한 경우 반환 값을 확인하여 호출에 실패할 경우를 처리해야 한 다. 외부호출 (5)
  • 10. 외부호출 (5) 이어서 // bad someAddress.send(55); someAddress.call.value(55)(); // this is doubly dangerous, as it will forward all remaining gas and doesn't check for result someAddress.call.value(100)(bytes4(sha3("deposit()"))); // if deposit throws an exception, the raw call() will only return false and transaction will NOT be reverted // good if(!someAddress.send(55)) { // handle failure code } ExternalContract(someAddress).deposit.value(100);
  • 11. 외부 호출에 대한 Push Over Pull 외부 통화가 실수로 또는 의도적으로 실패 할 수 있다. 이러한 실패로 인한 피해를 최소화하려면 각 외부 호 출을 호출 수신자가 시작할 수있는 자체 트랜잭션으로 격리하는 것이 좋다. 이것은 특히 지급과 관련이 있 다. 사용자가 자동으로 자금을 밀어 넣는 대신 사용자가 자금을 인출 할 수 있도록하는 것이 더 좋으며 이렇 게 하면 “가스 제한 문제”의 발생을 줄일 수 있다. 단일 트랜잭션에서 여러 send () 호출을 결합하지 말라. 외부호출 (6)
  • 12. 외부호출 (6) 이어서 // 틀린예 contract auction { address highestBidder; uint highestBid; function bid() payable { require(msg.value >= highestBid); if (highestBidder != address(0)) { highestBidder.transfer(highestBid); // if this call consistently fails, no one else can bid } highestBidder = msg.sender; highestBid = msg.value; } }
  • 13. 외부호출 (6) 이어서 // 좋은예 contract auction { address highestBidder; uint highestBid; mapping(address => uint) refunds; function bid() payable external { require(msg.value >= highestBid); if (highestBidder != address(0)) { refunds[highestBidder] += highestBid; // record the refund that this user can claim } highestBidder = msg.sender; highestBid = msg.value; } function withdrawRefund() external { uint refund = refunds[msg.sender]; refunds[msg.sender] = 0; msg.sender.transfer(refund); } }
  • 14. 신뢰할수 없는 코드에 delegatecall을 하지 말라. delegatecall 함수는 호출자 계약에 속한 것처럼 다른 계약의 함수를 호출하는 데 사용된다. 따라서 피 호출 자는 발신 주소의 상태를 변경할 수 있으며 이것은 안전하지 않을 수 있다. 아래 예제는 delegatecall을 사용 하면 계약이 파기되고 잔고가 없어질 수 있음을 보여준다. 배포 된 Destructor 계약의 주소를 인수로 사용하여 Worker.doWork ()를 호출하면 Worker 계약이 자체 파 괴된다. 신뢰할 수있는 계약에만 실행 위임하며 사용자가 제공 한 주소에는 절대 위임하지 않도록 한다. 외부호출 (7)
  • 15. 외부호출 (7) 이어서 contract Destructor { function doWork() external { selfdestruct(0); } } contract Worker { function doWork(address _internalWorker) public { // unsafe _internalWorker.delegatecall(bytes4(keccak256("doWork()"))); } }
  • 16. 이더는 계좌에 강제로 보내 질 수 있다. 엄격하게 계약의 잔고를 체크하는 코드를 주의한다. 공격자는 임의의 계정에 강제로 이더를 보낼 수 있으며, 이를 막을 수는 없다. (revert ()를 수행하는 폴백 (fallback) 기능조차도) 공격자는 계약을 생성하고 1 wei로 자금을 조달하고 selfdestruct (victimAddress)를 호출하여 이를 수행 할 수 있다. victimAddress에서 코드가 호출되지 않으므로 이를 막을 수 없다.
  • 17. 블록체인 데이터는 공개되어 있다. 많은 응용 프로그램은 제출 된 데이터가 작동하기 위해 어느 시점까지 비공개로 있어야 한다. 게임 (예 : 가 위바위보)과 경매 메커니즘이 예시에 해당하는 두 가지 주요 카테고리이다. 개인 정보가 문제가 되는 응용 프로그램을 만드는 경우 사용자가 정보를 너무 일찍 게시하지 않도록 하라. 가장 좋은 전략은 커밋 계획을 별도의 단계로 사용하는 것이다. 먼저 값의 해시를 사용하고 나중에 단계에서 값을 나타낸다. ● 가위바위보 게임에서 두 플레이어가 먼저 의도한 동작의 해시를 제출하도록 요구 한 다음, 양 플레이 어가 자신의 동작을 제출한다. 제출 된 동작이 해시와 일치하지 않으면 이를 버린다. ● 경매에서 플레이어는 초기 단계에서 입찰가 값의 해시를 제출하고 (입찰 값보다 큰 입금액과 함께) 두 번째 단계에서 경매 입찰가를 제출해야 한다. ● 난수 생성기에 따라 응용 프로그램을 개발할 때 순서는 항상 (1) 플레이어가 이동을 제출하고, (2) 생 성 된 난수를 계산하고, (3) 플레이어가 지불해야 한다. 난수 생성 방법은 그 자체로 활발한 연구 영역 이다. 현재 동급 최강의 솔루션으로는 Bitcoin 블록 헤더 (http://btcrelay.org를 통해 검증 됨), hash- commit-reveal 방식 (한 당사자가 숫자를 생성하고 해당 해시를 값에 "커밋"하도록 게시하며 나중에 값을 공개) 그리고 RANDAO가 있다. Ethereum은 결정론적 프로토콜이기 때문에 프로토콜 내의 변수 를 예측할 수 없는 난수로 사용할 수 없다. 또한 채굴자가 block.blockhash () 값 *을 어느 정도 제어하 고 있음을 알아 두어야 한다.
  • 18. "오프라인 상태"로 전환하여 복귀하지 않을 가능성 특정 조치를 수행하는 특정 당사자에 따라 자금을 환원하지 않고 환불 또는 청구 프로세스를 수행하지 말라. 예를 들어, 가위바위보 게임에서 한 가지 공통적인 실수는 두 플레이어가 자신의 동작을 제출할 때까지 지 불금을 지불하지 않는 것이다. 그러나 악의적인 플레이어는 자신의 동작을 단순히 제출하지 않음으로써 상 대방을 힘들게 할 수 있다. 실제로 다른 플레이어가 공개한 동작을 보고 자신이 진 것을 알게되면 자신의 동 작을 제출할 이유가 전혀 없다. 그러한 상황이 문제가 된다면, (1) 제한 시간을 통해 참여자를 움직이게 하 는 방법을 제공하고, (2) 참여자에게는 추가적인 경제적 인센티브를 주어, 자신이 있는 모든 상황에서 정보 를 제출하도록 유도한다.
  • 19. 음의 정수의 부정을 주의한다. Solidity는 부호있는 정수로 작업 할 수 있는 몇 가지 유형을 제공한다. 대부분의 프로그래밍 언어와 마찬가 지로 Solidity에서는 N 비트의 부호있는 정수는 -2 ^ (N-1)에서 2 ^ (N-1) -1까지의 값을 나타낼 수 있다. 즉, MIN_INT에는 양수 값이 없다. 부정(Negation) 은 숫자 2의 보수를 찾는 것으로 구현되므로 가장 큰 숫자의 부정은 같은 숫자가 된다. 이러한 속성은 Solidity (int8, int16, ..., int256)의 모든 부호있는 정수 유형에 해당된다. 이를 처리하는 한 가지 방법은 부정 이전에 변수의 값을 확인하고 MIN_INT와 같으면 던져 넣는 것이다. 또 다른 옵션은 용량이 더 큰 유형을 사용하여 가장 큰 숫자에 도달하지 않도록 하는 것이다. (예 : int16 대신 int32) MIN_INT가 -1로 나누거나 곱해질 때 int 타입과 비슷한 문제가 발생한다.
  • 20. 음의 정수의 부정을 주의한다. contract Negation { function negate8(int8 _i) public pure returns(int8) { return -_i; } function negate16(int16 _i) public pure returns(int16) { return -_i; } int8 public a = negate8(-128); // -128 int16 public b = negate16(-128); // 128 int16 public c = negate16(-32768); // -32768 }
  • 21. 솔리디티 권고안 다음 권장 사항은 Solidity에만 적용되지만 다른 언어로 된 스마트 계약 개발에 도움이 될수도 있다.
  • 22. 불변값은 Assert()를 강제한다. 불변의 속성이 변경되는 경우와 같은 assert 이 실패하면 assert 가드가 트리거 된다. 예를 들어, 토큰 발행 계약에서 토큰 대 이더 발행 비율은 고정시킬 수 있다. 이러한 케이스는 항상 assert ()로 입증할 수 있다. Assert 가드는 종종 계약 일시 중지나 업그레이드 허용과 같은 다른 기술과 결합되어야 한다. (그렇지 않으 면 Assert가 항상 실패해서 진행이 막힐수가 있다.) contract Token { mapping(address => uint) public balanceOf; uint public totalSupply; function deposit() public payable { balanceOf[msg.sender] += msg.value; totalSupply += msg.value; assert(this.balance >= totalSupply); } }
  • 23. assert (), require (), revert ()를 올바르게 사용한다. 편의 함수 assert와 require는 조건을 검사하고 조건이 충족되지 않으면 예외를 throw하는 데 사용할 수 있 다. ● assert 함수는 내부 오류를 테스트하고 불변성을 검사하는 데에만 사용해야 한다. ● require 함수는 입력 또는 계약 상태 변수와 같은 유효한 조건을 확인하거나 외부 계약에 대한 호출에 서 반환 값의 유효성을 검사하는 데 사용해야 한다. 이 패러다임을 따르면 분석 도구를 통해 유효하지 않은 opcode에 도달 할 수 없음을 확인할 수 있다. 즉, 코 드의 불변 변수가 위반되지 않고 코드가 정식으로 검증됨을 의미한다.
  • 24. assert (), require (), revert ()를 올바르게 사용한다. pragma solidity ^0.5.0; contract Sharer { function sendHalf(address payable addr) public payable returns (uint balance) { require(msg.value % 2 == 0, "Even value required."); //Require() can have an optional message string uint balanceBeforeTransfer = address(this).balance; addr.transfer(msg.value / 2); // Since transfer throws an exception on failure and // cannot call back here, there should be no way for us to // still have half of the money. assert(address(this).balance == balanceBeforeTransfer - msg.value / 2); // used for internal error checking return address(this).balance; } }
  • 25. modifier는 assertion 용도로만 사용한다. modifier 내부의 코드는 일반적으로 함수 본문 전에 실행되므로 상태 변경이나 외부 호출이 Checks- Effects- Interactions 패턴을 위반하게 된다. 또한 수정 자에 대한 코드가 함수 선언과 멀리 떨어져있을 수 있기 때문에 이러한 문장은 개발자가 알아 채지 못할 수도 있다. 예를 들어, modifier에 외부 호출을 하면 재 진입 공격이 발생할 수 있다. 아래 코드와 같은 레지스트리 계약은 isVoter () 내부에서 Election.vote ()를 호출하여 재진입 공격을 할 수 있다. modifier를 사용하면 isOwner(), require 또는 revert 와 같은 여러 함수에서 중복 조건 검사를 대체할 수 있 다. 이렇게 하면 스마트 계약 코드가 읽기 쉽고 감사하기 쉬워진다.
  • 26. modifier는 assertion 용도로만 사용한다. contract Registry { address owner; function isVoter(address _addr) external returns(bool) { // Code } } contract Election { Registry registry; modifier isEligible(address _addr) { require(registry.isVoter(_addr)); _; } function vote() isEligible(msg.sender) public { // Code } }
  • 27. 정수 나누기에 주의한다. 모든 정수 나누기는 가장 가까운 정수로 내림한다. 더 많은 정밀도가 필요하면 승수를 사용해서 값을 크게 하거나 분자와 분모를 모두 저장하는 것이 좋다. // bad uint x = 5 / 2; // Result is 2, all integer divison rounds DOWN to the nearest integer 승수를 사용하면 반올림을 방지 할 수 있다.하지만 나중에 x값을 사용할때 곱해진 승수를 고려해야 한다. // good uint multiplier = 10; uint x = (5 * multiplier) / 2; 분자와 분모를 저장한다는 것은 분자 / 분모 오프 체인 (off-chain)의 결과를 계산할 수 있음을 의미한다. // good uint numerator = 5; uint denominator = 2;
  • 28. abstract와 interface는 트레이드오프다. interface와 abstract 는 모두 스마트 계약을 위한 커스터마이징과 재사용이 가능한 접근 방식을 제공한다. Solidity 0.4.11에서 소개 된 인터페이스는 추상인 계약과 유사하지만 함수를 가질 수는 없다. 인터페이스는 스토리지에 액세스 할 수 없고 다른 인터페이스를 상속할수 없다는 제약이 있어, 일반적으로 abstract contract 가 좀더 실용적으로 보인다. 하지만 인터페이스는 구현하기 전에 contract를 설계하는 데 확실히 유용하다. 또한 abstract 계약을 상속하는 경우 구현되지 않은 모든 기능을 구현해야 하며 그렇지 않다면 그 계약도 abstract contract 가 된다.
  • 29. 폴백(Fallback) 함수 폴백함수는 심플하게 유지한다. Fallback 함수는 계약서에 인수가 없는 메시지가 전달되거나 함수가 일치하지 않을 때 호출되며 .send () 또 는 .transfer ()에서 호출 될 때 2,300의 가스만 소모할 수 있다. .send () 또는 .transfer ()에서 이더를 수신 할 수 있게 하려면 fallback 함수에서 할 수 있는 일은 이벤트를 기록하는 것이다. 더 많은 가스를 계산해야하는 경우 그에 적합한 함수를 사용한다. // bad function() payable { balances[msg.sender] += msg.value; } // good function deposit() payable external { balances[msg.sender] += msg.value; } function() payable { require(msg.data.length == 0); emit LogDepositReceived(msg.sender); }
  • 30. 폴백(Fallback) 함수 폴백함수내에서 데이터 길이를 체크한다. 폴백함수는 일반 이더 전송(데이터가 없음)뿐만 아니라 다른 함수에 매칭되지 않은 경우에도 사용되므로, 수신되는 이더를 로깅할 목적으로만 폴백함수를 사용하려는 경우 데이터가 비어 있는지 확인해야 한다. 그 렇지 않으면 호출자가 계약이 잘못 사용되는지와 존재하지 않는 함수가 호출되었는지 여부를 알 수 없다. // bad function() payable { emit LogDepositReceived(msg.sender); } // good function() payable { require(msg.data.length == 0); emit LogDepositReceived(msg.sender); }
  • 31. 지불 함수 및 상태 변수를 명시한다. Solidity 0.4.0부터 이더를 받는 모든 함수는 payable 수식어를 사용해야 한다. 그렇지 않으면 msg.value> 0 인 트랜잭션이 revert 된다. (강제 실행은 제외) transfer()를 호출하려면, 변수와 함수 인수는 address payable 로 선언해야 한다. address payable에 는 .transfer (..) 와 .send (..)를 호출할수가 있으나, 그냥 address에는 사용할 수 없다. 낮은 수준의 호출인 call() 은 value가 있더라도 address와 address payable에 모두 사용할 수 있으나 이는 권장 사항이 아니다. 주의사항 : payable modifier는 외부 계약의 호출에만 적용된다. 만약 동일한 계약의 payable 함수에서 non- payable 함수를 호출하면 msg.value가 설정되어 있어도 non-payable 함수는 실패하지 않는다.
  • 32. 함수와 상태 변수의 가시성을 명시한다. 함수 및 상태 변수의 가시성을 명시적으로 지정한다. 기능은 external, public, internal, private 으로 지정할 수 있다. 그들 사이의 차이점을 이해해야 하는데, 예를 들면, public이 아닌 external로 충분할 수 있다. 상태 변수의 경우 external은 불가능하다. 가시성을 명시적으로 지정하면 함수를 호출하거나 변수에 액세스 하는 사용자가 잘못 접근하는 경우를 쉽게 찾을 수 있다. ● External 함수는 계약 인터페이스의 일부이다. 외부 함수 f 를 내부적으로 호출 할 수 없다. 즉 f ()가 작 동하지 않지만 this.f ()가 작동한다. 외부 함수는 때로는 대용량 데이터 배열을 수신 할 때 더 효율적이 다. ● Public 함수는 계약 인터페이스의 일부이며 내부적으로 또는 메시지를 통해 호출 할 수 있다. public 상태 변수의 경우 자동 getter 함수가 생성된다. ● Internal 함수 및 상태 변수는 this를 사용하지 않고 내부적으로만 액세스 할 수 있다. ● Private 함수와 상태 변수는 정의된 계약에서만 볼 수 있고 파생된 계약에서는 보이지 않는다. 참고 : Contract 내부에있는 모든 내용은 그 변수가 Private라 할지라도 누구나 블록체인을 통해 전부 볼 수 있다.
  • 33. 함수와 상태 변수의 가시성을 명시한다. // bad uint x; // the default is internal for state variables, but it should be made explicit function buy() { // the default is public // public code } // good uint private y; function buy() external { // only callable externally or using this.buy() } function utility() public { // callable externally, as well as internally: changing this code requires thinking about both cases. } function internalAction() internal { // internal code }
  • 34. 특정 컴파일러 버전으로 pragma 설정 계약서는 가장 많이 테스트된 것과 동일한 컴파일러 버전과 플래그를 사용하여 배포해야 한다. pragma 를 고정하면 계약이 실수로 최신 컴파일러를 사용하여 배포되는 걸 막을 수 있는데, 최신 컴파일러는 발견되지 않은 버그의 잠재적 위험이 높기 때문이다. 계약은 다른 사람이 배포 할 수도 있으나 pragma는 원본 작성자 가 의도한 컴파일러 버전을 나타내준다. 참고 : ^로 시작되는 floating pragma 버전 (예 : ^0.4.25)은 0.4.26-nightly.2018.9.25 로 컴파일이 잘 되지만 nightly 빌드는 프로덕션을 위한 코드를 컴파일하는 데 사용해서는 안된다. 경고: 프라그마 진술은 도서관이나 EthPM 패키지 계약의 경우와 같이 다른 개발자가 계약을 맺을 의도가있 을 때 떠 다니게 할 수 있다. 그렇지 않으면 개발자는 로컬에서 컴파일하기 위해 pragma를 수동으로 업데이 트 해야 한다. // bad pragma solidity ^0.4.4; // good pragma solidity 0.4.4;
  • 35. Contract 활동 모니터링에 Event를 사용한다. 계약이 디플로이된 후에 계약 활동을 모니터하는 것은 매우 유용하다. 이를 위한 방법으로 계약의 모든 트 랜잭션을 살펴 보는 것이 있지만 계약 간의 메시지 호출은 블록 체인에 기록되지 않으므로 충분하지 않다. 또한 입력 매개변수만 표시하며 실제 변경 사항은 상태에 적용되지 않는다. 또한 이벤트를 사용하여 사용자 인터페이스의 기능을 트리거 할 수 있다. contract Charity { mapping(address => uint) balances; function donate() payable public { balances[msg.sender] += msg.value; } } contract Game { function buyCoins() payable public { // 5% goes to charity charity.donate.value(msg.value / 20)(); } }
  • 36. Contract 활동 모니터링에 Event를 사용한다. 여기서 Game 계약은 Charity.donate ()에 내부적으로 호출을 한다. 이 거래는 Charity의 외부 트랜잭션 목록 에는 나타나지 않으며, 내부 트랜잭션에서만 볼 수 있다. 이벤트는 contract 에서 발생한 일을 기록하는 편리한 방법이다. 발생된 이벤트는 다른 contract 데이터와 함께 블록 체인에 남아 있으며 향후 감사를 위해 사용할 수 있다. 아래 코드는 이벤트를 통해 Charity의 기부 히스토리를 제공하는 것을 보여준다.
  • 37. Contract 활동 모니터링에 Event를 사용한다. contract Charity { // define event event LogDonate(uint _amount); mapping(address => uint) balances; function donate() payable public { balances[msg.sender] += msg.value; // emit event emit LogDonate(msg.value); } } contract Game { function buyCoins() payable public { // 5% goes to charity charity.donate.value(msg.value / 20)(); } } 여기서 Charity 계약을 직접 또는 간접적으로 거치는 모든 트랜잭션은, 기부 금액과 함께 해당 계약의 이벤 트 목록에 표시된다.
  • 38. 'Built-ins'은 덮어쓸 수 있다. 현재 Solidity에서 기본제공 되는 전역 변수 및 함수들은 덮어쓸 수 있다. 이를 통해 계약은 msg 및 revert() 같은 내장 함수의 기능을 대체 할 수 있다. 이것은 의도 된 것이지만 계약의 실제 행동과 관련하여 계약 사 용자를 헷갈리게 할 수 있다. 계약 사용자 (및 감사자)는 자신이 사용하려는 프로그램의 모든 계약 소스코드를 파악하고 있어야 한다. contract PretendingToRevert { function revert() internal constant {} } contract ExampleContract is PretendingToRevert { function somethingBad() public { revert(); } }
  • 39. tx.origin 은 사용하지 말라. 다른 계약서에서 나의 계약을 호출 할때 tx.origin을 권한용으로 사용하면 안된다. 이럴경우 나의 주소가 tx.origin 인 경우 계약에서 해당 거래를 승인하게 된다. contract MyContract { address owner; function MyContract() public { owner = msg.sender; } function sendTo(address receiver, uint amount) public { require(tx.origin == owner); receiver.transfer(amount); } } 경고: 권한 문제와 더불어 향후에 tx.origin은 이더리움 프로토콜에서 제거 될 가능성이 있으므로 tx.origin을 사용하는 코드는 향후 릴리스와 호환되지 않는다. 비탈릭은 “tx.origin을 앞으로 계속 사용할 수 있거나 의미 가 있다고 생각하지 말라”고 언급했다.
  • 40. tx.origin 은 사용하지 말라. 권환확인에는 msg.sender를 사용해야 한다. 다른 계약에서 이 계약을 호출하는 경우 msg.sender가 계약의 주소이며 계약을 호출한 사용자의 주소는 아니다. 또한 tx.origin을 사용하면 계약 간의 상호 운용성이 제한되는데, tx.origin을 사용하는 계약은 tx.origin가 되 지 않으면, 다른 계약에서 사용할 수 없기 때문이다. contract AttackingContract { MyContract myContract; address attacker; function AttackingContract(address myContractAddress) public { myContract = MyContract(myContractAddress); attacker = msg.sender; } function() public { myContract.sendTo(attacker, msg.sender.balance); } }
  • 41. 타임 스탬프 의존성 계약에서 중요한 기능을 실행하기 위해 타임 스탬프를 사용할 때, 특히 작업에 자금 이체가 포함될 때 고려 해야 할 세 가지 중요한 사항이 있다. 타임 스탬프 조작 블록의 타임 스탬프는 채굴자가 조작 할 수 있다. 아래 계약을 확인한다. uint256 constant private salt = block.timestamp; function random(uint Max) constant private returns (uint256 result){ //get the best seed for randomness uint256 x = salt * 100/Max; uint256 y = salt * block.number/(salt % 5) ; uint256 seed = block.number/3 + (salt % 300) + Last_Payout + y; uint256 h = uint256(block.blockhash(seed)); return uint256((h / x)) % Max + 1; //random number between 1 and Max }
  • 42. 타임 스탬프 의존성 계약에 타임 스탬프를 사용하여 임의의 숫자를 입력하면 채굴자는 블록 유효 기간의 15 초 내에 타임 스탬 프를 기록할 수 있으므로 채굴자가 추첨에서 자신의 기회에 더 유리한 옵션을 미리 계산할 수 있다. 타임 스 탬프는 랜덤이 아니므로 해당 상황에서 사용하면 안된다.
  • 43. 타임 스탬프 의존성 15초 룰 이더리움 황서 (이더리움 레퍼런스 스펙)는 블록이 어느 정도의 시간간격으로 떨어질 수 있는지는 제한하 고 있지는 않지만 각 타임스탬프는 부모 블록의 타임스탬프보다 커야 함을 지정하고 있다. 널리 사용되는 이더리움 프로토콜 구현체인 Geth와 Parity는 15 초 이상 떨어져 있는 타임 스탬프가 있는 블록은 거부한다. 노트: 시간에 의존성 있는 이벤트의 스케일이 15초마다 달라질 수 있고 무결성을 유지한다면 block.timestamp를 사용해도 안전하다. block.number를 타임 스탬프로 사용하지 말라. block.number 속성과 평균 블록 시간을 사용하여 시간 델타를 추정하는 것이 가능하지만, 블록 시간이 변경 될 수 있으므로 미래의 증거가 될수는 없다. (예 : 포크 재구성와 난이도 폭탄). 연속된 일정기간 내에서는 15초 규칙으로 안정적인 시간 추정을 할 수 있다.
  • 44. 다중 상속주의 Solidity에서 다중 상속을 사용할 때는 컴파일러가 상속 그래프를 어떻게 만드는지 이해할 필요가 있다. contract Final { uint public a; function Final(uint f) public { a = f; } } contract B is Final { int public fee; function B(uint f) Final(f) public { } function setFee() public { fee = 3; } }
  • 45. 다중 상속주의 계약이 배포되면, 컴파일러는 상속을 오른쪽에서 왼쪽으로 선형화한다. 계약 A의 선형화는 다음과 같다. Final <- B <- C <- A contract C is Final { int public fee; function C(uint f) Final(f) public { } function setFee() public { fee = 5; } } contract A is B, C { function A() public B(3) C(5) { setFee(); } }
  • 46. 다중 상속주의 C가 가장 많이 파생 된 계약이므로 선형화의 결과로 fee 값은 5가 된다. 이것은 명백해 보일지 모르지만, C 가 중요한 기능을 숨기고 부울 절을 재정렬하고 개발자가 악용 가능한 계약을 작성하게하는 시나리오를 상 상해보라. 정적 분석은 현재 Shadow가 있는 함수의 문제를 제기하지 않으므로 수동으로 검사해야만 한다.
  • 47. 타입안전을 위해 주소 대신 인터페이스 사용 함수가 계약 주소를 파라미터로 사용하는 경우 address가 아닌 interface나 contract 타입을 전달하는 것이 좋다. 함수가 소스 코드 내의 다른 곳에서 호출되면 컴파일러는 추가 타입안전을 보장한다. contract Validator { function validate(uint) external returns(bool); } contract TypeSafeAuction { // good function validateBet(Validator _validator, uint _value) internal returns(bool) { bool valid = _validator.validate(_value); return valid; } } contract TypeUnsafeAuction { // bad function validateBet(address _addr, uint _value) internal returns(bool) { Validator validator = Validator(_addr); bool valid = validator.validate(_value); return valid; } }
  • 48. 타입안전을 위해 주소 대신 인터페이스 사용 위에서 TypeSafeAuction 계약을 사용하면 얻을 수 있는 이점을 다음 예제에서 볼 수 있다. validateBet ()이 Address 인수로 호출되거나 Validator 이외의 계약 유형으로 호출되면 컴파일러에서 오류를 발생한다. contract NonValidator{} contract Auction is TypeSafeAuction { NonValidator nonValidator; function bet(uint _value) { bool valid = validateBet(nonValidator, _value); // TypeError: Invalid type for argument } }
  • 49. EOA 체크를 위해 extcodesize를 사용하지 말라. 다음 modifier는 외부 소유 계정 (EOA) 또는 contract 계정에서 호출이 이루어 졌는지 확인하는 데 자주 사 용된다. // bad modifier isNotContract(address _a) { uint size; assembly { size := extcodesize(_a) } require(size == 0); _; }
  • 50. EOA 체크를 위해 extcodesize를 사용하지 말라. 아이디어는 간단하다. 주소에 코드가 포함되어 있으면 EOA가 아니라 계약 계정이다. 그러나 계약은 생성 중에는 소스 코드를 가지고 있지 않다. 즉, 생성자가 실행 중일 때 다른 계약을 호출 할 수 있지만 해당 주소 에 대한 extcodesize는 0을 반환한다. 아래는 이 방법이 어떻게 우회 될 수 있는지 보여주는 간단한 예시다. contract OnlyForEOA { uint public flag; // bad modifier isNotContract(address _a){ uint len; assembly { len := extcodesize(_a) } require(len == 0); _; } function setFlag(uint i) public isNotContract(msg.sender){ flag = i; } }
  • 51. 계약 주소는 사전에 계산될 수 있기 때문에 블록 n에서 비어 있다면 이 방법도 실패할 수 있지만 n보다 큰 블록에서 배포된 계약을 가지고 있을 수 있다. 경고: 이건 미묘한 문제다. 만약 목표가 다른 계약이 나의 계약을 호출할 수 없게 하려는 경우, extcodesize 로도 충분하다. 다른 방법으로는 단점은 있지만 (tx.origin == msg.sender)의 값을 확인하는 것이다. extcodesize 검사가 목적을 충족시키는 다른 상황이 있을 수 있다. 그것들을 여기서 모두를 논하는 것은 이 범위를 벗어난다. 기본적으로 EVM의 기본 동작을 이해하고 판단하는 것이 필요하다. EOA 체크를 위해 extcodesize를 사용하지 말라. contract FakeEOA { constructor(address _a) public { OnlyForEOA c = OnlyForEOA(_a); c.setFlag(1); } }
  • 52. 사용되지 않는 오래된 권고사항 이는 프로토콜 변경이나 기능향상으로 인해 더 이상 적합하지 않은 권장 사항이며 확인차 기록한다. 0으로 나누기주의 (Solidity <0.4) 버전 0.4 이전에는 Solidity가 0을 반환하고 숫자를 0으로 나눌 때 예외가 발생하지 않는다. 버전 0.4 이상을 실행하고 있는지 확인한다. 함수와 이벤트 구분(Solidity <0.4.21) 함수와 이벤트가 혼동될 위험을 방지하기 위해 이벤트는 대문자로 시작한다. 함수의 경우 항상 생성자를 제 외하고 소문자로 시작한다.
  • 54. 협업 지앤클라우드는 클라우드 서비스 및 블록체인 개발회사로서 다음과 같은 서비스를 제공합니다. 1. Smart Contract 보안감사 및 리포트 제공 2. Smart Contract 개발 3. Dapp 개발 (Smart Contract + Front-End + Back-End) 4. 기타 블록체인 개발 컨설팅 hello@gncloud.kr 02-508-1151 서울시 서초구 신반포로45길 18 주일빌딩 502호 https://gncloud.kr