본문으로 바로가기

Account와 Account간에는 트랜잭션에 의해 State가 변화됩니다. EOA와 EOA또는 CA간에는 Transaction으로만 State가 변화되며, CA와 CA간에는 Internal Transaction(Message) 로만 State가 변화합니다. 

즉 다음의 그림과 같이 동작을 하게 됩니다.



EOA와 EOA간의 트랜잭션은 일반적인 이더의 전송에 해당되고, EOA와 CA간의 트랜잭션은 앞에서 만들어본 ERC20 토큰의 Contract에 이더를 전송했을때 Fallback 함수가 호출되어 토큰을 이더를 전송한 EOA로 전송하는 동작에 해당됩니다.


이번 포스팅에서는 CA와 CA간에는 어떻게 호출이 되는지를 살펴보도록 하겠습니다.

먼저 다음 코드를 remix로 입력후에 컴파일후 deploy를 해보도록 하겠습니다.


pragma solidity ^0.4.17;
contract SomeContract {
event callMeMaybeEvent(address _from);
function callMeMaybe() payable public {
callMeMaybeEvent(this);
}
}
contract ThatCallsSomeContract {
function callTheOtherContract(address _contractAddress) public {
require(_contractAddress.call(bytes4(keccak256("callMeMaybe()"))));
require(_contractAddress.delegatecall(bytes4(keccak256("callMeMaybe()"))));
SomeLib.calledSomeLibFun();
}
}
library SomeLib {
event calledSomeLib(address _from);
function calledSomeLibFun() public {
calledSomeLib(this);
}
}


Remix에 코드를 입력후 컴파일하고, Run에서 각각 SomeContract와 ThatCallsSomeContract를 Deploy하여 각각의 Contract Address를 생성하도록 합니다.

ComeContract 의 주소를 확인하여 해당 주소를 callTheOtherContract 함수의 인자로 넣고, callTheOtherContract 함수를 호출합니다.



이것은 위의 함수에서 알수 있듯이 인자로 받은 주소를 사용하여 해당 주소에 있는 callMeMaybe 함수를 호출하게 됩니다.

여기서 함수를 호출하는데 call과 delegatecall 두개의 함수가 사용됩니다.

각각 호출된 callMeMaybe 함수에서는 단순히 이벤트를 보내는 일만 수행합니다.

이처럼 특정 Contract Account의 주소를 사용하여 call/delegatecall 함수를 사용하여 해당 Contract에 메세지를 보내 함수를 호출하게 됩니다.

동일하게 함수를 호출하지만, call/delegatecall간에는 차이가 있습니다.


 callTheOtherContract 함수 호출의 결과를 한번 확인해보도록 하겠습니다.

첫번째 로그는 call을 사용하여 호출했을때의 로그이며, 호출된 함수에서의 this, 즉 주소는 SomeContract 에 해당됩니다.

두번째 로그는 delegatecall을 사용하여 호출했을때의 로그이며, 호출된 함수에서의 this, 즉 주소는 ThatCallsSomeContract 에 해당됩니다.

세번째 로그는 SomeLib를 호출하여 해당 라이브러리안의 함수를 호출했을때의 로그이고, 주소는 ThatCallsSomeContract 에 해당됩니다.



이 결과는 다음과 같이 정리를 할수 있습니다.


A Contract에서 call 을 사용하여 다른 Contract B의 Func함수를 호출하게 되면, 해당 함수를 호출하여 함수가 실행되는 시점에 Context는 함수 Func가 포함된 Context로 변경이 됩니다. 이말은 그 시점에서는 A Contract의 메모리나 데이타를 억세스할수가 없게 된다는 말입니다.


하지만, delegatecall 을 사용하여 호출을 하게되면, 다른 Contract의 함수를 호출해도 해당 함수가 호출되는 시점에도 여전히 Contract A의 context를 유지하기 때문에 Contract A의 메모리나 데이타를 억세스할수 있습니다.


라이브러리를 사용할때, 라이브러리에 있는 함수를 호출할때에는 호출하는 Contract의 데이타등을 억세스할수 있어야 하며, 이때에는 내부적으로 delegatecall을 사용하여 동작을 해야하며, 위에서 예를 들어본것처럼 라이브러리를 사용하여 호출시에 delegatecall을 사용하여 호출할때와 동일한 결과를 확인해볼수 있습니다.