forked from cerc-io/laconicd-deprecated
imp(tests): prune node integration tests (#1212)
* first pass * extra comment * fixed pruned node tests. Fix getBalance on pruned. Fix BaseFee on pruned. * fix tests execution * check logs on tests * address pr comments * address comments * Update rpc/namespaces/ethereum/eth/api.go * update error msg check * fix lint * fix linter * fix linter * fix py lint * test lint * fix lint * pin golangcli version * pin golanci version * pin lint to version 0.48 * fix linter * fix last linter last file Co-authored-by: ramacarlucho <ramirocarlucho@gmail.com> Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
This commit is contained in:
parent
7331cd2065
commit
ccbaf1fe05
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
||||
go.sum
|
||||
- uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
version: latest
|
||||
version: v1.48.0
|
||||
args: --timeout 10m
|
||||
github-token: ${{ secrets.github_token }}
|
||||
# Check only if there are differences in the source code
|
||||
@ -59,7 +59,7 @@ jobs:
|
||||
PATTERNS: |
|
||||
**/**.py
|
||||
- run: |
|
||||
nix-shell -I nixpkgs=./nix -p test-env --run "make lint-py"
|
||||
nix-shell -I nixpkgs=./nix -p test-env --run "make lint-py"
|
||||
if: env.GIT_DIFF
|
||||
gomod2nix:
|
||||
name: Check gomod2nix.toml file is up to date
|
||||
|
@ -1,4 +1,5 @@
|
||||
/*Package ante defines the SDK auth module's AnteHandler as well as an internal
|
||||
/*
|
||||
Package ante defines the SDK auth module's AnteHandler as well as an internal
|
||||
AnteHandler for an Ethereum transaction (i.e MsgEthereumTx).
|
||||
|
||||
During CheckTx, the transaction is passed through a series of
|
||||
|
@ -62,7 +62,7 @@ func (app *EthermintApp) ExportAppStateAndValidators(
|
||||
|
||||
// prepare for fresh start at zero height
|
||||
// NOTE zero height genesis is a temporary feature which will be deprecated
|
||||
// in favor of export at a block height
|
||||
// in favor of export at a block height
|
||||
func (app *EthermintApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs []string) error {
|
||||
applyAllowedAddrs := false
|
||||
|
||||
|
@ -154,7 +154,7 @@ func TestAppImportExport(t *testing.T) {
|
||||
|
||||
fmt.Printf("importing genesis...\n")
|
||||
|
||||
// nolint: dogsled
|
||||
//nolint: dogsled
|
||||
_, newDB, newDir, _, _, err := simapp.SetupSimulation("leveldb-app-sim-2", "Simulation-2")
|
||||
require.NoError(t, err, "simulation setup failed")
|
||||
|
||||
|
@ -38,14 +38,16 @@ const (
|
||||
mnemonicEntropySize = 256
|
||||
)
|
||||
|
||||
/* RunAddCmd
|
||||
/*
|
||||
RunAddCmd
|
||||
input
|
||||
- bip39 mnemonic
|
||||
- bip39 passphrase
|
||||
- bip44 path
|
||||
- local encryption password
|
||||
- bip39 mnemonic
|
||||
- bip39 passphrase
|
||||
- bip44 path
|
||||
- local encryption password
|
||||
|
||||
output
|
||||
- armor encrypted private key (saved to file)
|
||||
- armor encrypted private key (saved to file)
|
||||
*/
|
||||
func RunAddCmd(ctx client.Context, cmd *cobra.Command, args []string, inBuf *bufio.Reader) error {
|
||||
var err error
|
||||
|
@ -40,7 +40,7 @@ func SetBech32Prefixes(config *sdk.Config) {
|
||||
func SetBip44CoinType(config *sdk.Config) {
|
||||
config.SetCoinType(ethermint.Bip44CoinType)
|
||||
config.SetPurpose(sdk.Purpose) // Shared
|
||||
config.SetFullFundraiserPath(ethermint.BIP44HDPath) // nolint: staticcheck
|
||||
config.SetFullFundraiserPath(ethermint.BIP44HDPath) //nolint: staticcheck
|
||||
}
|
||||
|
||||
// RegisterDenoms registers the base and display denominations to the SDK.
|
||||
|
@ -22,7 +22,7 @@ import (
|
||||
)
|
||||
|
||||
// BackendI implements the Cosmos and EVM backend.
|
||||
type BackendI interface { // nolint: revive
|
||||
type BackendI interface { //nolint: revive
|
||||
CosmosBackend
|
||||
EVMBackend
|
||||
}
|
||||
|
@ -287,7 +287,7 @@ func (a *API) BlockProfile(file string, nsec uint) error {
|
||||
|
||||
// CpuProfile turns on CPU profiling for nsec seconds and writes
|
||||
// profile data to file.
|
||||
func (a *API) CpuProfile(file string, nsec uint) error { // nolint: golint, stylecheck, revive
|
||||
func (a *API) CpuProfile(file string, nsec uint) error { //nolint: golint, stylecheck, revive
|
||||
a.logger.Debug("debug_cpuProfile", "file", file, "nsec", nsec)
|
||||
if err := a.StartCPUProfile(file); err != nil {
|
||||
return err
|
||||
|
@ -128,7 +128,7 @@ func (e *PublicAPI) ProtocolVersion() hexutil.Uint {
|
||||
}
|
||||
|
||||
// ChainId is the EIP-155 replay-protection chain id for the current ethereum chain config.
|
||||
func (e *PublicAPI) ChainId() (*hexutil.Big, error) { // nolint
|
||||
func (e *PublicAPI) ChainId() (*hexutil.Big, error) { //nolint
|
||||
e.logger.Debug("eth_chainId")
|
||||
// if current block is at or past the EIP-155 replay-protection fork block, return chainID from config
|
||||
bn, err := e.backend.BlockNumber()
|
||||
@ -294,6 +294,11 @@ func (e *PublicAPI) GetBalance(address common.Address, blockNrOrHash rpctypes.Bl
|
||||
return nil, errors.New("invalid balance")
|
||||
}
|
||||
|
||||
// balance can only be negative in case of pruned node
|
||||
if val.IsNegative() {
|
||||
return nil, errors.New("couldn't fetch balance. Node state is pruned")
|
||||
}
|
||||
|
||||
return (*hexutil.Big)(val.BigInt()), nil
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,7 @@ func NewRangeFilter(logger log.Logger, backend Backend, begin, end int64, addres
|
||||
// Flatten the address and topic filter clauses into a single bloombits filter
|
||||
// system. Since the bloombits are not positional, nil topics are permitted,
|
||||
// which get flattened into a nil byte slice.
|
||||
var filtersBz [][][]byte // nolint: prealloc
|
||||
var filtersBz [][][]byte //nolint: prealloc
|
||||
if len(addresses) > 0 {
|
||||
filter := make([][]byte, len(addresses))
|
||||
for i, address := range addresses {
|
||||
|
@ -15,9 +15,9 @@ import (
|
||||
)
|
||||
|
||||
// QueryClient defines a gRPC Client used for:
|
||||
// - Transaction simulation
|
||||
// - EVM module queries
|
||||
// - Fee market module queries
|
||||
// - Transaction simulation
|
||||
// - EVM module queries
|
||||
// - Fee market module queries
|
||||
type QueryClient struct {
|
||||
tx.ServiceClient
|
||||
evmtypes.QueryClient
|
||||
|
9
tests/integration_tests/.isort.cfg
Normal file
9
tests/integration_tests/.isort.cfg
Normal file
@ -0,0 +1,9 @@
|
||||
[settings]
|
||||
# compatible with black
|
||||
# https://black.readthedocs.io/en/stable/the_black_code_style.html
|
||||
multi_line_output = 3
|
||||
include_trailing_comma = True
|
||||
force_grid_wrap = 0
|
||||
use_parentheses = True
|
||||
ensure_newline_before_comments = True
|
||||
line_length = 88
|
12
tests/integration_tests/configs/pruned_node.jsonnet
Normal file
12
tests/integration_tests/configs/pruned_node.jsonnet
Normal file
@ -0,0 +1,12 @@
|
||||
local config = import 'default.jsonnet';
|
||||
|
||||
config {
|
||||
'ethermint_9000-1'+: {
|
||||
'app-config'+: {
|
||||
pruning: 'everything',
|
||||
'state-sync'+: {
|
||||
'snapshot-interval': 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
142
tests/integration_tests/test_pruned_node.py
Normal file
142
tests/integration_tests/test_pruned_node.py
Normal file
@ -0,0 +1,142 @@
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from eth_bloom import BloomFilter
|
||||
from eth_utils import abi, big_endian_to_int
|
||||
from hexbytes import HexBytes
|
||||
from web3.datastructures import AttributeDict
|
||||
|
||||
from .network import setup_custom_ethermint
|
||||
from .utils import (
|
||||
ADDRS,
|
||||
CONTRACTS,
|
||||
KEYS,
|
||||
deploy_contract,
|
||||
sign_transaction,
|
||||
w3_wait_for_new_blocks,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def pruned(request, tmp_path_factory):
|
||||
"""start-cronos
|
||||
params: enable_auto_deployment
|
||||
"""
|
||||
yield from setup_custom_ethermint(
|
||||
tmp_path_factory.mktemp("pruned"),
|
||||
26900,
|
||||
Path(__file__).parent / "configs/pruned_node.jsonnet",
|
||||
)
|
||||
|
||||
|
||||
def test_pruned_node(pruned):
|
||||
"""
|
||||
test basic json-rpc apis works in pruned node
|
||||
"""
|
||||
w3 = pruned.w3
|
||||
erc20 = deploy_contract(
|
||||
w3,
|
||||
CONTRACTS["TestERC20A"],
|
||||
key=KEYS["validator"],
|
||||
)
|
||||
tx = erc20.functions.transfer(ADDRS["community"], 10).buildTransaction(
|
||||
{"from": ADDRS["validator"]}
|
||||
)
|
||||
signed = sign_transaction(w3, tx, KEYS["validator"])
|
||||
txhash = w3.eth.send_raw_transaction(signed.rawTransaction)
|
||||
print("wait for prunning happens")
|
||||
w3_wait_for_new_blocks(w3, 10)
|
||||
|
||||
tx_receipt = w3.eth.get_transaction_receipt(txhash.hex())
|
||||
assert len(tx_receipt.logs) == 1
|
||||
expect_log = {
|
||||
"address": erc20.address,
|
||||
"topics": [
|
||||
HexBytes(
|
||||
abi.event_signature_to_log_topic("Transfer(address,address,uint256)")
|
||||
),
|
||||
HexBytes(b"\x00" * 12 + HexBytes(ADDRS["validator"])),
|
||||
HexBytes(b"\x00" * 12 + HexBytes(ADDRS["community"])),
|
||||
],
|
||||
"data": "0x000000000000000000000000000000000000000000000000000000000000000a",
|
||||
"transactionIndex": 0,
|
||||
"logIndex": 0,
|
||||
"removed": False,
|
||||
}
|
||||
assert expect_log.items() <= tx_receipt.logs[0].items()
|
||||
|
||||
# check get_balance and eth_call don't work on pruned state
|
||||
# we need to check error message here.
|
||||
# `get_balance` returns unmarshallJson and thats not what it should
|
||||
res = w3.eth.get_balance(ADDRS["validator"], "latest")
|
||||
assert res > 0
|
||||
|
||||
pruned_res = pruned.w3.provider.make_request(
|
||||
"eth_getBalance", [ADDRS["validator"], hex(tx_receipt.blockNumber)]
|
||||
)
|
||||
assert "error" in pruned_res
|
||||
assert (
|
||||
pruned_res["error"]["message"] == "couldn't fetch balance. Node state is pruned"
|
||||
)
|
||||
|
||||
with pytest.raises(Exception):
|
||||
erc20.caller(block_identifier=tx_receipt.blockNumber).balanceOf(
|
||||
ADDRS["validator"]
|
||||
)
|
||||
|
||||
# check block bloom
|
||||
block = w3.eth.get_block(tx_receipt.blockNumber)
|
||||
|
||||
assert "baseFeePerGas" in block
|
||||
assert block.miner == "0x0000000000000000000000000000000000000000"
|
||||
bloom = BloomFilter(big_endian_to_int(block.logsBloom))
|
||||
assert HexBytes(erc20.address) in bloom
|
||||
for topic in expect_log["topics"]:
|
||||
assert topic in bloom
|
||||
|
||||
tx1 = w3.eth.get_transaction(txhash)
|
||||
tx2 = w3.eth.get_transaction_by_block(
|
||||
tx_receipt.blockNumber, tx_receipt.transactionIndex
|
||||
)
|
||||
exp_tx = AttributeDict(
|
||||
{
|
||||
"from": "0x57f96e6B86CdeFdB3d412547816a82E3E0EbF9D2",
|
||||
"gas": 51542,
|
||||
"input": (
|
||||
"0xa9059cbb000000000000000000000000378c50d9264c63f3f92b806d4ee56e"
|
||||
"9d86ffb3ec000000000000000000000000000000000000000000000000000000"
|
||||
"000000000a"
|
||||
),
|
||||
"nonce": 2,
|
||||
"to": erc20.address,
|
||||
"transactionIndex": 0,
|
||||
"value": 0,
|
||||
"type": "0x2",
|
||||
"accessList": [],
|
||||
"chainId": "0x2328",
|
||||
}
|
||||
)
|
||||
assert tx1 == tx2
|
||||
for name in exp_tx.keys():
|
||||
assert tx1[name] == tx2[name] == exp_tx[name]
|
||||
|
||||
logs = w3.eth.get_logs(
|
||||
{
|
||||
"fromBlock": tx_receipt.blockNumber,
|
||||
"toBlock": tx_receipt.blockNumber,
|
||||
}
|
||||
)[0]
|
||||
assert (
|
||||
"address" in logs
|
||||
and logs["address"] == "0x68542BD12B41F5D51D6282Ec7D91D7d0D78E4503"
|
||||
)
|
||||
assert "topics" in logs and len(logs["topics"]) == 3
|
||||
assert logs["topics"][0] == HexBytes(
|
||||
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
|
||||
)
|
||||
assert logs["topics"][1] == HexBytes(
|
||||
"0x00000000000000000000000057f96e6b86cdefdb3d412547816a82e3e0ebf9d2"
|
||||
)
|
||||
assert logs["topics"][2] == HexBytes(
|
||||
"0x000000000000000000000000378c50d9264c63f3f92b806d4ee56e9d86ffb3ec"
|
||||
)
|
@ -1,10 +1,44 @@
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from eth_account import Account
|
||||
from web3._utils.transactions import fill_nonce, fill_transaction_defaults
|
||||
|
||||
load_dotenv(Path(__file__).parent.parent.parent / "scripts/.env")
|
||||
Account.enable_unaudited_hdwallet_features()
|
||||
ACCOUNTS = {
|
||||
"validator": Account.from_mnemonic(os.getenv("VALIDATOR1_MNEMONIC")),
|
||||
"community": Account.from_mnemonic(os.getenv("COMMUNITY_MNEMONIC")),
|
||||
"signer1": Account.from_mnemonic(os.getenv("SIGNER1_MNEMONIC")),
|
||||
"signer2": Account.from_mnemonic(os.getenv("SIGNER2_MNEMONIC")),
|
||||
}
|
||||
KEYS = {name: account.key for name, account in ACCOUNTS.items()}
|
||||
ADDRS = {name: account.address for name, account in ACCOUNTS.items()}
|
||||
ETHERMINT_ADDRESS_PREFIX = "ethm"
|
||||
TEST_CONTRACTS = {
|
||||
"TestERC20A": "TestERC20A.sol",
|
||||
}
|
||||
|
||||
|
||||
def contract_path(name, filename):
|
||||
return (
|
||||
Path(__file__).parent
|
||||
/ "contracts/artifacts/contracts/"
|
||||
/ filename
|
||||
/ (name + ".json")
|
||||
)
|
||||
|
||||
|
||||
CONTRACTS = {
|
||||
**{
|
||||
name: contract_path(name, filename) for name, filename in TEST_CONTRACTS.items()
|
||||
},
|
||||
}
|
||||
|
||||
Account.enable_unaudited_hdwallet_features()
|
||||
|
||||
ACCOUNTS = {
|
||||
@ -32,6 +66,29 @@ def wait_for_port(port, host="127.0.0.1", timeout=40.0):
|
||||
) from ex
|
||||
|
||||
|
||||
def w3_wait_for_new_blocks(w3, n):
|
||||
begin_height = w3.eth.block_number
|
||||
while True:
|
||||
time.sleep(0.5)
|
||||
cur_height = w3.eth.block_number
|
||||
if cur_height - begin_height >= n:
|
||||
break
|
||||
|
||||
|
||||
def deploy_contract(w3, jsonfile, args=(), key=KEYS["validator"]):
|
||||
"""
|
||||
deploy contract and return the deployed contract instance
|
||||
"""
|
||||
acct = Account.from_key(key)
|
||||
info = json.loads(jsonfile.read_text())
|
||||
contract = w3.eth.contract(abi=info["abi"], bytecode=info["bytecode"])
|
||||
tx = contract.constructor(*args).buildTransaction({"from": acct.address})
|
||||
txreceipt = send_transaction(w3, tx, key)
|
||||
assert txreceipt.status == 1
|
||||
address = txreceipt.contractAddress
|
||||
return w3.eth.contract(address=address, abi=info["abi"])
|
||||
|
||||
|
||||
def fill_defaults(w3, tx):
|
||||
return fill_nonce(w3, fill_transaction_defaults(w3, tx))
|
||||
|
||||
|
@ -283,8 +283,13 @@ func (k *Keeper) GetNonce(ctx sdk.Context, addr common.Address) uint64 {
|
||||
// GetBalance load account's balance of gas token
|
||||
func (k *Keeper) GetBalance(ctx sdk.Context, addr common.Address) *big.Int {
|
||||
cosmosAddr := sdk.AccAddress(addr.Bytes())
|
||||
params := k.GetParams(ctx)
|
||||
coin := k.bankKeeper.GetBalance(ctx, cosmosAddr, params.EvmDenom)
|
||||
evmDenom := ""
|
||||
k.paramSpace.GetIfExists(ctx, types.ParamStoreKeyEVMDenom, &evmDenom)
|
||||
// if node is pruned, params is empty. Return invalid value
|
||||
if evmDenom == "" {
|
||||
return big.NewInt(-1)
|
||||
}
|
||||
coin := k.bankKeeper.GetBalance(ctx, cosmosAddr, evmDenom)
|
||||
return coin.Amount.BigInt()
|
||||
}
|
||||
|
||||
|
@ -178,7 +178,7 @@ func (k Keeper) GetHashFn(ctx sdk.Context) vm.GetHashFunc {
|
||||
// ApplyTransaction runs and attempts to perform a state transition with the given transaction (i.e Message), that will
|
||||
// only be persisted (committed) to the underlying KVStore if the transaction does not fail.
|
||||
//
|
||||
// Gas tracking
|
||||
// # Gas tracking
|
||||
//
|
||||
// Ethereum consumes gas according to the EVM opcodes instead of general reads and writes to store. Because of this, the
|
||||
// state transition needs to ignore the SDK gas consumption mechanism defined by the GasKVStore and instead consume the
|
||||
@ -309,18 +309,18 @@ func (k *Keeper) ApplyTransaction(ctx sdk.Context, tx *ethtypes.Transaction) (*t
|
||||
// If the message fails, the VM execution error with the reason will be returned to the client
|
||||
// and the transaction won't be committed to the store.
|
||||
//
|
||||
// Reverted state
|
||||
// # Reverted state
|
||||
//
|
||||
// The snapshot and rollback are supported by the `statedb.StateDB`.
|
||||
//
|
||||
// Different Callers
|
||||
// # Different Callers
|
||||
//
|
||||
// It's called in three scenarios:
|
||||
// 1. `ApplyTransaction`, in the transaction processing flow.
|
||||
// 2. `EthCall/EthEstimateGas` grpc query handler.
|
||||
// 3. Called by other native modules directly.
|
||||
//
|
||||
// Prechecks and Preprocessing
|
||||
// # Prechecks and Preprocessing
|
||||
//
|
||||
// All relevant state transition prechecks for the MsgEthereumTx are performed on the AnteHandler,
|
||||
// prior to running the transaction against the state. The prechecks run are the following:
|
||||
@ -336,11 +336,11 @@ func (k *Keeper) ApplyTransaction(ctx sdk.Context, tx *ethtypes.Transaction) (*t
|
||||
//
|
||||
// 1. set up the initial access list (iff fork > Berlin)
|
||||
//
|
||||
// Tracer parameter
|
||||
// # Tracer parameter
|
||||
//
|
||||
// It should be a `vm.Tracer` object or nil, if pass `nil`, it'll create a default one based on keeper options.
|
||||
//
|
||||
// Commit parameter
|
||||
// # Commit parameter
|
||||
//
|
||||
// If commit is true, the `StateDB` will be committed, otherwise discarded.
|
||||
func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, msg core.Message, tracer vm.EVMLogger, commit bool, cfg *types.EVMConfig, txConfig statedb.TxConfig) (*types.MsgEthereumTxResponse, error) {
|
||||
|
@ -250,8 +250,8 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject)
|
||||
// CreateAccount is called during the EVM CREATE operation. The situation might arise that
|
||||
// a contract does the following:
|
||||
//
|
||||
// 1. sends funds to sha(account ++ (nonce + 1))
|
||||
// 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1)
|
||||
// 1. sends funds to sha(account ++ (nonce + 1))
|
||||
// 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1)
|
||||
//
|
||||
// Carrying over the balance ensures that Ether doesn't disappear.
|
||||
func (s *StateDB) CreateAccount(addr common.Address) {
|
||||
|
@ -89,7 +89,7 @@ func (log *Log) ToEthereum() *ethtypes.Log {
|
||||
}
|
||||
|
||||
func NewLogsFromEth(ethlogs []*ethtypes.Log) []*Log {
|
||||
var logs []*Log // nolint: prealloc
|
||||
var logs []*Log //nolint: prealloc
|
||||
for _, ethlog := range ethlogs {
|
||||
logs = append(logs, NewLogFromEth(ethlog))
|
||||
}
|
||||
@ -99,7 +99,7 @@ func NewLogsFromEth(ethlogs []*ethtypes.Log) []*Log {
|
||||
|
||||
// LogsToEthereum casts the Ethermint Logs to a slice of Ethereum Logs.
|
||||
func LogsToEthereum(logs []*Log) []*ethtypes.Log {
|
||||
var ethLogs []*ethtypes.Log // nolint: prealloc
|
||||
var ethLogs []*ethtypes.Log //nolint: prealloc
|
||||
for i := range logs {
|
||||
ethLogs = append(ethLogs, logs[i].ToEthereum())
|
||||
}
|
||||
|
@ -71,8 +71,9 @@ func NewTxDataFromTx(tx *ethtypes.Transaction) (TxData, error) {
|
||||
//
|
||||
// CONTRACT: v value is either:
|
||||
//
|
||||
// - {0,1} + CHAIN_ID * 2 + 35, if EIP155 is used
|
||||
// - {0,1} + 27, otherwise
|
||||
// - {0,1} + CHAIN_ID * 2 + 35, if EIP155 is used
|
||||
// - {0,1} + 27, otherwise
|
||||
//
|
||||
// Ref: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md
|
||||
func DeriveChainID(v *big.Int) *big.Int {
|
||||
if v == nil || v.Sign() < 1 {
|
||||
|
@ -19,7 +19,7 @@ const (
|
||||
// prefix bytes for the feemarket persistent store
|
||||
const (
|
||||
prefixBlockGasWanted = iota + 1
|
||||
deprecatedPrefixBaseFee // nolint
|
||||
deprecatedPrefixBaseFee //nolint
|
||||
)
|
||||
|
||||
const (
|
||||
|
Loading…
Reference in New Issue
Block a user