Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions utils/e2e-tests/ts/lib/abis/deposit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// pragma solidity ^0.8.13;
//
// contract Deposit {
// constructor() payable {}
//
// function withdrawAll() external {
// (bool success, ) = msg.sender.call{value: address(this).balance}("");
// require(success, "Transfer failed!");
// }
// }

export default {
abi: [
{
type: "constructor",
inputs: [],
stateMutability: "payable",
},
{
type: "function",
name: "withdrawAll",
inputs: [],
outputs: [],
stateMutability: "nonpayable",
},
],
bytecode:
"0x608060405260f4806100105f395ff3fe6080604052348015600e575f5ffd5b50600436106026575f3560e01c8063853828b614602a575b5f5ffd5b60306032565b005b6040515f90339047908381818185875af1925050503d805f8114606f576040519150601f19603f3d011682016040523d82523d5f602084013e6074565b606091505b505090508060bb5760405162461bcd60e51b815260206004820152601060248201526f5472616e73666572206661696c65642160801b604482015260640160405180910390fd5b5056fea2646970667358221220ce7ea06cb9a3afb69b76e54771df61ff4f8e2efa0fabbb5f7ca5fb868448768664736f6c634300081c0033",
} as const;
71 changes: 71 additions & 0 deletions utils/e2e-tests/ts/tests/eth/preserveNonceOnZeroContractBalance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { describe, it, expect, assert } from "vitest";
import { RunNodeState, runNode } from "../../lib/node";
import * as eth from "../../lib/ethViem";
import deposit from "../../lib/abis/deposit";
import "../../lib/expect";
import { beforeEachWithCleanup } from "../../lib/lifecycle";

describe("contract account's nonce", () => {
let node: RunNodeState;
let publicClient: eth.PublicClientWebSocket;
let devClients: eth.DevClientsWebSocket;
beforeEachWithCleanup(async (cleanup) => {
node = runNode({ args: ["--dev", "--tmp"] }, cleanup.push);

await node.waitForBoot;

publicClient = eth.publicClientFromNodeWebSocket(node, cleanup.push);
devClients = eth.devClientsFromNodeWebSocket(node, cleanup.push);
}, 60 * 1000);

// See also: https://github.com/humanode-network/humanode/issues/1402
it("is being preserved after zeroing the balance", async () => {
const [alice, _] = devClients;

const deployContractTxHash = await alice.deployContract({
abi: deposit.abi,
bytecode: deposit.bytecode,
value: 1n, // Even the smallest deposit is enough
gas: 150_274n,
});
const deployContractTxReceipt =
await publicClient.waitForTransactionReceipt({
hash: deployContractTxHash,
timeout: 18_000,
});
expect(deployContractTxReceipt.status).toBe("success");
const contract = deployContractTxReceipt.contractAddress;
assert(contract);

// The nonce of the contract account immediately after creation.
const INITIAL_NONCE = 1;

// EIP-161 https://eips.ethereum.org/EIPS/eip-161:
//
// > At the end of the transaction, any account touched by ... that transaction which is now *empty*
// > SHALL instead become non-existent (i.e. deleted).
// > Where: ...
// > An account is considered *empty* when it has no code and zero nonce and zero balance.
//
// So once the balance is zeroed below, the account state shouldn't be deleted because
// the account contains the contract code.
const withdrawalTx = await alice.writeContract({
address: contract,
abi: deposit.abi,
functionName: "withdrawAll",
gas: 30_585n,
});
const withdrawalReceipt = await publicClient.waitForTransactionReceipt({
hash: withdrawalTx,
timeout: 18_000,
});
expect(withdrawalReceipt.status, "status of withdrawal").toBe("success");

// Ethereum RPC returns the account nonce in the `eth_getTransactionCount` RPC call.
// https://github.com/humanode-network/frontier/blob/1afab28e8d5aebe7d44f9043b3ba19e9555123dc/client/rpc/src/eth/state.rs#L116-L169
const nonce = await publicClient.getTransactionCount({
address: contract,
});
expect(nonce).toBe(INITIAL_NONCE);
});
});