Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CREATE2 can re-run constructor multiple times in a selfdestructed contract #17882

Closed
eggy-eth opened this issue Oct 10, 2018 · 5 comments
Closed

Comments

@eggy-eth
Copy link

System information

Geth version: geth Version: 1.8.17-stable

Expected behaviour

Via EIP 1014:

Specifically, if nonce or code is nonzero, then the create-operation fails.

It should also be noted that SELFDESTRUCT has no immediate effect on nonce or code, thus a contract cannot be destroyed and recreated within one transaction.

Hence if you selfdestruct a contract created with the same init_code and salt via CREATE2 and then redeploy it with the same parameters it should not be able to recreate.

Actual behaviour

You can selfdestruct a contract and redeploy it.

Steps to reproduce the behaviour

Step 1: Deploy TestMe:

contract TestMe{
    
    address public Last;
    
    uint public Argument;
    
    uint public time;
    
    function Deploy(uint seed, uint arg) public returns (address) {
        Argument = arg;
        // ctr is the actual runtime bytecode of contract A 
        // compiled with 0.5.0-nightly.2018.10.9+commit.4ab2e03b.Emscripten.clang in remix
        bytes memory ctr = hex'608060405234801561001057600080fd5b50600033905080600160a060020a0316631543b3136040518163ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040160206040518083038186803b15801561006b57600080fd5b505afa15801561007f573d6000803e3d6000fd5b505050506040513d602081101561009557600080fd5b50516000908155604080517f64dac4080000000000000000000000000000000000000000000000000000000081529051600160a060020a038416926364dac408926004808201939182900301818387803b1580156100f257600080fd5b505af1158015610106573d6000803e3d6000fd5b503392505050fffe';
        address a;
        assembly{
            let pointer := ctr
            let len := mload(pointer)
            let start := add(pointer, 0x20)
            a := create2(0, start, len, seed)
        }
        Last = a;
        return (a);
    }
    
    function Go() public {
        time = now;
    }
}

contract A{
    uint public CurrVal;
    constructor() public {
        TestMe target = TestMe(msg.sender);
        CurrVal = target.Argument();
        target.Go();
        selfdestruct(msg.sender);
    }
}

Step 2) Validate that Go() has been called by calling time() in TestMe.
Step 3) Re-call Deploy() with the same seed (note: for this implementation use a different block)
Step 4) Validate that time() has been changed hence the constructor has ran twice.

@holiman
Copy link
Contributor

holiman commented Oct 10, 2018

So, a 'constructor' is built out of a piece of init_code. The init_code must return the contract-bytecode in order for any code to be deployed.
In the constructor of A, you perform a selfdestruct. Thus, it does not deploy any code, and the account is marked for deletion.

I'll use the same numbering as you and explain some quirks

  1. Testme is created, Deploy is called. It calls create2 to create a contract A.
  • In the A constructor, it invokes A.Go(), which updates the timestamp.
  • execution of constructor is aborted by a selfdestruct (so no code is deployed).
  • I am not sure if the returnvalue a here actually contains an address or not (wheter Last is 0 or an address), but for the sake of argument, let's assume it does.
  1. Validation works, Go() was called before the initcode terminated
  2. Regardless of whether the contract was created or not, the selfdestruct went into effect when the block finished. Either the contract was never created, or it has now been cleaned. A new invocation has the exact same effect as the first one.
  3. Yes, it did run again.

Quoting from the EIP:

It should also be noted that SELFDESTRUCT has no immediate effect on nonce or code, thus a contract cannot be destroyed and recreated within one transaction.

(emphasis mine) -- immediate effect means that it does not go into effect within the same transaction/block, but only after the block postprocessing where selfdestructs are handled.

@holiman holiman closed this as completed Oct 10, 2018
@holiman
Copy link
Contributor

holiman commented Oct 10, 2018

ps: I don't know what type of setup you are using, but it might be simpler to investigate if you trace the execution. If you're using the raw evm executable, you can use --json. If you're using a proper geth node on an private network, you can invoke debug.traceTransaction to get the execution trace. Then you can see exactly what each operation does, and what the returnvalues are, etc.

@eggy-eth
Copy link
Author

@holiman What confuses me here is that I thought that when constructor code is deployed the nonce of the target account is actually set to 1. If this happens, then if the constructor is ran again it should fail (and set a to 0x0).

So the question here is, if no bytecode is deployed at a contract then the nonce of the contract we are deploying to is not set to 1. (In case of a CREATE this would still increase the nonce of the contract which is trying to create this new contract)

@holiman
Copy link
Contributor

holiman commented Oct 10, 2018

So, it's totally ok not to return bytecode from the constructor, and thereby create a code-less contract with nonce 1. And then it shouldn't be possible to recreate it.

However, you're also selfdestructing the account, so it becomes erased.

@eggy-eth
Copy link
Author

Yes the latter is an assumption of mine which was wrong. I assumed that when you selfdestruct an existing contract it would not erase the nonce.

Thanks again =)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants