Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
Binary file added tests/jsonrpc/simulator/simulator
Binary file not shown.
29 changes: 29 additions & 0 deletions tests/systemtests/accountabstraction/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package accountabstraction

import (
"crypto/ecdsa"
"testing"
"time"

"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
)

type AccountAbstractionTestSuite interface {
// Test Utils
SetupTest(t *testing.T)
AwaitNBlocks(t *testing.T, n int64, duration ...time.Duration)

// Query
GetChainID() uint64
GetNonce(accID string) uint64
GetPrivKey(accID string) *ecdsa.PrivateKey
GetAddr(accID string) common.Address
GetSmartWalletAddr() common.Address

// Transaction
SendSetCodeTx(accID string, signedAuth ...ethtypes.SetCodeAuthorization) (common.Hash, error)

// Verification
CheckSetCode(authorityAccID string, delegate common.Address, expectDelegation bool)
}
179 changes: 179 additions & 0 deletions tests/systemtests/accountabstraction/test_eip7702.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package accountabstraction

import (
"testing"

"github.com/ethereum/go-ethereum/common"

//nolint:revive // dot imports are fine for Ginkgo
. "github.com/onsi/ginkgo/v2"
//nolint:revive // dot imports are fine for Ginkgo
. "github.com/onsi/gomega"
)

func TestEIP7702(t *testing.T) {
const (
user0 = "acc0"
user1 = "acc1"
)

Describe("test SetCode tx with diverse SetCodeAuthorization", Ordered, func() {
var (
s AccountAbstractionTestSuite
)

// We intentionally use BeforeAll instead of BeforeAll because,
// The test takes too much time if we restart network for each test case.
BeforeAll(func() {
s = NewTestSuite(t)
s.SetupTest(t)
})

AfterEach(func() {
// Reset code of EoAs to 0x0 address
//
// We set user0's authorization nonce to currentNonce + 1
// because user0 will also send the SetCode transaction.
// Since the sender’s nonce is incremented before applying authorization,
// the SetCodeAuthorization must use currentNonce + 1.
user0Nonce := s.GetNonce(user0) + 1
cleanupAuth0 := createSetCodeAuthorization(s.GetChainID(), user0Nonce, common.Address{})
signedCleanup0, signErr := signSetCodeAuthorization(s.GetPrivKey(user0), cleanupAuth0)
Expect(signErr).To(BeNil())

user1Nonce := s.GetNonce(user1)
cleanupAuth1 := createSetCodeAuthorization(s.GetChainID(), user1Nonce, common.Address{})
signedCleanup1, signErr := signSetCodeAuthorization(s.GetPrivKey(user1), cleanupAuth1)
Expect(signErr).To(BeNil())

_, err := s.SendSetCodeTx(user0, signedCleanup0, signedCleanup1)
Expect(err).To(BeNil(), "error while clearing SetCode delegation")

s.AwaitNBlocks(t, 1)
s.CheckSetCode(user0, common.Address{}, false)
s.CheckSetCode(user1, common.Address{}, false)
})

type testCase struct {
authChainID func() uint64
authNonce func() uint64
authAddress func() common.Address
authSigner string
txSender string
expDelegation bool
}

DescribeTable("SetCode authorization scenarios", func(tc testCase) {
authorization := createSetCodeAuthorization(tc.authChainID(), tc.authNonce(), tc.authAddress())
signedAuthorization, err := signSetCodeAuthorization(s.GetPrivKey(tc.authSigner), authorization)
Expect(err).To(BeNil())

_, err = s.SendSetCodeTx(tc.txSender, signedAuthorization)
Expect(err).To(BeNil(), "error while sending SetCode tx")
s.AwaitNBlocks(t, 1)

s.CheckSetCode(tc.authSigner, tc.authAddress(), tc.expDelegation)
},
Entry("setCode with invalid chainID should fail", testCase{
authChainID: func() uint64 { return s.GetChainID() + 1 },
authNonce: func() uint64 {
return s.GetNonce(user0) + 1
},
authAddress: func() common.Address {
return s.GetSmartWalletAddr()
},
authSigner: user0,
txSender: user0,
expDelegation: false,
}),
Entry("setCode with empty address should reset delegation", testCase{
authChainID: func() uint64 { return s.GetChainID() + 1 },
authNonce: func() uint64 {
return s.GetNonce(user0) + 1
},
authAddress: func() common.Address {
return common.HexToAddress("0x0")
},
authSigner: user0,
txSender: user0,
expDelegation: false,
}),
Entry("setCode with invalid address should fail", testCase{
authChainID: func() uint64 { return s.GetChainID() + 1 },
authNonce: func() uint64 {
return s.GetNonce(user0) + 1
},
authAddress: func() common.Address {
return common.BytesToAddress([]byte("invalid"))
},
authSigner: user0,
txSender: user0,
expDelegation: false,
}),
Entry("setCode with EoA address should fail", testCase{
authChainID: func() uint64 { return s.GetChainID() + 1 },
authNonce: func() uint64 {
return s.GetNonce(user0) + 1
},
authAddress: func() common.Address {
return s.GetAddr(user1)
},
authSigner: user0,
txSender: user0,
expDelegation: false,
}),
Entry("same signer/sender with matching nonce should fail", testCase{
authChainID: func() uint64 { return s.GetChainID() },
authNonce: func() uint64 {
return s.GetNonce(user0)
},
authAddress: func() common.Address {
return s.GetSmartWalletAddr()
},
authSigner: user0,
txSender: user0,
expDelegation: false,
}),
Entry("same signer/sender with future nonce sholud succeed", testCase{
authChainID: func() uint64 { return s.GetChainID() },
authNonce: func() uint64 {
return s.GetNonce(user0) + 1
},
authAddress: func() common.Address {
return s.GetSmartWalletAddr()
},
authSigner: user0,
txSender: user0,
expDelegation: true,
}),
Entry("different signer/sender with current nonce should succeed", testCase{
authChainID: func() uint64 { return s.GetChainID() },
authNonce: func() uint64 {
return s.GetNonce(user1)
},
authAddress: func() common.Address {
return s.GetSmartWalletAddr()
},
authSigner: user1,
txSender: user0,
expDelegation: true,
}),
Entry("different signer/sender with future nonce should fail", testCase{
authChainID: func() uint64 { return s.GetChainID() },
authNonce: func() uint64 {
return s.GetNonce(user1) + 1
},
authAddress: func() common.Address {
return s.GetSmartWalletAddr()
},
authSigner: user1,
txSender: user0,
expDelegation: false,
}),
)
})

// Run Ginkgo integration tests
RegisterFailHandler(Fail)
RunSpecs(t, "EIP7702 Integration Test Suite")
}
126 changes: 126 additions & 0 deletions tests/systemtests/accountabstraction/test_suite.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package accountabstraction

import (
"context"
"crypto/ecdsa"
"fmt"
"path/filepath"
"testing"

"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/holiman/uint256"

//nolint:revive // dot imports are fine for Ginkgo
. "github.com/onsi/gomega"

basesuite "github.com/cosmos/evm/tests/systemtests/suite"
"github.com/stretchr/testify/require"
)

type TestSuite struct {
*basesuite.SystemTestSuite

smartWalletAddress common.Address
}

func NewTestSuite(t *testing.T) *TestSuite {
return &TestSuite{
SystemTestSuite: basesuite.NewSystemTestSuite(t),
}
}

func (s *TestSuite) SetupTest(t *testing.T) {
s.SystemTestSuite.SetupTest(t)

smartWalletPath := filepath.Join("..", "..", "contracts", "account_abstraction", "smartwallet", "SimpleSmartWallet.json")
bytecode, err := loadSmartWalletCreationBytecode(smartWalletPath)
Expect(err).To(BeNil(), "failed to load smart wallet creation bytecode")

addr, err := deployContract(s.EthClient, bytecode)
require.NoError(t, err, "failed to deploy smart wallet contract")
s.smartWalletAddress = addr
}

func (s *TestSuite) GetChainID() uint64 {
return s.EthClient.ChainID.Uint64()
}

func (s *TestSuite) GetNonce(accID string) uint64 {
nonce, err := s.NonceAt("node0", accID)
Expect(err).To(BeNil())
return nonce
}

func (s *TestSuite) GetPrivKey(accID string) *ecdsa.PrivateKey {
return s.EthClient.Accs[accID].PrivKey
}

func (s *TestSuite) GetAddr(accID string) common.Address {
return s.EthClient.Accs[accID].Address
}

func (s *TestSuite) GetSmartWalletAddr() common.Address {
return s.smartWalletAddress
}

func (s *TestSuite) SendSetCodeTx(accID string, signedAuths ...ethtypes.SetCodeAuthorization) (common.Hash, error) {
ctx := context.Background()
ethCli := s.EthClient.Clients["node0"]
acc := s.EthClient.Accs[accID]
if acc == nil {
return common.Hash{}, fmt.Errorf("account %s not found", accID)
}
key := acc.PrivKey

chainID, err := ethCli.ChainID(ctx)
if err != nil {
return common.Hash{}, fmt.Errorf("failed to get evm chain id")
}

fromAddr := acc.Address
nonce, err := ethCli.PendingNonceAt(ctx, fromAddr)
if err != nil {
return common.Hash{}, fmt.Errorf("failed to fetch pending nonce: %w", err)
}

txdata := &ethtypes.SetCodeTx{
ChainID: uint256.MustFromBig(chainID),
Nonce: nonce,
GasTipCap: uint256.NewInt(1_000_000),
GasFeeCap: uint256.NewInt(1_000_000_000),
Gas: 100_000,
To: common.Address{},
Value: uint256.NewInt(0),
Data: []byte{},
AccessList: ethtypes.AccessList{},
AuthList: signedAuths,
}

signer := ethtypes.LatestSignerForChainID(chainID)
signedTx := ethtypes.MustSignNewTx(key, signer, txdata)

if err := ethCli.SendTransaction(ctx, signedTx); err != nil {
return common.Hash{}, fmt.Errorf("failed to send transaction: %w", err)
}

return signedTx.Hash(), nil
}

func (s *TestSuite) CheckSetCode(authorityAccID string, delegate common.Address, expectDelegation bool) {
code, err := s.EthClient.CodeAt("node0", authorityAccID)
Expect(err).To(BeNil(), "unable to retrieve updated code for %s", authorityAccID)

if expectDelegation {
// 3byte prefix + 20byte authorized contract address
Expect(len(code)).To(Equal(23), "expected delegation code for %s", authorityAccID)
resolvedAddr, ok := ethtypes.ParseDelegation(code)
Expect(ok).To(BeTrue(), "expected delegation prefix in code for %s", authorityAccID)
Expect(resolvedAddr).To(Equal(delegate), "unexpected delegate for %s", authorityAccID)
return
} else {
Expect(len(code)).To(Equal(0), "expected delegation code for %s", authorityAccID)
_, ok := ethtypes.ParseDelegation(code)
Expect(ok).To(BeFalse(), "expected delegation prefix in code for %s", authorityAccID)
}
}
Loading
Loading