Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ _aggregator-*.txt
_operator-*.txt
*.zip

# PR comment output files
pr-comments-*.json

# Environment files
.env.test
.env.local
Expand Down
265 changes: 233 additions & 32 deletions aggregator/rpc_server.go

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions aggregator/withdrawal.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package aggregator

import (
"context"
"fmt"
"math/big"
"strings"

"github.com/AvaProtocol/EigenLayer-AVS/core/chainio/aa"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)

// ERC20 transfer function signature: transfer(address,uint256)
Expand Down Expand Up @@ -88,3 +90,28 @@ func buildERC20WithdrawalCalldata(tokenAddress, recipient common.Address, amount
// - data: transfer function calldata
return aa.PackExecute(tokenAddress, big.NewInt(0), transferCalldata)
}

// CalculateMaxWithdrawableAmount calculates the maximum amount that can be withdrawn
// by subtracting the estimated gas reimbursement from the wallet balance.
// This ensures the wallet has enough balance to cover both withdrawal and reimbursement.
func CalculateMaxWithdrawableAmount(
ctx context.Context,
client *ethclient.Client,
smartWalletAddress common.Address,
estimatedReimbursement *big.Int,
) (*big.Int, error) {
// Get current wallet balance
balance, err := client.BalanceAt(ctx, smartWalletAddress, nil)
if err != nil {
return nil, fmt.Errorf("failed to get wallet balance: %w", err)
}

// Calculate max withdrawable: balance - reimbursement
// If balance is less than reimbursement, return 0
maxWithdrawable := new(big.Int).Sub(balance, estimatedReimbursement)
if maxWithdrawable.Cmp(big.NewInt(0)) < 0 {
return big.NewInt(0), nil
}

return maxWithdrawable, nil
}
4 changes: 2 additions & 2 deletions cmd/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ var (
}
defer db.Close()

// Check active tasks from database
kvs, err := db.GetByPrefix(taskengine.TaskByStatusStoragePrefix(avsproto.TaskStatus_Active))
// Check enabled tasks from database
kvs, err := db.GetByPrefix(taskengine.TaskByStatusStoragePrefix(avsproto.TaskStatus_Enabled))
if err != nil && err != badger.ErrKeyNotFound {
fmt.Printf("❌ Failed to query active tasks: %v\n", err)
os.Exit(1)
Expand Down
8 changes: 4 additions & 4 deletions core/apqueue/cleanup.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ func (q *Queue) CleanupOrphanedJobs() (*CleanupStats, error) {
}

// Check if task exists in any status
// Tasks can be in: active (a), completed (c), failed (f), canceled (l), executing (x)
// Tasks can be in: enabled (a), completed (c), failed (f), disabled (i), running (x)
taskStatuses := []avsproto.TaskStatus{
avsproto.TaskStatus_Active,
avsproto.TaskStatus_Enabled,
avsproto.TaskStatus_Completed,
avsproto.TaskStatus_Failed,
avsproto.TaskStatus_Canceled,
avsproto.TaskStatus_Executing,
avsproto.TaskStatus_Disabled,
avsproto.TaskStatus_Running,
}

taskExists := false
Expand Down
32 changes: 17 additions & 15 deletions core/chainio/aa/aa.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,25 +134,27 @@ func GetSenderAddress(conn *ethclient.Client, ownerAddress common.Address, salt
// GetSenderAddressForFactory computes the smart wallet address using the factory proxy address
// Callers should pass the default factory address from config.DefaultFactoryProxyAddressHex
// or a custom factory address if needed.
//
// NOTE: This function always uses the on-chain factory.getAddress() method as the source of truth,
// because the factory contract uses CREATE2 with ERC1967Proxy creation code, which is different
// from the init code format used in UserOps (factory address + calldata). The factory's getAddress
// method correctly calculates the address that will actually be deployed.
func GetSenderAddressForFactory(conn *ethclient.Client, ownerAddress common.Address, factoryAddress common.Address, salt *big.Int) (*common.Address, error) {
// Use factory proxy address (stable constant) + owner + salt for deterministic address derivation
// This ensures addresses remain stable across factory implementation upgrades
addr, err := computeSmartWalletAddress(factoryAddress, ownerAddress, salt)
// Always use on-chain factory.getAddress() as the source of truth
// The factory contract's getAddress method uses CREATE2 with ERC1967Proxy creation code,
// which is different from the init code format (factory address + calldata) used in UserOps.
// This ensures we get the correct address that will actually be deployed.
simpleFactory, err := NewSimpleFactory(factoryAddress, conn)
if err != nil {
// Fallback to on-chain call if local calculation fails
simpleFactory, err2 := NewSimpleFactory(factoryAddress, conn)
if err2 != nil {
return nil, fmt.Errorf("failed to create factory instance at %s: %w", factoryAddress.Hex(), err2)
}
return nil, fmt.Errorf("failed to create factory instance at %s: %w", factoryAddress.Hex(), err)
}

sender, err2 := simpleFactory.GetAddress(nil, ownerAddress, salt)
if err2 != nil {
return nil, fmt.Errorf("failed to derive wallet address from factory %s for owner %s with salt %s: %w (local calc error: %v)",
factoryAddress.Hex(), ownerAddress.Hex(), salt.String(), err2, err)
}
return &sender, nil
sender, err := simpleFactory.GetAddress(nil, ownerAddress, salt)
if err != nil {
return nil, fmt.Errorf("failed to derive wallet address from factory %s for owner %s with salt %s: %w",
factoryAddress.Hex(), ownerAddress.Hex(), salt.String(), err)
}
return &addr, nil
return &sender, nil
}

func GetNonce(conn *ethclient.Client, ownerAddress common.Address, salt *big.Int) (*big.Int, error) {
Expand Down
2 changes: 1 addition & 1 deletion core/taskengine/TENDERLY_STATE_OVERRIDES.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Where:

## Go Implementation

See `calculateERC20StorageSlots()` in `vm_runner_contract_write_uniswap_test.go` for a working example:
Example implementation for calculating ERC20 storage slots:

```go
func calculateERC20StorageSlots(owner, spender, tokenAddress common.Address, balanceSlot, allowanceSlot uint64) (balanceStorageSlot, allowanceStorageSlot string) {
Expand Down
Loading