EIP-4758: Deactivate selfdestruct

Basically, our system uses ERC-1155 to represent Financial NFTs, which are backed by tokens. Say an NFT is worth 10 tokens each, it has ID=10, and a supply of 15. 10 copies of ID=10 are owned by User1 and 5 copies of ID=10 are owned by User2

The code our system uses is as follows:

                address smartWallAdd = Clones.cloneDeterministic(TEMPLATE, keccak256(abi.encode(fnftId)));
                RevestSmartWallet wallet = RevestSmartWallet(smartWallAdd);
                amountToWithdraw = quantity * IERC20(asset).balanceOf(smartWallAdd) / supplyBefore;
                wallet.withdraw(amountToWithdraw, asset, user);

The OpenZeppelin method follows:

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create2 opcode and a `salt` to deterministically deploy
     * the clone. Using the same `implementation` and `salt` multiple time will revert, since
     * the clones cannot be deployed twice at the same address.
     */
    function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
            mstore(add(ptr, 0x14), shl(0x60, implementation))
            mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
            instance := create2(0, ptr, 0x37, salt)
        }
        require(instance != address(0), "ERC1167: create2 failed");
    }

So if User1 withdraws all of his FNFTs, then User2 will find that his are permanently inaccessible because the contract already exists, as the self_destruct that would have occurred at the end of withdraw was never triggered

1 Like

This EIP seems to be missing some details that should be considered:

  • Doesn’t specify whether SENDALL should halt execution like SELFDESTRUCT does
  • Whether the gas cost should be adjusted
  • Providing an alternative for replacing code at a given address
  • Plans for backwards compatibility beyond “get rekd”

The last I find is specifically important. As a platform for building immutable, composeable dApps breaking some functionality which some projects seem to critically rely on sets a bad precedent. There is some small precedent for mutating contracts via social consensus (DAO hack) but the circumstances were very different there.

Beyond harming users of such dApps it would encourage more immutability and upgradeability as devs may want to ensure that they can fix their protocol should the protocol choose to one day break it.

2 Likes

I agree the EIP could use a bit more clarity, but my interpretation is that the opcode continues doing what it did before except the changes listed. This means it will still halt execution just as before. The same thing for gas cost (doesn’t change except for the removal of the refund).

1 Like

Having SENDALL halt execution like SELFDESTRUCT does make sense, otherwise it could lead to quirky vulnerabilities whereby code from another branch after the <0xff> can all of a sudden be reached from the branch that originally ended in SELFDESTRUCT.

However if the <0xff> opcode is getting such a significant overhaul I’d argue for a reduction of the static gas cost. At least down to the static cost of a CALL (100) or even lower considering SENDALL would not initiate a new call context and also terminates the current context.

EDIT: In fact opcodes that end the current context (STOP, REVERT, RETURN) typically have a base cost of zero so arguably the static cost of SENDALL should also be 0.

2 Likes

If selfdestruct were changed to send-all with the exception of when a contract is created and destroyed in the same transaction, would that satisfy your needs? Does your system only have a hard dependency on this pattern intra-transaction, or do you sometimes create in one transaction and destroy in another (such that you need to be able to re-use that address)?

Does this affect the non-SELFDESTRUCT contract code or storage in any way?

It seems the contract code is perpetually accessible and functional, which kills usage of SELFDESTRUCT as a form of “deactivate” or “disable” switch? (there are of course alternative methods to implement this feature)

Since contract code is currently capped, could we allow SELFDESTRUCT to destroy (delete) code whilst storage persists? In the absence of code deletion, the contract could still be deactivated.

However it could interact quite messily with the following [from the verkle tire HackMD doc at top]:

Instead, we add a new Verkle tree that starts out empty, and only new changes to state and copies of accessed state are stored in the tree. The Patricia tree continues to exist, but is frozen.

Would 100% fix the potential issue. Hard dependency is intra-transaction, contracts will always be destroyed at the end of the same transaction they were created

We also make use of CREATE2+SELFDESTRUCT in our production application which has processed over US$1B.

Eliminating SELFDESTRUCT would break our product and require significant re-engineering. It also breaks a fundamental trust that needs to exist between a product and a platform. How can we continue to develop on the EVM if opcodes can arbitrarily be removed?

3 Likes

This is a little terrifying tbh, unless we change the name of the opcode to reflect it’s true purpose (i.e. instead of SENDALL, it’s CONSTRUCTORDESTRUCTANDSENDALL or something). This overloading of the opcode (context dependence) is quite painful imo.

1 Like

Posting this from Twitter to document here: https://twitter.com/Mudit__Gupta/status/1596028389957447680

1 Like

Here’s an alternative idea which would keep the revival/upgrade pattern working:

2 Likes

I see this wasn’t linked yet, but the previous attempt at removing it 2 years ago is here:

Yeah, this wouldn’t cause issues for my particular pattern

While we are still evaluating how to kill SELFDESTRUCT I think we can officially deprecate the existing SELFDESTRUCT.

Here is an EIP for that Deprecate SELFDESTRUCT

If you only read the Yellow Paper then you probably don’t know you shouldn’t be using SELFDESTRUCT. That EIP fixes that.

3 Likes

Bumping for curiosity sake, is this approach just not tractable / doesn’t solve the problem seeking to be addressed by client teams?

Wanted to bump after seeing EIP-4758: Deactivate selfdestruct - #30 by timbeiko from @maxsam4

Unfortunately it doesn’t solve the problem of having a strong guarantee that if a piece of code exists at a particular address then that same code will always exist at that address.

You don’t necessarily have that guarantee even without SELFDESTRUCT. One can write a smart contract function which has behavior that its author/controller can change at a later date, to be arbitrarily different. Analysis of the code can reveal this possibility, allowing potential callers to apply the appropriate size proverbial grain of salt or hedges around calls. Analysis of the code can also reveal the use of SELFDESTRUCT.

In conclusion, I don’t see this EIP addressing this concern, and seriously doubt that the concern can be addressed within Ethereum without serious reductions including elimination of valuable functionality people are relying on for important use cases.

This behaviour is import to us at Optimism to preserve, as we use it to burn ETH when it is withdrawn from L2 to L1.

This is valuable to us as there is (AFAIK) no other way to remove ETH from the state.

3 Likes

Want to note that the ability to “force send” ETH as described in this EIP is important functionality to keep.

“Force send” can be used to prevent denial of service attacks when “push” patterns are used (generally for efficiency and/or user experience benefits). We recently opted to use this type of pattern in a minter developed at Art Blocks, as described in this PR which was inspired by a forceSendSafeEth function in the Solady repo

1 Like

I found a very useful idea compared to the traditional deposit method on exchanges.

Using this method, not only does it not need to manage secret keys, any account can perform token collection, but it also costs less gas. But selfdestruct must be used, otherwise the gas fee will increase 3-4 times. (Of course, if contracts are reused, it will be cheaper.)

contract Imputation {
    address immutable public owner=msg.sender;
    function getwalletadd(uint256 path)public view returns(address walletadd){
        unchecked{
            bytes32 salt = keccak256(abi.encodePacked(path));
            return address(uint160(uint(keccak256(abi.encodePacked(
                    bytes1(0xff),
                    address(this),
                    salt,
                    keccak256(type(Wallet).creationCode)
                )))));
        }
    }
    function imputationtoken(uint256[] calldata paths,IERC20 token)public {
        unchecked{
            for (uint256 i; i<paths.length; i++) {
                address n_add = getwalletadd(paths[i]);
                if(n_add.codehash ==0){
                    creatwallet(paths[i],token);
                }
            }
        }
    }
    function creatwallet(uint256 path,IERC20 token)private {
        bytes32 salt = keccak256(abi.encodePacked( path));
        new Wallet{salt: salt}(owner,token); 
    }

}
contract Wallet {
    constructor(address owner,IERC20 token){
        token.transfer(owner,token.balanceOf(address(this)));
        selfdestruct(payable(owner));
    }
}