Block-Chain/Solidity

Send Ethereum

WebDevLee 2023. 4. 4. 17:12

코인 이더리움을 송신하는 방법 및 관련 개념에 대해 정리하였습니다.

 

 


< 이더를 보내는 함수 >

코인 이더리움을 보내는 3가지 방법은 send, transfer, call이 있습니다.

키워드 Payable은 코인과 상호작용(송금)시 필요한 키워드입니다. 주로 함수, 주소, 생성자에 붙여 사용됩니다.

msg.value는 송금보낸 코인의 값입니다.

 

  • send : 2300 gas를 소비. 성공여부를 true 또는 false로 리턴
  • transfer : 2300 gas를 소비. 실패시 에러를 발생
  • call : 가변적인 gas 소비(gas값 지정 가능). 성공여부를 true 또는 false로 리턴

 

Ex)

event howMuch(uint256 _value);

function sendNow(address payable _to) public payable {
  bool sent = _to.send(msg.value); // return true or false
  require(sent, "failed to send ethereum");
  emit howMuch(msg.value);
}

function transferNow(address payable _to) public payable {
  _to.transfer(msg.value);
  emit howMuch(msg.value);
}

function callNow(address payable _to) public payable {
  // ~0.7
  bool sent = _to.call.gas(1000).value(msg.value)("");
  require(sent, "failed to send ethereum");
  
  // 0.7~
  bool sent = _to.call{value: msg.value, gas: 1000}("");
  require(sent, "failed to send ethereum");  
}

 

 


< balance와 msg.sender >

balance는 해당 지갑 주소의 현재 이더 잔액을 말합니다.

msg.sender는 스마트컨트랙트와 상호작용하는 지갑주소를 말합니다.

 

Ex)

event SendInfo(address _msgSender, uint256 _currentValue);
event MyCurrentValue(address _msgSender, uint256 _value);
event CurrentValueOfSomeone(address _msgSender, address _to, uint256 _value);

function sendEther(address payable _to) public payable {
  require(msg.sender.balance >= msg.value, "Your balance is not enough");
  _to.transfer(msg.value);
  emit SendInfo(msg.sender, (msg.sender).balance);
}

function checkValueNow() public {
  emit MyCurrentValue(msg.sender, msg.sender.balance);
}

function checkUserMoney(address _to) public {
  emit CurrentValueOfSomeone(msg.sender, _to, _to.balance);
}

 

 


< 생성자 payable, 함수 사용 권한 제한 >

생성자는 스마트컨트랙트 생성(첫 배포)시 작동하는데, 여기에 payable을 붙이면 이더를 스마트컨트랙트 안에 넣어줄 수 있습니다.

msg.sender를 require와 연계하여 특정 지갑주소에게만 특정 함수를 사용할 수 있도록 권한을 설정할 수 있습니다. 

 

Ex)

contract lecture34 {
  address owner;
  constructor() payable {
    owner = msg.sender;
  }
  
  modifier onlyOwner {
    require(msg.sender == owner, "Only Onwer!");
    _;
  }
  
  event SendInfo(address _msgSender, uint256 _currentValue);
  event MyCurrentValue(address _msgSender, uint256 _value);
  event CurrentValueOfSomeone(address _msgSender, address _to,uint256 _value);
  
  function sendEther(address payable _to) public onlyOwner payable {
    require(msg.sender.balance>=msg.value, "Your balance is not enough");
    _to.transfer(msg.value);    
    emit SendInfo(msg.sender,(msg.sender).balance);
  }
    
  function checkValueNow() public onlyOwner {
    emit MyCurrentValue(msg.sender, msg.sender.balance);
  }
    
  function checkUserMoney(address _to) public onlyOwner {
    emit CurrentValueOfSomeone(msg.sender,_to ,_to.balance);
  }
}

 

 


< fallback / receive >

fallback는 말 그대로 대비책 함수입니다. 무기명 함수이고, 다음과 같은 이유로 사용합니다.

 

fallback의 사용 이유 3가지

  • 스마트 컨트랙이 이더를 받을 수 있게 한다. => payable
  • 이더를 받고난 후 어떠한 행동을 취하게 할 수 있다. => payable
  • call함수로 없는 함수가 불려질 때, 어떠한 행동을 취하게 할 수 있다. => external

 

< 0.6 이전, 이후 fallback >
이전 :
fallback() external payable {
  
}


이후 : receive와 fallback 두가지 형태로 나뉨.
- receive : 순수하게 이더만 받을때 작동
- fallback : 함수를 실행하며 이더를 보낼때, 또는 불려진 함수가 없을 때 작동합니다.

receive() external payable {

}

fallback() external payable {

}

 

 


< call >

call 함수는 코인 송금 외에도, 외부 스마트컨트랙트의 함수에 접근할 수 있습니다. 

 

Ex)

contract add {
  event JustFallback(string _str);
  event JustReceive(string _str);
  
  function addNumber(uint256 _num1, uint256 _num2) public pure returns(uint256) {
    return _num1 + _num2;
  }
  fallback() external {
    emit JustFallback("JustFallback is called");
  }
  receive() external payable {
    emit JustReceive("JustReceive is called");
  }
}

contract caller {
  event calledFunction(bool _success, bytes _output);
  
  // call을 이용한 송금
  function transferEther(address payable _to) public payable {
    (bool success, ) = _to.call{ value:msg.value }("");
    require(success, "failed to transfer ether");
  }
  
  // call을 이용한 외부 스마트 컨트랙트 함수 부르기
  function  callMethod(address _contractAddr, uint256 _num1, uint256 _num2) public {
    (bool success, bytes memory outputFromCalledFunction) = _contractADdr.call(
      abi.encodeWithSignature("addNumber2(uint256, uint256)", _num1, _num2)
    );
    require(success, "failed to call other function");
    emit callMethod(success, outputFromCalledFunction);
  }
}

 

 


< call vs delegate call >

delegate call은 스마트컨트랙트 B의 함수들을 스마트 컨트랙트 A에 옮겨놓는 것처럼 행동하는 것을 말합니다.
쉽게 말하자면, 스마트 컨트랙 A는 껍데기이고, 스마트 컨트랙 B는 주요 함수들을 갖고 있는 핵심이라 할 수 있습니다. 

 

조건 : A스마트컨트랙트와 B스마트컨트랙트는 같은 변수를 갖고 있어야 한다.

 

Ex: num 변수 값 변경


< Call >

1. 스마트컨트랙트 A의 msg.sender는 Alice의 주소

2. 스마트컨트랙트 B의 msg.sender는 스마트 컨트랙트 A의 주소

3. 스마트 컨트랙트 B의 num은 5로 변경되고, num=5라는 것은 스마트 컨트랙트 B에 저장된다.

 


< Delegate Call >

1. 스마트컨트랙트 B의 msg.sender는 스마트 컨트랙트 A의 주소가 아니라, Alice의 주소

2. 스마트컨트랙트 B의 함수가 불려서 num 값이 5가 되었지만, 정작 num=5라는 것은 스마트컨트랙트 A에 저장되어있고, 스마트컨트랙트 B의 num값은 그대로 3이다.

 

contract add {
  uint256 public num = 0;
  event Info(address _addr, uint256 _num);
  
  function plusOne() public {
    num = num + 1;
    emit Info(msg.sender, num);
  }
}

contract caller {
  uint256 public num = 0;
  
  function callNow(address _contractAddr) public payable {
    (bool success, ) = _contractAddr.call(abi.encodeWithSignature("plusOne()"));
    require(success, "failed to transfer ether");
  }
  function delcateCallNow(address _contractAddr) public payable {
    (bool success, ) = _contractAddr.delegatecall(abi.encodeWithSignature("plusOne()"));
    require(success, "failed to transfer ether");
  }
}

 


< delegate call이 필요한 이유 >
num을 5로 변경하는 함수가, 10으로 변경하는 로직으로 수정해야 할 때

=>
스마트컨트랙트 A는 껍데기니까, 스마트컨트랙트 B의 주요 로직을 변경해서 새로운 스마트컨트랙트 B'를 배포한다.
그리고 스마트컨트랙트 A의 delegate call 주소를 B -> B'로 변경하면 완료!

이를 upgradable smart contract framework 라고 합니다.