[Ethereum] 컨트랙트에서 eth 송금하기

0xDave·2022년 8월 23일
0

Ethereum

목록 보기
5/112
post-thumbnail

이더리움에서 송금할 때 3가지 방법이 있다. 각각 어떤 차이점이 있는지 알아보자.

1. send()


받을주소.send(amount);

이더리움 초기에 만든 방법이다. 전송에 성공하면 true를 리턴하고 실패하면 false를 리턴한다. send의 단점은 에러를 리턴하지 않고, 고정된 gas를 소비한다는 것이다.(2300gas)


2. call{}()


그 다음에 나온 애가 call{}()이다. send와 다르게 가변적으로 gas를 소비할 수 있다는 장점이 있다. 하지만 단점 또한 명확하다. 첫째는 여전히 true,false로 성공여부를 리턴한다는 점. 둘째는 가변적으로 gas를 소비할 수 있기 때문에 재진입 공격에 취약하다는 점이다. 재진입 공격은 더 다오(The DAO) 해킹으로 유명한 공격이다.

A컨트랙트에서 익명의 B컨트랙트로 송금했을 때 송금이 완료되기 전에 B컨트랙트 내부의 있던 악의적 함수가 A컨트랙트 내부의 송금 함수를 계속해서 호출하게 한다. 결국 마지막에는 A컨트랙트 자금이 모두 없어질 때 까지 호출해서 자금이 탈취되는 공격이다.


3. transfer()


받을주소.transfer(amount);

3가지 방법 중 가장 안전하게 송금하는 방법은 transfer()였다. 고정된 gas를 소비(2300gas)하고, 실패시 에러를 리턴하기 때문이다. 하지만 2019년 이스탄불 하드포크 이후에 가스비가 오르면서 2300gas로는 충분하지 않게 되었다. 제한된 가스가 소용이 없게 되면서 transfer()도 재진입 공격에 노출되는 위험이 생겨났다.

그럼 어떤 방식으로 송금하는 것이 안전할까?

1) Checks Effects Interaction 패턴

function 내에서 원하는 기능을 실행하기 전에, 사전에 필수적으로 체크되어야 할 전제조건을 모두 확인한 후 실행이 되도록 하는 패턴을 사용한다면 비교적 안전하게 송금할 수 있다.

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

2) 송금금액 = 0 값을 주기

아래 코드는 송금을 먼저 하고나서 sender의 금액을 0으로 만든다. 만약 해커가 fallback 함수로 withdraw() 함수를 계속 호출한다면 sender의 금액을 0으로 만들기 전에 Bank의 잔고가 먼저 0이 될 것이다.

따라서 아래처럼 call 함수가 실행되기 전에 sender의 금액을 0으로 만들어주면 해커가 withdraw() 함수를 계속 호출한다고 해도 이미 sender의 금액은 0이므로 송금이 불가능하다.


출처 및 참고자료


  1. Units and Globally Available Variables
  2. How to use address.call{}() in Solidity
  3. 솔리디티 강좌 32강 - payable,msg.value, 와 이더를 보내는 3가지 함수 (send, transfer, call)
  4. 스마트 컨트랙트: Security 패턴
  5. 솔리디티 깨부수기 - Security 1-3강 재진입 공격(re-entrancy attack) 해결법 1
profile
Just BUIDL :)

0개의 댓글