스마트 컨트랙트: Security 패턴

Seungwon Go
ReturnValues
Published in
9 min readSep 27, 2018

--

Checks Effects Interaction 패턴

Checks Effects Interaction 패턴은 예기치 않는 실행을 막기 위한 가장 기본적인 코딩 패턴입니다. function 내에서 원하는 기능을 실행하기 전에, 사전에 필수적으로 체크되어야 할 전제조건을 모두 확인한 후 실행이 되도록 프로그래밍 하는 패턴입니다.

아래 코드는 Checks Effects Interaction 패턴 적용 예제입니다.

function auctionEnd() public { 
// 1. Checks require(now >= auctionEnd);
require(!ended);
// 2. Effects
ended = true;
// 3. Interaction
beneficiary.transfer(highestBid);
}

아래 코드는 Checks Effects Interaction 패턴이 적용되지 않아서 re-entrancy 공격이 가능한 예제입니다.

// INSECURE
mapping (address => uint) private userBalances;

function withdrawBalance() public {
uint amountToWithdraw = userBalances[msg.sender];
if (!(msg.sender.call.value(amountToWithdraw)())) { throw; } // At this point, the caller's code is executed, and can call withdrawBalance again
userBalances[msg.sender] = 0;
}

위의 예제를 살펴보면 userBalances[msg.sender] = 0; 이 실행되기 전에 withdrawBalance() 가 재귀 호출함으로써 자신의 보유한 잔고보다 더 많은 잔고를 인출해 나갈수 있습니다.

이를 해결하기 위해서 아래의 코드 처럼, reentrancy에 대한 상태를 관리하도록 함으로써, withdrawBalance() 가 재귀 호출되는것을 막을 수 있습니다.

bool private reentrancyLock = false;modifier nonReentrancy() {
require(!reentrancyLock);
reentrancyLock = true;
_;
reentrancyLock = false;
}
mapping (address => uint) private userBalances;

function withdrawBalance() public nonReentrancy() {
uint amountToWithdraw = userBalances[msg.sender];
if (!(msg.sender.call.value(amountToWithdraw)())) { throw; } // At this point, the caller's code is executed, and can call withdrawBalance again
userBalances[msg.sender] = 0;
}

Emergency Stop 패턴

한번 블록체인에 배포된 스마트컨트랙트는 수정이 불가능합니다. 그래서 예기치 못한 버그로 인해서 큰 피해를 줄 수 가 있습니다. Emergency Stop 패턴은 이렇게 예기치 못한 버그가 발생했을때, 특정 함수들이 실행되지 않도록 제어할 수 있도록 프로그래밍 하는 패턴입니다.
여러분은 이미 Pausable 이라는 컨트랙트를 많이 봤을수도 있습니다. 하지만 아래의 코드처럼 halt 라는 좀 더 강력한 어조로 사용하는것이 더 유용합니다. 일반적으로 Pausable은 반드시 버그가 아니여도 컨트랙트 owner의 의도에 따라 잠시 중단 시킬 수 있는 기능을 제공하는것에 더 가깝습니다.

contract EmergencyStop is Owned { 
bool public contractStopped = false;
modifier haltInEmergency {
require(contractStopped)
_;
}
modifier enableInEmergency {
require(!contractStopped)
_;
}
function toggleContractStopped() public onlyOwner {
contractStopped = !contractStopped;
}
function deposit() public payable haltInEmergency {
// some code
}
function withdraw() public view enableInEmergency {
// some code
}
}

Speed Bump 패턴

자동차를 타고 가다 보면, 특정 도로에서 자동차 속도를 줄이기 위해 도로에 높게 쏟아 있는 가속방지턱을 보게 됩니다. 특히 학교, 아파트 단지 등 보행자를 보호해야 하는 곳에서 많이 볼 수 있습니다.

Speed Bump 패턴은 특정 함수를 실행하기 위해서는 의도적으로 시간을 지연시키거나 한번에 인출할 수 있는 금액에 제한을 두는 패턴입니다. 이렇게 하는 이유는 특정 함수를 다수의 사용자가 무제한적으로 호출함으로 컨트랙트에 이상이 생기는 것을 막기 위함입니다. 아래의 코드는 인출을 하기 전에 requestWithdrawal 이라는 함수를 호출하여 인출 내역을 저장한 후, 최종적으로 withdraw라는 함수를 통해 인출이 이루어지게 프로그래밍 되어 있습니다. 그런데 withdraw를 호출 했을때, requestWithdrawal을 호출하여 인출 예약을 한지 7일이 지났는지를 체크해서 7일이 지나야만 인출이 되도록 구현되어져 있습니다.

contract SpeedBump { 
struct Withdrawal {
uint amount;
uint requestedAt;
}
mapping (address => uint) private balances;
mapping (address => Withdrawal) private withdrawals;
uint constant WAIT_PERIOD = 7 days;
function deposit() public payable {
if(!(withdrawals[msg.sender].amount > 0)) {
balances[msg.sender] += msg.value;
}
}
function requestWithdrawal() public {
if (balances[msg.sender] > 0) {
uint amountToWithdraw = balances[msg.sender];
balances[msg.sender] = 0;
withdrawals[msg.sender] = Withdrawal({ amount: amountToWithdraw, requestedAt: now });
}
}
function withdraw() public {
if(withdrawals[msg.sender].amount > 0 && now > withdrawals[msg.sender].requestedAt + WAIT_PERIOD) {
uint amount = withdrawals[msg.sender].amount;
withdrawals[msg.sender].amount = 0;
msg.sender.transfer(amount);
}
}
}

Rate Limit 패턴

Rate Limit 패턴은 특정 함수를 호출할 수 있는 빈도를 제한하는 패턴입니다. 아래의 코드는 함수 f가 한번 실행이 되면 1분이 지나야만 다시 실행할 수 있도록 구현되어져 있습니다.

contract RateLimit { 
uint enabledAt = now;
modifier enabledEvery(uint t) {
if (now >= enabledAt) {
enabledAt = now + t;
_;
}
}
function f() public enabledEvery(1 minutes) {
// some code
}
}

Mutex 패턴

Mutex 패턴은 외부 컨트랙트로 부터 재귀 호출을 막기 위한 패턴입니다.

contract Mutex { 
bool locked;
modifier noReentrancy() {
require(!locked);
locked = true;
_;
locked = false;
}
// f is protected by a mutex, thus reentrant calls
// from within msg.sender.call cannot call f again
function f() noReentrancy public returns (uint) {
require(msg.sender.call());
return 1;
}
}

Balance Limit 패턴

스마트컨트랙트 내의 잔액의 최대값을 설정하여, 최대값 이상으로 컨트랙트로 금액이 보내는지는 것을 막는 패턴입니다. 아래의 코드는 LimitBalance 함수를 통해 컨트랙트에서 보유할 수 있는 최대 잔액(limit)을 설정 한 후, 보유한 잔액이 설정된 최대 잔액(limit) 보다 작을 때만 deposit 함수를 실행할 수 있도록 구현되어져 있습니다.

contract LimitBalance { 
uint256 public limit;
function LimitBalance(uint256 value) public {
limit = value;
}
modifier limitedPayable() {
require(this.balance <= limit);
_;
}
function deposit() public payable limitedPayable {
// some code
}
}

--

--

Seungwon Go
ReturnValues

Writer, Youtuber, Investor, Programmer, Founder