diff --git a/tests/e2e-evm/.solhint.json b/tests/e2e-evm/.solhint.json index ce2220e0b..3d0f38d14 100644 --- a/tests/e2e-evm/.solhint.json +++ b/tests/e2e-evm/.solhint.json @@ -1,3 +1,6 @@ { - "extends": "solhint:recommended" + "extends": "solhint:recommended", + "rules": { + "func-visibility": ["warn", { "ignoreConstructors": true }] + } } diff --git a/tests/e2e-evm/contracts/ABI_BasicTests.sol b/tests/e2e-evm/contracts/ABI_BasicTests.sol index 0548dfda0..f1077498d 100644 --- a/tests/e2e-evm/contracts/ABI_BasicTests.sol +++ b/tests/e2e-evm/contracts/ABI_BasicTests.sol @@ -13,8 +13,10 @@ contract Caller { (bool success, bytes memory result) = to.call{value: msg.value}(data); if (!success) { - if (result.length == 0) revert(); + // solhint-disable-next-line gas-custom-errors + if (result.length == 0) revert("reverted with no reason"); + // solhint-disable-next-line no-inline-assembly assembly { revert(add(32, result), mload(result)) } @@ -24,11 +26,14 @@ contract Caller { // TODO: Callcode function functionDelegateCall(address to, bytes calldata data) external { + // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory result) = to.delegatecall(data); if (!success) { - if (result.length == 0) revert(); + // solhint-disable-next-line gas-custom-errors + if (result.length == 0) revert("reverted with no reason"); + // solhint-disable-next-line no-inline-assembly assembly { revert(add(32, result), mload(result)) } @@ -39,8 +44,10 @@ contract Caller { (bool success, bytes memory result) = to.staticcall(data); if (!success) { - if (result.length == 0) revert(); + // solhint-disable-next-line gas-custom-errors + if (result.length == 0) revert("reverted with no reason"); + // solhint-disable-next-line no-inline-assembly assembly { revert(add(32, result), mload(result)) } @@ -52,7 +59,7 @@ contract Caller { // High level caller // contract NoopCaller { - NoopNoReceiveNoFallback target; + NoopNoReceiveNoFallback private target; constructor(NoopNoReceiveNoFallback _target) { target = _target; diff --git a/tests/e2e-evm/contracts/ABI_DisabledTests.sol b/tests/e2e-evm/contracts/ABI_DisabledTests.sol index 408dd65ed..aaa090deb 100644 --- a/tests/e2e-evm/contracts/ABI_DisabledTests.sol +++ b/tests/e2e-evm/contracts/ABI_DisabledTests.sol @@ -1,37 +1,38 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; -import "./ABI_BasicTests.sol"; +import "./ABI_BasicTests.sol" as ABI_BasicTests; // // Disabled contract that is payable and callable with any calldata (receive + fallback) // -contract NoopDisabledMock is NoopReceivePayableFallback{ +contract NoopDisabledMock is ABI_BasicTests.NoopReceivePayableFallback { // solc-ignore-next-line func-mutability function noopNonpayable() external { - mockRevert(); + mockRevert(); } function noopPayable() external payable { - mockRevert(); + mockRevert(); } // solc-ignore-next-line func-mutability function noopView() external view { - mockRevert(); + mockRevert(); } function noopPure() external pure { - mockRevert(); + mockRevert(); } receive() external payable { - mockRevert(); + mockRevert(); } fallback() external payable { - mockRevert(); + mockRevert(); } // // Mimic revert + revert reason // function mockRevert() private pure { - revert("call not allowed to disabled contract"); + // solhint-disable-next-line reason-string, gas-custom-errors + revert("call not allowed to disabled contract"); } } diff --git a/tests/e2e-evm/hardhat.config.ts b/tests/e2e-evm/hardhat.config.ts index 10b776fb5..f5cc853c2 100644 --- a/tests/e2e-evm/hardhat.config.ts +++ b/tests/e2e-evm/hardhat.config.ts @@ -10,6 +10,7 @@ import "hardhat-ignore-warnings"; // Chai setup // chai.use(chaiAsPromised); +chai.config.truncateThreshold = 0; // // Load HRE extensions diff --git a/tests/e2e-evm/test/abi_basic.test.ts b/tests/e2e-evm/test/abi_basic.test.ts index 02094fc06..185232f94 100644 --- a/tests/e2e-evm/test/abi_basic.test.ts +++ b/tests/e2e-evm/test/abi_basic.test.ts @@ -1,13 +1,18 @@ import hre from "hardhat"; import type { ArtifactsMap } from "hardhat/types/artifacts"; -import type { - PublicClient, - WalletClient, - ContractName, - GetContractReturnType, -} from "@nomicfoundation/hardhat-viem/types"; +import type { PublicClient, WalletClient, GetContractReturnType } from "@nomicfoundation/hardhat-viem/types"; import { expect } from "chai"; -import { Address, Hex, toFunctionSelector, toFunctionSignature, concat, encodeFunctionData } from "viem"; +import { + Address, + Hex, + toFunctionSelector, + toFunctionSignature, + concat, + encodeFunctionData, + CallParameters, + Chain, + isHex, +} from "viem"; import { Abi } from "abitype"; import { getAbiFallbackFunction, getAbiReceiveFunction } from "./helpers/abi"; import { whaleAddress } from "./addresses"; @@ -17,23 +22,25 @@ const contractCallerGas = defaultGas + 10000n; interface ContractTestCase { interface: keyof ArtifactsMap; - mock: ContractName; + // Ensures contract name ends with "Mock", but does not enforce the prefix + // matches the interface. + mock: `${keyof ArtifactsMap}Mock`; precompile: Address; - caller: ContractName; + caller: keyof ArtifactsMap; } const precompiles: Address[] = [ - "0x9000000000000000000000000000000000000001", // noop no recieve no fallback + "0x9000000000000000000000000000000000000001", // noop no receive no fallback "0x9000000000000000000000000000000000000002", // noop receive no fallback "0x9000000000000000000000000000000000000003", // noop receive payable fallback "0x9000000000000000000000000000000000000004", // noop receive non payable fallback "0x9000000000000000000000000000000000000005", // noop no receive payable fallback - "0x9000000000000000000000000000000000000006", // noop no recieve non payable fallback + "0x9000000000000000000000000000000000000006", // noop no receive non payable fallback ]; // ABI_BasicTests assert ethereum + solidity transaction ABI interactions perform as expected. describe("ABI_BasicTests", function () { - const testCases = [ + const testCases: ContractTestCase[] = [ // Test function modifiers without receive & fallback { interface: "NoopNoReceiveNoFallback", @@ -75,7 +82,7 @@ describe("ABI_BasicTests", function () { precompile: precompiles[5], caller: "NoopCaller", }, - ] as ContractTestCase[]; + ]; // // Client + Wallet Setup @@ -124,6 +131,17 @@ describe("ABI_BasicTests", function () { address: Address; caller: Address; } + + // AbiFunctionTestCase defines a test case for an ABI function, this is + // run on every function defined in an expected ABI. + interface AbiFunctionTestCase { + name: string; + expectedStatus: "success" | "reverted"; + txParams: (ctx: AbiContext) => CallParameters; + // If defined, check the balance of the contract after the transaction + expectedBalance?: (startingBalance: bigint) => bigint; + } + function itImplementsTheAbi(abi: Abi, getContext: () => Promise) { let ctx: AbiContext; const receiveFunction = getAbiReceiveFunction(abi); @@ -141,164 +159,219 @@ describe("ABI_BasicTests", function () { const funcSelector = toFunctionSelector(toFunctionSignature(funcDesc)); - describe(`${funcDesc.name} ${funcDesc.stateMutability}`, function () { - const isPayable = funcDesc.stateMutability === "payable"; - - it("can be called", async function () { - const txData = { to: ctx.address, data: funcSelector, gas: defaultGas }; - - await expect(publicClient.call(txData)).to.be.fulfilled; - - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal("success"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; - }); - - it("can be called by low level contract call", async function () { - const data = encodeFunctionData({ - abi: caller.abi, - functionName: "functionCall", - args: [ctx.address, funcSelector], - }); - const txData = { to: caller.address, data: data, gas: contractCallerGas }; - - await expect(publicClient.call(txData)).to.be.fulfilled; - - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal("success"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; - }); - - if (funcDesc.stateMutability === "view" || funcDesc.stateMutability === "pure") { - it("can be called by static call", async function () { - const data = encodeFunctionData({ + // Dynamically build test cases for each function individually, as + // the cases depend on the function's stateMutability. + const abiFunctionTestCases: AbiFunctionTestCase[] = [ + { + name: "can be called", + txParams: (ctx) => ({ to: ctx.address, data: funcSelector, gas: defaultGas }), + expectedStatus: "success", + }, + { + name: "can be called by low level contract call", + txParams: (ctx) => ({ + to: caller.address, + data: encodeFunctionData({ abi: caller.abi, - functionName: "functionStaticCall", + functionName: "functionCall", args: [ctx.address, funcSelector], - }); - const txData = { to: caller.address, data: data, gas: contractCallerGas }; + }), + gas: contractCallerGas, + }), + expectedStatus: "success", + }, + { + name: "can be called by high level contract call", + txParams: (ctx) => ({ + to: ctx.caller, + data: funcSelector, + gas: contractCallerGas, + }), + expectedStatus: "success", + }, + { + name: "can be called with extra data", + txParams: (ctx) => ({ + to: ctx.address, + data: concat([funcSelector, "0x01"]), + gas: defaultGas, + }), + expectedStatus: "success", + }, + ]; + + if (funcDesc.stateMutability === "view" || funcDesc.stateMutability === "pure") { + abiFunctionTestCases.push( + { + name: "can be called by static call", + txParams: (ctx) => ({ + to: caller.address, + data: encodeFunctionData({ + abi: caller.abi, + functionName: "functionStaticCall", + args: [ctx.address, funcSelector], + }), + gas: contractCallerGas, + }), + expectedStatus: "success", + }, + { + name: "can be called by static call with extra data", + txParams: (ctx) => ({ + to: caller.address, + data: encodeFunctionData({ + abi: caller.abi, + functionName: "functionStaticCall", + args: [ctx.address, concat([funcSelector, "0x01"])], + }), + gas: contractCallerGas, + }), + expectedStatus: "success", + }, + ); + } - await expect(publicClient.call(txData)).to.be.fulfilled; + if (funcDesc.stateMutability === "payable") { + abiFunctionTestCases.push( + { + name: "can be called with value", + txParams: (ctx) => ({ + to: ctx.address, + data: funcSelector, + gas: defaultGas, + value: 1n, + }), + expectedBalance: (startingBalance) => startingBalance + 1n, + expectedStatus: "success", + }, + { + name: "can be called by low level contract call with value", + txParams: (ctx) => ({ + to: ctx.caller, + data: funcSelector, + gas: 50000n, + value: 1n, + }), + expectedBalance: (startingBalance) => startingBalance + 1n, + expectedStatus: "success", + }, + { + name: "can be called by high level contract call with value", + txParams: (ctx) => ({ + to: caller.address, + data: encodeFunctionData({ + abi: caller.abi, + functionName: "functionCall", + args: [ctx.address, funcSelector], + }), + gas: contractCallerGas, + value: 1n, + }), + expectedBalance: (startingBalance) => startingBalance + 1n, + expectedStatus: "success", + }, + { + name: "can be called with value and extra data", + txParams: (ctx) => ({ + to: ctx.address, + data: concat([funcSelector, "0x01"]), + gas: defaultGas, + value: 1n, + }), + expectedBalance: (startingBalance) => startingBalance + 1n, + expectedStatus: "success", + }, + ); + } - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal("success"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; - }); + if (funcDesc.stateMutability !== "payable") { + abiFunctionTestCases.push( + { + name: "can not be called with value", + txParams: (ctx) => ({ + to: ctx.address, + data: funcSelector, + gas: defaultGas, + value: 1n, + }), + // Balance stays the same + expectedBalance: (startingBalance) => startingBalance, + expectedStatus: "reverted", + }, + { + name: "can not be called by low level contract call with value", + txParams: (ctx) => ({ + to: ctx.caller, + data: funcSelector, + gas: 50000n, + value: 1n, + }), + // Same + expectedBalance: (startingBalance) => startingBalance, + expectedStatus: "reverted", + }, + { + name: "can not be called by high level contract call with value", + txParams: (ctx) => ({ + to: caller.address, + data: encodeFunctionData({ + abi: caller.abi, + functionName: "functionCall", + args: [ctx.address, funcSelector], + }), + gas: contractCallerGas, + value: 1n, + }), + expectedBalance: (startingBalance) => startingBalance, + expectedStatus: "reverted", + }, + { + name: "can not be called with value and extra data", + txParams: (ctx) => ({ + to: ctx.address, + data: concat([funcSelector, "0x01"]), + gas: defaultGas, + value: 1n, + }), + expectedBalance: (startingBalance) => startingBalance, + expectedStatus: "reverted", + }, + ); + } - it("can be called by static call with extra data", async function () { - const data = encodeFunctionData({ - abi: caller.abi, - functionName: "functionStaticCall", - args: [ctx.address, concat([funcSelector, "0x01"])], - }); - const txData = { to: caller.address, data: data, gas: contractCallerGas }; + describe(`${funcDesc.name} ${funcDesc.stateMutability}`, function () { + // Run test cases for each function + for (const testCase of abiFunctionTestCases) { + it(testCase.name, async function () { + const startingBalance = await publicClient.getBalance({ address: ctx.address }); + + const txData = testCase.txParams(ctx); - await expect(publicClient.call(txData)).to.be.fulfilled; + if (testCase.expectedStatus === "success") { + await expect(publicClient.call(txData)).to.be.fulfilled; + } else { + await expect(publicClient.call(txData)).to.be.rejected; + } const txHash = await walletClient.sendTransaction(txData); const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal("success"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; - }); - } + expect(txReceipt.status).to.equal(testCase.expectedStatus); - it("can be called by high level contract call", async function () { - const txData = { to: ctx.caller, data: funcSelector, gas: contractCallerGas }; + if (txData.gas) { + expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; + } - await expect(publicClient.call(txData)).to.be.fulfilled; - - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal("success"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; - }); - - it(`can ${isPayable ? "" : "not "}be called with value`, async function () { - const txData = { to: ctx.address, data: funcSelector, gas: defaultGas, value: 1n }; - const startingBalance = await publicClient.getBalance({ address: ctx.address }); - - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal(isPayable ? "success" : "reverted"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; - - let expectedBalance = startingBalance; - if (isPayable) { - expectedBalance = startingBalance + txData.value; - } - const balance = await publicClient.getBalance({ address: ctx.address }); - expect(balance).to.equal(expectedBalance); - }); + if (testCase.expectedBalance) { + const expectedBalance = testCase.expectedBalance(startingBalance); - it(`can ${isPayable ? "" : "not "}be called by low level contract call with value`, async function () { - const txData = { to: ctx.caller, data: funcSelector, gas: 50000n, value: 1n }; - const startingBalance = await publicClient.getBalance({ address: ctx.address }); - - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal(isPayable ? "success" : "reverted"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; - - let expectedBalance = startingBalance; - if (isPayable) { - expectedBalance = startingBalance + txData.value; - } - const balance = await publicClient.getBalance({ address: ctx.address }); - expect(balance).to.equal(expectedBalance); - }); - - it(`can ${isPayable ? "" : "not "}be called by high level contract call with value`, async function () { - const data = encodeFunctionData({ - abi: caller.abi, - functionName: "functionCall", - args: [ctx.address, funcSelector], + const balance = await publicClient.getBalance({ address: ctx.address }); + expect(balance).to.equal(expectedBalance); + } else { + const balance = await publicClient.getBalance({ address: ctx.address }); + expect(balance).to.equal(startingBalance, "balance to not change if expectedBalance is not defined"); + } }); - const txData = { to: caller.address, data: data, gas: contractCallerGas, value: 1n}; - const startingBalance = await publicClient.getBalance({ address: ctx.address }); - - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal(isPayable ? "success" : "reverted"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; - - let expectedBalance = startingBalance; - if (isPayable) { - expectedBalance = startingBalance + txData.value; - } - const balance = await publicClient.getBalance({ address: ctx.address }); - expect(balance).to.equal(expectedBalance); - }); - - it("can be called with extra data", async function () { - const data = concat([funcSelector, "0x01"]); - const txData = { to: ctx.address, data: data, gas: defaultGas }; - - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal("success"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; - }); - - it(`can ${isPayable ? "" : "not "}be called with value and extra data`, async function () { - const data = concat([funcSelector, "0x01"]); - const txData = { to: ctx.address, data: data, gas: defaultGas, value: 1n }; - const startingBalance = await publicClient.getBalance({ address: ctx.address }); - - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal(isPayable ? "success" : "reverted"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; - - let expectedBalance = startingBalance; - if (isPayable) { - expectedBalance = startingBalance + txData.value; - } - const balance = await publicClient.getBalance({ address: ctx.address }); - expect(balance).to.equal(expectedBalance); - }); + } }); } }); @@ -310,313 +383,426 @@ describe("ABI_BasicTests", function () { // Fallback functions can be payable or non-payable and can receive data in both cases const testName = `ABI special functions: ${receiveFunction ? "" : "no "}receive and ${fallbackFunction ? fallbackFunction.stateMutability : "no"} fallback`; describe(testName, function () { - if (receiveFunction || fallbackFunction) { - it("can receive zero value transfers with no data", async function () { - const txData = { to: ctx.address, gas: defaultGas }; - - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal("success"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; - }); - - it("can be called by another contract with no data", async function() { - const data = encodeFunctionData({ - abi: caller.abi, - functionName: "functionCall", - args: [ctx.address, "0x"], - }); - const txData = { to: caller.address, data: data, gas: contractCallerGas }; - - await expect(publicClient.call(txData)).to.be.fulfilled; - - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal("success"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; - }); - - it("can be called by static call with no data", async function () { - const data = encodeFunctionData({ - abi: caller.abi, - functionName: "functionStaticCall", - args: [ctx.address, "0x"], - }); - const txData = { to: caller.address, data: data, gas: contractCallerGas }; - - await expect(publicClient.call(txData)).to.be.fulfilled; - - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal("success"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; - }); + // Dynamically build test cases depending on the receive and fallback + // functions defined in the ABI + const specialFunctionTests: AbiFunctionTestCase[] = []; + + if (receiveFunction && fallbackFunction) { + specialFunctionTests.push( + { + name: "can receive zero value transfers with no data", + txParams: (ctx) => ({ to: ctx.address, gas: defaultGas }), + expectedStatus: "success", + }, + { + name: "can be called by another contract with no data", + txParams: (ctx) => ({ + to: caller.address, + data: encodeFunctionData({ + abi: caller.abi, + functionName: "functionCall", + args: [ctx.address, "0x"], + }), + gas: contractCallerGas, + }), + expectedStatus: "success", + }, + { + name: "can be called by static call with no data", + txParams: (ctx) => ({ + to: caller.address, + data: encodeFunctionData({ + abi: caller.abi, + functionName: "functionStaticCall", + args: [ctx.address, "0x"], + }), + gas: contractCallerGas, + }), + expectedStatus: "success", + }, + ); } if (!receiveFunction && !fallbackFunction) { - it("can not receive zero value transfers with no data", async function () { - const txData = { to: ctx.address, gas: defaultGas }; - - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal("reverted"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; - }); - - it("can not receive zero value transfers with no data", async function () { - const data = encodeFunctionData({ - abi: caller.abi, - functionName: "functionCall", - args: [ctx.address, "0x"], - }); - const txData = { to: caller.address, data: data, gas: contractCallerGas }; - - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal("reverted"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; - }); - - it("can not be called by static call with no data", async function () { - const data = encodeFunctionData({ - abi: caller.abi, - functionName: "functionStaticCall", - args: [ctx.address, "0x"], - }); - const txData = { to: caller.address, data: data, gas: contractCallerGas }; - - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal("reverted"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; - }); + specialFunctionTests.push( + { + name: "can not receive zero value transfers with no data", + txParams: (ctx) => ({ to: ctx.address, gas: defaultGas }), + expectedStatus: "reverted", + }, + { + name: "can not receive zero value transfers by high level contract call with no data", + txParams: (ctx) => ({ + to: caller.address, + data: encodeFunctionData({ + abi: caller.abi, + functionName: "functionCall", + args: [ctx.address, "0x"], + }), + gas: contractCallerGas, + }), + expectedStatus: "reverted", + }, + { + name: "can not receive zero value transfers by static call with no data", + txParams: (ctx) => ({ + to: caller.address, + data: encodeFunctionData({ + abi: caller.abi, + functionName: "functionStaticCall", + args: [ctx.address, "0x"], + }), + gas: contractCallerGas, + }), + expectedStatus: "reverted", + }, + ); } + // No receive function AND no payable fallback if (!receiveFunction && (!fallbackFunction || fallbackFunction.stateMutability !== "payable")) { - it("can not receive plain transfers", async function () { - const txData = { to: ctx.address, gas: defaultGas, value: 1n }; - const startingBalance = await publicClient.getBalance({ address: ctx.address }); - - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal("reverted"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; - - let expectedBalance = startingBalance; - const balance = await publicClient.getBalance({ address: ctx.address }); - expect(balance).to.equal(expectedBalance); - }); - - it("can not receive plain transfers via message call", async function () { - const data = encodeFunctionData({ - abi: caller.abi, - functionName: "functionCall", - args: [ctx.address, "0x"], - }); - const txData = { to: caller.address, data: data, gas: contractCallerGas, value: 1n}; - const startingBalance = await publicClient.getBalance({ address: ctx.address }); - - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal("reverted"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; - - let expectedBalance = startingBalance; - const balance = await publicClient.getBalance({ address: ctx.address }); - expect(balance).to.equal(expectedBalance); - }); + specialFunctionTests.push( + { + name: "can not receive plain transfers", + txParams: (ctx) => ({ to: ctx.address, gas: defaultGas, value: 1n }), + expectedStatus: "reverted", + }, + { + name: "can not receive plain transfers via message call", + txParams: (ctx) => ({ + to: caller.address, + data: encodeFunctionData({ + abi: caller.abi, + functionName: "functionCall", + args: [ctx.address, "0x"], + }), + gas: contractCallerGas, + value: 1n, + }), + expectedStatus: "reverted", + }, + ); } if (receiveFunction || (fallbackFunction && fallbackFunction.stateMutability === "payable")) { - it("can receive plain transfers", async function () { - const txData = { to: ctx.address, gas: defaultGas, value: 1n }; - const startingBalance = await publicClient.getBalance({ address: ctx.address }); - - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal("success"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; - - const expectedBalance = startingBalance + txData.value; - const balance = await publicClient.getBalance({ address: ctx.address }); - expect(balance).to.equal(expectedBalance); - }); - - it("can plain transfers via message call", async function () { - const data = encodeFunctionData({ - abi: caller.abi, - functionName: "functionCall", - args: [ctx.address, "0x"], - }); - const txData = { to: caller.address, data: data, gas: contractCallerGas, value: 1n}; - const startingBalance = await publicClient.getBalance({ address: ctx.address }); - - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal("success"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; - - const expectedBalance = startingBalance + txData.value; - const balance = await publicClient.getBalance({ address: ctx.address }); - expect(balance).to.equal(expectedBalance); - }); + specialFunctionTests.push( + { + name: "can receive plain transfers", + txParams: (ctx) => ({ to: ctx.address, gas: defaultGas, value: 1n }), + expectedBalance: (startingBalance) => startingBalance + 1n, + expectedStatus: "success", + }, + { + name: "can receive plain transfers via message call", + txParams: (ctx) => ({ + to: caller.address, + data: encodeFunctionData({ + abi: caller.abi, + functionName: "functionCall", + args: [ctx.address, "0x"], + }), + gas: contractCallerGas, + value: 1n, + }), + expectedBalance: (startingBalance) => startingBalance + 1n, + expectedStatus: "success", + }, + ); } - it(`can ${fallbackFunction ? "" : "not "}be called with a non-matching function selector`, async function () { - const data = toFunctionSelector("does_not_exist()"); - const txData = { to: ctx.address, data: data, gas: defaultGas }; - - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal(fallbackFunction ? "success" : "reverted"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; - }); - - it(`can ${fallbackFunction ? "" : "not "}be called with a non-matching function selector via message call`, async function () { - const data = encodeFunctionData({ - abi: caller.abi, - functionName: "functionCall", - args: [ctx.address, toFunctionSelector("does_not_exist()")], - }); - const txData = { to: caller.address, data: data, gas: contractCallerGas}; - - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal(fallbackFunction ? "success" : "reverted"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; - }); - - it(`can ${fallbackFunction ? "" : "not "}be called with a non-matching function selector via static call`, async function () { - const data = encodeFunctionData({ - abi: caller.abi, - functionName: "functionStaticCall", - args: [ctx.address, toFunctionSelector("does_not_exist()")], - }); - const txData = { to: caller.address, data: data, gas: contractCallerGas}; - - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal(fallbackFunction ? "success" : "reverted"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; - }); - - it(`can ${fallbackFunction ? "" : "not "}be called with an invalid (short) function selector`, async function () { - const data: Hex = "0x010203"; - const txData = { to: ctx.address, data: data, gas: defaultGas }; - - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal(fallbackFunction ? "success" : "reverted"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; - }); - - it(`can ${fallbackFunction ? "" : "not "}be called with an invalid (short) via message call`, async function () { - const data = encodeFunctionData({ - abi: caller.abi, - functionName: "functionCall", - args: [ctx.address, "0x010203"], - }); - const txData = { to: caller.address, data: data, gas: contractCallerGas}; - - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal(fallbackFunction ? "success" : "reverted"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; - }); - - it(`can ${fallbackFunction ? "" : "not "}be called with an invalid (short) via static call`, async function () { - const data = encodeFunctionData({ - abi: caller.abi, - functionName: "functionStaticCall", - args: [ctx.address, "0x010203"], - }); - const txData = { to: caller.address, data: data, gas: contractCallerGas}; - - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal(fallbackFunction ? "success" : "reverted"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; - }); - if (fallbackFunction) { - it(`can ${fallbackFunction.stateMutability === "payable" ? "" : "not "}receive value with a non-matching function selector`, async function () { - const data = toFunctionSelector("does_not_exist()"); - const txData = { to: ctx.address, data: data, gas: defaultGas, value: 1n }; - const startingBalance = await publicClient.getBalance({ address: ctx.address }); + specialFunctionTests.push( + { + name: "can be called with a non-matching function selector", + txParams: (ctx) => ({ + to: ctx.address, + data: toFunctionSelector("does_not_exist()"), + gas: defaultGas, + }), + expectedStatus: "success", + }, + { + name: "can be called with a non-matching function selector via message call", + txParams: (ctx) => ({ + to: caller.address, + data: encodeFunctionData({ + abi: caller.abi, + functionName: "functionCall", + args: [ctx.address, toFunctionSelector("does_not_exist()")], + }), + gas: contractCallerGas, + }), + expectedStatus: "success", + }, + { + name: "can be called with a non-matching function selector via static call", + txParams: (ctx) => ({ + to: caller.address, + data: encodeFunctionData({ + abi: caller.abi, + functionName: "functionStaticCall", + args: [ctx.address, toFunctionSelector("does_not_exist()")], + }), + gas: contractCallerGas, + }), + expectedStatus: "success", + }, + { + name: "can be called with an invalid (short) function selector", + txParams: (ctx) => ({ + to: ctx.address, + data: "0x010203", + gas: defaultGas, + }), + expectedStatus: "success", + }, + { + name: "can be called with an invalid (short) function selector via message call", + txParams: (ctx) => ({ + to: caller.address, + data: encodeFunctionData({ + abi: caller.abi, + functionName: "functionCall", + args: [ctx.address, "0x010203"], + }), + gas: contractCallerGas, + }), + expectedStatus: "success", + }, + { + name: "can be called with an invalid (short) function selector via static call", + txParams: (ctx) => ({ + to: caller.address, + data: encodeFunctionData({ + abi: caller.abi, + functionName: "functionStaticCall", + args: [ctx.address, "0x010203"], + }), + gas: contractCallerGas, + }), + expectedStatus: "success", + }, + ); + } - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal(fallbackFunction.stateMutability === "payable" ? "success" : "reverted"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; + if (!fallbackFunction) { + specialFunctionTests.push( + { + name: "can not be called with a non-matching function selector", + txParams: (ctx) => ({ + to: ctx.address, + data: toFunctionSelector("does_not_exist()"), + gas: defaultGas, + }), + expectedStatus: "reverted", + }, + { + name: "can not be called with a non-matching function selector via message call", + txParams: (ctx) => ({ + to: caller.address, + data: encodeFunctionData({ + abi: caller.abi, + functionName: "functionCall", + args: [ctx.address, toFunctionSelector("does_not_exist()")], + }), + gas: contractCallerGas, + }), + expectedStatus: "reverted", + }, + { + name: "can not be called with a non-matching function selector via static call", + txParams: (ctx) => ({ + to: caller.address, + data: encodeFunctionData({ + abi: caller.abi, + functionName: "functionStaticCall", + args: [ctx.address, toFunctionSelector("does_not_exist()")], + }), + gas: contractCallerGas, + }), + expectedStatus: "reverted", + }, + { + name: "can not be called with an invalid (short) function selector", + txParams: (ctx) => ({ + to: ctx.address, + data: "0x010203", + gas: defaultGas, + }), + expectedStatus: "reverted", + }, + { + name: "can not be called with an invalid (short) function selector via message call", + txParams: (ctx) => ({ + to: caller.address, + data: encodeFunctionData({ + abi: caller.abi, + functionName: "functionCall", + args: [ctx.address, "0x010203"], + }), + gas: contractCallerGas, + }), + expectedStatus: "reverted", + }, + { + name: "can not be called with an invalid (short) function selector via static call", + txParams: (ctx) => ({ + to: caller.address, + data: encodeFunctionData({ + abi: caller.abi, + functionName: "functionStaticCall", + args: [ctx.address, "0x010203"], + }), + gas: contractCallerGas, + }), + expectedStatus: "reverted", + }, + ); + } - let expectedBalance = startingBalance; - if (fallbackFunction.stateMutability === "payable") { - expectedBalance = startingBalance + txData.value; - } - const balance = await publicClient.getBalance({ address: ctx.address }); - expect(balance).to.equal(expectedBalance); - }); + if (fallbackFunction && fallbackFunction.stateMutability === "payable") { + specialFunctionTests.push( + { + name: "can receive value with a non-matching function selector", + txParams: (ctx) => ({ + to: ctx.address, + data: toFunctionSelector("does_not_exist()"), + gas: defaultGas, + value: 1n, + }), + expectedBalance: (startingBalance) => startingBalance + 1n, + expectedStatus: "success", + }, + { + name: "can receive value with a non-matching function selector via message call", + txParams: (ctx) => ({ + to: caller.address, + data: encodeFunctionData({ + abi: caller.abi, + functionName: "functionCall", + args: [ctx.address, toFunctionSelector("does_not_exist()")], + }), + gas: contractCallerGas, + value: 1n, + }), + expectedBalance: (startingBalance) => startingBalance + 1n, + expectedStatus: "success", + }, + { + name: "can receive value with an invalid (short) function selector", + txParams: (ctx) => ({ + to: ctx.address, + data: "0x010203", + gas: defaultGas, + value: 1n, + }), + expectedBalance: (startingBalance) => startingBalance + 1n, + expectedStatus: "success", + }, + { + name: "can receive value with an invalid (short) function selector via message call", + txParams: (ctx) => ({ + to: caller.address, + data: encodeFunctionData({ + abi: caller.abi, + functionName: "functionCall", + args: [ctx.address, "0x010203"], + }), + gas: contractCallerGas, + value: 1n, + }), + expectedBalance: (startingBalance) => startingBalance + 1n, + expectedStatus: "success", + }, + ); + } + + if (fallbackFunction && fallbackFunction.stateMutability !== "payable") { + specialFunctionTests.push( + { + name: "can not receive value with a non-matching function selector", + txParams: (ctx) => ({ + to: ctx.address, + data: toFunctionSelector("does_not_exist()"), + gas: defaultGas, + value: 1n, + }), + expectedBalance: (startingBalance) => startingBalance, + expectedStatus: "reverted", + }, + { + name: "can not receive value with a non-matching function selector via message call", + txParams: (ctx) => ({ + to: caller.address, + data: encodeFunctionData({ + abi: caller.abi, + functionName: "functionCall", + args: [ctx.address, toFunctionSelector("does_not_exist()")], + }), + gas: contractCallerGas, + value: 1n, + }), + expectedBalance: (startingBalance) => startingBalance, + expectedStatus: "reverted", + }, + { + name: "can not receive value with an invalid (short) function selector", + txParams: (ctx) => ({ + to: ctx.address, + data: "0x010203", + gas: defaultGas, + value: 1n, + }), + expectedBalance: (startingBalance) => startingBalance, + expectedStatus: "reverted", + }, + { + name: "can not receive value with an invalid (short) function selector via message call", + txParams: (ctx) => ({ + to: caller.address, + data: encodeFunctionData({ + abi: caller.abi, + functionName: "functionCall", + args: [ctx.address, "0x010203"], + }), + gas: contractCallerGas, + value: 1n, + }), + expectedBalance: (startingBalance) => startingBalance, + expectedStatus: "reverted", + }, + ); + } - it(`can ${fallbackFunction.stateMutability === "payable" ? "" : "not "}receive value with a non-matching function selector via message call`, async function () { - const data = encodeFunctionData({ - abi: caller.abi, - functionName: "functionCall", - args: [ctx.address, toFunctionSelector("does_not_exist()")], - }); - const txData = { to: caller.address, data: data, gas: contractCallerGas, value: 1n}; + for (const testCase of specialFunctionTests) { + it(testCase.name, async function () { const startingBalance = await publicClient.getBalance({ address: ctx.address }); - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal(fallbackFunction.stateMutability === "payable" ? "success" : "reverted"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; + const txData = testCase.txParams(ctx); - let expectedBalance = startingBalance; - if (fallbackFunction.stateMutability === "payable") { - expectedBalance = startingBalance + txData.value; + if (testCase.expectedStatus === "success") { + await expect(publicClient.call(txData)).to.be.fulfilled; + } else { + await expect(publicClient.call(txData)).to.be.rejected; } - const balance = await publicClient.getBalance({ address: ctx.address }); - expect(balance).to.equal(expectedBalance); - }); - - it(`can ${fallbackFunction.stateMutability === "payable" ? "" : "not "}recieve value with an invalid function selector`, async function () { - const data: Hex = "0x010203"; - const txData = { to: ctx.address, data: data, gas: defaultGas, value: 1n }; - const startingBalance = await publicClient.getBalance({ address: ctx.address }); const txHash = await walletClient.sendTransaction(txData); const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal(fallbackFunction.stateMutability === "payable" ? "success" : "reverted"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; + expect(txReceipt.status).to.equal(testCase.expectedStatus); - let expectedBalance = startingBalance; - if (fallbackFunction.stateMutability === "payable") { - expectedBalance = startingBalance + txData.value; + if (txData.gas) { + expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; } - const balance = await publicClient.getBalance({ address: ctx.address }); - expect(balance).to.equal(expectedBalance); - }); - - it(`can ${fallbackFunction.stateMutability === "payable" ? "" : "not "}recieve value with an invalid function selector via message call`, async function () { - const data = encodeFunctionData({ - abi: caller.abi, - functionName: "functionCall", - args: [ctx.address, "0x010203"], - }); - const txData = { to: caller.address, data: data, gas: contractCallerGas, value: 1n}; - const startingBalance = await publicClient.getBalance({ address: ctx.address }); - const txHash = await walletClient.sendTransaction(txData); - const txReceipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - expect(txReceipt.status).to.equal(fallbackFunction.stateMutability === "payable" ? "success" : "reverted"); - expect(txReceipt.gasUsed < txData.gas, "gas to not be exhausted").to.be.true; + if (testCase.expectedBalance) { + const expectedBalance = testCase.expectedBalance(startingBalance); - let expectedBalance = startingBalance; - if (fallbackFunction.stateMutability === "payable") { - expectedBalance = startingBalance + txData.value; + const balance = await publicClient.getBalance({ address: ctx.address }); + expect(balance).to.equal(expectedBalance); + } else { + const balance = await publicClient.getBalance({ address: ctx.address }); + expect(balance).to.equal(startingBalance, "balance to not change if expectedBalance is not defined"); } - const balance = await publicClient.getBalance({ address: ctx.address }); - expect(balance).to.equal(expectedBalance); }); } }); @@ -632,6 +818,9 @@ describe("ABI_BasicTests", function () { describe(tc.interface, function () { const abi = hre.artifacts.readArtifactSync(tc.interface).abi; + // Enforce that the mock contract name starts with the interface name + expect(tc.mock.startsWith(tc.interface), "Mock contract name must start with the interface name").to.be.true; + // The interface is tested against a mock on all networks. // This serves as a reference to ensure all precompiles have mocks that behavior similarly // for testing on non-kava networks, in addition to ensure that we match normal contract @@ -642,11 +831,24 @@ describe("ABI_BasicTests", function () { let deployedBytecode: Hex; before("deploy mock", async function () { - mockAddress = (await hre.viem.deployContract(tc.mock)).address; - callerAddress = (await hre.viem.deployContract(tc.caller, [mockAddress])).address; - // TODO: fix typing and do not use explicit any, unsafe assignment, or unsafe access - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access - deployedBytecode = ((await hre.artifacts.readArtifact(tc.mock)) as any).deployedBytecode; + // Make type keyof ArtifactsMap to be a string as workaround for + // viem.deployContract() not accepting keyof ArtifactsMap. Each + // contract has it's own deployContract overload that accepts a + // literal string for the contract name, but using keyof ArtifactsMap + // falls back to the generic overload that only accepts a string NOT + // in the ArtifactsMap. + const mockContractName: string = tc.mock; + const callerContractName: string = tc.caller; + + mockAddress = (await hre.viem.deployContract(mockContractName)).address; + callerAddress = (await hre.viem.deployContract(callerContractName, [mockAddress])).address; + + const mockArtifact = await hre.artifacts.readArtifact(mockContractName); + if (isHex(mockArtifact.deployedBytecode)) { + deployedBytecode = mockArtifact.deployedBytecode; + } else { + expect.fail("deployedBytecode is not hex"); + } }); itHasCorrectState(() => @@ -672,8 +874,10 @@ describe("ABI_BasicTests", function () { const precompileAddress = tc.precompile; let callerAddress: Address; - before("deploy pecompile caller", async function () { - callerAddress = (await hre.viem.deployContract(tc.caller, [precompileAddress])).address; + before("deploy precompile caller", async function () { + const callerContractName: string = tc.caller; + + callerAddress = (await hre.viem.deployContract(callerContractName, [precompileAddress])).address; }); itHasCorrectState(() => diff --git a/tests/e2e-evm/test/abi_disabled.test.ts b/tests/e2e-evm/test/abi_disabled.test.ts index 0c69988bf..b58335e92 100644 --- a/tests/e2e-evm/test/abi_disabled.test.ts +++ b/tests/e2e-evm/test/abi_disabled.test.ts @@ -1,13 +1,17 @@ import hre from "hardhat"; import type { ArtifactsMap } from "hardhat/types/artifacts"; -import type { - PublicClient, - WalletClient, - ContractName, - GetContractReturnType, -} from "@nomicfoundation/hardhat-viem/types"; +import type { PublicClient, WalletClient, GetContractReturnType } from "@nomicfoundation/hardhat-viem/types"; import { expect } from "chai"; -import { Address, BaseError, Hex, toFunctionSelector, toFunctionSignature, concat, encodeFunctionData } from "viem"; +import { + Address, + BaseError, + Hex, + toFunctionSelector, + toFunctionSignature, + concat, + encodeFunctionData, + isHex, +} from "viem"; import { Abi } from "abitype"; import { getAbiFallbackFunction, getAbiReceiveFunction } from "./helpers/abi"; import { whaleAddress } from "./addresses"; @@ -17,23 +21,23 @@ const messageCallGas = defaultGas + 10000n; interface ContractTestCase { interface: keyof ArtifactsMap; - mock: ContractName; + mock: keyof ArtifactsMap; precompile: Address; } const disabledPrecompiles: Address[] = [ - "0x9000000000000000000000000000000000000007", // noop recieve and payable fallback + "0x9000000000000000000000000000000000000007", // noop receive and payable fallback ]; // ABI_DisabledTests assert ethereum + solidity transaction ABI interactions perform as expected. describe("ABI_DisabledTests", function () { - const testCases = [ + const testCases: ContractTestCase[] = [ { interface: "NoopReceivePayableFallback", // interface to test valid function selectors against mock: "NoopDisabledMock", // mimics how a disabled precompile would behave - precompile: disabledPrecompiles[0], // disabled noop recieve and payable fallback + precompile: disabledPrecompiles[0], // disabled noop receive and payable fallback }, - ] as ContractTestCase[]; + ]; // // Client + Wallet Setup @@ -84,71 +88,97 @@ describe("ABI_DisabledTests", function () { ctx = await getContext(); }); + // testCase is a single test case with the value and data to send. Each of + // these will be tested against each of the callCases. interface testCase { - name: string + name: string; value: bigint; data: Hex; } - const testCases: testCase[] = [ - { name: "zero value transfer", value: 0n, data: "0x" }, - { name: "value transfer", value: 1n, data: "0x" }, - { name: "invalid function selector", value: 0n, data: "0x010203" }, - { name: "invalid function selector with value", value: 1n, data: "0x010203" }, - { name: "non-matching function selector", value: 0n, data: toFunctionSelector("does_not_exist()")}, - { name: "non-matching function selector with value", value: 1n, data: toFunctionSelector("does_not_exist()")}, - { name: "non-matching function selector with extra data", value: 0n, data: concat([toFunctionSelector("does_not_exist()"), "0x01"])}, - { name: "non-matching function selector with value and extra data", value: 1n, data: concat([toFunctionSelector("does_not_exist()"), "0x01"])}, - ]; + const testCases: testCase[] = []; + + if (receiveFunction) { + testCases.push( + { name: "zero value transfer", value: 0n, data: "0x" }, + { name: "value transfer", value: 1n, data: "0x" }, + ); + } + + if (fallbackFunction) { + testCases.push( + { name: "invalid function selector", value: 0n, data: "0x010203" }, + { name: "invalid function selector with value", value: 1n, data: "0x010203" }, + { name: "non-matching function selector", value: 0n, data: toFunctionSelector("does_not_exist()") }, + { name: "non-matching function selector with value", value: 1n, data: toFunctionSelector("does_not_exist()") }, + { + name: "non-matching function selector with extra data", + value: 0n, + data: concat([toFunctionSelector("does_not_exist()"), "0x01"]), + }, + { + name: "non-matching function selector with value and extra data", + value: 1n, + data: concat([toFunctionSelector("does_not_exist()"), "0x01"]), + }, + ); + } for (const funcDesc of abi) { - if (funcDesc.type !== "function") continue; + if (funcDesc.type !== "function") { + continue; + } + const funcSelector = toFunctionSelector(toFunctionSignature(funcDesc)); - testCases.concat([ + + testCases.push( { name: funcDesc.name, value: 0n, data: funcSelector }, { name: `${funcDesc.name} with value`, value: 1n, data: funcSelector }, { name: `${funcDesc.name} with extra data`, value: 0n, data: concat([funcSelector, "0x01"]) }, { name: `${funcDesc.name} with value and extra data`, value: 1n, data: concat([funcSelector, "0x01"]) }, - ]); + ); } const callCases: { name: string; - mutateData: (tc: Hex) => Promise; - to: () => Promise
; + mutateData: (tc: Hex) => Hex; + to: () => Address; gas: bigint; }[] = [ { name: "external call", - to: async () => ctx.address, - mutateData: async (data) => data, + to: () => ctx.address, + mutateData: (data) => data, gas: defaultGas, }, { name: "message call", - to: async () => caller.address, - mutateData: async (data) => encodeFunctionData({ abi: caller.abi, functionName: "functionCall", args: [ctx.address, data] }), + to: () => caller.address, + mutateData: (data) => + encodeFunctionData({ abi: caller.abi, functionName: "functionCall", args: [ctx.address, data] }), gas: messageCallGas, }, { name: "message delegatecall", - to: async () => caller.address, - mutateData: async (data) => encodeFunctionData({ abi: caller.abi, functionName: "functionDelegateCall", args: [ctx.address, data] }), + to: () => caller.address, + mutateData: (data) => + encodeFunctionData({ abi: caller.abi, functionName: "functionDelegateCall", args: [ctx.address, data] }), gas: messageCallGas, }, { name: "message staticcall", - to: async () => caller.address, - mutateData: async (data) => encodeFunctionData({ abi: caller.abi, functionName: "functionStaticCall", args: [ctx.address, data] }), + to: () => caller.address, + mutateData: (data) => + encodeFunctionData({ abi: caller.abi, functionName: "functionStaticCall", args: [ctx.address, data] }), gas: messageCallGas, }, ]; for (const tc of testCases) { - describe(tc.name, function() { + describe(tc.name, function () { for (const cc of callCases) { - it(`reverts on ${cc.name}`, async function() { - const to = await cc.to(); - const data = await cc.mutateData(tc.data); + it(`reverts on ${cc.name}`, async function () { + const to = cc.to(); + const data = cc.mutateData(tc.data); const txData = { to: to, data: data, gas: cc.gas }; const startingBalance = await publicClient.getBalance({ address: ctx.address }); @@ -165,14 +195,16 @@ describe("ABI_DisabledTests", function () { let revertDetail = ""; try { await call; - } catch(e) { + } catch (e) { + expect(e).to.be.instanceOf(BaseError, "expected error to be a BaseError"); + if (e instanceof BaseError) { revertDetail = e.details; } } - const expectedMatch = /call not allowed to disabled contract/; - expect(expectedMatch.test(revertDetail), `expected ${revertDetail} to match ${expectedMatch}`).to.be.true; + const expectedMatch = "call not allowed to disabled contract"; + expect(revertDetail).to.equal(revertDetail, `expected ${revertDetail} to match ${expectedMatch}`); }); } }); @@ -198,10 +230,16 @@ describe("ABI_DisabledTests", function () { let deployedBytecode: Hex; before("deploy mock", async function () { - mockAddress = (await hre.viem.deployContract(tc.mock)).address; - // TODO: fix typing and do not use explicit any, unsafe assignment, or unsafe access - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access - deployedBytecode = ((await hre.artifacts.readArtifact(tc.mock)) as any).deployedBytecode; + const mockContractName: string = tc.mock; + + mockAddress = (await hre.viem.deployContract(mockContractName)).address; + + const mockArtifact = await hre.artifacts.readArtifact(mockContractName); + if (isHex(mockArtifact.deployedBytecode)) { + deployedBytecode = mockArtifact.deployedBytecode; + } else { + expect.fail("deployedBytecode is not hex"); + } }); itHasCorrectState(() =>