Skip to content

Commit 379238d

Browse files
committed
test(taskengine): prove erc20_overrides flip the Uniswap swap revert end-to-end
Strengthen the RPC-level E2E test so it is a real before/after rather than a revert-string check: assert the control run (no overrides) reverts on the funding/approval precondition, then assert the same swap succeeds once erc20_overrides seed USDC balance (slot 9) and the SwapRouter02 allowance (slot 10) through the request → proto → RunNodeImmediately → SimulationStateMap → Tenderly path. Slots confirmed empirically against the deployed Sepolia USDC.
1 parent ba78188 commit 379238d

1 file changed

Lines changed: 40 additions & 17 deletions

File tree

core/taskengine/run_node_immediately_rpc_test.go

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package taskengine
33
import (
44
"fmt"
55
"math/big"
6+
"strings"
67
"testing"
78

89
"github.com/AvaProtocol/EigenLayer-AVS/core/chainio/aa"
@@ -465,31 +466,38 @@ func TestRunNodeImmediatelyRPC(t *testing.T) {
465466
require.NotNil(t, control)
466467
t.Logf("control (no overrides): success=%v error=%q", control.Success, control.Error)
467468

469+
// The swap pulls USDC from the runner via transferFrom, so without an
470+
// approval (and balance) it reverts on the funding/approval precondition.
471+
// The control run above establishes that baseline; assert it so the
472+
// override run below is a meaningful before/after, not a no-op.
473+
require.Falsef(t, control.Success,
474+
"control run (no overrides) unexpectedly succeeded — the test wallet already has a standing USDC approval for SwapRouter02, so this test no longer proves erc20_overrides did anything. Reset the wallet's approval or use a fresh runner. control.Error=%q", control.Error)
475+
require.True(t,
476+
isFundingOrApprovalRevert(control.Error),
477+
"control run should revert on the allowance/balance precondition the override targets, got: "+control.Error)
478+
468479
// Seed the runner's USDC balance and its approval for SwapRouter02 via the
469-
// request. USDC on Sepolia is a FiatToken (balance slot 9, allowance slot
470-
// 10); we also cover the standard ERC20 layout (balance 0, allowance 3) so
471-
// the override lands regardless of the deployed token's storage layout. Each
472-
// entry drives a distinct slot through the real request → proto →
473-
// simulation-state path.
480+
// request. USDC on Sepolia is Circle's FiatToken: balanceOf lives at
481+
// storage slot 9 and allowance at slot 10 (confirmed empirically against
482+
// the deployed token). Each entry drives a distinct slot through the real
483+
// request → proto → RunNodeImmediately → SimulationStateMap → Tenderly
484+
// state_objects path — the surface this PR adds.
474485
maxAllowance := "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
475-
bigBalance := "0x38d7ea4c68000" // 1,000,000 USDC
476-
overrides := []*avsproto.ERC20StateOverride{}
477-
for _, slot := range []uint64{0, 9} {
478-
overrides = append(overrides, &avsproto.ERC20StateOverride{
486+
bigBalance := "0x38d7ea4c68000" // 1,000,000 USDC (6 decimals)
487+
overrides := []*avsproto.ERC20StateOverride{
488+
{
479489
TokenAddress: overridesUSDC,
480490
OwnerAddress: runner,
481491
Balance: strPtr(bigBalance),
482-
BalanceSlot: u64Ptr(slot),
483-
})
484-
}
485-
for _, slot := range []uint64{3, 10} {
486-
overrides = append(overrides, &avsproto.ERC20StateOverride{
492+
BalanceSlot: u64Ptr(9),
493+
},
494+
{
487495
TokenAddress: overridesUSDC,
488496
OwnerAddress: runner,
489497
SpenderAddress: strPtr(overridesSwapRouter02),
490498
Allowance: strPtr(maxAllowance),
491-
AllowanceSlot: u64Ptr(slot),
492-
})
499+
AllowanceSlot: u64Ptr(10),
500+
},
493501
}
494502

495503
result, err := engine.RunNodeImmediatelyRPC(user, newReq(overrides))
@@ -499,13 +507,19 @@ func TestRunNodeImmediatelyRPC(t *testing.T) {
499507

500508
// The feature's contract: with balance + allowance seeded through the
501509
// request, the simulation must not revert on the funding/approval
502-
// precondition. Any remaining revert (e.g. pool liquidity) is unrelated.
510+
// precondition that the control run hit.
503511
assert.NotContains(t, result.Error, "transfer amount exceeds allowance",
504512
"erc20_overrides should have seeded the allowance through the RPC path")
505513
assert.NotContains(t, result.Error, "transfer amount exceeds balance",
506514
"erc20_overrides should have seeded the balance through the RPC path")
507515
assert.NotContains(t, result.Error, "STF",
508516
"Uniswap TransferHelper 'STF' means transferFrom failed — overrides should prevent it")
517+
518+
// Decisive proof: the override run flips the control's precondition revert
519+
// into a clean simulated swap. Only the seeded balance/allowance — flowing
520+
// from the request all the way into the Tenderly simulation — can do that.
521+
assert.Truef(t, result.Success,
522+
"swap simulation should succeed once erc20_overrides seed balance+allowance, got error=%q", result.Error)
509523
})
510524
}
511525

@@ -521,6 +535,15 @@ const (
521535
func strPtr(s string) *string { return &s }
522536
func u64Ptr(v uint64) *uint64 { return &v }
523537

538+
// isFundingOrApprovalRevert reports whether a simulation error is the
539+
// ERC20 funding/approval precondition that erc20_overrides exist to seed —
540+
// either the OpenZeppelin-style messages or Uniswap's TransferHelper "STF".
541+
func isFundingOrApprovalRevert(errMsg string) bool {
542+
return strings.Contains(errMsg, "transfer amount exceeds allowance") ||
543+
strings.Contains(errMsg, "transfer amount exceeds balance") ||
544+
strings.Contains(errMsg, "STF")
545+
}
546+
524547
// exactInputSingleSwapNode builds a SwapRouter02.exactInputSingle contractWrite
525548
// node that pulls `amountIn` USDC from the runner via transferFrom — the exact
526549
// path that reverts with "transfer amount exceeds allowance/balance" (Uniswap

0 commit comments

Comments
 (0)