contract A {
B b;
function A(address _b) public {
b = B(_b);
}
function callB() public {
b.call();
}
}
contract B {
address public sender;
function call() public {
sender = msg.sender;
}
}
If you call the callB function in A contract, the sender of B contract becomes the address of contract A. On the other hand, if you call call function of B contract, sender becomes address of EOA.
call
, callcode
and delegatecall
: To interface with contracts that do not adhere to the ABI, the function call is provided which takes an arbitrary number of arguments of any type.
call
- Execute code of another contractdelegatecall
- Execute code of another contract, but with the state(storage) of the calling contract.callcode
(deprecated)
example:
address nameReg = 0x72ba7d8e73fe8eb666ea66babc8116a41bfb10e2;
nameReg.call("register", "MyName”);`
p.recipient.call.value(p.amount * 1 ether)(transactionBytecode);
This would be similar to sending a normal transaction with the web3 api. pseudo-code:
tx = {
from: (in the example, the calling contracts address)
to: (p.recipient is the address),
value: (p.amount * 1 ether),
data: (transactionByteCode),
...
}
web3.eth.sendTransaction(tx)
The delegatecall
method was a bug fix for callcode
, which did not preserve msg.sender and msg.value, so callcode is deprecated and will be removed in the future.
It is important to note, that delegatecall
involves a security-risk for the calling contract, as the called contract can access/manipulate the calling contracts storage.
There are, of course, inherent security risks with calling a function on a contract from a given address and this way of calling contracts breaks type-safety in Solidity. Therefore, call, callcode and delegatecall are supposed to be used only as a last resort.
If Alice invokes Bob who does DELEGATECALL
to Charlie, the msg.sender in the DELEGATECALL
is Alice (whereas if CALLCODE
was used the msg.sender would be Bob).
When D does CALL
on E, the code runs in the context of E: the storage of E is used.
When D does CALLCODE
on E, the code runs in the context of D. So imagine that the code of E is in D. Whenever the code writes to storage, it writes to the storage of account D, instead of E.
pragma solidity ^0.4.18;
contract D {
uint public n;
address public sender;
uint256 public amount;
function callSetN(address _e, uint _n) public {
_e.call.value(10).gas(6712353)(bytes4(keccak256("setN(uint256)")), _n);
// E's storage is set, D is not modified
}
function callcodeSetN(address _e, uint _n) public {
_e.callcode.value(10).gas(6712353)(bytes4(keccak256("setN(uint256)")), _n);
// D's storage is set, E is not modified
}
function delegatecallSetN(address _e, uint _n) public {
// TypeError: Member "value"
// _e.delegatecall.value(10).gas(250000)(bytes4(sha3("setN(uint256)")), _n);
_e.delegatecall.gas(6712353)(bytes4(keccak256("setN(uint256)")), _n);
// D's storage is set, E is not modified
}
}
contract E {
uint public n;
address public sender;
uint256 public amount;
function setN(uint _n) {
n = _n;
sender = msg.sender;
amount = msg.value;
// msg.sender is D if invoked by D's callcodeSetN. None of E's storage is updated
// msg.sender is C if invoked by C.foo(). None of E's storage is updated
// the value of "this" is D, when invoked by either D's callcodeSetN or C.foo()
}
}
contract C {
function foo(D _d, E _e, uint _n) {
_d.delegatecallSetN(_e, _n);
}
}
When D does CALLCODE
on E, msg.sender inside E is D as commented in the code above.
When an account C invokes D, and D does DELEGATECALL
on E, msg.sender inside E is C. That is, E has the same msg.sender and msg.value as D.
2017년 7월 19일에 parity 해킹 사건이 발생한다. 이 사건으로 인해 150,000 ETH가 도난당한다. 이 사건은 parity에서 만든 multisig 기능을 제공하는 contract의 잘못된 코드로 인해 발생했다.
이 contract에는 multisig의 owner들을 초기화하는 함수가 있다.
// constructor - just pass on the owner array to the multiowned and // the limit to daylimit
function initWallet(address[] _owners, uint _required, uint _daylimit) {
initDaylimit(_daylimit);
initMultiowned(_owners, _required);
}
그런데 이 함수를 delegatecall
로 누구나 initWallet
함수를 호출할 수 있다.
function() payable {
// just being sent some cash?
if (msg.value > 0)
Deposit(msg.sender, msg.value);
else if (msg.data.length > 0)
_walletLibrary.delegatecall(msg.data);
}
해커는 delegatecall
을 이용해 initWallet
함수를 호출했고 execute
함수를 호출해서 ETH를 빼갔다.
이후 parity 사에서는 코드를 수정하고 다시 배포한다.
function initWallet(address[] _owners, uint _required, uint _daylimit) only_uninitialized {
initDaylimit(_daylimit); initDaylimit(_daylimit);
initMultiowned(_owners, _required); initMultiowned(_owners, _required);
} }
수정된 코드를 보면 only_uninitialized
인 조건에서만 동작하도록 변경했다. 이말은 initWallet
함수가 한번 초기화되면 다시 호출될 수 없음을 의미한다.
그런데 아무도 contract에 초기화 함수를 호출하지 않았다. 그러던 와중 스마트 컨트랙트를 공부하는 누군가 initWallet
함수를 호출했고, 실수로 kill
함수를 호출하는 황당한 사건이 발생한다.
그래서 이 multisig contract를 사용하는 contract에 multisig에 묶여있는 모든 ETH와 token들을 사용할 수 없게 되는 문제가 발생했다.
추가로 multisig를 library로 만들지 않고 contract로 만들어서 발생한 문제도 포함하고 있다.
- 자체의
owner
를 허용한다. 제대로 선언된 라이브러리 함수는 많은 사람들이 사용하기 때문에 owner가 없어야 한다. 더 심각한 것은, owner를 가지면서 누구나 이 owner를 변경할 수 있다는 것이다. - contract의 경우 owner는
kill
기능을 통해 소유하고 있는 contract를 삭제할 수 있다.
- https://ethereum.stackexchange.com/questions/2370/what-does-p-recipient-call-valuep-amount-1-ethertransactionbytecode-do
- https://ethereum.stackexchange.com/questions/6707/whats-the-difference-between-call-value-and-call-value/17631#17631
- https://www.dasp.co/timeline.html
- https://ethereum.stackexchange.com/questions/30128/explanation-of-parity-library-suicide
- https://etherscan.io/address/0x863df6bfa4469f3ead0be8f9f2aae51c91a907b4
- https://github.com/paritytech/parity/blob/4d08e7b0aec46443bf26547b17d10cb302672835/js/src/contracts/snippets/enhanced-wallet.sol#L107
- https://github.com/paritytech/parity/pull/6102/files#diff-8ea4aa7c2ba715c683bc764337f51585
- https://medium.com/chain-cloud-company-blog/parity-multisig-hack-again-b46771eaa838
- https://blog.zeppelin.solutions/on-the-parity-wallet-multisig-hack-405a8c12e8f7
- https://ethereum.stackexchange.com/questions/3667/difference-between-call-callcode-and-delegatecall
- https://blog.zeppelin.solutions/on-the-parity-wallet-multisig-hack-405a8c12e8f7
- https://medium.com/chain-cloud-company-blog/parity-multisig-hack-again-b46771eaa838