Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apply currency conversion to EffeciveGasTip #316

Open
wants to merge 21 commits into
base: celo11
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
2153c7f
Add EffectiveGasTipInCurrency and EffectiveGasTipInCelo
Kourin1996 Jan 29, 2025
e6001ef
Remove EffectiveGasTipCmp
Kourin1996 Jan 29, 2025
d53ab06
Apply base fee currency conversion for EffectiveGasTip
Kourin1996 Jan 29, 2025
4ab43d9
Fix lint error
Kourin1996 Jan 29, 2025
7d70488
Cleanup EffectiveGasTipInCurrency and EffectiveGasTipInCelo
Kourin1996 Jan 30, 2025
508a025
Add tests for FeeHistory and SuggestOptimismPriorityFee API
Kourin1996 Feb 3, 2025
35851d7
Add tests for gas fee currency conversion in GraphQL
Kourin1996 Feb 3, 2025
dd57714
Add tests for miner fee calculation with fee conversions
Kourin1996 Feb 3, 2025
4ed67e4
Fix error handlings in processBlock
Kourin1996 Feb 4, 2025
1e74c79
Revert fix in GraphQL code
Kourin1996 Feb 10, 2025
e478fe8
Revert deletion of EffectiveGasTipCmp
Kourin1996 Feb 10, 2025
fc0f40d
Simplify EffectiveGasTipInCurrency and EffectiveGasTipInCelo
Kourin1996 Feb 10, 2025
4e29936
Remove CeloAPIBackend from miner's Backend interface
Kourin1996 Feb 10, 2025
17cc4cc
Remove unused testAPIBackend
Kourin1996 Feb 10, 2025
b3e3641
Fix tests
Kourin1996 Feb 10, 2025
a28d3f1
Fix lint error
Kourin1996 Feb 10, 2025
44fe731
Remove t.Fail() for debug
Kourin1996 Feb 10, 2025
9b7243c
Removed celoBackend field from Ethereum object
Kourin1996 Feb 11, 2025
a325425
Merge remote-tracking branch 'origin/celo11' into Kourin1996/apply-cu…
Kourin1996 Feb 11, 2025
2965776
Removed unnecessary error
Kourin1996 Feb 11, 2025
19afbaa
Remove unnecessary interface
Kourin1996 Feb 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions core/types/celo_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,31 @@ func (tx *Transaction) MaxFeeInFeeCurrency() *big.Int {
return maxFeeInFeeCurrency
}

// EffectiveGasTipInCurrency returns the effective gas tip in the specified currency
// For transactions with a specified fee currency, the function takes the base fee in Celo,
// calculates in the specified currency, and returns the value in the currency
func (tx *Transaction) EffectiveGasTipInCurrency(baseFee *big.Int, exchangeRates common.ExchangeRates) (*big.Int, error) {
if baseFee != nil {
var err error
baseFee, err = exchange.ConvertCeloToCurrency(exchangeRates, tx.FeeCurrency(), baseFee)
if err != nil {
return nil, err
}
}
return tx.EffectiveGasTip(baseFee)
}

// EffectiveGasTipInCelo returns the effective gas tip in Celo
// For transactions with a specified fee currency, the function takes the base fee in Celo,
// calculates in the specified currency, and returns the value in Celo
func (tx *Transaction) EffectiveGasTipInCelo(baseFee *big.Int, exchangeRates common.ExchangeRates) (*big.Int, error) {
gasTipInCurrency, err := tx.EffectiveGasTipInCurrency(baseFee, exchangeRates)
if err != nil {
return nil, err
}
return exchange.ConvertCurrencyToCelo(exchangeRates, tx.FeeCurrency(), gasTipInCurrency)
}

// CompareWithRates compares the effective gas price of two transactions according to the exchange rates and
// the base fees in the transactions currencies.
func CompareWithRates(a, b *Transaction, ratesAndFees *exchange.RatesAndFees) int {
Expand Down
121 changes: 121 additions & 0 deletions core/types/celo_transaction_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Copyright 2024 The Celo Authors
// This file is part of the celo library.
//
// The celo library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The celo library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the celo library. If not, see <http://www.gnu.org/licenses/>.

package types

import (
"math/big"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/exchange"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestTransactionEffectiveGasTipInCurrency(t *testing.T) {
t.Parallel()

usdToken := common.HexToAddress("0x765de816845861e75a25fca122bb6898b8b1282a")
eurToken := common.HexToAddress("0xd8763cba276a3738e6de85b4b3bf5fded6d6ca73")
exchangeRates := common.ExchangeRates{
usdToken: big.NewRat(2, 1), // 1 Celo ≒ 2 USD
}

getGasTips := func(t *testing.T, tx *Transaction, baseFee *big.Int) (*big.Int, *big.Int) {
t.Helper()

gasTipInCelo, err := tx.EffectiveGasTipInCelo(baseFee, exchangeRates)
require.NoError(t, err)

gasTipInCurrency, err := tx.EffectiveGasTipInCurrency(baseFee, exchangeRates)
require.NoError(t, err)

return gasTipInCelo, gasTipInCurrency
}

// Normal Tx
t.Run("tx should return the difference between GasFeeCap and BaseFee when tx type is not CeloDynamicFeeTxV2", func(t *testing.T) {
gasTipInCelo, gasTipInCurrency := getGasTips(t, NewTx(&DynamicFeeTx{
GasFeeCap: big.NewInt(9e8),
GasTipCap: big.NewInt(5e8),
}), big.NewInt(5e8))

assert.Equal(t, big.NewInt(4e8), gasTipInCelo)
assert.Equal(t, big.NewInt(4e8), gasTipInCurrency)
})

t.Run("tx should return the GasTipCap when tx type is not CeloDynamicFeeTxV2", func(t *testing.T) {
gasTipInCelo, gasTipInCurrency := getGasTips(t, NewTx(&DynamicFeeTx{
GasFeeCap: big.NewInt(9e8),
GasTipCap: big.NewInt(3e8),
}), big.NewInt(5e8))

assert.Equal(t, big.NewInt(3e8), gasTipInCelo)
assert.Equal(t, big.NewInt(3e8), gasTipInCurrency)
})

// CeloDynamicFeeTxV2
t.Run("tx should return the difference between GasFeeCap and BaseFee with conversions between Celo and USDT when the transaction is CeloDynamicFeeTxV2 with the specified fee currency", func(t *testing.T) {
gasTipInCelo, gasTipInCurrency := getGasTips(t, NewTx(&CeloDynamicFeeTxV2{
FeeCurrency: &usdToken,
GasFeeCap: big.NewInt(18e8), // USD
GasTipCap: big.NewInt(8e8), // USD
Kourin1996 marked this conversation as resolved.
Show resolved Hide resolved
}), big.NewInt(6e8)) // Celo

assert.Equal(t, big.NewInt(3e8), gasTipInCelo)
assert.Equal(t, big.NewInt(6e8), gasTipInCurrency)
})

t.Run("tx should return the GasTipCap with conversions between Celo and USDT when the transaction is CeloDynamicFeeTxV2 with the specified fee currency", func(t *testing.T) {
gasTipInCelo, gasTipInCurrency := getGasTips(t, NewTx(&CeloDynamicFeeTxV2{
FeeCurrency: &usdToken,
GasFeeCap: big.NewInt(18e8), // USD
GasTipCap: big.NewInt(4e8), // USD
}), big.NewInt(5e8)) // Celo

assert.Equal(t, big.NewInt(2e8), gasTipInCelo)
assert.Equal(t, big.NewInt(4e8), gasTipInCurrency)
})

t.Run("tx should return GasTipCap with conversions between Celo and USDT when the transaction is CeloDynamicFeeTxV2 with the specified fee currency but the base fee is nil", func(t *testing.T) {
gasTipInCelo, gasTipInCurrency := getGasTips(t, NewTx(&CeloDynamicFeeTxV2{
FeeCurrency: &usdToken,
GasFeeCap: big.NewInt(18e8), // USD
GasTipCap: big.NewInt(6e8), // USD
}), nil)

assert.Equal(t, big.NewInt(3e8), gasTipInCelo)
assert.Equal(t, big.NewInt(6e8), gasTipInCurrency)
})

// Error cases
t.Run("tx should return an error when the fee currency which is not listed in the exchange rates is specified", func(t *testing.T) {
tx := NewTx(&CeloDynamicFeeTxV2{
FeeCurrency: &eurToken,
GasFeeCap: big.NewInt(18e8),
GasTipCap: big.NewInt(8e8),
})

res, err := tx.EffectiveGasTipInCelo(big.NewInt(5e8), exchangeRates)
assert.ErrorIs(t, err, exchange.ErrUnregisteredFeeCurrency)
require.Nil(t, res)

res, err = tx.EffectiveGasTipInCurrency(big.NewInt(5e8), exchangeRates)
assert.ErrorIs(t, err, exchange.ErrUnregisteredFeeCurrency)
require.Nil(t, res)
})
}
2 changes: 1 addition & 1 deletion eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
if eth.APIBackend.allowUnprotectedTxs {
log.Info("Unprotected transactions allowed")
}
eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, config.GPO, config.Miner.GasPrice)
eth.APIBackend.gpo = gasprice.NewOracle(celoapi.NewCeloAPIBackend(eth.APIBackend), config.GPO, config.Miner.GasPrice)

if config.RollupSequencerHTTP != "" {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
Expand Down
197 changes: 197 additions & 0 deletions eth/gasprice/celo_feehistory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
// Copyright 2024 The celo Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.

package gasprice

import (
"context"
"crypto/ecdsa"
"math/big"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/exchange"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/beacon"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/contracts"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

type celoTestBackend struct {
testBackend
rates common.ExchangeRates
}

func (b *celoTestBackend) GetExchangeRates(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (common.ExchangeRates, error) {
return b.rates, nil
}

func newCeloTestBackend(t *testing.T, pending bool, genBlock func(i int, b *core.BlockGen, gspec *core.Genesis, signer types.Signer, key *ecdsa.PrivateKey, address common.Address)) *celoTestBackend {
var (
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr = crypto.PubkeyToAddress(key.PublicKey)

config = *params.TestChainConfig
gspec = &core.Genesis{
Config: &config,
Alloc: core.CeloGenesisAccounts(addr),
}
signer = types.LatestSigner(gspec.Config)
)
// Enable all forks from genesis
config.LondonBlock = big.NewInt(0)
config.GingerbreadBlock = big.NewInt(0)
config.ArrowGlacierBlock = big.NewInt(0)
config.GrayGlacierBlock = big.NewInt(0)
config.ShanghaiTime = &gspec.Timestamp
config.CancunTime = &gspec.Timestamp

var engine consensus.Engine = beacon.New(ethash.NewFaker())
td := params.GenesisDifficulty.Uint64()

// Generate testing blocks
db, blocks, _ := core.GenerateChainWithGenesis(gspec, engine, testHead+1, func(i int, b *core.BlockGen) {
b.SetPoS()
genBlock(i, b, gspec, signer, key, addr)
td += b.Difficulty().Uint64()
})

gspec.Config.TerminalTotalDifficulty = new(big.Int).SetUint64(td)
chain, err := core.NewBlockChain(db, &core.CacheConfig{TrieCleanNoPrefetch: true}, gspec, nil, engine, vm.Config{}, nil)
if err != nil {
t.Fatalf("failed to create local chain, %v", err)
}
if i, err := chain.InsertChain(blocks); err != nil {
t.Fatalf("error inserting block %d: %v", i, err)
}

chain.SetFinalized(chain.GetBlockByNumber(25).Header())
chain.SetSafe(chain.GetBlockByNumber(25).Header())

state, _ := chain.State()

backend := contracts.CeloBackend{
ChainConfig: &config,
State: state,
}
exchangeRates, err := contracts.GetExchangeRates(&backend)
if err != nil {
t.Fatal("could not get exchange rates")
}

return &celoTestBackend{
testBackend: testBackend{chain: chain, pending: pending},
rates: exchangeRates,
}
}

// TestCeloFeeHistory verifies that the fee history calculation correctly accounts for currency conversion
// in Cel2. This test is similar to TestFeeHistory but focuses specifically on verifying the
// accuracy of fee currency conversions.
func TestCeloFeeHistory(t *testing.T) {
t.Parallel()

feeCurrencies := []*common.Address{nil, &core.DevFeeCurrencyAddr, &core.DevFeeCurrencyAddr2}
feeCaps := []*big.Int{
big.NewInt(10 * params.GWei),
big.NewInt(30 * params.GWei), // 15 GWei in Celo
big.NewInt(50 * params.GWei), // 100 GWei in Celo
}

backend := newCeloTestBackend(t, false, func(i int, b *core.BlockGen, gspec *core.Genesis, signer types.Signer, key *ecdsa.PrivateKey, address common.Address) {
addTx := func(gasFeeCap, gasTipCap *big.Int, feeCurrency *common.Address) {
if feeCurrency == nil {
b.AddTx(types.MustSignNewTx(key, signer, &types.DynamicFeeTx{
ChainID: gspec.Config.ChainID,
Nonce: b.TxNonce(address),
To: &common.Address{},
Gas: 80000,
GasFeeCap: gasFeeCap,
GasTipCap: gasTipCap,
Data: []byte{},
}))
} else {
b.AddTx(types.MustSignNewTx(key, signer, &types.CeloDynamicFeeTxV2{
ChainID: gspec.Config.ChainID,
Nonce: b.TxNonce(address),
To: &common.Address{},
Gas: 80000,
FeeCurrency: feeCurrency,
GasFeeCap: gasFeeCap,
GasTipCap: gasTipCap,
Data: []byte{},
}))
}
}

if i == 19 {
// Bulding Block #20
// Set sufficiently high gas fee cap so that the gas tip cap is used
for idx := range feeCurrencies {
addTx(big.NewInt(10000*params.GWei), feeCaps[idx], feeCurrencies[idx])
}
} else if i == 20 {
// Bulding Block #21
// Set fee cap and tip cap to the same amount so that (fee cap - base fee) is used
for idx := range feeCurrencies {
addTx(feeCaps[idx], feeCaps[idx], feeCurrencies[idx])
}
}
})

oracle := NewOracle(backend, Config{
MaxHeaderHistory: 1000,
MaxBlockHistory: 1000,
}, nil)
first, reward, baseFee, _, _, _, err := oracle.FeeHistory(context.Background(), 2, 21, []float64{0, 50, 100})
backend.teardown()

require.NoError(t, err)
require.Equal(t, big.NewInt(20), first)

rates, err := backend.GetExchangeRates(context.Background(), rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(21)))
require.NotNil(t, rates)
require.NoError(t, err)

// create expected values
expectedReward20 := make([]*big.Int, 3)
expectedReward21 := make([]*big.Int, 3)
for idx := range expectedReward20 {
expectedReward20[idx], err = exchange.ConvertCurrencyToCelo(rates, feeCurrencies[idx], feeCaps[idx])
require.NoError(t, err)

baseFeeInCurrency, err := exchange.ConvertCeloToCurrency(rates, feeCurrencies[idx], baseFee[1])
require.NoError(t, err)

expectedReward21[idx], err = exchange.ConvertCurrencyToCelo(
rates,
feeCurrencies[idx],
new(big.Int).Sub(feeCaps[idx], baseFeeInCurrency),
)
require.NoError(t, err)
}

assert.Equal(t, expectedReward20, reward[0])
assert.Equal(t, expectedReward21, reward[1])
}
Loading
Loading