The only defense against deploying two contracts to one address is ContractInfo::new's is_contract(address) check at storage.rs, which reads AccountInfoOf, and a contract is deliberately not written into AccountInfoOf until its constructor frame pops (see exec.rs, "we do not store on instantiate"), so an attacker who deploys a factory D that runs CREATE2(salt=S, code=C), lets the resulting contract B1's constructor (written by the attacker) re enter D (reentrancy is on by default, allows_reentry = true at exec.rs), and has D call the same nonce independent CREATE2(salt=S, code=C) a second time will find the collision check still sees address X as empty and constructs a second contract B2 at X, which, because inc_consumers and increment_refcount run once per constructor frame (exec.rs and exec.rs) but terminate decrements only once, permanently leaks a consumer (the account can never be reaped and its ED is locked forever), permanently leaks the code refcount (the blob can never be pruned), and orphans B2's child trie with its storage deposit locked forever once B1 overwrites AccountInfoOf[X] on pop, all from a single transaction fully controlled by the attacker and in direct violation of EIP-684 (which sets the target nonce to 1 at the start of construction precisely to block this).
Fix: make the collision check nonce based (EIP-684), or track in construction addresses on the frame stack and reject a nested instantiate targeting an address already present as a Constructor frame.
The only defense against deploying two contracts to one address is
ContractInfo::new'sis_contract(address)check at storage.rs, which readsAccountInfoOf, and a contract is deliberately not written intoAccountInfoOfuntil its constructor frame pops (see exec.rs, "we do not store on instantiate"), so an attacker who deploys a factoryDthat runsCREATE2(salt=S, code=C), lets the resulting contractB1's constructor (written by the attacker) re enterD(reentrancy is on by default,allows_reentry = trueat exec.rs), and hasDcall the same nonce independentCREATE2(salt=S, code=C)a second time will find the collision check still sees addressXas empty and constructs a second contractB2atX, which, becauseinc_consumersandincrement_refcountrun once per constructor frame (exec.rs and exec.rs) butterminatedecrements only once, permanently leaks a consumer (the account can never be reaped and its ED is locked forever), permanently leaks the code refcount (the blob can never be pruned), and orphansB2's child trie with its storage deposit locked forever onceB1overwritesAccountInfoOf[X]on pop, all from a single transaction fully controlled by the attacker and in direct violation of EIP-684 (which sets the target nonce to 1 at the start of construction precisely to block this).Fix: make the collision check nonce based (EIP-684), or track in construction addresses on the frame stack and reject a nested instantiate targeting an address already present as a
Constructorframe.