Call, Static Call, Delegate Call

4e5ung·2023년 6월 14일
0

Call, Static Call, Delegate Call

컨트랙트내에서 다른 컨트랙트를 호출 할 때 사용하며, 크게 3가지 유형이 있다.

아래 설명은 3가지 유형에 대한 컨트랙트 호출 순서를 EOA -> SC-A -> SC-B로 진행한다.

Call

다른 컨트랙트의 인터페이스를 이용해서 함수를 호출하거나, Call 메서드를 이용해서 함수를 호출 할 수 있다.
사용자가 SC-A 함수(내부 SC-B 함수)를 호출할 때 msg.sender의 경우 SC-A는 사용자 EOA가 되며,
SC-B의 경우 SC-A의 주소가 msg.sender로 된다.
또한 SC-A 함수를 이용하여, SC-B의 데이터 값 변경(State)이 가능하다.

CallEOASC-ASC-B
msg.senderEOASC-A
state변경

Delegate Call

사용자가 SC-A 함수(내부 SC-B 함수)를 호출할 때 msg.sender의 경우 최초 사용자 EOA로 유지가되며,
SC-A 함수(내부 SC-B 함수) 호출 시 SC-B가 아닌 SC-A의 상태가 변경이 된다.
프록시 컨트랙트를 사용할 때 많이 이용된다. (데이터 저장은 SC-A에 하고, 로직은 SC-B를 사용)

CallEOASC-ASC-B
msg.senderEOAEOA
state변경

Static Call

Call 방식과 동일하지만, 컨트랙트의 뷰 함수 호출할 때 사용되며, 상태 변경이나 저장은 허용하지 않는다.

CallEOASC-ASC-B
msg.senderEOASC-A
state불가능

예제

테스트 샘플 컨트랙트

pragma solidity ^0.8.0;

contract CallContract{
    uint256 value;

    function setValue(uint256 _value) external{
        value = _value;
    }

    function getValue() external view returns(uint256){
        return value;
    }

    function getSender() external view returns(address){
        return msg.sender;
    }
}

contract MyContract {
    uint256 value;

    function setValueByCall(address _contract, uint256 _value) external{
        CallContract(_contract).setValue(_value);
    }

    function getSenderByCall(address _contract) external view returns(address){
        return CallContract(_contract).getSender();
    }

    function setValueByDelegateCall(address _contract, uint256 _value) external{
        (bool success, ) = _contract.delegatecall(abi.encodeWithSignature("setValue(uint256)", _value));
        require(success, "setValueByDelegateCall fail");
    }

    function getSenderByDelegateCall(address _contract) external returns(address sender){
        (bool success, bytes memory data) = _contract.delegatecall(abi.encodeWithSignature("getSender()"));
        require(success, "getSenderByDelegateCall fail");
        
        assembly {
            sender := mload(add(data, 32))
        }

        return sender;
    }

    function getValueByStaticCall(address _contract ) external view returns(uint256){
        (bool success, bytes memory data) = _contract.staticcall(abi.encodeWithSignature("getValue()"));
        require(success, "getValueByStaticCall fail");
        return abi.decode(data, (uint256));
    }

    function getSenderByStaticCall(address _contract) external view returns(address sender){
        (bool success, bytes memory data) = _contract.staticcall(abi.encodeWithSignature("getSender()"));
        require(success, "getSenderByStaticCall fail");
        
        assembly {
            sender := mload(add(data, 32))
        }

        return sender;
    }


    function setValue(uint256 _value) external{
        value = _value;
    }

    function getValue() external view returns(uint256){
        return value;
    }
}

call 호출

msg.sender 확인

let sender = await myContract.getSenderByCall(callContract.address)

// mycontract address
assert.equal(sender, myContract.address)

데이터 저장 위치 확인

await myContract.setValueByCall(callContract.address, 1)

// callContract에 저장
let value = await callContract.getValue()
assert.equal(value, 1)

// myContract에 저장되지 않음
value = await myContract.getValue()
assert.notEqual(value, 1)

delegate call 호출

msg.sender 확인

let sender = await myContract.callStatic.getSenderByDelegateCall(callContract.address)

// owner address
assert.equal(sender, accounts[0].address)

데이터 저장 위치 확인

await myContract.setValueByDelegateCall(callContract.address, 1)

// callContract에 저장하지 않음
let value = await callContract.getValue()
assert.notEqual(value, 1)

// myContract에 저장
value = await myContract.getValue()
assert.equal(value, 1)

static call 호출

msg.sender 확인

let sender = await myContract.callStatic.getSenderByStaticCall(callContract.address)

// myContract address
assert.equal(sender, myContract.address)

데이터 저장 위치 확인

await callContract.setValue(1)
// call contract data 확인
let value = await myContract.getValueByStaticCall(callContract.address)
assert.equal(value, 1)

// myContract data 값 읽지 않음
await myContract.setValue(2)
value = await myContract.getValueByStaticCall(callContract.address)
assert.notEqual(value, 2)

git repository

https://github.com/4e5ung/solidity-study/tree/main/call_static_delegate

0개의 댓글