Skip to content

Commit

Permalink
CIP-64 / CIP-66 compatible TransactionArgs (#123)
Browse files Browse the repository at this point in the history
Fixes #109

Add CIP-66 transaction type
This introduces the CeloDenominatedTx type, which will when implemented allow for the fee-currency to be denominated in cel2 native token.

Make the TransactionArgs CIP-64/CIP-66 compatible
The TransactionArgs are used in some API endpoints to fill incomplete fields of passed in transactions and then
convert this internally to an EVM-message or Transaction. This PR adds code that distinguishes some combination of passed in fields for the TransactionArgs between CIP-64 and CIP-66 transactions in order to create the concrete internal transaction type. The filling of missing required fields now considers wether the transaction is Celo-denominated or not.
Additionally, the new MaxFeeInFeeCurrency field is passed along to the internal transaction representations

Included commits:

* Format comments in TransactionArgs fields

* Add CIP-66 tx type (Celo denominated)

Co-authored-by: bandu

* Fix nil-deref in CIP-66 Transaction getter

* Fix required RLP field for fee-currency in CIP66

* Fix initialize MaxFeePerFeeCurrency value upon copy

* Convert TransactionArgs to CIP-64/66 transaction

Closes #109

* Add MaxFeeInFeeCurrency to EVM Message

* Fix CIP-64/66 related sanity checks

The fee-currency conversion pre-London (legacy-tx) was unneccessary,
since we don't allow celo transactions here.

Additionally some sanity-checks regarding Celo related fields were
missing

* Add tests for CIP-64/66 compatible TransactionArgs

* Fix call-arg naming for currency conversion

* Fix comments and formatting
  • Loading branch information
ezdac authored and karlb committed Jul 12, 2024
1 parent cfdf811 commit 0670e4e
Show file tree
Hide file tree
Showing 15 changed files with 288 additions and 62 deletions.
2 changes: 1 addition & 1 deletion accounts/external/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 1 addition & 2 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,7 @@ type Message struct {
// FeeCurrency specifies the currency for gas fees.
// `nil` corresponds to CELO (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.
}

Expand Down
121 changes: 121 additions & 0 deletions core/types/celo_denominated_tx.go
Original file line number Diff line number Diff line change
@@ -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 }
3 changes: 2 additions & 1 deletion core/types/celo_dynamic_fee_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
26 changes: 22 additions & 4 deletions core/types/celo_transaction_signing.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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)
Expand All @@ -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)
}
3 changes: 2 additions & 1 deletion core/types/deposit_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
16 changes: 15 additions & 1 deletion core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -110,6 +111,7 @@ type TxData interface {

// Celo specific fields
feeCurrency() *common.Address
maxFeeInFeeCurrency() *big.Int
}

// EncodeRLP implements rlp.Encoder
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion core/types/tx_access_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
3 changes: 2 additions & 1 deletion core/types/tx_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
3 changes: 2 additions & 1 deletion core/types/tx_dynamic_fee.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
3 changes: 2 additions & 1 deletion core/types/tx_legacy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
8 changes: 4 additions & 4 deletions internal/celoapi/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, celoAmount *big.Int, toFeeCurrency *common.Address) (*big.Int, error) {
er, err := b.GetExchangeRates(ctx, blockNumOrHash)
if err != nil {
return nil, err
}
return exchange.ConvertCeloToCurrency(er, fromFeeCurrency, value)
return exchange.ConvertCeloToCurrency(er, toFeeCurrency, celoAmount)
}

func (b *CeloAPIBackend) ConvertToCelo(ctx context.Context, blockNumOrHash rpc.BlockNumberOrHash, value *big.Int, toFeeCurrency *common.Address) (*big.Int, error) {
func (b *CeloAPIBackend) ConvertToCelo(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.ConvertCurrencyToCelo(er, value, toFeeCurrency)
return exchange.ConvertCurrencyToCelo(er, currencyAmount, fromFeeCurrency)
}
8 changes: 5 additions & 3 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 0670e4e

Please sign in to comment.