From c2aa2975af618992db355c1c1355ddddfe1399e8 Mon Sep 17 00:00:00 2001 From: danijelTxFusion Date: Tue, 13 Aug 2024 23:31:34 +0200 Subject: [PATCH] feat(types): improve the design of L2 transaction BREAKING CHANGE: `Transaction712` has been replaced by `Transaction`, an improved version that offers a more convenient API and greater extensibility. --- accounts/signer.go | 18 +- accounts/smart_account.go | 126 +++++----- accounts/smart_account_utils.go | 17 +- accounts/smart_account_utils_test.go | 79 ++----- accounts/types.go | 98 ++++---- accounts/wallet_l2.go | 42 ++-- test/account_abstraction_test.go | 8 +- test/smart_account_test.go | 8 +- test/smart_account_utils_test.go | 8 +- test/wallet_test.go | 26 +- types/eip712_tx_test.go | 235 ------------------ types/{eip712_tx.go => transaction.go} | 228 ++++++++++++++---- types/transaction_test.go | 315 +++++++++++++++++++++++++ 13 files changed, 692 insertions(+), 516 deletions(-) delete mode 100644 types/eip712_tx_test.go rename types/{eip712_tx.go => transaction.go} (52%) create mode 100644 types/transaction_test.go diff --git a/accounts/signer.go b/accounts/signer.go index 807103e..2751feb 100644 --- a/accounts/signer.go +++ b/accounts/signer.go @@ -26,7 +26,7 @@ type Signer interface { SignHash(msg []byte) ([]byte, error) // SignTypedData signs the given EIP-712 typed data using the signer's private key and returns the signature. // The domain parameter is the EIP-712 domain separator, and the data parameter is the EIP-712 typed data. - SignTypedData(d *eip712.Domain, data eip712.TypedData) ([]byte, error) + SignTypedData(typedData apitypes.TypedData) ([]byte, error) } // BaseSigner represents basis implementation of Signer interface. @@ -111,21 +111,7 @@ func (s *BaseSigner) PrivateKey() *ecdsa.PrivateKey { return s.pk } -func (s *BaseSigner) SignTypedData(domain *eip712.Domain, data eip712.TypedData) ([]byte, error) { - // compile TypedData structure - eip712Msg, err := data.EIP712Message() - if err != nil { - return nil, err - } - typedData := apitypes.TypedData{ - Types: apitypes.Types{ - data.EIP712Type(): data.EIP712Types(), - domain.EIP712Type(): domain.EIP712Types(), - }, - PrimaryType: data.EIP712Type(), - Domain: domain.EIP712Domain(), - Message: eip712Msg, - } +func (s *BaseSigner) SignTypedData(typedData apitypes.TypedData) ([]byte, error) { hash, err := s.HashTypedData(typedData) if err != nil { return nil, fmt.Errorf("failed to get hash of typed data: %w", err) diff --git a/accounts/smart_account.go b/accounts/smart_account.go index 13eb25c..a288aa8 100644 --- a/accounts/smart_account.go +++ b/accounts/smart_account.go @@ -12,7 +12,6 @@ import ( "github.com/zksync-sdk/zksync2-go/contracts/ethtoken" "github.com/zksync-sdk/zksync2-go/contracts/l2bridge" "github.com/zksync-sdk/zksync2-go/contracts/nonceholder" - "github.com/zksync-sdk/zksync2-go/eip712" "github.com/zksync-sdk/zksync2-go/types" "github.com/zksync-sdk/zksync2-go/utils" "math/big" @@ -128,7 +127,7 @@ func (a *SmartAccount) DeploymentNonce(opts *CallOpts) (*big.Int, error) { // PopulateTransaction populates the transaction tx using the provided TransactionBuilder function. // If tx.From is not set, it sets the value from the Address method which can // be utilized in the TransactionBuilder function. -func (a *SmartAccount) PopulateTransaction(ctx context.Context, tx *types.Transaction712) error { +func (a *SmartAccount) PopulateTransaction(ctx context.Context, tx *types.Transaction) error { if tx.From == nil { from := a.Address() tx.From = &from @@ -139,7 +138,7 @@ func (a *SmartAccount) PopulateTransaction(ctx context.Context, tx *types.Transa // SignTransaction returns a signed transaction that is ready to be broadcast to // the network. The PopulateTransaction method is called first to ensure that all // necessary properties for the transaction to be valid have been populated. -func (a *SmartAccount) SignTransaction(ctx context.Context, tx *types.Transaction712) ([]byte, error) { +func (a *SmartAccount) SignTransaction(ctx context.Context, tx *types.Transaction) ([]byte, error) { err := a.cacheData(ensureContext(ctx)) if err != nil { return nil, err @@ -150,31 +149,20 @@ func (a *SmartAccount) SignTransaction(ctx context.Context, tx *types.Transactio return nil, err } - domain := eip712.ZkSyncEraEIP712Domain(a.chainId.Int64()) - - eip712Msg, err := tx.EIP712Message() + typedData, err := tx.TypedData() if err != nil { return nil, err } - - signature, err := a.SignTypedData(ctx, apitypes.TypedData{ - Types: apitypes.Types{ - tx.EIP712Type(): tx.EIP712Types(), - domain.EIP712Type(): domain.EIP712Types(), - }, - PrimaryType: tx.EIP712Type(), - Domain: domain.EIP712Domain(), - Message: eip712Msg, - }) + signature, err := a.SignTypedData(ctx, *typedData) if err != nil { return nil, err } - return tx.RLPValues(signature) + return tx.Encode(signature) } // SendTransaction injects a transaction into the pending pool for execution. // The SignTransaction is called first to ensure transaction is properly signed. -func (a *SmartAccount) SendTransaction(ctx context.Context, tx *types.Transaction712) (common.Hash, error) { +func (a *SmartAccount) SendTransaction(ctx context.Context, tx *types.Transaction) (common.Hash, error) { rawTx, err := a.SignTransaction(ensureContext(ctx), tx) if err != nil { return common.Hash{}, err @@ -237,20 +225,18 @@ func (a *SmartAccount) Withdraw(auth *TransactOpts, tx WithdrawalTransaction) (c return common.Hash{}, packErr } - return a.SendTransaction(opts.Context, &types.Transaction712{ - Nonce: opts.Nonce, - GasTipCap: opts.GasTipCap, - GasFeeCap: opts.GasFeeCap, - Gas: new(big.Int).SetUint64(opts.GasLimit), - To: &utils.L2BaseTokenAddress, - Value: opts.Value, - Data: data, - From: &from, - ChainID: a.chainId, - Meta: &types.Eip712Meta{ - GasPerPubdata: utils.NewBig(utils.DefaultGasPerPubdataLimit.Int64()), - PaymasterParams: tx.PaymasterParams, - }, + return a.SendTransaction(opts.Context, &types.Transaction{ + Nonce: opts.Nonce, + GasTipCap: opts.GasTipCap, + GasFeeCap: opts.GasFeeCap, + Gas: new(big.Int).SetUint64(opts.GasLimit), + To: &utils.L2BaseTokenAddress, + Value: opts.Value, + Data: data, + From: &from, + ChainID: a.chainId, + GasPerPubdata: utils.DefaultGasPerPubdataLimit, + PaymasterParams: tx.PaymasterParams, }) } @@ -268,20 +254,18 @@ func (a *SmartAccount) Withdraw(auth *TransactOpts, tx WithdrawalTransaction) (c return common.Hash{}, abiPack } - return a.SendTransaction(opts.Context, &types.Transaction712{ - Nonce: opts.Nonce, - GasTipCap: opts.GasTipCap, - GasFeeCap: opts.GasFeeCap, - Gas: new(big.Int).SetUint64(opts.GasLimit), - To: tx.BridgeAddress, - Value: opts.Value, - Data: data, - ChainID: a.chainId, - From: &from, - Meta: &types.Eip712Meta{ - GasPerPubdata: utils.NewBig(utils.DefaultGasPerPubdataLimit.Int64()), - PaymasterParams: tx.PaymasterParams, - }, + return a.SendTransaction(opts.Context, &types.Transaction{ + Nonce: opts.Nonce, + GasTipCap: opts.GasTipCap, + GasFeeCap: opts.GasFeeCap, + Gas: new(big.Int).SetUint64(opts.GasLimit), + To: tx.BridgeAddress, + Value: opts.Value, + Data: data, + ChainID: a.chainId, + From: &from, + GasPerPubdata: utils.DefaultGasPerPubdataLimit, + PaymasterParams: tx.PaymasterParams, }) } @@ -304,19 +288,17 @@ func (a *SmartAccount) Transfer(auth *TransactOpts, tx TransferTransaction) (com } if tx.Token == utils.L2BaseTokenAddress { - return a.SendTransaction(opts.Context, &types.Transaction712{ - Nonce: opts.Nonce, - GasTipCap: opts.GasTipCap, - GasFeeCap: opts.GasFeeCap, - Gas: new(big.Int).SetUint64(opts.GasLimit), - To: &tx.To, - Value: tx.Amount, - ChainID: a.chainId, - From: &from, - Meta: &types.Eip712Meta{ - GasPerPubdata: utils.NewBig(utils.DefaultGasPerPubdataLimit.Int64()), - PaymasterParams: tx.PaymasterParams, - }, + return a.SendTransaction(opts.Context, &types.Transaction{ + Nonce: opts.Nonce, + GasTipCap: opts.GasTipCap, + GasFeeCap: opts.GasFeeCap, + Gas: new(big.Int).SetUint64(opts.GasLimit), + To: &tx.To, + Value: tx.Amount, + ChainID: a.chainId, + From: &from, + GasPerPubdata: utils.DefaultGasPerPubdataLimit, + PaymasterParams: tx.PaymasterParams, }) } @@ -330,20 +312,18 @@ func (a *SmartAccount) Transfer(auth *TransactOpts, tx TransferTransaction) (com return common.Hash{}, err } - return a.SendTransaction(opts.Context, &types.Transaction712{ - Nonce: opts.Nonce, - GasTipCap: opts.GasTipCap, - GasFeeCap: opts.GasFeeCap, - Gas: new(big.Int).SetUint64(opts.GasLimit), - To: &tx.Token, - Value: big.NewInt(0), - Data: data, - ChainID: a.chainId, - From: &from, - Meta: &types.Eip712Meta{ - GasPerPubdata: utils.NewBig(utils.DefaultGasPerPubdataLimit.Int64()), - PaymasterParams: tx.PaymasterParams, - }, + return a.SendTransaction(opts.Context, &types.Transaction{ + Nonce: opts.Nonce, + GasTipCap: opts.GasTipCap, + GasFeeCap: opts.GasFeeCap, + Gas: new(big.Int).SetUint64(opts.GasLimit), + To: &tx.Token, + Value: big.NewInt(0), + Data: data, + ChainID: a.chainId, + From: &from, + GasPerPubdata: utils.DefaultGasPerPubdataLimit, + PaymasterParams: tx.PaymasterParams, }) } diff --git a/accounts/smart_account_utils.go b/accounts/smart_account_utils.go index 85cf162..c763164 100644 --- a/accounts/smart_account_utils.go +++ b/accounts/smart_account_utils.go @@ -80,7 +80,7 @@ var SignPayloadWithMultipleECDSA PayloadSigner = func(ctx context.Context, paylo // - Populates tx.Meta.GasPerPubdata with utils.DefaultGasPerPubdataLimit. // // Expects the secret to be ECDSA private in hex format. -var PopulateTransactionECDSA TransactionBuilder = func(ctx context.Context, tx *types.Transaction712, secret interface{}, client *clients.Client) error { +var PopulateTransactionECDSA TransactionBuilder = func(ctx context.Context, tx *types.Transaction, secret interface{}, client *clients.Client) error { var err error if client == nil { return errors.New("client is required but is not provided") @@ -101,10 +101,8 @@ var PopulateTransactionECDSA TransactionBuilder = func(ctx context.Context, tx * if tx.GasTipCap == nil { tx.GasTipCap = common.Big0 } - if tx.Meta == nil { - tx.Meta = &types.Eip712Meta{GasPerPubdata: utils.NewBig(utils.DefaultGasPerPubdataLimit.Int64())} - } else if tx.Meta.GasPerPubdata == nil { - tx.Meta.GasPerPubdata = utils.NewBig(utils.DefaultGasPerPubdataLimit.Int64()) + if tx.GasPerPubdata == nil { + tx.GasPerPubdata = utils.DefaultGasPerPubdataLimit } if (tx.Gas == nil || tx.Gas.Uint64() == 0) || (tx.GasFeeCap == nil) { from := *tx.From @@ -132,7 +130,12 @@ var PopulateTransactionECDSA TransactionBuilder = func(ctx context.Context, tx * GasTipCap: tx.GasTipCap, Value: tx.Value, Data: tx.Data, - Meta: tx.Meta, + Meta: &types.Eip712Meta{ + GasPerPubdata: (*hexutil.Big)(tx.GasPerPubdata), + CustomSignature: tx.CustomSignature, + FactoryDeps: tx.FactoryDeps, + PaymasterParams: tx.PaymasterParams, + }, }) if err != nil { return fmt.Errorf("failed to EstimateFee: %w", err) @@ -154,7 +157,7 @@ var PopulateTransactionECDSA TransactionBuilder = func(ctx context.Context, tx * // PopulateTransactionMultipleECDSA populates missing properties meant for signing using multiple ECDSA private keys. // It uses PopulateTransactionECDSA, where the address of the first ECDSA key is set as the secret argument. // Expects the secret to be a slice of ECDSA private in hex format. -var PopulateTransactionMultipleECDSA TransactionBuilder = func(ctx context.Context, tx *types.Transaction712, secret interface{}, client *clients.Client) error { +var PopulateTransactionMultipleECDSA TransactionBuilder = func(ctx context.Context, tx *types.Transaction, secret interface{}, client *clients.Client) error { privateKeys, ok := (secret).([]string) if !ok { return errors.New("secret should be a slice of ECDSA private keys") diff --git a/accounts/smart_account_utils_test.go b/accounts/smart_account_utils_test.go index a62fe8d..564ca25 100644 --- a/accounts/smart_account_utils_test.go +++ b/accounts/smart_account_utils_test.go @@ -8,7 +8,6 @@ import ( "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/signer/core/apitypes" "github.com/stretchr/testify/assert" - "github.com/zksync-sdk/zksync2-go/eip712" "github.com/zksync-sdk/zksync2-go/types" "github.com/zksync-sdk/zksync2-go/utils" "math/big" @@ -24,35 +23,20 @@ var Address2 = common.HexToAddress("0xa61464658AfeAf65CccaaFD3a512b69A83B77618") func TestSignPayloadWithECDSASignTransaction(t *testing.T) { signature := "0x475e207d1e5da85721e37118cea54b2a3ac8e5dcd79cd7935c59bdd5cbc71e9824d4ab9dbaa5f8542e51588f4187c406fc4311c2ce9a9aa2a269f14298e5777d1b" - tx := types.Transaction712{ - Nonce: big.NewInt(0), - GasTipCap: big.NewInt(0), - GasFeeCap: big.NewInt(1_000_000_000), - Gas: big.NewInt(1_000_000_000), - To: &Address2, - Value: big.NewInt(7_000_000_000), - Data: hexutil.Bytes{}, - ChainID: big.NewInt(270), - From: &Address1, - Meta: &types.Eip712Meta{ - GasPerPubdata: utils.NewBig(utils.DefaultGasPerPubdataLimit.Int64()), - }, + tx := types.Transaction{ + Nonce: big.NewInt(0), + GasTipCap: big.NewInt(0), + GasFeeCap: big.NewInt(1_000_000_000), + Gas: big.NewInt(1_000_000_000), + To: &Address2, + Value: big.NewInt(7_000_000_000), + Data: hexutil.Bytes{}, + ChainID: big.NewInt(270), + From: &Address1, + GasPerPubdata: utils.DefaultGasPerPubdataLimit, } - domain := eip712.ZkSyncEraEIP712Domain(270) - - eip712Msg, err := tx.EIP712Message() - assert.NoError(t, err, "EIP712Message should not return an error") - - hash, _, err := apitypes.TypedDataAndHash(apitypes.TypedData{ - Types: apitypes.Types{ - tx.EIP712Type(): tx.EIP712Types(), - domain.EIP712Type(): domain.EIP712Types(), - }, - PrimaryType: tx.EIP712Type(), - Domain: domain.EIP712Domain(), - Message: eip712Msg, - }) + hash, err := tx.Hash() assert.NoError(t, err, "TypedDataAndHash should not return an error") sig, err := SignPayloadWithECDSA(context.Background(), hash, PrivateKey1, nil) @@ -104,35 +88,20 @@ func TestSignPayloadWithECDSASignTypedData(t *testing.T) { func TestSignPayloadWithMultipleECDSASignTransaction(t *testing.T) { signature := "0x475e207d1e5da85721e37118cea54b2a3ac8e5dcd79cd7935c59bdd5cbc71e9824d4ab9dbaa5f8542e51588f4187c406fc4311c2ce9a9aa2a269f14298e5777d1b4ff4f280885d2dd0b2234d82cacec8ba94bd6659b64b1d516668b4ca79faf58a58c469fd95590e2541ca01866e312e56c7e38a74b4a8b72fdb07a69a3b34c19f1c" - tx := types.Transaction712{ - Nonce: big.NewInt(0), - GasTipCap: big.NewInt(0), - GasFeeCap: big.NewInt(1_000_000_000), - Gas: big.NewInt(1_000_000_000), - To: &Address2, - Value: big.NewInt(7_000_000_000), - Data: hexutil.Bytes{}, - ChainID: big.NewInt(270), - From: &Address1, - Meta: &types.Eip712Meta{ - GasPerPubdata: utils.NewBig(utils.DefaultGasPerPubdataLimit.Int64()), - }, + tx := types.Transaction{ + Nonce: big.NewInt(0), + GasTipCap: big.NewInt(0), + GasFeeCap: big.NewInt(1_000_000_000), + Gas: big.NewInt(1_000_000_000), + To: &Address2, + Value: big.NewInt(7_000_000_000), + Data: hexutil.Bytes{}, + ChainID: big.NewInt(270), + From: &Address1, + GasPerPubdata: utils.DefaultGasPerPubdataLimit, } - domain := eip712.ZkSyncEraEIP712Domain(270) - - eip712Msg, err := tx.EIP712Message() - assert.NoError(t, err, "EIP712Message should not return an error") - - hash, _, err := apitypes.TypedDataAndHash(apitypes.TypedData{ - Types: apitypes.Types{ - tx.EIP712Type(): tx.EIP712Types(), - domain.EIP712Type(): domain.EIP712Types(), - }, - PrimaryType: tx.EIP712Type(), - Domain: domain.EIP712Domain(), - Message: eip712Msg, - }) + hash, err := tx.Hash() assert.NoError(t, err, "TypedDataAndHash should not return an error") sig, err := SignPayloadWithMultipleECDSA(context.Background(), hash, []string{PrivateKey1, PrivateKey2}, nil) diff --git a/accounts/types.go b/accounts/types.go index bde06aa..4676a90 100644 --- a/accounts/types.go +++ b/accounts/types.go @@ -347,8 +347,8 @@ func (t *TransactOpts) ToTransactOpts(from common.Address, signer bind.SignerFn) } } -// Transaction is similar to types.Transaction712 but does not include the From field. This design is intended for use -// with AdapterL1, AdapterL2, or Deployer, which already have an associated account. The From field is bound to +// Transaction is similar to types.Transaction but does not include the From field. This design is intended for use +// with entities which already have an associated account. The From field is bound to // their associated account, and thus, it is not included in this type. type Transaction struct { To *common.Address // The address of the recipient. @@ -359,22 +359,39 @@ type Transaction struct { GasFeeCap *big.Int // EIP-1559 fee cap per gas. Gas uint64 // Gas limit to set for the transaction execution. - ChainID *big.Int // Chain ID of the network. - Meta *types.Eip712Meta // EIP-712 metadata. -} - -func (t *Transaction) ToTransaction712(from common.Address) *types.Transaction712 { - return &types.Transaction712{ - Nonce: t.Nonce, - GasTipCap: t.GasTipCap, - GasFeeCap: t.GasFeeCap, - Gas: new(big.Int).SetUint64(t.Gas), - To: t.To, - Value: t.Value, - Data: t.Data, - ChainID: t.ChainID, - From: &from, - Meta: t.Meta, + ChainID *big.Int // Chain ID of the network. + + // GasPerPubdata denotes the maximum amount of gas the user is willing + // to pay for a single byte of pubdata. + GasPerPubdata *big.Int `json:"gasPerPubdata,omitempty"` + // CustomSignature is used for the cases in which the signer's account + // is not an EOA. + CustomSignature hexutil.Bytes `json:"customSignature,omitempty"` + // FactoryDeps is a non-empty array of bytes. For deployment transactions, + // it should contain the bytecode of the contract being deployed. + // If the contract is a factory contract, i.e. it can deploy other contracts, + // the array should also contain the bytecodes of the contracts which it can deploy. + FactoryDeps []hexutil.Bytes `json:"factoryDeps"` + // PaymasterParams contains parameters for configuring the custom paymaster + // for the transaction. + PaymasterParams *types.PaymasterParams `json:"paymasterParams,omitempty"` +} + +func (t *Transaction) ToTransaction712(from common.Address) *types.Transaction { + return &types.Transaction{ + Nonce: t.Nonce, + GasTipCap: t.GasTipCap, + GasFeeCap: t.GasFeeCap, + Gas: new(big.Int).SetUint64(t.Gas), + To: t.To, + Value: t.Value, + Data: t.Data, + ChainID: t.ChainID, + From: &from, + GasPerPubdata: t.GasPerPubdata, + CustomSignature: t.CustomSignature, + FactoryDeps: t.FactoryDeps, + PaymasterParams: t.PaymasterParams, } } @@ -387,7 +404,12 @@ func (t *Transaction) ToCallMsg(from common.Address) types.CallMsg { GasTipCap: t.GasTipCap, Value: t.Value, Data: t.Data, - Meta: t.Meta, + Meta: &types.Eip712Meta{ + GasPerPubdata: (*hexutil.Big)(t.GasPerPubdata), + CustomSignature: t.CustomSignature, + FactoryDeps: t.FactoryDeps, + PaymasterParams: t.PaymasterParams, + }, } } @@ -653,16 +675,14 @@ func (t *CreateTransaction) ToTransaction(deploymentType DeploymentType, opts *T } return &Transaction{ - To: &utils.ContractDeployerAddress, - Data: data, - Nonce: auth.Nonce, - GasFeeCap: auth.GasFeeCap, - GasTipCap: auth.GasTipCap, - Gas: auth.GasLimit, - Meta: &types.Eip712Meta{ - GasPerPubdata: utils.NewBig(utils.DefaultGasPerPubdataLimit.Int64()), - FactoryDeps: factoryDeps, - }, + To: &utils.ContractDeployerAddress, + Data: data, + Nonce: auth.Nonce, + GasFeeCap: auth.GasFeeCap, + GasTipCap: auth.GasTipCap, + Gas: auth.GasLimit, + GasPerPubdata: utils.DefaultGasPerPubdataLimit, + FactoryDeps: factoryDeps, }, nil } @@ -710,16 +730,14 @@ func (t *Create2Transaction) ToTransaction(deploymentType DeploymentType, opts * } return &Transaction{ - To: &utils.ContractDeployerAddress, - Data: data, - Nonce: auth.Nonce, - GasFeeCap: auth.GasFeeCap, - GasTipCap: auth.GasTipCap, - Gas: auth.GasLimit, - Meta: &types.Eip712Meta{ - GasPerPubdata: utils.NewBig(utils.DefaultGasPerPubdataLimit.Int64()), - FactoryDeps: factoryDeps, - }, + To: &utils.ContractDeployerAddress, + Data: data, + Nonce: auth.Nonce, + GasFeeCap: auth.GasFeeCap, + GasTipCap: auth.GasTipCap, + Gas: auth.GasLimit, + GasPerPubdata: utils.DefaultGasPerPubdataLimit, + FactoryDeps: factoryDeps, }, nil } @@ -747,4 +765,4 @@ type PayloadSigner func(ctx context.Context, payload []byte, secret interface{}, // TransactionBuilder populates missing fields in a tx with default values. // The client is used to fetch data from the network if it is required. -type TransactionBuilder func(ctx context.Context, tx *types.Transaction712, secret interface{}, client *clients.Client) error +type TransactionBuilder func(ctx context.Context, tx *types.Transaction, secret interface{}, client *clients.Client) error diff --git a/accounts/wallet_l2.go b/accounts/wallet_l2.go index 058d953..e6b4228 100644 --- a/accounts/wallet_l2.go +++ b/accounts/wallet_l2.go @@ -204,6 +204,9 @@ func (a *WalletL2) EstimateGasWithdraw(ctx context.Context, msg WithdrawalCallMs // Transfer moves the base token or any ERC20 token from the associated account to the target account. func (a *WalletL2) Transfer(auth *TransactOpts, tx TransferTransaction) (*ethTypes.Transaction, error) { + // returns types.Transaction + // check the type of the transaction + opts := ensureTransactOpts(auth) if tx.Token == utils.LegacyEthAddress || tx.Token == utils.EthAddressInContracts { @@ -253,7 +256,7 @@ func (a *WalletL2) CallContract(ctx context.Context, msg CallMsg, blockNumber *b // required fields are Transaction.To and either Transaction.Data or // Transaction.Value (or both, if the method is payable). Any other fields that // are not set will be prepared by this method. -func (a *WalletL2) PopulateTransaction(ctx context.Context, tx Transaction) (*types.Transaction712, error) { +func (a *WalletL2) PopulateTransaction(ctx context.Context, tx Transaction) (*types.Transaction, error) { if tx.ChainID == nil { tx.ChainID = (*a.signer).Domain().ChainId } @@ -274,10 +277,8 @@ func (a *WalletL2) PopulateTransaction(ctx context.Context, tx Transaction) (*ty if tx.GasTipCap == nil { tx.GasTipCap = big.NewInt(0) } - if tx.Meta == nil { - tx.Meta = &types.Eip712Meta{GasPerPubdata: utils.NewBig(utils.DefaultGasPerPubdataLimit.Int64())} - } else if tx.Meta.GasPerPubdata == nil { - tx.Meta.GasPerPubdata = utils.NewBig(utils.DefaultGasPerPubdataLimit.Int64()) + if tx.GasPerPubdata == nil { + tx.GasPerPubdata = utils.DefaultGasPerPubdataLimit } if tx.Gas == 0 || tx.GasFeeCap == nil { fee, err := a.client.EstimateFee(ensureContext(ctx), tx.ToCallMsg(a.Address())) @@ -302,31 +303,38 @@ func (a *WalletL2) PopulateTransaction(ctx context.Context, tx Transaction) (*ty // the network. The input transaction must be a valid transaction with all fields // having appropriate values. To obtain a valid transaction, you can use the // PopulateTransaction method. -func (a *WalletL2) SignTransaction(tx *types.Transaction712) ([]byte, error) { +func (a *WalletL2) SignTransaction(tx *types.Transaction) ([]byte, error) { var gas uint64 = 0 if tx.Gas != nil { gas = tx.Gas.Uint64() } preparedTx, err := a.PopulateTransaction(context.Background(), Transaction{ - To: tx.To, - Data: tx.Data, - Value: tx.Value, - Nonce: tx.Nonce, - GasTipCap: tx.GasTipCap, - GasFeeCap: tx.GasFeeCap, - Gas: gas, - ChainID: tx.ChainID, - Meta: tx.Meta, + To: tx.To, + Data: tx.Data, + Value: tx.Value, + Nonce: tx.Nonce, + GasTipCap: tx.GasTipCap, + GasFeeCap: tx.GasFeeCap, + Gas: gas, + ChainID: tx.ChainID, + GasPerPubdata: tx.GasPerPubdata, + CustomSignature: tx.CustomSignature, + FactoryDeps: tx.FactoryDeps, + PaymasterParams: tx.PaymasterParams, }) if err != nil { return nil, err } - signature, err := (*a.signer).SignTypedData((*a.signer).Domain(), preparedTx) + typedData, err := preparedTx.TypedData() + if err != nil { + return nil, err + } + signature, err := (*a.signer).SignTypedData(*typedData) if err != nil { return nil, err } - return preparedTx.RLPValues(signature) + return preparedTx.Encode(signature) } // SendTransaction injects a transaction into the pending pool for execution. Any diff --git a/test/account_abstraction_test.go b/test/account_abstraction_test.go index 64239d1..ce63e36 100644 --- a/test/account_abstraction_test.go +++ b/test/account_abstraction_test.go @@ -118,11 +118,9 @@ func TestIntegration_ApprovalPaymaster(t *testing.T) { assert.NoError(t, err, "GetPaymasterParams should not return an error") hash, err := wallet.SendTransaction(context.Background(), &accounts.Transaction{ - To: &tokenAddress, - Data: calldata, - Meta: &types.Eip712Meta{ - PaymasterParams: paymasterParams, - }, + To: &tokenAddress, + Data: calldata, + PaymasterParams: paymasterParams, }) assert.NoError(t, err, "SendTransaction should not return an error") diff --git a/test/smart_account_test.go b/test/smart_account_test.go index 76b4613..0d7dd5f 100644 --- a/test/smart_account_test.go +++ b/test/smart_account_test.go @@ -1085,7 +1085,7 @@ func TestIntegrationSmartAccount_PopulateTransaction(t *testing.T) { account := accounts.NewECDSASmartAccount(Address1, PrivateKey1, client) - tx := &types.Transaction712{ + tx := &types.Transaction{ To: &Address2, Value: big.NewInt(7_000_000_000), From: &Address1, @@ -1102,7 +1102,7 @@ func TestIntegrationSmartAccount_PopulateTransaction(t *testing.T) { assert.NotNil(t, tx.Gas, "Gas must not be nil") assert.NotNil(t, tx.GasFeeCap, "GasFeeCap must not be nil") assert.NotNil(t, tx.GasTipCap, "GasTipCap must not be nil") - assert.NotNil(t, tx.Meta, "Meta must not be nil") + assert.NotNil(t, tx.GasPerPubdata, "GasPerPubdata must not be nil") } func TestIntegrationSmartAccount_SignTransaction(t *testing.T) { @@ -1112,7 +1112,7 @@ func TestIntegrationSmartAccount_SignTransaction(t *testing.T) { account := accounts.NewECDSASmartAccount(Address1, PrivateKey1, client) - signedTx, err := account.SignTransaction(context.Background(), &types.Transaction712{ + signedTx, err := account.SignTransaction(context.Background(), &types.Transaction{ To: &Address2, Value: big.NewInt(1_000_000_000_000_000_000), // 1ETH }) @@ -1181,7 +1181,7 @@ func TestIntegrationSmartAccount_SendTransaction(t *testing.T) { approveTokenCalldata, err := tokenAbi.Pack("approve", Address2, big.NewInt(1)) assert.NoError(t, err, "abi.Pack should not return an error") - txHash, err := account.SendTransaction(context.Background(), &types.Transaction712{ + txHash, err := account.SendTransaction(context.Background(), &types.Transaction{ To: &L2Dai, Data: approveTokenCalldata, }) diff --git a/test/smart_account_utils_test.go b/test/smart_account_utils_test.go index d1c93ce..22731d4 100644 --- a/test/smart_account_utils_test.go +++ b/test/smart_account_utils_test.go @@ -15,7 +15,7 @@ func TestIntegration_PopulateTransactionECDSA(t *testing.T) { defer client.Close() assert.NoError(t, err, "clients.DialBase should not return an error") - tx := types.Transaction712{ + tx := types.Transaction{ To: &Address2, Value: big.NewInt(7_000_000_000), ChainID: big.NewInt(270), @@ -33,11 +33,11 @@ func TestIntegration_PopulateTransactionECDSA(t *testing.T) { assert.NotNil(t, tx.Gas, "Gas must not be nil") assert.NotNil(t, tx.GasFeeCap, "GasFeeCap must not be nil") assert.NotNil(t, tx.GasTipCap, "GasTipCap must not be nil") - assert.NotNil(t, tx.Meta, "Meta must not be nil") + assert.NotNil(t, tx.GasPerPubdata, "GasPerPubdata must not be nil") } func TestIntegration_PopulateTransactionECDSA_ErrorNoClientProvided(t *testing.T) { - tx := types.Transaction712{ + tx := types.Transaction{ To: &Address2, Value: big.NewInt(7_000_000_000), ChainID: big.NewInt(270), @@ -53,6 +53,6 @@ func TestIntegration_PopulateTransactionMultipleECDSA_ErrorNoMultipleKeysProvide defer client.Close() assert.NoError(t, err, "clients.DialBase should not return an error") - err = accounts.PopulateTransactionMultipleECDSA(context.Background(), &types.Transaction712{}, [1]string{PrivateKey1}, client) + err = accounts.PopulateTransactionMultipleECDSA(context.Background(), &types.Transaction{}, [1]string{PrivateKey1}, client) assert.Error(t, err, "PopulateTransactionMultipleECDSA should return an error when only one private key is provided") } diff --git a/test/wallet_test.go b/test/wallet_test.go index 1604577..3146997 100644 --- a/test/wallet_test.go +++ b/test/wallet_test.go @@ -760,19 +760,17 @@ func TestIntegrationWallet_PopulateTransaction(t *testing.T) { nonce, err := wallet.Nonce(context.Background(), nil) assert.NoError(t, err, "NewWallet should not return an error") - tx := &types.Transaction712{ - Nonce: new(big.Int).SetUint64(nonce), - GasTipCap: big.NewInt(0), - GasFeeCap: big.NewInt(100_000_000), - Gas: big.NewInt(154_379), - To: &Address2, - Value: big.NewInt(7_000_000_000), - ChainID: big.NewInt(270), - From: &Address1, - Data: hexutil.Bytes{}, - Meta: &types.Eip712Meta{ - GasPerPubdata: utils.NewBig(50_000), - }, + tx := &types.Transaction{ + Nonce: new(big.Int).SetUint64(nonce), + GasTipCap: big.NewInt(0), + GasFeeCap: big.NewInt(100_000_000), + Gas: big.NewInt(154_379), + To: &Address2, + Value: big.NewInt(7_000_000_000), + ChainID: big.NewInt(270), + From: &Address1, + Data: hexutil.Bytes{}, + GasPerPubdata: big.NewInt(50_000), } populatedTx, err := wallet.PopulateTransaction(context.Background(), accounts.Transaction{ @@ -792,7 +790,7 @@ func TestIntegrationWallet_SignTransaction(t *testing.T) { wallet, err := accounts.NewWallet(common.Hex2Bytes(PrivateKey1), client, nil) assert.NoError(t, err, "NewWallet should not return an error") - signedTx, err := wallet.SignTransaction(&types.Transaction712{ + signedTx, err := wallet.SignTransaction(&types.Transaction{ To: &Address2, Value: big.NewInt(1_000_000_000_000_000_000), // 1ETH }) diff --git a/types/eip712_tx_test.go b/types/eip712_tx_test.go deleted file mode 100644 index b9dfde3..0000000 --- a/types/eip712_tx_test.go +++ /dev/null @@ -1,235 +0,0 @@ -package types - -import ( - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/stretchr/testify/assert" - "github.com/zksync-sdk/zksync2-go/contracts/paymasterflow" - "math/big" - "strings" - "testing" -) - -var Address1 = common.HexToAddress("0x36615Cf349d7F6344891B1e7CA7C72883F5dc049") -var Address2 = common.HexToAddress("0xa61464658AfeAf65CccaaFD3a512b69A83B77618") -var Paymaster = common.HexToAddress("0xa222f0c183AFA73a8Bc1AFb48D34C88c9Bf7A174") -var ApprovalToken = common.HexToAddress("0x841c43Fa5d8fFfdB9efE3358906f7578d8700Dd4") - -func TestTransaction712_DecodeUnsignedTx(t *testing.T) { - serializedTx := "0x71f8418080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0" - - tx := Transaction712{ - Nonce: big.NewInt(0), - GasTipCap: big.NewInt(0), - GasFeeCap: big.NewInt(0), - Gas: big.NewInt(0), - Value: big.NewInt(1_000_000), - Data: hexutil.Bytes{}, - ChainID: big.NewInt(270), - From: &Address1, - To: &Address2, - Meta: &Eip712Meta{ - GasPerPubdata: (*hexutil.Big)(big.NewInt(50_000)), - CustomSignature: hexutil.Bytes{}, - FactoryDeps: []hexutil.Bytes{}, - }, - } - - decodedTx := new(Transaction712) - err := decodedTx.Decode(common.FromHex(serializedTx)) - assert.NoError(t, err, "Decode should not return an error") - assert.Equal(t, *decodedTx, tx, "Transaction should be same") -} - -func TestTransaction712_DecodeUnsignedTxWithPaymaster(t *testing.T) { - serializedTx := "0x71f8dd8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080f89b94a222f0c183afa73a8bc1afb48d34c88c9bf7a174b884949431dc000000000000000000000000841c43fa5d8fffdb9efe3358906f7578d8700dd4000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - - paymasterFlowAbi, err := abi.JSON(strings.NewReader(paymasterflow.IPaymasterFlowMetaData.ABI)) - paymasterInput, err := paymasterFlowAbi.Pack("approvalBased", - ApprovalToken, - big.NewInt(1), - []byte{}, - ) - assert.NoError(t, err, "Pack should not return an error") - - tx := Transaction712{ - Nonce: big.NewInt(0), - GasTipCap: big.NewInt(0), - GasFeeCap: big.NewInt(0), - Gas: big.NewInt(0), - Value: big.NewInt(1_000_000), - Data: hexutil.Bytes{}, - ChainID: big.NewInt(270), - From: &Address1, - To: &Address2, - Meta: &Eip712Meta{ - GasPerPubdata: (*hexutil.Big)(big.NewInt(50_000)), - CustomSignature: hexutil.Bytes{}, - FactoryDeps: []hexutil.Bytes{}, - PaymasterParams: &PaymasterParams{Paymaster, paymasterInput}, - }, - } - - decodedTx := new(Transaction712) - err = decodedTx.Decode(common.FromHex(serializedTx)) - assert.NoError(t, err, "Decode should not return an error") - assert.Equal(t, *decodedTx, tx, "Transaction should be same") -} - -func TestTransaction712_DecodeSignedTx(t *testing.T) { - serializedTx := "0x71f8c68080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c0b884307861666533646639333965383139373437613036386664356134346633316335313765643965313933336166333730633062636532623232303139623666373139326161336536316364363165373039356264353665343636316639336130303231396133393663313130366364383935623361663338623534633236373032313163c0" - signature := "0xafe3df939e819747a068fd5a44f31c517ed9e1933af370c0bce2b22019b6f7192aa3e61cd61e7095bd56e4661f93a00219a396c1106cd895b3af38b54c2670211c" - - tx := Transaction712{ - Nonce: big.NewInt(0), - GasTipCap: big.NewInt(0), - GasFeeCap: big.NewInt(0), - Gas: big.NewInt(0), - Value: big.NewInt(1_000_000), - Data: hexutil.Bytes{}, - ChainID: big.NewInt(270), - From: &Address1, - To: &Address2, - Meta: &Eip712Meta{ - GasPerPubdata: (*hexutil.Big)(big.NewInt(50_000)), - CustomSignature: hexutil.Bytes(signature), - FactoryDeps: []hexutil.Bytes{}, - }, - } - - decodedTx := new(Transaction712) - err := decodedTx.Decode(common.FromHex(serializedTx)) - assert.NoError(t, err, "Decode should not return an error") - assert.Equal(t, *decodedTx, tx, "Transaction should be same") -} - -func TestTransaction712_DecodeSignedTxWithPaymaster(t *testing.T) { - serializedTx := "0x71f901628080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c0b884307837373262396162343735386435636630386637643732303161646332653534383933616532376263666562323162396337643666643430393766346464653063303166376630353332323866346636643838653662663334333436343931343135363761633930363632306661653832633239333339393062353563613336363162f89b94a222f0c183afa73a8bc1afb48d34c88c9bf7a174b884949431dc000000000000000000000000841c43fa5d8fffdb9efe3358906f7578d8700dd4000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - signature := "0x772b9ab4758d5cf08f7d7201adc2e54893ae27bcfeb21b9c7d6fd4097f4dde0c01f7f053228f4f6d88e6bf3434649141567ac906620fae82c2933990b55ca3661b" - - paymasterFlowAbi, err := abi.JSON(strings.NewReader(paymasterflow.IPaymasterFlowMetaData.ABI)) - paymasterInput, err := paymasterFlowAbi.Pack("approvalBased", - ApprovalToken, - big.NewInt(1), - []byte{}, - ) - assert.NoError(t, err, "Pack should not return an error") - - tx := Transaction712{ - Nonce: big.NewInt(0), - GasTipCap: big.NewInt(0), - GasFeeCap: big.NewInt(0), - Gas: big.NewInt(0), - Value: big.NewInt(1_000_000), - Data: hexutil.Bytes{}, - ChainID: big.NewInt(270), - From: &Address1, - To: &Address2, - Meta: &Eip712Meta{ - GasPerPubdata: (*hexutil.Big)(big.NewInt(50_000)), - CustomSignature: hexutil.Bytes(signature), - FactoryDeps: []hexutil.Bytes{}, - PaymasterParams: &PaymasterParams{Paymaster, paymasterInput}, - }, - } - - decodedTx := new(Transaction712) - err = decodedTx.Decode(common.FromHex(serializedTx)) - assert.NoError(t, err, "Decode should not return an error") - assert.Equal(t, *decodedTx, tx, "Transaction should be same") -} - -func TestTransaction712_RLPValues(t *testing.T) { - serializedTx := "0x71f8c68080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c0b884307861666533646639333965383139373437613036386664356134346633316335313765643965313933336166333730633062636532623232303139623666373139326161336536316364363165373039356264353665343636316639336130303231396133393663313130366364383935623361663338623534633236373032313163c0" - signature := "0xafe3df939e819747a068fd5a44f31c517ed9e1933af370c0bce2b22019b6f7192aa3e61cd61e7095bd56e4661f93a00219a396c1106cd895b3af38b54c2670211c" - - tx := Transaction712{ - Nonce: big.NewInt(0), - GasTipCap: big.NewInt(0), - GasFeeCap: big.NewInt(0), - Gas: big.NewInt(0), - Value: big.NewInt(1_000_000), - Data: hexutil.Bytes{}, - ChainID: big.NewInt(270), - From: &Address1, - To: &Address2, - Meta: &Eip712Meta{ - GasPerPubdata: (*hexutil.Big)(big.NewInt(50_000)), - CustomSignature: hexutil.Bytes(signature), - FactoryDeps: []hexutil.Bytes{}, - }, - } - - result, err := tx.RLPValues(nil) - assert.NoError(t, err, "RLPValues should not return an error") - assert.Equal(t, common.FromHex(serializedTx), result, "Serialized transactions should be same") -} - -func TestTransaction712_RLPValuesPaymasterWithoutSignature(t *testing.T) { - serializedTx := "0x71f8dd8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080f89b94a222f0c183afa73a8bc1afb48d34c88c9bf7a174b884949431dc000000000000000000000000841c43fa5d8fffdb9efe3358906f7578d8700dd4000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - - paymasterFlowAbi, err := abi.JSON(strings.NewReader(paymasterflow.IPaymasterFlowMetaData.ABI)) - paymasterInput, err := paymasterFlowAbi.Pack("approvalBased", - ApprovalToken, - big.NewInt(1), - []byte{}, - ) - assert.NoError(t, err, "Pack should not return an error") - - tx := Transaction712{ - Nonce: big.NewInt(0), - GasTipCap: big.NewInt(0), - GasFeeCap: big.NewInt(0), - Gas: big.NewInt(0), - Value: big.NewInt(1_000_000), - Data: hexutil.Bytes{}, - ChainID: big.NewInt(270), - From: &Address1, - To: &Address2, - Meta: &Eip712Meta{ - GasPerPubdata: (*hexutil.Big)(big.NewInt(50_000)), - CustomSignature: hexutil.Bytes{}, - FactoryDeps: []hexutil.Bytes{}, - PaymasterParams: &PaymasterParams{Paymaster, paymasterInput}, - }, - } - - result, err := tx.RLPValues(nil) - assert.NoError(t, err, "RLPValues should not return an error") - assert.Equal(t, common.FromHex(serializedTx), result, "Serialized transactions should be same") -} - -func TestTransaction712_RLPValuesPaymasterWithSignature(t *testing.T) { - serializedTx := "0x71f901628080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c0b884307837373262396162343735386435636630386637643732303161646332653534383933616532376263666562323162396337643666643430393766346464653063303166376630353332323866346636643838653662663334333436343931343135363761633930363632306661653832633239333339393062353563613336363162f89b94a222f0c183afa73a8bc1afb48d34c88c9bf7a174b884949431dc000000000000000000000000841c43fa5d8fffdb9efe3358906f7578d8700dd4000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" - signature := "0x772b9ab4758d5cf08f7d7201adc2e54893ae27bcfeb21b9c7d6fd4097f4dde0c01f7f053228f4f6d88e6bf3434649141567ac906620fae82c2933990b55ca3661b" - - paymasterFlowAbi, err := abi.JSON(strings.NewReader(paymasterflow.IPaymasterFlowMetaData.ABI)) - paymasterInput, err := paymasterFlowAbi.Pack("approvalBased", - ApprovalToken, - big.NewInt(1), - []byte{}, - ) - assert.NoError(t, err, "Pack should not return an error") - - tx := Transaction712{ - Nonce: big.NewInt(0), - GasTipCap: big.NewInt(0), - GasFeeCap: big.NewInt(0), - Gas: big.NewInt(0), - Value: big.NewInt(1_000_000), - Data: hexutil.Bytes{}, - ChainID: big.NewInt(270), - From: &Address1, - To: &Address2, - Meta: &Eip712Meta{ - GasPerPubdata: (*hexutil.Big)(big.NewInt(50_000)), - CustomSignature: hexutil.Bytes(signature), - FactoryDeps: []hexutil.Bytes{}, - PaymasterParams: &PaymasterParams{Paymaster, paymasterInput}, - }, - } - result, err := tx.RLPValues(nil) - assert.NoError(t, err, "RLPValues should not return an error") - assert.Equal(t, common.FromHex(serializedTx), result, "Serialized transactions should be same") -} diff --git a/types/eip712_tx.go b/types/transaction.go similarity index 52% rename from types/eip712_tx.go rename to types/transaction.go index 7fbfc39..f06f268 100644 --- a/types/eip712_tx.go +++ b/types/transaction.go @@ -7,33 +7,48 @@ import ( "fmt" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/signer/core/apitypes" + "github.com/zksync-sdk/zksync2-go/eip712" "math/big" ) // EIP712TxType represents an EIP-712 transaction type. const EIP712TxType = `0x71` -// Transaction712 represents an EIP-712 compliant transaction. -// It shares similarities with regular transactions but also includes ZKsync Era-specific features such as account -// abstraction and paymasters. -// Smart contracts must be deployed with support for the EIP-712 transaction type. -type Transaction712 struct { - Nonce *big.Int // Nonce to use for the transaction execution. - GasTipCap *big.Int // EIP-1559 tip per gas. - GasFeeCap *big.Int // EIP-1559 fee cap per gas. - Gas *big.Int // Gas limit to set for the transaction execution. - To *common.Address // The address of the recipient. - Value *big.Int // Funds to transfer along the transaction (nil = 0 = no funds). - Data hexutil.Bytes // Input data, usually an ABI-encoded contract method invocation. +// Transaction provides support for ZKsync Era-specific features +// such as account abstraction and paymasters. +// Smart contracts must be deployed with this transaction type. +type Transaction struct { + Nonce *big.Int `json:"nonce"` // Nonce to use for the transaction execution. + GasTipCap *big.Int `json:"gasTipCap"` // EIP-1559 tip per gas. + GasFeeCap *big.Int `json:"gasFeeCap"` // EIP-1559 fee cap per gas. + Gas *big.Int `json:"gas"` // Gas limit to set for the transaction execution. + To *common.Address `json:"to"` // The address of the recipient. + Value *big.Int `json:"value"` // Funds to transfer along the transaction (nil = 0 = no funds). + Data hexutil.Bytes `json:"data"` // Input data, usually an ABI-encoded contract method invocation. - ChainID *big.Int // Chain ID of the network. - From *common.Address // The address of the sender. - Meta *Eip712Meta // EIP-712 metadata. + ChainID *big.Int `json:"chainID"` // Chain ID of the network. + From *common.Address `json:"from"` // The address of the sender. + + // GasPerPubdata denotes the maximum amount of gas the user is willing + // to pay for a single byte of pubdata. + GasPerPubdata *big.Int `json:"gasPerPubdata"` + // CustomSignature is used for the cases in which the signer's account + // is not an EOA. + CustomSignature hexutil.Bytes `json:"customSignature"` + // FactoryDeps is a non-empty array of bytes. For deployment transactions, + // it should contain the bytecode of the contract being deployed. + // If the contract is a factory contract, i.e. it can deploy other contracts, + // the array should also contain the bytecodes of the contracts which it can deploy. + FactoryDeps []hexutil.Bytes `json:"factoryDeps"` + // PaymasterParams contains parameters for configuring the custom paymaster + // for the transaction. + PaymasterParams *PaymasterParams `json:"paymasterParams"` } -func (tx *Transaction712) RLPValues(sig []byte) ([]byte, error) { +func (tx *Transaction) Encode(sig []byte) ([]byte, error) { // use custom struct to get right RLP sequence and types to use default rlp encoder zkSyncTxRLP := struct { Nonce uint64 @@ -44,10 +59,10 @@ func (tx *Transaction712) RLPValues(sig []byte) ([]byte, error) { Value *big.Int Data hexutil.Bytes // zkSync part - ChainID1 *big.Int // legacy + ChainId1 *big.Int // legacy Empty1 string // legacy Empty2 string // legacy - ChainID2 *big.Int + ChainId2 *big.Int From *common.Address // Meta fields *Meta GasPerPubdata *big.Int @@ -62,13 +77,13 @@ func (tx *Transaction712) RLPValues(sig []byte) ([]byte, error) { To: tx.To, Value: tx.Value, Data: tx.Data, - ChainID1: tx.ChainID, - ChainID2: tx.ChainID, + ChainId1: tx.ChainID, + ChainId2: tx.ChainID, From: tx.From, - GasPerPubdata: tx.Meta.GasPerPubdata.ToInt(), - FactoryDeps: tx.Meta.FactoryDeps, - CustomSignature: tx.Meta.CustomSignature, - PaymasterParams: tx.Meta.PaymasterParams, + GasPerPubdata: tx.GasPerPubdata, + FactoryDeps: tx.FactoryDeps, + CustomSignature: tx.CustomSignature, + PaymasterParams: tx.PaymasterParams, } if len(zkSyncTxRLP.CustomSignature) == 0 { zkSyncTxRLP.CustomSignature = sig @@ -81,7 +96,7 @@ func (tx *Transaction712) RLPValues(sig []byte) ([]byte, error) { return append([]byte{0x71}, res...), nil } -func (tx *Transaction712) Decode(input []byte) error { +func (tx *Transaction) Decode(input []byte) error { type zkSyncTxRLP struct { Nonce uint64 MaxPriorityFeePerGas *big.Int @@ -91,10 +106,10 @@ func (tx *Transaction712) Decode(input []byte) error { Value *big.Int Data hexutil.Bytes // zkSync part - ChainID1 *big.Int // legacy + ChainId1 *big.Int // legacy Empty1 string // legacy Empty2 string // legacy - ChainID2 *big.Int + ChainId2 *big.Int From *common.Address // Meta fields *Meta GasPerPubdata *big.Int @@ -115,23 +130,118 @@ func (tx *Transaction712) Decode(input []byte) error { tx.To = decodedTx.To tx.Value = decodedTx.Value tx.Data = decodedTx.Data - tx.ChainID = decodedTx.ChainID2 + tx.ChainID = decodedTx.ChainId2 tx.From = decodedTx.From - tx.Meta = &Eip712Meta{ - GasPerPubdata: (*hexutil.Big)(decodedTx.GasPerPubdata), - CustomSignature: decodedTx.CustomSignature, - FactoryDeps: decodedTx.FactoryDeps, - PaymasterParams: decodedTx.PaymasterParams, + tx.GasPerPubdata = decodedTx.GasPerPubdata + tx.CustomSignature = decodedTx.CustomSignature + tx.FactoryDeps = decodedTx.FactoryDeps + tx.PaymasterParams = decodedTx.PaymasterParams + return nil +} + +func (tx *Transaction) TypedData() (*apitypes.TypedData, error) { + domain := eip712.ZkSyncEraEIP712Domain(tx.ChainID.Int64()) + message, err := tx.typedDataMessage() + if err != nil { + return nil, err } + if err != nil { + return nil, err + } + return &apitypes.TypedData{ + Types: apitypes.Types{ + "Transaction": tx.types(), + domain.EIP712Type(): domain.EIP712Types(), + }, + PrimaryType: "Transaction", + Domain: domain.EIP712Domain(), + Message: message, + }, nil +} - return nil +func (tx *Transaction) Hash() ([]byte, error) { + typedData, err := tx.TypedData() + if err != nil { + return nil, fmt.Errorf("failed to get typed data: %w", err) + } + hash, err := tx.hashTypedData(*typedData) + if err != nil { + return nil, fmt.Errorf("failed to get hash of typed data: %w", err) + } + return hash, nil } -func (tx *Transaction712) EIP712Type() string { - return "Transaction" +func (tx *Transaction) Copy() *Transaction { + if tx == nil { + return nil + } + + cpy := &Transaction{ + Nonce: new(big.Int), + GasTipCap: new(big.Int), + GasFeeCap: new(big.Int), + Gas: new(big.Int), + Value: new(big.Int), + ChainID: new(big.Int), + To: copyAddressPtr(tx.To), + From: copyAddressPtr(tx.From), + Data: common.CopyBytes(tx.Data), + CustomSignature: common.CopyBytes(tx.CustomSignature), + GasPerPubdata: new(big.Int), + FactoryDeps: make([]hexutil.Bytes, len(tx.FactoryDeps)), + } + + if tx.Nonce != nil { + cpy.Nonce.Set(tx.Nonce) + } + if tx.GasTipCap != nil { + cpy.GasTipCap.Set(tx.GasTipCap) + } + if tx.GasFeeCap != nil { + cpy.GasFeeCap.Set(tx.GasFeeCap) + } + if tx.Gas != nil { + cpy.Gas.Set(tx.Gas) + } + if tx.Value != nil { + cpy.Value.Set(tx.Value) + } + if tx.ChainID != nil { + cpy.ChainID.Set(tx.ChainID) + } + if tx.GasPerPubdata != nil { + cpy.GasPerPubdata.Set(tx.GasPerPubdata) + } + + for i, dep := range tx.FactoryDeps { + cpy.FactoryDeps[i] = common.CopyBytes(dep) + } + + if tx.PaymasterParams != nil { + cpy.PaymasterParams = &PaymasterParams{ + Paymaster: *copyAddressPtr(&tx.PaymasterParams.Paymaster), + PaymasterInput: common.CopyBytes(tx.PaymasterParams.PaymasterInput), + } + } + + return cpy +} + +func (tx *Transaction) hashTypedData(data apitypes.TypedData) ([]byte, error) { + domain, err := data.HashStruct("EIP712Domain", data.Domain.Map()) + if err != nil { + return nil, fmt.Errorf("failed to get hash of typed data domain: %w", err) + } + dataHash, err := data.HashStruct(data.PrimaryType, data.Message) + if err != nil { + return nil, fmt.Errorf("failed to get hash of typed message: %w", err) + } + prefixedData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domain), string(dataHash))) + prefixedDataHash := crypto.Keccak256(prefixedData) + return prefixedDataHash, nil } -func (tx *Transaction712) EIP712Types() []apitypes.Type { +func (tx *Transaction) types() []apitypes.Type { return []apitypes.Type{ {Name: "txType", Type: "uint256"}, {Name: "from", Type: "uint256"}, @@ -149,12 +259,12 @@ func (tx *Transaction712) EIP712Types() []apitypes.Type { } } -func (tx *Transaction712) EIP712Message() (apitypes.TypedDataMessage, error) { +func (tx *Transaction) typedDataMessage() (apitypes.TypedDataMessage, error) { paymaster := big.NewInt(0) paymasterInput := hexutil.Bytes{} - if tx.Meta != nil && tx.Meta.PaymasterParams != nil { - paymaster = big.NewInt(0).SetBytes(tx.Meta.PaymasterParams.Paymaster.Bytes()) - paymasterInput = tx.Meta.PaymasterParams.PaymasterInput + if tx.PaymasterParams != nil { + paymaster = big.NewInt(0).SetBytes(tx.PaymasterParams.Paymaster.Bytes()) + paymasterInput = tx.PaymasterParams.PaymasterInput } value := `0x0` if tx.Value != nil { @@ -169,7 +279,7 @@ func (tx *Transaction712) EIP712Message() (apitypes.TypedDataMessage, error) { "from": big.NewInt(0).SetBytes(tx.From.Bytes()).String(), "to": big.NewInt(0).SetBytes(tx.To.Bytes()).String(), "gasLimit": tx.Gas.String(), - "gasPerPubdataByteLimit": tx.Meta.GasPerPubdata.String(), + "gasPerPubdataByteLimit": tx.GasPerPubdata.String(), "maxFeePerGas": tx.GasFeeCap.String(), "maxPriorityFeePerGas": tx.GasTipCap.String(), "paymaster": paymaster.String(), @@ -181,12 +291,30 @@ func (tx *Transaction712) EIP712Message() (apitypes.TypedDataMessage, error) { }, nil } -func (tx *Transaction712) getFactoryDepsHashes() ([]interface{}, error) { - if tx.Meta == nil || len(tx.Meta.FactoryDeps) == 0 { +func (tx *Transaction) MarshalJSON() ([]byte, error) { + type Alias Transaction + fdb := make([][]uint, len(tx.FactoryDeps)) + for i, v := range tx.FactoryDeps { + fdb[i] = make([]uint, len(v)) + for j, b := range v { + fdb[i][j] = uint(b) + } + } + return json.Marshal(&struct { + *Alias + FactoryDeps [][]uint `json:"factoryDeps"` + }{ + Alias: (*Alias)(tx), + FactoryDeps: fdb, + }) +} + +func (tx *Transaction) getFactoryDepsHashes() ([]interface{}, error) { + if len(tx.FactoryDeps) == 0 { return []interface{}{}, nil } - res := make([]interface{}, len(tx.Meta.FactoryDeps)) - for i, d := range tx.Meta.FactoryDeps { + res := make([]interface{}, len(tx.FactoryDeps)) + for i, d := range tx.FactoryDeps { h, err := hashBytecode(d) if err != nil { return nil, fmt.Errorf("failed to get hash of some bytecode in FactoryDeps") @@ -274,3 +402,11 @@ func hashBytecode(bytecode []byte) ([]byte, error) { copy(bytecodeHash[2:4], length2b) return bytecodeHash[:], nil } + +func copyAddressPtr(a *common.Address) *common.Address { + if a == nil { + return nil + } + cpy := *a + return &cpy +} diff --git a/types/transaction_test.go b/types/transaction_test.go new file mode 100644 index 0000000..7b9c328 --- /dev/null +++ b/types/transaction_test.go @@ -0,0 +1,315 @@ +package types + +import ( + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/stretchr/testify/assert" + "github.com/zksync-sdk/zksync2-go/contracts/paymasterflow" + "math/big" + "strings" + "testing" +) + +var Address1 = common.HexToAddress("0x36615Cf349d7F6344891B1e7CA7C72883F5dc049") +var Address2 = common.HexToAddress("0xa61464658AfeAf65CccaaFD3a512b69A83B77618") +var Paymaster = common.HexToAddress("0xa222f0c183AFA73a8Bc1AFb48D34C88c9Bf7A174") +var ApprovalToken = common.HexToAddress("0x841c43Fa5d8fFfdB9efE3358906f7578d8700Dd4") + +func TestTransaction712_DecodeUnsignedTx(t *testing.T) { + serializedTx := "0x71f8418080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080c0" + + tx := Transaction{ + Nonce: big.NewInt(0), + GasTipCap: big.NewInt(0), + GasFeeCap: big.NewInt(0), + Gas: big.NewInt(0), + Value: big.NewInt(1_000_000), + Data: hexutil.Bytes{}, + ChainID: big.NewInt(270), + From: &Address1, + To: &Address2, + GasPerPubdata: big.NewInt(50_000), + CustomSignature: hexutil.Bytes{}, + FactoryDeps: []hexutil.Bytes{}, + } + + decodedTx := new(Transaction) + err := decodedTx.Decode(common.FromHex(serializedTx)) + assert.NoError(t, err, "Decode should not return an error") + assert.Equal(t, *decodedTx, tx, "Transaction should be same") +} + +func TestTransaction712_DecodeUnsignedTxWithPaymaster(t *testing.T) { + serializedTx := "0x71f8dd8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080f89b94a222f0c183afa73a8bc1afb48d34c88c9bf7a174b884949431dc000000000000000000000000841c43fa5d8fffdb9efe3358906f7578d8700dd4000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + + paymasterFlowAbi, err := abi.JSON(strings.NewReader(paymasterflow.IPaymasterFlowMetaData.ABI)) + paymasterInput, err := paymasterFlowAbi.Pack("approvalBased", + ApprovalToken, + big.NewInt(1), + []byte{}, + ) + assert.NoError(t, err, "Pack should not return an error") + + tx := Transaction{ + Nonce: big.NewInt(0), + GasTipCap: big.NewInt(0), + GasFeeCap: big.NewInt(0), + Gas: big.NewInt(0), + Value: big.NewInt(1_000_000), + Data: hexutil.Bytes{}, + ChainID: big.NewInt(270), + From: &Address1, + To: &Address2, + GasPerPubdata: big.NewInt(50_000), + CustomSignature: hexutil.Bytes{}, + FactoryDeps: []hexutil.Bytes{}, + PaymasterParams: &PaymasterParams{Paymaster, paymasterInput}, + } + + decodedTx := new(Transaction) + err = decodedTx.Decode(common.FromHex(serializedTx)) + assert.NoError(t, err, "Decode should not return an error") + assert.Equal(t, *decodedTx, tx, "Transaction should be same") +} + +func TestTransaction712_DecodeSignedTx(t *testing.T) { + serializedTx := "0x71f8c68080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c0b884307861666533646639333965383139373437613036386664356134346633316335313765643965313933336166333730633062636532623232303139623666373139326161336536316364363165373039356264353665343636316639336130303231396133393663313130366364383935623361663338623534633236373032313163c0" + signature := "0xafe3df939e819747a068fd5a44f31c517ed9e1933af370c0bce2b22019b6f7192aa3e61cd61e7095bd56e4661f93a00219a396c1106cd895b3af38b54c2670211c" + + tx := Transaction{ + Nonce: big.NewInt(0), + GasTipCap: big.NewInt(0), + GasFeeCap: big.NewInt(0), + Gas: big.NewInt(0), + Value: big.NewInt(1_000_000), + Data: hexutil.Bytes{}, + ChainID: big.NewInt(270), + From: &Address1, + To: &Address2, + GasPerPubdata: big.NewInt(50_000), + CustomSignature: hexutil.Bytes(signature), + FactoryDeps: []hexutil.Bytes{}, + } + + decodedTx := new(Transaction) + err := decodedTx.Decode(common.FromHex(serializedTx)) + assert.NoError(t, err, "Decode should not return an error") + assert.Equal(t, *decodedTx, tx, "Transaction should be same") +} + +func TestTransaction712_DecodeSignedTxWithPaymaster(t *testing.T) { + serializedTx := "0x71f901628080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c0b884307837373262396162343735386435636630386637643732303161646332653534383933616532376263666562323162396337643666643430393766346464653063303166376630353332323866346636643838653662663334333436343931343135363761633930363632306661653832633239333339393062353563613336363162f89b94a222f0c183afa73a8bc1afb48d34c88c9bf7a174b884949431dc000000000000000000000000841c43fa5d8fffdb9efe3358906f7578d8700dd4000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + signature := "0x772b9ab4758d5cf08f7d7201adc2e54893ae27bcfeb21b9c7d6fd4097f4dde0c01f7f053228f4f6d88e6bf3434649141567ac906620fae82c2933990b55ca3661b" + + paymasterFlowAbi, err := abi.JSON(strings.NewReader(paymasterflow.IPaymasterFlowMetaData.ABI)) + paymasterInput, err := paymasterFlowAbi.Pack("approvalBased", + ApprovalToken, + big.NewInt(1), + []byte{}, + ) + assert.NoError(t, err, "Pack should not return an error") + + tx := Transaction{ + Nonce: big.NewInt(0), + GasTipCap: big.NewInt(0), + GasFeeCap: big.NewInt(0), + Gas: big.NewInt(0), + Value: big.NewInt(1_000_000), + Data: hexutil.Bytes{}, + ChainID: big.NewInt(270), + From: &Address1, + To: &Address2, + GasPerPubdata: big.NewInt(50_000), + CustomSignature: hexutil.Bytes(signature), + FactoryDeps: []hexutil.Bytes{}, + PaymasterParams: &PaymasterParams{Paymaster, paymasterInput}, + } + + decodedTx := new(Transaction) + err = decodedTx.Decode(common.FromHex(serializedTx)) + assert.NoError(t, err, "Decode should not return an error") + assert.Equal(t, *decodedTx, tx, "Transaction should be same") +} + +func TestTransaction712_RLPValues(t *testing.T) { + serializedTx := "0x71f8c68080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c0b884307861666533646639333965383139373437613036386664356134346633316335313765643965313933336166333730633062636532623232303139623666373139326161336536316364363165373039356264353665343636316639336130303231396133393663313130366364383935623361663338623534633236373032313163c0" + signature := "0xafe3df939e819747a068fd5a44f31c517ed9e1933af370c0bce2b22019b6f7192aa3e61cd61e7095bd56e4661f93a00219a396c1106cd895b3af38b54c2670211c" + + tx := Transaction{ + Nonce: big.NewInt(0), + GasTipCap: big.NewInt(0), + GasFeeCap: big.NewInt(0), + Gas: big.NewInt(0), + Value: big.NewInt(1_000_000), + Data: hexutil.Bytes{}, + ChainID: big.NewInt(270), + From: &Address1, + To: &Address2, + GasPerPubdata: big.NewInt(50_000), + CustomSignature: hexutil.Bytes(signature), + FactoryDeps: []hexutil.Bytes{}, + } + + result, err := tx.Encode(nil) + assert.NoError(t, err, "Encode should not return an error") + assert.Equal(t, common.FromHex(serializedTx), result, "Serialized transactions should be same") +} + +func TestTransaction712_RLPValuesPaymasterWithoutSignature(t *testing.T) { + serializedTx := "0x71f8dd8080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c080f89b94a222f0c183afa73a8bc1afb48d34c88c9bf7a174b884949431dc000000000000000000000000841c43fa5d8fffdb9efe3358906f7578d8700dd4000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + + paymasterFlowAbi, err := abi.JSON(strings.NewReader(paymasterflow.IPaymasterFlowMetaData.ABI)) + paymasterInput, err := paymasterFlowAbi.Pack("approvalBased", + ApprovalToken, + big.NewInt(1), + []byte{}, + ) + assert.NoError(t, err, "Pack should not return an error") + + tx := Transaction{ + Nonce: big.NewInt(0), + GasTipCap: big.NewInt(0), + GasFeeCap: big.NewInt(0), + Gas: big.NewInt(0), + Value: big.NewInt(1_000_000), + Data: hexutil.Bytes{}, + ChainID: big.NewInt(270), + From: &Address1, + To: &Address2, + GasPerPubdata: big.NewInt(50_000), + CustomSignature: hexutil.Bytes{}, + FactoryDeps: []hexutil.Bytes{}, + PaymasterParams: &PaymasterParams{Paymaster, paymasterInput}, + } + + result, err := tx.Encode(nil) + assert.NoError(t, err, "Encode should not return an error") + assert.Equal(t, common.FromHex(serializedTx), result, "Serialized transactions should be same") +} + +func TestTransaction712_RLPValuesPaymasterWithSignature(t *testing.T) { + serializedTx := "0x71f901628080808094a61464658afeaf65cccaafd3a512b69a83b77618830f42408082010e808082010e9436615cf349d7f6344891b1e7ca7c72883f5dc04982c350c0b884307837373262396162343735386435636630386637643732303161646332653534383933616532376263666562323162396337643666643430393766346464653063303166376630353332323866346636643838653662663334333436343931343135363761633930363632306661653832633239333339393062353563613336363162f89b94a222f0c183afa73a8bc1afb48d34c88c9bf7a174b884949431dc000000000000000000000000841c43fa5d8fffdb9efe3358906f7578d8700dd4000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000" + signature := "0x772b9ab4758d5cf08f7d7201adc2e54893ae27bcfeb21b9c7d6fd4097f4dde0c01f7f053228f4f6d88e6bf3434649141567ac906620fae82c2933990b55ca3661b" + + paymasterFlowAbi, err := abi.JSON(strings.NewReader(paymasterflow.IPaymasterFlowMetaData.ABI)) + paymasterInput, err := paymasterFlowAbi.Pack("approvalBased", + ApprovalToken, + big.NewInt(1), + []byte{}, + ) + assert.NoError(t, err, "Pack should not return an error") + + tx := Transaction{ + Nonce: big.NewInt(0), + GasTipCap: big.NewInt(0), + GasFeeCap: big.NewInt(0), + Gas: big.NewInt(0), + Value: big.NewInt(1_000_000), + Data: hexutil.Bytes{}, + ChainID: big.NewInt(270), + From: &Address1, + To: &Address2, + GasPerPubdata: big.NewInt(50_000), + CustomSignature: hexutil.Bytes(signature), + FactoryDeps: []hexutil.Bytes{}, + PaymasterParams: &PaymasterParams{Paymaster, paymasterInput}, + } + result, err := tx.Encode(nil) + assert.NoError(t, err, "Encode should not return an error") + assert.Equal(t, common.FromHex(serializedTx), result, "Serialized transactions should be same") +} + +func TestTransactionCopy(t *testing.T) { + original := &Transaction{ + Nonce: big.NewInt(1), + GasTipCap: big.NewInt(2000000000), + GasFeeCap: big.NewInt(3000000000), + Gas: big.NewInt(21000), + To: &Address1, + Value: big.NewInt(1000000000000000000), + Data: hexutil.Bytes{1, 2, 3, 4}, + ChainID: big.NewInt(1), + From: &Address1, + GasPerPubdata: big.NewInt(50000), + CustomSignature: hexutil.Bytes{5, 6, 7, 8}, + FactoryDeps: []hexutil.Bytes{{9, 10}, {11, 12}}, + PaymasterParams: &PaymasterParams{ + Paymaster: Address1, + PaymasterInput: hexutil.Bytes{13, 14, 15, 16}, + }, + } + + deepCopy := original.Copy() + + // Test equality of values + assert.Equal(t, original.Nonce, deepCopy.Nonce, "Nonce should be equal") + assert.Equal(t, original.GasTipCap, deepCopy.GasTipCap, "GasTipCap should be equal") + assert.Equal(t, original.GasFeeCap, deepCopy.GasFeeCap, "GasFeeCap should be equal") + assert.Equal(t, original.Gas, deepCopy.Gas, "Gas should be equal") + assert.Equal(t, original.To, deepCopy.To, "To should be equal") + assert.Equal(t, original.Value, deepCopy.Value, "Value should be equal") + assert.Equal(t, original.Data, deepCopy.Data, "Data should be equal") + assert.Equal(t, original.ChainID, deepCopy.ChainID, "ChainID should be equal") + assert.Equal(t, original.From, deepCopy.From, "From should be equal") + assert.Equal(t, original.GasPerPubdata, deepCopy.GasPerPubdata, "GasPerPubdata should be equal") + assert.Equal(t, original.CustomSignature, deepCopy.CustomSignature, "CustomSignature should be equal") + assert.Equal(t, original.FactoryDeps, deepCopy.FactoryDeps, "FactoryDeps should be equal") + assert.Equal(t, original.PaymasterParams, deepCopy.PaymasterParams, "PaymasterParams should be equal") + + // Test different references for pointer and slice fields + assert.NotSame(t, &original.Nonce, &deepCopy.Nonce, "Nonce should have different memory addresses") + assert.NotSame(t, &original.GasTipCap, &deepCopy.GasTipCap, "GasTipCap should have different memory addresses") + assert.NotSame(t, &original.GasFeeCap, &deepCopy.GasFeeCap, "GasFeeCap should have different memory addresses") + assert.NotSame(t, &original.Gas, &deepCopy.Gas, "Gas should have different memory addresses") + assert.NotSame(t, original.To, deepCopy.To, "To should have different memory addresses") + assert.NotSame(t, &original.Value, &deepCopy.Value, "Value should have different memory addresses") + assert.NotSame(t, &original.Data, &deepCopy.Data, "Data should have different memory addresses") + assert.NotSame(t, &original.ChainID, &deepCopy.ChainID, "ChainID should have different memory addresses") + assert.NotSame(t, original.From, deepCopy.From, "From should have different memory addresses") + assert.NotSame(t, &original.GasPerPubdata, &deepCopy.GasPerPubdata, "GasPerPubdata should have different memory addresses") + assert.NotSame(t, &original.CustomSignature, &deepCopy.CustomSignature, "CustomSignature should have different memory addresses") + assert.NotSame(t, &original.FactoryDeps, &deepCopy.FactoryDeps, "FactoryDeps should have different memory addresses") + assert.NotSame(t, original.PaymasterParams, deepCopy.PaymasterParams, "PaymasterParams should have different memory addresses") + + // Test deep Copy method by modifying the deepCopy + deepCopy.Nonce.Add(deepCopy.Nonce, big.NewInt(2)) + assert.NotEqual(t, original.Nonce, deepCopy.Nonce, "Modifying deepCopy's Nonce should not affect original") + + newAddress := Address2 + deepCopy.To = &newAddress + assert.NotEqual(t, original.To, deepCopy.To, "Modifying deepCopy's To should not affect original") + + deepCopy.Data[0] = 99 + assert.NotEqual(t, original.Data, deepCopy.Data, "Modifying deepCopy's Data should not affect original") + + deepCopy.FactoryDeps[0][0] = 99 + assert.NotEqual(t, original.FactoryDeps, deepCopy.FactoryDeps, "Modifying deepCopy's FactoryDeps should not affect original") + + deepCopy.PaymasterParams.PaymasterInput[0] = 99 + assert.NotEqual(t, original.PaymasterParams.PaymasterInput, deepCopy.PaymasterParams.PaymasterInput, "Modifying deepCopy's PaymasterParams should not affect original") + + // Test nil fields + nilTx := &Transaction{} + nilCopy := nilTx.Copy() + assert.Equal(t, &Transaction{ + Nonce: new(big.Int), + GasTipCap: new(big.Int), + GasFeeCap: new(big.Int), + Gas: new(big.Int), + To: nil, + Value: new(big.Int), + Data: hexutil.Bytes(nil), + ChainID: new(big.Int), + From: nil, + GasPerPubdata: new(big.Int), + CustomSignature: hexutil.Bytes(nil), + FactoryDeps: []hexutil.Bytes{}, + PaymasterParams: nil, + }, nilCopy, "Copying a Transaction with nil fields should return zero values where appropriate") + + // Test nil Transaction + var nullTx *Transaction + nullCopy := nullTx.Copy() + assert.Nil(t, nullCopy, "Copying a nil Transaction should return nil") +}