From 6a8f51b683762e57c4121543decbc841508c7cef Mon Sep 17 00:00:00 2001 From: Luis Schaab Date: Thu, 8 Feb 2024 17:03:10 -0300 Subject: [PATCH 1/5] Add tests for transient storage opcode --- .../internal/hardhat-network/provider/node.ts | 302 ++++++++++++++++++ 1 file changed, 302 insertions(+) diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts index 581b24559d..4e0fd9cbf1 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts @@ -1376,4 +1376,306 @@ describe("HardhatNode", () => { }); }); }); + + describe("Transient storage", function () { + const TLOAD_DEPLOYMENT_BYTECODE = "0x60FF5c"; // PUSH1 FF TLOAD + const TSTORE_DEPLOYMENT_BYTECODE = "0x60FF60FF5d"; // PUSH1 FF TLOAD + + const nodeConfig: LocalNodeConfig = { + automine: true, + chainId: 1, + networkId: 1, + hardfork: "", + blockGasLimit: 1_000_000, + minGasPrice: 0n, + genesisAccounts: DEFAULT_ACCOUNTS, + chains: defaultHardhatNetworkParams.chains, + mempoolOrder: "priority", + coinbase: "0x0000000000000000000000000000000000000000", + allowBlocksWithSameTimestamp: false, + enableTransientStorage: false, + }; + + describe("When in a pre-cancun hardfork", function () { + it("Should revert if trying to run TLOAD in a tx", async function () { + const [, hardhatNode] = await HardhatNode.create({ + ...nodeConfig, + hardfork: "shanghai", + }); + + const tx = createTestTransaction({ + nonce: 0, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: undefined, + data: TLOAD_DEPLOYMENT_BYTECODE, + gasLimit: 1_000_000n, + gasPrice: 10n ** 9n, + }); + + const transactionResult = await hardhatNode.sendTransaction(tx); + + if ( + typeof transactionResult === "string" || + Array.isArray(transactionResult) + ) { + assert.fail("Expected a MineBlockResult"); + } + + const error = transactionResult.traces[0].error; + assert.isDefined(error); + assert.include(error!.message, "invalid opcode"); + }); + + it("Should revert if trying to run TSTORE in a tx", async function () { + const [, hardhatNode] = await HardhatNode.create({ + ...nodeConfig, + hardfork: "shanghai", + }); + + const tx = createTestTransaction({ + nonce: 0, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: undefined, + data: TSTORE_DEPLOYMENT_BYTECODE, + gasLimit: 1_000_000n, + gasPrice: 10n ** 9n, + }); + + const transactionResult = await hardhatNode.sendTransaction(tx); + + if ( + typeof transactionResult === "string" || + Array.isArray(transactionResult) + ) { + assert.fail("Expected a MineBlockResult"); + } + + const error = transactionResult.traces[0].error; + assert.isDefined(error); + assert.include(error!.message, "invalid opcode"); + }); + + it("Should revert if trying to run TLOAD in a call", async function () { + const [, hardhatNode] = await HardhatNode.create({ + ...nodeConfig, + hardfork: "shanghai", + }); + + const callResult = await hardhatNode.runCall( + { + to: undefined, + from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), + data: toBuffer(TLOAD_DEPLOYMENT_BYTECODE), + value: 0n, + gasLimit: 1_000_000n, + }, + 0n + ); + + assert.isDefined(callResult.error); + assert.include(callResult.error!.message, "invalid opcode"); + }); + + it("Should revert if trying to run TSTORE in a call", async function () { + const [, hardhatNode] = await HardhatNode.create({ + ...nodeConfig, + hardfork: "shanghai", + }); + + const callResult = await hardhatNode.runCall( + { + to: undefined, + from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), + data: toBuffer(TSTORE_DEPLOYMENT_BYTECODE), + value: 0n, + gasLimit: 1_000_000n, + }, + 0n + ); + + assert.isDefined(callResult.error); + assert.include(callResult.error!.message, "invalid opcode"); + }); + + it("Should revert if trying to run TLOAD in a gasEstimate", async function () { + const [, hardhatNode] = await HardhatNode.create({ + ...nodeConfig, + hardfork: "shanghai", + }); + + const estimateGasResult = await hardhatNode.estimateGas( + { + to: undefined, + from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), + data: toBuffer(TLOAD_DEPLOYMENT_BYTECODE), + value: 0n, + gasLimit: 1_000_000n, + }, + 0n + ); + + assert.isDefined(estimateGasResult.error); + assert.include(estimateGasResult.error!.message, "invalid opcode"); + }); + + it("Should revert if trying to run TSTORE in a gasEstimate", async function () { + const [, hardhatNode] = await HardhatNode.create({ + ...nodeConfig, + hardfork: "shanghai", + }); + + const estimateGasResult = await hardhatNode.estimateGas( + { + to: undefined, + from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), + data: toBuffer(TSTORE_DEPLOYMENT_BYTECODE), + value: 0n, + gasLimit: 1_000_000n, + }, + 0n + ); + + assert.isDefined(estimateGasResult.error); + assert.include(estimateGasResult.error!.message, "invalid opcode"); + }); + }); + + describe("When in the cancun hardfork", function () { + it("Should not revert if trying to run TLOAD in a tx", async function () { + const [, hardhatNode] = await HardhatNode.create({ + ...nodeConfig, + hardfork: "cancun", + }); + + const tx = createTestTransaction({ + nonce: 0, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: undefined, + data: TLOAD_DEPLOYMENT_BYTECODE, + gasLimit: 1_000_000n, + gasPrice: 10n ** 9n, + }); + + const transactionResult = await hardhatNode.sendTransaction(tx); + + if ( + typeof transactionResult === "string" || + Array.isArray(transactionResult) + ) { + assert.fail("Expected a MineBlockResult"); + } + + const error = transactionResult.traces[0].error; + assert.isUndefined(error); + }); + + it("Should not revert if trying to run TSTORE in a tx", async function () { + const [, hardhatNode] = await HardhatNode.create({ + ...nodeConfig, + hardfork: "cancun", + }); + + const tx = createTestTransaction({ + nonce: 0, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: undefined, + data: TSTORE_DEPLOYMENT_BYTECODE, + gasLimit: 1_000_000n, + gasPrice: 10n ** 9n, + }); + + const transactionResult = await hardhatNode.sendTransaction(tx); + + if ( + typeof transactionResult === "string" || + Array.isArray(transactionResult) + ) { + assert.fail("Expected a MineBlockResult"); + } + + const error = transactionResult.traces[0].error; + assert.isUndefined(error); + }); + + it("Should not revert if trying to run TLOAD in a call", async function () { + const [, hardhatNode] = await HardhatNode.create({ + ...nodeConfig, + hardfork: "cancun", + }); + + const callResult = await hardhatNode.runCall( + { + to: undefined, + from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), + data: toBuffer(TLOAD_DEPLOYMENT_BYTECODE), + value: 0n, + gasLimit: 1_000_000n, + }, + 0n + ); + + assert.isUndefined(callResult.error); + }); + + it("Should revert if trying to run TSTORE in a call", async function () { + const [, hardhatNode] = await HardhatNode.create({ + ...nodeConfig, + hardfork: "cancun", + }); + + const callResult = await hardhatNode.runCall( + { + to: undefined, + from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), + data: toBuffer(TSTORE_DEPLOYMENT_BYTECODE), + value: 0n, + gasLimit: 1_000_000n, + }, + 0n + ); + + assert.isUndefined(callResult.error); + }); + + it("Should not revert if trying to run TLOAD in a gasEstimate", async function () { + const [, hardhatNode] = await HardhatNode.create({ + ...nodeConfig, + hardfork: "cancun", + }); + + const estimateGasResult = await hardhatNode.estimateGas( + { + to: undefined, + from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), + data: toBuffer(TLOAD_DEPLOYMENT_BYTECODE), + value: 0n, + gasLimit: 1_000_000n, + }, + 0n + ); + + assert.isUndefined(estimateGasResult.error); + }); + + it("Should not revert if trying to run TSTORE in a gasEstimate", async function () { + const [, hardhatNode] = await HardhatNode.create({ + ...nodeConfig, + hardfork: "cancun", + }); + + const estimateGasResult = await hardhatNode.estimateGas( + { + to: undefined, + from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), + data: toBuffer(TSTORE_DEPLOYMENT_BYTECODE), + value: 0n, + gasLimit: 1_000_000n, + }, + 0n + ); + + assert.isUndefined(estimateGasResult.error); + }); + }); + }); }); From e63cadedacd0d022e030799e854fe4a30d276d9f Mon Sep 17 00:00:00 2001 From: Luis Schaab Date: Fri, 9 Feb 2024 10:47:03 -0300 Subject: [PATCH 2/5] Add tests for MCOPY --- .../internal/hardhat-network/provider/node.ts | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts index 4e0fd9cbf1..f86fdb889d 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts @@ -1678,4 +1678,166 @@ describe("HardhatNode", () => { }); }); }); + + describe("Memory copying instruction", function () { + const MCOPY_DEPLOYMENT_BYTECODE = "0x6001600160015e"; // PUSH1 01 PUSH1 01 PUSH1 01 MCOPY + + const nodeConfig: LocalNodeConfig = { + automine: true, + chainId: 1, + networkId: 1, + hardfork: "", + blockGasLimit: 1_000_000, + minGasPrice: 0n, + genesisAccounts: DEFAULT_ACCOUNTS, + chains: defaultHardhatNetworkParams.chains, + mempoolOrder: "priority", + coinbase: "0x0000000000000000000000000000000000000000", + allowBlocksWithSameTimestamp: false, + enableTransientStorage: false, + }; + + describe("When in a pre-cancun hardfork", function () { + it("Should revert if trying to run MCOPY in a tx", async function () { + const [, hardhatNode] = await HardhatNode.create({ + ...nodeConfig, + hardfork: "shanghai", + }); + + const tx = createTestTransaction({ + nonce: 0, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: undefined, + data: MCOPY_DEPLOYMENT_BYTECODE, + gasLimit: 1_000_000n, + gasPrice: 10n ** 9n, + }); + + const transactionResult = await hardhatNode.sendTransaction(tx); + + if ( + typeof transactionResult === "string" || + Array.isArray(transactionResult) + ) { + assert.fail("Expected a MineBlockResult"); + } + + const error = transactionResult.traces[0].error; + assert.isDefined(error); + assert.include(error!.message, "invalid opcode"); + }); + + it("Should revert if trying to run MCOPY in a call", async function () { + const [, hardhatNode] = await HardhatNode.create({ + ...nodeConfig, + hardfork: "shanghai", + }); + + const callResult = await hardhatNode.runCall( + { + to: undefined, + from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), + data: toBuffer(MCOPY_DEPLOYMENT_BYTECODE), + value: 0n, + gasLimit: 1_000_000n, + }, + 0n + ); + + assert.isDefined(callResult.error); + assert.include(callResult.error!.message, "invalid opcode"); + }); + + it("Should revert if trying to run MCOPY in a gasEstimate", async function () { + const [, hardhatNode] = await HardhatNode.create({ + ...nodeConfig, + hardfork: "shanghai", + }); + + const estimateGasResult = await hardhatNode.estimateGas( + { + to: undefined, + from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), + data: toBuffer(MCOPY_DEPLOYMENT_BYTECODE), + value: 0n, + gasLimit: 1_000_000n, + }, + 0n + ); + + assert.isDefined(estimateGasResult.error); + assert.include(estimateGasResult.error!.message, "invalid opcode"); + }); + }); + + describe("When in the cancun hardfork", function () { + it("Should not revert if trying to run MCOPY in a tx", async function () { + const [, hardhatNode] = await HardhatNode.create({ + ...nodeConfig, + hardfork: "cancun", + }); + + const tx = createTestTransaction({ + nonce: 0, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: undefined, + data: MCOPY_DEPLOYMENT_BYTECODE, + gasLimit: 1_000_000n, + gasPrice: 10n ** 9n, + }); + + const transactionResult = await hardhatNode.sendTransaction(tx); + + if ( + typeof transactionResult === "string" || + Array.isArray(transactionResult) + ) { + assert.fail("Expected a MineBlockResult"); + } + + const error = transactionResult.traces[0].error; + assert.isUndefined(error); + }); + + it("Should not revert if trying to run MCOPY in a call", async function () { + const [, hardhatNode] = await HardhatNode.create({ + ...nodeConfig, + hardfork: "cancun", + }); + + const callResult = await hardhatNode.runCall( + { + to: undefined, + from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), + data: toBuffer(MCOPY_DEPLOYMENT_BYTECODE), + value: 0n, + gasLimit: 1_000_000n, + }, + 0n + ); + + assert.isUndefined(callResult.error); + }); + + it("Should not revert if trying to run MCOPY in a gasEstimate", async function () { + const [, hardhatNode] = await HardhatNode.create({ + ...nodeConfig, + hardfork: "cancun", + }); + + const estimateGasResult = await hardhatNode.estimateGas( + { + to: undefined, + from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), + data: toBuffer(MCOPY_DEPLOYMENT_BYTECODE), + value: 0n, + gasLimit: 1_000_000n, + }, + 0n + ); + + assert.isUndefined(estimateGasResult.error); + }); + }); + }); }); From 0642bbccf6724b909e96567ba5e31f77009d3dfe Mon Sep 17 00:00:00 2001 From: Luis Schaab Date: Fri, 9 Feb 2024 11:07:16 -0300 Subject: [PATCH 3/5] Add tests for BLOBBASEFEE --- .../internal/hardhat-network/provider/node.ts | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts index f86fdb889d..1111b628ae 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts @@ -1840,4 +1840,166 @@ describe("HardhatNode", () => { }); }); }); + + describe("BLOBBASEFEE instruction", function () { + const BLOBBASEFEE_DEPLOYMENT_BYTECODE = "0x4a"; // BLOBBASEFEE + + const nodeConfig: LocalNodeConfig = { + automine: true, + chainId: 1, + networkId: 1, + hardfork: "", + blockGasLimit: 1_000_000, + minGasPrice: 0n, + genesisAccounts: DEFAULT_ACCOUNTS, + chains: defaultHardhatNetworkParams.chains, + mempoolOrder: "priority", + coinbase: "0x0000000000000000000000000000000000000000", + allowBlocksWithSameTimestamp: false, + enableTransientStorage: false, + }; + + describe("When in a pre-cancun hardfork", function () { + it("Should revert if trying to run BLOBBASEFEE in a tx", async function () { + const [, hardhatNode] = await HardhatNode.create({ + ...nodeConfig, + hardfork: "shanghai", + }); + + const tx = createTestTransaction({ + nonce: 0, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: undefined, + data: BLOBBASEFEE_DEPLOYMENT_BYTECODE, + gasLimit: 1_000_000n, + gasPrice: 10n ** 9n, + }); + + const transactionResult = await hardhatNode.sendTransaction(tx); + + if ( + typeof transactionResult === "string" || + Array.isArray(transactionResult) + ) { + assert.fail("Expected a MineBlockResult"); + } + + const error = transactionResult.traces[0].error; + assert.isDefined(error); + assert.include(error!.message, "invalid opcode"); + }); + + it("Should revert if trying to run BLOBBASEFEE in a call", async function () { + const [, hardhatNode] = await HardhatNode.create({ + ...nodeConfig, + hardfork: "shanghai", + }); + + const callResult = await hardhatNode.runCall( + { + to: undefined, + from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), + data: toBuffer(BLOBBASEFEE_DEPLOYMENT_BYTECODE), + value: 0n, + gasLimit: 1_000_000n, + }, + 0n + ); + + assert.isDefined(callResult.error); + assert.include(callResult.error!.message, "invalid opcode"); + }); + + it("Should revert if trying to run BLOBBASEFEE in a gasEstimate", async function () { + const [, hardhatNode] = await HardhatNode.create({ + ...nodeConfig, + hardfork: "shanghai", + }); + + const estimateGasResult = await hardhatNode.estimateGas( + { + to: undefined, + from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), + data: toBuffer(BLOBBASEFEE_DEPLOYMENT_BYTECODE), + value: 0n, + gasLimit: 1_000_000n, + }, + 0n + ); + + assert.isDefined(estimateGasResult.error); + assert.include(estimateGasResult.error!.message, "invalid opcode"); + }); + }); + + describe("When in the cancun hardfork", function () { + it("Should not revert if trying to run BLOBBASEFEE in a tx", async function () { + const [, hardhatNode] = await HardhatNode.create({ + ...nodeConfig, + hardfork: "cancun", + }); + + const tx = createTestTransaction({ + nonce: 0, + from: DEFAULT_ACCOUNTS_ADDRESSES[0], + to: undefined, + data: BLOBBASEFEE_DEPLOYMENT_BYTECODE, + gasLimit: 1_000_000n, + gasPrice: 10n ** 9n, + }); + + const transactionResult = await hardhatNode.sendTransaction(tx); + + if ( + typeof transactionResult === "string" || + Array.isArray(transactionResult) + ) { + assert.fail("Expected a MineBlockResult"); + } + + const error = transactionResult.traces[0].error; + assert.isUndefined(error); + }); + + it("Should not revert if trying to run BLOBBASEFEE in a call", async function () { + const [, hardhatNode] = await HardhatNode.create({ + ...nodeConfig, + hardfork: "cancun", + }); + + const callResult = await hardhatNode.runCall( + { + to: undefined, + from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), + data: toBuffer(BLOBBASEFEE_DEPLOYMENT_BYTECODE), + value: 0n, + gasLimit: 1_000_000n, + }, + 0n + ); + + assert.isUndefined(callResult.error); + }); + + it("Should not revert if trying to run BLOBBASEFEE in a gasEstimate", async function () { + const [, hardhatNode] = await HardhatNode.create({ + ...nodeConfig, + hardfork: "cancun", + }); + + const estimateGasResult = await hardhatNode.estimateGas( + { + to: undefined, + from: toBuffer(DEFAULT_ACCOUNTS_ADDRESSES[0]), + data: toBuffer(BLOBBASEFEE_DEPLOYMENT_BYTECODE), + value: 0n, + gasLimit: 1_000_000n, + }, + 0n + ); + + assert.isUndefined(estimateGasResult.error); + }); + }); + }); }); From 4f1508db30fab82bf80e17ff0354fbe09edfa5c0 Mon Sep 17 00:00:00 2001 From: Luis Schaab Date: Mon, 12 Feb 2024 19:00:39 -0300 Subject: [PATCH 4/5] Add tests for SELFDESTRUCT --- .../hardhat-network/helpers/contracts.ts | 66 +- .../hardhat-network/provider/selfdestruct.ts | 581 +++++++++++++----- 2 files changed, 497 insertions(+), 150 deletions(-) diff --git a/packages/hardhat-core/test/internal/hardhat-network/helpers/contracts.ts b/packages/hardhat-core/test/internal/hardhat-network/helpers/contracts.ts index ee43d8914d..b60ba95b03 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/helpers/contracts.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/helpers/contracts.ts @@ -548,6 +548,8 @@ export const SELFDESTRUCT_CONTRACT = { pragma solidity ^0.8.0; contract SelfDestruct { + uint public x = 1; + constructor() payable {} function sd(address payable to) public { @@ -558,12 +560,17 @@ contract SelfDestruct { bytecode: { linkReferences: {}, object: - "6080604052610118806100136000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80632a89f35c14602d575b600080fd5b60436004803603810190603f919060ba565b6045565b005b8073ffffffffffffffffffffffffffffffffffffffff16ff5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000608c826063565b9050919050565b609a816083565b811460a457600080fd5b50565b60008135905060b4816093565b92915050565b60006020828403121560cd5760cc605e565b5b600060d98482850160a7565b9150509291505056fea2646970667358221220a236a2dc4f9b1a8a1452246a46321de452c82ffd55b9bc1a43a979607da709be64736f6c63430008090033", + "608060405260015f55610180806100155f395ff3fe608060405234801561000f575f80fd5b5060043610610034575f3560e01c80630c55699c146100385780632a89f35c14610056575b5f80fd5b610040610072565b60405161004d91906100a8565b60405180910390f35b610070600480360381019061006b919061011f565b610077565b005b5f5481565b8073ffffffffffffffffffffffffffffffffffffffff16ff5b5f819050919050565b6100a281610090565b82525050565b5f6020820190506100bb5f830184610099565b92915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6100ee826100c5565b9050919050565b6100fe816100e4565b8114610108575f80fd5b50565b5f81359050610119816100f5565b92915050565b5f60208284031215610134576101336100c1565b5b5f6101418482850161010b565b9150509291505056fea2646970667358221220b7b14aedb779482ea200b0fe9a63232aa5e952c4e0996df1322f885904a99b3d64736f6c63430008180033", opcodes: - "PUSH1 0x80 PUSH1 0x40 MSTORE PUSH2 0x118 DUP1 PUSH2 0x13 PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH1 0xF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH1 0x28 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0x2A89F35C EQ PUSH1 0x2D JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x43 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH1 0x3F SWAP2 SWAP1 PUSH1 0xBA JUMP JUMPDEST PUSH1 0x45 JUMP JUMPDEST STOP JUMPDEST DUP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SELFDESTRUCT JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH1 0x0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x8C DUP3 PUSH1 0x63 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x9A DUP2 PUSH1 0x83 JUMP JUMPDEST DUP2 EQ PUSH1 0xA4 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP JUMP JUMPDEST PUSH1 0x0 DUP2 CALLDATALOAD SWAP1 POP PUSH1 0xB4 DUP2 PUSH1 0x93 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x0 PUSH1 0x20 DUP3 DUP5 SUB SLT ISZERO PUSH1 0xCD JUMPI PUSH1 0xCC PUSH1 0x5E JUMP JUMPDEST JUMPDEST PUSH1 0x0 PUSH1 0xD9 DUP5 DUP3 DUP6 ADD PUSH1 0xA7 JUMP JUMPDEST SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 LOG2 CALLDATASIZE LOG2 0xDC 0x4F SWAP12 BYTE DUP11 EQ MSTORE 0x24 PUSH11 0x46321DE452C82FFD55B9BC BYTE NUMBER 0xA9 PUSH26 0x607DA709BE64736F6C6343000809003300000000000000000000 ", - sourceMap: "57:122:0:-:0;;;;;;;;;", + "PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x1 PUSH0 SSTORE PUSH2 0x180 DUP1 PUSH2 0x15 PUSH0 CODECOPY PUSH0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0xF JUMPI PUSH0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH2 0x34 JUMPI PUSH0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0xC55699C EQ PUSH2 0x38 JUMPI DUP1 PUSH4 0x2A89F35C EQ PUSH2 0x56 JUMPI JUMPDEST PUSH0 DUP1 REVERT JUMPDEST PUSH2 0x40 PUSH2 0x72 JUMP JUMPDEST PUSH1 0x40 MLOAD PUSH2 0x4D SWAP2 SWAP1 PUSH2 0xA8 JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH2 0x70 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH2 0x6B SWAP2 SWAP1 PUSH2 0x11F JUMP JUMPDEST PUSH2 0x77 JUMP JUMPDEST STOP JUMPDEST PUSH0 SLOAD DUP2 JUMP JUMPDEST DUP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SELFDESTRUCT JUMPDEST PUSH0 DUP2 SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH2 0xA2 DUP2 PUSH2 0x90 JUMP JUMPDEST DUP3 MSTORE POP POP JUMP JUMPDEST PUSH0 PUSH1 0x20 DUP3 ADD SWAP1 POP PUSH2 0xBB PUSH0 DUP4 ADD DUP5 PUSH2 0x99 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH0 DUP1 REVERT JUMPDEST PUSH0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH0 PUSH2 0xEE DUP3 PUSH2 0xC5 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH2 0xFE DUP2 PUSH2 0xE4 JUMP JUMPDEST DUP2 EQ PUSH2 0x108 JUMPI PUSH0 DUP1 REVERT JUMPDEST POP JUMP JUMPDEST PUSH0 DUP2 CALLDATALOAD SWAP1 POP PUSH2 0x119 DUP2 PUSH2 0xF5 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH0 PUSH1 0x20 DUP3 DUP5 SUB SLT ISZERO PUSH2 0x134 JUMPI PUSH2 0x133 PUSH2 0xC1 JUMP JUMPDEST JUMPDEST PUSH0 PUSH2 0x141 DUP5 DUP3 DUP6 ADD PUSH2 0x10B JUMP JUMPDEST SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 0xB7 0xB1 BLOBBASEFEE 0xED 0xB7 PUSH26 0x482EA200B0FE9A63232AA5E952C4E0996DF1322F885904A99B3D PUSH5 0x736F6C6343 STOP ADDMOD XOR STOP CALLER ", + sourceMap: "57:156:2:-:0;;;101:1;85:17;;57:156;;;;;;", }, abi: [ + { + inputs: [], + stateMutability: "payable", + type: "constructor", + }, { inputs: [ { @@ -577,6 +584,13 @@ contract SelfDestruct { stateMutability: "nonpayable", type: "function", }, + { + inputs: [], + name: "x", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, ], selectors: { sd: "0x2a89f35c", @@ -717,6 +731,52 @@ contract CallSelfDestruct { topics: {}, }; +export const SELFDESTRUCT_DEPLOY_CONTRACT = { + sourceCode: ` +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract SelfDestruct { + uint public x = 1; + + constructor(address payable to) payable { + selfdestruct(to); + } +} +`, + bytecode: { + linkReferences: {}, + object: + "608060405260015f556040516100cc3803806100cc833981810160405281019061002991906100a0565b8073ffffffffffffffffffffffffffffffffffffffff16ff5b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61006f82610046565b9050919050565b61007f81610065565b8114610089575f80fd5b50565b5f8151905061009a81610076565b92915050565b5f602082840312156100b5576100b4610042565b5b5f6100c28482850161008c565b9150509291505056fe", + opcodes: + "PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x1 PUSH0 SSTORE PUSH1 0x40 MLOAD PUSH2 0xCC CODESIZE SUB DUP1 PUSH2 0xCC DUP4 CODECOPY DUP2 DUP2 ADD PUSH1 0x40 MSTORE DUP2 ADD SWAP1 PUSH2 0x29 SWAP2 SWAP1 PUSH2 0xA0 JUMP JUMPDEST DUP1 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND SELFDESTRUCT JUMPDEST PUSH0 DUP1 REVERT JUMPDEST PUSH0 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF DUP3 AND SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH0 PUSH2 0x6F DUP3 PUSH2 0x46 JUMP JUMPDEST SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH2 0x7F DUP2 PUSH2 0x65 JUMP JUMPDEST DUP2 EQ PUSH2 0x89 JUMPI PUSH0 DUP1 REVERT JUMPDEST POP JUMP JUMPDEST PUSH0 DUP2 MLOAD SWAP1 POP PUSH2 0x9A DUP2 PUSH2 0x76 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH0 PUSH1 0x20 DUP3 DUP5 SUB SLT ISZERO PUSH2 0xB5 JUMPI PUSH2 0xB4 PUSH2 0x42 JUMP JUMPDEST JUMPDEST PUSH0 PUSH2 0xC2 DUP5 DUP3 DUP6 ADD PUSH2 0x8C JUMP JUMPDEST SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP INVALID ", + sourceMap: + "57:127:2:-:0;;;101:1;85:17;;109:73;;;;;;;;;;;;;;;;;;;;;:::i;:::-;172:2;159:16;;;88:117:3;197:1;194;187:12;334:126;371:7;411:42;404:5;400:54;389:65;;334:126;;;:::o;466:104::-;511:7;540:24;558:5;540:24;:::i;:::-;529:35;;466:104;;;:::o;576:138::-;657:32;683:5;657:32;:::i;:::-;650:5;647:43;637:71;;704:1;701;694:12;637:71;576:138;:::o;720:159::-;785:5;816:6;810:13;801:22;;832:41;867:5;832:41;:::i;:::-;720:159;;;;:::o;885:367::-;963:6;1012:2;1000:9;991:7;987:23;983:32;980:119;;;1018:79;;:::i;:::-;980:119;1138:1;1163:72;1227:7;1218:6;1207:9;1203:22;1163:72;:::i;:::-;1153:82;;1109:136;885:367;;;;:::o", + }, + abi: [ + { + inputs: [ + { + internalType: "address payable", + name: "to", + type: "address", + }, + ], + stateMutability: "payable", + type: "constructor", + }, + { + inputs: [], + name: "x", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + ], + selectors: {}, + topics: {}, +}; + export const EXAMPLE_TOUCH_ADDRESS_CONTRACT = { sourceCode: `pragma solidity 0.8.17; diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/selfdestruct.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/selfdestruct.ts index 0c69c3dd5e..f49d9e9ae4 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/selfdestruct.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/selfdestruct.ts @@ -1,9 +1,11 @@ import { assert } from "chai"; -import { assertContractFieldEqualNumber } from "../helpers/assertions"; +import { ethers } from "ethers"; +import { assertContractFieldEqualNumber } from "../helpers/assertions"; import { CALL_SELFDESTRUCT_CONTRACT, SELFDESTRUCT_CONTRACT, + SELFDESTRUCT_DEPLOY_CONTRACT, } from "../helpers/contracts"; import { setCWD } from "../helpers/cwd"; import { DEFAULT_ACCOUNTS_ADDRESSES, PROVIDERS } from "../helpers/providers"; @@ -13,158 +15,443 @@ describe("selfdestruct", function () { PROVIDERS.forEach(({ name, useProvider }) => { describe(`${name} provider`, function () { setCWD(); - useProvider(); - - it("should transfer balance", async function () { - const receiverAddress = "0x755113a7411e8788db98d9d74faf2750fb5570a4"; - - const contractAddress = await deployContract( - this.provider, - `0x${SELFDESTRUCT_CONTRACT.bytecode.object}`, - DEFAULT_ACCOUNTS_ADDRESSES[1], - 1000 - ); - - // self destruct contract and send funds to some address - await this.provider.send("eth_sendTransaction", [ - { - from: DEFAULT_ACCOUNTS_ADDRESSES[1], - to: contractAddress, - data: `${ - SELFDESTRUCT_CONTRACT.selectors.sd - }000000000000000000000000${receiverAddress.slice(2)}`, - }, - ]); - const receiverAddressBalance = await this.provider.send( - "eth_getBalance", - [receiverAddress] - ); + describe("When in a pre-cancun hardfork", function () { + useProvider({ hardfork: "shanghai" }); - assert.equal(BigInt(receiverAddressBalance), 1000n); - }); + it("should transfer balance", async function () { + const receiverAddress = "0x755113a7411e8788db98d9d74faf2750fb5570a4"; + + const contractAddress = await deployContract( + this.provider, + `0x${SELFDESTRUCT_CONTRACT.bytecode.object}`, + DEFAULT_ACCOUNTS_ADDRESSES[1], + 1000 + ); + + // self destruct contract and send funds to some address + await this.provider.send("eth_sendTransaction", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[1], + to: contractAddress, + data: `${ + SELFDESTRUCT_CONTRACT.selectors.sd + }000000000000000000000000${receiverAddress.slice(2)}`, + }, + ]); + + const contractAddressBalance = await this.provider.send( + "eth_getBalance", + [contractAddress] + ); + const receiverAddressBalance = await this.provider.send( + "eth_getBalance", + [receiverAddress] + ); + + assert.equal(BigInt(contractAddressBalance), 0n); + assert.equal(BigInt(receiverAddressBalance), 1000n); + }); + + it("shouldn't have code or balance after selfdestructing", async function () { + const receiverAddress = "0x755113a7411e8788db98d9d74faf2750fb5570a4"; - it("shouldn't have code or balance after selfdestructing", async function () { - const receiverAddress = "0x755113a7411e8788db98d9d74faf2750fb5570a4"; - - const contractAddress = await deployContract( - this.provider, - `0x${SELFDESTRUCT_CONTRACT.bytecode.object}`, - DEFAULT_ACCOUNTS_ADDRESSES[1], - 1000 - ); - - // self destruct contract and send funds to some address - await this.provider.send("eth_sendTransaction", [ - { - from: DEFAULT_ACCOUNTS_ADDRESSES[1], - to: contractAddress, - data: `${ - SELFDESTRUCT_CONTRACT.selectors.sd + const contractAddress = await deployContract( + this.provider, + `0x${SELFDESTRUCT_CONTRACT.bytecode.object}`, + DEFAULT_ACCOUNTS_ADDRESSES[1], + 1000 + ); + + // self destruct contract and send funds to some address + await this.provider.send("eth_sendTransaction", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[1], + to: contractAddress, + data: `${ + SELFDESTRUCT_CONTRACT.selectors.sd + }000000000000000000000000${receiverAddress.slice(2)}`, + }, + ]); + + const contractBalance = await this.provider.send("eth_getBalance", [ + contractAddress, + ]); + const contractCode = await this.provider.send("eth_getCode", [ + contractAddress, + ]); + + assert.equal(BigInt(contractBalance), 0n); + assert.equal(contractCode, "0x"); + }); + + it("within the transaction, should have code after selfdestructing but no balance", async function () { + const receiverAddress = "0x755113a7411e8788db98d9d74faf2750fb5570a4"; + + // the constructor of this contract deploys a SelfDestruct contract, and + // records its code length and balance before and after making it + // selfdestruct + const contractAddress = await deployContract( + this.provider, + `0x${ + CALL_SELFDESTRUCT_CONTRACT.bytecode.object }000000000000000000000000${receiverAddress.slice(2)}`, - }, - ]); - - const contractBalance = await this.provider.send("eth_getBalance", [ - contractAddress, - ]); - const contractCode = await this.provider.send("eth_getCode", [ - contractAddress, - ]); - - assert.equal(BigInt(contractBalance), 0n); - assert.equal(contractCode, "0x"); + DEFAULT_ACCOUNTS_ADDRESSES[1], + 1000 + ); + + // check contract state within tx + const { + balanceBeforeSelfDestruct, + balanceAfterSelfDestruct, + receiverBalanceBeforeSelfDestruct, + receiverBalanceAfterSelfDestruct, + codeLengthBeforeSelfDestruct, + codeLengthAfterSelfDestruct, + } = CALL_SELFDESTRUCT_CONTRACT.selectors; + + // before selfdestruct + await assertContractFieldEqualNumber( + this.provider, + contractAddress, + balanceBeforeSelfDestruct, + 1000n + ); + await assertContractFieldEqualNumber( + this.provider, + contractAddress, + receiverBalanceBeforeSelfDestruct, + 0n + ); + await assertContractFieldEqualNumber( + this.provider, + contractAddress, + codeLengthBeforeSelfDestruct, + 280n + ); + + // after selfdestruct + await assertContractFieldEqualNumber( + this.provider, + contractAddress, + balanceAfterSelfDestruct, + 0n + ); + await assertContractFieldEqualNumber( + this.provider, + contractAddress, + receiverBalanceAfterSelfDestruct, + 1000n + ); + await assertContractFieldEqualNumber( + this.provider, + contractAddress, + codeLengthAfterSelfDestruct, + 280n // the code is not immediately removed + ); + + // check conditions after tx + let selfDestructAddress = await this.provider.send("eth_call", [ + { + to: contractAddress, + data: `${CALL_SELFDESTRUCT_CONTRACT.selectors.selfDestruct}`, + }, + ]); + selfDestructAddress = `0x${selfDestructAddress.slice(-40)}`; // unpad address + + const contractBalanceAfterTx = await this.provider.send( + "eth_getBalance", + [selfDestructAddress] + ); + const contractCodeAfterTx = await this.provider.send("eth_getCode", [ + selfDestructAddress, + ]); + const receiverAddressBalanceAfterTx = await this.provider.send( + "eth_getBalance", + [receiverAddress] + ); + + assert.equal(BigInt(contractBalanceAfterTx), 0n); + assert.equal(contractCodeAfterTx, "0x"); + assert.equal(BigInt(receiverAddressBalanceAfterTx), 1000n); + }); + + describe("when selfdestructing during a deployment", function () { + it("should delete the code, storage and nonce", async function () { + const receiverAddress = + "0x755113a7411e8788db98d9d74faf2750fb5570a4"; + + const abiCoder = new ethers.AbiCoder(); + const constructorParameters = abiCoder + .encode(["address"], [receiverAddress]) + .slice(2); + + const contractAddress = await deployContract( + this.provider, + `0x${SELFDESTRUCT_DEPLOY_CONTRACT.bytecode.object}${constructorParameters}`, + DEFAULT_ACCOUNTS_ADDRESSES[1], + 1000 + ); + + const contractCode = await this.provider.send("eth_getCode", [ + contractAddress, + ]); + assert.equal(contractCode, "0x"); + + const storage = await this.provider.send("eth_getStorageAt", [ + contractAddress, + "0x0", + ]); + assert.equal(BigInt(storage), 0n); + + const nonce = await this.provider.send("eth_getTransactionCount", [ + contractAddress, + ]); + assert.equal(nonce, "0x0"); + }); + + it("should transfer the balance", async function () { + const receiverAddress = + "0x755113a7411e8788db98d9d74faf2750fb5570a4"; + + const abiCoder = new ethers.AbiCoder(); + const constructorParameters = abiCoder + .encode(["address"], [receiverAddress]) + .slice(2); + + const contractAddress = await deployContract( + this.provider, + `0x${SELFDESTRUCT_DEPLOY_CONTRACT.bytecode.object}${constructorParameters}`, + DEFAULT_ACCOUNTS_ADDRESSES[1], + 1000 + ); + + const contractAddressBalance = await this.provider.send( + "eth_getBalance", + [contractAddress] + ); + const receiverAddressBalance = await this.provider.send( + "eth_getBalance", + [receiverAddress] + ); + + assert.equal(BigInt(contractAddressBalance), 0n); + assert.equal(BigInt(receiverAddressBalance), 1000n); + }); + + it("should end with a balance of 0 if the target of the transfer is the contract itself", async function () { + const abiCoder = new ethers.AbiCoder(); + const constructorParameters = abiCoder + .encode(["address"], [DEFAULT_ACCOUNTS_ADDRESSES[1]]) + .slice(2); + + const contractAddress = await deployContract( + this.provider, + `0x${SELFDESTRUCT_DEPLOY_CONTRACT.bytecode.object}${constructorParameters}`, + DEFAULT_ACCOUNTS_ADDRESSES[1], + 1000 + ); + + const contractAddressBalance = await this.provider.send( + "eth_getBalance", + [contractAddress] + ); + + assert.equal(BigInt(contractAddressBalance), 0n); + }); + }); }); - it("within the transaction, should have code after selfdestructing but no balance", async function () { - const receiverAddress = "0x755113a7411e8788db98d9d74faf2750fb5570a4"; - - // the constructor of this contract deploys a SelfDestruct contract, and - // records its code length and balance before and after making it - // selfdestruct - const contractAddress = await deployContract( - this.provider, - `0x${ - CALL_SELFDESTRUCT_CONTRACT.bytecode.object - }000000000000000000000000${receiverAddress.slice(2)}`, - DEFAULT_ACCOUNTS_ADDRESSES[1], - 1000 - ); - - // check contract state within tx - const { - balanceBeforeSelfDestruct, - balanceAfterSelfDestruct, - receiverBalanceBeforeSelfDestruct, - receiverBalanceAfterSelfDestruct, - codeLengthBeforeSelfDestruct, - codeLengthAfterSelfDestruct, - } = CALL_SELFDESTRUCT_CONTRACT.selectors; - - // before selfdestruct - await assertContractFieldEqualNumber( - this.provider, - contractAddress, - balanceBeforeSelfDestruct, - 1000n - ); - await assertContractFieldEqualNumber( - this.provider, - contractAddress, - receiverBalanceBeforeSelfDestruct, - 0n - ); - await assertContractFieldEqualNumber( - this.provider, - contractAddress, - codeLengthBeforeSelfDestruct, - 280n - ); - - // after selfdestruct - await assertContractFieldEqualNumber( - this.provider, - contractAddress, - balanceAfterSelfDestruct, - 0n - ); - await assertContractFieldEqualNumber( - this.provider, - contractAddress, - receiverBalanceAfterSelfDestruct, - 1000n - ); - await assertContractFieldEqualNumber( - this.provider, - contractAddress, - codeLengthAfterSelfDestruct, - 280n // the code is not immediately removed - ); - - // check conditions after tx - let selfDestructAddress = await this.provider.send("eth_call", [ - { - to: contractAddress, - data: `${CALL_SELFDESTRUCT_CONTRACT.selectors.selfDestruct}`, - }, - ]); - selfDestructAddress = `0x${selfDestructAddress.slice(-40)}`; // unpad address - - const contractBalanceAfterTx = await this.provider.send( - "eth_getBalance", - [selfDestructAddress] - ); - const contractCodeAfterTx = await this.provider.send("eth_getCode", [ - selfDestructAddress, - ]); - const receiverAddressBalanceAfterTx = await this.provider.send( - "eth_getBalance", - [receiverAddress] - ); - - assert.equal(BigInt(contractBalanceAfterTx), 0n); - assert.equal(contractCodeAfterTx, "0x"); - assert.equal(BigInt(receiverAddressBalanceAfterTx), 1000n); + describe("When in the cancun hardfork", function () { + useProvider({ hardfork: "cancun" }); + + describe("when calling a function that selfdestructs", function () { + it("shouldn't delete the code, the storage or the nonce", async function () { + const receiverAddress = + "0x755113a7411e8788db98d9d74faf2750fb5570a4"; + + const contractAddress = await deployContract( + this.provider, + `0x${SELFDESTRUCT_CONTRACT.bytecode.object}`, + DEFAULT_ACCOUNTS_ADDRESSES[1], + 1000 + ); + + // self destruct contract and send funds to some address + await this.provider.send("eth_sendTransaction", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[1], + to: contractAddress, + data: `${ + SELFDESTRUCT_CONTRACT.selectors.sd + }000000000000000000000000${receiverAddress.slice(2)}`, + }, + ]); + + const contractCode = await this.provider.send("eth_getCode", [ + contractAddress, + ]); + assert.notEqual(contractCode, "0x"); + + const storage = await this.provider.send("eth_getStorageAt", [ + contractAddress, + "0x0", + ]); + assert.equal(BigInt(storage), 1n); + + const nonce = await this.provider.send("eth_getTransactionCount", [ + contractAddress, + ]); + assert.equal(nonce, "0x1"); + }); + + it("should transfer the balance", async function () { + const receiverAddress = + "0x755113a7411e8788db98d9d74faf2750fb5570a4"; + + const contractAddress = await deployContract( + this.provider, + `0x${SELFDESTRUCT_CONTRACT.bytecode.object}`, + DEFAULT_ACCOUNTS_ADDRESSES[1], + 1000 + ); + + // self destruct contract and send funds to some address + await this.provider.send("eth_sendTransaction", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[1], + to: contractAddress, + data: `${ + SELFDESTRUCT_CONTRACT.selectors.sd + }000000000000000000000000${receiverAddress.slice(2)}`, + }, + ]); + + const contractAddressBalance = await this.provider.send( + "eth_getBalance", + [contractAddress] + ); + const receiverAddressBalance = await this.provider.send( + "eth_getBalance", + [receiverAddress] + ); + + assert.equal(BigInt(contractAddressBalance), 0n); + assert.equal(BigInt(receiverAddressBalance), 1000n); + }); + + it("shouldn't change the balance if the target of the transfer is the contract itself", async function () { + const contractAddress = await deployContract( + this.provider, + `0x${SELFDESTRUCT_CONTRACT.bytecode.object}`, + DEFAULT_ACCOUNTS_ADDRESSES[1], + 1000 + ); + + // self destruct contract and send funds to some address + await this.provider.send("eth_sendTransaction", [ + { + from: DEFAULT_ACCOUNTS_ADDRESSES[1], + to: contractAddress, + data: `${ + SELFDESTRUCT_CONTRACT.selectors.sd + }000000000000000000000000${contractAddress.slice(2)}`, + }, + ]); + + const contractAddressBalance = await this.provider.send( + "eth_getBalance", + [contractAddress] + ); + + assert.equal(BigInt(contractAddressBalance), 1000n); + }); + }); + + describe("when selfdestructing during a deployment", function () { + it("should delete the code, storage and nonce", async function () { + const receiverAddress = + "0x755113a7411e8788db98d9d74faf2750fb5570a4"; + + const abiCoder = new ethers.AbiCoder(); + const constructorParameters = abiCoder + .encode(["address"], [receiverAddress]) + .slice(2); + + const contractAddress = await deployContract( + this.provider, + `0x${SELFDESTRUCT_DEPLOY_CONTRACT.bytecode.object}${constructorParameters}`, + DEFAULT_ACCOUNTS_ADDRESSES[1], + 1000 + ); + + const contractCode = await this.provider.send("eth_getCode", [ + contractAddress, + ]); + assert.equal(contractCode, "0x"); + + const storage = await this.provider.send("eth_getStorageAt", [ + contractAddress, + "0x0", + ]); + assert.equal(BigInt(storage), 0n); + + const nonce = await this.provider.send("eth_getTransactionCount", [ + contractAddress, + ]); + assert.equal(nonce, "0x0"); + }); + + it("should transfer the balance", async function () { + const receiverAddress = + "0x755113a7411e8788db98d9d74faf2750fb5570a4"; + + const abiCoder = new ethers.AbiCoder(); + const constructorParameters = abiCoder + .encode(["address"], [receiverAddress]) + .slice(2); + + const contractAddress = await deployContract( + this.provider, + `0x${SELFDESTRUCT_DEPLOY_CONTRACT.bytecode.object}${constructorParameters}`, + DEFAULT_ACCOUNTS_ADDRESSES[1], + 1000 + ); + + const contractAddressBalance = await this.provider.send( + "eth_getBalance", + [contractAddress] + ); + const receiverAddressBalance = await this.provider.send( + "eth_getBalance", + [receiverAddress] + ); + + assert.equal(BigInt(contractAddressBalance), 0n); + assert.equal(BigInt(receiverAddressBalance), 1000n); + }); + + it("should end with a balance of 0 if the target of the transfer is the contract itself", async function () { + const abiCoder = new ethers.AbiCoder(); + const constructorParameters = abiCoder + .encode(["address"], [DEFAULT_ACCOUNTS_ADDRESSES[1]]) + .slice(2); + + const contractAddress = await deployContract( + this.provider, + `0x${SELFDESTRUCT_DEPLOY_CONTRACT.bytecode.object}${constructorParameters}`, + DEFAULT_ACCOUNTS_ADDRESSES[1], + 1000 + ); + + const contractAddressBalance = await this.provider.send( + "eth_getBalance", + [contractAddress] + ); + + assert.equal(BigInt(contractAddressBalance), 0n); + }); + }); }); }); }); From 731bdff5c5d1aeafd48aa4014f7523e1a413db8d Mon Sep 17 00:00:00 2001 From: Luis Schaab Date: Tue, 13 Feb 2024 13:21:34 -0300 Subject: [PATCH 5/5] Update packages/hardhat-core/test/internal/hardhat-network/provider/node.ts Co-authored-by: Franco Victorio --- .../hardhat-core/test/internal/hardhat-network/provider/node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts b/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts index 1111b628ae..e0c3312cfc 100644 --- a/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts +++ b/packages/hardhat-core/test/internal/hardhat-network/provider/node.ts @@ -1379,7 +1379,7 @@ describe("HardhatNode", () => { describe("Transient storage", function () { const TLOAD_DEPLOYMENT_BYTECODE = "0x60FF5c"; // PUSH1 FF TLOAD - const TSTORE_DEPLOYMENT_BYTECODE = "0x60FF60FF5d"; // PUSH1 FF TLOAD + const TSTORE_DEPLOYMENT_BYTECODE = "0x60FF60FF5d"; // PUSH1 FF PUSH1 FF TSTORE const nodeConfig: LocalNodeConfig = { automine: true,