Skip to content

Commit

Permalink
Refactor e2e tests
Browse files Browse the repository at this point in the history
Since some e2e tests now produce invalidated transactions that remain
in the txpool until replaced, the `send_tx.mjs` script required some
changes to replace the sent CIP64 transaction. It will get replaced
after 2 blocks of producing no receipt.
Those changes also required the geth-node to run in periodic
--dev mode instead of the on-demand "produce-block-per-transaction"
mode, which
will unfortunately make the tests run longer than before.

The bash scripts and tests itself have also been refactored
to make code segments more reusable with the help of functions.
  • Loading branch information
ezdac committed Sep 10, 2024
1 parent a83780f commit b35cbe1
Show file tree
Hide file tree
Showing 10 changed files with 255 additions and 66 deletions.
11 changes: 3 additions & 8 deletions e2e_test/debug-fee-currency/deploy_and_send_tx.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@
#shellcheck disable=SC2034,SC2155,SC2086
set -xeo pipefail

export FEE_CURRENCY=$(\
forge create --root . --contracts . --private-key $ACC_PRIVKEY DebugFeeCurrency.sol:DebugFeeCurrency --constructor-args '100000000000000000000000000' $1 $2 $3 --json\
| jq .deployedTo -r)
source ./lib.sh

cast send --private-key $ACC_PRIVKEY $ORACLE3 'setExchangeRate(address, uint256, uint256)' $FEE_CURRENCY 2ether 1ether
cast send --private-key $ACC_PRIVKEY $FEE_CURRENCY_DIRECTORY_ADDR 'setCurrencyConfig(address, address, uint256)' $FEE_CURRENCY $ORACLE3 60000
echo Fee currency: $FEE_CURRENCY

(cd ../js-tests/ && ./send_tx.mjs "$(cast chain-id)" $ACC_PRIVKEY $FEE_CURRENCY)
fee_currency=$(deploy_fee_currency $1 $2 $3)
cip_64_tx $fee_currency
60 changes: 60 additions & 0 deletions e2e_test/debug-fee-currency/lib.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/bin/bash
#shellcheck disable=SC2034,SC2155,SC2086
set -xeo pipefail

function txpool_content() {
cast rpc txpool_content | jq
}

# args:
# $1: failOnDebit (bool):
# if true, this will make the DebugFeeCurrenc.DebitFees() call fail with a revert
# $2: failOnCredit (bool)
# if true, this will make the DebugFeeCurrenc.CreditFees() call fail with a revert
# $3: highGasOnCredit (bool)
# if true, this will make the DebugFeeCurrenc.CreditFees() call use
# a high amount of gas
# returns:
# deployed fee-currency address
function deploy_fee_currency() {
(
local fee_currency=$(
forge create --root "$SCRIPT_DIR/debug-fee-currency" --contracts "$SCRIPT_DIR/debug-fee-currency" --private-key $ACC_PRIVKEY DebugFeeCurrency.sol:DebugFeeCurrency --constructor-args '100000000000000000000000000' $1 $2 $3 --json | jq .deployedTo -r
)
if [ -z "${fee_currency}" ]; then
exit 1
fi
cast send --private-key $ACC_PRIVKEY $ORACLE3 'setExchangeRate(address, uint256, uint256)' $fee_currency 2ether 1ether &>/dev/null
cast send --private-key $ACC_PRIVKEY $FEE_CURRENCY_DIRECTORY_ADDR 'setCurrencyConfig(address, address, uint256)' $fee_currency $ORACLE3 60000 &>/dev/null
echo "$fee_currency"
)
}

# args:
# $1: feeCurrencyAddress (string):
# which fee-currency address to use for the default CIP-64 transaction
function cip_64_tx() {
$SCRIPT_DIR/js-tests/send_tx.mjs "$(cast chain-id)" $ACC_PRIVKEY $1
}

# use this function to assert the cip_64_tx return value, by using a pipe like
# `cip_64_tx "$fee-currency" | assert_cip_64_tx true`
#
# args:
# $1: success (string):
# expected success value, "true" for when the cip-64 tx should have succeeded, "false" if not
# $2: error-regex (string):
# expected RPC return-error value regex to grep for, use "null", "" or unset value if no error is assumed.
function assert_cip_64_tx() {
local value
read -r value
local expected_error="$2"

if [ "$(echo "$value" | jq .success)" != "$1" ]; then
exit 1
fi
if [ -z "$expected_error" ]; then
expected_error="null"
fi
echo "$value" | jq .error | grep -qE "$expected_error"
}
143 changes: 113 additions & 30 deletions e2e_test/js-tests/send_tx.mjs
Original file line number Diff line number Diff line change
@@ -1,44 +1,127 @@
#!/usr/bin/env node
import {
createPublicClient,
createWalletClient,
http,
defineChain,
createWalletClient,
createPublicClient,
http,
defineChain,
TransactionReceiptNotFoundError,
} from "viem";
import { celoAlfajores } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";

const [chainId, privateKey, feeCurrency] = 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"],
},
},
...celoAlfajores,
id: parseInt(chainId, 10),
name: "local dev chain",
network: "dev",
rpcUrls: {
default: {
http: ["http://127.0.0.1:8545"],
},
},
});

const account = privateKeyToAccount(privateKey);
const walletClient = createWalletClient({
account,
chain: devChain,
transport: http(),
});

const request = await walletClient.prepareTransactionRequest({
account,
to: "0x00000000000000000000000000000000DeaDBeef",
value: 2,
gas: 90000,
feeCurrency,
maxFeePerGas: 2000000000n,
maxPriorityFeePerGas: 0n,
const publicClient = createPublicClient({
account,
chain: devChain,
transport: http(),
});
const signature = await walletClient.signTransaction(request);
const hash = await walletClient.sendRawTransaction({
serializedTransaction: signature,
const walletClient = createWalletClient({
account,
chain: devChain,
transport: http(),
});
console.log(hash);

async function getTransactionReceiptAfterWait(hash, numBlocks) {
var count = 0;

const res = new Promise((resolve) => {
resolve();
});
const unwatch = publicClient.watchBlockNumber({
onBlockNumber: () => {
count++;
if (count >= numBlocks) {
res.resolve();
}
},
});
await res;
unwatch();
try {
return await publicClient.getTransactionReceipt({ hash: hash });
} catch (e) {
if (e instanceof TransactionReceiptNotFoundError) {
return undefined;
}
throw e;
}
}

async function replaceTx(tx) {
const request = await walletClient.prepareTransactionRequest({
account: tx.account,
to: account.address,
value: 0n,
gas: 21000,
nonce: tx.nonce,
maxFeePerGas: tx.maxFeePerGas,
maxPriorityFeePerGas: tx.maxPriorityFeePerGas + 1000n,
});
const hash = await walletClient.sendRawTransaction({
serializedTransaction: await walletClient.signTransaction(request),
});
const receipt = await publicClient.waitForTransactionReceipt({
hash: hash,
confirmations: 2,
});
return receipt;
}

async function main() {
const request = await walletClient.prepareTransactionRequest({
account,
to: "0x00000000000000000000000000000000DeaDBeef",
value: 2n,
gas: 90000,
feeCurrency,
maxFeePerGas: 2000000000n,
maxPriorityFeePerGas: 0n,
});

var hash;
try {
hash = await walletClient.sendRawTransaction({
serializedTransaction: await walletClient.signTransaction(request),
});
} catch (e) {
// direct revert
console.log(
JSON.stringify({
success: false,
error: e,
}),
);
return;
}

var success = true;
var receipt = await getTransactionReceiptAfterWait(hash, 2);
if (!receipt) {
receipt = await replaceTx(request);
success = false;
}
// print for bash script wrapper return value
console.log(
JSON.stringify({
success: success,
error: null,
}),
);

return receipt;
}
await main();
2 changes: 1 addition & 1 deletion e2e_test/js-tests/test_viem_tx.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ describe("viem send tx", () => {
nativeCurrency,
false,
);
}).timeout(10_000);
}).timeout(20_000);

it("send tx with unregistered fee currency", async () => {
const request = await walletClient.prepareTransactionRequest({
Expand Down
25 changes: 11 additions & 14 deletions e2e_test/run_all_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@
set -eo pipefail

SCRIPT_DIR=$(readlink -f "$(dirname "$0")")
source "$SCRIPT_DIR/shared.sh"
source "$SCRIPT_DIR/debug-fee-currency/lib.sh"

TEST_GLOB=$1

## Start geth
cd "$SCRIPT_DIR/.." || exit 1
make geth
trap 'kill %%' EXIT # kill bg job at exit
build/bin/geth --dev --http --http.api eth,web3,net &> "$SCRIPT_DIR/geth.log" &
trap 'kill %%' EXIT # kill bg job at exit
build/bin/geth --dev --dev.period 1 --http --http.api eth,web3,net &>"$SCRIPT_DIR/geth.log" &

# Wait for geth to be ready
for _ in {1..10}
do
if cast block &> /dev/null
then
for _ in {1..10}; do
if cast block &>/dev/null; then
break
fi
sleep 0.2
Expand All @@ -27,16 +28,13 @@ cd "$SCRIPT_DIR" || exit 1
# There's a problem with geth return errors on the first transaction sent.
# See https://github.com/ethereum/web3.py/issues/3212
# To work around this, send a transaction before running tests
source ./shared.sh
cast send --json --private-key $ACC_PRIVKEY $TOKEN_ADDR 'transfer(address to, uint256 value) returns (bool)' 0x000000000000000000000000000000000000dEaD 100 --async
cast send --json --private-key $ACC_PRIVKEY $TOKEN_ADDR 'transfer(address to, uint256 value) returns (bool)' 0x000000000000000000000000000000000000dEaD 100

failures=0
tests=0
for f in test_*"$TEST_GLOB"*
do
for f in test_*"$TEST_GLOB"*; do
echo -e "\nRun $f"
if "./$f"
then
if "./$f"; then
tput setaf 2 || true
echo "PASS $f"
else
Expand All @@ -50,8 +48,7 @@ done

## Final summary
echo
if [[ $failures -eq 0 ]]
then
if [[ $failures -eq 0 ]]; then
tput setaf 2 || true
echo All $tests tests succeeded!
else
Expand Down
9 changes: 7 additions & 2 deletions e2e_test/shared.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/bin/bash
#shellcheck disable=SC2034 # unused vars make sense in a shared file

SCRIPT_DIR=$(readlink -f "$(dirname "$0")")
export SCRIPT_DIR
export ETH_RPC_URL=http://127.0.0.1:8545

export ACC_ADDR=0x42cf1bbc38BaAA3c4898ce8790e21eD2738c6A4a
Expand All @@ -15,6 +17,9 @@ export ORACLE3=0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb0003
export FIXIDITY_1=1000000000000000000000000
export ZERO_ADDRESS=0x0000000000000000000000000000000000000000

prepare_node () {
(cd js-tests || exit 1; [[ -d node_modules ]] || npm install)
prepare_node() {
(
cd js-tests || exit 1
[[ -d node_modules ]] || npm install
)
}
5 changes: 4 additions & 1 deletion e2e_test/test_base_fee_recipient.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ base_fee=$(cast base-fee $block_number)
expected_balance_change=$((base_fee * gas_used))
balance_after=$(cast balance $FEE_HANDLER)
echo "Balance change: $balance_before -> $balance_after"
[[ $((balance_before + expected_balance_change)) -eq $balance_after ]] || (echo "Balance did not change as expected"; exit 1)
[[ $((balance_before + expected_balance_change)) -eq $balance_after ]] || (
echo "Balance did not change as expected"
exit 1
)
26 changes: 23 additions & 3 deletions e2e_test/test_fee_currency_fails_intrinsic.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,30 @@
set -eo pipefail

source shared.sh
source debug-fee-currency/lib.sh

# Expect that the creditGasFees failed and is logged by geth
tail -f -n0 geth.log >debug-fee-currency/geth.intrinsic.log & # start log capture
(cd debug-fee-currency && ./deploy_and_send_tx.sh false false true)
tail -F -n 0 geth.log >debug-fee-currency/geth.intrinsic.log & # start log capture
(
sleep 0.2
fee_currency=$(deploy_fee_currency false false true)

# trigger the first failed call to the CreditFees(), causing the
# currency to get temporarily blocklisted.
# initial tx should not succeed, should have required a replacement transaction.
cip_64_tx $fee_currency | assert_cip_64_tx false

sleep 2

# since the fee currency is temporarily blocked,
# this should NOT make the transaction execute anymore,
# but invalidate the transaction earlier.
# initial tx should not succeed, should have required a replacement transaction.
cip_64_tx $fee_currency | assert_cip_64_tx false

)
sleep 0.5
kill %1 # stop log capture
grep "surpassed maximum allowed intrinsic gas for CreditFees() in fee-currency: out of gas" debug-fee-currency/geth.intrinsic.log
# although we sent a transaction wih faulty fee-currency twice,
# the EVM call should have been executed only once
if [ "$(grep -aEc "fee-currency EVM execution error, temporarily blocking fee-currency in local txpools .+ surpassed maximum allowed intrinsic gas for CreditFees\(\) in fee-currency" debug-fee-currency/geth.intrinsic.log)" -ne 1 ]; then exit 1; fi
28 changes: 24 additions & 4 deletions e2e_test/test_fee_currency_fails_on_credit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,30 @@
set -eo pipefail

source shared.sh
source debug-fee-currency/lib.sh

# Expect that the creditGasFees failed and is logged by geth
tail -f -n0 geth.log >debug-fee-currency/geth.partial.log & # start log capture
(cd debug-fee-currency && ./deploy_and_send_tx.sh false true false)
tail -F -n0 geth.log >debug-fee-currency/geth.partial.log & # start log capture
(
sleep 0.2
fee_currency=$(deploy_fee_currency false true false)

# trigger the first failed call to the CreditFees(), causing the
# currency to get temporarily blocklisted.
# initial tx should not succeed, should have required a replacement transaction.
cip_64_tx $fee_currency | assert_cip_64_tx false

sleep 2

# since the fee currency is temporarily blocked,
# this should NOT make the transaction execute anymore,
# but invalidate the transaction earlier.
# initial tx should not succeed, should have required a replacement transaction.
cip_64_tx $fee_currency | assert_cip_64_tx false

)
sleep 0.5
kill %1 # stop log capture
grep "This DebugFeeCurrency always fails in (old) creditGasFees!" debug-fee-currency/geth.partial.log
# although we sent a transaction wih faulty fee-currency twice,
# the EVM call should have been executed only once
grep -a "" debug-fee-currency/geth.partial.log
if [ "$(grep -aEc "fee-currency EVM execution error, temporarily blocking fee-currency in local txpools .+ This DebugFeeCurrency always fails in \(old\) creditGasFees!" debug-fee-currency/geth.partial.log)" -ne 1 ]; then exit 1; fi
Loading

0 comments on commit b35cbe1

Please sign in to comment.