diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 13587331be..5e9be423fe 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -670,6 +670,7 @@ type storedReceiptRLP struct { PostStateOrStatus []byte CumulativeGasUsed uint64 Logs []*types.LogForStorage + FeeInFeeCurrency *big.Int } // ReceiptLogs is a barebone version of ReceiptForStorage which only keeps diff --git a/core/state_processor.go b/core/state_processor.go index c0fa3663d5..37094e6b30 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -172,6 +172,7 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, gp *GasPool } else { receipt.Status = types.ReceiptStatusSuccessful } + receipt.FeeInFeeCurrency = result.FeeInFeeCurrency receipt.TxHash = tx.Hash() receipt.GasUsed = result.UsedGas diff --git a/core/state_transition.go b/core/state_transition.go index 3441ac9b1a..2bc146c71f 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -60,21 +60,22 @@ import ( // 5. Run Script section // 6. Derive new state root type StateTransition struct { - gp *GasPool - msg Message - gas uint64 - gasPrice *big.Int - gasFeeCap *big.Int - gasTipCap *big.Int - initialGas uint64 - value *big.Int - data []byte - state vm.StateDB - evm *vm.EVM - vmRunner vm.EVMRunner - gasPriceMinimum *big.Int - sysCtx *SysContractCallCtx - erc20FeeDebited *big.Int + gp *GasPool + msg Message + gas uint64 + gasPrice *big.Int + gasFeeCap *big.Int + gasTipCap *big.Int + initialGas uint64 + value *big.Int + data []byte + state vm.StateDB + evm *vm.EVM + vmRunner vm.EVMRunner + gasPriceMinimum *big.Int + sysCtx *SysContractCallCtx + erc20FeeDebited *big.Int // Total debited in erc20 gas currencies + feeInFeeCurrency *big.Int // total fee paid (debited - credited) in fee currency for CELO deno txs } // Message represents a message sent to a contract. @@ -118,9 +119,10 @@ func CheckEthCompatibility(msg Message) error { // ExecutionResult includes all output after executing given evm // message no matter the execution itself is successful or not. type ExecutionResult struct { - UsedGas uint64 // Total used gas but include the refunded gas - Err error // Any error encountered during the execution(listed in core/vm/errors.go) - ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode) + UsedGas uint64 // Total used gas but include the refunded gas + Err error // Any error encountered during the execution(listed in core/vm/errors.go) + ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode) + FeeInFeeCurrency *big.Int // Fee paid (debit - credit) on CELO denominated txs } // Unwrap returns the internal evm error which allows us for further @@ -558,9 +560,10 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { return nil, err } return &ExecutionResult{ - UsedGas: st.gasUsed(), - Err: vmerr, - ReturnData: ret, + UsedGas: st.gasUsed(), + Err: vmerr, + ReturnData: ret, + FeeInFeeCurrency: st.feeInFeeCurrency, }, nil } @@ -602,6 +605,8 @@ func (st *StateTransition) creditTxFees(feeCurrencyRate *currency.ExchangeRate) refund.Sub(st.erc20FeeDebited, totalTxFee) // refund = debited - tip - basefee // No need to exchange gateway fee since it's it's deprecated on G fork, // and MaxFeeInFeeCurrency can only be present in H fork (which implies G fork) + // Set receipt field + st.feeInFeeCurrency = totalTxFee } feeCurrency := st.msg.FeeCurrency() diff --git a/core/types/celo_denominated_tx.go b/core/types/celo_denominated_tx.go index 140e7b8b65..66c9541f48 100644 --- a/core/types/celo_denominated_tx.go +++ b/core/types/celo_denominated_tx.go @@ -1,16 +1,11 @@ package types import ( - "errors" "math/big" "github.com/celo-org/celo-blockchain/common" ) -var ( - ErrNonCeloDenominated error = errors.New("Tx not CeloDenominated") -) - type CeloDenominatedTx struct { ChainID *big.Int Nonce uint64 @@ -48,6 +43,9 @@ func (tx *CeloDenominatedTx) copy() TxData { R: new(big.Int), S: new(big.Int), } + if tx.MaxFeeInFeeCurrency != nil { + cpy.MaxFeeInFeeCurrency = new(big.Int).Set(tx.MaxFeeInFeeCurrency) + } copy(cpy.AccessList, tx.AccessList) if tx.Value != nil { cpy.Value.Set(tx.Value) @@ -76,7 +74,6 @@ func (tx *CeloDenominatedTx) copy() TxData { // accessors for innerTx. func (tx *CeloDenominatedTx) txType() byte { return CeloDenominatedTxType } func (tx *CeloDenominatedTx) chainID() *big.Int { return tx.ChainID } -func (tx *CeloDenominatedTx) protected() bool { return true } func (tx *CeloDenominatedTx) accessList() AccessList { return tx.AccessList } func (tx *CeloDenominatedTx) data() []byte { return tx.Data } func (tx *CeloDenominatedTx) gas() uint64 { return tx.Gas } diff --git a/core/types/gen_header_json.go b/core/types/gen_header_json.go index 9bae389b60..c7cd552e7b 100644 --- a/core/types/gen_header_json.go +++ b/core/types/gen_header_json.go @@ -137,8 +137,5 @@ func (h *Header) UnmarshalJSON(input []byte) error { if dec.BaseFee != nil { h.BaseFee = (*big.Int)(dec.BaseFee) } - if dec.BaseFee != nil { - h.BaseFee = (*big.Int)(dec.BaseFee) - } return nil } diff --git a/core/types/gen_receipt_json.go b/core/types/gen_receipt_json.go index a0bcef2a62..abc394d0f0 100644 --- a/core/types/gen_receipt_json.go +++ b/core/types/gen_receipt_json.go @@ -22,6 +22,7 @@ func (r Receipt) MarshalJSON() ([]byte, error) { CumulativeGasUsed hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"` Bloom Bloom `json:"logsBloom" gencodec:"required"` Logs []*Log `json:"logs" gencodec:"required"` + FeeInFeeCurrency *hexutil.Big `json:"feeInFeeCurrency"` TxHash common.Hash `json:"transactionHash" gencodec:"required"` ContractAddress common.Address `json:"contractAddress"` GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` @@ -36,6 +37,7 @@ func (r Receipt) MarshalJSON() ([]byte, error) { enc.CumulativeGasUsed = hexutil.Uint64(r.CumulativeGasUsed) enc.Bloom = r.Bloom enc.Logs = r.Logs + enc.FeeInFeeCurrency = (*hexutil.Big)(r.FeeInFeeCurrency) enc.TxHash = r.TxHash enc.ContractAddress = r.ContractAddress enc.GasUsed = hexutil.Uint64(r.GasUsed) @@ -54,6 +56,7 @@ func (r *Receipt) UnmarshalJSON(input []byte) error { CumulativeGasUsed *hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"` Bloom *Bloom `json:"logsBloom" gencodec:"required"` Logs []*Log `json:"logs" gencodec:"required"` + FeeInFeeCurrency *hexutil.Big `json:"feeInFeeCurrency"` TxHash *common.Hash `json:"transactionHash" gencodec:"required"` ContractAddress *common.Address `json:"contractAddress"` GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` @@ -86,6 +89,9 @@ func (r *Receipt) UnmarshalJSON(input []byte) error { return errors.New("missing required field 'logs' for Receipt") } r.Logs = dec.Logs + if dec.FeeInFeeCurrency != nil { + r.FeeInFeeCurrency = (*big.Int)(dec.FeeInFeeCurrency) + } if dec.TxHash == nil { return errors.New("missing required field 'transactionHash' for Receipt") } diff --git a/core/types/receipt.go b/core/types/receipt.go index 2560e1fb89..38a34cc53e 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -70,6 +70,9 @@ type Receipt struct { BlockHash common.Hash `json:"blockHash,omitempty"` BlockNumber *big.Int `json:"blockNumber,omitempty"` TransactionIndex uint `json:"transactionIndex"` + + FeeInFeeCurrency *big.Int `json:"feeInFeeCurrency,omitempty"` // CIP-66 receipt field + } type receiptMarshaling struct { @@ -80,6 +83,7 @@ type receiptMarshaling struct { GasUsed hexutil.Uint64 BlockNumber *hexutil.Big TransactionIndex hexutil.Uint + FeeInFeeCurrency *hexutil.Big } // receiptRLP is the consensus encoding of a receipt. @@ -90,11 +94,27 @@ type receiptRLP struct { Logs []*Log } +type celoDenominatedReceiptRLP struct { + PostStateOrStatus []byte + CumulativeGasUsed uint64 + Bloom Bloom + Logs []*Log + FeeInFeeCurrency *big.Int +} + // storedReceiptRLP is the storage encoding of a receipt. type storedReceiptRLP struct { PostStateOrStatus []byte CumulativeGasUsed uint64 Logs []*LogForStorage + FeeInFeeCurrency *big.Int +} + +// storedPreCIP66ReceiptRLP is the storage encoding of a receipt before adding the FeeInFeeCurrency field +type storedPreCIP66ReceiptRLP struct { + PostStateOrStatus []byte + CumulativeGasUsed uint64 + Logs []*LogForStorage } // v4StoredReceiptRLP is the storage encoding of a receipt used in database version 4. @@ -145,7 +165,13 @@ func (r *Receipt) EncodeRLP(w io.Writer) error { defer encodeBufferPool.Put(buf) buf.Reset() buf.WriteByte(r.Type) - if err := rlp.Encode(buf, data); err != nil { + var d interface{} + if r.Type == CeloDenominatedTxType { + d = &celoDenominatedReceiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs, r.FeeInFeeCurrency} + } else { + d = data + } + if err := rlp.Encode(buf, d); err != nil { return err } return rlp.Encode(w, buf.Bytes()) @@ -176,12 +202,18 @@ func (r *Receipt) DecodeRLP(s *rlp.Stream) error { return errEmptyTypedReceipt } r.Type = b[0] - if r.Type == AccessListTxType || r.Type == DynamicFeeTxType || r.Type == CeloDynamicFeeTxType || r.Type == CeloDynamicFeeTxV2Type || r.Type == CeloDenominatedTxType { + if r.Type == AccessListTxType || r.Type == DynamicFeeTxType || r.Type == CeloDynamicFeeTxType || r.Type == CeloDynamicFeeTxV2Type { var dec receiptRLP if err := rlp.DecodeBytes(b[1:], &dec); err != nil { return err } return r.setFromRLP(dec) + } else if r.Type == CeloDenominatedTxType { + var dec celoDenominatedReceiptRLP + if err := rlp.DecodeBytes(b[1:], &dec); err != nil { + return err + } + return r.setFromCeloDenominatedRLP(dec) } return ErrTxTypeNotSupported default: @@ -194,6 +226,11 @@ func (r *Receipt) setFromRLP(data receiptRLP) error { return r.setStatus(data.PostStateOrStatus) } +func (r *Receipt) setFromCeloDenominatedRLP(data celoDenominatedReceiptRLP) error { + r.CumulativeGasUsed, r.Bloom, r.Logs, r.FeeInFeeCurrency = data.CumulativeGasUsed, data.Bloom, data.Logs, data.FeeInFeeCurrency + return r.setStatus(data.PostStateOrStatus) +} + func (r *Receipt) setStatus(postStateOrStatus []byte) error { switch { case bytes.Equal(postStateOrStatus, receiptStatusSuccessfulRLP): @@ -226,6 +263,9 @@ func (r *Receipt) Size() common.StorageSize { for _, log := range r.Logs { size += common.StorageSize(len(log.Topics)*common.HashLength + len(log.Data)) } + if r.FeeInFeeCurrency != nil { + size += common.StorageSize(r.FeeInFeeCurrency.BitLen() / 8) + } return size } @@ -240,6 +280,7 @@ func (r *ReceiptForStorage) EncodeRLP(w io.Writer) error { PostStateOrStatus: (*Receipt)(r).statusEncoding(), CumulativeGasUsed: r.CumulativeGasUsed, Logs: make([]*LogForStorage, len(r.Logs)), + FeeInFeeCurrency: r.FeeInFeeCurrency, } for i, log := range r.Logs { enc.Logs[i] = (*LogForStorage)(log) @@ -261,6 +302,9 @@ func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error { if err := decodeStoredReceiptRLP(r, blob); err == nil { return nil } + if err := decodePreCIP66ReceiptRLP(r, blob); err == nil { + return nil + } if err := decodeV3StoredReceiptRLP(r, blob); err == nil { return nil } @@ -282,7 +326,27 @@ func decodeStoredReceiptRLP(r *ReceiptForStorage, blob []byte) error { r.Logs[i] = (*Log)(log) } r.Bloom = CreateBloom(Receipts{(*Receipt)(r)}) + // rlp encodes the nil value as a zero instance + if stored.FeeInFeeCurrency != nil && stored.FeeInFeeCurrency.Cmp(common.Big0) != 0 { + r.FeeInFeeCurrency = stored.FeeInFeeCurrency + } + return nil +} +func decodePreCIP66ReceiptRLP(r *ReceiptForStorage, blob []byte) error { + var stored storedPreCIP66ReceiptRLP + if err := rlp.DecodeBytes(blob, &stored); err != nil { + return err + } + if err := (*Receipt)(r).setStatus(stored.PostStateOrStatus); err != nil { + return err + } + r.CumulativeGasUsed = stored.CumulativeGasUsed + r.Logs = make([]*Log, len(stored.Logs)) + for i, log := range stored.Logs { + r.Logs[i] = (*Log)(log) + } + r.Bloom = CreateBloom(Receipts{(*Receipt)(r)}) return nil } @@ -354,7 +418,8 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) { rlp.Encode(w, data) case CeloDenominatedTxType: w.WriteByte(CeloDenominatedTxType) - rlp.Encode(w, data) + cip66data := &celoDenominatedReceiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs, r.FeeInFeeCurrency} + rlp.Encode(w, cip66data) default: // For unsupported types, write nothing. Since this is for // DeriveSha, the error will be caught matching the derived hash @@ -366,7 +431,6 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) { // data and contextual infos like containing block and transactions. func (r Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, number uint64, txs Transactions) error { signer := MakeSigner(config, new(big.Int).SetUint64(number)) - logIndex := uint(0) if len(txs) != len(r) { // The receipts may include an additional "block finalization" receipt (only IBFT) diff --git a/core/types/transaction.go b/core/types/transaction.go index ebec066f06..abd8be1f30 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -487,7 +487,13 @@ func (tx *Transaction) GatewaySet() bool { func (tx *Transaction) EthCompatible() bool { return tx.inner.ethCompatible() } // MaxFeeInFeeCurrency returns the max fee in the fee_currency for celo denominated txs. -func (tx *Transaction) MaxFeeInFeeCurrency() *big.Int { return tx.inner.maxFeeInFeeCurrency() } +func (tx *Transaction) MaxFeeInFeeCurrency() *big.Int { + maxFee := tx.inner.maxFeeInFeeCurrency() + if maxFee == nil { + return nil + } + return new(big.Int).Set(maxFee) +} // Fee calculates the fess paid by the transaction include the gateway fee. func (tx *Transaction) Fee() *big.Int { diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index 30151578cf..ea93d4ed27 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -40,6 +40,8 @@ type sigCache struct { func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer { var signer Signer switch { + case config.IsHFork(blockNumber): + signer = NewHForkSigner(config.ChainID) case config.IsGingerbreadP2(blockNumber): signer = NewGingerbreadSigner(config.ChainID) case config.IsEspresso(blockNumber): @@ -63,6 +65,9 @@ func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer { // have the current block number available, use MakeSigner instead. func LatestSigner(config *params.ChainConfig) Signer { if config.ChainID != nil { + if config.HForkBlock != nil { + return NewHForkSigner(config.ChainID) + } if config.GingerbreadP2Block != nil { return NewGingerbreadSigner(config.ChainID) } diff --git a/e2e_test/e2e_test.go b/e2e_test/e2e_test.go index fc73486ec6..7c24415297 100644 --- a/e2e_test/e2e_test.go +++ b/e2e_test/e2e_test.go @@ -44,8 +44,8 @@ func init() { // network to process the transaction. func TestSendCelo(t *testing.T) { ac := test.AccountConfig(3, 2) - gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + hforkBlock := common.Big0 + gc, ec, err := test.BuildConfig(ac, hforkBlock) require.NoError(t, err) network, shutdown, err := test.NewNetwork(ac, gc, ec) require.NoError(t, err) @@ -70,8 +70,8 @@ func TestSendCelo(t *testing.T) { // and can be useful for debugging these traces. func TestTraceSendCeloViaGoldToken(t *testing.T) { ac := test.AccountConfig(3, 2) - gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + hforkBlock := common.Big0 + gc, ec, err := test.BuildConfig(ac, hforkBlock) require.NoError(t, err) network, shutdown, err := test.NewNetwork(ac, gc, ec) require.NoError(t, err) @@ -107,8 +107,8 @@ func TestTraceSendCeloViaGoldToken(t *testing.T) { // Use the callTracer to trace a native CELO transfer. func TestCallTraceTransactionNativeTransfer(t *testing.T) { ac := test.AccountConfig(1, 2) - gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + hforkBlock := common.Big0 + gc, ec, err := test.BuildConfig(ac, hforkBlock) require.NoError(t, err) network, shutdown, err := test.NewNetwork(ac, gc, ec) require.NoError(t, err) @@ -145,8 +145,8 @@ func TestCallTraceTransactionNativeTransfer(t *testing.T) { // Use the prestateTracer to trace a native CELO transfer. func TestPrestateTransactionNativeTransfer(t *testing.T) { ac := test.AccountConfig(1, 2) - gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + hforkBlock := common.Big0 + gc, ec, err := test.BuildConfig(ac, hforkBlock) require.NoError(t, err) network, shutdown, err := test.NewNetwork(ac, gc, ec) require.NoError(t, err) @@ -183,8 +183,8 @@ func TestSingleNodeNetworkManyTxs(t *testing.T) { iterations := 5 txsPerIteration := 5 ac := test.AccountConfig(1, 1) - gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + hforkBlock := common.Big0 + gc, ec, err := test.BuildConfig(ac, hforkBlock) require.NoError(t, err) gc.Istanbul.Epoch = uint64(iterations) * 50 // avoid the epoch for this test network, shutdown, err := test.NewNetwork(ac, gc, ec) @@ -209,8 +209,8 @@ func TestSingleNodeNetworkManyTxs(t *testing.T) { // We previously had an open bug for this https://github.com/celo-org/celo-blockchain/issues/1574 func TestEpochBlockMarshaling(t *testing.T) { accounts := test.AccountConfig(1, 0) - gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(accounts, gingerbreadBlock) + hforkBlock := common.Big0 + gc, ec, err := test.BuildConfig(accounts, hforkBlock) require.NoError(t, err) // Configure the shortest possible epoch, uptimeLookbackWindow minimum is 3 @@ -239,8 +239,8 @@ func TestEpochBlockMarshaling(t *testing.T) { // validators are shut down, when they restart the network is able to continue. func TestStartStopValidators(t *testing.T) { ac := test.AccountConfig(4, 2) - gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + hforkBlock := common.Big0 + gc, ec, err := test.BuildConfig(ac, hforkBlock) require.NoError(t, err) network, _, err := test.NewNetwork(ac, gc, ec) require.NoError(t, err) @@ -376,8 +376,8 @@ func TestStartStopValidators(t *testing.T) { // trace block code was the source of the concurrent map access. func TestBlockTracingConcurrentMapAccess(t *testing.T) { ac := test.AccountConfig(1, 2) - gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + hforkBlock := common.Big0 + gc, ec, err := test.BuildConfig(ac, hforkBlock) require.NoError(t, err) network, shutdown, err := test.NewNetwork(ac, gc, ec) require.NoError(t, err) @@ -427,8 +427,8 @@ func TestBlockTracingConcurrentMapAccess(t *testing.T) { // Helpful for debugging issues in TestBlockTracingConcurrentMapAccess. func TestBlockTracingSequentialAccess(t *testing.T) { ac := test.AccountConfig(1, 2) - gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + hforkBlock := common.Big0 + gc, ec, err := test.BuildConfig(ac, hforkBlock) require.NoError(t, err) network, shutdown, err := test.NewNetwork(ac, gc, ec) require.NoError(t, err) @@ -462,8 +462,8 @@ type rpcCustomTransaction struct { // price by the tx, which could be less than the feeCap (as in this example) func TestRPCDynamicTxGasPriceWithBigFeeCap(t *testing.T) { ac := test.AccountConfig(3, 2) - gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + hforkBlock := common.Big0 + gc, ec, err := test.BuildConfig(ac, hforkBlock) require.NoError(t, err) network, shutdown, err := test.NewNetwork(ac, gc, ec) require.NoError(t, err) @@ -503,8 +503,8 @@ func TestRPCDynamicTxGasPriceWithBigFeeCap(t *testing.T) { // GasPriceMinimum contract func TestRPCDynamicTxGasPriceWithState(t *testing.T) { ac := test.AccountConfig(3, 2) - gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + hforkBlock := common.Big0 + gc, ec, err := test.BuildConfig(ac, hforkBlock) require.NoError(t, err) ec.TxLookupLimit = 0 ec.NoPruning = true @@ -559,30 +559,30 @@ func TestRPCDynamicTxGasPriceWithState(t *testing.T) { // TestRPCDynamicTxGasPriceWithoutState aims to test the scenario where a // an old dynamic tx is requested via rpc, to a full node that does not have // the state anymore. -func TestRPCDynamicTxGasPriceWithoutStateBeforeGingerbread(t *testing.T) { +func TestRPCDynamicTxGasPriceWithoutStateBeforeHFork(t *testing.T) { testRPCDynamicTxGasPriceWithoutState(t, false, false) } -func TestRPCDynamicTxGasPriceWithoutStateAfterGingerbread(t *testing.T) { +func TestRPCDynamicTxGasPriceWithoutStateAfterHFork(t *testing.T) { testRPCDynamicTxGasPriceWithoutState(t, true, false) } -func TestRPCDynamicTxGasPriceWithoutStateForAlternativeCurrencyBeforeGingerbread(t *testing.T) { +func TestRPCDynamicTxGasPriceWithoutStateForAlternativeCurrencyBeforeHFork(t *testing.T) { testRPCDynamicTxGasPriceWithoutState(t, false, true) } -func TestRPCDynamicTxGasPriceWithoutStateForAlternativeCurrencyAfterGingerbread(t *testing.T) { +func TestRPCDynamicTxGasPriceWithoutStateForAlternativeCurrencyAfterHFork(t *testing.T) { testRPCDynamicTxGasPriceWithoutState(t, true, true) } -func testRPCDynamicTxGasPriceWithoutState(t *testing.T, afterGingerbread, alternativeCurrency bool) { - var gingerbreadBlock *big.Int - if afterGingerbread { - gingerbreadBlock = common.Big0 +func testRPCDynamicTxGasPriceWithoutState(t *testing.T, afterHfork, alternativeCurrency bool) { + var hforkBlock *big.Int + if afterHfork { + hforkBlock = common.Big0 } cusdAddress := common.HexToAddress("0xd008") ac := test.AccountConfig(3, 2) - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + gc, ec, err := test.BuildConfig(ac, hforkBlock) ec.TrieDirtyCache = 5 require.NoError(t, err) network, shutdown, err := test.NewNetwork(ac, gc, ec) @@ -634,7 +634,7 @@ func testRPCDynamicTxGasPriceWithoutState(t *testing.T, afterGingerbread, altern // Blocknumber != nil it means that it eas already processed require.NotNil(t, json2.BlockNumber) - if afterGingerbread && !alternativeCurrency { + if !alternativeCurrency { require.Equal(t, json.GasPrice, json2.GasPrice) } else { require.Nil(t, json2.GasPrice) @@ -655,8 +655,8 @@ func pruneStateOfBlock(ctx context.Context, node *test.Node, blockNumber *big.In func runMochaTest(t *testing.T, add_args func(*env.AccountsConfig, *genesis.Config, test.Network) []string) { ac := test.AccountConfig(1, 1) - gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + hforkBlock := common.Big0 + gc, ec, err := test.BuildConfig(ac, hforkBlock) require.NoError(t, err) network, shutdown, err := test.NewNetwork(ac, gc, ec) require.NoError(t, err) @@ -699,10 +699,10 @@ func TestEthersJSCompatibility(t *testing.T) { // This test checks the functionality of the configuration to enable/disable // returning the 'gasLimit' and 'baseFeePerGas' fields on RPC blocks. -func TestEthersJSCompatibilityDisableAfterGingerbread(t *testing.T) { +func TestEthersJSCompatibilityDisable(t *testing.T) { ac := test.AccountConfig(1, 1) - gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + hforkBlock := common.Big0 + gc, ec, err := test.BuildConfig(ac, hforkBlock) require.NoError(t, err) // Check fields present (compatibility set by default) @@ -719,7 +719,7 @@ func TestEthersJSCompatibilityDisableAfterGingerbread(t *testing.T) { for _, field := range []string{"gasLimit", "baseFeePerGas", "sha3Uncles", "uncles", "nonce", "mixHash", "difficulty"} { _, ok := result[field] - assert.Truef(t, ok, "%s field should be present on RPC block after Gingerbread", field) + assert.Truef(t, ok, "%s field should be present on RPC block", field) } require.Equal(t, result["sha3Uncles"], "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347") @@ -736,150 +736,12 @@ func TestEthersJSCompatibilityDisableAfterGingerbread(t *testing.T) { err = network[0].WsClient.GetRPCClient().CallContext(ctx, &result, "eth_getBlockByNumber", "0x0", true) require.NoError(t, err) - // After Gingerbread, gasLimit should be returned directly from the header, even if + // gasLimit should be returned directly from the header, even if // RPCEthCompatibility is off, since it is now part of the header hash. _, ok := result["gasLimit"] - assert.True(t, ok, "gasLimit field must be present on RPC block after Gingerbread") + assert.True(t, ok, "gasLimit field must be present on RPC block") _, ok = result["baseFeePerGas"] - assert.True(t, ok, "baseFeePerGas field must be present on RPC block after Gingerbread") -} - -// This test checks the functionality of the configuration to enable/disable -// returning the 'gasLimit' and 'baseFeePerGas' fields on RPC blocks before the Gingerbread happened. -// Gingerbread is relevant because it added the gasLimit to the header. -func TestEthersJSCompatibilityDisableBeforeGingerbread(t *testing.T) { - ac := test.AccountConfig(1, 1) - var gingerbreadBlock *big.Int = nil - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) - require.NoError(t, err) - - // Check fields present (compatibility set by default) - network, shutdown, err := test.NewNetwork(ac, gc, ec) - require.NoError(t, err) - defer shutdown() - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) - defer cancel() - - result := make(map[string]interface{}) - err = network[0].WsClient.GetRPCClient().CallContext(ctx, &result, "eth_getBlockByNumber", "latest", true) - require.NoError(t, err) - - for _, field := range []string{"gasLimit", "baseFeePerGas", "difficulty"} { - _, ok := result[field] - assert.Truef(t, ok, "%s field should be present on RPC block before Gingerbread", field) - } - for _, field := range []string{"sha3Uncles", "uncles", "nonce", "mixHash"} { - _, ok := result[field] - assert.Falsef(t, ok, "%s field should not be present on RPC block before Gingerbread", field) - } - - // Turn off compatibility and check fields are not present - ec.RPCEthCompatibility = false - network, shutdown, err = test.NewNetwork(ac, gc, ec) - require.NoError(t, err) - defer shutdown() - - ctx, cancel = context.WithTimeout(context.Background(), time.Second*20) - defer cancel() - - result = make(map[string]interface{}) - err = network[0].WsClient.GetRPCClient().CallContext(ctx, &result, "eth_getBlockByNumber", "latest", true) - require.NoError(t, err) - - for _, field := range []string{"gasLimit", "baseFeePerGas", "sha3Uncles", "uncles", "nonce", "mixHash", "difficulty"} { - _, ok := result[field] - assert.Falsef(t, ok, "%s field should not be present on RPC block before Gingerbread", field) - } -} - -// Initially we could not retrieve the eth compatibility fields (gasLimit & -// baseFee) on the genesis block because our logic dictated that the effective -// base fee and gas limit for a block were the ones set in the previous block -// (because those values are required by the vm before processing a block). -// There was no special case to handle the genesis block. We now have a special -// case to handle the genesis block which returns the values retrieved from the -// state at the genesis block. -func TestEthCompatibilityFieldsOnGenesisBlock(t *testing.T) { - ac := test.AccountConfig(1, 1) - // Gingerbread needs to be disabled for this test to be meaningful (since - // gingerbread added gasLimt & baseFee fields to the block) - var gingerbreadBlock *big.Int = nil - - // Fist we test without eth compatibility to ensure that the setting has an effect. - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) - ec.RPCEthCompatibility = false - require.NoError(t, err) - network, shutdown, err := test.NewNetwork(ac, gc, ec) - require.NoError(t, err) - defer shutdown() - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) - defer cancel() - - block, err := network[0].WsClient.BlockByNumber(ctx, big.NewInt(0)) - require.NoError(t, err) - - var nilBigInt *big.Int - require.Equal(t, nilBigInt, block.BaseFee()) - require.Equal(t, uint64(0), block.GasLimit()) - - // Now we with eth compatility enabled and see that gasLimit and baseFee - // are returned on the block. - gc, ec, err = test.BuildConfig(ac, gingerbreadBlock) - ec.RPCEthCompatibility = true - require.NoError(t, err) - network, shutdown, err = test.NewNetwork(ac, gc, ec) - require.NoError(t, err) - defer shutdown() - - block, err = network[0].WsClient.BlockByNumber(ctx, big.NewInt(0)) - require.NoError(t, err) - - require.NotEqual(t, nilBigInt, block.BaseFee()) - require.Greater(t, block.BaseFee().Uint64(), uint64(0)) - require.Greater(t, block.GasLimit(), uint64(0)) -} - -// Initially we were not able to set the gingerbread activation block to be the genesis block, this test checks that it's possible. -func TestSettingGingerbreadOnGenesisBlock(t *testing.T) { - ac := test.AccountConfig(1, 1) - - // Fist we test without gingerbread to ensure that setting the gingerbread - // actually has an effect. - var gingerbreadBlock *big.Int = nil - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) - ec.RPCEthCompatibility = false - require.NoError(t, err) - network, shutdown, err := test.NewNetwork(ac, gc, ec) - require.NoError(t, err) - defer shutdown() - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*20) - defer cancel() - - block, err := network[0].WsClient.BlockByNumber(ctx, big.NewInt(0)) - require.NoError(t, err) - - var nilBigInt *big.Int - require.Equal(t, nilBigInt, block.BaseFee()) - require.Equal(t, uint64(0), block.GasLimit()) - - // Now we check that setting the gingerbread block at genesis causes gasLimit and baseFee to be set on the block. - gingerbreadBlock = big.NewInt(0) - gc, ec, err = test.BuildConfig(ac, gingerbreadBlock) - ec.RPCEthCompatibility = false - require.NoError(t, err) - network, shutdown, err = test.NewNetwork(ac, gc, ec) - require.NoError(t, err) - defer shutdown() - - block, err = network[0].WsClient.BlockByNumber(ctx, big.NewInt(0)) - require.NoError(t, err) - - require.NotEqual(t, nilBigInt, block.BaseFee()) - require.Greater(t, block.BaseFee().Uint64(), uint64(0)) - require.Greater(t, block.GasLimit(), uint64(0)) + assert.True(t, ok, "baseFeePerGas field must be present on RPC block") } // This test checks that retreiveing the "finalized" block results in the same response as retrieving the "latest" block. diff --git a/e2e_test/e2e_transfer_test.go b/e2e_test/e2e_transfer_test.go index 4ebbbf929f..d5720072e8 100644 --- a/e2e_test/e2e_transfer_test.go +++ b/e2e_test/e2e_transfer_test.go @@ -36,8 +36,8 @@ const ( // - validator account has tip fee added. func TestTransferCELO(t *testing.T) { ac := test.AccountConfig(1, 3) - gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + hforkBlock := common.Big0 + gc, ec, err := test.BuildConfig(ac, hforkBlock) require.NoError(t, err) network, shutdown, err := test.NewNetwork(ac, gc, ec) require.NoError(t, err) @@ -258,8 +258,8 @@ func prepareTransaction(txArgs ethapi.TransactionArgs, senderKey *ecdsa.PrivateK func TestTransferERC20(t *testing.T) { ac := test.AccountConfig(1, 3) - gingerbreadBlock := common.Big0 - gc, ec, err := test.BuildConfig(ac, gingerbreadBlock) + hforkBlock := common.Big0 + gc, ec, err := test.BuildConfig(ac, hforkBlock) require.NoError(t, err) network, shutdown, err := test.NewNetwork(ac, gc, ec) require.NoError(t, err) @@ -278,13 +278,16 @@ func TestTransferERC20(t *testing.T) { header, err := network[0].WsClient.HeaderByNumber(ctx, nil) require.NoError(t, err) datum := header.BaseFee + actualRate, _ := new(big.Int).SetString("1", 10) + maxRate, _ := new(big.Int).SetString("1", 10) stableTokenAddress := env.MustProxyAddressFor("StableToken") intrinsicGas := hexutil.Uint64(config.IntrinsicGasForAlternativeFeeCurrency + 21000) testCases := []struct { - name string - txArgs *ethapi.TransactionArgs - expectedErr error + name string + txArgs *ethapi.TransactionArgs + expectedFeeInFeeCurrency *big.Int + expectedErr error }{ { name: "Celo transfer with fee currency", @@ -298,6 +301,35 @@ func TestTransferERC20(t *testing.T) { }, expectedErr: nil, }, + { + name: "Celo denominated transfer with fee currency", + txArgs: ðapi.TransactionArgs{ + To: &recipient.Address, + Value: (*hexutil.Big)(new(big.Int).SetInt64(oneCelo)), + MaxFeePerGas: (*hexutil.Big)(datum), // set in previous test + MaxPriorityFeePerGas: (*hexutil.Big)(datum), + Gas: &intrinsicGas, + FeeCurrency: &stableTokenAddress, + MaxFeeInFeeCurrency: (*hexutil.Big)(new(big.Int).Mul(new(big.Int).Mul(datum, big.NewInt(int64(intrinsicGas))), maxRate)), + }, + expectedFeeInFeeCurrency: new(big.Int).Mul(actualRate, new(big.Int).Mul(datum, big.NewInt(int64(intrinsicGas)))), + expectedErr: nil, + }, + { + name: "Celo denominated -low MaxFeeInFeeCurrency", + txArgs: ðapi.TransactionArgs{ + To: &recipient.Address, + Value: (*hexutil.Big)(new(big.Int).SetInt64(oneCelo)), + MaxFeePerGas: (*hexutil.Big)(datum), // set in previous test + MaxPriorityFeePerGas: (*hexutil.Big)(datum), + Gas: &intrinsicGas, + FeeCurrency: &stableTokenAddress, + MaxFeeInFeeCurrency: (*hexutil.Big)(new(big.Int).Sub( + new(big.Int).Mul(actualRate, new(big.Int).Mul(datum, big.NewInt(int64(intrinsicGas)))), + common.Big1)), + }, + expectedErr: core.ErrDenominatedLowMaxFee, + }, } // Get feeHandlerAddress @@ -351,6 +383,11 @@ func TestTransferERC20(t *testing.T) { expected := tx.Value() actual := watcher.Delta(recipient.Address) assert.Equal(t, expected, actual, "Recipient's balance increase unexpected", "expected", expected.Int64(), "actual", actual.Int64()) + + // Check for expected FeeInFeeCurrency in receipt for CELO denominated txs + expectedFeeInReceipt := tc.expectedFeeInFeeCurrency + actualFeeInReceipt := receipt.FeeInFeeCurrency + assert.Equal(t, expectedFeeInReceipt, actualFeeInReceipt, "Receipt FeeInFeeCurrency unexpected", "expected", expectedFeeInReceipt.String(), "actual", actualFeeInReceipt.String()) }) } } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 6d5346144e..1c2d4c9e68 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1289,6 +1289,7 @@ type RPCTransaction struct { GasFeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"` GasTipCap *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"` FeeCurrency *common.Address `json:"feeCurrency"` + MaxFeeInFeeCurrency *hexutil.Big `json:"maxFeeInFeeCurrency,omitempty"` GatewayFeeRecipient *common.Address `json:"gatewayFeeRecipient"` GatewayFee *hexutil.Big `json:"gatewayFee"` Hash common.Hash `json:"hash"` @@ -1331,6 +1332,7 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber Gas: hexutil.Uint64(tx.Gas()), GasPrice: (*hexutil.Big)(tx.GasPrice()), FeeCurrency: tx.FeeCurrency(), + MaxFeeInFeeCurrency: (*hexutil.Big)(tx.MaxFeeInFeeCurrency()), GatewayFeeRecipient: tx.GatewayFeeRecipient(), GatewayFee: (*hexutil.Big)(tx.GatewayFee()), Hash: tx.Hash(), @@ -2111,6 +2113,7 @@ func generateReceiptResponse(ctx context.Context, backend Backend, receipt *type "contractAddress": nil, "logs": receipt.Logs, "logsBloom": receipt.Bloom, + "feeInFeeCurrency": (*hexutil.Big)(receipt.FeeInFeeCurrency), "type": hexutil.Uint(receipt.Type), } diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index 2151e9591f..abd970c6c4 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -172,6 +172,7 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error { FeeCurrency: args.FeeCurrency, GatewayFee: args.GatewayFee, GatewayFeeRecipient: args.GatewayFeeRecipient, + MaxFeeInFeeCurrency: args.MaxFeeInFeeCurrency, Value: args.Value, Data: (*hexutil.Bytes)(&data), AccessList: args.AccessList, @@ -355,7 +356,7 @@ func (args *TransactionArgs) ToTransaction() *types.Transaction { func (args *TransactionArgs) checkEthCompatibility() error { // Reject if Celo-only fields set when EthCompatible is true - if args.EthCompatible && !(args.FeeCurrency == nil && args.GatewayFeeRecipient == nil && (args.GatewayFee == nil || args.GatewayFee.ToInt().Sign() == 0)) { + if args.EthCompatible && !(args.FeeCurrency == nil && args.GatewayFeeRecipient == nil && (args.GatewayFee == nil || args.GatewayFee.ToInt().Sign() == 0) && args.MaxFeeInFeeCurrency == nil) { return types.ErrEthCompatibleTransactionIsntCompatible } return nil diff --git a/mycelo/genesis/base_config.go b/mycelo/genesis/base_config.go index bed7742e9b..a10404c218 100644 --- a/mycelo/genesis/base_config.go +++ b/mycelo/genesis/base_config.go @@ -10,17 +10,13 @@ import ( // BaseConfig creates base parameters for celo // Callers must complete missing pieces -func BaseConfig(gingerbreadBlock *big.Int) *Config { - baseFeeOpCodeActivationBlock := gingerbreadBlock +func BaseConfig() *Config { + baseFeeOpCodeActivationBlock := big.NewInt(1) bigInt := big.NewInt bigIntStr := common.MustBigInt fixed := fixed.MustNew decimal := decimal.RequireFromString - if baseFeeOpCodeActivationBlock == nil { - baseFeeOpCodeActivationBlock = big.NewInt(0) // deactivated - } - return &Config{ SortedOracles: SortedOraclesParameters{ ReportExpirySeconds: 5 * Minute, diff --git a/mycelo/genesis/genesis.go b/mycelo/genesis/genesis.go index 9d50c58b09..30fef589ec 100644 --- a/mycelo/genesis/genesis.go +++ b/mycelo/genesis/genesis.go @@ -19,8 +19,8 @@ import ( var genesisMsgHash = common.HexToHash("ecc833a7747eaa8327335e8e0c6b6d8aa3a38d0063591e43ce116ccf5c89753e") // CreateCommonGenesisConfig generates a config starting point which templates can then customize further -func CreateCommonGenesisConfig(chainID *big.Int, adminAccountAddress common.Address, istanbulConfig params.IstanbulConfig, gingerbreadBlock *big.Int) (*Config, error) { - genesisConfig := BaseConfig(gingerbreadBlock) +func CreateCommonGenesisConfig(chainID *big.Int, adminAccountAddress common.Address, istanbulConfig params.IstanbulConfig, hforkBlock *big.Int) (*Config, error) { + genesisConfig := BaseConfig() genesisConfig.ChainID = chainID genesisConfig.GenesisTimestamp = uint64(time.Now().Unix()) genesisConfig.Istanbul = istanbulConfig @@ -28,8 +28,9 @@ func CreateCommonGenesisConfig(chainID *big.Int, adminAccountAddress common.Addr ChurritoBlock: common.Big0, DonutBlock: common.Big0, EspressoBlock: common.Big0, - GingerbreadBlock: gingerbreadBlock, - GingerbreadP2Block: gingerbreadBlock, + GingerbreadBlock: common.Big0, + GingerbreadP2Block: common.Big0, + HForkBlock: hforkBlock, } // Make admin account manager of Governance & Reserve diff --git a/test/node.go b/test/node.go index 74e3f9b879..3b8e65a9f4 100644 --- a/test/node.go +++ b/test/node.go @@ -344,12 +344,12 @@ func AccountConfig(numValidators, numExternal int) *env.AccountsConfig { // NOTE: Do not edit the Istanbul field of the returned genesis config it will // be overwritten with the corresponding config from the Istanbul field of the // returned eth config. -func BuildConfig(accounts *env.AccountsConfig, gingerbreadBlock *big.Int) (*genesis.Config, *ethconfig.Config, error) { +func BuildConfig(accounts *env.AccountsConfig, hforkBlock *big.Int) (*genesis.Config, *ethconfig.Config, error) { gc, err := genesis.CreateCommonGenesisConfig( big.NewInt(1), accounts.AdminAccount().Address, params.IstanbulConfig{}, - gingerbreadBlock, + hforkBlock, ) if err != nil { return nil, nil, err