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
에 대해 알아보도록 하겠습니다.
인자값으로 함수 서명과 함수 서명의 매개변수들이 들어갑니다.
여기서 함수 서명이란 함수와 함수가 사용하는 매개변수 타입의 조합입니다.
예시를 보시면 아래와 같은 함수가 있다고 가정해보겠습니다.
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번째 매개변수 함수서명 이후부터는 들어가는 인자값을 차례대로 입력하면 됩니다.
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.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번째 인자값에는 함수를 실행하는데 필요한 모든 인자값들이 들어갑니다. ()
를 주의하셔야 할 것 같습니다.
이상으로 함수내용을 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 IDE
에 transaciton
이 남긴 이벤트 로그를 확인해봅시다.
approval
이벤트에서는 최초 3000000개의 토큰에 대한 권한을 부여해주었지만 1000000개의 토큰을 전송했으니 2000000개로 정상 출력되었고, transfer
이벤트에서 1000000개의 토큰이 A지갑에서 B에 지갑으로 보내진것을 확인할 수 있습니다.
1000000개씩 총 3000000 토큰을 모두 전송했다면 더 이상 A컨트랙트에게 위임된 토큰이 없으니 다음과 같은 가스 추정 에러가 발생하는지 확인하고 마치도록 하겠습니다.