관리 메뉴

멍개의 연구소

[ethereum] solidity - call, delegatecall 본문

블록체인

[ethereum] solidity - call, delegatecall

멍개. 2022. 8. 27. 16:18

solidity에서 call과 delegatecall에 대해서 다뤄보겠습니다.

우선 이번글은 solidity 0.5 버전 기준으로 합니다.

call과 delegatecall은 0.5 버전 전, 후로 사용법이 바뀌었습니다.

● 샘플코드

먼저, call과 delegatecall을 사용한 sample 코드부터 확인하겠습니다.

pragma solidity ^0.5;

contract Sample1 {

    uint public t ;

    constructor() public {
        
    }
    event L(uint a, uint b, address c);
    
    function test(uint a, uint b) public returns(uint){
        t = a + b;
        emit L(a, b, msg.sender);
        return a + b;
    }
}

contract Sample2 {
    uint public t;
    
    constructor() public {
        
    }
    
    function callTest(address contractAddr, uint to, uint value) public returns (bool, bytes memory, address) {
        (bool success, bytes memory data) = address(contractAddr).call(abi.encodeWithSignature("test(uint256,uint256)", to, value));
        
        if(!success) {
            revert();
        }
        
        //(uint a, uint[2] memory b, bytes memory c) = abi.decode(data, (uint, uint[2], bytes));

        return (success, data, contractAddr);
    }
    
    function delegatecallTest(address contractAddr, uint to, uint value) public returns (bool, bytes memory, address) {
        (bool success, bytes memory data) = address(contractAddr).delegatecall(abi.encodeWithSignature("test(uint256,uint256)", to, value));
        
        if(!success) {
            revert();
        }
        
        //(uint a, uint[2] memory b, bytes memory c) = abi.decode(data, (uint, uint[2], bytes));

        return (success, data, contractAddr);
    }
}

· 샘플코드 배포

샘플 코드를 보면 Sample1Sample2가 존재합니다. 각각 배포를 해줍니다. 순서는 상관없습니다.

remix를 이용하면 매우 편하게 배포가 가능합니다. 빠른 테스트를 위해 Javascript VM을 사용합니다.

우측 하단에서 배포된 컨트랙트를 확인할 수 있습니다.

· 배포정보 확인하기

앞의 사진에서 검정, 파랑, 빨강박스 순으로 트랜잭션 호출자, sample1 컨트랙트, sample2 컨트랙트 입니다.

트랜잭션 발생자(EOA): 0xca35b7d915458ef540ade6068dfe2f44e8fa733c
sample1 contract address(ca): 0x692a70d2e424a56d2c6c27aa97d1a86395877b3a
sample2 contract address(ca): 0xbbf289d846208c16edc8474705c748aff07732db

해당 정보는 테스트 할 때마다 다르게 배포됩니다.

· Sample1 컨트랙트 직접 호출

test 함수를 호출할 때 1과 2를 전달합니다. Sample1의 test()는 2개의 인자를 받아서 합하여 t에 저장합니다. 그리고 event를 호출하여 전달받은 2개의 인자와 해당 함수를 호출한 address를 인자로 전달합니다.

test() 호출 후 logs를 확인해보면

"args": {
    "a": "1",
    "b": "2",
    "c": "0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c",
    "length": 3
}

전달한 값과 EOA 계정이 뜨는것을 확인할 수 있습니다.

그리고 t 변수값을 확인하면 2개의 값이 더해진 3이 저장된것을 확인할 수 있습니다.

● Sample2에서 call과 delegatecall 차이 확인

앞의 방법은 매우 일반적인 방법입니다. 이제 call과 delegatecall을 이용하여 어떤 형태로 동작하는지 알아보겠습니다.

먼저, calldelegatecall은 일반적으로 사용자 계정이 호출하는 형태는 아니고 컨트랙트에 의해서 호출하도록 도와주는 기능입니다.

· call

call을 확인하기 위해서는 Sample2에 해보된 callTest()를 호출합니다.

callTest를 호출할 때 첫 번째 인자로 해당 컨트랙트가 호출할 컨트랙트 주소를 넣어줍니다. 그 이후의 인자는 호출할 컨트랙트가 전달받을 인자들입니다.

여기서는 Sample2가 Sample1의 컨트랙트에 있는 test()를 호출하게 됩니다.

"args": {
    "a": "4",
    "b": "5",
    "c": "0xbBF289D846208c16EDc8474705C748aff07732dB",
}

마찬가지로 logs 부분을 살쳐보면 a와 b는 callTest를 호출할 때 전달된 인자가 발생합니다. 그런데 c는 Sample2의 컨트랙트 주소가 표시됩니다. 그 이유는 Sample1로 배포된 컨트랙트를 Sample2가 호출했기 때문입니다.

EOA가 Sample2의 컨트랙트를 호출, Sample2는 Sample1 호출하는 구조가 됩니다. 그럼 t의 값을 확인해보겠습니다.

Sample1로 배포된 컨트랙트의 t값만 바뀌게 됩니다.

· delegatecall

delegatecall은 call이랑 다른 형태로 동작하게 됩니다.

이번에는 Sample2로 배포된 컨트랙트에서 delegatecallTest를 호출해보겠습니다. callTest와 똑같이 넣어줍니다. 첫 번째 인자는 Sample1로 배포된 컨트랙즈 주소 두 번째와 세 번째 인자는 Sample2에서 호출할 test 함수로 전달할 인자를 넣어줍니다.

이번에는 앞의 테스트와 차이점을 보기위해 다른 수치를 넣고 트랜잭션을 발생시켜 보겠습니다.

"args": {
    "a": "6",
    "b": "7",
    "c": "0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c"
}

결과를 확인해보니 c가 Sample2의 컨트랙트가 아닌 Sample2를 호출한 EOA가 표시됩니다. 그리고 Sample1과 Sample2로 배포된 컨트랙트에서 t값을 확인해보겠습니다.

Sample1로 배포된 컨트랙트의 t가 아닌 Sample2로 배포된 컨트랙트의 t가 바뀝니다.

delegatecall을 하게되면 msg.sender와 msg.value가 유지됩니다.

그리고 context가 유지되게 됩니다. context 유지란 Sample2에서 Sample1을 delegatecall을 하게되면 Sample1 컨트랙트의 데이터를 수정하는것이 아닌 Sample2의 데이터를 수정하게 됩니다. 즉, 호출 당한 컨트랙트가 호출자 메모리 접근이 가능한 것을 의미합니다.

Sample1로 배포된 컨트랙트 test()에서 t = a + b를 할 때 call을 하게되면 Sample1에 있는 t에 접근하여 값을 변경을 하고 delegatecall을 하게되면 context가 Sample2가 유지 되기 때문에 Sample2로 배포된 컨트랙트에 있는 t의 값이 변경되게 됩니다.

● call과 delegatecall 0.5에서 변경된 사항

0.5 버전부터는 호출 성공/실패 유무와 반환된 데이터를 받아올 수 있습니다.

· 0.5 버전 이후

 
(bool success, bytes memory data) = address(contractAddr).delegatecall(abi.encodeWithSignature("test(uint256,uint256)", to, value));

· 0.5 버전 이전

bool success = address(contractAddr).call(bytes4(sha3("test(uint256,uint256)")), to, value);

개인적으로 solidity에서 가장 어려운 개념이지 않나 싶다.

Comments