The motivation behind this EIP in all variants is to help people whose funds were blocked in Parity's multi-signature wallets due to the destruction of the library contract holding all wallet logic. However, it is worth noticing that this problem is not the first of its kind. @mjmau noticed in the EIP 156 discussion in August that over 3500 ETH is stuck in roughly 300 smart contracts without any code. Currently, there are more than 5000. The actual number of stuck ether may be much higher if we take into account all ether which was lost due to replay attacks where "a contract was created on ETC, funds sent on ETH but the contract not created on ETH" (quote from EIP 156).
In the following sections, four potential variants of this contract revival proposal will be introduced. Some points for an initial discussion are appended to the proposal drafts.
We introduce a new builtin createat
which works precisely like create-transaction
, but it has an additional parameter, which specifies a nonce used to create a contract address.
There are two additional parameters. The parameters are prepended to the data sent to the builtin. Both of those parameters are integers aligned to 32 bytes. The first one represents a nonce
that should be used when computing the contract address. The second one is the initial_nonce
that the new, derivative contract begins on.
Instead of computing the contract address being created as in create-transaction
as:
keccak256(SENDER ++ SENDERS_NONCE) % 2**160
We compute it as :
keccak256(SENDER ++ nonce) % 2**160
The createat
call succeeds if:
- There is no code under
ADDRESS
- The
nonce
is <SENDERS_NONCE
- Any regular
create-transaction
with the same set of parameters would succeed.
The second additional parameter initial_nonce
is useful when a contract deploying another contract has been suicided and recreated. Suicided contracts have their nonces reset, so by giving them initial_nonce
, no additional logic needs to be written to make their CREATE
opcode always work.
Introduce a new builtin claim
which accepts three parameters. nonce
, initial_nonce
, and transaction_data
(same as if it was passed to create-transaction
). nonce
and initial_nonce
are integers aligned to 32 bytes.
This builtin creates two smart contracts:
-
A proxy contract at address
keccak(SENDER ++ nonce) % 2**160
. This contract is locked for500_000
blocks (to be discussed). The proxy contractnonce
is set toinitial_nonce
.pragma solidity ^0.4.19; contract Claim { // these variables are set directly in a contract source uint constant claim_block_number = block.number + 500000; address constant destination = 0x00d35100000000000000000000000000000000; function () public { if (block.number > claim_block_number) { require(destination.delegatecall(msg.data)); } } }
-
A target contract at address
keccak(SENDER ++ SENDER_NONCE) % 2**160
. Contract code is created according to the same rules as if it was created usingcreate-transaction
withdata
equaltransaction_data
.
The claim
call succeeds if:
- Call-data length is equal or longer than 64 bytes
- There is no code under
keccak(SENDER ++ nonce) % 2**160
- The
nonce
is <SENDERS_NONCE
- And regular
create-transaction
with the sametransaction_data
would succeed.
Introduce new builtin proxy
which accepts three parameters: nonce
, initial_nonce
, and destination
. The builtin works in a very similar way to the claim
builtin; the main difference, however, is the code of proxy contract.
When called, the builtin transfers all funds from address keccak(SENDER ++ nonce) % 2**160
to SENDER
and then it creates two smart contracts:
-
A proxy contract at address
keccak(SENDER ++ nonce) % 2**160
. This contract always behaves as if it was killed, until the caller of this contract agrees to it by callingsetupProxyForContract
orsetupProxyForSelf
on theProxyState
contract:pragma solidity ^0.4.19; contract Proxy { // these variables are set directly in a contract source address constant state = 0x0051413000000000000000000000000000000000; address constant destination = 0x00d35100000000000000000000000000000000; // the contract does not have payable modifier, so it cannot accept any ether function () public { ProxyState(state).isProxySetup(msg.sender).delegatecall(msg.data); } }
-
The proxy state contract at the address
keccak(SENDER ++ SENDER_NONCE) % 2**160
.pragma solidity ^0.4.19; contract ProxyState { mapping (address => bool) private proxy; function setupProxyForSelf() public { proxy[msg.sender] = true; } // please note the usage of tx.origin function setupProxyForContract(uint nonce) public { proxy[address(keccak256(msg.sender, nonce))] = true; } function isProxySetup(address a) public view returns(bool) { return proxy[a]; } }
The proxy
call succeeds if:
- Call data length is equal to 96 bytes
- There is no code under
keccak(SENDER ++ nonce) % 2**160
- The
nonce
is <SENDERS_NONCE
.
We introduce a new builtin multi-proxy
which accepts two parameters: address
and nonce
. The builtin works in a very similar way as proxy
builtin, but it allows everyone to set their destination of the proxy.
When called, the builtin transfers all funds from address(address, nonce)
to the creator of this address and then it creates two smart contracts:
-
A proxy contract at address
address(address, nonce) % 2 ** 160
. This contract always behaves as if it was killed until the caller of this contract sets up the proxy by callingsetupProxyForContract
orsetupProxyForSelf
on theProxyState
contract:pragma solidity ^0.4.19; // contract deployed at address contract Proxy { // this variable is set directly in the contract source address constant state = 0x0051413000000000000000000000000000000000; // proxy function, proxies all calls from sender function () public { ProxyState(state).getProxyAddress(msg.sender).delegatecall(msg.data); } }
-
A proxy state contract at address
keccak(SENDER ++ SENDER_NONCE) % 2**160
:pragma solidity ^0.4.19; // contract deployed at address(msg.sender, nonce(msg.sender)) contract ProxyState { mapping (address => address) private proxy; function setupProxyForSelf(address destination) public { proxy[msg.sender] = destination; } function setupProxyForContract(uint nonce, address destination) public { proxy[address(keccak256(msg.sender, nonce))] = destination; } function getProxyAddress(address a) public view returns(address) { return proxy[a]; } }
The multi-proxy
call succeeds if:
- Call-data length is equal to 32 bytes
nonce(address) >= nonce
- There is no code under
address
.
At present there are two ways a contract can alter how it handles a transaction: it can call another contract (particularly with DELEGATECALL
) with a dynamically determined address, or it can SUICIDE
. If no such operation is present in its code, then it will never change.
The same is true with these EIP drafts. The only difference is the SUICIDE
opcode:
If there is a SUICIDE
opcode, then there is a possibility that, according to the logic surrounding it, the code will have changed by the time the transaction is executed. Before the consideration of these EIP drafts, SUICIDE
would have meant the absolute loss of anything sent into its custody that required any action on its part for access, in particular ETH, but also ERC20 tokens, badges, etc. With these EIP drafts, there is a possibility to attach new code to the address, and thereby potentially claim ownership over any such assets rather than leave them in limbo forever. Strictly for the sender, the difference is negligible: they lose the asset in both cases.
Each version of the proposal has different advantages and disadvantages that will be discussed in the following paragraphs.
Pros:
- The creator of the killed smart contract at
0xX
can fix it by redeploying new smart contract at the same address. - This proposal variant introduces only a low level of complexity.
Cons:
- If the creator of the smart contract at
0xX
has malicious intentions and has the ability to kill the smart contract, he can replace it with malicious code. - Currently, the most significant risk of using contracts with
selfdestruct
is having the funds locked in contracts referencing to them. With this proposal variant, someone may additionally take control over those locked funds. - This requires a creator of the killed contract at
0xX
to send a transaction to rescue the funds. - This changes EVM semantics.
In real-world applications, the user should never trust and use a contract which can be killed by anyone but himself. Simply because, by doing this, he gives someone an ability to lock his assets. But even if those funds become locked, let's see if it's possible to reduce the risk of someone taking control of them.
Pros:
- The creator of the killed smart contract at
0xX
can fix it by redeploying new smart contract at the same address. - Even if a creator of the contract redeploys malicious code at
0xX
, for some period (e.g.,500_000
blocks or ~90 days), the contract works as if it was killed. During this time, the user can review the code himself and decide whether he wants to use it.
Cons:
- This requires a creator of the killed contract at
0xX
to send a transaction to rescue the funds. - If the contract at
0xX
is replaced by a malicious contract, funds will be still locked. - This introduces additional complexity.
- This changes EVM semantics.
Another version of this proposal (C) is also trying to reduce the risk of losing control over funds. It requires the user to agree to the new code of contract. If he doesn't accept this, the code works as if it was killed.
Pros:
- Even if a creator of a contract at
0xX
redeploys malicious code, the user has to agree to use those changes.
Cons:
- A user can setup the proxy for himself and the contracts he created, but creation does not equal ownership.
- At least two parties need to cooperate to unlock the funds from locked the contract that is: a creator of killed smart contract at
0xX
, and a creator of a contract which uses contract0xX
. - If the contract at
0xX
is replaced by a malicious contract, funds are still locked. - This introduces additional complexity.
- This changes EVM semantics.
Again, version C is addressing one problem but introducing another. Additionally, it requires the trust of the other party. Variant D is trying to simplify this version of the proposal by removing a dependency on the creator of the killed contract.
Pros:
- Anyone can create a proxy at the address of the killed contract. By default, the proxy behaves as if it was a destructed contract.
- The proxy destination can be set per user. A malicious contract creator cannot harm anyone.
Cons:
- A user can setup the proxy for himself and the contracts he created, but creation does not grant ownership.
- This introduces additional complexity.
- This changes EVM semantics.
The proposal variant D is the only proposal, where nothing depends on the creator of the killed contract. At the same time, it gives a lot of power to the creator of a contract using the killed contract. This creator may be at the same time the owner of the contract but doesn't have to be.
This proposal is both a rescue and a technical improvement to the protocol. We need to consider how much has already been lost, and how much will be lost in the future. (quote).
Feedback, critics, and suggestions very welcome. Discuss it either here on Github, on Reddit, or send a mail to community@parity.io to get in touch.
I was thinking of existing contracts impacted by this contract revival proposal: stuck omise go token on suicide multi sig.
The story :
On july, with the initWallet parity vulnerability exploitation, WHG (thanks to them again!) rescue fonds of hundred of multi sig wallets. Then WHG call suicide on those rescued wallets. OmiseGo airdrop omg token to those suicided multi sig addresses. (wrong block timing choice of the eth getBalance i suppose).
To analyse the impact of the above proposals, we can ask ourself three questions :
1- What was the human intention. An airdrop of omisego token according to the eth holder balances. The eth holder of a multi sig contract are the owners address list field.
2-What will be technically possible according to the smart contract revival and who will own those omisego token at the end :
3- Is it better than stuck fund ?
Bonus questions :
WHG create multi sig wallet to give back the money. It will be good to ask them if they want to have the power of the contract creator explained above. Also, It will be good to ask the people that have made the choice ( thanks to ChooseWHGReturnAddress.sol contract) of using the multi sig wallet created by WHG. Do they will have made another choice if they had known that, one day, creator will have that power. Other choice, like created themself the multi sig contract...