블록체인 Block-Chain - 외부 컨트랙트 호출하기 ( encoding, call )

dev_swan·2022년 12월 19일
0

블록체인

목록 보기
35/36
post-thumbnail
post-custom-banner

외부 컨트랙트를 호출?

A컨트랙트에서 B컨트랙트에 함수를 호출할 수 있을까요?
결론부터 말하면 가능합니다.

정확히는 A에서 B의 계약주소만 알고 있다면 가능합니다.

방법은 A컨트랙트에서 B의 계약주소와 call() 메소드를 사용하여 접근이 가능합니다.

이때 call 메소드의 인자값으로 bytecode가 필요한데, 이 bytecode는 실행할 함수, 인자값을 인코딩한 bytecode입니다.

그렇다면 call() 메소드를 사용하기 전에 인코딩 하는 방법을 먼저 알아보도록 하겠습니다.
Solidity에서 인코딩을 하는 방법으로는 여러가지가 있습니다.

abi.encode, abi.encodePacked, abi.encodeWithSignature 등등.. 여러가지 방법이 있지만,
오늘은 조금 생소한 abi.encodeWithSignature, abi. encodeWithSelector, abi.encodeCall에 대해 알아보도록 하겠습니다.

abi.encodeWithSignautre

인자값으로 함수 서명과 함수 서명의 매개변수들이 들어갑니다.

여기서 함수 서명이란 함수와 함수가 사용하는 매개변수 타입의 조합입니다.

예시를 보시면 아래와 같은 함수가 있다고 가정해보겠습니다.

function tranfer(address to, uint256 amount) public {
  // CODE
}

이런 간단한 transfer 함수가 있다고 하면 위의 함수의 함수서명은 아래와 같습니다.

transfer(address, uint256)

이제 encodeWithSignature로 함수 내용을 bytecode로 가져오는 함수를 만들어 보겠습니다.

function encodeWithSignature(address from, address to, uint amount) external pure returns (bytes memory) {
  return abi.encodeWithSignature("transferFrom(address,address,uint256)", from, to, amount);
}

transferFrom이란 함수의 내용을 bytecode로 가져오는 함수를 만들었습니다. 1번째 매개변수 함수서명 이후부터는 들어가는 인자값을 차례대로 입력하면 됩니다.

abi.encodeWithSelector

2번째 매개변수부터 인코딩하여 함수 선택자앞에 붙입니다.

여기서 함수 선택자란 호출할 함수를 지정하는 함수 호출 데이터의 처음 4Byte입니다. 간단하게 정리하면 EVM에서 어떤 스마트 컨트랙트를 실행한다고 하면 어떤 기능을 실행할 지 알려주는 역할입니다.

encodeWithSelector로 함수 내용을 bytecode로 가져오는 함수를 만들어 보겠습니다.

function encodeWithSelector(address from, address to, uint amount) external pure returns (bytes memory) {
	return abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, amount);
}

encodeWithSignature와 마찬가지로 1번째 매개변수 이후부터는 들어가는 인자값을 차례대로 입력하면 됩니다.

abi.encodeCall

abi.encodeWithSelector와 유사하지만 제공된 값이 호출된 함수의 타입과 일치하는지 확인합니다.
일치하지 않으면 compile에서 Error가 발생하는것 같습니다.

바로 encodeCall로 함수 내용을 bytecode로 가져오는 함수를 만들어 보겠습니다.

function encodeCall(address from, address to, uint amount) external pure returns (bytes memory) {
	return abi.encodeCall(IERC20.transferFrom, (from, to, amount));
}

주의할 점은 2번째 인자값에는 함수를 실행하는데 필요한 모든 인자값들이 들어갑니다. ()를 주의하셔야 할 것 같습니다.

Test

이상으로 함수내용을 bytecode로 만드는 것은 끝났고 다음으로는 call 메소드를 이용해서 실제로 A컨트랙트에서 B컨트랙트를 호출하여 Token을 전송해보도록 하겠습니다.

먼저 Call 메소드를 이용하여 다른 컨트랙트를 호출할 함수와 함수 내용을 인코딩하는 함수들을 만들어 배포할 컨트랙트를 작성해보도록 하겠습니다.

작성후 저는 Remix IDE를 사용하여 Goerli 테스트넷에 배포하여 테스트 해보겠습니다.

먼저 함수 내용을 bytecode로 가져와보도록 하겠습니다.

저는 만들어둔 encodeCall, encodeWithSelector, encodeWithSignature 세가지 함수를 이용하여 A지갑에서 B지갑으로 토큰을 1000000개 전송하는 transferFrom 함수의 bytecode를 가져왔습니다. 자세히 보시면 모든 bytecode가 동일한 것을 알 수 있습니다.

bytecode를 가져왔다면 transferFrom 함수를 테스트할 것이니 Token이 있어야겠네요.
저는 테스트할 때 사용하려고 발행한 토큰이 있으니 이걸 가져와서 테스트할 것입니다.

Remix IDE에서는 배포한 컴파일된 컨트랙트와 배포된 컨트랙트 주소만 있다면 언제든 다시 가져올 수 있습니다.

자세한 내용은 아래 링크를 참고해주세요.
[Remix] 이전 스마트컨트랙트 불러오기

자 이제 토큰 계약을 불러왔다면 위임을 해줘야합니다.
Call 메소드를 사용하여 A컨트랙트에서 B컨트랙트의 함수를 호출하는 순간 함수의 실행자는 A계약주소가 됩니다.
따라서 transferFrom에 들어가는 from 지갑 주소에서 A계약주소가 내 토큰을 관리할 수 있도록 권한 위임을 해줘야하는것 입니다.

그럼 토큰 컨트랙트에 있는 approve 함수를 이용해서 A컨트랙트에게 From 지갑주소의 토큰을 사용할 수 있도록 권한을 부여해 봅시다.

저는 총 3000000개의 토큰에 대한 권한을 부여해 주도록 하겠습니다.

권한을 위임하는 함수를 호출하면 메타마스크에서 친절하게 다음 계약주소가 귀하의 자금에 액세스할 수 있다고 알려줍니다.

권한을 부여했다면 확인 한 번 해보고 넘어가도록 하겠습니다. 저는 위임한 토큰 수량이 정확히 나와있습니다.

자 이제 모든 준비가 완료되었습니다. A컨트랙트에서 B컨트랙트를 호출하여 From 지갑에서 To 지갑으로 토큰을 전송해보도록 하겠습니다.

작성한 test 함수에 토큰 계약주소와 인코딩을 통해 얻은 bytecode 코드를 입력해주고 transaction을 보내보겠습니다.

Transaction이 정상적으로 블록에 들어갔다면 Remix IDEtransaciton이 남긴 이벤트 로그를 확인해봅시다.

approval 이벤트에서는 최초 3000000개의 토큰에 대한 권한을 부여해주었지만 1000000개의 토큰을 전송했으니 2000000개로 정상 출력되었고, transfer 이벤트에서 1000000개의 토큰이 A지갑에서 B에 지갑으로 보내진것을 확인할 수 있습니다.

1000000개씩 총 3000000 토큰을 모두 전송했다면 더 이상 A컨트랙트에게 위임된 토큰이 없으니 다음과 같은 가스 추정 에러가 발생하는지 확인하고 마치도록 하겠습니다.

post-custom-banner

0개의 댓글