diff --git a/Makefile b/Makefile index f286432a..f31b43ed 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,14 @@ start-testchain-anvil: start-testchain-anvil-verbose: cd ./testutil/testchain && pnpm start:anvil:verbose +# Fork a chain: make start-testchain-fork FORK_URL=https://nodes.sequence.app/polygon-zkevm +FORK_URL ?= https://nodes.sequence.app/polygon-zkevm +start-testchain-fork: + cd ./testutil/testchain && FORK_URL="$(FORK_URL)" pnpm start:anvil:fork -- "$(FORK_URL)" + +start-testchain-fork-verbose: + cd ./testutil/testchain && FORK_URL="$(FORK_URL)" pnpm start:anvil:fork:verbose -- "$(FORK_URL)" + check-testchain-running: @curl http://localhost:8545 -H"Content-type: application/json" -X POST -d '{"jsonrpc":"2.0","method":"eth_syncing","params":[],"id":1}' --write-out '%{http_code}' --silent --output /dev/null | grep 200 > /dev/null \ || { echo "*****"; echo "Oops! testchain is not running. Please run 'make start-testchain' in another terminal."; echo "*****"; exit 1; } diff --git a/README.md b/README.md index 715e81e8..6ce8cd4a 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ For documentation on sequence, please see our [docs](https://docs.sequence.xyz) 2. `make start-testchain` -- starts the test ethereum chain (id 1337) 3. (in a separate terminal) `make test` -- runs test suite +To run tests against a forked chain instead: `make start-testchain-fork` (default fork URL is Polygon ZK EVM). Override with `make start-testchain-fork FORK_URL=https://your-rpc-url`. + ## Testing diff --git a/auth.go b/auth.go index 37979b0b..04b02eca 100644 --- a/auth.go +++ b/auth.go @@ -101,7 +101,7 @@ func EIP6492ValidateSignature() ethauth.ValidatorFunc { return false, "", fmt.Errorf("sig is invalid: %w", err) } - isValid, err := eip6492.ValidateEIP6492Offchain(ctx, provider, signer, hash, sig, nil) + isValid, err := eip6492.ValidateEIP6492(ctx, provider, signer, hash, sig, nil) if err != nil { return false, "", fmt.Errorf("failed to validate: %w", err) } diff --git a/core/v2/v2.go b/core/v2/v2.go index 97104b65..7cd8b238 100644 --- a/core/v2/v2.go +++ b/core/v2/v2.go @@ -1036,7 +1036,7 @@ func (l *signatureTreeDynamicSignatureLeaf) recover(ctx context.Context, payload signature := l.signature if provider != nil { - isValid, err := eip6492.ValidateEIP6492Offchain(ctx, provider, l.address, payload.Digest().Hash, signature, nil) + isValid, err := eip6492.ValidateEIP6492(ctx, provider, l.address, payload.Digest().Hash, signature, nil) if err != nil { return nil, nil, fmt.Errorf("unable to validate signature for %v: %w", l.address, err) } diff --git a/core/v3/v3.go b/core/v3/v3.go index edf4a3d4..d8930bec 100644 --- a/core/v3/v3.go +++ b/core/v3/v3.go @@ -1122,7 +1122,7 @@ func (l *signatureTreeSignatureERC1271Leaf) recover(ctx context.Context, payload signature := l.Signature if provider != nil { - isValid, err := eip6492.ValidateEIP6492Offchain(ctx, provider, l.Address, payload.Digest().Hash, signature, nil) + isValid, err := eip6492.ValidateEIP6492(ctx, provider, l.Address, payload.Digest().Hash, signature, nil) if err != nil { return nil, nil, fmt.Errorf("unable to validate ERC-1271 signature: %w", err) } diff --git a/eip6492_live_test.go b/eip6492_live_test.go new file mode 100644 index 00000000..0acf0eef --- /dev/null +++ b/eip6492_live_test.go @@ -0,0 +1,139 @@ +package sequence_test + +import ( + "context" + "strings" + "testing" + + "github.com/0xsequence/ethkit/go-ethereum/accounts" + "github.com/0xsequence/ethkit/go-ethereum/common" + "github.com/0xsequence/go-sequence" + "github.com/0xsequence/go-sequence/lib/eip6492" + "github.com/stretchr/testify/require" +) + +// These tests require a live testchain (make start-testchain or make start-testchain-fork). +// They create a Sequence wallet, sign a message, wrap in EIP-6492, and validate via +// ValidateEIP6492Offchain or ValidateEIP6492Onchain. Onchain tests skip if the validator +// is not deployed at EIP_6492_ADDRESS on the chain. + +func TestEIP6492Live_ValidateSequenceWalletMessage_Offchain(t *testing.T) { + // V2 counterfactual wallet on testchain + wallet, err := testChain.V2DummySequenceWallet(10, true) + require.NoError(t, err) + + message := []byte("hello world!") + _, eip191Message := accounts.TextAndHash(message) + + sig, err := wallet.SignMessage([]byte(eip191Message)) + require.NoError(t, err) + + wrapped, err := sequence.EIP6492Signature(sig, wallet.GetWalletConfig()) + require.NoError(t, err) + + digest := common.BytesToHash(accounts.TextHash(message)) + ctx := context.Background() + + valid, err := eip6492.ValidateEIP6492Offchain(ctx, testChain.Provider, wallet.Address(), digest, wrapped, nil) + require.NoError(t, err) + require.True(t, valid, "EIP-6492 offchain validation should succeed for Sequence wallet message signature") +} + +func TestEIP6492Live_ValidateSequenceWalletMessage_ViaIsValidMessageSignature(t *testing.T) { + // Same flow as above but via the public API to ensure full path works + wallet, err := testChain.V2DummySequenceWallet(11, true) + require.NoError(t, err) + + message := []byte("eip6492 live test") + _, eip191Message := accounts.TextAndHash(message) + + sig, err := wallet.SignMessage([]byte(eip191Message)) + require.NoError(t, err) + + wrapped, err := sequence.EIP6492Signature(sig, wallet.GetWalletConfig()) + require.NoError(t, err) + + isValid, err := sequence.IsValidMessageSignature( + wallet.Address(), + message, + wrapped, + testChain.ChainID(), + testChain.Provider, + nil, + ) + require.NoError(t, err) + require.True(t, isValid, "IsValidMessageSignature should succeed for EIP-6492 wrapped Sequence wallet signature") +} + +func TestEIP6492Live_ValidateDeployedSequenceWallet_Offchain(t *testing.T) { + // Deploy the wallet first, then validate (tests both counterfactual deploy path and already-deployed path) + wallet, err := testChain.V2DummySequenceWallet(12, false) + require.NoError(t, err) + + message := []byte("deployed wallet sign") + _, eip191Message := accounts.TextAndHash(message) + + sig, err := wallet.SignMessage([]byte(eip191Message)) + require.NoError(t, err) + + wrapped, err := sequence.EIP6492Signature(sig, wallet.GetWalletConfig()) + require.NoError(t, err) + + digest := common.BytesToHash(accounts.TextHash(message)) + ctx := context.Background() + + valid, err := eip6492.ValidateEIP6492Offchain(ctx, testChain.Provider, wallet.Address(), digest, wrapped, nil) + require.NoError(t, err) + require.True(t, valid, "EIP-6492 offchain validation should succeed for deployed Sequence wallet") +} + +func TestEIP6492Live_ValidateSequenceWalletMessage_Onchain(t *testing.T) { + // Same as offchain test but via pre-deployed validator at EIP_6492_ADDRESS. + // Skips if the validator is not deployed on this chain. + wallet, err := testChain.V2DummySequenceWallet(13, true) + require.NoError(t, err) + + message := []byte("hello world onchain!") + _, eip191Message := accounts.TextAndHash(message) + + sig, err := wallet.SignMessage([]byte(eip191Message)) + require.NoError(t, err) + + wrapped, err := sequence.EIP6492Signature(sig, wallet.GetWalletConfig()) + require.NoError(t, err) + + digest := common.BytesToHash(accounts.TextHash(message)) + ctx := context.Background() + + valid, err := eip6492.ValidateEIP6492Onchain(ctx, testChain.Provider, wallet.Address(), digest, wrapped, nil) + if err != nil && strings.Contains(err.Error(), "returned no data") { + t.Skipf("EIP-6492 validator not deployed on this chain: %v", err) + } + require.NoError(t, err) + require.True(t, valid, "EIP-6492 onchain validation should succeed for Sequence wallet message signature") +} + +func TestEIP6492Live_ValidateDeployedSequenceWallet_Onchain(t *testing.T) { + // Deployed wallet, validated via onchain contract. Skips if validator not deployed. + wallet, err := testChain.V2DummySequenceWallet(14, false) + require.NoError(t, err) + + message := []byte("deployed wallet onchain sign") + _, eip191Message := accounts.TextAndHash(message) + + sig, err := wallet.SignMessage([]byte(eip191Message)) + require.NoError(t, err) + + wrapped, err := sequence.EIP6492Signature(sig, wallet.GetWalletConfig()) + require.NoError(t, err) + + digest := common.BytesToHash(accounts.TextHash(message)) + ctx := context.Background() + + valid, err := eip6492.ValidateEIP6492Onchain(ctx, testChain.Provider, wallet.Address(), digest, wrapped, nil) + if err != nil && strings.Contains(err.Error(), "returned no data") { + t.Skipf("EIP-6492 validator not deployed on this chain: %v", err) + } + require.NoError(t, err) + require.True(t, valid, "EIP-6492 onchain validation should succeed for deployed Sequence wallet") +} diff --git a/lib/eip6492/eip6492.go b/lib/eip6492/eip6492.go index ab0937c3..f695613c 100644 --- a/lib/eip6492/eip6492.go +++ b/lib/eip6492/eip6492.go @@ -187,6 +187,7 @@ contract ValidateSigOffchain { const EIP_6492_OFFCHAIN_DEPLOY_CODE = "0x608060405234801561001057600080fd5b5060405161124a38038061124a83398101604081905261002f91610124565b600060405161003d906100dd565b604051809103906000f080158015610059573d6000803e3d6000fd5b5090506000816001600160a01b0316638f0684308686866040518463ffffffff1660e01b815260040161008e939291906101fb565b6020604051808303816000875af11580156100ad573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100d19190610244565b9050806000526001601ff35b610fdc8061026e83390190565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561011b578181015183820152602001610103565b50506000910152565b60008060006060848603121561013957600080fd5b83516001600160a01b038116811461015057600080fd5b6020850151604086015191945092506001600160401b038082111561017457600080fd5b818601915086601f83011261018857600080fd5b81518181111561019a5761019a6100ea565b604051601f8201601f19908116603f011681019083821181831017156101c2576101c26100ea565b816040528281528960208487010111156101db57600080fd5b6101ec836020830160208801610100565b80955050505050509250925092565b60018060a01b0384168152826020820152606060408201526000825180606084015261022e816080850160208701610100565b601f01601f191691909101608001949350505050565b60006020828403121561025657600080fd5b8151801515811461026657600080fd5b939250505056fe608060405234801561001057600080fd5b50610fbc806100206000396000f3fe608060405234801561001057600080fd5b50600436106100675760003560e01c806376be4cea1161005057806376be4cea146100a65780638f068430146100b957806398ef1ed8146100cc57600080fd5b80631c6453271461006c5780633d787b6314610093575b600080fd5b61007f61007a366004610ad4565b6100df565b604051901515815260200160405180910390f35b61007f6100a1366004610ad4565b61023d565b61007f6100b4366004610b3e565b61031e565b61007f6100c7366004610ad4565b6108e1565b61007f6100da366004610ad4565b61096e565b6040517f76be4cea00000000000000000000000000000000000000000000000000000000815260009030906376be4cea9061012890889088908890889088908190600401610bc3565b6020604051808303816000875af1925050508015610181575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820190925261017e91810190610c45565b60015b610232573d8080156101af576040519150601f19603f3d011682016040523d82523d6000602084013e6101b4565b606091505b508051600181900361022757816000815181106101d3576101d3610c69565b6020910101517fff00000000000000000000000000000000000000000000000000000000000000167f0100000000000000000000000000000000000000000000000000000000000000149250610235915050565b600092505050610235565b90505b949350505050565b6040517f76be4cea00000000000000000000000000000000000000000000000000000000815260009030906376be4cea906102879088908890889088906001908990600401610bc3565b6020604051808303816000875af19250505080156102e0575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01682019092526102dd91810190610c45565b60015b610232573d80801561030e576040519150601f19603f3d011682016040523d82523d6000602084013e610313565b606091505b506000915050610235565b600073ffffffffffffffffffffffffffffffffffffffff87163b6060827f64926492649264926492649264926492649264926492649264926492649264928888610369602082610c98565b610375928b9290610cd8565b61037e91610d02565b1490508015610484576000606089828a610399602082610c98565b926103a693929190610cd8565b8101906103b39190610e18565b955090925090508415806103c45750865b1561047d576000808373ffffffffffffffffffffffffffffffffffffffff16836040516103f19190610eb2565b6000604051808303816000865af19150503d806000811461042e576040519150601f19603f3d011682016040523d82523d6000602084013e610433565b606091505b50915091508161047a57806040517f9d0d6e2d0000000000000000000000000000000000000000000000000000000081526004016104719190610f18565b60405180910390fd5b50505b50506104be565b87878080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509294505050505b80806104ca5750600083115b156106bb576040517f1626ba7e00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8b1690631626ba7e90610523908c908690600401610f2b565b602060405180830381865afa92505050801561057a575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820190925261057791810190610f44565b60015b61060f573d8080156105a8576040519150601f19603f3d011682016040523d82523d6000602084013e6105ad565b606091505b50851580156105bc5750600084115b156105db576105d08b8b8b8b8b600161031e565b9450505050506108d7565b806040517f6f2a95990000000000000000000000000000000000000000000000000000000081526004016104719190610f18565b7fffffffff0000000000000000000000000000000000000000000000000000000081167f1626ba7e000000000000000000000000000000000000000000000000000000001480158161065f575086155b801561066b5750600085115b1561068b5761067f8c8c8c8c8c600161031e565b955050505050506108d7565b841580156106965750825b80156106a0575087155b156106af57806000526001601ffd5b94506108d79350505050565b6041871461074b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f5369676e617475726556616c696461746f72237265636f7665725369676e657260448201527f3a20696e76616c6964207369676e6174757265206c656e6774680000000000006064820152608401610471565b600061075a6020828a8c610cd8565b61076391610d02565b90506000610775604060208b8d610cd8565b61077e91610d02565b905060008a8a604081811061079557610795610c69565b919091013560f81c915050601b81148015906107b557508060ff16601c14155b15610842576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f5369676e617475726556616c696461746f723a20696e76616c6964207369676e60448201527f617475726520762076616c7565000000000000000000000000000000000000006064820152608401610471565b6040805160008152602081018083528e905260ff831691810191909152606081018490526080810183905273ffffffffffffffffffffffffffffffffffffffff8e169060019060a0016020604051602081039080840390855afa1580156108ad573d6000803e3d6000fd5b5050506020604051035173ffffffffffffffffffffffffffffffffffffffff161496505050505050505b9695505050505050565b6040517f76be4cea00000000000000000000000000000000000000000000000000000000815260009030906376be4cea9061092b9088908890889088906001908990600401610bc3565b6020604051808303816000875af115801561094a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102329190610c45565b6040517f76be4cea00000000000000000000000000000000000000000000000000000000815260009030906376be4cea906109b790889088908890889088908190600401610bc3565b6020604051808303816000875af1925050508015610a10575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201909252610a0d91810190610c45565b60015b610232573d808015610a3e576040519150601f19603f3d011682016040523d82523d6000602084013e610a43565b606091505b5080516001819003610a6257816000815181106101d3576101d3610c69565b8082fd5b73ffffffffffffffffffffffffffffffffffffffff81168114610a8857600080fd5b50565b60008083601f840112610a9d57600080fd5b50813567ffffffffffffffff811115610ab557600080fd5b602083019150836020828501011115610acd57600080fd5b9250929050565b60008060008060608587031215610aea57600080fd5b8435610af581610a66565b935060208501359250604085013567ffffffffffffffff811115610b1857600080fd5b610b2487828801610a8b565b95989497509550505050565b8015158114610a8857600080fd5b60008060008060008060a08789031215610b5757600080fd5b8635610b6281610a66565b955060208701359450604087013567ffffffffffffffff811115610b8557600080fd5b610b9189828a01610a8b565b9095509350506060870135610ba581610b30565b91506080870135610bb581610b30565b809150509295509295509295565b73ffffffffffffffffffffffffffffffffffffffff8716815285602082015260a060408201528360a0820152838560c0830137600060c085830181019190915292151560608201529015156080820152601f9092017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016909101019392505050565b600060208284031215610c5757600080fd5b8151610c6281610b30565b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b81810381811115610cd2577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b92915050565b60008085851115610ce857600080fd5b83861115610cf557600080fd5b5050820193919092039150565b80356020831015610cd2577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff602084900360031b1b1692915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082601f830112610d7e57600080fd5b813567ffffffffffffffff80821115610d9957610d99610d3e565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f01168101908282118183101715610ddf57610ddf610d3e565b81604052838152866020858801011115610df857600080fd5b836020870160208301376000602085830101528094505050505092915050565b600080600060608486031215610e2d57600080fd5b8335610e3881610a66565b9250602084013567ffffffffffffffff80821115610e5557600080fd5b610e6187838801610d6d565b93506040860135915080821115610e7757600080fd5b50610e8486828701610d6d565b9150509250925092565b60005b83811015610ea9578181015183820152602001610e91565b50506000910152565b60008251610ec4818460208701610e8e565b9190910192915050565b60008151808452610ee6816020860160208601610e8e565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000610c626020830184610ece565b8281526040602082015260006102356040830184610ece565b600060208284031215610f5657600080fd5b81517fffffffff0000000000000000000000000000000000000000000000000000000081168114610c6257600080fdfea26469706673582212201a72aed4b15ffb05b6502997a9bb655992e06590bd26b336dfbb153d7ff6f34b64736f6c63430008120033" const EIP_6492_SUFFIX = "0x6492649264926492649264926492649264926492649264926492649264926492" +const EIP_6492_ADDRESS = "0x0000000044dE9306b161ddE8D58682054e70c766" var EIP6492MagicBytes = hexutil.MustDecode(EIP_6492_SUFFIX) @@ -213,6 +214,28 @@ func DecodeEIP6492Signature(signature []byte) (common.Address, []byte, []byte, e return create2Factory, factoryCalldata, sigToValidate, nil } +func ValidateEIP6492( + ctx context.Context, + provider *ethrpc.Provider, + signer common.Address, + hash common.Hash, + signature []byte, + block *big.Int, +) (bool, error) { + validatorAddr := common.HexToAddress(EIP_6492_ADDRESS) + code, err := provider.CodeAt(ctx, validatorAddr, block) + if err == nil && len(code) > 0 { + // Validate using the deployed EIP-6492 validator + valid, onchainErr := ValidateEIP6492Onchain(ctx, provider, signer, hash, signature, block) + if onchainErr == nil && valid { + return true, nil + } + } + + // Fall back to offchain validation + return ValidateEIP6492Offchain(ctx, provider, signer, hash, signature, block) +} + func ValidateEIP6492Offchain( ctx context.Context, provider *ethrpc.Provider, @@ -252,3 +275,45 @@ func ValidateEIP6492Offchain( expectedResult := hexutil.MustDecode("0x01") return bytes.Equal(result, expectedResult), nil } + +// ValidateEIP6492Onchain validates an EIP-6492 signature by calling the pre-deployed +// UniversalSigValidator at EIP_6492_ADDRESS and invoking isValidSigWithSideEffects. +// It takes the same arguments as ValidateEIP6492Offchain but uses the onchain contract +// instead of deployment via constructor. +func ValidateEIP6492Onchain( + ctx context.Context, + provider *ethrpc.Provider, + signer common.Address, + hash common.Hash, + signature []byte, + block *big.Int, +) (bool, error) { + calldata, err := ethcoder.AbiEncodeMethodCalldata( + "isValidSigWithSideEffects(address,bytes32,bytes)", + []interface{}{signer, hash, signature}, + ) + if err != nil { + return false, err + } + + contractAddr := common.HexToAddress(EIP_6492_ADDRESS) + msg := ethereum.CallMsg{ + To: &contractAddr, + Data: calldata, + } + + result, err := provider.CallContract(ctx, msg, block) + if err != nil { + return false, err + } + + if len(result) == 0 { + return false, fmt.Errorf("contract call returned no data: validator may not be deployed at %s or call reverted", EIP_6492_ADDRESS) + } + + var valid bool + if err := ethcoder.AbiDecoder([]string{"bool"}, result, []interface{}{&valid}); err != nil { + return false, err + } + return valid, nil +} diff --git a/lib/eip6492/eip6492_test.go b/lib/eip6492/eip6492_test.go new file mode 100644 index 00000000..1457b9f3 --- /dev/null +++ b/lib/eip6492/eip6492_test.go @@ -0,0 +1,134 @@ +package eip6492 + +import ( + "context" + "testing" + + "github.com/0xsequence/ethkit/ethcoder" + "github.com/0xsequence/ethkit/ethrpc" + "github.com/0xsequence/ethkit/go-ethereum/common" + "github.com/0xsequence/ethkit/go-ethereum/common/hexutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestIsEIP6492Signature(t *testing.T) { + t.Run("nil", func(t *testing.T) { + assert.False(t, IsEIP6492Signature(nil)) + }) + t.Run("empty", func(t *testing.T) { + assert.False(t, IsEIP6492Signature([]byte{})) + }) + t.Run("shorter than magic", func(t *testing.T) { + assert.False(t, IsEIP6492Signature([]byte{0x64, 0x92})) + }) + t.Run("plain signature no suffix", func(t *testing.T) { + sig := make([]byte, 65) + sig[64] = 27 + assert.False(t, IsEIP6492Signature(sig)) + }) + t.Run("ends with magic bytes", func(t *testing.T) { + payload := []byte("some payload") + sig := append(append([]byte{}, payload...), EIP6492MagicBytes...) + assert.True(t, IsEIP6492Signature(sig)) + }) + t.Run("exactly magic bytes", func(t *testing.T) { + assert.True(t, IsEIP6492Signature(EIP6492MagicBytes)) + }) + t.Run("suffix but wrong content", func(t *testing.T) { + // 31 bytes + one wrong byte at end (magic is 32 bytes) + bad := make([]byte, 32) + copy(bad, EIP6492MagicBytes[:31]) + bad[31] = 0xff + assert.False(t, IsEIP6492Signature(bad)) + }) +} + +func TestDecodeEIP6492Signature(t *testing.T) { + t.Run("not eip6492", func(t *testing.T) { + _, _, _, err := DecodeEIP6492Signature([]byte("not a 6492 sig")) + require.Error(t, err) + assert.Contains(t, err.Error(), "not an eip6492 signature") + }) + t.Run("only magic bytes", func(t *testing.T) { + _, _, _, err := DecodeEIP6492Signature(EIP6492MagicBytes) + require.Error(t, err) + // ABI decode will fail on empty/short payload + assert.Error(t, err) + }) + t.Run("valid payload roundtrip", func(t *testing.T) { + factory := common.HexToAddress("0x00000000000000000000000000000000deadbeef") + factoryCalldata := []byte{0xde, 0xad, 0xbe, 0xef} + innerSig := make([]byte, 65) + innerSig[64] = 28 + + encoded, err := ethcoder.AbiCoder( + []string{"address", "bytes", "bytes"}, + []interface{}{factory, factoryCalldata, innerSig}, + ) + require.NoError(t, err) + signature := append(encoded, EIP6492MagicBytes...) + + decodedFactory, decodedCalldata, decodedSig, err := DecodeEIP6492Signature(signature) + require.NoError(t, err) + assert.Equal(t, factory, decodedFactory) + assert.Equal(t, factoryCalldata, decodedCalldata) + assert.Equal(t, innerSig, decodedSig) + }) + t.Run("decode then is eip6492", func(t *testing.T) { + factory := common.HexToAddress("0x1111111111111111111111111111111111111111") + calldata := []byte("deploy()") + innerSig := bytesFromHex("0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef00") + if len(innerSig) != 65 { + innerSig = make([]byte, 65) + innerSig[64] = 27 + } + + encoded, err := ethcoder.AbiCoder( + []string{"address", "bytes", "bytes"}, + []interface{}{factory, calldata, innerSig}, + ) + require.NoError(t, err) + signature := append(encoded, EIP6492MagicBytes...) + + assert.True(t, IsEIP6492Signature(signature)) + gotFactory, gotCalldata, gotSig, err := DecodeEIP6492Signature(signature) + require.NoError(t, err) + assert.Equal(t, factory, gotFactory) + assert.Equal(t, calldata, gotCalldata) + assert.Equal(t, innerSig, gotSig) + }) +} + +func bytesFromHex(s string) []byte { + b, _ := hexutil.Decode(s) + return b +} + +func TestValidateEIP6492Offchain_RequiresRPC(t *testing.T) { + if testing.Short() { + t.Skip("skipping ValidateEIP6492Offchain in short mode") + } + // Connect to local testchain; skip if not running + provider, err := ethrpc.NewProvider("http://localhost:8545") + if err != nil { + t.Skipf("provider not available: %v", err) + } + ctx := context.Background() + _, err = provider.ChainID(ctx) + if err != nil { + t.Skipf("testchain not running: %v", err) + } + + // Use a zero address and invalid payload: we only check that the offchain path + // runs and returns false (invalid sig). The chain may return (false, nil) or + // (false, err) when the validator reverts. + signer := common.HexToAddress("0x0000000000000000000000000000000000000001") + hash := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000") + signature := append([]byte{0x00}, EIP6492MagicBytes...) + + valid, err := ValidateEIP6492Offchain(ctx, provider, signer, hash, signature, nil) + assert.False(t, valid, "invalid payload must not validate") + // err may be nil (contract returned false) or non-nil (execution reverted etc.) + _ = err +} diff --git a/sequence_test.go b/sequence_test.go index 76bfed46..51c5f400 100644 --- a/sequence_test.go +++ b/sequence_test.go @@ -28,5 +28,7 @@ func init() { } func TestChainID(t *testing.T) { - assert.Equal(t, testChain.ChainID().Uint64(), uint64(1337)) + // Default testchain (hardhat/anvil) is 1337; polygon-zkevm fork is 1101 + assert.NotZero(t, testChain.ChainID().Uint64(), "testchain should report a non-zero chain ID") + assert.Contains(t, []uint64{1337, 1101}, testChain.ChainID().Uint64(), "expected default testchain (1337) or polygon-zkevm fork (1101)") } diff --git a/signature.go b/signature.go index ff74ef8c..2ddf13b8 100644 --- a/signature.go +++ b/signature.go @@ -216,7 +216,7 @@ func IsValidTypedDataSignature(address common.Address, encodedTypedData []byte, } func IsValidSignature(log logger.Logger, walletAddress common.Address, digest common.Hash, seqSig []byte, walletContexts WalletContexts, chainID *big.Int, provider *ethrpc.Provider) (bool, error) { - eip6492isValid, _ := eip6492.ValidateEIP6492Offchain(context.Background(), provider, walletAddress, digest, seqSig, nil) + eip6492isValid, _ := eip6492.ValidateEIP6492(context.Background(), provider, walletAddress, digest, seqSig, nil) if eip6492isValid { return true, nil } diff --git a/testutil/testchain/package.json b/testutil/testchain/package.json index 6f9ee988..4100d45c 100644 --- a/testutil/testchain/package.json +++ b/testutil/testchain/package.json @@ -11,6 +11,8 @@ "start:geth:verbose": "docker run -p 8545:8545 --rm ethereum/client-go:v1.16.4 --dev --dev.period 1 --dev.gaslimit ${npm_package_config_testchainGasLimit} --miner.gaslimit ${npm_package_config_testchainGasLimit} --miner.gasprice 1 --http --http.addr 0.0.0.0 --rpc.allow-unprotected-txs", "start:anvil": "anvil --mnemonic \"${npm_package_config_mnemonic}\" --block-time 1 --balance ${npm_package_config_etherBalance} --host 0.0.0.0 --chain-id ${npm_package_config_testchainChainID} --gas-limit ${npm_package_config_testchainGasLimit} --gas-price ${npm_package_config_testchainGasPrice}", "start:anvil:verbose": "anvil --mnemonic \"${npm_package_config_mnemonic}\" --block-time 1 --balance ${npm_package_config_etherBalance} --host 0.0.0.0 --chain-id ${npm_package_config_testchainChainID} --gas-limit ${npm_package_config_testchainGasLimit} --gas-price ${npm_package_config_testchainGasPrice} -vvv", + "start:anvil:fork": "sh -c 'anvil --fork-url \"${FORK_URL:-${1:-https://nodes.sequence.app/berachain}}\" --mnemonic \"${npm_package_config_mnemonic}\" --block-time 1 --host 0.0.0.0 --gas-limit ${npm_package_config_testchainGasLimit}' sh", + "start:anvil:fork:verbose": "sh -c 'anvil --fork-url \"${FORK_URL:-${1:-https://nodes.sequence.app/berachain}}\" --mnemonic \"${npm_package_config_mnemonic}\" --block-time 1 --host 0.0.0.0 --gas-limit ${npm_package_config_testchainGasLimit} -vvv' sh", "install:anvil": "curl -L https://foundry.paradigm.xyz | bash; foundryup", "wait:server": "wait-on -t 120000 tcp:127.0.0.1:8545" }, @@ -27,4 +29,4 @@ "etherBalance": "100000", "extra": "" } -} +} \ No newline at end of file