Skip to content

Commit

Permalink
Add smoketest to e2e tests (#261)
Browse files Browse the repository at this point in the history
Adds an e2e smoketest that sends value transfer, contract interaction
and contract creation transactions for all of the valid transaction types.

It also verifies that deprecated transactions (celo legacy & cip42) are
not supported.

Co-authored-by: Karl Bartel <[email protected]>
  • Loading branch information
piersy and karlb authored Nov 26, 2024
1 parent 63fb235 commit 70a7e9b
Show file tree
Hide file tree
Showing 9 changed files with 297 additions and 107 deletions.
6 changes: 1 addition & 5 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,12 @@ jobs:
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly-f625d0fa7c51e65b4bf1e8f7931cd1c6e2e285e9
version: nightly-143abd6a768eeb52a5785240b763d72a56987b4a

- name: Run e2e tests local
shell: bash
run: e2e_test/run_all_tests.sh

- name: Run e2e tests alfajores
shell: bash
run: NETWORK=alfajores e2e_test/run_all_tests.sh

Lint:
runs-on: ["8-cpu","self-hosted","org"]
steps:
Expand Down
42 changes: 42 additions & 0 deletions .github/workflows/e2e-test-deployed-network.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: e2e-test-deployed-network

on:
schedule:
- cron: "0 14 * * *"
pull_request:
branches:
- master
- celo*
paths:
- 'e2e_test/**'

workflow_dispatch:

permissions:
contents: read

jobs:
e2e-tests:
runs-on: ["8-cpu","self-hosted","org"]
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version: '1.21'

- name: Build
run: make all

- uses: actions/setup-node@v4
with:
node-version: 18

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly-143abd6a768eeb52a5785240b763d72a56987b4a

- name: Run e2e tests alfajores
shell: bash
run: NETWORK=alfajores e2e_test/run_all_tests.sh
3 changes: 3 additions & 0 deletions core/types/celo_transaction_signing_tx_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ var (
return nil, nil, nil, fmt.Errorf("%w %v", ErrDeprecatedTxType, tx.Type())
},
sender: func(tx *Transaction, hashFunc func(tx *Transaction, chainID *big.Int) common.Hash, signerChainID *big.Int) (common.Address, error) {
if tx.IsCeloLegacy() {
return common.Address{}, fmt.Errorf("%w %v %v", ErrDeprecatedTxType, tx.Type(), "(celo legacy)")
}
return common.Address{}, fmt.Errorf("%w %v", ErrDeprecatedTxType, tx.Type())
},
}
Expand Down
44 changes: 2 additions & 42 deletions e2e_test/js-tests/send_tx.mjs
Original file line number Diff line number Diff line change
@@ -1,50 +1,10 @@
#!/usr/bin/env node
import {
createWalletClient,
createPublicClient,
http,
defineChain,
TransactionReceiptNotFoundError,
} from "viem";
import { celoAlfajores } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
import { publicClient, walletClient, account } from "./viem_setup.mjs"

const [chainId, privateKey, feeCurrency, waitBlocks, replaceTxAfterWait, celoValue] =
process.argv.slice(2);
const devChain = defineChain({
...celoAlfajores,
id: parseInt(chainId, 10),
name: "local dev chain",
network: "dev",
rpcUrls: {
default: {
http: ["http://127.0.0.1:8545"],
},
},
});

var chain;
switch(process.env.NETWORK) {
case "alfajores":
chain = celoAlfajores;
break;
default:
chain = devChain;
// Code to run if no cases match
};

const account = privateKeyToAccount(privateKey);

const publicClient = createPublicClient({
account,
chain: chain,
transport: http(),
});
const walletClient = createWalletClient({
account,
chain: chain,
transport: http(),
});
const [chainId, privateKey, feeCurrency, waitBlocks, replaceTxAfterWait, celoValue] = process.argv.slice(2);

function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
Expand Down
76 changes: 76 additions & 0 deletions e2e_test/js-tests/test_viem_smoketest.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { assert } from "chai";
import "mocha";
import {
parseAbi,
} from "viem";
import fs from "fs";
import { publicClient, walletClient } from "./viem_setup.mjs"

// Load compiled contract
const testContractJSON = JSON.parse(fs.readFileSync(process.env.COMPILED_TEST_CONTRACT, 'utf8'));

// check checks that the receipt has status success and that the transaction
// type matches the expected type, since viem sometimes mangles the type when
// building txs.
async function check(txHash, type) {
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
assert.equal(receipt.status, "success", "receipt status 'failure'");
const transaction = await publicClient.getTransaction({ hash: txHash });
assert.equal(transaction.type, type, "transaction type does not match");
}

// sendTypedTransaction sends a transaction with the given type and an optional
// feeCurrency.
async function sendTypedTransaction(type, feeCurrency) {
return await walletClient.sendTransaction({
to: "0x00000000000000000000000000000000DeaDBeef",
value: 1,
type: type,
feeCurrency: feeCurrency,
});
}

// sendTypedSmartContractTransaction initiates a token transfer with the given type
// and an optional feeCurrency.
async function sendTypedSmartContractTransaction(type, feeCurrency) {
const abi = parseAbi(['function transfer(address to, uint256 value) external returns (bool)']);
return await walletClient.writeContract({
abi: abi,
address: process.env.TOKEN_ADDR,
functionName: 'transfer',
args: ['0x00000000000000000000000000000000DeaDBeef', 1n],
type: type,
feeCurrency: feeCurrency,
});
}

// sendTypedCreateTransaction sends a create transaction with the given type
// and an optional feeCurrency.
async function sendTypedCreateTransaction(type, feeCurrency) {
return await walletClient.deployContract({
type: type,
feeCurrency: feeCurrency,
bytecode: testContractJSON.bytecode.object,
abi: testContractJSON.abi,
// The constructor args for the test contract at ../debug-fee-currency/DebugFeeCurrency.sol
args: [1n, true, true, true],
});
}

["legacy", "eip2930", "eip1559", "cip64"].forEach(function (type) {
describe("viem smoke test, tx type " + type, () => {
const feeCurrency = type == "cip64" ? process.env.FEE_CURRENCY : undefined;
it("send tx", async () => {
const send = await sendTypedTransaction(type, feeCurrency);
await check(send, type);
});
it("send create tx", async () => {
const create = await sendTypedCreateTransaction(type, feeCurrency);
await check(create, type);
});
it("send contract interaction tx", async () => {
const contract = await sendTypedSmartContractTransaction(type, feeCurrency);
await check(contract, type);
});
});
});
73 changes: 13 additions & 60 deletions e2e_test/js-tests/test_viem_tx.mjs
Original file line number Diff line number Diff line change
@@ -1,48 +1,9 @@
import { assert } from "chai";
import "mocha";
import {
createPublicClient,
createWalletClient,
http,
defineChain,
parseAbi,
} from "viem";
import { celoAlfajores } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";

// Setup up chain
const devChain = defineChain({
...celoAlfajores,
id: 1337,
name: "local dev chain",
network: "dev",
rpcUrls: {
default: {
http: [process.env.ETH_RPC_URL],
},
},
});

const chain = (() => {
switch (process.env.NETWORK) {
case 'alfajores':
return celoAlfajores
default:
return devChain
};
})();

// Set up clients/wallet
const publicClient = createPublicClient({
chain: chain,
transport: http(),
});
const account = privateKeyToAccount(process.env.ACC_PRIVKEY);
const walletClient = createWalletClient({
account,
chain: chain,
transport: http(),
});
import { publicClient, walletClient } from "./viem_setup.mjs"

// Returns the base fee per gas for the current block multiplied by 2 to account for any increase in the subsequent block.
async function getGasFees(publicClient, tip, feeCurrency) {
Expand All @@ -60,13 +21,12 @@ const testNonceBump = async (
shouldReplace,
) => {
const syncBarrierRequest = await walletClient.prepareTransactionRequest({
account,

to: "0x00000000000000000000000000000000DeaDBeef",
value: 2,
gas: 22000,
});
const firstTxHash = await walletClient.sendTransaction({
account,
to: "0x00000000000000000000000000000000DeaDBeef",
value: 2,
gas: 171000,
Expand All @@ -78,7 +38,6 @@ const testNonceBump = async (
var secondTxHash;
try {
secondTxHash = await walletClient.sendTransaction({
account,
to: "0x00000000000000000000000000000000DeaDBeef",
value: 3,
gas: 171000,
Expand All @@ -95,7 +54,7 @@ const testNonceBump = async (
shouldReplace
) {
throw err; // Only throw if unexpected error.
}
}
}
const syncBarrierSignature =
await walletClient.signTransaction(syncBarrierRequest);
Expand All @@ -115,7 +74,6 @@ const testNonceBump = async (
describe("viem send tx", () => {
it("send basic tx and check receipt", async () => {
const request = await walletClient.prepareTransactionRequest({
account,
to: "0x00000000000000000000000000000000DeaDBeef",
value: 1,
gas: 21000,
Expand All @@ -130,7 +88,6 @@ describe("viem send tx", () => {

it("send basic tx using viem gas estimation and check receipt", async () => {
const request = await walletClient.prepareTransactionRequest({
account,
to: "0x00000000000000000000000000000000DeaDBeef",
value: 1,
});
Expand All @@ -145,7 +102,6 @@ describe("viem send tx", () => {
it("send fee currency tx with explicit gas fields and check receipt", async () => {
const [maxFeePerGas, tip] = await getGasFees(publicClient, 2n, process.env.FEE_CURRENCY);
const request = await walletClient.prepareTransactionRequest({
account,
to: "0x00000000000000000000000000000000DeaDBeef",
value: 2,
gas: 171000,
Expand All @@ -163,7 +119,6 @@ describe("viem send tx", () => {

it("test gas price difference for fee currency", async () => {
const request = await walletClient.prepareTransactionRequest({
account,
to: "0x00000000000000000000000000000000DeaDBeef",
value: 2,
gas: 171000,
Expand Down Expand Up @@ -208,8 +163,8 @@ describe("viem send tx", () => {
// The expected value for the max fee should be the (baseFeePerGas * multiplier) + maxPriorityFeePerGas
// Instead what is currently returned is (maxFeePerGas * multiplier) + maxPriorityFeePerGas
const maxPriorityFeeInFeeCurrency = (maxPriorityFeePerGasNative * numerator) / denominator;
const maxFeeInFeeCurrency = ((block.baseFeePerGas +maxPriorityFeePerGasNative)*numerator) /denominator;
assert.equal(fees.maxFeePerGas, ((maxFeeInFeeCurrency*12n)/10n) + maxPriorityFeeInFeeCurrency);
const maxFeeInFeeCurrency = ((block.baseFeePerGas + maxPriorityFeePerGasNative) * numerator) / denominator;
assert.equal(fees.maxFeePerGas, ((maxFeeInFeeCurrency * 12n) / 10n) + maxPriorityFeeInFeeCurrency);
assert.equal(fees.maxPriorityFeePerGas, maxPriorityFeeInFeeCurrency);

// check that the prepared transaction request uses the
Expand All @@ -220,7 +175,6 @@ describe("viem send tx", () => {

it("send fee currency with gas estimation tx and check receipt", async () => {
const request = await walletClient.prepareTransactionRequest({
account,
to: "0x00000000000000000000000000000000DeaDBeef",
value: 2,
feeCurrency: process.env.FEE_CURRENCY,
Expand Down Expand Up @@ -286,7 +240,6 @@ describe("viem send tx", () => {

it("send tx with unregistered fee currency", async () => {
const request = await walletClient.prepareTransactionRequest({
account,
to: "0x00000000000000000000000000000000DeaDBeef",
value: 2,
gas: 171000,
Expand Down Expand Up @@ -334,11 +287,11 @@ describe("viem send tx", () => {
assert.fail(`Converted base fee (${convertedBaseFee}) not less than native base fee (${block.baseFeePerGas})`);
}
const request = await walletClient.prepareTransactionRequest({
account,
to: "0x00000000000000000000000000000000DeaDBeef",
value: 2,
gas: 171000,
feeCurrency: process.env.FEE_CURRENCY,
feeCurrency: fc,
maxFeePerGas: convertedBaseFee +2n,
maxPriorityFeePerGas: 2n,
});
Expand All @@ -352,13 +305,13 @@ describe("viem send tx", () => {
});

async function getRate(feeCurrencyAddress) {
const abi = parseAbi(['function getExchangeRate(address token) public view returns (uint256 numerator, uint256 denominator)']);
const [numerator, denominator] = await publicClient.readContract({
address: process.env.FEE_CURRENCY_DIRECTORY_ADDR,
abi: abi,
functionName: 'getExchangeRate',
args: [feeCurrencyAddress],
});
const abi = parseAbi(['function getExchangeRate(address token) public view returns (uint256 numerator, uint256 denominator)']);
const [numerator, denominator] = await publicClient.readContract({
address: process.env.FEE_CURRENCY_DIRECTORY_ADDR,
abi: abi,
functionName: 'getExchangeRate',
args: [feeCurrencyAddress],
});
return {
toFeeCurrency: (v) => (v * numerator) / denominator,
toNative: (v) => (v * denominator) / numerator,
Expand Down
Loading

0 comments on commit 70a7e9b

Please sign in to comment.