Skip to content

Commit

Permalink
E2E-test: withdrawAll from the contracts' factory between two CREATEs
Browse files Browse the repository at this point in the history
  • Loading branch information
quasiyoke committed Jan 13, 2025
1 parent 239869c commit a4bd625
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
!/.yarn/releases
!/.yarn/sdks
!/.yarn/versions
/utils/e2e-tests/ts/node_modules
75 changes: 75 additions & 0 deletions utils/e2e-tests/ts/lib/abis/contractsFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// pragma solidity ^0.8.13;
//
// contract ContractsFactory {
// address private owner;
//
// /**
// * Sets contract deployer as owner.
// */
// constructor() {
// owner = msg.sender; // 'msg.sender' is sender of current call, contract deployer for a constructor
// }
//
// function build()
// public
// returns (Item itemAddress)
// {
// return new Item();
// }
//
// function deposit() external payable {}
//
// function withdrawAll() external isOwner {
// (bool success,) = owner.call{value:address(this).balance}("");
// require(success, "Transfer failed!");
// }
//
// /**
// * Makes sure if caller is owner.
// */
// modifier isOwner() {
// require(msg.sender == owner, "Caller is not owner");
// _;
// }
// }
//
// contract Item {}

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

describe("contracts factory", () => {
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);

it("creates contracts at unique addresses", async () => {
const [alice, bob] = devClients;

const deployContractTxHash = await alice.deployContract({
abi: contractsFactory.abi,
bytecode: contractsFactory.bytecode,
});
const deployContractTxReceipt =
await publicClient.waitForTransactionReceipt({
hash: deployContractTxHash,
timeout: 18_000,
});
const factoryAddress = deployContractTxReceipt.contractAddress!;

const depositPromise = alice
.writeContract({
address: factoryAddress,
abi: contractsFactory.abi,
functionName: "deposit",
value: 1n, // Even the smallest deposit is enough
})
.then((depositTx) =>
publicClient.waitForTransactionReceipt({
hash: depositTx,
timeout: 18_000,
}),
);
const item1AddressPromise = build(factoryAddress, bob, publicClient);
// Trying to wait for responses in parallel is worth it, since the test is already too long.
// Ordering between `deposit` and 1st `build` doesn't matter.
// Contract factory's `CREATE` nonce for the 1st `build` will be 1.
const [item1Address] = await Promise.all([
item1AddressPromise,
depositPromise,
]);

// If there's a bug in the EVM, it will clear the contract state after `withdrawAll`.
const withdrawalTx = await alice.writeContract({
address: factoryAddress,
abi: contractsFactory.abi,
functionName: "withdrawAll",
});
await publicClient.waitForTransactionReceipt({
hash: withdrawalTx,
timeout: 18_000,
});

// Contract factory's `CREATE` nonce for the 2nd `build` should be 2 (in the buggy EVM: 0).
await build(factoryAddress, bob, publicClient);

// Contract factory's `CREATE` nonce for the 3rd `build` should be 3.
// In the buggy EVM nonce = 1, the same as for the 1st `build`; transaction will be reverted.
const item3Address = await build(factoryAddress, bob, publicClient);
expect(item1Address).not.toBe(item3Address);
});
});

async function build(
factoryAddress: `0x${string}`,
client: eth.DevClientsWebSocket[0],
publicClient: eth.PublicClientWebSocket,
): Promise<`0x${string}`> {
const { request: buildingRequest, result: itemAddress } =
await publicClient.simulateContract({

Check failure on line 84 in utils/e2e-tests/ts/tests/eth/contractsFactory.ts

View workflow job for this annotation

GitHub Actions / End-to-end tests / base

tests/eth/contractsFactory.ts > contracts factory > creates contracts at unique addresses

ContractFunctionExecutionError: The contract function "build" reverted with the following reason: VM Exception while processing transaction: revert Contract Call: address: 0xc01ee7f10ea4af4673cfff62710e1d7792aba8f3 function: build() sender: 0x3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0 Docs: https://viem.sh/docs/contract/simulateContract Version: viem@2.22.2 ❯ getContractError ../../../node_modules/viem/utils/errors/getContractError.ts:78:10 ❯ simulateContract ../../../node_modules/viem/actions/public/simulateContract.ts:308:11 ❯ build tests/eth/contractsFactory.ts:84:5 ❯ tests/eth/contractsFactory.ts:73:26 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { details: undefined, docsPath: '/docs/contract/simulateContract', metaMessages: [ 'Contract Call:', ' address: 0xc01ee7f10ea4af4673cfff62710e1d7792aba8f3\n function: build()\n sender: 0x3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0' ], shortMessage: 'The contract function "build" reverted with the following reason:\nVM Exception while processing transaction: revert', version: '2.22.2', abi: [ { type: 'constructor', inputs: [], stateMutability: 'nonpayable' }, { type: 'function', name: 'build', inputs: [], outputs: [ { name: 'itemAddress', type: 'address', internalType: 'contract Item' } ], stateMutability: 'nonpayable' }, { type: 'function', name: 'deposit', inputs: [], outputs: [], stateMutability: 'payable' }, { type: 'function', name: 'withdrawAll', inputs: [], outputs: [], stateMutability: 'nonpayable' } ], args: undefined, contractAddress: '0xc01ee7f10ea4af4673cfff62710e1d7792aba8f3', formattedArgs: undefined, functionName: 'build', sender: '0x3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0', walk: 'Function<walk>' } Caused by: Caused by: ContractFunctionRevertedError: The contract function "build" reverted with the following reason: VM Exception while processing transaction: revert Version: viem@2.22.2 ❯ ../../../node_modules/viem/utils/errors/getContractError.ts:65:14 ❯ getContractError ../../../node_modules/viem/utils/errors/getContractError.ts:76:5 ❯ simulateContract ../../../node_modules/viem/actions/public/simulateContract.ts:308:11 ❯ build tests/eth/contractsFactory.ts:84:5 ❯ tests/eth/contractsFactory.ts:73:26 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { details: undefined, docsPath: undefined, metaMessages: undefined, shortMessage: 'The contract function "build" reverted with the following reason:\nVM Exception while processing transaction: revert', version: '2.22.2', data: undefined, reason: 'VM Exception while processing transaction: revert', signature: undefined, walk: 'Function<walk>' }
address: factoryAddress,
abi: contractsFactory.abi,
functionName: "build",
account: client.account,
});
const buildingTx = await client.writeContract(buildingRequest);
await publicClient.waitForTransactionReceipt({
hash: buildingTx,
timeout: 18_000,
});
return itemAddress;
}
2 changes: 1 addition & 1 deletion utils/e2e-tests/ts/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ export default defineConfig({
singleThread: true,
},
},
testTimeout: 30_000,
testTimeout: 40_000,
},
});

0 comments on commit a4bd625

Please sign in to comment.