How to SCORE #3

ICX Transfer, Exception Handling with fallback & payable

Joonyoung Choi
B!ock.Chain
15 min readApr 26, 2019

--

이번 시리즈를 이해하기 위해서는 Python 언어 및 ICON 이 제공하는 CLI 개발도구인 T-Bears 및 ICON SDK 활용법에 대한 약간의 사전지식이 필요합니다.

두번째 파트에서는 DB Abstraction 에 대해 살펴보았습니다. 혹시 아직 못보신 분이 있으시면 파트 2을 먼저 보시는 것을 권장합니다.

시리즈의 세번째 파트에서는 SCORE가 ICX를 전송 받기 위해 구현해야할 fallback 메소드와 @payable 데코레이터, ICX 전송과 예외 처리에 대해 살펴보겠습니다.

Table of Contents

Two Cases of Transferring ICX to SCORE

  • Invoking fallback method
  • Invoking external SCORE method

fallback & payable

  • payable
  • fallback

ICX Transfer & Exception Handling

  • Exception Handling
  • self.icx.transfer
  • self.icx.send

Hands-on Exercise

  • fallback & payable
  • payable with external methods
  • Exception Handling

Two Cases of Transferring ICX to SCORE

Invoking fallback method

T-Bears CLI의 transfer, ICONex의 Transfer 를 통해 SCORE로도 ICX를 전송할 수 있으며, 이러한 경우 자동적으로 SCORE의 fallback 메소드가 호출됩니다.

단순한 ICX 전송에 해당하는 JSON-RPC Request는 예시와 같은 형식으로 요청할 수 있으며, T-Bears CLI의 transfer, ICONex의 Transfer를 통해 ICX를 전송하는 경우, 이러한 형식을 갖춘 JSON-RPC Request를 전송하게 됩니다.

{
"jsonrpc": "2.0",
"method": "icx_sendTransaction",
"id": 1234,
"params": {
"version": "0x3",
"from": "hxbe258ceb872e08851f1f59694dac2558708ece11",
"to": "cxb0776ee37f5b45bfaea8cff1d8232fbb6122ec32",
"value": "0xde0b6b3a7640000",
"stepLimit": "0x12345",
"timestamp": "0x563a6cf330136",
"nid": "0x3",
"nonce": "0x1",
"signature": "VAia7YZ2Ji6igKWzjR2YsG..."
}
}

to : ICX를 전송받을 주소로, 예시로 제공된 JSON-RPC Request의 경우 SCORE로 ICX를 전송하고 있습니다.

value : 전송하는 ICX의 양으로 0xde0b6b3a7640000hex값으로 10진수로 환산하자면 10¹⁸ loop = 1 ICX 에 해당합니다.

Invoking external SCORE method

ICX를 전송하는 것은 stateDB의 상태를 변화시키는 트랜잭션을 발생시키는 것으로, 데이터를 읽어오기만 하는 external readonly 메소드를 호출하는 경우에는 ICX를 전송할 수 없습니다.

T-Bears CLI의 sendtx, ICONex Contract를 통한 external 메소드 호출과 같이 SCORE의 특정 메소드를 호출하며 ICX를 함께 전송할 수 있으며, 이러한 경우 fallback 메소드가 호출되지 않습니다.

SCORE external 메소드 호출에 해당하는 JSON-RPC Request 는 예시와 같은 형식으로 요청할 수 있으며, T-Bears CLI의 sendtx, ICONex의 Contract를 통해 external 메소드를 호출하며 ICX를 함께 전송하는 경우, 이러한 형식을 갖춘 JSON-RPC Request를 전송하게 됩니다.

{
"jsonrpc": "2.0",
"method": "icx_sendTransaction",
"id": 1234,
"params": {
"version": "0x3",
"from": "hxbe258ceb872e08851f1f59694dac2558708ece11",
"to": "cxb0776ee37f5b45bfaea8cff1d8232fbb6122ec32",
"value": "0xde0b6b3a7640000",
"stepLimit": "0x12345",
"timestamp": "0x563a6cf330136",
"nid": "0x3",
"nonce": "0x1",
"signature": "VAia7YZ2Ji6igKWzjR2YsGa2m53n...",
"dataType": "call",
"data": {
"method": "transfer",
"params": {
"to": "hxab2d8215eab14bc6bdd8bfb2c8151257032ecd8b",
"value": "0x1"
}
}
}
}

to : ICX를 전송받을 주소로, 예시로 제공된 JSON-RPC Request의 경우 SCORE로 ICX를 전송하고 있습니다.

value : 전송하는 ICX의 양으로 0xde0b6b3a7640000hex값으로 10진수로 환산하자면 10¹⁸ loop = 1 ICX 에 해당합니다.

data : 호출하고자 하는 SCORE의 메소드에 대한 정보들로, 예시의 JSON-RPC Request의 경우 tovalue를 인자로 입력받는 SCORE의 transfer 메소드를 호출하고 있습니다.

datatype : call을 통해 SCORE의 메소드를 호출할 수 있습니다.

지금까지는 EOA -> SCORE에 해당하는 ICX 전송으로 사용자가 전자서명을 통해 트랜잭션을 생성하는 경우에 대해 알아보았습니다.

이번 파트의 후반부에서 이어질 ICX Transfer & Exception Handling에서는 개인키를 가지고 있지 않은 SCORE가 ICX를 전송하는 self.icx.transferself.icx.send에 대해 살펴보도록 하겠습니다.

JSON-RPC API에 대한 자세한 정보는 링크를 참고해주세요.

fallback & payable

payable

SCORE의 메소드가 ICX를 전송받기 위해서는 반드시 @payable로 장식되어 있어야 합니다. 만약 @payable로 장식되어 있지 않은 메소드를 호출하며 ICX를 전송하는 경우, 해당 트랜잭션은 실패합니다.

    @payable
@external
def icxAcceptable(self):
pass

@payable로 장식되어 있는 icxAcceptable 메소드는 ICX 전송과 함께 호출되더라도 해당 트랜잭션이 실패하지 않습니다.

    @external
def icxUnacceptable(self):
pass

@payable로 장식되어 있지 않은 icxUnacceptable 메소드는 ICX 전송과 함께 호출되는 경우 해당 트랜잭션은 실패합니다.

fallback

fallback 메소드는 SCORE가 다른 메소드 호출 없이 단순히 ICX 만을 전송 받는 경우 실행됩니다. 따라서 fallback 메소드는 @external 로 장식되어 외부로 부터 호출되는 것을 금지하고 있습니다.

만약 fallback 메소드가 @payable로 장식되어 있지 않은 경우, 해당 SCORE는 메소드 호출과 함께 ICX를 전송하는 것이 아닌 단순한 ICX 전송을 받을 수 없습니다.

@external에 대해서는 이어지는 시리즈에서 살펴보도록 하겠습니다.

케이스 별로 SCORE 프로젝트를 생성하여 ICX 전송 결과를 확인해 봅시다.

Case 1. @payable 없이 fallback 구현

tbears init without_payable WithoutPayable

    def fallback(self):
pass

생성한 SCORE 프로젝트를 T-Bears 환경에 배포해 봅시다.

tbears deploy without_payable

tbears txresult 0xe7e323a75…

테스트 지갑을 통해 배포한 SCORE에 ICX를 전송해 봅시다.

@payable없이 fallback 메소드를 구현하는 경우 SCORE는 단순한 ICX 전송을 받을 수 없습니다.

tbears transfer -f hxe7af… cx55b65… 1e18

tbears txresult 0x0103e67…

Case 2 & 3 에 대하여서도 deploy부터 scoreAddress를 확인까지 동일한 과정을 수행합니다. 따라서 ICX 전송 결과를 제외한 부분은 생략하였습니다.

Case 2. @payable과 함께 fallback 구현

tbears init with_payable WithPayable

    @payable
def fallback(self):
pass

@payable 과 함께 fallback 메소드를 구현한 SCORE에 ICX를 전송하고 트랜잭션의 결과를 확인해 봅시다.

@payable과 함께 fallback 메소드를 구현하는 경우, 단순한 ICX 전송이 일어날 때 실행될 로직을 정의할 수 있습니다.

Case 3. fallback 미구현

tbears init no_fallback NoFallback

fallback 메소드를 구현하지 않은 SCORE에 ICX를 전송하고 트랜잭션의 결과를 확인해 봅시다.

ICX Transfer & Exception Handling

IconScoreBase, utility function 과 같이 아직까지 살펴보지 않은 부분들은 이어지는 시리즈를 통해 살펴보기로 하고, 이번 파트에서는 설명하지 않고 넘어가도록 하겠습니다.

Exception Handling

SCORE에서 발생한 Exception을 처리하는 경우 IconServiceBaseException을 상속받아 해당 Exception에 대한 처리를 하기보다는 revert 함수를 통한 Exception 처리를 권장합니다.

  • revert : IconScoreBase에서 utility function으로 제공되는 기능으로, revert 함수가 호출되는 경우 RevertException을 발생시키고, 트랜잭션에 의해 변화된 상태를 이전 상태로 복구시킵니다.
 revert(message: str)

의도하지 않은 상황이 발생하는 경우, revert 함수를 호출하는 것으로 해당 트랜잭션에 의한 상태 변화를 강제로 되돌릴 수 있습니다.

self.icx.transfer

self.icx.transfer(receiver: Address, amount: int)

tranfer메소드를 통해 amount 만큼의 ICXreceiver 주소로 전송합니다.

ICX 전송이 성공적으로 실행되는 경우 True를 반환하지만, SCORE의 balance가 충분하지 않아 ICX 전송이 실패하는 경우, Out of balance라는 메시지와 함께 InvalidParamsException을 발생시킵니다. 발생한 Exception에 대한 별도의 처리가 없다면 트랜잭션은 실패하며, 이전까지의 상태 변화를 되돌립니다.

try/except를 통해 발생하는 InvalidParamsException을 처리할 수 있습니다.

self.icx.send

self.icx.send(receiver: Address, amount: int)

send메소드를 통해 amount 만큼의 ICXreceiver 주소로 전송합니다.

ICX 전송이 성공적으로 실행되는 경우 transfer와 마찬가지로 True를 반환합니다. 하지만 SCORE의 balance가 충분하지 않아 ICX 전송이 실패하는 경우 sendInvalidParamsException을 발생시키지 않고 False를 반환합니다. send에 의한 ICX 전송이 실패하더라도 트랜잭션은 실패하지 않습니다.

if/else를 통해 ICX 전송이 실패한 경우에 대한 처리가 가능합니다.

Hands-on Exercise

지난 파트 2 에서는 샘플 코드를 작성하며 VarDB, ArrayDB, DictDB를 사용해 보았습니다. 이번에는 파트 2 에서 작성한 코드에 세번째 파트에서 살펴 본 revert, fallback, @payable를 활용하는 부분을 추가로 작성해 봅시다.

fallback & payable

간단하게 SCORE가 단순한 ICX를 전송받을 수 있도록 @payable 과 함께 별다른 로직을 수행하지 않는 fallback메소드를 구현해 봅시다.

    @payable
def fallback(self):
pass

payable with external methods

fallback이 아닌 다른 external 메소드들도 @payable로 장식되어 있는 경우 메소드 호출과 함께 ICX를 전송받을 수 있습니다.

    @payable
@external
def deposit(self):
pass

ICONex를 통해 deposit 메소드 호출과 함께 ICX를 전송해 봅시다.

ICONex로 deposit 메소드 호출 #1
ICONex로 deposit 메소드 호출 #2
ICONex로 deposit 메소드 호출 #3
ICONex를 통해 생성된 트랜잭션 조회
deposit 메소드 호출 결과 확인

status를 통해 deposit 메소드 호출과 함께 ICX를 전송하는 트랜잭션이 성공적으로 실행된 것을 확인할 수 있습니다.

Exception Handling

revert함수를 통해 10 ICX 이상의 전송은 받지 않도록 fallback메소드의 로직을 구현해 봅시다.

self.msg.valueloop 단위입니다. 1 ICX = 10¹⁸ loop

    @payable
def fallback(self):
if self.msg.value >= 10000000000000000000:
revert("ICX amount must be lower than 10")

작성한 SCORE를 배포하고, ICX를 전송한 결과를 확인하는 것으로 세번째 파트를 마치겠습니다.

SCORE 배포부터 scoreAddress를 확인하는 과정에 대한 설명은 생략합니다.

1e18 = 10¹⁸ loop -> 1 ICX 전송

1 ICX를 전송한 트랜잭션이 성공하였습니다.

1e19 = 10¹⁹ loop -> 10 ICX 전송

10 ICX를 전송한 트랜잭션이 실패하고, message가 반환되었습니다.

이것으로 How to SCORE 시리즈의 세번째 파트를 마치겠습니다.

다음 파트에서는 @external & @eventlog 데코레이터에 대해 알아보도록 하겠습니다.

ICON 과 관련된 추가적인 질문사항이 있는 경우, ICON 의 공식 개발자포럼 혹은 페이스북 그룹에 질문하시면 답변을 얻으실 수 있습니다.

아이콘 공식 개발자포럼

Dive into ICON 페이스북 그룹

ICON Developers 유튜브 채널

--

--

Joonyoung Choi
B!ock.Chain

Blockchain / Java / Python / Developer / Researcher