diff --git a/accounts/external/backend.go b/accounts/external/backend.go index 344042c8bf..49b5f2e934 100644 --- a/accounts/external/backend.go +++ b/accounts/external/backend.go @@ -215,7 +215,7 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio switch tx.Type() { case types.LegacyTxType, types.AccessListTxType: args.GasPrice = (*hexutil.Big)(tx.GasPrice()) - case types.DynamicFeeTxType, types.CeloDynamicFeeTxType: + case types.DynamicFeeTxType, types.CeloDynamicFeeTxType, types.CeloDenominatedTxType: args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap()) args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap()) default: diff --git a/core/state_transition.go b/core/state_transition.go index c92da7ea3b..f8e9fed008 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -182,8 +182,7 @@ type Message struct { // FeeCurrency specifies the currency for gas fees. // `nil` corresponds to Celo Gold (native currency). // All other values should correspond to ERC20 contract addresses. - FeeCurrency *common.Address - + FeeCurrency *common.Address MaxFeeInFeeCurrency *big.Int // MaxFeeInFeeCurrency is the maximum fee that can be charged in the fee currency. } @@ -213,7 +212,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In } // If baseFee provided, set gasPrice to effectiveGasPrice. if baseFee != nil { - if msg.FeeCurrency != nil { + if tx.Type() == types.CeloDynamicFeeTxType { var err error baseFee, err = exchange.ConvertGoldToCurrency(exchangeRates, msg.FeeCurrency, baseFee) if err != nil { diff --git a/core/types/celo_denominated_tx.go b/core/types/celo_denominated_tx.go new file mode 100644 index 0000000000..977397d650 --- /dev/null +++ b/core/types/celo_denominated_tx.go @@ -0,0 +1,121 @@ +package types + +import ( + "bytes" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" +) + +type CeloDenominatedTx struct { + ChainID *big.Int + Nonce uint64 + GasTipCap *big.Int + GasFeeCap *big.Int + Gas uint64 + To *common.Address `rlp:"nil"` // nil means contract creation + Value *big.Int + Data []byte + AccessList AccessList + + FeeCurrency *common.Address + MaxFeeInFeeCurrency *big.Int + + // Signature values + V *big.Int `json:"v" gencodec:"required"` + R *big.Int `json:"r" gencodec:"required"` + S *big.Int `json:"s" gencodec:"required"` +} + +// copy creates a deep copy of the transaction data and initializes all fields. +func (tx *CeloDenominatedTx) copy() TxData { + cpy := &CeloDenominatedTx{ + Nonce: tx.Nonce, + To: copyAddressPtr(tx.To), + Data: common.CopyBytes(tx.Data), + Gas: tx.Gas, + FeeCurrency: copyAddressPtr(tx.FeeCurrency), + // These are copied below. + MaxFeeInFeeCurrency: new(big.Int), + AccessList: make(AccessList, len(tx.AccessList)), + Value: new(big.Int), + ChainID: new(big.Int), + GasTipCap: new(big.Int), + GasFeeCap: new(big.Int), + V: new(big.Int), + R: new(big.Int), + S: new(big.Int), + } + if tx.MaxFeeInFeeCurrency != nil { + cpy.MaxFeeInFeeCurrency.Set(tx.MaxFeeInFeeCurrency) + } + copy(cpy.AccessList, tx.AccessList) + if tx.Value != nil { + cpy.Value.Set(tx.Value) + } + if tx.ChainID != nil { + cpy.ChainID.Set(tx.ChainID) + } + if tx.GasTipCap != nil { + cpy.GasTipCap.Set(tx.GasTipCap) + } + if tx.GasFeeCap != nil { + cpy.GasFeeCap.Set(tx.GasFeeCap) + } + if tx.V != nil { + cpy.V.Set(tx.V) + } + if tx.R != nil { + cpy.R.Set(tx.R) + } + if tx.S != nil { + cpy.S.Set(tx.S) + } + return cpy +} + +// accessors for innerTx. +func (tx *CeloDenominatedTx) txType() byte { return CeloDenominatedTxType } +func (tx *CeloDenominatedTx) chainID() *big.Int { return tx.ChainID } +func (tx *CeloDenominatedTx) accessList() AccessList { return tx.AccessList } +func (tx *CeloDenominatedTx) data() []byte { return tx.Data } +func (tx *CeloDenominatedTx) gas() uint64 { return tx.Gas } +func (tx *CeloDenominatedTx) gasFeeCap() *big.Int { return tx.GasFeeCap } +func (tx *CeloDenominatedTx) gasTipCap() *big.Int { return tx.GasTipCap } +func (tx *CeloDenominatedTx) gasPrice() *big.Int { return tx.GasFeeCap } +func (tx *CeloDenominatedTx) value() *big.Int { return tx.Value } +func (tx *CeloDenominatedTx) nonce() uint64 { return tx.Nonce } +func (tx *CeloDenominatedTx) to() *common.Address { return tx.To } +func (tx *CeloDenominatedTx) isSystemTx() bool { return false } + +func (tx *CeloDenominatedTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { + if baseFee == nil { + return dst.Set(tx.GasFeeCap) + } + tip := dst.Sub(tx.GasFeeCap, baseFee) + if tip.Cmp(tx.GasTipCap) > 0 { + tip.Set(tx.GasTipCap) + } + return tip.Add(tip, baseFee) +} + +func (tx *CeloDenominatedTx) rawSignatureValues() (v, r, s *big.Int) { + return tx.V, tx.R, tx.S +} + +func (tx *CeloDenominatedTx) setSignatureValues(chainID, v, r, s *big.Int) { + tx.ChainID, tx.V, tx.R, tx.S = chainID, v, r, s +} + +func (tx *CeloDenominatedTx) encode(b *bytes.Buffer) error { + return rlp.Encode(b, tx) +} + +func (tx *CeloDenominatedTx) decode(input []byte) error { + return rlp.DecodeBytes(input, tx) +} + +func (tx *CeloDenominatedTx) feeCurrency() *common.Address { return tx.FeeCurrency } + +func (tx *CeloDenominatedTx) maxFeeInFeeCurrency() *big.Int { return tx.MaxFeeInFeeCurrency } diff --git a/core/types/celo_dynamic_fee_tx.go b/core/types/celo_dynamic_fee_tx.go index 0ff5186b31..800addd11e 100644 --- a/core/types/celo_dynamic_fee_tx.go +++ b/core/types/celo_dynamic_fee_tx.go @@ -114,4 +114,5 @@ func (tx *CeloDynamicFeeTx) decode(input []byte) error { return rlp.DecodeBytes(input, tx) } -func (tx *CeloDynamicFeeTx) feeCurrency() *common.Address { return tx.FeeCurrency } +func (tx *CeloDynamicFeeTx) feeCurrency() *common.Address { return tx.FeeCurrency } +func (tx *CeloDynamicFeeTx) maxFeeInFeeCurrency() *big.Int { return nil } diff --git a/core/types/celo_transaction_signing.go b/core/types/celo_transaction_signing.go index 1df278929f..1dffdbe408 100644 --- a/core/types/celo_transaction_signing.go +++ b/core/types/celo_transaction_signing.go @@ -36,7 +36,7 @@ func NewCel2Signer(chainId *big.Int) Signer { } func (s cel2Signer) Sender(tx *Transaction) (common.Address, error) { - if tx.Type() != CeloDynamicFeeTxType { + if tx.Type() != CeloDynamicFeeTxType && tx.Type() != CeloDenominatedTxType { return s.londonSigner.Sender(tx) } V, R, S := tx.RawSignatureValues() @@ -55,13 +55,14 @@ func (s cel2Signer) Equal(s2 Signer) bool { } func (s cel2Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { - txdata, ok := tx.inner.(*CeloDynamicFeeTx) - if !ok { + if tx.Type() != CeloDynamicFeeTxType && tx.Type() != CeloDenominatedTxType { return s.londonSigner.SignatureValues(tx, sig) } + // Check that chain ID of tx matches the signer. We also accept ID zero here, // because it indicates that the chain ID was not specified in the tx. - if txdata.ChainID.Sign() != 0 && txdata.ChainID.Cmp(s.chainId) != 0 { + chainID := tx.inner.chainID() + if chainID.Sign() != 0 && chainID.Cmp(s.chainId) != 0 { return nil, nil, nil, ErrInvalidChainId } R, S, _ = decodeSignature(sig) @@ -88,5 +89,22 @@ func (s cel2Signer) Hash(tx *Transaction) common.Hash { tx.FeeCurrency(), }) } + if tx.Type() == CeloDenominatedTxType { + return prefixedRlpHash( + tx.Type(), + []interface{}{ + s.chainId, + tx.Nonce(), + tx.GasTipCap(), + tx.GasFeeCap(), + tx.Gas(), + tx.To(), + tx.Value(), + tx.Data(), + tx.AccessList(), + tx.FeeCurrency(), + tx.MaxFeeInFeeCurrency(), + }) + } return s.londonSigner.Hash(tx) } diff --git a/core/types/deposit_tx.go b/core/types/deposit_tx.go index 4b6c673eb9..bbfec79323 100644 --- a/core/types/deposit_tx.go +++ b/core/types/deposit_tx.go @@ -102,4 +102,5 @@ func (tx *DepositTx) decode(input []byte) error { return rlp.DecodeBytes(input, tx) } -func (tx *DepositTx) feeCurrency() *common.Address { return nil } +func (tx *DepositTx) feeCurrency() *common.Address { return nil } +func (tx *DepositTx) maxFeeInFeeCurrency() *big.Int { return nil } diff --git a/core/types/transaction.go b/core/types/transaction.go index 1c2524bd5c..6db82c6430 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -51,7 +51,8 @@ const ( DynamicFeeTxType = 0x02 BlobTxType = 0x03 // CeloDynamicFeeTxType = 0x7c old Celo tx type with gateway fee - CeloDynamicFeeTxType = 0x7b + CeloDynamicFeeTxType = 0x7b + CeloDenominatedTxType = 0x7a ) // Transaction is an Ethereum transaction. @@ -110,6 +111,7 @@ type TxData interface { // Celo specific fields feeCurrency() *common.Address + maxFeeInFeeCurrency() *big.Int } // EncodeRLP implements rlp.Encoder @@ -216,6 +218,8 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) { inner = new(DynamicFeeTx) case CeloDynamicFeeTxType: inner = new(CeloDynamicFeeTx) + case CeloDenominatedTxType: + inner = new(CeloDenominatedTx) case BlobTxType: inner = new(BlobTx) case DepositTxType: @@ -628,6 +632,16 @@ func (tx *Transaction) FeeCurrency() *common.Address { return copyAddressPtr(tx.inner.feeCurrency()) } +// MaxFeeInFeeCurrency is only used to guard against very quickly changing exchange rates. +// Txs must be discarded if MaxFeeInFeeCurrency is exceeded. +func (tx *Transaction) MaxFeeInFeeCurrency() *big.Int { + mfifc := tx.inner.maxFeeInFeeCurrency() + if mfifc == nil { + return nil + } + return new(big.Int).Set(mfifc) +} + // Transactions implements DerivableList for transactions. type Transactions []*Transaction diff --git a/core/types/tx_access_list.go b/core/types/tx_access_list.go index 6c06b86181..618b3de863 100644 --- a/core/types/tx_access_list.go +++ b/core/types/tx_access_list.go @@ -129,4 +129,5 @@ func (tx *AccessListTx) decode(input []byte) error { return rlp.DecodeBytes(input, tx) } -func (tx *AccessListTx) feeCurrency() *common.Address { return nil } +func (tx *AccessListTx) feeCurrency() *common.Address { return nil } +func (tx *AccessListTx) maxFeeInFeeCurrency() *big.Int { return nil } diff --git a/core/types/tx_blob.go b/core/types/tx_blob.go index 012190dbdb..728cb2b952 100644 --- a/core/types/tx_blob.go +++ b/core/types/tx_blob.go @@ -238,4 +238,5 @@ func (tx *BlobTx) decode(input []byte) error { return nil } -func (tx *BlobTx) feeCurrency() *common.Address { return nil } +func (tx *BlobTx) feeCurrency() *common.Address { return nil } +func (tx *BlobTx) maxFeeInFeeCurrency() *big.Int { return nil } diff --git a/core/types/tx_dynamic_fee.go b/core/types/tx_dynamic_fee.go index 1b559b08ac..e23accb299 100644 --- a/core/types/tx_dynamic_fee.go +++ b/core/types/tx_dynamic_fee.go @@ -125,4 +125,5 @@ func (tx *DynamicFeeTx) decode(input []byte) error { return rlp.DecodeBytes(input, tx) } -func (tx *DynamicFeeTx) feeCurrency() *common.Address { return nil } +func (tx *DynamicFeeTx) feeCurrency() *common.Address { return nil } +func (tx *DynamicFeeTx) maxFeeInFeeCurrency() *big.Int { return nil } diff --git a/core/types/tx_legacy.go b/core/types/tx_legacy.go index a66dff8397..d1489d5292 100644 --- a/core/types/tx_legacy.go +++ b/core/types/tx_legacy.go @@ -125,4 +125,5 @@ func (tx *LegacyTx) decode([]byte) error { panic("decode called on LegacyTx)") } -func (tx *LegacyTx) feeCurrency() *common.Address { return nil } +func (tx *LegacyTx) feeCurrency() *common.Address { return nil } +func (tx *LegacyTx) maxFeeInFeeCurrency() *big.Int { return nil } diff --git a/internal/celoapi/backend.go b/internal/celoapi/backend.go index dcc14b49a2..3132dd52e1 100644 --- a/internal/celoapi/backend.go +++ b/internal/celoapi/backend.go @@ -60,18 +60,18 @@ func (b *CeloAPIBackend) GetExchangeRates(ctx context.Context, blockNumOrHash rp return er, nil } -func (b *CeloAPIBackend) ConvertToCurrency(ctx context.Context, blockNumOrHash rpc.BlockNumberOrHash, value *big.Int, fromFeeCurrency *common.Address) (*big.Int, error) { +func (b *CeloAPIBackend) ConvertToCurrency(ctx context.Context, blockNumOrHash rpc.BlockNumberOrHash, goldAmount *big.Int, toFeeCurrency *common.Address) (*big.Int, error) { er, err := b.GetExchangeRates(ctx, blockNumOrHash) if err != nil { return nil, err } - return exchange.ConvertGoldToCurrency(er, fromFeeCurrency, value) + return exchange.ConvertGoldToCurrency(er, toFeeCurrency, goldAmount) } -func (b *CeloAPIBackend) ConvertToGold(ctx context.Context, blockNumOrHash rpc.BlockNumberOrHash, value *big.Int, toFeeCurrency *common.Address) (*big.Int, error) { +func (b *CeloAPIBackend) ConvertToGold(ctx context.Context, blockNumOrHash rpc.BlockNumberOrHash, currencyAmount *big.Int, fromFeeCurrency *common.Address) (*big.Int, error) { er, err := b.GetExchangeRates(ctx, blockNumOrHash) if err != nil { return nil, err } - return exchange.ConvertCurrencyToGold(er, value, toFeeCurrency) + return exchange.ConvertCurrencyToGold(er, currencyAmount, fromFeeCurrency) } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 2bc7b8de49..e67fee25f3 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1477,7 +1477,8 @@ type RPCTransaction struct { DepositReceiptVersion *hexutil.Uint64 `json:"depositReceiptVersion,omitempty"` // Celo - FeeCurrency *common.Address `json:"feeCurrency,omitempty"` + FeeCurrency *common.Address `json:"feeCurrency,omitempty"` + MaxFeeInFeeCurrency *hexutil.Big `json:"maxFeeInFeeCurrency,omitempty"` } // newRPCTransaction returns a transaction that will serialize to the RPC @@ -1500,7 +1501,8 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber R: (*hexutil.Big)(r), S: (*hexutil.Big)(s), // Celo - FeeCurrency: tx.FeeCurrency(), + FeeCurrency: tx.FeeCurrency(), + MaxFeeInFeeCurrency: (*hexutil.Big)(tx.MaxFeeInFeeCurrency()), } if blockHash != (common.Hash{}) { result.BlockHash = &blockHash @@ -1542,7 +1544,7 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber result.ChainID = (*hexutil.Big)(tx.ChainId()) result.YParity = &yparity - case types.DynamicFeeTxType, types.CeloDynamicFeeTxType: + case types.DynamicFeeTxType, types.CeloDynamicFeeTxType, types.CeloDenominatedTxType: al := tx.AccessList() yparity := hexutil.Uint64(v.Sign()) result.Accesses = &al diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index b561782ba3..34148b6959 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -75,12 +75,10 @@ type TransactionArgs struct { // This configures whether blobs are allowed to be passed. blobSidecarAllowed bool - // Celo specific - // CIP-64, CIP-66 - FeeCurrency *common.Address `json:"feeCurrency,omitempty"` - // CIP-66 - MaxFeeInFeeCurrency *hexutil.Big `json:"maxFeeInFeeCurrency,omitempty"` + // Celo specific: + FeeCurrency *common.Address `json:"feeCurrency,omitempty"` // CIP-64, CIP-66 + MaxFeeInFeeCurrency *hexutil.Big `json:"maxFeeInFeeCurrency,omitempty"` // CIP-66 } // from retrieves the transaction sender address. @@ -211,6 +209,10 @@ func (args *TransactionArgs) setFeeDefaults(ctx context.Context, b CeloBackend) // other tx values. See https://github.com/ethereum/go-ethereum/pull/23274 // for more information. eip1559ParamsSet := args.MaxFeePerGas != nil && args.MaxPriorityFeePerGas != nil + + if args.MaxFeeInFeeCurrency != nil && args.FeeCurrency == nil { + return errors.New("feeCurrency must be set when maxFeeInFeeCurrency is given") + } // Sanity check the EIP-1559 fee parameters if present. if args.GasPrice == nil && eip1559ParamsSet { if args.MaxFeePerGas.ToInt().Sign() == 0 { @@ -247,17 +249,6 @@ func (args *TransactionArgs) setFeeDefaults(ctx context.Context, b CeloBackend) if err != nil { return err } - if args.IsFeeCurrencyDenominated() { - price, err = b.ConvertToCurrency( - ctx, - rpc.BlockNumberOrHashWithHash(head.Hash(), false), - price, - args.FeeCurrency, - ) - if err != nil { - return fmt.Errorf("can't convert suggested gasTipCap to fee-currency: %w", err) - } - } args.GasPrice = (*hexutil.Big)(price) } return nil @@ -447,10 +438,11 @@ func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int, ex gas = globalGasCap } var ( - gasPrice *big.Int - gasFeeCap *big.Int - gasTipCap *big.Int - blobFeeCap *big.Int + gasPrice *big.Int + gasFeeCap *big.Int + gasTipCap *big.Int + blobFeeCap *big.Int + maxFeeInFeeCurrency *big.Int ) if baseFee == nil { // If there's no basefee, then it must be a non-1559 execution @@ -503,6 +495,9 @@ func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int, ex if args.AccessList != nil { accessList = *args.AccessList } + if args.MaxFeeInFeeCurrency != nil { + maxFeeInFeeCurrency = args.MaxFeeInFeeCurrency.ToInt() + } msg := &core.Message{ From: addr, To: args.To, @@ -516,7 +511,9 @@ func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int, ex BlobGasFeeCap: blobFeeCap, BlobHashes: args.BlobHashes, SkipAccountChecks: true, - FeeCurrency: args.FeeCurrency, + // Celo specific: + FeeCurrency: args.FeeCurrency, + MaxFeeInFeeCurrency: maxFeeInFeeCurrency, } return msg, nil } @@ -557,16 +554,47 @@ func (args *TransactionArgs) toTransaction() *types.Transaction { if args.AccessList != nil { al = *args.AccessList } - data = &types.DynamicFeeTx{ - To: args.To, - ChainID: (*big.Int)(args.ChainID), - Nonce: uint64(*args.Nonce), - Gas: uint64(*args.Gas), - GasFeeCap: (*big.Int)(args.MaxFeePerGas), - GasTipCap: (*big.Int)(args.MaxPriorityFeePerGas), - Value: (*big.Int)(args.Value), - Data: args.data(), - AccessList: al, + if args.FeeCurrency != nil { + if args.IsFeeCurrencyDenominated() { + data = &types.CeloDynamicFeeTx{ + To: args.To, + ChainID: (*big.Int)(args.ChainID), + Nonce: uint64(*args.Nonce), + Gas: uint64(*args.Gas), + GasFeeCap: (*big.Int)(args.MaxFeePerGas), + GasTipCap: (*big.Int)(args.MaxPriorityFeePerGas), + Value: (*big.Int)(args.Value), + Data: args.data(), + AccessList: al, + FeeCurrency: args.FeeCurrency, + } + } else { + data = &types.CeloDenominatedTx{ + To: args.To, + ChainID: (*big.Int)(args.ChainID), + Nonce: uint64(*args.Nonce), + Gas: uint64(*args.Gas), + GasFeeCap: (*big.Int)(args.MaxFeePerGas), + GasTipCap: (*big.Int)(args.MaxPriorityFeePerGas), + Value: (*big.Int)(args.Value), + Data: args.data(), + AccessList: al, + FeeCurrency: args.FeeCurrency, + MaxFeeInFeeCurrency: (*big.Int)(args.MaxFeeInFeeCurrency), + } + } + } else { + data = &types.DynamicFeeTx{ + To: args.To, + ChainID: (*big.Int)(args.ChainID), + Nonce: uint64(*args.Nonce), + Gas: uint64(*args.Gas), + GasFeeCap: (*big.Int)(args.MaxFeePerGas), + GasTipCap: (*big.Int)(args.MaxPriorityFeePerGas), + Value: (*big.Int)(args.Value), + Data: args.data(), + AccessList: al, + } } case args.AccessList != nil: diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go index 8aebe1962f..900299b987 100644 --- a/internal/ethapi/transaction_args_test.go +++ b/internal/ethapi/transaction_args_test.go @@ -51,11 +51,14 @@ func TestSetFeeDefaults(t *testing.T) { } var ( - b = newCeloBackendMock() - zero = (*hexutil.Big)(big.NewInt(0)) - fortytwo = (*hexutil.Big)(big.NewInt(42)) - maxFee = (*hexutil.Big)(new(big.Int).Add(new(big.Int).Mul(b.current.BaseFee, big.NewInt(2)), fortytwo.ToInt())) - al = &types.AccessList{types.AccessTuple{Address: common.Address{0xaa}, StorageKeys: []common.Hash{{0x01}}}} + b = newCeloBackendMock() + zero = (*hexutil.Big)(big.NewInt(0)) + fortytwo = (*hexutil.Big)(big.NewInt(42)) + maxFee = (*hexutil.Big)(new(big.Int).Add(new(big.Int).Mul(b.current.BaseFee, big.NewInt(2)), fortytwo.ToInt())) + al = &types.AccessList{types.AccessTuple{Address: common.Address{0xaa}, StorageKeys: []common.Hash{{0x01}}}} + feeCurrency = common.BigToAddress(big.NewInt(42)) + eightyfour = (*hexutil.Big)(big.NewInt(84)) + doubleMaxFee = (*hexutil.Big)(new(big.Int).Mul(maxFee.ToInt(), big.NewInt(2))) ) tests := []test{ @@ -228,6 +231,37 @@ func TestSetFeeDefaults(t *testing.T) { &TransactionArgs{BlobHashes: []common.Hash{}, BlobFeeCap: (*hexutil.Big)(big.NewInt(4)), MaxFeePerGas: maxFee, MaxPriorityFeePerGas: fortytwo}, nil, }, + // CIP-64 + { + "Fee-currency denominated tx, set maxPriorityFeePerGas in converted valued", + "cancun", + &TransactionArgs{MaxFeePerGas: doubleMaxFee, FeeCurrency: &feeCurrency}, + // maxPriorityFeePerGas is double in feeCurrency + &TransactionArgs{MaxFeePerGas: doubleMaxFee, MaxPriorityFeePerGas: eightyfour, FeeCurrency: &feeCurrency}, + nil, + }, + { + "Fee-currency denominated tx, set maxFeePerGas in converted valued", + "cancun", + &TransactionArgs{MaxPriorityFeePerGas: eightyfour, FeeCurrency: &feeCurrency}, + &TransactionArgs{MaxFeePerGas: doubleMaxFee, MaxPriorityFeePerGas: eightyfour, FeeCurrency: &feeCurrency}, + nil, + }, + // CIP-66 + { + "CIP-66 transaction, maxPriorityFeePerGas gets set in non-converted value", + "cancun", + &TransactionArgs{MaxFeePerGas: maxFee, MaxFeeInFeeCurrency: fortytwo, FeeCurrency: &feeCurrency}, + &TransactionArgs{MaxFeePerGas: maxFee, MaxPriorityFeePerGas: fortytwo, MaxFeeInFeeCurrency: fortytwo, FeeCurrency: &feeCurrency}, + nil, + }, + { + "set maxFeeInFeeCurrency without feeCurrency", + "cancun", + &TransactionArgs{MaxFeePerGas: maxFee, MaxPriorityFeePerGas: fortytwo, MaxFeeInFeeCurrency: fortytwo}, + nil, + errors.New("feeCurrency must be set when maxFeeInFeeCurrency is given"), + }, } ctx := context.Background() @@ -271,18 +305,22 @@ func (c *celoBackendMock) GetFeeBalance(ctx context.Context, blockNumOrHash rpc. func (c *celoBackendMock) GetExchangeRates(ctx context.Context, blockNumOrHash rpc.BlockNumberOrHash) (common.ExchangeRates, error) { var er common.ExchangeRates - // Celo specific backend features are currently not tested + // This Celo specific backend features are currently not tested return er, errCeloNotImplemented } func (c *celoBackendMock) ConvertToCurrency(ctx context.Context, blockNumOrHash rpc.BlockNumberOrHash, value *big.Int, fromFeeCurrency *common.Address) (*big.Int, error) { - // Celo specific backend features are currently not tested - return nil, errCeloNotImplemented + if fromFeeCurrency == nil { + return value, nil + } + return new(big.Int).Mul(value, big.NewInt(2)), nil } func (c *celoBackendMock) ConvertToGold(ctx context.Context, blockNumOrHash rpc.BlockNumberOrHash, value *big.Int, toFeeCurrency *common.Address) (*big.Int, error) { - // Celo specific backend features are currently not tested - return nil, errCeloNotImplemented + if toFeeCurrency == nil { + return value, nil + } + return new(big.Int).Div(value, big.NewInt(2)), nil } type backendMock struct {