컨트랙트내에서 다른 컨트랙트를 호출 할 때 사용하며, 크게 3가지 유형이 있다.
아래 설명은 3가지 유형에 대한 컨트랙트 호출 순서를 EOA -> SC-A -> SC-B로 진행한다.
다른 컨트랙트의 인터페이스를 이용해서 함수를 호출하거나, Call 메서드를 이용해서 함수를 호출 할 수 있다.
사용자가 SC-A 함수(내부 SC-B 함수)를 호출할 때 msg.sender의 경우 SC-A는 사용자 EOA가 되며,
SC-B의 경우 SC-A의 주소가 msg.sender로 된다.
또한 SC-A 함수를 이용하여, SC-B의 데이터 값 변경(State)이 가능하다.
Call | EOA | SC-A | SC-B |
---|---|---|---|
msg.sender | EOA | SC-A | |
state | 변경 |
사용자가 SC-A 함수(내부 SC-B 함수)를 호출할 때 msg.sender의 경우 최초 사용자 EOA로 유지가되며,
SC-A 함수(내부 SC-B 함수) 호출 시 SC-B가 아닌 SC-A의 상태가 변경이 된다.
프록시 컨트랙트를 사용할 때 많이 이용된다. (데이터 저장은 SC-A에 하고, 로직은 SC-B를 사용)
Call | EOA | SC-A | SC-B |
---|---|---|---|
msg.sender | EOA | EOA | |
state | 변경 |
Call 방식과 동일하지만, 컨트랙트의 뷰 함수 호출할 때 사용되며, 상태 변경이나 저장은 허용하지 않는다.
Call | EOA | SC-A | SC-B |
---|---|---|---|
msg.sender | EOA | SC-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;
}
}
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)
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)
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)
https://github.com/4e5ung/solidity-study/tree/main/call_static_delegate