diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/cancun-6780-selfdestruct-sweep.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/cancun-6780-selfdestruct-sweep.json new file mode 100644 index 00000000000..668434d98ec --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/cancun-6780-selfdestruct-sweep.json @@ -0,0 +1,114 @@ +{ + "cli": [ + "t8n", + "--input.alloc=stdin", + "--input.txs=stdin", + "--input.env=stdin", + "--output.result=stdout", + "--output.alloc=stdout", + "--output.body=stdout", + "--state.fork=Cancun", + "--state.chainid=1", + "--state.reward=0" + ], + "stdin": { + "alloc": { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "nonce": "0x00", + "balance": "0x3635c9adc5dea00000", + "code": "0x", + "storage": {} + }, + "0x1111111111111111111111111111111111111111": { + "nonce": "0x00", + "balance": "0x01", + "code": "0x60015f555fff", + "storage": {} + } + }, + "txs": [ + { + "type": "0x0", + "chainId": "0x1", + "nonce": "0x0", + "gasPrice": "0xa", + "gas": "0x5f5e100", + "value": "0x0", + "input": "0x", + "to": "0x1111111111111111111111111111111111111111", + "sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "v": "0x1b", + "r": "0x7529278ba20a00f86b3659cd9f48285243075a63d7d3083f0f8977da3fc43a6f", + "s": "0x3745796d09090fa3b1aea76626aa0a3153b496f937f5a08b974309858d30e91d" + } + ], + "env": { + "currentCoinbase": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentGasLimit": "10000000000", + "currentNumber": "1", + "currentTimestamp": "1000", + "currentRandom": "0", + "currentDifficulty": "0", + "parentDifficulty": "0", + "parentBaseFee": "7", + "parentGasUsed": "0", + "parentGasLimit": "10000000000", + "parentTimestamp": "0", + "blockHashes": { + "0": "0xb9a3dd3d2865b4f8d6c701d6610a99800ad7e4ace851fb4e8d4e26fc1b7ad8dc" + }, + "ommers": [], + "withdrawals": [], + "parentUncleHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "parentDataGasUsed": "0", + "parentExcessDataGas": "0" + } + }, + "stdout": { + "alloc": { + "0x0000000000000000000000000000000000000000": { + "balance": "0x1" + }, + "0x1111111111111111111111111111111111111111": { + "code": "0x60015f555fff", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "balance": "0x0" + }, + "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba": { + "balance": "0x37731" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x3635c9adc5de9472b2", + "nonce": "0x1" + } + }, + "body": "0xf863f861800a8405f5e10094111111111111111111111111111111111111111180801ba07529278ba20a00f86b3659cd9f48285243075a63d7d3083f0f8977da3fc43a6fa03745796d09090fa3b1aea76626aa0a3153b496f937f5a08b974309858d30e91d", + "result": { + "stateRoot": "0x3a0e532de836d767cae901aba671040fedc07557d277f7203066f640ed95f78d", + "txRoot": "0x60ae0f99c255ecf6436fdf1b503dbce0b7c84573fd5ed17bbfc7850c444f7bc3", + "receiptsRoot": "0x6b0b401f0a222e669b278b3a0ea50264b2771b63a3ad88f3892b507e4d8dfb2e", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [ + { + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0x127bb", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0x2b992759a7ca56e96a5d44f118f0edb740a27c6f49482367799918c1e65b673e", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x127bb", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x0" + } + ], + "currentDifficulty": null, + "gasUsed": "0x127bb", + "currentBaseFee": "0x7", + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + } + } +} \ No newline at end of file diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/cancun-6780-selfdestruct-to-self.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/cancun-6780-selfdestruct-to-self.json new file mode 100644 index 00000000000..20314f7f95a --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/cancun-6780-selfdestruct-to-self.json @@ -0,0 +1,113 @@ +{ + "cli": [ + "t8n", + "--input.alloc=stdin", + "--input.txs=stdin", + "--input.env=stdin", + "--output.result=stdout", + "--output.alloc=stdout", + "--output.body=stdout", + "--state.fork=Cancun", + "--state.chainid=1", + "--state.reward=0" + ], + "stdin": { + "alloc": { + "0x095e7baea6a6c7c4c2dfeb977efac326af552d87": { + "balance": "0x0de0b6b3a7640000", + "code": "0x3060005530ff00", + "nonce": "0x00", + "storage": { + } + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x0de0b6b3a7640000", + "code": "0x", + "nonce": "0x00", + "storage": { + } + } + }, + "txs": [ + { + "type": "0x0", + "chainId": "0x1", + "nonce": "0x0", + "gasPrice": "0xa", + "gas": "0x0f4240", + "value": "0x0186a0", + "input": "0x", + "to": "0x095e7baea6a6c7c4c2dfeb977efac326af552d87", + "sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "v": "0x1c", + "r": "0x7bb4986663aec020c016ea3db37ba36e62e9c7d355dc8ed8566b20ce7452b600", + "s": "0x7da62397d8a969f674442837f419001f2671df0f19a45586ed3acfd93e819d82" + } + ], + "env": { + "currentCoinbase": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentGasLimit": "10000000000", + "currentNumber": "1", + "currentTimestamp": "1000", + "currentRandom": "0", + "currentDifficulty": "0", + "parentDifficulty": "0", + "parentBaseFee": "7", + "parentGasUsed": "0", + "parentGasLimit": "10000000000", + "parentTimestamp": "0", + "blockHashes": { + "0": "0xb9a3dd3d2865b4f8d6c701d6610a99800ad7e4ace851fb4e8d4e26fc1b7ad8dc" + }, + "ommers": [], + "withdrawals": [], + "parentUncleHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "parentDataGasUsed": "0", + "parentExcessDataGas": "0" + } + }, + "stdout": { + "alloc" : { + "0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : { + "code" : "0x3060005530ff00", + "storage" : { + "0x0000000000000000000000000000000000000000000000000000000000000000" : "0x000000000000000000000000095e7baea6a6c7c4c2dfeb977efac326af552d87" + }, + "balance" : "0xde0b6b3a76586a0" + }, + "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" : { + "balance" : "0x233c1" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0xde0b6b3a75b2232", + "nonce" : "0x1" + } + }, + "body" : "0xf865f863800a830f424094095e7baea6a6c7c4c2dfeb977efac326af552d87830186a0801ca07bb4986663aec020c016ea3db37ba36e62e9c7d355dc8ed8566b20ce7452b600a07da62397d8a969f674442837f419001f2671df0f19a45586ed3acfd93e819d82", + "result" : { + "stateRoot" : "0xddd3a541e86e2dd0293959736de63e1fad74ae95149f34740b1173378e82527a", + "txRoot" : "0x0cbd46498d79551ba2f4237443d408194f7493f1fd567dbeaf1d53b41b41485a", + "receiptsRoot" : "0xbc67bed8ee77b1d9dd8eb6d6e55abd11e49c50e16832f0c350ae07027c859f19", + "logsHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom" : "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts" : [ + { + "root" : "0x", + "status" : "0x1", + "cumulativeGasUsed" : "0xbbeb", + "logsBloom" : "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs" : null, + "transactionHash" : "0xa87c1a093fe07f3d38db9cde21d05b407f527e88f7c698c9008b6138119d2487", + "contractAddress" : "0x0000000000000000000000000000000000000000", + "gasUsed" : "0xbbeb", + "blockHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex" : "0x0" + } + ], + "currentDifficulty" : null, + "gasUsed" : "0xbbeb", + "currentBaseFee" : "0x7", + "withdrawalsRoot" : "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + } + } +} \ No newline at end of file diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/cancun-6780-selfdestruct-transient.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/cancun-6780-selfdestruct-transient.json new file mode 100644 index 00000000000..5e3c6ea4754 --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/cancun-6780-selfdestruct-transient.json @@ -0,0 +1,98 @@ +{ + "cli": [ + "t8n", + "--input.alloc=stdin", + "--input.txs=stdin", + "--input.env=stdin", + "--output.result=stdout", + "--output.alloc=stdout", + "--output.body=stdout", + "--state.fork=Cancun", + "--state.chainid=1", + "--state.reward=0" + ], + "stdin": { + "alloc": { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "nonce": "0x00", + "balance": "0x3635c9adc5dea00000", + "code": "0x", + "storage": {} + } + }, + "txs": [ + { + "type": "0x0", + "chainId": "0x0", + "nonce": "0x0", + "gasPrice": "0xa", + "gas": "0x5f5e100", + "value": "0x0", + "input": "0x600d8060175f39805f80f05f805f805f855af1505ffffe600280600b5f39805ff3fe5fff", + "sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "v": "0x1b", + "r": "0xf2bb558b73cfb96466c41785fdd0f1e367b4703d49653e617cffdb7316a01e87", + "s": "0x11e0423aeea027a1e48dc3adffe2b27652d8154599f10b6f552d51b1fcb632ae" + } + ], + "env": { + "currentCoinbase": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentGasLimit": "10000000000", + "currentNumber": "1", + "currentTimestamp": "1000", + "currentRandom": "0", + "currentDifficulty": "0", + "parentDifficulty": "0", + "parentBaseFee": "7", + "parentGasUsed": "0", + "parentGasLimit": "10000000000", + "parentTimestamp": "0", + "blockHashes": { + "0": "0xc5d4b3d67827b580c95a2a0980670255c15bfb964e8c4183d18971659bd5b6a8" + }, + "ommers": [], + "withdrawals": [], + "parentUncleHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "parentDataGasUsed": "0", + "parentExcessDataGas": "0" + } + }, + "stdout": { + "alloc": { + "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba": { + "balance": "0x48540" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x3635c9adc5de90ee80", + "nonce": "0x1" + } + }, + "body": "0xf873f871800a8405f5e1008080a4600d8060175f39805f80f05f805f805f855af1505ffffe600280600b5f39805ff3fe5fff1ba0f2bb558b73cfb96466c41785fdd0f1e367b4703d49653e617cffdb7316a01e87a011e0423aeea027a1e48dc3adffe2b27652d8154599f10b6f552d51b1fcb632ae", + "result": { + "stateRoot": "0x8d6ff9ecb860b2ca140a73ae8591615e384a4e397d859af831b4a475e47ff7b0", + "txRoot": "0x35ab0ce85af281d6e600d467fb3ed12063994cc9e105cbdb4eb1ba65ed9edddf", + "receiptsRoot": "0x2f9ef11d9889ea86d77151c8a6cf578fed535563e1a016e67997d22984ca1bef", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [ + { + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0x181c0", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0xd954e10ebf0b55b71512b53a6ebae09372ccc6b2bf87b199e8aa8a5c4f757430", + "contractAddress": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "gasUsed": "0x181c0", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x0" + } + ], + "currentDifficulty": null, + "gasUsed": "0x181c0", + "currentBaseFee": "0x7", + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + } + } +} \ No newline at end of file diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/cancun-blobs-per-tx.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/cancun-blobs-per-tx.json index 8ee3e7b21ad..99acfb9e058 100644 --- a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/cancun-blobs-per-tx.json +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/cancun-blobs-per-tx.json @@ -36,7 +36,6 @@ "input": "0x", "to": "0x0000000000000000000000000000000000000100", "accessList": [], - "protected": true, "maxFeePerDataGas": "0x1", "sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", "blobVersionedHashes": [ diff --git a/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java b/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java index fdebbdb2de4..4df9415a108 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java @@ -843,15 +843,18 @@ public static void registerCancunOperations( final BigInteger chainID) { registerShanghaiOperations(registry, gasCalculator, chainID); - // EIP-4844 DATAHASH - registry.put(new DataHashOperation(gasCalculator)); - // EIP-1153 TSTORE/TLOAD registry.put(new TStoreOperation(gasCalculator)); registry.put(new TLoadOperation(gasCalculator)); + // EIP-4844 DATAHASH + registry.put(new DataHashOperation(gasCalculator)); + // EIP-5656 MCOPY registry.put(new MCopyOperation(gasCalculator)); + + // EIP-6780 nerf self destruct + registry.put(new SelfDestructOperation(gasCalculator, true)); } /** diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java index a71838e22b7..1dd7801ce15 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java @@ -218,6 +218,7 @@ public enum Type { private final List logs; private long gasRefund; private final Set
selfDestructs; + private final Set
creates; private final Map refunds; private final Set
warmedUpAddresses; private final Multimap warmedUpStorage; @@ -305,6 +306,7 @@ private MessageFrame( this.logs = new ArrayList<>(); this.gasRefund = 0L; this.selfDestructs = new HashSet<>(); + this.creates = new HashSet<>(); this.refunds = new HashMap<>(); this.recipient = recipient; this.originator = originator; @@ -971,6 +973,51 @@ public Set
getSelfDestructs() { return selfDestructs; } + /** + * Add recipient to the create set if not already present. + * + * @param address The recipient to create + */ + public void addCreate(final Address address) { + creates.add(address); + } + /** + * Add addresses to the create set if they are not already present. + * + * @param addresses The addresses to create + */ + public void addCreates(final Set
addresses) { + creates.addAll(addresses); + } + + /** Removes all entries in the create set. */ + public void clearCreates() { + creates.clear(); + } + + /** + * Returns the create set. + * + * @return the create set + */ + public Set
getCreates() { + return creates; + } + + /** + * Was the account at this address created in this transaction? (in any of the previously executed + * message frames in this transaction). + * + * @param address the address to check + * @return true if the account was created in any parent or prior message frame in this + * transaction. False if the account existed in the world state at the beginning of the + * transaction. + */ + public boolean wasCreatedInTransaction(final Address address) { + return creates.contains((address)) + || (parentMessageFrame != null && parentMessageFrame.wasCreatedInTransaction(address)); + } + /** * Add refund to the refunds map if not already present. * diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java index 529754905aa..5cd11e55a3a 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java @@ -259,6 +259,7 @@ public void complete(final MessageFrame frame, final MessageFrame childFrame) { frame.setReturnData(outputData); frame.addLogs(childFrame.getLogs()); frame.addSelfDestructs(childFrame.getSelfDestructs()); + frame.addCreates(childFrame.getCreates()); frame.incrementGasRefund(childFrame.getGasRefund()); final long gasRemaining = childFrame.getRemainingGas(); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java index f3d04f772e7..9496624334a 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java @@ -141,6 +141,7 @@ private void spawnChildMessage(final MessageFrame frame, final Code code, final final Wei value = Wei.wrap(frame.getStackItem(0)); final Address contractAddress = targetContractAddress(frame); + frame.addCreate(contractAddress); final long childGasStipend = gasCalculator().gasAvailableForChildCreate(frame.getRemainingGas()); @@ -184,6 +185,7 @@ private void complete(final MessageFrame frame, final MessageFrame childFrame, f frame.incrementRemainingGas(childFrame.getRemainingGas()); frame.addLogs(childFrame.getLogs()); frame.addSelfDestructs(childFrame.getSelfDestructs()); + frame.addCreates(childFrame.getCreates()); frame.incrementGasRefund(childFrame.getGasRefund()); if (childFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SelfDestructOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SelfDestructOperation.java index e0e5a728293..98807652034 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SelfDestructOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SelfDestructOperation.java @@ -27,54 +27,77 @@ /** The Self destruct operation. */ public class SelfDestructOperation extends AbstractOperation { + final boolean eip6780Semantics; + /** * Instantiates a new Self destruct operation. * * @param gasCalculator the gas calculator */ public SelfDestructOperation(final GasCalculator gasCalculator) { + this(gasCalculator, false); + } + + /** + * Instantiates a new Self destruct operation, with an optional EIP-6780 semantics flag. EIP-6780 + * will only remove an account if the account was created within the current transaction. All + * other semantics remain. + * + * @param gasCalculator the gas calculator + * @param eip6780Semantics Enforce EIP6780 semantics. + */ + public SelfDestructOperation(final GasCalculator gasCalculator, final boolean eip6780Semantics) { super(0xFF, "SELFDESTRUCT", 1, 0, gasCalculator); + this.eip6780Semantics = eip6780Semantics; } @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - final Address recipientAddress = Words.toAddress(frame.popStackItem()); - - // because of weird EIP150/158 reasons we care about a null account, so we can't merge this. - final Account recipientNullable = frame.getWorldUpdater().get(recipientAddress); - final Wei inheritance = frame.getWorldUpdater().get(frame.getRecipientAddress()).getBalance(); + // First calculate cost. There's a bit of yak shaving getting values to calculate the cost. + final Address beneficiaryAddress = Words.toAddress(frame.popStackItem()); + // Because of weird EIP150/158 reasons we care about a null account, so we can't merge this. + final Account beneficiaryNullable = frame.getWorldUpdater().get(beneficiaryAddress); + final boolean beneficiaryIsWarm = + frame.warmUpAddress(beneficiaryAddress) || gasCalculator().isPrecompile(beneficiaryAddress); - final boolean accountIsWarm = - frame.warmUpAddress(recipientAddress) || gasCalculator().isPrecompile(recipientAddress); + final Address originatorAddress = frame.getRecipientAddress(); + final Wei originatorBalance = frame.getWorldUpdater().get(originatorAddress).getBalance(); final long cost = - gasCalculator().selfDestructOperationGasCost(recipientNullable, inheritance) - + (accountIsWarm ? 0L : gasCalculator().getColdAccountAccessCost()); + gasCalculator().selfDestructOperationGasCost(beneficiaryNullable, originatorBalance) + + (beneficiaryIsWarm ? 0L : gasCalculator().getColdAccountAccessCost()); + // With the cost we can test for two early exits: static or not enough gas. if (frame.isStatic()) { return new OperationResult(cost, ExceptionalHaltReason.ILLEGAL_STATE_CHANGE); } else if (frame.getRemainingGas() < cost) { return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); } - final Address address = frame.getRecipientAddress(); - final MutableAccount account = frame.getWorldUpdater().getAccount(address).getMutable(); - - frame.addSelfDestruct(address); + // We passed preliminary checks, get mutable accounts. + final MutableAccount originatorAccount = + frame.getWorldUpdater().getAccount(originatorAddress).getMutable(); + final MutableAccount beneficiaryAccount = + frame.getWorldUpdater().getOrCreate(beneficiaryAddress).getMutable(); - final MutableAccount recipient = - frame.getWorldUpdater().getOrCreate(recipientAddress).getMutable(); + // Do the "sweep," all modes send all originator balance to the beneficiary account. + originatorAccount.decrementBalance(originatorBalance); + beneficiaryAccount.incrementBalance(originatorBalance); - if (!account.getAddress().equals(recipient.getAddress())) { - recipient.incrementBalance(account.getBalance()); + // If we are actually destroying the originator (pre-Cancun or same-tx-create) we need to + // explicitly zero out the account balance (destroying ether/value if the originator is the + // beneficiary) as well as tag it for later self-destruct cleanup. + if (!eip6780Semantics || frame.wasCreatedInTransaction(originatorAddress)) { + frame.addSelfDestruct(originatorAddress); + originatorAccount.setBalance(Wei.ZERO); } - // add refund in message frame - frame.addRefund(recipient.getAddress(), account.getBalance()); - - account.setBalance(Wei.ZERO); + // Add refund in message frame. + frame.addRefund(beneficiaryAddress, originatorBalance); + // Set frame to CODE_SUCCESS so that the frame performs a normal halt. frame.setState(MessageFrame.State.CODE_SUCCESS); + return new OperationResult(cost, null); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/processor/ContractCreationProcessor.java b/evm/src/main/java/org/hyperledger/besu/evm/processor/ContractCreationProcessor.java index 58f809aee64..17fa4da60b8 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/processor/ContractCreationProcessor.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/processor/ContractCreationProcessor.java @@ -111,12 +111,13 @@ public void start(final MessageFrame frame, final OperationTracer operationTrace final MutableAccount sender = frame.getWorldUpdater().getSenderAccount(frame).getMutable(); sender.decrementBalance(frame.getValue()); + Address contractAddress = frame.getContractAddress(); final MutableAccount contract = - frame.getWorldUpdater().getOrCreate(frame.getContractAddress()).getMutable(); + frame.getWorldUpdater().getOrCreate(contractAddress).getMutable(); if (accountExists(contract)) { LOG.trace( "Contract creation error: account has already been created for address {}", - frame.getContractAddress()); + contractAddress); frame.setExceptionalHaltReason(Optional.of(ExceptionalHaltReason.INSUFFICIENT_GAS)); frame.setState(MessageFrame.State.EXCEPTIONAL_HALT); operationTracer.traceAccountCreationResult( @@ -126,6 +127,7 @@ public void start(final MessageFrame frame, final OperationTracer operationTrace contract.setNonce(initialContractNonce); contract.clearStorage(); frame.setState(MessageFrame.State.CODE_EXECUTING); + frame.addCreate(contractAddress); } } catch (final ModificationNotAllowedException ex) { LOG.trace("Contract creation error: attempt to mutate an immutable account"); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/SelfDestructOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/SelfDestructOperationTest.java new file mode 100644 index 00000000000..3ed4d38a339 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operations/SelfDestructOperationTest.java @@ -0,0 +1,187 @@ +/* + * Copyright contributors to Hyperledger Besu + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operations; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.code.CodeFactory; +import org.hyperledger.besu.evm.frame.BlockValues; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator; +import org.hyperledger.besu.evm.internal.Words; +import org.hyperledger.besu.evm.operation.Operation; +import org.hyperledger.besu.evm.operation.SelfDestructOperation; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; +import org.hyperledger.besu.evm.worldstate.WrappedEvmAccount; + +import java.util.ArrayDeque; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class SelfDestructOperationTest { + + private static final Bytes SELFDESTRUCT_CODE = + Bytes.fromHexString( + "6000" // PUSH1 0 + + "35" // CALLDATALOAD + + "ff" // SELFDESTRUCT + ); + + private MessageFrame messageFrame; + @Mock private WorldUpdater worldUpdater; + @Mock private WrappedEvmAccount accountOriginator; + @Mock private WrappedEvmAccount accountBeneficiary; + @Mock private MutableAccount mutableAccountOriginator; + @Mock private MutableAccount mutableAccountBeneficiary; + @Mock private EVM evm; + + private final SelfDestructOperation frontierOperation = + new SelfDestructOperation(new ConstantinopleGasCalculator()); + + private final SelfDestructOperation eip6780Operation = + new SelfDestructOperation(new ConstantinopleGasCalculator(), true); + + void checkContractDeletionCommon( + final String originator, + final String beneficiary, + final String balanceHex, + final boolean newContract, + final SelfDestructOperation operation) { + Address originatorAddress = Address.fromHexString(originator); + Address beneficiaryAddress = Address.fromHexString(beneficiary); + messageFrame = + MessageFrame.builder() + .type(MessageFrame.Type.CONTRACT_CREATION) + .contract(Address.ZERO) + .inputData(Bytes.EMPTY) + .sender(beneficiaryAddress) + .value(Wei.ZERO) + .apparentValue(Wei.ZERO) + .code(CodeFactory.createCode(SELFDESTRUCT_CODE, 0, true)) + .depth(1) + .completer(__ -> {}) + .address(originatorAddress) + .blockHashLookup(n -> Hash.hash(Words.longBytes(n))) + .blockValues(mock(BlockValues.class)) + .gasPrice(Wei.ZERO) + .messageFrameStack(new ArrayDeque<>()) + .miningBeneficiary(Address.ZERO) + .originator(Address.ZERO) + .initialGas(100_000L) + .worldUpdater(worldUpdater) + .build(); + messageFrame.pushStackItem(Bytes.fromHexString(beneficiary)); + if (newContract) { + messageFrame.addCreate(originatorAddress); + } + + when(worldUpdater.getAccount(originatorAddress)).thenReturn(accountOriginator); + when(worldUpdater.get(originatorAddress)).thenReturn(accountOriginator); + if (!originatorAddress.equals(beneficiaryAddress)) { + when(worldUpdater.get(beneficiaryAddress)).thenReturn(accountBeneficiary); + } + when(worldUpdater.getOrCreate(beneficiaryAddress)).thenReturn(accountBeneficiary); + when(accountOriginator.getMutable()).thenReturn(mutableAccountOriginator); + when(accountOriginator.getBalance()).thenReturn(Wei.fromHexString(balanceHex)); + when(accountBeneficiary.getMutable()).thenReturn(mutableAccountBeneficiary); + + final Operation.OperationResult operationResult = operation.execute(messageFrame, evm); + assertThat(operationResult).isNotNull(); + + // The interactions with the contracts varies based on the parameterized tests, but it will be + // some subset of these calls. + verify(accountOriginator, atLeast(0)).getBalance(); + verify(accountBeneficiary, atLeast(0)).getBalance(); + verify(mutableAccountOriginator, atLeast(0)).getBalance(); + verify(mutableAccountOriginator).decrementBalance(Wei.fromHexString(balanceHex)); + verify(mutableAccountOriginator, atLeast(0)).setBalance(Wei.ZERO); + verify(mutableAccountBeneficiary).incrementBalance(Wei.fromHexString(balanceHex)); + } + + public static Object[][] params() { + return new Object[][] { + { + "0x00112233445566778899aabbccddeeff11223344", + "0x1234567890abcdef1234567890abcdef12345678", + true, + "0x1234567890" + }, + { + "0x00112233445566778899aabbccddeeff11223344", + "0x1234567890abcdef1234567890abcdef12345678", + false, + "0x1234567890" + }, + { + "0x00112233445566778899aabbccddeeff11223344", + "0x00112233445566778899aabbccddeeff11223344", + true, + "0x1234567890" + }, + { + "0x1234567890abcdef1234567890abcdef12345678", + "0x1234567890abcdef1234567890abcdef12345678", + false, + "0x1234567890" + }, + }; + } + + @ParameterizedTest + @MethodSource("params") + void checkContractDeletionFrontier( + final String originator, + final String beneficiary, + final boolean newAccount, + final String balanceHex) { + checkContractDeletionCommon(originator, beneficiary, balanceHex, newAccount, frontierOperation); + + assertThat(messageFrame.getSelfDestructs()).contains(Address.fromHexString(originator)); + } + + @ParameterizedTest + @MethodSource("params") + void checkContractDeletionEIP6780( + final String originator, + final String beneficiary, + final boolean newAccount, + final String balanceHex) { + checkContractDeletionCommon(originator, beneficiary, balanceHex, newAccount, eip6780Operation); + + Address orignatorAddress = Address.fromHexString(originator); + if (newAccount) { + assertThat(messageFrame.getSelfDestructs()).contains(orignatorAddress); + assertThat(messageFrame.getCreates()).contains(orignatorAddress); + } else { + assertThat(messageFrame.getSelfDestructs()).isEmpty(); + assertThat(messageFrame.getCreates()).doesNotContain(orignatorAddress); + } + } +}