Skip to content

Commit

Permalink
feat(types): improve the design of L2 transaction
Browse files Browse the repository at this point in the history
BREAKING CHANGE: `Transaction712` has been replaced by `Transaction`, an improved version
that offers a more convenient API and greater extensibility.
  • Loading branch information
danijelTxFusion committed Sep 29, 2024
1 parent f310974 commit c2aa297
Show file tree
Hide file tree
Showing 13 changed files with 692 additions and 516 deletions.
18 changes: 2 additions & 16 deletions accounts/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down
126 changes: 53 additions & 73 deletions accounts/smart_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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,
})
}

Expand All @@ -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,
})
}

Expand All @@ -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,
})
}

Expand All @@ -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,
})
}

Expand Down
17 changes: 10 additions & 7 deletions accounts/smart_account_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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")
Expand Down
79 changes: 24 additions & 55 deletions accounts/smart_account_utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
Loading

0 comments on commit c2aa297

Please sign in to comment.