diff --git a/e2e_test/debug-fee-currency/deploy_and_send_tx.sh b/e2e_test/debug-fee-currency/deploy_and_send_tx.sh index 1cfd9eec73..258b3b013c 100755 --- a/e2e_test/debug-fee-currency/deploy_and_send_tx.sh +++ b/e2e_test/debug-fee-currency/deploy_and_send_tx.sh @@ -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 diff --git a/e2e_test/debug-fee-currency/lib.sh b/e2e_test/debug-fee-currency/lib.sh new file mode 100755 index 0000000000..726f45d43f --- /dev/null +++ b/e2e_test/debug-fee-currency/lib.sh @@ -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" +} diff --git a/e2e_test/js-tests/send_tx.mjs b/e2e_test/js-tests/send_tx.mjs index 5a9b469502..b102adfab0 100755 --- a/e2e_test/js-tests/send_tx.mjs +++ b/e2e_test/js-tests/send_tx.mjs @@ -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(); diff --git a/e2e_test/js-tests/test_viem_tx.mjs b/e2e_test/js-tests/test_viem_tx.mjs index ea79beacaf..b1b2377418 100644 --- a/e2e_test/js-tests/test_viem_tx.mjs +++ b/e2e_test/js-tests/test_viem_tx.mjs @@ -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({ diff --git a/e2e_test/run_all_tests.sh b/e2e_test/run_all_tests.sh index 1a87baa83a..d3d603ca35 100755 --- a/e2e_test/run_all_tests.sh +++ b/e2e_test/run_all_tests.sh @@ -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 @@ -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 @@ -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 diff --git a/e2e_test/shared.sh b/e2e_test/shared.sh index 87d85dc24d..0b946fa0f4 100644 --- a/e2e_test/shared.sh +++ b/e2e_test/shared.sh @@ -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 @@ -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 + ) } diff --git a/e2e_test/test_base_fee_recipient.sh b/e2e_test/test_base_fee_recipient.sh index ec22198f88..9a8b2f9e79 100755 --- a/e2e_test/test_base_fee_recipient.sh +++ b/e2e_test/test_base_fee_recipient.sh @@ -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 +) diff --git a/e2e_test/test_fee_currency_fails_intrinsic.sh b/e2e_test/test_fee_currency_fails_intrinsic.sh index df4d14a512..b1f060658e 100755 --- a/e2e_test/test_fee_currency_fails_intrinsic.sh +++ b/e2e_test/test_fee_currency_fails_intrinsic.sh @@ -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 diff --git a/e2e_test/test_fee_currency_fails_on_credit.sh b/e2e_test/test_fee_currency_fails_on_credit.sh index dbf16dfa2d..bbb13dcfd1 100755 --- a/e2e_test/test_fee_currency_fails_on_credit.sh +++ b/e2e_test/test_fee_currency_fails_on_credit.sh @@ -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 diff --git a/e2e_test/test_fee_currency_fails_on_debit.sh b/e2e_test/test_fee_currency_fails_on_debit.sh index 105c448e36..cfaf0af481 100755 --- a/e2e_test/test_fee_currency_fails_on_debit.sh +++ b/e2e_test/test_fee_currency_fails_on_debit.sh @@ -3,8 +3,14 @@ set -eo pipefail source shared.sh +source debug-fee-currency/lib.sh # Expect that the debitGasFees fails during tx submission -(cd debug-fee-currency && ./deploy_and_send_tx.sh true false false) &>debug-fee-currency/send_tx.log || true -grep "debitGasFees reverted: This DebugFeeCurrency always fails in debitGasFees!" debug-fee-currency/send_tx.log || - (cat debug-fee-currency/send_tx.log && false) +( + # source ./debug-fee-currency/lib.sh + + fee_currency=$(deploy_fee_currency true false false) + # this fails during the RPC call, since the DebitFees() is part of the pre-validation + cip_64_tx $fee_currency | assert_cip_64_tx false "fee-currency internal error" + +)