-
Notifications
You must be signed in to change notification settings - Fork 851
Closed
Labels
linearCreated by Linear-GitHub SyncCreated by Linear-GitHub Sync
Description
Seid version
% seid version --long | head
name: sei
server_name: <appd>
version: v6.0.5-6-g620c03b1
commit: 620c03b10312874c52c96c6c9c07e87f23b9ae0a
build_tags: netgo,ledger
go: go version go1.23.0 darwin/arm64
build_deps:
- cosmossdk.io/[email protected]
- filippo.io/[email protected]
- github.com/99designs/[email protected]
Chain ID
1329
Describe the Issue
I’m trying to integrate Sei’s EVM base fee calculation into my project and have extracted the function from this code.
For most blocks, my implementation gives the expected result, but for some, I see a discrepancy of -1, and for others, the difference is much larger. I’m using the mainnet parameters retrieved from seid
.
Could someone provide an example of how the calculation should be done step by step, or help me verify if I’m using the correct parameters?
Additional context
Here are some example blocks where I noticed a larger difference in calculations:
Block | Parent Base Fee | Calculated Fee | Actual Fee | Diff (Wei) | Parent Gas Limit | Diff (%) |
----------------------------------------------------------------------------------------------------
140310769 | 1000000000 | 1006650013 | 1006581340 | 68673 | 10000000 | 0.0068223995%
140310772 | 1002075274 | 1002293288 | 1000170285 | 2123003 | 10000000 | 0.2122641546%
140310774 | 1000753349 | 1001336255 | 1001258747 | 77508 | 10000000 | 0.0077410560%
140310803 | 1000000000 | 1000339254 | 1000277950 | 61304 | 10000000 | 0.0061286965%
140310819 | 1000000000 | 1000554712 | 1000492828 | 61884 | 10000000 | 0.0061853517%
140311951 | 1000312343 | 1000494563 | 1000432665 | 61898 | 10000000 | 0.0061871230%
Here are all of the results ~400 blocks: results.txt
Here is the script:
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"log/slog"
"math/big"
"net/http"
"os"
"strconv"
"strings"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
)
const (
publicEndpoint = "https://evm-rpc.sei-apis.com/"
publicEndpoint2 = "https://evm-rpc.pacific-1.sei.io/"
ourEndpoint = "http://eth-rpc.sei.mainnet.prod"
rpcEndpoint = publicEndpoint2
sleepTime = 100 * time.Millisecond
retry = 3
)
func main() {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
ctx := context.Background()
blocksToCheck := 1000
nextHeight := int64(0)
var (
latestBlock *Block
err error
)
// Initialize the parameters from the memory
params := DefaultParams()
logger.Info("Using parameters",
"priorityNormalizer", params.PriorityNormalizer.String(),
"baseFeePerGas", params.BaseFeePerGas.String(),
"maxDynamicBaseFeeUpwardAdjustment", params.MaxDynamicBaseFeeUpwardAdjustment.String(),
"maxDynamicBaseFeeDownwardAdjustment", params.MaxDynamicBaseFeeDownwardAdjustment.String(),
"minimumFeePerGas", params.MinimumFeePerGas.String(),
"deliverTxHookWasmGasLimit", params.DeliverTxHookWasmGasLimit,
"targetGasUsedPerBlock", params.TargetGasUsedPerBlock,
"maximumFeePerGas", params.MaximumFeePerGas.String())
// Create a table header for the results
fmt.Printf("%-12s | %-20s | %-20s | %-20s | %-10s | %-15s | %-10s\n",
"Block", "Parent Base Fee", "Calculated Fee", "Actual Fee", "Diff (Wei)", "Parent Gas Limit", "Diff (%)")
fmt.Println(strings.Repeat("-", 100))
for i := 0; i < blocksToCheck; i++ {
if nextHeight == 0 {
latestBlock, err = fetchLatestBlock(ctx, "latest")
if err != nil {
logger.Error("Failed to fetch latest block", "error", err)
return
}
} else {
latestBlock, err = fetchBlockByNumber(ctx, fmt.Sprintf("0x%x", nextHeight))
if err != nil {
logger.Error("Failed to fetch block by number", "error", err)
return
}
}
nextHeight = latestBlock.Number.ToInt().Int64() + 1
latestBlockInt := latestBlock.Number.ToInt()
parentBlockInt := new(big.Int).Sub(latestBlockInt, big.NewInt(1))
parentBlockNumberHex := hexutil.EncodeBig(parentBlockInt)
parentBlock, err := fetchBlockByNumber(ctx, parentBlockNumberHex)
if err != nil {
logger.Error("Failed to fetch parent block", "error", err)
return
}
// Create a new context and keeper for each block
keeper := NewKeeper()
var sdkCtx *Context
// Set the parent block's base fee as the next base fee
if parentBlock.BaseFeePerGas != nil {
weiValue := parentBlock.BaseFeePerGas.ToInt()
prevBaseFee := sdk.NewDecFromBigInt(weiValue)
sdkCtx = NewContext(parentBlockInt.Int64(), int64(parentBlock.GasLimit))
keeper.SetNextBaseFeePerGas(sdkCtx, prevBaseFee)
keeper.SetParams(sdkCtx, params)
}
// Calculate the new base fee using the original implementation
newBaseFee := keeper.AdjustDynamicBaseFeePerGas(sdkCtx, uint64(parentBlock.GasUsed))
if newBaseFee == nil {
logger.Error("Failed to calculate new base fee")
return
}
// Convert to integer for comparison (same as in the chain)
newBaseFeeInt := newBaseFee.TruncateInt().BigInt()
actualBaseFee := latestBlock.BaseFeePerGas.ToInt()
// Calculate difference
difference := new(big.Int).Sub(newBaseFeeInt, actualBaseFee)
percentDiff := "0%"
if actualBaseFee.Sign() > 0 {
diffFloat := new(big.Float).SetInt(difference)
actualFloat := new(big.Float).SetInt(actualBaseFee)
// Calculate percentage: (diff / actual) * 100
percent := new(big.Float).Quo(diffFloat, actualFloat)
percent = percent.Mul(percent, big.NewFloat(100))
// Convert to string with 2 decimal places
percentVal, _ := percent.Float64()
percentDiff = fmt.Sprintf("%.10f%%", percentVal)
time.Sleep(sleepTime)
}
// Print the results in table format
fmt.Printf("%-12s | %-20s | %-20s | %-20s | %-10s | %-15d | %-10s\n",
latestBlock.Number.ToInt().String(),
parentBlock.BaseFeePerGas.ToInt().String(),
newBaseFeeInt.String(),
actualBaseFee.String(),
difference.String(),
parentBlock.GasLimit,
percentDiff)
}
}
// Block represents the Ethereum block structure we need
type Block struct {
Number hexutil.Big `json:"number"`
Hash common.Hash `json:"hash"`
ParentHash common.Hash `json:"parentHash"`
GasUsed hexutil.Uint64 `json:"gasUsed"`
GasLimit hexutil.Uint64 `json:"gasLimit"`
BaseFeePerGas *hexutil.Big `json:"baseFeePerGas,omitempty"`
}
// RPCResponse represents the JSON-RPC response
type RPCResponse struct {
JSONRPC string `json:"jsonrpc"`
ID int `json:"id"`
Result interface{} `json:"result,omitempty"`
Error *RPCError `json:"error,omitempty"`
}
// RPCError represents the JSON-RPC error
type RPCError struct {
Code int `json:"code"`
Message string `json:"message"`
}
// RPCRequest represents the JSON-RPC request
type RPCRequest struct {
JSONRPC string `json:"jsonrpc"`
Method string `json:"method"`
Params []interface{} `json:"params"`
ID int `json:"id"`
}
// Params represents the EVM module parameters
type Params struct {
PriorityNormalizer sdk.Dec `json:"priority_normalizer"`
BaseFeePerGas sdk.Dec `json:"base_fee_per_gas"`
MaxDynamicBaseFeeUpwardAdjustment sdk.Dec `json:"max_dynamic_base_fee_upward_adjustment"`
MaxDynamicBaseFeeDownwardAdjustment sdk.Dec `json:"max_dynamic_base_fee_downward_adjustment"`
MinimumFeePerGas sdk.Dec `json:"minimum_fee_per_gas"`
DeliverTxHookWasmGasLimit uint64 `json:"deliver_tx_hook_wasm_gas_limit"`
WhitelistedCwCodeHashesForDelegateCall [][]byte `json:"whitelisted_cw_code_hashes_for_delegate_call"`
TargetGasUsedPerBlock uint64 `json:"target_gas_used_per_block"`
MaximumFeePerGas sdk.Dec `json:"maximum_fee_per_gas"`
}
// DefaultParams returns the default parameters used in Sei mainnet
func DefaultParams() Params {
return Params{
PriorityNormalizer: sdk.MustNewDecFromStr("1.000000000000000000"),
BaseFeePerGas: sdk.MustNewDecFromStr("0.000000000000000000"),
MaxDynamicBaseFeeUpwardAdjustment: sdk.MustNewDecFromStr("0.007500000000000000"),
MaxDynamicBaseFeeDownwardAdjustment: sdk.MustNewDecFromStr("0.003900000000000000"),
MinimumFeePerGas: sdk.NewDec(1000000000),
DeliverTxHookWasmGasLimit: 300000,
WhitelistedCwCodeHashesForDelegateCall: [][]byte(nil),
TargetGasUsedPerBlock: 850000,
MaximumFeePerGas: sdk.MustNewDecFromStr("1000000000000.000000000000000000"),
}
}
// Context represents a simplified version of the Cosmos SDK context
type Context struct {
blockHeight int64
gasLimit int64
params Params
store map[string][]byte
}
// NewContext creates a new context with default values
func NewContext(blockHeight int64, gasLimit int64) *Context {
return &Context{
blockHeight: blockHeight,
gasLimit: gasLimit,
store: make(map[string][]byte),
}
}
// BlockHeight returns the current block height
func (ctx *Context) BlockHeight() int64 {
return ctx.blockHeight
}
// WithBlockHeight returns a new context with the given block height
func (ctx *Context) WithBlockHeight(height int64) *Context {
newCtx := *ctx
newCtx.blockHeight = height
return &newCtx
}
// ConsensusParams returns the consensus parameters
func (ctx *Context) ConsensusParams() *tmproto.ConsensusParams {
return &tmproto.ConsensusParams{
Block: &tmproto.BlockParams{
MaxGas: ctx.gasLimit,
},
}
}
// KVStore represents a simplified key-value store
type KVStore struct {
store map[string][]byte
}
// Get returns the value for the given key
func (s *KVStore) Get(key []byte) []byte {
return s.store[string(key)]
}
// Set sets the value for the given key
func (s *KVStore) Set(key []byte, value []byte) {
s.store[string(key)] = value
}
// Delete deletes the value for the given key
func (s *KVStore) Delete(key []byte) {
delete(s.store, string(key))
}
// KVStore returns a key-value store for the given store key
func (ctx *Context) KVStore(storeKey string) *KVStore {
return &KVStore{
store: ctx.store,
}
}
// Keeper represents a simplified version of the EVM keeper
type Keeper struct {
storeKey string
params Params
currBaseFeePerGas sdk.Dec
nextBaseFeePerGas sdk.Dec
}
// NewKeeper creates a new keeper with default values
func NewKeeper() *Keeper {
return &Keeper{
storeKey: "evm",
params: DefaultParams(),
}
}
// GetStoreKey returns the store key
func (k *Keeper) GetStoreKey() string {
return k.storeKey
}
// GetParams returns the module parameters
func (k *Keeper) GetParams(ctx *Context) Params {
return k.params
}
// SetParams sets the module parameters
func (k *Keeper) SetParams(ctx *Context, params Params) {
k.params = params
}
// GetMinimumFeePerGas returns the minimum fee per gas
func (k *Keeper) GetMinimumFeePerGas(ctx *Context) sdk.Dec {
return k.params.MinimumFeePerGas
}
// GetMaximumFeePerGas returns the maximum fee per gas
func (k *Keeper) GetMaximumFeePerGas(ctx *Context) sdk.Dec {
return k.params.MaximumFeePerGas
}
// GetTargetGasUsedPerBlock returns the target gas used per block
func (k *Keeper) GetTargetGasUsedPerBlock(ctx *Context) uint64 {
return k.params.TargetGasUsedPerBlock
}
// GetMaxDynamicBaseFeeUpwardAdjustment returns the maximum dynamic base fee upward adjustment
func (k *Keeper) GetMaxDynamicBaseFeeUpwardAdjustment(ctx *Context) sdk.Dec {
return k.params.MaxDynamicBaseFeeUpwardAdjustment
}
// GetMaxDynamicBaseFeeDownwardAdjustment returns the maximum dynamic base fee downward adjustment
func (k *Keeper) GetMaxDynamicBaseFeeDownwardAdjustment(ctx *Context) sdk.Dec {
return k.params.MaxDynamicBaseFeeDownwardAdjustment
}
// GetCurrBaseFeePerGas returns the current base fee per gas
func (k *Keeper) GetCurrBaseFeePerGas(ctx *Context) sdk.Dec {
return k.currBaseFeePerGas
}
// SetCurrBaseFeePerGas sets the current base fee per gas
func (k *Keeper) SetCurrBaseFeePerGas(ctx *Context, baseFeePerGas sdk.Dec) {
k.currBaseFeePerGas = baseFeePerGas
}
// SetNextBaseFeePerGas sets the next base fee per gas
func (k *Keeper) SetNextBaseFeePerGas(ctx *Context, baseFeePerGas sdk.Dec) {
k.nextBaseFeePerGas = baseFeePerGas
}
// GetNextBaseFeePerGas returns the next base fee per gas
func (k *Keeper) GetNextBaseFeePerGas(ctx *Context) sdk.Dec {
return k.nextBaseFeePerGas
}
// AdjustDynamicBaseFeePerGas adjusts the dynamic base fee per gas
// This is the exact implementation from the Sei mainnet EVM module
func (k *Keeper) AdjustDynamicBaseFeePerGas(ctx *Context, blockGasUsed uint64) *sdk.Dec {
if ctx.ConsensusParams() == nil || ctx.ConsensusParams().Block == nil {
return nil
}
prevBaseFee := k.GetNextBaseFeePerGas(ctx)
// set the resulting base fee for block n-1 on block n
k.SetCurrBaseFeePerGas(ctx, prevBaseFee)
targetGasUsed := sdk.NewDec(int64(k.GetTargetGasUsedPerBlock(ctx)))
if targetGasUsed.IsZero() { // avoid division by zero
return &prevBaseFee // return the previous base fee as is
}
minimumFeePerGas := k.GetParams(ctx).MinimumFeePerGas
maximumFeePerGas := k.GetParams(ctx).MaximumFeePerGas
blockGasLimit := sdk.NewDec(ctx.ConsensusParams().Block.MaxGas)
blockGasUsedDec := sdk.NewDec(int64(blockGasUsed))
// cap block gas used to block gas limit
if blockGasUsedDec.GT(blockGasLimit) {
blockGasUsedDec = blockGasLimit
}
var newBaseFee sdk.Dec
if blockGasUsedDec.GT(targetGasUsed) {
// upward adjustment
numerator := blockGasUsedDec.Sub(targetGasUsed)
denominator := blockGasLimit.Sub(targetGasUsed)
percentageFull := numerator.Quo(denominator)
adjustmentFactor := k.GetMaxDynamicBaseFeeUpwardAdjustment(ctx).Mul(percentageFull)
newBaseFee = prevBaseFee.Mul(sdk.NewDec(1).Add(adjustmentFactor))
} else {
// downward adjustment
numerator := targetGasUsed.Sub(blockGasUsedDec)
denominator := targetGasUsed
percentageEmpty := numerator.Quo(denominator)
adjustmentFactor := k.GetMaxDynamicBaseFeeDownwardAdjustment(ctx).Mul(percentageEmpty)
newBaseFee = prevBaseFee.Mul(sdk.NewDec(1).Sub(adjustmentFactor))
}
// Ensure the new base fee is not lower than the minimum fee
if newBaseFee.LT(minimumFeePerGas) {
newBaseFee = minimumFeePerGas
}
// Ensure the new base fee is not higher than the maximum fee
if newBaseFee.GT(maximumFeePerGas) {
newBaseFee = maximumFeePerGas
}
// Set the new base fee for the next height
k.SetNextBaseFeePerGas(ctx, newBaseFee)
return &newBaseFee
}
// fetchLatestBlock fetches the latest block from the RPC endpoint
func fetchLatestBlock(ctx context.Context, blockNum string) (*Block, error) {
if blockNum != "latest" {
blockNumParsed, err := strconv.ParseUint(blockNum, 10, 64)
if err != nil {
return nil, fmt.Errorf("failed to parse block number: %w", err)
}
blockNum = fmt.Sprintf("0x%x", blockNumParsed)
}
rpcResp, err := sendRPCRequest(ctx, "eth_getBlockByNumber", []interface{}{blockNum, true})
i := 0
for err != nil && i < retry {
time.Sleep(sleepTime)
rpcResp, err = sendRPCRequest(ctx, "eth_getBlockByNumber", []interface{}{blockNum, true})
if err == nil {
break
}
i++
}
if err != nil {
return nil, fmt.Errorf("failed to fetch block by number: %w", err)
}
resultJSON, err := json.Marshal(rpcResp.Result)
if err != nil {
return nil, fmt.Errorf("failed to marshal result: %w", err)
}
var block Block
if err := json.Unmarshal(resultJSON, &block); err != nil {
return nil, fmt.Errorf("failed to unmarshal block: %w", err)
}
return &block, nil
}
// fetchBlockByNumber fetches a specific block by number
func fetchBlockByNumber(ctx context.Context, number string) (*Block, error) {
rpcResp, err := sendRPCRequest(ctx, "eth_getBlockByNumber", []interface{}{number, true})
i := 0
for err != nil && i < retry {
time.Sleep(sleepTime)
rpcResp, err = sendRPCRequest(ctx, "eth_getBlockByNumber", []interface{}{number, true})
if err == nil {
break
}
i++
}
if err != nil {
return nil, fmt.Errorf("failed to fetch block by number: %w", err)
}
resultJSON, err := json.Marshal(rpcResp.Result)
if err != nil {
return nil, fmt.Errorf("failed to marshal result: %w", err)
}
var block Block
if err := json.Unmarshal(resultJSON, &block); err != nil {
return nil, fmt.Errorf("failed to unmarshal block: %w", err)
}
return &block, nil
}
// sendRPCRequest sends a JSON-RPC request and returns the response
func sendRPCRequest(ctx context.Context, method string, params []interface{}) (*RPCResponse, error) {
req := RPCRequest{
JSONRPC: "2.0",
Method: method,
Params: params,
ID: 1,
}
reqBody, err := json.Marshal(req)
if err != nil {
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
httpReq, err := http.NewRequestWithContext(ctx, "POST", rpcEndpoint, bytes.NewBuffer(reqBody))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
httpReq.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
var rpcResp RPCResponse
if err := json.NewDecoder(resp.Body).Decode(&rpcResp); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
if rpcResp.Error != nil {
return nil, fmt.Errorf("RPC error: %s", rpcResp.Error.Message)
}
return &rpcResp, nil
}
Metadata
Metadata
Assignees
Labels
linearCreated by Linear-GitHub SyncCreated by Linear-GitHub Sync