Skip to content

feat: add E2E tests for EIP-6780 (SELFDESTRUCT only in same transaction)#8

Open
BenderVeChain wants to merge 9 commits intomainfrom
feat/eip-6780-selfdestruct-tests
Open

feat: add E2E tests for EIP-6780 (SELFDESTRUCT only in same transaction)#8
BenderVeChain wants to merge 9 commits intomainfrom
feat/eip-6780-selfdestruct-tests

Conversation

@BenderVeChain
Copy link
Copy Markdown
Collaborator

Summary

Adds end-to-end tests for EIP-6780, implemented in vechain/thor#1590.

EIP-6780 restricts SELFDESTRUCT: a contract can only be fully deleted (code + storage removed) if SELFDESTRUCT is executed in the same transaction that created it. Calling SELFDESTRUCT on a pre-existing contract only transfers the balance — the contract code and storage persist.

Test cases

Test Behaviour verified
TestEIP6780_PreFork_SelfDestructExecutes Pre-fork: SELFDESTRUCT (0xff) is a valid opcode and does not revert
TestEIP6780_PreFork_OpcodeValid Opcode remains valid both pre- and post-INTERSTELLAR fork
TestEIP6780_PostFork_PreExistingContractNotDeleted Post-fork: SELFDESTRUCT on a pre-existing contract transfers balance only — code and storage survive
TestEIP6780_PostFork_SameTxCreationDeleted Post-fork: factory deploys a child and calls SELFDESTRUCT on it in the same tx — child has no code afterwards (same-tx exception)
TestEIP6780_PostFork_SelfDestructToSelfIsNoop Post-fork: SELFDESTRUCT with self as beneficiary is a no-op — contract survives and balance is unchanged

Implementation notes

  • All tests use raw EVM bytecode (no Solidity compiler), consistent with the existing eip5656 pattern.
  • Real transactions (helper.BuildTx / SendTransaction / WaitForReceipt) are used for all persistent-state assertions.
  • InspectClauses is used for stateless opcode-validity checks (pre/post-fork comparisons).
  • The factory test uses a hand-assembled bytecode that runs CREATE + CALL(child) in a single execution context to exercise the EIP-6780 same-tx deletion path.
  • Child contract address is derived via thor.CreateContractAddress(txID, clauseIndex, creationCount).

Related

Hermes Agent and others added 9 commits April 9, 2026 16:44
…lity

Replace hand-assembled EVM bytecode with Solidity contracts:
- contracts/Destructible.sol: exposes destroy(recipient) via SELFDESTRUCT
- contracts/Factory.sol: deploys a child and calls SELFDESTRUCT in same tx
- contracts/gen.go: go:generate directive (solc:0.8.28 --evm-version cancun)
- contracts/compiled/: pre-compiled ABI + bin artifacts

Test logic is unchanged; all 4 EIP-6780 behaviours are covered:
1. Pre-fork: SELFDESTRUCT opcode is valid (does not revert)
2. Post-fork: pre-existing contract not deleted (code persists after destroy())
3. Post-fork: same-tx creation IS deleted (Factory.deployAndDestroy())
4. Post-fork: SELFDESTRUCT to self is a no-op (code + balance unchanged)
Test 1 (TestEIP6780_PostFork_PreExistingContractNotDeleted):
- Was checking HasCode at PostForkRevision (block 1), but the deploy tx
  may be mined at block 2+, so block 1 state doesn't have the contract.
- Fix: use default 'best' revision for the post-deploy HasCode check.

Test 2 (TestEIP6780_PostFork_SelfDestructToSelfIsNoop):
- Was sending a plain VET transfer to Destructible which had no receive()
  function — Solidity reverts plain transfers to contracts without a
  receive()/fallback().
- Fix: add 'receive() external payable {}' to Destructible.sol and
  recompile artifacts.
Root causes (both confirmed by running tests against a live network):

1. TestEIP6780_PostFork_PreExistingContractNotDeleted:
   The check at line 158 (HasCode after SELFDESTRUCT on pre-existing contract)
   was failing because the default Thor branch ('evm-upgrades') does not
   contain EIP-6780 yet — vechain/thor#1590 is still open. Update
   defaultThorBranch to 'moglu/eip6780' so local 'make test' and CI (when
   THOR_BRANCH is not set) use the branch that implements opSuicide6780.

2. TestEIP6780_PostFork_SelfDestructToSelfIsNoop:
   The funding tx (Step 2) used 21_000 gas. In VeChain, 21_000 is the base
   clause cost; calling receive() on a contract requires slightly more
   (~21055 total). Increased to 50_000 so the transfer succeeds.

Both fixes verified green locally with THOR_EXISTING_PATH=/tmp/thor_eip6780.
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

Successfully merging this pull request may close these issues.

2 participants