Skip to content
This repository has been archived by the owner on Apr 4, 2024. It is now read-only.

benchmark contract execution #436

Merged
merged 5 commits into from
Aug 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -212,4 +212,12 @@ contract StandardTokenMock is ERC20 {
allowed[_account][msg.sender] = allowed[_account][msg.sender].sub(_amount);
_burn(_account, _amount);
}

// For benchmarks
event TestLog(address sender, uint i);
function benchmarkLogs(uint n) public {
for (uint i=0; i<n; i++) {
emit TestLog(msg.sender, i);
}
}
}
4 changes: 2 additions & 2 deletions x/evm/keeper/ERC20Contract.json

Large diffs are not rendered by default.

79 changes: 79 additions & 0 deletions x/evm/keeper/benchevm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package keeper_test

import (
"math/big"
"testing"

"github.com/stretchr/testify/require"

sdk "github.com/cosmos/cosmos-sdk/types"
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
"github.com/ethereum/go-ethereum/common"

ethtypes "github.com/ethereum/go-ethereum/core/types"
ethermint "github.com/tharsis/ethermint/types"
"github.com/tharsis/ethermint/x/evm/types"
)

func SetupContract(b *testing.B) (*KeeperTestSuite, common.Address) {
suite := KeeperTestSuite{}
suite.DoSetupTest(b)

amt := sdk.Coins{ethermint.NewPhotonCoinInt64(1000000000000000000)}
err := suite.app.BankKeeper.MintCoins(suite.ctx, types.ModuleName, amt)
require.NoError(b, err)
err = suite.app.BankKeeper.SendCoinsFromModuleToAccount(suite.ctx, types.ModuleName, suite.address.Bytes(), amt)
require.NoError(b, err)

contractAddr := suite.DeployTestContract(b, suite.address, sdk.NewIntWithDecimal(1000, 18).BigInt())
suite.Commit()

return &suite, contractAddr
}

type TxBuilder func(suite *KeeperTestSuite, contract common.Address) *types.MsgEthereumTx

func DoBenchmark(b *testing.B, txBuilder TxBuilder) {
suite, contractAddr := SetupContract(b)

msg := txBuilder(suite, contractAddr)
msg.From = suite.address.Hex()
err := msg.Sign(ethtypes.LatestSignerForChainID(suite.app.EvmKeeper.ChainID()), suite.signer)
require.NoError(b, err)

b.ResetTimer()
b.StartTimer()
for i := 0; i < b.N; i++ {
ctx, _ := suite.ctx.CacheContext()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why cache context here?

Copy link
Contributor Author

@yihuang yihuang Aug 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make each run independent, we don't want to mutate the shared storage for each runs.


// deduct fee first
txData, err := types.UnpackTxData(msg.Data)
require.NoError(b, err)

fees := sdk.Coins{sdk.NewCoin(suite.EvmDenom(), sdk.NewIntFromBigInt(txData.Fee()))}
err = authante.DeductFees(suite.app.BankKeeper, suite.ctx, suite.app.AccountKeeper.GetAccount(ctx, msg.GetFrom()), fees)
require.NoError(b, err)

rsp, err := suite.app.EvmKeeper.EthereumTx(sdk.WrapSDKContext(ctx), msg)
require.NoError(b, err)
require.False(b, rsp.Failed())
}
}

func BenchmarkTokenTransfer(b *testing.B) {
DoBenchmark(b, func(suite *KeeperTestSuite, contract common.Address) *types.MsgEthereumTx {
input, err := ContractABI.Pack("transfer", common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), big.NewInt(1000))
require.NoError(b, err)
nonce := suite.app.EvmKeeper.GetNonce(suite.address)
return types.NewTx(suite.app.EvmKeeper.ChainID(), nonce, &contract, big.NewInt(0), 410000, big.NewInt(1), input, nil)
})
}

func BenchmarkEmitLogs(b *testing.B) {
DoBenchmark(b, func(suite *KeeperTestSuite, contract common.Address) *types.MsgEthereumTx {
input, err := ContractABI.Pack("benchmarkLogs", big.NewInt(1000))
require.NoError(b, err)
nonce := suite.app.EvmKeeper.GetNonce(suite.address)
return types.NewTx(suite.app.EvmKeeper.ChainID(), nonce, &contract, big.NewInt(0), 4100000, big.NewInt(1), input, nil)
})
}
76 changes: 5 additions & 71 deletions x/evm/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
package keeper_test

import (
_ "embed"
"encoding/json"
"fmt"
"math/big"

"google.golang.org/grpc/metadata"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
ethcmn "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
ethcrypto "github.com/ethereum/go-ethereum/crypto"

sdk "github.com/cosmos/cosmos-sdk/types"
Expand All @@ -27,29 +24,6 @@ import (
//Not valid Ethereum address
const invalidAddress = "0x0000"

var (
//go:embed ERC20Contract.json
compiledContractJSON []byte
contractBin []byte
contractABI abi.ABI
)

func init() {
var tmp struct {
Abi string
Bin string
}
err := json.Unmarshal(compiledContractJSON, &tmp)
if err != nil {
panic(err)
}
contractBin = common.FromHex(tmp.Bin)
err = json.Unmarshal([]byte(tmp.Abi), &contractABI)
if err != nil {
panic(err)
}
}

func (suite *KeeperTestSuite) TestQueryAccount() {
var (
req *types.QueryAccountRequest
Expand Down Expand Up @@ -707,46 +681,6 @@ func (suite *KeeperTestSuite) TestQueryValidatorAccount() {
}
}

// DeployTestContract deploy a test erc20 contract and returns the contract address
func (suite *KeeperTestSuite) deployTestContract(owner common.Address, supply *big.Int) common.Address {
ctx := sdk.WrapSDKContext(suite.ctx)
chainID := suite.app.EvmKeeper.ChainID()

ctorArgs, err := contractABI.Pack("", owner, supply)
suite.Require().NoError(err)

data := append(contractBin, ctorArgs...)
args, err := json.Marshal(&types.CallArgs{
From: &suite.address,
Data: (*hexutil.Bytes)(&data),
})
suite.Require().NoError(err)

res, err := suite.queryClient.EstimateGas(ctx, &types.EthCallRequest{
Args: args,
GasCap: 25_000_000,
})
suite.Require().NoError(err)

nonce := suite.app.EvmKeeper.GetNonce(suite.address)
erc20DeployTx := types.NewTxContract(
chainID,
nonce,
nil, // amount
res.Gas, // gasLimit
nil, // gasPrice
data, // input
nil, // accesses
)
erc20DeployTx.From = suite.address.Hex()
err = erc20DeployTx.Sign(ethtypes.LatestSignerForChainID(chainID), suite.signer)
suite.Require().NoError(err)
rsp, err := suite.app.EvmKeeper.EthereumTx(ctx, erc20DeployTx)
suite.Require().NoError(err)
suite.Require().Empty(rsp.VmError)
return crypto.CreateAddress(suite.address, nonce)
}

func (suite *KeeperTestSuite) TestEstimateGas() {
ctx := sdk.WrapSDKContext(suite.ctx)
gasHelper := hexutil.Uint64(20000)
Expand Down Expand Up @@ -784,19 +718,19 @@ func (suite *KeeperTestSuite) TestEstimateGas() {
}, false, 0},
// estimate gas of an erc20 contract deployment, the exact gas number is checked with geth
{"contract deployment", func() {
ctorArgs, err := contractABI.Pack("", &suite.address, sdk.NewIntWithDecimal(1000, 18).BigInt())
ctorArgs, err := ContractABI.Pack("", &suite.address, sdk.NewIntWithDecimal(1000, 18).BigInt())
suite.Require().NoError(err)
data := append(contractBin, ctorArgs...)
data := append(ContractBin, ctorArgs...)
args = types.CallArgs{
From: &suite.address,
Data: (*hexutil.Bytes)(&data),
}
}, true, 1144643},
}, true, 1186778},
// estimate gas of an erc20 transfer, the exact gas number is checked with geth
{"erc20 transfer", func() {
contractAddr := suite.deployTestContract(suite.address, sdk.NewIntWithDecimal(1000, 18).BigInt())
contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdk.NewIntWithDecimal(1000, 18).BigInt())
suite.Commit()
transferData, err := contractABI.Pack("transfer", common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), big.NewInt(1000))
transferData, err := ContractABI.Pack("transfer", common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), big.NewInt(1000))
suite.Require().NoError(err)
args = types.CallArgs{To: &contractAddr, From: &suite.address, Data: (*hexutil.Bytes)(&transferData)}
}, true, 51880},
Expand Down
101 changes: 90 additions & 11 deletions x/evm/keeper/keeper_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package keeper_test

import (
_ "embed"
"encoding/json"
"math/big"
"testing"
"time"

"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"

"github.com/cosmos/cosmos-sdk/baseapp"
Expand All @@ -20,19 +24,46 @@ import (
"github.com/tharsis/ethermint/app"
"github.com/tharsis/ethermint/crypto/ethsecp256k1"
"github.com/tharsis/ethermint/encoding"
"github.com/tharsis/ethermint/server/config"
"github.com/tharsis/ethermint/tests"
ethermint "github.com/tharsis/ethermint/types"
"github.com/tharsis/ethermint/x/evm/types"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
ethcmn "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
ethcrypto "github.com/ethereum/go-ethereum/crypto"

abci "github.com/tendermint/tendermint/abci/types"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
)

var (
//go:embed ERC20Contract.json
compiledContractJSON []byte
ContractBin []byte
ContractABI abi.ABI
)

func init() {
var tmp struct {
Abi string
Bin string
}
err := json.Unmarshal(compiledContractJSON, &tmp)
if err != nil {
panic(err)
}
ContractBin = common.FromHex(tmp.Bin)
err = json.Unmarshal([]byte(tmp.Abi), &ContractABI)
if err != nil {
panic(err)
}
}

var testTokens = sdk.NewIntWithDecimal(1000, 18)

type KeeperTestSuite struct {
Expand All @@ -52,18 +83,19 @@ type KeeperTestSuite struct {
signer keyring.Signer
}

func (suite *KeeperTestSuite) SetupTest() {
/// DoSetupTest setup test environment, it uses`require.TestingT` to support both `testing.T` and `testing.B`.
func (suite *KeeperTestSuite) DoSetupTest(t require.TestingT) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, can you add a comment on why you are using require.TestingT instead of just making this the setup function as before?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also on why you are using require.XXX(t, ...) instead of suite.Require(). Otherwise someone without the context could revert the changes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make it work on both testing.B and testing.T.

Added a line of comment to explain.

checkTx := false

// account key
priv, err := ethsecp256k1.GenerateKey()
suite.Require().NoError(err)
require.NoError(t, err)
suite.address = ethcmn.BytesToAddress(priv.PubKey().Address().Bytes())
suite.signer = tests.NewSigner(priv)

// consensus key
priv, err = ethsecp256k1.GenerateKey()
suite.Require().NoError(err)
require.NoError(t, err)
suite.consAddress = sdk.ConsAddress(priv.PubKey().Address())

suite.app = app.Setup(checkTx)
Expand All @@ -89,9 +121,9 @@ func (suite *KeeperTestSuite) SetupTest() {
valAddr := sdk.ValAddress(suite.address.Bytes())
validator, err := stakingtypes.NewValidator(valAddr, priv.PubKey(), stakingtypes.Description{})
err = suite.app.StakingKeeper.SetValidatorByConsAddr(suite.ctx, validator)
suite.Require().NoError(err)
require.NoError(t, err)
err = suite.app.StakingKeeper.SetValidatorByConsAddr(suite.ctx, validator)
suite.Require().NoError(err)
require.NoError(t, err)
suite.app.StakingKeeper.SetValidator(suite.ctx, validator)

encodingConfig := encoding.MakeConfig(app.ModuleBasics)
Expand All @@ -101,13 +133,20 @@ func (suite *KeeperTestSuite) SetupTest() {

// mint some tokens to coinbase address
_, bankKeeper := suite.initKeepersWithmAccPerms()
ctx := sdk.WrapSDKContext(suite.ctx)
rsp, err := suite.queryClient.Params(ctx, &types.QueryParamsRequest{})
suite.Require().NoError(err)
initCoin := sdk.NewCoins(sdk.NewCoin(rsp.Params.EvmDenom, testTokens))

require.NoError(t, err)
initCoin := sdk.NewCoins(sdk.NewCoin(suite.EvmDenom(), testTokens))
err = simapp.FundAccount(bankKeeper, suite.ctx, acc.GetAddress(), initCoin)
suite.Require().NoError(err)
require.NoError(t, err)
}

func (suite *KeeperTestSuite) SetupTest() {
suite.DoSetupTest(suite.T())
}

func (suite *KeeperTestSuite) EvmDenom() string {
ctx := sdk.WrapSDKContext(suite.ctx)
rsp, _ := suite.queryClient.Params(ctx, &types.QueryParamsRequest{})
return rsp.Params.EvmDenom
}

// Commit and begin new block
Expand Down Expand Up @@ -146,6 +185,46 @@ func (suite *KeeperTestSuite) initKeepersWithmAccPerms() (authkeeper.AccountKeep
return authKeeper, keeper
}

// DeployTestContract deploy a test erc20 contract and returns the contract address
func (suite *KeeperTestSuite) DeployTestContract(t require.TestingT, owner common.Address, supply *big.Int) common.Address {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curious of why we are using require.TestingT instead of testing.T

Copy link
Contributor Author

@yihuang yihuang Aug 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make it work on both testing.B and testing.T.

Added a line of comment to explain.

ctx := sdk.WrapSDKContext(suite.ctx)
chainID := suite.app.EvmKeeper.ChainID()

ctorArgs, err := ContractABI.Pack("", owner, supply)
require.NoError(t, err)

data := append(ContractBin, ctorArgs...)
args, err := json.Marshal(&types.CallArgs{
From: &suite.address,
Data: (*hexutil.Bytes)(&data),
})
require.NoError(t, err)

res, err := suite.queryClient.EstimateGas(ctx, &types.EthCallRequest{
Args: args,
GasCap: uint64(config.DefaultGasCap),
})
require.NoError(t, err)

nonce := suite.app.EvmKeeper.GetNonce(suite.address)
erc20DeployTx := types.NewTxContract(
chainID,
nonce,
nil, // amount
res.Gas, // gasLimit
nil, // gasPrice
data, // input
nil, // accesses
)
erc20DeployTx.From = suite.address.Hex()
err = erc20DeployTx.Sign(ethtypes.LatestSignerForChainID(chainID), suite.signer)
require.NoError(t, err)
rsp, err := suite.app.EvmKeeper.EthereumTx(ctx, erc20DeployTx)
require.NoError(t, err)
require.Empty(t, rsp.VmError)
return crypto.CreateAddress(suite.address, nonce)
}

func TestKeeperTestSuite(t *testing.T) {
suite.Run(t, new(KeeperTestSuite))
}