diff --git a/test/basic-e2e.bats b/test/basic-e2e.bats index cbd845f5..7124dcc2 100644 --- a/test/basic-e2e.bats +++ b/test/basic-e2e.bats @@ -11,11 +11,26 @@ setup() { } @test "Send EOA transaction" { + local sender_addr=$(cast wallet address --private-key "$private_key") + local initial_nonce=$(cast nonce "$sender_addr" --rpc-url "$rpc_url") || return 1 local value="10ether" + # case 1: Transaction successful sender has sufficient balance run sendTx "$private_key" "$receiver" "$value" assert_success assert_output --regexp "Transaction successful \(transaction hash: 0x[a-fA-F0-9]{64}\)" + + # case 2: Transaction rejected as sender attempts to transfer more than it has in its wallet. + # Transaction will fail pre-validation check on the node and will be dropped subsequently from the pool + # without recording it on the chain and hence nonce will not change + local sender_balance=$(cast balance "$sender_addr" --ether --rpc-url "$rpc_url") || return 1 + local excessive_value=$(echo "$sender_balance + 1" | bc)"ether" + run sendTx "$private_key" "$receiver" "$excessive_value" + assert_failure + + # Check whether the sender's nonce was updated correctly + local final_nonce=$(cast nonce "$sender_addr" --rpc-url "$rpc_url") || return 1 + assert_equal "$final_nonce" "$(echo "$initial_nonce + 1" | bc)" } @test "Deploy ERC20Mock contract" { diff --git a/test/helpers/common.bash b/test/helpers/common.bash index 709bf843..c0608d3b 100644 --- a/test/helpers/common.bash +++ b/test/helpers/common.bash @@ -16,13 +16,13 @@ function deployContract() { fi # Get the sender address - local senderAddr=$(cast wallet address "$private_key") + local sender_addr=$(cast wallet address "$private_key") if [[ $? -ne 0 ]]; then echo "Error: Failed to retrieve sender address." return 1 fi - echo "Attempting to deploy contract artifact '$contract_artifact' to $rpc_url (sender: $senderAddr)" >&3 + echo "Attempting to deploy contract artifact '$contract_artifact' to $rpc_url (sender: $sender_addr)" >&3 # Get bytecode from the contract artifact local bytecode=$(jq -r .bytecode "$contract_artifact") @@ -76,77 +76,111 @@ function sendTx() { fi local private_key="$1" # Sender private key - local account_addr="$2" # Receiver address + local receiver_addr="$2" # Receiver address local value_or_function_sig="$3" # Value or function signature # Error handling: Ensure the receiver is a valid Ethereum address - if [[ ! "$account_addr" =~ ^0x[a-fA-F0-9]{40}$ ]]; then - echo "Error: Invalid receiver address '$account_addr'." + if [[ ! "$receiver_addr" =~ ^0x[a-fA-F0-9]{40}$ ]]; then + echo "Error: Invalid receiver address '$receiver_addr'." return 1 fi - shift 3 # Shift the first 3 arguments (private_key, account_addr, value_or_function_sig) + shift 3 # Shift the first 3 arguments (private_key, receiver_addr, value_or_function_sig) + local params=("$@") # Collect all remaining arguments as function parameters - local senderAddr - senderAddr=$(cast wallet address "$private_key") - if [[ $? -ne 0 ]]; then - echo "Error: Failed to extract the sender address for $private_key" + # Get sender address from private key + local sender_addr + sender_addr=$(cast wallet address "$private_key") || { + echo "Error: Failed to extract the sender address." return 1 - fi + } - # Check if the first remaining argument is a numeric value (Ether to be transferred) - if [[ "$value_or_function_sig" =~ ^[0-9]+(ether)?$ ]]; then - # Case: EOA transaction (Ether transfer) - echo "Sending EOA transaction (RPC URL: $rpc_url, sender: $senderAddr) to: $account_addr " \ - "with value: $value_or_function_sig" >&3 + # Get initial ether balances of sender and receiver + local sender_initial_balance receiver_initial_balance + sender_initial_balance=$(cast balance "$sender_addr" --ether --rpc-url "$rpc_url") || return 1 + receiver_initial_balance=$(cast balance "$receiver_addr" --ether --rpc-url "$rpc_url") || return 1 - cast_output=$(cast send --rpc-url "$rpc_url" \ - --private-key "$private_key" \ - "$account_addr" --value "$value_or_function_sig" \ - --legacy \ - 2>&1) + # Check if the value_or_function_sig is a numeric value (Ether to be transferred) + if [[ "$value_or_function_sig" =~ ^[0-9]+(\.[0-9]+)?(ether)?$ ]]; then + # Case: Ether transfer (EOA transaction) + send_eoa_transaction "$private_key" "$receiver_addr" "$value_or_function_sig" "$sender_addr" "$sender_initial_balance" "$receiver_initial_balance" else - # Case: Smart contract transaction (contract interaction with function signature and parameters) - local params=("$@") # Collect all remaining arguments as function parameters + # Case: Smart contract interaction (contract interaction with function signature and parameters) + send_smart_contract_transaction "$private_key" "$receiver_addr" "$value_or_function_sig" "$sender_addr" "${params[@]}" + fi +} + +function send_eoa_transaction() { + local private_key="$1" + local receiver_addr="$2" + local value="$3" + local sender_addr="$4" + local sender_initial_balance="$5" + local receiver_initial_balance="$6" - echo "Function signature: '$value_or_function_sig'" >&3 + echo "Sending EOA transaction to: $receiver_addr with value: $value" >&3 - # Verify if the function signature starts with "function" - if [[ ! "$value_or_function_sig" =~ ^function\ .+\(.+\)$ ]]; then - echo "Error: Invalid function signature format '$value_or_function_sig'." - return 1 - fi + # Send transaction via cast + local cast_output tx_hash + cast_output=$(cast send --rpc-url "$rpc_url" --private-key "$private_key" "$receiver_addr" --value "$value" --legacy 2>&1) + if [[ $? -ne 0 ]]; then + echo "Error: Failed to send transaction. Output:" + echo "$cast_output" + return 1 + fi - echo "Sending smart contract transaction (RPC URL: $rpc_url, sender: $senderAddr) to $account_addr" \ - "with function signature: '$value_or_function_sig' and params: ${params[*]}" >&3 + tx_hash=$(extract_tx_hash "$cast_output") + [[ -z "$tx_hash" ]] && { + echo "Error: Failed to extract transaction hash." + return 1 + } - # Send the smart contract interaction using cast - cast_output=$(cast send --rpc-url "$rpc_url" \ - --private-key "$private_key" \ - "$account_addr" "$value_or_function_sig" "${params[@]}" \ - --legacy \ - 2>&1) + checkBalances "$sender_addr" "$receiver_addr" "$value" "$tx_hash" "$sender_initial_balance" "$receiver_initial_balance" + if [[ $? -ne 0 ]]; then + echo "Error: Balance not updated correctly." + return 1 fi - # Check if the transaction was successful + echo "Transaction successful (transaction hash: $tx_hash)" +} + +function send_smart_contract_transaction() { + local private_key="$1" + local receiver_addr="$2" + local function_sig="$3" + local sender_addr="$4" + shift 4 + local params=("$@") + + # Verify if the function signature starts with "function" + if [[ ! "$function_sig" =~ ^function\ .+\(.+\)$ ]]; then + echo "Error: Invalid function signature format '$function_sig'." + return 1 + fi + + echo "Sending smart contract transaction to $receiver_addr with function signature: '$function_sig' and params: ${params[*]}" >&3 + + # Send the smart contract interaction using cast + local cast_output tx_hash + cast_output=$(cast send --rpc-url "$rpc_url" --private-key "$private_key" "$receiver_addr" "$function_sig" "${params[@]}" --legacy 2>&1) if [[ $? -ne 0 ]]; then - echo "Error: Failed to send transaction. The cast send output:" + echo "Error: Failed to send transaction. Output:" echo "$cast_output" return 1 fi - # Extract the transaction hash from the output - local tx_hash=$(echo "$cast_output" | grep 'transactionHash' | awk '{print $2}' | tail -n 1) - echo "Tx hash: $tx_hash" - - if [[ -z "$tx_hash" ]]; then + tx_hash=$(extract_tx_hash "$cast_output") + [[ -z "$tx_hash" ]] && { echo "Error: Failed to extract transaction hash." return 1 - fi + } echo "Transaction successful (transaction hash: $tx_hash)" +} - return 0 +function extract_tx_hash() { + local cast_output="$1" + echo "$cast_output" | grep 'transactionHash' | awk '{print $2}' | tail -n 1 } function queryContract() { @@ -185,3 +219,58 @@ function queryContract() { return 0 } + +function checkBalances() { + local sender="$1" + local receiver="$2" + local amount="$3" + local tx_hash="$4" + local sender_initial_balance="$5" + local receiver_initial_balance="$6" + + # Ethereum address regex: 0x followed by 40 hexadecimal characters + if [[ ! "$sender" =~ ^0x[a-fA-F0-9]{40}$ ]]; then + echo "Error: Invalid sender address '$sender'." + return 1 + fi + + if [[ ! "$receiver" =~ ^0x[a-fA-F0-9]{40}$ ]]; then + echo "Error: Invalid receiver address '$receiver'." + return 1 + fi + + # Transaction hash regex: 0x followed by 64 hexadecimal characters + if [[ ! "$tx_hash" =~ ^0x[a-fA-F0-9]{64}$ ]]; then + echo "Error: Invalid transaction hash: $tx_hash". + return 1 + fi + + local sender_final_balance=$(cast balance "$sender" --ether --rpc-url "$rpc_url") || return 1 + local gas_used=$(cast tx "$tx_hash" --rpc-url "$rpc_url" | grep '^gas ' | awk '{print $2}') + local gas_price=$(cast tx "$tx_hash" --rpc-url "$rpc_url" | grep '^gasPrice' | awk '{print $2}') + local gas_fee=$(echo "$gas_used * $gas_price" | bc) + local gas_fee_in_ether=$(cast to-unit "$gas_fee" ether) + + local sender_balance_change=$(echo "$sender_initial_balance - $sender_final_balance" | bc) + echo "Sender balance changed by: '$sender_balance_change' wei" + echo "Gas fee paid: '$gas_fee_in_ether' ether" + + local receiver_final_balance=$(cast balance "$receiver" --ether --rpc-url "$rpc_url") || return 1 + local receiver_balance_change=$(echo "$receiver_final_balance - $receiver_initial_balance" | bc) + echo "Receiver balance changed by: '$receiver_balance_change' wei" + + # Trim 'ether' suffix from amount to get the numeric part + local value_in_ether=$(echo "$amount" | sed 's/ether$//') + + if ! echo "$receiver_balance_change == $value_in_ether" | bc -l; then + echo "Error: receiver balance updated incorrectly. Expected: $value_in_ether, Actual: $receiver_balance_change" + return 1 + fi + + # Calculate expected sender balance change + local expected_sender_change=$(echo "$value_in_ether + $gas_fee_in_ether" | bc) + if ! echo "$sender_balance_change == $expected_sender_change" | bc -l; then + echo "Error: sender balance updated incorrectly. Expected: $expected_sender_change, Actual: $sender_balance_change" + return 1 + fi +}