forked from cerc-io/laconicd-deprecated
update fork
This commit is contained in:
@@ -7,12 +7,18 @@ import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
|
||||
"github.com/cerc-io/laconicd/rpc/types"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
|
||||
|
||||
// . "github.com/onsi/ginkgo"
|
||||
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
// . "github.com/onsi/ginkgo/v2"
|
||||
// . "github.com/onsi/gomega"
|
||||
|
||||
sdkmath "cosmossdk.io/math"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
@@ -132,7 +138,6 @@ func (s *IntegrationTestSuite) TestBlock() {
|
||||
s.Require().NotNil(blockByNum)
|
||||
|
||||
// compare the ethereum header with the tendermint header
|
||||
s.Require().Equal(len(block.Block.Txs), len(blockByNum.Body().Transactions))
|
||||
s.Require().Equal(block.Block.LastBlockID.Hash.Bytes(), blockByNum.Header().ParentHash.Bytes())
|
||||
|
||||
hash := common.BytesToHash(block.Block.Hash())
|
||||
@@ -143,7 +148,12 @@ func (s *IntegrationTestSuite) TestBlock() {
|
||||
blockByHash, err := s.network.Validators[0].JSONRPCClient.BlockByHash(s.ctx, hash)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(blockByHash)
|
||||
s.Require().Equal(blockByNum, blockByHash)
|
||||
|
||||
// Compare blockByNumber and blockByHash results
|
||||
s.Require().Equal(blockByNum.Hash(), blockByHash.Hash())
|
||||
s.Require().Equal(blockByNum.Transactions().Len(), blockByHash.Transactions().Len())
|
||||
s.Require().Equal(blockByNum.ParentHash(), blockByHash.ParentHash())
|
||||
s.Require().Equal(blockByNum.Root(), blockByHash.Root())
|
||||
|
||||
// TODO: parse Tm block to Ethereum and compare
|
||||
}
|
||||
@@ -374,7 +384,7 @@ func (s *IntegrationTestSuite) TestGetBalance() {
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) TestGetLogs() {
|
||||
//TODO create tests to cover different filterQuery params
|
||||
// TODO create tests to cover different filterQuery params
|
||||
_, contractAddr := s.deployERC20Contract()
|
||||
|
||||
blockNum, err := s.network.Validators[0].JSONRPCClient.BlockNumber(s.ctx)
|
||||
@@ -400,7 +410,7 @@ func (s *IntegrationTestSuite) TestGetLogs() {
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) TestTransactionReceiptERC20Transfer() {
|
||||
//start with clean block
|
||||
// start with clean block
|
||||
err := s.network.WaitForNextBlock()
|
||||
s.Require().NoError(err)
|
||||
// deploy erc20 contract
|
||||
@@ -567,7 +577,7 @@ func (s *IntegrationTestSuite) deployContract(data []byte) (transaction common.H
|
||||
// Deploys erc20 contract, commits block and returns contract address
|
||||
func (s *IntegrationTestSuite) deployERC20Contract() (transaction common.Hash, contractAddr common.Address) {
|
||||
owner := common.BytesToAddress(s.network.Validators[0].Address)
|
||||
supply := math.NewIntWithDecimal(1000, 18).BigInt()
|
||||
supply := sdkmath.NewIntWithDecimal(1000, 18).BigInt()
|
||||
|
||||
ctorArgs, err := evmtypes.ERC20Contract.ABI.Pack("", owner, supply)
|
||||
s.Require().NoError(err)
|
||||
@@ -633,7 +643,6 @@ func (s *IntegrationTestSuite) transferERC20Transaction(contractAddr, to common.
|
||||
receipt := s.expectSuccessReceipt(ercTransferTx.AsTransaction().Hash())
|
||||
s.Require().NotEmpty(receipt.Logs)
|
||||
return ercTransferTx.AsTransaction().Hash()
|
||||
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) storeValueStorageContract(contractAddr common.Address, amount *big.Int) common.Hash {
|
||||
@@ -747,3 +756,142 @@ func (s *IntegrationTestSuite) TestPendingTransactionFilter() {
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal([]common.Hash{signedTx.Hash()}, filterResult)
|
||||
}
|
||||
|
||||
// TODO: add transactionIndex tests once we have OpenRPC interfaces
|
||||
func (s *IntegrationTestSuite) TestBatchETHTransactions() {
|
||||
const ethTxs = 2
|
||||
txBuilder := s.network.Validators[0].ClientCtx.TxConfig.NewTxBuilder()
|
||||
builder, ok := txBuilder.(authtx.ExtensionOptionsTxBuilder)
|
||||
s.Require().True(ok)
|
||||
|
||||
recipient := common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec")
|
||||
accountNonce := s.getAccountNonce(recipient)
|
||||
feeAmount := sdk.ZeroInt()
|
||||
|
||||
var gasLimit uint64
|
||||
var msgs []sdk.Msg
|
||||
|
||||
for i := 0; i < ethTxs; i++ {
|
||||
chainId, err := s.network.Validators[0].JSONRPCClient.ChainID(s.ctx)
|
||||
s.Require().NoError(err)
|
||||
|
||||
gasPrice := s.getGasPrice()
|
||||
from := common.BytesToAddress(s.network.Validators[0].Address)
|
||||
nonce := accountNonce + uint64(i) + 1
|
||||
|
||||
msgTx := evmtypes.NewTx(
|
||||
chainId,
|
||||
nonce,
|
||||
&recipient,
|
||||
big.NewInt(10),
|
||||
100000,
|
||||
gasPrice,
|
||||
big.NewInt(200),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
msgTx.From = from.Hex()
|
||||
err = msgTx.Sign(s.ethSigner, s.network.Validators[0].ClientCtx.Keyring)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// A valid msg should have empty `From`
|
||||
msgTx.From = ""
|
||||
msgs = append(msgs, msgTx.GetMsgs()...)
|
||||
txData, err := evmtypes.UnpackTxData(msgTx.Data)
|
||||
s.Require().NoError(err)
|
||||
feeAmount = feeAmount.Add(sdkmath.NewIntFromBigInt(txData.Fee()))
|
||||
gasLimit = gasLimit + txData.GetGas()
|
||||
}
|
||||
|
||||
option, err := codectypes.NewAnyWithValue(&evmtypes.ExtensionOptionsEthereumTx{})
|
||||
s.Require().NoError(err)
|
||||
|
||||
queryClient := types.NewQueryClient(s.network.Validators[0].ClientCtx)
|
||||
res, err := queryClient.Params(s.ctx, &evmtypes.QueryParamsRequest{})
|
||||
|
||||
fees := make(sdk.Coins, 0)
|
||||
if feeAmount.Sign() > 0 {
|
||||
fees = fees.Add(sdk.Coin{Denom: res.Params.EvmDenom, Amount: feeAmount})
|
||||
}
|
||||
|
||||
builder.SetExtensionOptions(option)
|
||||
err = builder.SetMsgs(msgs...)
|
||||
s.Require().NoError(err)
|
||||
builder.SetFeeAmount(fees)
|
||||
builder.SetGasLimit(gasLimit)
|
||||
|
||||
tx := builder.GetTx()
|
||||
txEncoder := s.network.Validators[0].ClientCtx.TxConfig.TxEncoder()
|
||||
txBytes, err := txEncoder(tx)
|
||||
s.Require().NoError(err)
|
||||
|
||||
syncCtx := s.network.Validators[0].ClientCtx.WithBroadcastMode(flags.BroadcastBlock)
|
||||
txResponse, err := syncCtx.BroadcastTx(txBytes)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(uint32(0), txResponse.Code)
|
||||
|
||||
block, err := s.network.Validators[0].JSONRPCClient.BlockByNumber(s.ctx, big.NewInt(txResponse.Height))
|
||||
s.Require().NoError(err)
|
||||
|
||||
txs := block.Transactions()
|
||||
s.Require().Len(txs, ethTxs)
|
||||
for i, tx := range txs {
|
||||
s.Require().Equal(accountNonce+uint64(i)+1, tx.Nonce())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) TestGasConsumptionOnNormalTransfer() {
|
||||
testCases := []struct {
|
||||
name string
|
||||
gasLimit uint64
|
||||
expectedGasUsed uint64
|
||||
}{
|
||||
{
|
||||
"gas used is the same as gas limit",
|
||||
21000,
|
||||
21000,
|
||||
},
|
||||
{
|
||||
"gas used is half of Gas limit",
|
||||
70000,
|
||||
35000,
|
||||
},
|
||||
{
|
||||
"gas used is less than half of gasLimit",
|
||||
30000,
|
||||
21000,
|
||||
},
|
||||
}
|
||||
|
||||
recipient := common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec")
|
||||
chainID, err := s.network.Validators[0].JSONRPCClient.ChainID(s.ctx)
|
||||
s.Require().NoError(err)
|
||||
from := common.BytesToAddress(s.network.Validators[0].Address)
|
||||
for _, tc := range testCases {
|
||||
s.Run(tc.name, func() {
|
||||
nonce := s.getAccountNonce(from)
|
||||
s.Require().NoError(err)
|
||||
gasPrice := s.getGasPrice()
|
||||
msgTx := evmtypes.NewTx(
|
||||
chainID,
|
||||
nonce,
|
||||
&recipient,
|
||||
nil,
|
||||
tc.gasLimit,
|
||||
gasPrice,
|
||||
nil, nil,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
msgTx.From = from.Hex()
|
||||
err = msgTx.Sign(s.ethSigner, s.network.Validators[0].ClientCtx.Keyring)
|
||||
s.Require().NoError(err)
|
||||
err := s.network.Validators[0].JSONRPCClient.SendTransaction(s.ctx, msgTx.AsTransaction())
|
||||
s.Require().NoError(err)
|
||||
s.waitForTransaction()
|
||||
receipt := s.expectSuccessReceipt(msgTx.AsTransaction().Hash())
|
||||
s.Equal(receipt.GasUsed, tc.expectedGasUsed)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,8 +96,12 @@ func (cc *ChainContext) Finalize(
|
||||
// Note: The block header and state database might be updated to reflect any
|
||||
// consensus rules that happen at finalization (e.g. block rewards).
|
||||
// TODO: Figure out if this needs to be hooked up to any part of the ABCI?
|
||||
func (cc *ChainContext) FinalizeAndAssemble(_ ethcons.ChainHeaderReader, _ *ethtypes.Header, _ *ethstate.StateDB, _ []*ethtypes.Transaction,
|
||||
_ []*ethtypes.Header, _ []*ethtypes.Receipt,
|
||||
func (cc *ChainContext) FinalizeAndAssemble(_ ethcons.ChainHeaderReader,
|
||||
_ *ethtypes.Header,
|
||||
_ *ethstate.StateDB,
|
||||
_ []*ethtypes.Transaction,
|
||||
_ []*ethtypes.Header,
|
||||
_ []*ethtypes.Receipt,
|
||||
) (*ethtypes.Block, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -56,10 +56,10 @@ type ImporterTestSuite struct {
|
||||
ctx sdk.Context
|
||||
}
|
||||
|
||||
/// DoSetupTest setup test environment, it uses`require.TestingT` to support both `testing.T` and `testing.B`.
|
||||
// / DoSetupTest setup test environment, it uses`require.TestingT` to support both `testing.T` and `testing.B`.
|
||||
func (suite *ImporterTestSuite) DoSetupTest(t require.TestingT) {
|
||||
checkTx := false
|
||||
suite.app = app.Setup(suite.T(), checkTx, nil)
|
||||
suite.app = app.Setup(checkTx, nil)
|
||||
// consensus key
|
||||
priv, err := ethsecp256k1.GenerateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,55 @@
|
||||
# RPC Integration tests
|
||||
|
||||
The RPC integration test suite uses nix for reproducible and configurable
|
||||
builds allowing to run integration tests using python web3 library against
|
||||
different Ethermint and [Geth](https://github.com/ethereum/go-ethereum) clients with multiple configurations.
|
||||
|
||||
## Installation
|
||||
|
||||
Nix Multi-user installation:
|
||||
|
||||
```
|
||||
sh <(curl -L https://nixos.org/nix/install) --daemon
|
||||
```
|
||||
|
||||
Make sure the following line has been added to your shell profile (e.g. ~/.profile):
|
||||
|
||||
```
|
||||
source ~/.nix-profile/etc/profile.d/nix.sh
|
||||
```
|
||||
|
||||
Then re-login shell, the nix installation is completed.
|
||||
|
||||
For linux:
|
||||
|
||||
```
|
||||
sh <(curl -L https://nixos.org/nix/install) --no-daemon
|
||||
```
|
||||
|
||||
## Run Local
|
||||
|
||||
First time run (can take a while):
|
||||
|
||||
```
|
||||
make run-integration-tests
|
||||
```
|
||||
|
||||
Once you've run them once and, you can run:
|
||||
|
||||
```
|
||||
nix-shell tests/integration_tests/shell.nix
|
||||
cd tests/integration_tests
|
||||
pytest -s -vv
|
||||
```
|
||||
|
||||
If you're changing anything on the ethermint rpc, rerun the first command.
|
||||
|
||||
|
||||
## Caching
|
||||
|
||||
You can enable Binary Cache to speed up the tests:
|
||||
|
||||
```
|
||||
$ nix-env -iA cachix -f https://cachix.org/api/v1/install
|
||||
$ cachix use ethermint
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
{ pkgs ? import ../../../nix { } }:
|
||||
let ethermintd = (pkgs.callPackage ../../../. { });
|
||||
in
|
||||
ethermintd.overrideAttrs (oldAttrs: {
|
||||
patches = oldAttrs.patches or [ ] ++ [
|
||||
./broken-ethermintd.patch
|
||||
];
|
||||
})
|
||||
@@ -0,0 +1,15 @@
|
||||
diff --git a/app/app.go b/app/app.go
|
||||
index 158bf7a3..a3b5718c 100644
|
||||
--- a/app/app.go
|
||||
+++ b/app/app.go
|
||||
@@ -681,6 +681,10 @@ func (app *EthermintApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBloc
|
||||
|
||||
// EndBlocker updates every end block
|
||||
func (app *EthermintApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock {
|
||||
+ if ctx.BlockHeight()%10 == 0 {
|
||||
+ store := ctx.KVStore(app.keys["evm"])
|
||||
+ store.Set([]byte("hello"), []byte("world"))
|
||||
+ }
|
||||
return app.mm.EndBlock(ctx, req)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
local config = import 'default.jsonnet';
|
||||
|
||||
config {
|
||||
'ethermint_9000-1'+: {
|
||||
genesis+: {
|
||||
app_state+: {
|
||||
feemarket+: {
|
||||
params+: {
|
||||
no_base_fee: false,
|
||||
base_fee:: super.base_fee,
|
||||
initial_base_fee: super.base_fee,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
{
|
||||
dotenv: '../../../scripts/.env',
|
||||
'ethermint_9000-1': {
|
||||
cmd: 'ethermintd',
|
||||
'start-flags': '--trace',
|
||||
config: {
|
||||
consensus: {
|
||||
// larger timeout for more stable mempool tests
|
||||
timeout_commit: '10s',
|
||||
},
|
||||
mempool: {
|
||||
// use v1 mempool to enable tx prioritization
|
||||
version: 'v1',
|
||||
},
|
||||
},
|
||||
'app-config': {
|
||||
'minimum-gas-prices': '0aphoton',
|
||||
'index-events': ['ethereum_tx.ethereumTxHash'],
|
||||
'json-rpc': {
|
||||
address: '0.0.0.0:{EVMRPC_PORT}',
|
||||
'ws-address': '0.0.0.0:{EVMRPC_PORT_WS}',
|
||||
api: 'eth,net,web3,debug',
|
||||
'feehistory-cap': 100,
|
||||
'block-range-cap': 10000,
|
||||
'logs-cap': 10000,
|
||||
},
|
||||
},
|
||||
validators: [{
|
||||
coins: '1000000000000000000stake,10000000000000000000000aphoton',
|
||||
staked: '1000000000000000000stake',
|
||||
mnemonic: '${VALIDATOR1_MNEMONIC}',
|
||||
}, {
|
||||
coins: '1000000000000000000stake,10000000000000000000000aphoton',
|
||||
staked: '1000000000000000000stake',
|
||||
mnemonic: '${VALIDATOR2_MNEMONIC}',
|
||||
}],
|
||||
accounts: [{
|
||||
name: 'community',
|
||||
coins: '10000000000000000000000aphoton',
|
||||
mnemonic: '${COMMUNITY_MNEMONIC}',
|
||||
}, {
|
||||
name: 'signer1',
|
||||
coins: '20000000000000000000000aphoton',
|
||||
mnemonic: '${SIGNER1_MNEMONIC}',
|
||||
}, {
|
||||
name: 'signer2',
|
||||
coins: '30000000000000000000000aphoton',
|
||||
mnemonic: '${SIGNER2_MNEMONIC}',
|
||||
}],
|
||||
genesis: {
|
||||
consensus_params: {
|
||||
block: {
|
||||
max_bytes: '1048576',
|
||||
max_gas: '81500000',
|
||||
},
|
||||
},
|
||||
app_state: {
|
||||
evm: {
|
||||
params: {
|
||||
evm_denom: 'aphoton',
|
||||
},
|
||||
},
|
||||
gov: {
|
||||
voting_params: {
|
||||
voting_period: '10s',
|
||||
},
|
||||
deposit_params: {
|
||||
max_deposit_period: '10s',
|
||||
min_deposit: [
|
||||
{
|
||||
denom: 'aphoton',
|
||||
amount: '1',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
transfer: {
|
||||
params: {
|
||||
receive_enabled: true,
|
||||
send_enabled: true,
|
||||
},
|
||||
},
|
||||
feemarket: {
|
||||
params: {
|
||||
no_base_fee: false,
|
||||
base_fee: '100000000000',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
local config = import 'default.jsonnet';
|
||||
|
||||
config {
|
||||
'ethermint_9000-1'+: {
|
||||
config+: {
|
||||
tx_index+: {
|
||||
indexer: 'null',
|
||||
},
|
||||
},
|
||||
'app-config'+: {
|
||||
pruning: 'everything',
|
||||
'state-sync'+: {
|
||||
'snapshot-interval': 0,
|
||||
},
|
||||
'json-rpc'+: {
|
||||
'enable-indexer': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
local config = import 'default.jsonnet';
|
||||
|
||||
config {
|
||||
'ethermint_9000-1'+: {
|
||||
'app-config'+: {
|
||||
pruning: 'everything',
|
||||
'state-sync'+: {
|
||||
'snapshot-interval': 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
local config = import 'default.jsonnet';
|
||||
|
||||
config {
|
||||
'ethermint_9000-1'+: {
|
||||
validators: super.validators + [{
|
||||
name: 'fullnode',
|
||||
}],
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
let
|
||||
pkgs = import ../../../nix { };
|
||||
fetchEthermint = rev: builtins.fetchTarball "https://github.com/evmos/ethermint/archive/${rev}.tar.gz";
|
||||
released = pkgs.buildGo118Module rec {
|
||||
name = "ethermintd";
|
||||
# the commit before https://github.com/evmos/ethermint/pull/943
|
||||
src = fetchEthermint "f21592ebfe74da7590eb42ed926dae970b2a9a3f";
|
||||
subPackages = [ "cmd/ethermintd" ];
|
||||
vendorSha256 = "sha256-ABm5t6R/u2S6pThGrgdsqe8n3fH5tIWw7a57kxJPbYw=";
|
||||
doCheck = false;
|
||||
};
|
||||
current = pkgs.callPackage ../../../. { };
|
||||
in
|
||||
pkgs.linkFarm "upgrade-test-package" [
|
||||
{ name = "genesis"; path = released; }
|
||||
{ name = "integration-test-upgrade"; path = current; }
|
||||
]
|
||||
@@ -0,0 +1,47 @@
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from .network import setup_custom_ethermint, setup_ethermint, setup_geth
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def ethermint(tmp_path_factory):
|
||||
path = tmp_path_factory.mktemp("ethermint")
|
||||
yield from setup_ethermint(path, 26650)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def ethermint_indexer(tmp_path_factory):
|
||||
path = tmp_path_factory.mktemp("indexer")
|
||||
yield from setup_custom_ethermint(
|
||||
path, 26660, Path(__file__).parent / "configs/enable-indexer.jsonnet"
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def geth(tmp_path_factory):
|
||||
path = tmp_path_factory.mktemp("geth")
|
||||
yield from setup_geth(path, 8545)
|
||||
|
||||
|
||||
@pytest.fixture(
|
||||
scope="session", params=["ethermint", "geth", "ethermint-ws", "enable-indexer"]
|
||||
)
|
||||
def cluster(request, ethermint, ethermint_indexer, geth):
|
||||
"""
|
||||
run on both ethermint and geth
|
||||
"""
|
||||
provider = request.param
|
||||
if provider == "ethermint":
|
||||
yield ethermint
|
||||
elif provider == "geth":
|
||||
yield geth
|
||||
elif provider == "ethermint-ws":
|
||||
ethermint_ws = ethermint.copy()
|
||||
ethermint_ws.use_websocket()
|
||||
yield ethermint_ws
|
||||
elif provider == "enable-indexer":
|
||||
yield ethermint_indexer
|
||||
else:
|
||||
raise NotImplementedError
|
||||
@@ -0,0 +1,11 @@
|
||||
node_modules
|
||||
.env
|
||||
coverage
|
||||
coverage.json
|
||||
typechain
|
||||
typechain-types
|
||||
|
||||
#Hardhat files
|
||||
cache
|
||||
artifacts
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# Sample Hardhat Project
|
||||
|
||||
This project demonstrates a basic Hardhat use case. It comes with a sample contract, a test for that contract, and a script that deploys that contract.
|
||||
|
||||
Try running some of the following tasks:
|
||||
|
||||
```shell
|
||||
npx hardhat help
|
||||
npx hardhat test
|
||||
GAS_REPORT=true npx hardhat test
|
||||
npx hardhat node
|
||||
npx hardhat run scripts/deploy.js
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
pragma solidity >0.5.0;
|
||||
|
||||
contract Greeter {
|
||||
string public greeting;
|
||||
|
||||
event ChangeGreeting(address from, string value);
|
||||
|
||||
constructor() public {
|
||||
greeting = "Hello";
|
||||
}
|
||||
|
||||
function setGreeting(string memory _greeting) public {
|
||||
greeting = _greeting;
|
||||
emit ChangeGreeting(msg.sender, _greeting);
|
||||
}
|
||||
|
||||
function greet() public view returns (string memory) {
|
||||
return greeting;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
pragma solidity 0.8.10;
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
|
||||
contract TestERC20A is ERC20 {
|
||||
|
||||
constructor() public ERC20("TestERC20", "Test") {
|
||||
_mint(msg.sender, 100000000000000000000000000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { HardhatUserConfig } from "hardhat/config";
|
||||
import "hardhat-typechain";
|
||||
|
||||
const config: HardhatUserConfig = {
|
||||
solidity: {
|
||||
compilers: [
|
||||
{
|
||||
version: "0.8.10",
|
||||
settings: {
|
||||
optimizer: {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
typechain: {
|
||||
outDir: "typechain",
|
||||
target: "ethers-v5",
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,834 @@
|
||||
import json
|
||||
import tempfile
|
||||
|
||||
import requests
|
||||
from dateutil.parser import isoparse
|
||||
from pystarport.utils import build_cli_args_safe, interact
|
||||
|
||||
DEFAULT_GAS_PRICE = "5000000000000aphoton"
|
||||
|
||||
|
||||
class ChainCommand:
|
||||
def __init__(self, cmd):
|
||||
self.cmd = cmd
|
||||
|
||||
def __call__(self, cmd, *args, stdin=None, **kwargs):
|
||||
"execute chain-maind"
|
||||
args = " ".join(build_cli_args_safe(cmd, *args, **kwargs))
|
||||
return interact(f"{self.cmd} {args}", input=stdin)
|
||||
|
||||
|
||||
class CosmosCLI:
|
||||
"the apis to interact with wallet and blockchain"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data_dir,
|
||||
node_rpc,
|
||||
cmd,
|
||||
):
|
||||
self.data_dir = data_dir
|
||||
self._genesis = json.loads(
|
||||
(self.data_dir / "config" / "genesis.json").read_text()
|
||||
)
|
||||
self.chain_id = self._genesis["chain_id"]
|
||||
self.node_rpc = node_rpc
|
||||
self.raw = ChainCommand(cmd)
|
||||
self.output = None
|
||||
self.error = None
|
||||
|
||||
@property
|
||||
def node_rpc_http(self):
|
||||
return "http" + self.node_rpc.removeprefix("tcp")
|
||||
|
||||
def node_id(self):
|
||||
"get tendermint node id"
|
||||
output = self.raw("tendermint", "show-node-id", home=self.data_dir)
|
||||
return output.decode().strip()
|
||||
|
||||
def delete_account(self, name):
|
||||
"delete wallet account in node's keyring"
|
||||
return self.raw(
|
||||
"keys",
|
||||
"delete",
|
||||
name,
|
||||
"-y",
|
||||
"--force",
|
||||
home=self.data_dir,
|
||||
output="json",
|
||||
keyring_backend="test",
|
||||
)
|
||||
|
||||
def create_account(self, name, mnemonic=None):
|
||||
"create new keypair in node's keyring"
|
||||
if mnemonic is None:
|
||||
output = self.raw(
|
||||
"keys",
|
||||
"add",
|
||||
name,
|
||||
home=self.data_dir,
|
||||
output="json",
|
||||
keyring_backend="test",
|
||||
)
|
||||
else:
|
||||
output = self.raw(
|
||||
"keys",
|
||||
"add",
|
||||
name,
|
||||
"--recover",
|
||||
home=self.data_dir,
|
||||
output="json",
|
||||
keyring_backend="test",
|
||||
stdin=mnemonic.encode() + b"\n",
|
||||
)
|
||||
return json.loads(output)
|
||||
|
||||
def init(self, moniker):
|
||||
"the node's config is already added"
|
||||
return self.raw(
|
||||
"init",
|
||||
moniker,
|
||||
chain_id=self.chain_id,
|
||||
home=self.data_dir,
|
||||
)
|
||||
|
||||
def validate_genesis(self):
|
||||
return self.raw("validate-genesis", home=self.data_dir)
|
||||
|
||||
def add_genesis_account(self, addr, coins, **kwargs):
|
||||
return self.raw(
|
||||
"add-genesis-account",
|
||||
addr,
|
||||
coins,
|
||||
home=self.data_dir,
|
||||
output="json",
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def gentx(self, name, coins, min_self_delegation=1, pubkey=None):
|
||||
return self.raw(
|
||||
"gentx",
|
||||
name,
|
||||
coins,
|
||||
min_self_delegation=str(min_self_delegation),
|
||||
home=self.data_dir,
|
||||
chain_id=self.chain_id,
|
||||
keyring_backend="test",
|
||||
pubkey=pubkey,
|
||||
)
|
||||
|
||||
def collect_gentxs(self, gentx_dir):
|
||||
return self.raw("collect-gentxs", gentx_dir, home=self.data_dir)
|
||||
|
||||
def status(self):
|
||||
return json.loads(self.raw("status", node=self.node_rpc))
|
||||
|
||||
def block_height(self):
|
||||
return int(self.status()["SyncInfo"]["latest_block_height"])
|
||||
|
||||
def block_time(self):
|
||||
return isoparse(self.status()["SyncInfo"]["latest_block_time"])
|
||||
|
||||
def balances(self, addr):
|
||||
return json.loads(
|
||||
self.raw("query", "bank", "balances", addr, home=self.data_dir)
|
||||
)["balances"]
|
||||
|
||||
def balance(self, addr, denom="aphoton"):
|
||||
denoms = {coin["denom"]: int(coin["amount"]) for coin in self.balances(addr)}
|
||||
return denoms.get(denom, 0)
|
||||
|
||||
def query_tx(self, tx_type, tx_value):
|
||||
tx = self.raw(
|
||||
"query",
|
||||
"tx",
|
||||
"--type",
|
||||
tx_type,
|
||||
tx_value,
|
||||
home=self.data_dir,
|
||||
chain_id=self.chain_id,
|
||||
node=self.node_rpc,
|
||||
)
|
||||
return json.loads(tx)
|
||||
|
||||
def query_all_txs(self, addr):
|
||||
txs = self.raw(
|
||||
"query",
|
||||
"txs-all",
|
||||
addr,
|
||||
home=self.data_dir,
|
||||
keyring_backend="test",
|
||||
chain_id=self.chain_id,
|
||||
node=self.node_rpc,
|
||||
)
|
||||
return json.loads(txs)
|
||||
|
||||
def distribution_commission(self, addr):
|
||||
coin = json.loads(
|
||||
self.raw(
|
||||
"query",
|
||||
"distribution",
|
||||
"commission",
|
||||
addr,
|
||||
output="json",
|
||||
node=self.node_rpc,
|
||||
)
|
||||
)["commission"][0]
|
||||
return float(coin["amount"])
|
||||
|
||||
def distribution_community(self):
|
||||
coin = json.loads(
|
||||
self.raw(
|
||||
"query",
|
||||
"distribution",
|
||||
"community-pool",
|
||||
output="json",
|
||||
node=self.node_rpc,
|
||||
)
|
||||
)["pool"][0]
|
||||
return float(coin["amount"])
|
||||
|
||||
def distribution_reward(self, delegator_addr):
|
||||
coin = json.loads(
|
||||
self.raw(
|
||||
"query",
|
||||
"distribution",
|
||||
"rewards",
|
||||
delegator_addr,
|
||||
output="json",
|
||||
node=self.node_rpc,
|
||||
)
|
||||
)["total"][0]
|
||||
return float(coin["amount"])
|
||||
|
||||
def address(self, name, bech="acc"):
|
||||
output = self.raw(
|
||||
"keys",
|
||||
"show",
|
||||
name,
|
||||
"-a",
|
||||
home=self.data_dir,
|
||||
keyring_backend="test",
|
||||
bech=bech,
|
||||
)
|
||||
return output.strip().decode()
|
||||
|
||||
def account(self, addr):
|
||||
return json.loads(
|
||||
self.raw(
|
||||
"query", "auth", "account", addr, output="json", node=self.node_rpc
|
||||
)
|
||||
)
|
||||
|
||||
def tx_search(self, events: str):
|
||||
"/tx_search"
|
||||
return json.loads(
|
||||
self.raw("query", "txs", events=events, output="json", node=self.node_rpc)
|
||||
)
|
||||
|
||||
def tx_search_rpc(self, events: str):
|
||||
rsp = requests.get(
|
||||
f"{self.node_rpc_http}/tx_search",
|
||||
params={
|
||||
"query": f'"{events}"',
|
||||
},
|
||||
).json()
|
||||
assert "error" not in rsp, rsp["error"]
|
||||
return rsp["result"]["txs"]
|
||||
|
||||
def tx(self, value, **kwargs):
|
||||
"/tx"
|
||||
default_kwargs = {
|
||||
"home": self.data_dir,
|
||||
}
|
||||
return json.loads(self.raw("query", "tx", value, **(default_kwargs | kwargs)))
|
||||
|
||||
def total_supply(self):
|
||||
return json.loads(
|
||||
self.raw("query", "bank", "total", output="json", node=self.node_rpc)
|
||||
)
|
||||
|
||||
def validator(self, addr):
|
||||
return json.loads(
|
||||
self.raw(
|
||||
"query",
|
||||
"staking",
|
||||
"validator",
|
||||
addr,
|
||||
output="json",
|
||||
node=self.node_rpc,
|
||||
)
|
||||
)
|
||||
|
||||
def validators(self):
|
||||
return json.loads(
|
||||
self.raw(
|
||||
"query", "staking", "validators", output="json", node=self.node_rpc
|
||||
)
|
||||
)["validators"]
|
||||
|
||||
def staking_params(self):
|
||||
return json.loads(
|
||||
self.raw("query", "staking", "params", output="json", node=self.node_rpc)
|
||||
)
|
||||
|
||||
def staking_pool(self, bonded=True):
|
||||
return int(
|
||||
json.loads(
|
||||
self.raw("query", "staking", "pool", output="json", node=self.node_rpc)
|
||||
)["bonded_tokens" if bonded else "not_bonded_tokens"]
|
||||
)
|
||||
|
||||
def transfer(self, from_, to, coins, generate_only=False, **kwargs):
|
||||
kwargs.setdefault("gas_prices", DEFAULT_GAS_PRICE)
|
||||
return json.loads(
|
||||
self.raw(
|
||||
"tx",
|
||||
"bank",
|
||||
"send",
|
||||
from_,
|
||||
to,
|
||||
coins,
|
||||
"-y",
|
||||
"--generate-only" if generate_only else None,
|
||||
home=self.data_dir,
|
||||
**kwargs,
|
||||
)
|
||||
)
|
||||
|
||||
def get_delegated_amount(self, which_addr):
|
||||
return json.loads(
|
||||
self.raw(
|
||||
"query",
|
||||
"staking",
|
||||
"delegations",
|
||||
which_addr,
|
||||
home=self.data_dir,
|
||||
chain_id=self.chain_id,
|
||||
node=self.node_rpc,
|
||||
output="json",
|
||||
)
|
||||
)
|
||||
|
||||
def delegate_amount(self, to_addr, amount, from_addr, gas_price=None):
|
||||
if gas_price is None:
|
||||
return json.loads(
|
||||
self.raw(
|
||||
"tx",
|
||||
"staking",
|
||||
"delegate",
|
||||
to_addr,
|
||||
amount,
|
||||
"-y",
|
||||
home=self.data_dir,
|
||||
from_=from_addr,
|
||||
keyring_backend="test",
|
||||
chain_id=self.chain_id,
|
||||
node=self.node_rpc,
|
||||
)
|
||||
)
|
||||
else:
|
||||
return json.loads(
|
||||
self.raw(
|
||||
"tx",
|
||||
"staking",
|
||||
"delegate",
|
||||
to_addr,
|
||||
amount,
|
||||
"-y",
|
||||
home=self.data_dir,
|
||||
from_=from_addr,
|
||||
keyring_backend="test",
|
||||
chain_id=self.chain_id,
|
||||
node=self.node_rpc,
|
||||
gas_prices=gas_price,
|
||||
)
|
||||
)
|
||||
|
||||
# to_addr: croclcl1... , from_addr: cro1...
|
||||
def unbond_amount(self, to_addr, amount, from_addr):
|
||||
return json.loads(
|
||||
self.raw(
|
||||
"tx",
|
||||
"staking",
|
||||
"unbond",
|
||||
to_addr,
|
||||
amount,
|
||||
"-y",
|
||||
home=self.data_dir,
|
||||
from_=from_addr,
|
||||
keyring_backend="test",
|
||||
chain_id=self.chain_id,
|
||||
node=self.node_rpc,
|
||||
)
|
||||
)
|
||||
|
||||
# to_validator_addr: crocncl1... , from_from_validator_addraddr: crocl1...
|
||||
def redelegate_amount(
|
||||
self, to_validator_addr, from_validator_addr, amount, from_addr
|
||||
):
|
||||
return json.loads(
|
||||
self.raw(
|
||||
"tx",
|
||||
"staking",
|
||||
"redelegate",
|
||||
from_validator_addr,
|
||||
to_validator_addr,
|
||||
amount,
|
||||
"-y",
|
||||
home=self.data_dir,
|
||||
from_=from_addr,
|
||||
keyring_backend="test",
|
||||
chain_id=self.chain_id,
|
||||
node=self.node_rpc,
|
||||
)
|
||||
)
|
||||
|
||||
# from_delegator can be account name or address
|
||||
def withdraw_all_rewards(self, from_delegator):
|
||||
return json.loads(
|
||||
self.raw(
|
||||
"tx",
|
||||
"distribution",
|
||||
"withdraw-all-rewards",
|
||||
"-y",
|
||||
from_=from_delegator,
|
||||
home=self.data_dir,
|
||||
keyring_backend="test",
|
||||
chain_id=self.chain_id,
|
||||
node=self.node_rpc,
|
||||
)
|
||||
)
|
||||
|
||||
def make_multisig(self, name, signer1, signer2):
|
||||
self.raw(
|
||||
"keys",
|
||||
"add",
|
||||
name,
|
||||
multisig=f"{signer1},{signer2}",
|
||||
multisig_threshold="2",
|
||||
home=self.data_dir,
|
||||
keyring_backend="test",
|
||||
)
|
||||
|
||||
def sign_multisig_tx(self, tx_file, multi_addr, signer_name):
|
||||
return json.loads(
|
||||
self.raw(
|
||||
"tx",
|
||||
"sign",
|
||||
tx_file,
|
||||
from_=signer_name,
|
||||
multisig=multi_addr,
|
||||
home=self.data_dir,
|
||||
keyring_backend="test",
|
||||
chain_id=self.chain_id,
|
||||
node=self.node_rpc,
|
||||
)
|
||||
)
|
||||
|
||||
def sign_batch_multisig_tx(
|
||||
self, tx_file, multi_addr, signer_name, account_number, sequence_number
|
||||
):
|
||||
r = self.raw(
|
||||
"tx",
|
||||
"sign-batch",
|
||||
"--offline",
|
||||
tx_file,
|
||||
account_number=account_number,
|
||||
sequence=sequence_number,
|
||||
from_=signer_name,
|
||||
multisig=multi_addr,
|
||||
home=self.data_dir,
|
||||
keyring_backend="test",
|
||||
chain_id=self.chain_id,
|
||||
node=self.node_rpc,
|
||||
)
|
||||
return r.decode("utf-8")
|
||||
|
||||
def encode_signed_tx(self, signed_tx):
|
||||
return self.raw(
|
||||
"tx",
|
||||
"encode",
|
||||
signed_tx,
|
||||
)
|
||||
|
||||
def sign_tx(self, tx_file, signer):
|
||||
return json.loads(
|
||||
self.raw(
|
||||
"tx",
|
||||
"sign",
|
||||
tx_file,
|
||||
from_=signer,
|
||||
home=self.data_dir,
|
||||
keyring_backend="test",
|
||||
chain_id=self.chain_id,
|
||||
node=self.node_rpc,
|
||||
)
|
||||
)
|
||||
|
||||
def sign_tx_json(self, tx, signer, max_priority_price=None):
|
||||
if max_priority_price is not None:
|
||||
tx["body"]["extension_options"].append(
|
||||
{
|
||||
"@type": "/ethermint.types.v1.ExtensionOptionDynamicFeeTx",
|
||||
"max_priority_price": str(max_priority_price),
|
||||
}
|
||||
)
|
||||
with tempfile.NamedTemporaryFile("w") as fp:
|
||||
json.dump(tx, fp)
|
||||
fp.flush()
|
||||
return self.sign_tx(fp.name, signer)
|
||||
|
||||
def combine_multisig_tx(self, tx_file, multi_name, signer1_file, signer2_file):
|
||||
return json.loads(
|
||||
self.raw(
|
||||
"tx",
|
||||
"multisign",
|
||||
tx_file,
|
||||
multi_name,
|
||||
signer1_file,
|
||||
signer2_file,
|
||||
home=self.data_dir,
|
||||
keyring_backend="test",
|
||||
chain_id=self.chain_id,
|
||||
node=self.node_rpc,
|
||||
)
|
||||
)
|
||||
|
||||
def combine_batch_multisig_tx(
|
||||
self, tx_file, multi_name, signer1_file, signer2_file
|
||||
):
|
||||
r = self.raw(
|
||||
"tx",
|
||||
"multisign-batch",
|
||||
tx_file,
|
||||
multi_name,
|
||||
signer1_file,
|
||||
signer2_file,
|
||||
home=self.data_dir,
|
||||
keyring_backend="test",
|
||||
chain_id=self.chain_id,
|
||||
node=self.node_rpc,
|
||||
)
|
||||
return r.decode("utf-8")
|
||||
|
||||
def broadcast_tx(self, tx_file, **kwargs):
|
||||
kwargs.setdefault("broadcast_mode", "block")
|
||||
kwargs.setdefault("output", "json")
|
||||
return json.loads(
|
||||
self.raw("tx", "broadcast", tx_file, node=self.node_rpc, **kwargs)
|
||||
)
|
||||
|
||||
def broadcast_tx_json(self, tx, **kwargs):
|
||||
with tempfile.NamedTemporaryFile("w") as fp:
|
||||
json.dump(tx, fp)
|
||||
fp.flush()
|
||||
return self.broadcast_tx(fp.name, **kwargs)
|
||||
|
||||
def unjail(self, addr):
|
||||
return json.loads(
|
||||
self.raw(
|
||||
"tx",
|
||||
"slashing",
|
||||
"unjail",
|
||||
"-y",
|
||||
from_=addr,
|
||||
home=self.data_dir,
|
||||
node=self.node_rpc,
|
||||
keyring_backend="test",
|
||||
chain_id=self.chain_id,
|
||||
)
|
||||
)
|
||||
|
||||
def create_validator(
|
||||
self,
|
||||
amount,
|
||||
moniker=None,
|
||||
commission_max_change_rate="0.01",
|
||||
commission_rate="0.1",
|
||||
commission_max_rate="0.2",
|
||||
min_self_delegation="1",
|
||||
identity="",
|
||||
website="",
|
||||
security_contact="",
|
||||
details="",
|
||||
):
|
||||
"""MsgCreateValidator
|
||||
create the node with create_node before call this"""
|
||||
pubkey = (
|
||||
"'"
|
||||
+ (
|
||||
self.raw(
|
||||
"tendermint",
|
||||
"show-validator",
|
||||
home=self.data_dir,
|
||||
)
|
||||
.strip()
|
||||
.decode()
|
||||
)
|
||||
+ "'"
|
||||
)
|
||||
return json.loads(
|
||||
self.raw(
|
||||
"tx",
|
||||
"staking",
|
||||
"create-validator",
|
||||
"-y",
|
||||
from_=self.address("validator"),
|
||||
amount=amount,
|
||||
pubkey=pubkey,
|
||||
min_self_delegation=min_self_delegation,
|
||||
# commision
|
||||
commission_rate=commission_rate,
|
||||
commission_max_rate=commission_max_rate,
|
||||
commission_max_change_rate=commission_max_change_rate,
|
||||
# description
|
||||
moniker=moniker,
|
||||
identity=identity,
|
||||
website=website,
|
||||
security_contact=security_contact,
|
||||
details=details,
|
||||
# basic
|
||||
home=self.data_dir,
|
||||
node=self.node_rpc,
|
||||
keyring_backend="test",
|
||||
chain_id=self.chain_id,
|
||||
)
|
||||
)
|
||||
|
||||
def edit_validator(
|
||||
self,
|
||||
commission_rate=None,
|
||||
moniker=None,
|
||||
identity=None,
|
||||
website=None,
|
||||
security_contact=None,
|
||||
details=None,
|
||||
):
|
||||
"""MsgEditValidator"""
|
||||
options = dict(
|
||||
commission_rate=commission_rate,
|
||||
# description
|
||||
moniker=moniker,
|
||||
identity=identity,
|
||||
website=website,
|
||||
security_contact=security_contact,
|
||||
details=details,
|
||||
)
|
||||
return json.loads(
|
||||
self.raw(
|
||||
"tx",
|
||||
"staking",
|
||||
"edit-validator",
|
||||
"-y",
|
||||
from_=self.address("validator"),
|
||||
home=self.data_dir,
|
||||
node=self.node_rpc,
|
||||
keyring_backend="test",
|
||||
chain_id=self.chain_id,
|
||||
**{k: v for k, v in options.items() if v is not None},
|
||||
)
|
||||
)
|
||||
|
||||
def gov_propose(self, proposer, kind, proposal, **kwargs):
|
||||
kwargs.setdefault("gas_prices", DEFAULT_GAS_PRICE)
|
||||
if kind == "software-upgrade":
|
||||
return json.loads(
|
||||
self.raw(
|
||||
"tx",
|
||||
"gov",
|
||||
"submit-proposal",
|
||||
kind,
|
||||
proposal["name"],
|
||||
"-y",
|
||||
from_=proposer,
|
||||
# content
|
||||
title=proposal.get("title"),
|
||||
description=proposal.get("description"),
|
||||
upgrade_height=proposal.get("upgrade-height"),
|
||||
upgrade_time=proposal.get("upgrade-time"),
|
||||
upgrade_info=proposal.get("upgrade-info"),
|
||||
deposit=proposal.get("deposit"),
|
||||
# basic
|
||||
home=self.data_dir,
|
||||
**kwargs,
|
||||
)
|
||||
)
|
||||
elif kind == "cancel-software-upgrade":
|
||||
return json.loads(
|
||||
self.raw(
|
||||
"tx",
|
||||
"gov",
|
||||
"submit-proposal",
|
||||
kind,
|
||||
"-y",
|
||||
from_=proposer,
|
||||
# content
|
||||
title=proposal.get("title"),
|
||||
description=proposal.get("description"),
|
||||
deposit=proposal.get("deposit"),
|
||||
# basic
|
||||
home=self.data_dir,
|
||||
**kwargs,
|
||||
)
|
||||
)
|
||||
else:
|
||||
with tempfile.NamedTemporaryFile("w") as fp:
|
||||
json.dump(proposal, fp)
|
||||
fp.flush()
|
||||
return json.loads(
|
||||
self.raw(
|
||||
"tx",
|
||||
"gov",
|
||||
"submit-proposal",
|
||||
kind,
|
||||
fp.name,
|
||||
"-y",
|
||||
from_=proposer,
|
||||
# basic
|
||||
home=self.data_dir,
|
||||
**kwargs,
|
||||
)
|
||||
)
|
||||
|
||||
def gov_vote(self, voter, proposal_id, option, **kwargs):
|
||||
kwargs.setdefault("gas_prices", DEFAULT_GAS_PRICE)
|
||||
return json.loads(
|
||||
self.raw(
|
||||
"tx",
|
||||
"gov",
|
||||
"vote",
|
||||
proposal_id,
|
||||
option,
|
||||
"-y",
|
||||
from_=voter,
|
||||
home=self.data_dir,
|
||||
**kwargs,
|
||||
)
|
||||
)
|
||||
|
||||
def gov_deposit(self, depositor, proposal_id, amount):
|
||||
return json.loads(
|
||||
self.raw(
|
||||
"tx",
|
||||
"gov",
|
||||
"deposit",
|
||||
proposal_id,
|
||||
amount,
|
||||
"-y",
|
||||
from_=depositor,
|
||||
home=self.data_dir,
|
||||
node=self.node_rpc,
|
||||
keyring_backend="test",
|
||||
chain_id=self.chain_id,
|
||||
)
|
||||
)
|
||||
|
||||
def query_proposals(self, depositor=None, limit=None, status=None, voter=None):
|
||||
return json.loads(
|
||||
self.raw(
|
||||
"query",
|
||||
"gov",
|
||||
"proposals",
|
||||
depositor=depositor,
|
||||
count_total=limit,
|
||||
status=status,
|
||||
voter=voter,
|
||||
output="json",
|
||||
node=self.node_rpc,
|
||||
)
|
||||
)
|
||||
|
||||
def query_proposal(self, proposal_id):
|
||||
return json.loads(
|
||||
self.raw(
|
||||
"query",
|
||||
"gov",
|
||||
"proposal",
|
||||
proposal_id,
|
||||
output="json",
|
||||
node=self.node_rpc,
|
||||
)
|
||||
)
|
||||
|
||||
def query_tally(self, proposal_id):
|
||||
return json.loads(
|
||||
self.raw(
|
||||
"query",
|
||||
"gov",
|
||||
"tally",
|
||||
proposal_id,
|
||||
output="json",
|
||||
node=self.node_rpc,
|
||||
)
|
||||
)
|
||||
|
||||
def ibc_transfer(
|
||||
self,
|
||||
from_,
|
||||
to,
|
||||
amount,
|
||||
channel, # src channel
|
||||
target_version, # chain version number of target chain
|
||||
i=0,
|
||||
):
|
||||
return json.loads(
|
||||
self.raw(
|
||||
"tx",
|
||||
"ibc-transfer",
|
||||
"transfer",
|
||||
"transfer", # src port
|
||||
channel,
|
||||
to,
|
||||
amount,
|
||||
"-y",
|
||||
# FIXME https://github.com/cosmos/cosmos-sdk/issues/8059
|
||||
"--absolute-timeouts",
|
||||
from_=from_,
|
||||
home=self.data_dir,
|
||||
node=self.node_rpc,
|
||||
keyring_backend="test",
|
||||
chain_id=self.chain_id,
|
||||
packet_timeout_height=f"{target_version}-10000000000",
|
||||
packet_timeout_timestamp=0,
|
||||
)
|
||||
)
|
||||
|
||||
def export(self):
|
||||
return self.raw("export", home=self.data_dir)
|
||||
|
||||
def unsaferesetall(self):
|
||||
return self.raw("unsafe-reset-all")
|
||||
|
||||
def build_evm_tx(self, raw_tx: str, **kwargs):
|
||||
return json.loads(
|
||||
self.raw(
|
||||
"tx",
|
||||
"evm",
|
||||
"raw",
|
||||
raw_tx,
|
||||
"-y",
|
||||
"--generate-only",
|
||||
home=self.data_dir,
|
||||
**kwargs,
|
||||
)
|
||||
)
|
||||
|
||||
def query_base_fee(self, **kwargs):
|
||||
default_kwargs = {"home": self.data_dir}
|
||||
return int(
|
||||
json.loads(
|
||||
self.raw(
|
||||
"q",
|
||||
"feemarket",
|
||||
"base-fee",
|
||||
**(default_kwargs | kwargs),
|
||||
)
|
||||
)["base_fee"]
|
||||
)
|
||||
|
||||
def rollback(self):
|
||||
self.raw("rollback", home=self.data_dir)
|
||||
|
||||
def migrate_keystore(self):
|
||||
return self.raw("keys", "migrate", home=self.data_dir)
|
||||
@@ -0,0 +1,76 @@
|
||||
EXPECTED_GET_STORAGE_AT = (
|
||||
"0x00000000000000000000000000000000000000000000000000120a0b063499d4"
|
||||
)
|
||||
|
||||
EXPECTED_GET_PROOF = {
|
||||
"address": "0x4CB06C43fcdABeA22541fcF1F856A6a296448B6c",
|
||||
"accountProof": [
|
||||
"0xf90211a03841a7ddd65c70c94b8efa79190d00f0ab134b26f18dcad508f60a7e74559d0ba0464b07429a05039e22931492d6c6251a860c018ea390045d596b1ac11b5c7aa7a011f4b89823a03c9c4b5a8ab079ee1bc0e2a83a508bb7a5dc7d7fb4f2e95d3186a0b5f7c51c3b2d51d97f171d2b38a4df1a7c0acc5eb0de46beeff4d07f5ed20e19a0b591a2ce02367eda31cf2d16eca7c27fd44dbf0864b64ea8259ad36696eb2a04a02b646a7552b8392ae94263757f699a27d6e9176b4c06b9fc0a722f893b964795a02df05d68bceb88eebf68aafde61d10ab942097afc1c58b8435ffd3895358a742a0c2f16143c4d1db03276c433696dddb3e9f3b113bcd854b127962262e98f43147a0828820316cc02bfefd899aba41340659fd06df1e0a0796287ec2a4110239f6d2a050496598670b04df7bbff3718887fa36437d6d8c7afb4eff86f76c5c7097dcc4a0c14e9060c6b3784e35b9e6ae2ad2984142a75910ccc89eb89dc1e2f44b6c58c2a009804db571d0ce07913e1cbacc4f1dc4fb8265c936f5c612e3a47e91c64d8e9fa063d96f38b3cb51b1665c6641e25ffe24803f2941e5df79942f6a53b7169647e4a0899f71abb18c6c956118bf567fac629b75f7e9526873e429d3d8abb6dbb58021a00fd717235298742623c0b3cafb3e4bd86c0b5ab1f71097b4dd19f3d6925d758da0096437146c16097f2ccc1d3e910d65a4132803baee2249e72c8bf0bcaaeb37e580", # noqa: E501
|
||||
"0xf90151a097b17a89fd2c03ee98cb6459c08f51b269da5cee46650e84470f62bf83b43efe80a03b269d284a4c3cf8f8deacafb637c6d77f607eec8d75e8548d778e629612310480a01403217a7f1416830c870087c524dabade3985271f6f369a12b010883c71927aa0f592ac54c879817389663be677166f5022943e2fe1b52617a1d15c2f353f27dda0ac8d015a9e668f5877fcc391fae33981c00577096f0455b42df4f8e8089ece24a003ba34a13e2f2fb4bf7096540b42d4955c5269875b9cf0f7b87632585d44c9a580a0b179e3230b07db294473ae57f0170262798f8c551c755b5665ace1215cee10ca80a0552d24252639a6ae775aa1df700ffb92c2411daea7286f158d44081c8172d072a0772a87d08cf38c4c68bfde770968571abd16fd3835cb902486bd2e515d53c12d80a0413774f3d900d2d2be7a3ad999ffa859a471dc03a74fb9a6d8275455f5496a548080", # noqa: E501
|
||||
"0xf869a020d13b52a61d3c1325ce3626a51418adebd6323d4840f1bdd93906359d11c933b846f8440180a01ab7c0b0a2a4bbb5a1495da8c142150891fc64e0c321e1feb70bd5f881951f7ea0551332d96d085185ab4019ad8bcf89c45321e136c261eb6271e574a2edf1461f", # noqa: E501
|
||||
],
|
||||
"balance": 0,
|
||||
"codeHash": "0x551332d96d085185ab4019ad8bcf89c45321e136c261eb6271e574a2edf1461f", # noqa: E501
|
||||
"nonce": 1,
|
||||
"storageHash": "0x1ab7c0b0a2a4bbb5a1495da8c142150891fc64e0c321e1feb70bd5f881951f7e", # noqa: E501
|
||||
"storageProof": [
|
||||
{
|
||||
"key": "0x00",
|
||||
"value": "0x48656c6c6f00000000000000000000000000000000000000000000000000000a", # noqa: E501
|
||||
"proof": [
|
||||
"0xf9019180a01ace80e7bed79fbadbe390876bd1a7d9770edf9462049ef8f4b555d05715d53ea049347a3c2eac6525a3fd7e3454dab19d73b4adeb9aa27d29493b9843f3f88814a085079b4abcd07fd4a5d6c52d35f4c4574aecc85830e90c478ca8c18fcbe590de80a02e3f8ad7ea29e784007f51852b9c3e470aef06b11bac32586a8b691134e4c27da064d2157a14bc31f195f73296ea4dcdbe7698edbf3ca81c44bf7730179d98d94ca09e7dc2597c9b7f72ddf84d7eebb0fe2a2fa2ab54fe668cd14fee44d9b40b1a53a0aa5d4acc7ac636d16bc9655556770bc325e1901fb62dc53770ef9110009e080380a0d5fde962bd2fb5326ddc7a9ca7fe0ee47c5bb3227f838b6d73d3299c22457596a08691410eff46b88f929ef649ea25025f62a5362ca8dc8876e5e1f4fc8e79256d80a0673e88d3a8a4616f676793096b5ae87cff931bd20fb8dd466f97809a1126aad8a08b774a45c2273553e2daf4bbc3a8d44fb542ea29b6f125098f79a4d211b3309ca02fed3139c1791269acb9365eddece93e743900eba6b42a6a8614747752ba268f80", # noqa: E501
|
||||
"0xf891808080a0c7d094301e0c54da37b696d85f72de5520b224ab2cf4f045d8db1a3374caf0488080a0fc5581783bfe27fab9423602e1914d719fd71433e9d7dd63c95fe7e58d10c9c38080a0c64f346fc7a21f6679cba8abdf37ca2de8c4fcd8f8bcaedb261b5f77627c93908080808080a0ddef2936a67a3ac7d3d4ff15a935a45f2cc4976c8f0310aed85daf763780e2b480", # noqa: E501
|
||||
"0xf843a0200decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563a1a048656c6c6f00000000000000000000000000000000000000000000000000000a", # noqa: E501
|
||||
],
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
EXPECTED_GET_TRANSACTION = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": 0,
|
||||
"result": {
|
||||
"hash": "0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b", # noqa: E501
|
||||
"blockHash": "0x1d59ff54b1eb26b013ce3cb5fc9dab3705b415a67127a003c3e61eb445bb8df2", # noqa: E501
|
||||
"blockNumber": "0x5daf3b",
|
||||
"from": "0xa7d9ddbe1f17865597fbd27ec712455208b6b76d",
|
||||
"gas": "0xc350",
|
||||
"gasPrice": "0x4a817c800",
|
||||
"input": "0x68656c6c6f21",
|
||||
"nonce": "0x15",
|
||||
"r": "0x1b5e176d927f8e9ab405058b2d2457392da3e20f328b16ddabcebc33eaac5fea",
|
||||
"s": "0x4ba69724e8f69de52f0125ad8b3c5c2cef33019bac3249e2c0a2192766d1721c",
|
||||
"to": "0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb",
|
||||
"transactionIndex": "0x41",
|
||||
"v": "0x25",
|
||||
"value": "0xf3dbb76162000",
|
||||
},
|
||||
}
|
||||
|
||||
EXPECTED_FEE_HISTORY = {
|
||||
"oldestBlock": 3,
|
||||
"reward": [[220, 7145389], [1000000, 6000213], [550, 550], [125, 12345678]],
|
||||
"baseFeePerGas": [202583058, 177634473, 155594425, 136217133, 119442408],
|
||||
"gasUsedRatio": [
|
||||
0.007390479689642084,
|
||||
0.0036988514889990873,
|
||||
0.0018512333048507866,
|
||||
0.00741217041320997,
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
EXPECTED_GET_TRANSACTION_RECEIPT = {
|
||||
"blockHash": "0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd",
|
||||
"blockNumber": 46147,
|
||||
"contractAddress": None,
|
||||
"cumulativeGasUsed": 21000,
|
||||
"from": "0xA1E4380A3B1f749673E270229993eE55F35663b4",
|
||||
"gasUsed": 21000,
|
||||
"logs": [],
|
||||
"logsBloom": "0x000000000000000000000000000000000000000000000000...0000",
|
||||
"status": 1, # 0 or 1
|
||||
"to": "0x5DF9B87991262F6BA471F09758CDE1c0FC1De734",
|
||||
"transactionHash": "0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060", # noqa: E501
|
||||
"transactionIndex": 0,
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import json
|
||||
import os
|
||||
import signal
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import web3
|
||||
from pystarport import ports
|
||||
from web3.middleware import geth_poa_middleware
|
||||
|
||||
from .cosmoscli import CosmosCLI
|
||||
from .utils import wait_for_port
|
||||
|
||||
DEFAULT_CHAIN_BINARY = "ethermintd"
|
||||
|
||||
|
||||
class Ethermint:
|
||||
def __init__(self, base_dir, chain_binary=DEFAULT_CHAIN_BINARY):
|
||||
self._w3 = None
|
||||
self.base_dir = base_dir
|
||||
self.config = json.loads((base_dir / "config.json").read_text())
|
||||
self.enable_auto_deployment = False
|
||||
self._use_websockets = False
|
||||
self.chain_binary = chain_binary
|
||||
|
||||
def copy(self):
|
||||
return Ethermint(self.base_dir)
|
||||
|
||||
@property
|
||||
def w3_http_endpoint(self, i=0):
|
||||
port = ports.evmrpc_port(self.base_port(i))
|
||||
return f"http://localhost:{port}"
|
||||
|
||||
@property
|
||||
def w3_ws_endpoint(self, i=0):
|
||||
port = ports.evmrpc_ws_port(self.base_port(i))
|
||||
return f"ws://localhost:{port}"
|
||||
|
||||
@property
|
||||
def w3(self, i=0):
|
||||
if self._w3 is None:
|
||||
if self._use_websockets:
|
||||
self._w3 = web3.Web3(
|
||||
web3.providers.WebsocketProvider(self.w3_ws_endpoint)
|
||||
)
|
||||
else:
|
||||
self._w3 = web3.Web3(web3.providers.HTTPProvider(self.w3_http_endpoint))
|
||||
return self._w3
|
||||
|
||||
def base_port(self, i):
|
||||
return self.config["validators"][i]["base_port"]
|
||||
|
||||
def node_rpc(self, i):
|
||||
return "tcp://127.0.0.1:%d" % ports.rpc_port(self.base_port(i))
|
||||
|
||||
def use_websocket(self, use=True):
|
||||
self._w3 = None
|
||||
self._use_websockets = use
|
||||
|
||||
def cosmos_cli(self, i=0):
|
||||
return CosmosCLI(
|
||||
self.base_dir / f"node{i}", self.node_rpc(i), self.chain_binary
|
||||
)
|
||||
|
||||
|
||||
class Geth:
|
||||
def __init__(self, w3):
|
||||
self.w3 = w3
|
||||
|
||||
|
||||
def setup_ethermint(path, base_port):
|
||||
cfg = Path(__file__).parent / "configs/default.jsonnet"
|
||||
yield from setup_custom_ethermint(path, base_port, cfg)
|
||||
|
||||
|
||||
def setup_geth(path, base_port):
|
||||
with (path / "geth.log").open("w") as logfile:
|
||||
cmd = [
|
||||
"start-geth",
|
||||
path,
|
||||
"--http.port",
|
||||
str(base_port),
|
||||
"--port",
|
||||
str(base_port + 1),
|
||||
]
|
||||
print(*cmd)
|
||||
proc = subprocess.Popen(
|
||||
cmd,
|
||||
preexec_fn=os.setsid,
|
||||
stdout=logfile,
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
||||
try:
|
||||
wait_for_port(base_port)
|
||||
w3 = web3.Web3(web3.providers.HTTPProvider(f"http://127.0.0.1:{base_port}"))
|
||||
w3.middleware_onion.inject(geth_poa_middleware, layer=0)
|
||||
yield Geth(w3)
|
||||
finally:
|
||||
os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
|
||||
# proc.terminate()
|
||||
proc.wait()
|
||||
|
||||
|
||||
def setup_custom_ethermint(
|
||||
path, base_port, config, post_init=None, chain_binary=None, wait_port=True
|
||||
):
|
||||
cmd = [
|
||||
"pystarport",
|
||||
"init",
|
||||
"--config",
|
||||
config,
|
||||
"--data",
|
||||
path,
|
||||
"--base_port",
|
||||
str(base_port),
|
||||
"--no_remove",
|
||||
]
|
||||
if chain_binary is not None:
|
||||
cmd = cmd[:1] + ["--cmd", chain_binary] + cmd[1:]
|
||||
print(*cmd)
|
||||
subprocess.run(cmd, check=True)
|
||||
if post_init is not None:
|
||||
post_init(path, base_port, config)
|
||||
proc = subprocess.Popen(
|
||||
["pystarport", "start", "--data", path, "--quiet"],
|
||||
preexec_fn=os.setsid,
|
||||
)
|
||||
try:
|
||||
if wait_port:
|
||||
wait_for_port(ports.evmrpc_port(base_port))
|
||||
wait_for_port(ports.evmrpc_ws_port(base_port))
|
||||
yield Ethermint(
|
||||
path / "ethermint_9000-1", chain_binary=chain_binary or DEFAULT_CHAIN_BINARY
|
||||
)
|
||||
finally:
|
||||
os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
|
||||
proc.wait()
|
||||
Generated
+1904
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,33 @@
|
||||
[tool.poetry]
|
||||
name = "integration_tests"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = ["chain-dev <chain@crypto.com>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
pytest = "^7.0.1"
|
||||
pytest-github-actions-annotate-failures = "^0.1.1"
|
||||
flake8 = "^4.0.1"
|
||||
black = "^22.3.0"
|
||||
flake8-black = "^0.3.2"
|
||||
flake8-isort = "^4.1.1"
|
||||
pep8-naming = "^0.11.1"
|
||||
protobuf = "^3.20.2"
|
||||
grpcio = "^1.33.2"
|
||||
PyYAML = "^5.3.1"
|
||||
python-dateutil = "^2.8.1"
|
||||
web3 = "^5.20.1"
|
||||
eth-bloom = "^1.0.4"
|
||||
python-dotenv = "^0.19.2"
|
||||
pystarport = { git = "https://github.com/crypto-com/pystarport.git", branch = "main" }
|
||||
websockets = "^9.1"
|
||||
toml = "^0.10.2"
|
||||
pysha3 = "^1.0.2"
|
||||
jsonnet = "^0.18.0"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry>=0.12"]
|
||||
build-backend = "poetry.masonry.api"
|
||||
@@ -0,0 +1,15 @@
|
||||
{ system ? builtins.currentSystem, pkgs ? import ../../nix { inherit system; } }:
|
||||
pkgs.mkShell {
|
||||
buildInputs = [
|
||||
pkgs.jq
|
||||
(pkgs.callPackage ../../. { }) # ethermintd
|
||||
pkgs.start-scripts
|
||||
pkgs.go-ethereum
|
||||
pkgs.cosmovisor
|
||||
pkgs.nodejs
|
||||
pkgs.test-env
|
||||
];
|
||||
shellHook = ''
|
||||
. ${../../scripts/.env}
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
def test_basic(cluster):
|
||||
w3 = cluster.w3
|
||||
assert w3.eth.chain_id == 9000
|
||||
@@ -0,0 +1,118 @@
|
||||
import pytest
|
||||
from web3 import Web3
|
||||
|
||||
from .utils import (
|
||||
ADDRS,
|
||||
CONTRACTS,
|
||||
deploy_contract,
|
||||
send_successful_transaction,
|
||||
send_transaction,
|
||||
)
|
||||
|
||||
|
||||
def test_pending_transaction_filter(cluster):
|
||||
w3: Web3 = cluster.w3
|
||||
flt = w3.eth.filter("pending")
|
||||
|
||||
# without tx
|
||||
assert flt.get_new_entries() == [] # GetFilterChanges
|
||||
|
||||
# with tx
|
||||
txhash = send_successful_transaction(w3)
|
||||
assert txhash in flt.get_new_entries()
|
||||
|
||||
# without new txs since last call
|
||||
assert flt.get_new_entries() == []
|
||||
|
||||
|
||||
def test_block_filter(cluster):
|
||||
w3: Web3 = cluster.w3
|
||||
flt = w3.eth.filter("latest")
|
||||
|
||||
# without tx
|
||||
assert flt.get_new_entries() == []
|
||||
|
||||
# with tx
|
||||
send_successful_transaction(w3)
|
||||
blocks = flt.get_new_entries()
|
||||
assert len(blocks) >= 1
|
||||
|
||||
# without new txs since last call
|
||||
assert flt.get_new_entries() == []
|
||||
|
||||
|
||||
def test_event_log_filter_by_contract(cluster):
|
||||
w3: Web3 = cluster.w3
|
||||
contract = deploy_contract(w3, CONTRACTS["Greeter"])
|
||||
assert contract.caller.greet() == "Hello"
|
||||
|
||||
# Create new filter from contract
|
||||
current_height = hex(w3.eth.get_block_number())
|
||||
flt = contract.events.ChangeGreeting.createFilter(fromBlock=current_height)
|
||||
|
||||
# without tx
|
||||
assert flt.get_new_entries() == [] # GetFilterChanges
|
||||
assert flt.get_all_entries() == [] # GetFilterLogs
|
||||
|
||||
# with tx
|
||||
tx = contract.functions.setGreeting("world").buildTransaction()
|
||||
tx_receipt = send_transaction(w3, tx)
|
||||
assert tx_receipt.status == 1
|
||||
|
||||
log = contract.events.ChangeGreeting().processReceipt(tx_receipt)[0]
|
||||
assert log["event"] == "ChangeGreeting"
|
||||
|
||||
new_entries = flt.get_new_entries()
|
||||
assert len(new_entries) == 1
|
||||
assert new_entries[0] == log
|
||||
assert contract.caller.greet() == "world"
|
||||
|
||||
# without new txs since last call
|
||||
assert flt.get_new_entries() == []
|
||||
assert flt.get_all_entries() == new_entries
|
||||
|
||||
# Uninstall
|
||||
assert w3.eth.uninstall_filter(flt.filter_id)
|
||||
assert not w3.eth.uninstall_filter(flt.filter_id)
|
||||
with pytest.raises(Exception):
|
||||
flt.get_all_entries()
|
||||
|
||||
|
||||
def test_event_log_filter_by_address(cluster):
|
||||
w3: Web3 = cluster.w3
|
||||
|
||||
contract = deploy_contract(w3, CONTRACTS["Greeter"])
|
||||
assert contract.caller.greet() == "Hello"
|
||||
|
||||
flt = w3.eth.filter({"address": contract.address})
|
||||
flt2 = w3.eth.filter({"address": ADDRS["validator"]})
|
||||
|
||||
# without tx
|
||||
assert flt.get_new_entries() == [] # GetFilterChanges
|
||||
assert flt.get_all_entries() == [] # GetFilterLogs
|
||||
|
||||
# with tx
|
||||
tx = contract.functions.setGreeting("world").buildTransaction()
|
||||
receipt = send_transaction(w3, tx)
|
||||
assert receipt.status == 1
|
||||
|
||||
assert len(flt.get_new_entries()) == 1
|
||||
assert len(flt2.get_new_entries()) == 0
|
||||
|
||||
|
||||
def test_get_logs(cluster):
|
||||
w3: Web3 = cluster.w3
|
||||
|
||||
contract = deploy_contract(w3, CONTRACTS["Greeter"])
|
||||
|
||||
# without tx
|
||||
assert w3.eth.get_logs({"address": contract.address}) == []
|
||||
assert w3.eth.get_logs({"address": ADDRS["validator"]}) == []
|
||||
|
||||
# with tx
|
||||
tx = contract.functions.setGreeting("world").buildTransaction()
|
||||
receipt = send_transaction(w3, tx)
|
||||
assert receipt.status == 1
|
||||
|
||||
assert len(w3.eth.get_logs({"address": contract.address})) == 1
|
||||
assert len(w3.eth.get_logs({"address": ADDRS["validator"]})) == 0
|
||||
@@ -0,0 +1,192 @@
|
||||
import sys
|
||||
|
||||
from .network import Ethermint
|
||||
from .utils import ADDRS, KEYS, eth_to_bech32, sign_transaction, wait_for_new_blocks
|
||||
|
||||
PRIORITY_REDUCTION = 1000000
|
||||
|
||||
|
||||
def effective_gas_price(tx, base_fee):
|
||||
if "maxFeePerGas" in tx:
|
||||
# dynamic fee tx
|
||||
return min(base_fee + tx["maxPriorityFeePerGas"], tx["maxFeePerGas"])
|
||||
else:
|
||||
# legacy tx
|
||||
return tx["gasPrice"]
|
||||
|
||||
|
||||
def tx_priority(tx, base_fee):
|
||||
if "maxFeePerGas" in tx:
|
||||
# dynamic fee tx
|
||||
return (
|
||||
min(tx["maxPriorityFeePerGas"], tx["maxFeePerGas"] - base_fee)
|
||||
// PRIORITY_REDUCTION
|
||||
)
|
||||
else:
|
||||
# legacy tx
|
||||
return (tx["gasPrice"] - base_fee) // PRIORITY_REDUCTION
|
||||
|
||||
|
||||
def test_priority(ethermint: Ethermint):
|
||||
"""
|
||||
test priorities of different tx types
|
||||
|
||||
use a relatively large priority number to counter
|
||||
the effect of base fee change during the testing.
|
||||
"""
|
||||
w3 = ethermint.w3
|
||||
amount = 10000
|
||||
base_fee = w3.eth.get_block("latest").baseFeePerGas
|
||||
|
||||
# [ ( sender, tx ), ... ]
|
||||
# use different senders to avoid nonce conflicts
|
||||
test_cases = [
|
||||
(
|
||||
"validator",
|
||||
{
|
||||
"to": "0x0000000000000000000000000000000000000000",
|
||||
"value": amount,
|
||||
"gas": 21000,
|
||||
"maxFeePerGas": base_fee + PRIORITY_REDUCTION * 600000,
|
||||
"maxPriorityFeePerGas": 0,
|
||||
},
|
||||
),
|
||||
(
|
||||
"community",
|
||||
{
|
||||
"to": "0x0000000000000000000000000000000000000000",
|
||||
"value": amount,
|
||||
"gas": 21000,
|
||||
"gasPrice": base_fee + PRIORITY_REDUCTION * 200000,
|
||||
},
|
||||
),
|
||||
(
|
||||
"signer2",
|
||||
{
|
||||
"to": "0x0000000000000000000000000000000000000000",
|
||||
"value": amount,
|
||||
"gasPrice": base_fee + PRIORITY_REDUCTION * 400000,
|
||||
"accessList": [
|
||||
{
|
||||
"address": "0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae",
|
||||
"storageKeys": (
|
||||
"0x00000000000000000000000000000000000000000000000000000000"
|
||||
"00000003",
|
||||
"0x00000000000000000000000000000000000000000000000000000000"
|
||||
"00000007",
|
||||
),
|
||||
}
|
||||
],
|
||||
},
|
||||
),
|
||||
(
|
||||
"signer1",
|
||||
{
|
||||
"to": "0x0000000000000000000000000000000000000000",
|
||||
"value": amount,
|
||||
"gas": 21000,
|
||||
"maxFeePerGas": base_fee + PRIORITY_REDUCTION * 600000,
|
||||
"maxPriorityFeePerGas": PRIORITY_REDUCTION * 600000,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
# test cases are ordered by priority
|
||||
expect_priorities = [tx_priority(tx, base_fee) for _, tx in test_cases]
|
||||
assert expect_priorities == [0, 200000, 400000, 600000]
|
||||
|
||||
signed = [sign_transaction(w3, tx, key=KEYS[sender]) for sender, tx in test_cases]
|
||||
# send the txs from low priority to high,
|
||||
# but the later sent txs should be included earlier.
|
||||
txhashes = [w3.eth.send_raw_transaction(tx.rawTransaction) for tx in signed]
|
||||
|
||||
receipts = [w3.eth.wait_for_transaction_receipt(txhash) for txhash in txhashes]
|
||||
print(receipts)
|
||||
assert all(receipt.status == 1 for receipt in receipts), "expect all txs success"
|
||||
|
||||
# the later txs should be included earlier because of higher priority
|
||||
# FIXME there's some non-deterministics due to mempool logic
|
||||
tx_indexes = [(r.blockNumber, r.transactionIndex) for r in receipts]
|
||||
print(tx_indexes)
|
||||
# the first sent tx are included later, because of lower priority
|
||||
assert all(i1 > i2 for i1, i2 in zip(tx_indexes, tx_indexes[1:]))
|
||||
|
||||
|
||||
def test_native_tx_priority(ethermint: Ethermint):
|
||||
cli = ethermint.cosmos_cli()
|
||||
base_fee = cli.query_base_fee()
|
||||
print("base_fee", base_fee)
|
||||
test_cases = [
|
||||
{
|
||||
"from": eth_to_bech32(ADDRS["community"]),
|
||||
"to": eth_to_bech32(ADDRS["validator"]),
|
||||
"amount": "1000aphoton",
|
||||
"gas_prices": f"{base_fee + PRIORITY_REDUCTION * 600000}aphoton",
|
||||
"max_priority_price": 0,
|
||||
},
|
||||
{
|
||||
"from": eth_to_bech32(ADDRS["signer1"]),
|
||||
"to": eth_to_bech32(ADDRS["signer2"]),
|
||||
"amount": "1000aphoton",
|
||||
"gas_prices": f"{base_fee + PRIORITY_REDUCTION * 600000}aphoton",
|
||||
"max_priority_price": PRIORITY_REDUCTION * 200000,
|
||||
},
|
||||
{
|
||||
"from": eth_to_bech32(ADDRS["signer2"]),
|
||||
"to": eth_to_bech32(ADDRS["signer1"]),
|
||||
"amount": "1000aphoton",
|
||||
"gas_prices": f"{base_fee + PRIORITY_REDUCTION * 400000}aphoton",
|
||||
"max_priority_price": PRIORITY_REDUCTION * 400000,
|
||||
},
|
||||
{
|
||||
"from": eth_to_bech32(ADDRS["validator"]),
|
||||
"to": eth_to_bech32(ADDRS["community"]),
|
||||
"amount": "1000aphoton",
|
||||
"gas_prices": f"{base_fee + PRIORITY_REDUCTION * 600000}aphoton",
|
||||
"max_priority_price": None, # no extension, maximum tipFeeCap
|
||||
},
|
||||
]
|
||||
txs = []
|
||||
expect_priorities = []
|
||||
for tc in test_cases:
|
||||
tx = cli.transfer(
|
||||
tc["from"],
|
||||
tc["to"],
|
||||
tc["amount"],
|
||||
gas_prices=tc["gas_prices"],
|
||||
generate_only=True,
|
||||
)
|
||||
txs.append(
|
||||
cli.sign_tx_json(
|
||||
tx, tc["from"], max_priority_price=tc.get("max_priority_price")
|
||||
)
|
||||
)
|
||||
gas_price = int(tc["gas_prices"].removesuffix("aphoton"))
|
||||
expect_priorities.append(
|
||||
min(
|
||||
get_max_priority_price(tc.get("max_priority_price")),
|
||||
gas_price - base_fee,
|
||||
)
|
||||
// PRIORITY_REDUCTION
|
||||
)
|
||||
assert expect_priorities == [0, 200000, 400000, 600000]
|
||||
|
||||
txhashes = []
|
||||
for tx in txs:
|
||||
rsp = cli.broadcast_tx_json(tx, broadcast_mode="sync")
|
||||
assert rsp["code"] == 0, rsp["raw_log"]
|
||||
txhashes.append(rsp["txhash"])
|
||||
|
||||
print("wait for two new blocks, so the sent txs are all included")
|
||||
wait_for_new_blocks(cli, 2)
|
||||
|
||||
tx_results = [cli.tx_search_rpc(f"tx.hash='{txhash}'")[0] for txhash in txhashes]
|
||||
tx_indexes = [(int(r["height"]), r["index"]) for r in tx_results]
|
||||
print(tx_indexes)
|
||||
# the first sent tx are included later, because of lower priority
|
||||
assert all(i1 > i2 for i1, i2 in zip(tx_indexes, tx_indexes[1:]))
|
||||
|
||||
|
||||
def get_max_priority_price(max_priority_price):
|
||||
"default to max int64 if None"
|
||||
return max_priority_price if max_priority_price is not None else sys.maxsize
|
||||
@@ -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"
|
||||
)
|
||||
@@ -0,0 +1,98 @@
|
||||
import configparser
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from pystarport import ports
|
||||
from pystarport.cluster import SUPERVISOR_CONFIG_FILE
|
||||
|
||||
from .network import setup_custom_ethermint
|
||||
from .utils import supervisorctl, wait_for_block, wait_for_port
|
||||
|
||||
|
||||
def update_node2_cmd(path, cmd, i):
|
||||
ini_path = path / SUPERVISOR_CONFIG_FILE
|
||||
ini = configparser.RawConfigParser()
|
||||
ini.read(ini_path)
|
||||
for section in ini.sections():
|
||||
if section == f"program:ethermint_9000-1-node{i}":
|
||||
ini[section].update(
|
||||
{
|
||||
"command": f"{cmd} start --home %(here)s/node{i}",
|
||||
"autorestart": "false", # don't restart when stopped
|
||||
}
|
||||
)
|
||||
with ini_path.open("w") as fp:
|
||||
ini.write(fp)
|
||||
|
||||
|
||||
def post_init(broken_binary):
|
||||
def inner(path, base_port, config):
|
||||
chain_id = "ethermint_9000-1"
|
||||
update_node2_cmd(path / chain_id, broken_binary, 2)
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def custom_ethermint(tmp_path_factory):
|
||||
path = tmp_path_factory.mktemp("rollback")
|
||||
|
||||
cmd = [
|
||||
"nix-build",
|
||||
"--no-out-link",
|
||||
Path(__file__).parent / "configs/broken-ethermintd.nix",
|
||||
]
|
||||
print(*cmd)
|
||||
broken_binary = (
|
||||
Path(subprocess.check_output(cmd).strip().decode()) / "bin/ethermintd"
|
||||
)
|
||||
print(broken_binary)
|
||||
|
||||
# init with genesis binary
|
||||
yield from setup_custom_ethermint(
|
||||
path,
|
||||
26300,
|
||||
Path(__file__).parent / "configs/rollback-test.jsonnet",
|
||||
post_init=post_init(broken_binary),
|
||||
wait_port=False,
|
||||
)
|
||||
|
||||
|
||||
def test_rollback(custom_ethermint):
|
||||
"""
|
||||
test using rollback command to fix app-hash mismatch situation.
|
||||
- the broken node will sync up to block 10 then crash.
|
||||
- use rollback command to rollback the db.
|
||||
- switch to correct binary should make the node syncing again.
|
||||
"""
|
||||
wait_for_port(ports.rpc_port(custom_ethermint.base_port(2)))
|
||||
|
||||
print("wait for node2 to sync the first 10 blocks")
|
||||
cli2 = custom_ethermint.cosmos_cli(2)
|
||||
wait_for_block(cli2, 10)
|
||||
|
||||
print("wait for a few more blocks on the healthy nodes")
|
||||
cli = custom_ethermint.cosmos_cli(0)
|
||||
wait_for_block(cli, 13)
|
||||
|
||||
# (app hash mismatch happens after the 10th block, detected in the 11th block)
|
||||
print("check node2 get stuck at block 10")
|
||||
assert cli2.block_height() == 10
|
||||
|
||||
print("stop node2")
|
||||
supervisorctl(
|
||||
custom_ethermint.base_dir / "../tasks.ini", "stop", "ethermint_9000-1-node2"
|
||||
)
|
||||
|
||||
print("do rollback on node2")
|
||||
cli2.rollback()
|
||||
|
||||
print("switch to normal binary")
|
||||
update_node2_cmd(custom_ethermint.base_dir, "ethermintd", 2)
|
||||
supervisorctl(custom_ethermint.base_dir / "../tasks.ini", "update")
|
||||
wait_for_port(ports.rpc_port(custom_ethermint.base_port(2)))
|
||||
|
||||
print("check node2 sync again")
|
||||
cli2 = custom_ethermint.cosmos_cli(2)
|
||||
wait_for_block(cli2, 15)
|
||||
@@ -0,0 +1,370 @@
|
||||
from .expected_constants import (
|
||||
EXPECTED_FEE_HISTORY,
|
||||
EXPECTED_GET_PROOF,
|
||||
EXPECTED_GET_STORAGE_AT,
|
||||
EXPECTED_GET_TRANSACTION,
|
||||
EXPECTED_GET_TRANSACTION_RECEIPT,
|
||||
)
|
||||
from .utils import (
|
||||
ADDRS,
|
||||
CONTRACTS,
|
||||
KEYS,
|
||||
deploy_contract,
|
||||
send_transaction,
|
||||
w3_wait_for_block,
|
||||
w3_wait_for_new_blocks,
|
||||
)
|
||||
|
||||
|
||||
def test_block(ethermint, geth):
|
||||
get_blocks(ethermint, geth, False)
|
||||
get_blocks(ethermint, geth, True)
|
||||
|
||||
|
||||
def get_blocks(ethermint, geth, with_transactions):
|
||||
eth_rpc = ethermint.w3.provider
|
||||
geth_rpc = geth.w3.provider
|
||||
make_same_rpc_calls(
|
||||
eth_rpc, geth_rpc, "eth_getBlockByNumber", ["0x0", with_transactions]
|
||||
)
|
||||
|
||||
make_same_rpc_calls(
|
||||
eth_rpc, geth_rpc, "eth_getBlockByNumber", ["0x2710", with_transactions]
|
||||
)
|
||||
|
||||
ethermint_blk = ethermint.w3.eth.get_block(1)
|
||||
# Get existing block, no transactions
|
||||
eth_rsp = eth_rpc.make_request(
|
||||
"eth_getBlockByHash", [ethermint_blk["hash"].hex(), with_transactions]
|
||||
)
|
||||
geth_rsp = geth_rpc.make_request(
|
||||
"eth_getBlockByHash",
|
||||
[
|
||||
"0x124d099a1f435d3a6155e5d157ff1078eaefb742435892677ee5b3cb5e6fa055",
|
||||
with_transactions,
|
||||
],
|
||||
)
|
||||
res, err = same_types(eth_rsp, geth_rsp)
|
||||
assert res, err
|
||||
|
||||
# Get not existing block
|
||||
make_same_rpc_calls(
|
||||
eth_rpc,
|
||||
geth_rpc,
|
||||
"eth_getBlockByHash",
|
||||
[
|
||||
"0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd",
|
||||
with_transactions,
|
||||
],
|
||||
)
|
||||
|
||||
# Bad call
|
||||
make_same_rpc_calls(
|
||||
eth_rpc, geth_rpc, "eth_getBlockByHash", ["0", with_transactions]
|
||||
)
|
||||
|
||||
|
||||
def test_accounts(ethermint, geth):
|
||||
eth_rpc = ethermint.w3.provider
|
||||
geth_rpc = geth.w3.provider
|
||||
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_accounts", [])
|
||||
|
||||
|
||||
def test_syncing(ethermint, geth):
|
||||
eth_rpc = ethermint.w3.provider
|
||||
geth_rpc = geth.w3.provider
|
||||
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_syncing", [])
|
||||
|
||||
|
||||
def test_coinbase(ethermint, geth):
|
||||
eth_rpc = ethermint.w3.provider
|
||||
geth_rpc = geth.w3.provider
|
||||
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_coinbase", [])
|
||||
|
||||
|
||||
def test_max_priority_fee(ethermint, geth):
|
||||
eth_rpc = ethermint.w3.provider
|
||||
geth_rpc = geth.w3.provider
|
||||
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_maxPriorityFeePerGas", [])
|
||||
|
||||
|
||||
def test_gas_price(ethermint, geth):
|
||||
eth_rpc = ethermint.w3.provider
|
||||
geth_rpc = geth.w3.provider
|
||||
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_gasPrice", [])
|
||||
|
||||
|
||||
def test_block_number(ethermint, geth):
|
||||
eth_rpc = ethermint.w3.provider
|
||||
geth_rpc = geth.w3.provider
|
||||
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_blockNumber", [])
|
||||
|
||||
|
||||
def test_balance(ethermint, geth):
|
||||
eth_rpc = ethermint.w3.provider
|
||||
geth_rpc = geth.w3.provider
|
||||
make_same_rpc_calls(
|
||||
eth_rpc,
|
||||
geth_rpc,
|
||||
"eth_getBalance",
|
||||
["0x57f96e6b86cdefdb3d412547816a82e3e0ebf9d2", "latest"],
|
||||
)
|
||||
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_getBalance", ["0", "latest"])
|
||||
make_same_rpc_calls(
|
||||
eth_rpc,
|
||||
geth_rpc,
|
||||
"eth_getBalance",
|
||||
["0x9907a0cf64ec9fbf6ed8fd4971090de88222a9ac", "latest"],
|
||||
)
|
||||
make_same_rpc_calls(
|
||||
eth_rpc,
|
||||
geth_rpc,
|
||||
"eth_getBalance",
|
||||
["0x57f96e6b86cdefdb3d412547816a82e3e0ebf9d2", "0x0"],
|
||||
)
|
||||
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_getBalance", ["0", "0x0"])
|
||||
make_same_rpc_calls(
|
||||
eth_rpc,
|
||||
geth_rpc,
|
||||
"eth_getBalance",
|
||||
["0x9907a0cf64ec9fbf6ed8fd4971090de88222a9ac", "0x0"],
|
||||
)
|
||||
make_same_rpc_calls(
|
||||
eth_rpc,
|
||||
geth_rpc,
|
||||
"eth_getBalance",
|
||||
["0x57f96e6b86cdefdb3d412547816a82e3e0ebf9d2", "0x10000"],
|
||||
)
|
||||
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_getBalance", ["0", "0x10000"])
|
||||
make_same_rpc_calls(
|
||||
eth_rpc,
|
||||
geth_rpc,
|
||||
"eth_getBalance",
|
||||
["0x9907a0cf64ec9fbf6ed8fd4971090de88222a9ac", "0x10000"],
|
||||
)
|
||||
|
||||
|
||||
def deploy_and_wait(w3, number=1):
|
||||
contract = deploy_contract(
|
||||
w3,
|
||||
CONTRACTS["TestERC20A"],
|
||||
)
|
||||
|
||||
w3_wait_for_new_blocks(w3, number)
|
||||
return contract
|
||||
|
||||
|
||||
def test_get_storage_at(ethermint, geth):
|
||||
eth_rpc = ethermint.w3.provider
|
||||
geth_rpc = geth.w3.provider
|
||||
make_same_rpc_calls(
|
||||
eth_rpc,
|
||||
geth_rpc,
|
||||
"eth_getStorageAt",
|
||||
["0x57f96e6b86cdefdb3d412547816a82e3e0ebf9d2", "0x0", "latest"],
|
||||
)
|
||||
|
||||
contract = deploy_and_wait(ethermint.w3)
|
||||
res = eth_rpc.make_request("eth_getStorageAt", [contract.address, "0x0", "latest"])
|
||||
res, err = same_types(res["result"], EXPECTED_GET_STORAGE_AT)
|
||||
assert res, err
|
||||
|
||||
|
||||
def send_and_get_hash(w3, tx_value=10):
|
||||
# Do an ethereum transfer
|
||||
gas_price = w3.eth.gas_price
|
||||
tx = {"to": ADDRS["community"], "value": tx_value, "gasPrice": gas_price}
|
||||
return send_transaction(w3, tx, KEYS["validator"])["transactionHash"].hex()
|
||||
|
||||
|
||||
def test_get_proof(ethermint, geth):
|
||||
# on ethermint the proof query will fail for block numbers <= 2
|
||||
# so we must wait for several blocks
|
||||
w3_wait_for_block(ethermint.w3, 3)
|
||||
eth_rpc = ethermint.w3.provider
|
||||
geth_rpc = geth.w3.provider
|
||||
make_same_rpc_calls(
|
||||
eth_rpc,
|
||||
geth_rpc,
|
||||
"eth_getProof",
|
||||
["0x57f96e6b86cdefdb3d412547816a82e3e0ebf9d2", ["0x0"], "latest"],
|
||||
)
|
||||
|
||||
make_same_rpc_calls(
|
||||
eth_rpc,
|
||||
geth_rpc,
|
||||
"eth_getProof",
|
||||
["0x57f96e6b86cdefdb3d412547816a82e3e0ebf9d2", ["0x0"], "0x1024"],
|
||||
)
|
||||
|
||||
_ = send_and_get_hash(ethermint.w3)
|
||||
|
||||
proof = eth_rpc.make_request(
|
||||
"eth_getProof", [ADDRS["validator"], ["0x0"], "latest"]
|
||||
)
|
||||
res, err = same_types(proof["result"], EXPECTED_GET_PROOF)
|
||||
assert res, err
|
||||
|
||||
|
||||
def test_get_code(ethermint, geth):
|
||||
eth_rpc = ethermint.w3.provider
|
||||
geth_rpc = geth.w3.provider
|
||||
make_same_rpc_calls(
|
||||
eth_rpc,
|
||||
geth_rpc,
|
||||
"eth_getCode",
|
||||
["0x57f96e6b86cdefdb3d412547816a82e3e0ebf9d2", "latest"],
|
||||
)
|
||||
|
||||
# Do an ethereum transfer
|
||||
contract = deploy_and_wait(ethermint.w3)
|
||||
code = eth_rpc.make_request("eth_getCode", [contract.address, "latest"])
|
||||
expected = {"id": "4", "jsonrpc": "2.0", "result": "0x"}
|
||||
res, err = same_types(code, expected)
|
||||
assert res, err
|
||||
|
||||
|
||||
def test_get_block_transaction_count(ethermint, geth):
|
||||
eth_rpc = ethermint.w3.provider
|
||||
geth_rpc = geth.w3.provider
|
||||
make_same_rpc_calls(
|
||||
eth_rpc, geth_rpc, "eth_getBlockTransactionCountByNumber", ["0x0"]
|
||||
)
|
||||
|
||||
make_same_rpc_calls(
|
||||
eth_rpc, geth_rpc, "eth_getBlockTransactionCountByNumber", ["0x100"]
|
||||
)
|
||||
|
||||
tx_hash = send_and_get_hash(ethermint.w3)
|
||||
|
||||
tx_res = eth_rpc.make_request("eth_getTransactionByHash", [tx_hash])
|
||||
block_number = tx_res["result"]["blockNumber"]
|
||||
block_hash = tx_res["result"]["blockHash"]
|
||||
block_res = eth_rpc.make_request(
|
||||
"eth_getBlockTransactionCountByNumber", [block_number]
|
||||
)
|
||||
|
||||
expected = {"id": "1", "jsonrpc": "2.0", "result": "0x1"}
|
||||
res, err = same_types(block_res, expected)
|
||||
assert res, err
|
||||
|
||||
make_same_rpc_calls(
|
||||
eth_rpc,
|
||||
geth_rpc,
|
||||
"eth_getBlockTransactionCountByHash",
|
||||
["0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd"],
|
||||
)
|
||||
block_res = eth_rpc.make_request("eth_getBlockTransactionCountByHash", [block_hash])
|
||||
expected = {"id": "1", "jsonrpc": "2.0", "result": "0x1"}
|
||||
res, err = same_types(block_res, expected)
|
||||
assert res, err
|
||||
|
||||
|
||||
def test_get_transaction(ethermint, geth):
|
||||
eth_rpc = ethermint.w3.provider
|
||||
geth_rpc = geth.w3.provider
|
||||
make_same_rpc_calls(
|
||||
eth_rpc,
|
||||
geth_rpc,
|
||||
"eth_getTransactionByHash",
|
||||
["0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060"],
|
||||
)
|
||||
|
||||
tx_hash = send_and_get_hash(ethermint.w3)
|
||||
|
||||
tx_res = eth_rpc.make_request("eth_getTransactionByHash", [tx_hash])
|
||||
res, err = same_types(tx_res, EXPECTED_GET_TRANSACTION)
|
||||
assert res, err
|
||||
|
||||
|
||||
def test_get_transaction_receipt(ethermint, geth):
|
||||
eth_rpc = ethermint.w3.provider
|
||||
geth_rpc = geth.w3.provider
|
||||
make_same_rpc_calls(
|
||||
eth_rpc,
|
||||
geth_rpc,
|
||||
"eth_getTransactionReceipt",
|
||||
["0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060"],
|
||||
)
|
||||
|
||||
tx_hash = send_and_get_hash(ethermint.w3)
|
||||
|
||||
tx_res = eth_rpc.make_request("eth_getTransactionReceipt", [tx_hash])
|
||||
res, err = same_types(tx_res["result"], EXPECTED_GET_TRANSACTION_RECEIPT)
|
||||
assert res, err
|
||||
|
||||
|
||||
def test_fee_history(ethermint, geth):
|
||||
eth_rpc = ethermint.w3.provider
|
||||
geth_rpc = geth.w3.provider
|
||||
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_feeHistory", [4, "latest", [10, 90]])
|
||||
|
||||
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_feeHistory", [4, "0x5000", [10, 90]])
|
||||
|
||||
fee_history = eth_rpc.make_request("eth_feeHistory", [4, "latest", [10, 90]])
|
||||
|
||||
res, err = same_types(fee_history["result"], EXPECTED_FEE_HISTORY)
|
||||
assert res, err
|
||||
|
||||
|
||||
def test_estimate_gas(ethermint, geth):
|
||||
tx = {"to": ADDRS["community"], "from": ADDRS["validator"]}
|
||||
|
||||
eth_rpc = ethermint.w3.provider
|
||||
geth_rpc = geth.w3.provider
|
||||
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_estimateGas", [tx])
|
||||
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_estimateGas", [tx, "0x0"])
|
||||
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_estimateGas", [tx, "0x5000"])
|
||||
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_estimateGas", [{}])
|
||||
|
||||
|
||||
def make_same_rpc_calls(rpc1, rpc2, method, params):
|
||||
res1 = rpc1.make_request(method, params)
|
||||
res2 = rpc2.make_request(method, params)
|
||||
res, err = same_types(res1, res2)
|
||||
assert res, err
|
||||
|
||||
|
||||
def test_incomplete_send_transaction(ethermint, geth):
|
||||
# Send ethereum tx with nothing in from field
|
||||
eth_rpc = ethermint.w3.provider
|
||||
geth_rpc = geth.w3.provider
|
||||
gas_price = ethermint.w3.eth.gas_price
|
||||
tx = {"from": "", "to": ADDRS["community"], "value": 0, "gasPrice": gas_price}
|
||||
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_sendTransaction", [tx])
|
||||
|
||||
|
||||
def same_types(object_a, object_b):
|
||||
|
||||
if isinstance(object_a, dict):
|
||||
if not isinstance(object_b, dict):
|
||||
return False, "A is dict, B is not"
|
||||
keys = list(set(list(object_a.keys()) + list(object_b.keys())))
|
||||
for key in keys:
|
||||
if key in object_b and key in object_a:
|
||||
if not same_types(object_a[key], object_b[key]):
|
||||
return False, key + " key on dict are not of same type"
|
||||
else:
|
||||
return False, key + " key on json is not in both results"
|
||||
return True, ""
|
||||
elif isinstance(object_a, list):
|
||||
if not isinstance(object_b, list):
|
||||
return False, "A is list, B is not"
|
||||
if len(object_a) == 0 and len(object_b) == 0:
|
||||
return True, ""
|
||||
if len(object_a) > 0 and len(object_b) > 0:
|
||||
return same_types(object_a[0], object_b[0])
|
||||
else:
|
||||
return True
|
||||
elif object_a is None and object_b is None:
|
||||
return True, ""
|
||||
elif type(object_a) is type(object_b):
|
||||
return True, ""
|
||||
else:
|
||||
return (
|
||||
False,
|
||||
"different types. A is type "
|
||||
+ type(object_a).__name__
|
||||
+ " B is type "
|
||||
+ type(object_b).__name__,
|
||||
)
|
||||
@@ -0,0 +1,173 @@
|
||||
import configparser
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from dateutil.parser import isoparse
|
||||
from pystarport import ports
|
||||
from pystarport.cluster import SUPERVISOR_CONFIG_FILE
|
||||
|
||||
from .network import Ethermint, setup_custom_ethermint
|
||||
from .utils import (
|
||||
ADDRS,
|
||||
CONTRACTS,
|
||||
deploy_contract,
|
||||
parse_events,
|
||||
send_transaction,
|
||||
wait_for_block,
|
||||
wait_for_block_time,
|
||||
wait_for_port,
|
||||
)
|
||||
|
||||
|
||||
def init_cosmovisor(home):
|
||||
"""
|
||||
build and setup cosmovisor directory structure in each node's home directory
|
||||
"""
|
||||
cosmovisor = home / "cosmovisor"
|
||||
cosmovisor.mkdir()
|
||||
(cosmovisor / "upgrades").symlink_to("../../../upgrades")
|
||||
(cosmovisor / "genesis").symlink_to("./upgrades/genesis")
|
||||
|
||||
|
||||
def post_init(path, base_port, config):
|
||||
"""
|
||||
prepare cosmovisor for each node
|
||||
"""
|
||||
chain_id = "ethermint_9000-1"
|
||||
cfg = json.loads((path / chain_id / "config.json").read_text())
|
||||
for i, _ in enumerate(cfg["validators"]):
|
||||
home = path / chain_id / f"node{i}"
|
||||
init_cosmovisor(home)
|
||||
|
||||
# patch supervisord ini config
|
||||
ini_path = path / chain_id / SUPERVISOR_CONFIG_FILE
|
||||
ini = configparser.RawConfigParser()
|
||||
ini.read(ini_path)
|
||||
reg = re.compile(rf"^program:{chain_id}-node(\d+)")
|
||||
for section in ini.sections():
|
||||
m = reg.match(section)
|
||||
if m:
|
||||
i = m.group(1)
|
||||
ini[section].update(
|
||||
{
|
||||
"command": f"cosmovisor start --home %(here)s/node{i}",
|
||||
"environment": (
|
||||
f"DAEMON_NAME=ethermintd,DAEMON_HOME=%(here)s/node{i}"
|
||||
),
|
||||
}
|
||||
)
|
||||
with ini_path.open("w") as fp:
|
||||
ini.write(fp)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def custom_ethermint(tmp_path_factory):
|
||||
path = tmp_path_factory.mktemp("upgrade")
|
||||
cmd = [
|
||||
"nix-build",
|
||||
Path(__file__).parent / "configs/upgrade-test-package.nix",
|
||||
"-o",
|
||||
path / "upgrades",
|
||||
]
|
||||
print(*cmd)
|
||||
subprocess.run(cmd, check=True)
|
||||
# init with genesis binary
|
||||
yield from setup_custom_ethermint(
|
||||
path,
|
||||
26100,
|
||||
Path(__file__).parent / "configs/cosmovisor.jsonnet",
|
||||
post_init=post_init,
|
||||
chain_binary=str(path / "upgrades/genesis/bin/ethermintd"),
|
||||
)
|
||||
|
||||
|
||||
def test_cosmovisor_upgrade(custom_ethermint: Ethermint):
|
||||
"""
|
||||
- propose an upgrade and pass it
|
||||
- wait for it to happen
|
||||
- it should work transparently
|
||||
- check that queries on legacy blocks still works after upgrade.
|
||||
"""
|
||||
cli = custom_ethermint.cosmos_cli()
|
||||
height = cli.block_height()
|
||||
target_height = height + 5
|
||||
print("upgrade height", target_height)
|
||||
|
||||
w3 = custom_ethermint.w3
|
||||
contract = deploy_contract(w3, CONTRACTS["TestERC20A"])
|
||||
old_height = w3.eth.block_number
|
||||
old_balance = w3.eth.get_balance(ADDRS["validator"], block_identifier=old_height)
|
||||
old_base_fee = w3.eth.get_block(old_height).baseFeePerGas
|
||||
old_erc20_balance = contract.caller.balanceOf(ADDRS["validator"])
|
||||
print("old values", old_height, old_balance, old_base_fee)
|
||||
|
||||
plan_name = "integration-test-upgrade"
|
||||
rsp = cli.gov_propose(
|
||||
"community",
|
||||
"software-upgrade",
|
||||
{
|
||||
"name": plan_name,
|
||||
"title": "upgrade test",
|
||||
"description": "ditto",
|
||||
"upgrade-height": target_height,
|
||||
"deposit": "10000aphoton",
|
||||
},
|
||||
)
|
||||
assert rsp["code"] == 0, rsp["raw_log"]
|
||||
|
||||
# get proposal_id
|
||||
ev = parse_events(rsp["logs"])["submit_proposal"]
|
||||
assert ev["proposal_type"] == "SoftwareUpgrade", rsp
|
||||
proposal_id = ev["proposal_id"]
|
||||
|
||||
rsp = cli.gov_vote("validator", proposal_id, "yes")
|
||||
assert rsp["code"] == 0, rsp["raw_log"]
|
||||
# rsp = custom_ethermint.cosmos_cli(1).gov_vote("validator", proposal_id, "yes")
|
||||
# assert rsp["code"] == 0, rsp["raw_log"]
|
||||
|
||||
proposal = cli.query_proposal(proposal_id)
|
||||
wait_for_block_time(cli, isoparse(proposal["voting_end_time"]))
|
||||
proposal = cli.query_proposal(proposal_id)
|
||||
assert proposal["status"] == "PROPOSAL_STATUS_PASSED", proposal
|
||||
|
||||
# update cli chain binary
|
||||
custom_ethermint.chain_binary = (
|
||||
Path(custom_ethermint.chain_binary).parent.parent.parent
|
||||
/ f"{plan_name}/bin/ethermintd"
|
||||
)
|
||||
cli = custom_ethermint.cosmos_cli()
|
||||
|
||||
# block should pass the target height
|
||||
wait_for_block(cli, target_height + 1, timeout=480)
|
||||
wait_for_port(ports.rpc_port(custom_ethermint.base_port(0)))
|
||||
|
||||
# test migrate keystore
|
||||
cli.migrate_keystore()
|
||||
|
||||
# check basic tx works after upgrade
|
||||
wait_for_port(ports.evmrpc_port(custom_ethermint.base_port(0)))
|
||||
|
||||
receipt = send_transaction(
|
||||
w3,
|
||||
{
|
||||
"to": ADDRS["community"],
|
||||
"value": 1000,
|
||||
"maxFeePerGas": 1000000000000,
|
||||
"maxPriorityFeePerGas": 10000,
|
||||
},
|
||||
)
|
||||
assert receipt.status == 1
|
||||
|
||||
# check json-rpc query on older blocks works
|
||||
assert old_balance == w3.eth.get_balance(
|
||||
ADDRS["validator"], block_identifier=old_height
|
||||
)
|
||||
assert old_base_fee == w3.eth.get_block(old_height).baseFeePerGas
|
||||
|
||||
# check eth_call on older blocks works
|
||||
assert old_erc20_balance == contract.caller(
|
||||
block_identifier=target_height - 2
|
||||
).balanceOf(ADDRS["validator"])
|
||||
@@ -0,0 +1,180 @@
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import bech32
|
||||
from dateutil.parser import isoparse
|
||||
from dotenv import load_dotenv
|
||||
from eth_account import Account
|
||||
from hexbytes import HexBytes
|
||||
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",
|
||||
"Greeter": "Greeter.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()
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def wait_for_port(port, host="127.0.0.1", timeout=40.0):
|
||||
start_time = time.perf_counter()
|
||||
while True:
|
||||
try:
|
||||
with socket.create_connection((host, port), timeout=timeout):
|
||||
break
|
||||
except OSError as ex:
|
||||
time.sleep(0.1)
|
||||
if time.perf_counter() - start_time >= timeout:
|
||||
raise TimeoutError(
|
||||
"Waited too long for the port {} on host {} to start accepting "
|
||||
"connections.".format(port, host)
|
||||
) 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 wait_for_new_blocks(cli, n):
|
||||
begin_height = int((cli.status())["SyncInfo"]["latest_block_height"])
|
||||
while True:
|
||||
time.sleep(0.5)
|
||||
cur_height = int((cli.status())["SyncInfo"]["latest_block_height"])
|
||||
if cur_height - begin_height >= n:
|
||||
break
|
||||
|
||||
|
||||
def wait_for_block(cli, height, timeout=240):
|
||||
for i in range(timeout * 2):
|
||||
try:
|
||||
status = cli.status()
|
||||
except AssertionError as e:
|
||||
print(f"get sync status failed: {e}", file=sys.stderr)
|
||||
else:
|
||||
current_height = int(status["SyncInfo"]["latest_block_height"])
|
||||
if current_height >= height:
|
||||
break
|
||||
print("current block height", current_height)
|
||||
time.sleep(0.5)
|
||||
else:
|
||||
raise TimeoutError(f"wait for block {height} timeout")
|
||||
|
||||
|
||||
def w3_wait_for_block(w3, height, timeout=240):
|
||||
for i in range(timeout * 2):
|
||||
try:
|
||||
current_height = w3.eth.block_number
|
||||
except Exception as e:
|
||||
print(f"get json-rpc block number failed: {e}", file=sys.stderr)
|
||||
else:
|
||||
if current_height >= height:
|
||||
break
|
||||
print("current block height", current_height)
|
||||
time.sleep(0.5)
|
||||
else:
|
||||
raise TimeoutError(f"wait for block {height} timeout")
|
||||
|
||||
|
||||
def wait_for_block_time(cli, t):
|
||||
print("wait for block time", t)
|
||||
while True:
|
||||
now = isoparse((cli.status())["SyncInfo"]["latest_block_time"])
|
||||
print("block time now:", now)
|
||||
if now >= t:
|
||||
break
|
||||
time.sleep(0.5)
|
||||
|
||||
|
||||
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))
|
||||
|
||||
|
||||
def sign_transaction(w3, tx, key=KEYS["validator"]):
|
||||
"fill default fields and sign"
|
||||
acct = Account.from_key(key)
|
||||
tx["from"] = acct.address
|
||||
tx = fill_transaction_defaults(w3, tx)
|
||||
tx = fill_nonce(w3, tx)
|
||||
return acct.sign_transaction(tx)
|
||||
|
||||
|
||||
def send_transaction(w3, tx, key=KEYS["validator"]):
|
||||
signed = sign_transaction(w3, tx, key)
|
||||
txhash = w3.eth.send_raw_transaction(signed.rawTransaction)
|
||||
return w3.eth.wait_for_transaction_receipt(txhash)
|
||||
|
||||
|
||||
def send_successful_transaction(w3):
|
||||
signed = sign_transaction(w3, {"to": ADDRS["community"], "value": 1000})
|
||||
txhash = w3.eth.send_raw_transaction(signed.rawTransaction)
|
||||
receipt = w3.eth.wait_for_transaction_receipt(txhash)
|
||||
assert receipt.status == 1
|
||||
return txhash
|
||||
|
||||
|
||||
def eth_to_bech32(addr, prefix=ETHERMINT_ADDRESS_PREFIX):
|
||||
bz = bech32.convertbits(HexBytes(addr), 8, 5)
|
||||
return bech32.bech32_encode(prefix, bz)
|
||||
|
||||
|
||||
def supervisorctl(inipath, *args):
|
||||
subprocess.run(
|
||||
(sys.executable, "-msupervisor.supervisorctl", "-c", inipath, *args),
|
||||
check=True,
|
||||
)
|
||||
|
||||
|
||||
def parse_events(logs):
|
||||
return {
|
||||
ev["type"]: {attr["key"]: attr["value"] for attr in ev["attributes"]}
|
||||
for ev in logs[0]["events"]
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
// This is a test utility for Ethermint's Web3 JSON-RPC services.
|
||||
//
|
||||
// To run these tests please first ensure you have the ethermintd running
|
||||
//
|
||||
// You can configure the desired HOST and MODE as well in integration-test-all.sh
|
||||
package rpc
|
||||
|
||||
@@ -10,12 +8,13 @@ import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
rpctypes "github.com/cerc-io/laconicd/rpc/ethereum/types"
|
||||
rpctypes "github.com/cerc-io/laconicd/rpc/types"
|
||||
)
|
||||
|
||||
// func TestMain(m *testing.M) {
|
||||
@@ -269,8 +268,9 @@ func TestEth_Pending_GetTransactionByBlockNumberAndIndex(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEth_Pending_GetTransactionByHash(t *testing.T) {
|
||||
sleep := 0 * time.Second
|
||||
// negative case, check that it returns empty.
|
||||
rpcRes := Call(t, "eth_getTransactionByHash", []interface{}{"0xec5fa15e1368d6ac314f9f64118c5794f076f63c02e66f97ea5fe1de761a8973"})
|
||||
rpcRes := CallWithSleep(t, "eth_getTransactionByHash", []interface{}{"0xec5fa15e1368d6ac314f9f64118c5794f076f63c02e66f97ea5fe1de761a8973"}, sleep)
|
||||
var tx map[string]interface{}
|
||||
err := json.Unmarshal(rpcRes.Result, &tx)
|
||||
require.NoError(t, err)
|
||||
@@ -281,17 +281,17 @@ func TestEth_Pending_GetTransactionByHash(t *testing.T) {
|
||||
param := makePendingTxParams(t)
|
||||
param[0]["data"] = data
|
||||
|
||||
txRes := Call(t, "eth_sendTransaction", param)
|
||||
txRes := CallWithSleep(t, "eth_sendTransaction", param, sleep)
|
||||
var txHash common.Hash
|
||||
err = txHash.UnmarshalJSON(txRes.Result)
|
||||
require.NoError(t, err)
|
||||
|
||||
rpcRes = Call(t, "eth_getTransactionByHash", []interface{}{txHash})
|
||||
rpcRes = CallWithSleep(t, "eth_getTransactionByHash", []interface{}{txHash}, sleep)
|
||||
var pendingTx map[string]interface{}
|
||||
err = json.Unmarshal(rpcRes.Result, &pendingTx)
|
||||
require.NoError(t, err)
|
||||
|
||||
txsRes := Call(t, "eth_getPendingTransactions", []interface{}{})
|
||||
txsRes := CallWithSleep(t, "eth_getPendingTransactions", []interface{}{}, sleep)
|
||||
var pendingTxs []map[string]interface{}
|
||||
err = json.Unmarshal(txsRes.Result, &pendingTxs)
|
||||
require.NoError(t, err)
|
||||
|
||||
+31
-2
@@ -17,7 +17,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
rpctypes "github.com/cerc-io/laconicd/rpc/ethereum/types"
|
||||
rpctypes "github.com/cerc-io/laconicd/rpc/types"
|
||||
ethermint "github.com/cerc-io/laconicd/types"
|
||||
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
|
||||
|
||||
@@ -301,6 +301,17 @@ func deployTestContract(t *testing.T) (hexutil.Bytes, map[string]interface{}) {
|
||||
return hash, receipt
|
||||
}
|
||||
|
||||
func getTransactionReceipt(t *testing.T, hash hexutil.Bytes) map[string]interface{} {
|
||||
param := []string{hash.String()}
|
||||
rpcRes := call(t, "eth_getTransactionReceipt", param)
|
||||
|
||||
receipt := make(map[string]interface{})
|
||||
err := json.Unmarshal(rpcRes.Result, &receipt)
|
||||
require.NoError(t, err)
|
||||
|
||||
return receipt
|
||||
}
|
||||
|
||||
func waitForReceipt(t *testing.T, hash hexutil.Bytes) map[string]interface{} {
|
||||
timeout := time.After(12 * time.Second)
|
||||
ticker := time.Tick(500 * time.Millisecond)
|
||||
@@ -310,7 +321,7 @@ func waitForReceipt(t *testing.T, hash hexutil.Bytes) map[string]interface{} {
|
||||
case <-timeout:
|
||||
return nil
|
||||
case <-ticker:
|
||||
receipt := GetTransactionReceipt(t, hash)
|
||||
receipt := getTransactionReceipt(t, hash)
|
||||
if receipt != nil {
|
||||
return receipt
|
||||
}
|
||||
@@ -318,6 +329,24 @@ func waitForReceipt(t *testing.T, hash hexutil.Bytes) map[string]interface{} {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEth_IncompleteSendTransaction(t *testing.T) {
|
||||
// get gasprice
|
||||
gasPrice := GetGasPrice(t)
|
||||
|
||||
// make tx params without from address
|
||||
param := make([]map[string]string, 1)
|
||||
param[0] = make(map[string]string)
|
||||
param[0]["from"] = ""
|
||||
param[0]["to"] = "0x1122334455667788990011223344556677889900"
|
||||
param[0]["value"] = "0x1"
|
||||
param[0]["gasPrice"] = gasPrice
|
||||
_, err := callWithError("eth_sendTransaction", param)
|
||||
|
||||
// require well-formatted error (should not be "method handler crashed")
|
||||
require.Error(t, err)
|
||||
require.NotEqual(t, err.Error(), "method handler crashed", "no from field dealt with incorrectly")
|
||||
}
|
||||
|
||||
func TestEth_GetFilterChanges_NoTopics(t *testing.T) {
|
||||
rpcRes := call(t, "eth_blockNumber", []string{})
|
||||
|
||||
|
||||
+10
-4
@@ -63,12 +63,14 @@ func CreateRequest(method string, params interface{}) Request {
|
||||
}
|
||||
}
|
||||
|
||||
func Call(t *testing.T, method string, params interface{}) *Response {
|
||||
func CallWithSleep(t *testing.T, method string, params interface{}, sleep time.Duration) *Response {
|
||||
req, err := json.Marshal(CreateRequest(method, params))
|
||||
require.NoError(t, err)
|
||||
|
||||
var rpcRes *Response
|
||||
time.Sleep(1 * time.Second)
|
||||
if sleep > 0 {
|
||||
time.Sleep(sleep)
|
||||
}
|
||||
|
||||
httpReq, err := http.NewRequestWithContext(context.Background(), "POST", HOST, bytes.NewBuffer(req))
|
||||
if err != nil {
|
||||
@@ -94,6 +96,10 @@ func Call(t *testing.T, method string, params interface{}) *Response {
|
||||
return rpcRes
|
||||
}
|
||||
|
||||
func Call(t *testing.T, method string, params interface{}) *Response {
|
||||
return CallWithSleep(t, method, params, time.Second)
|
||||
}
|
||||
|
||||
func CallWithError(method string, params interface{}) (*Response, error) {
|
||||
req, err := json.Marshal(CreateRequest(method, params))
|
||||
if err != nil {
|
||||
@@ -167,7 +173,7 @@ func DeployTestContract(t *testing.T, addr []byte) (hexutil.Bytes, map[string]in
|
||||
param := make([]map[string]string, 1)
|
||||
param[0] = make(map[string]string)
|
||||
param[0]["from"] = "0x" + fmt.Sprintf("%x", addr)
|
||||
param[0]["data"] = "0x6080604052348015600f57600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a2603580604b6000396000f3fe6080604052600080fdfea165627a7a723058206cab665f0f557620554bb45adf266708d2bd349b8a4314bdff205ee8440e3c240029"
|
||||
param[0]["data"] = "0x6080604052348015600f57600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a2603580604b6000396000f3fe6080604052600080fdfea165627a7a723058206cab665f0f557620554bb45adf266708d2bd349b8a4314bdff205ee8440e3c240029" //nolint:lll
|
||||
param[0]["gas"] = "0x200000"
|
||||
|
||||
rpcRes := Call(t, "personal_unlockAccount", []interface{}{param[0]["from"], ""})
|
||||
@@ -206,7 +212,7 @@ func DeployTestContractWithFunction(t *testing.T, addr []byte) hexutil.Bytes {
|
||||
// }
|
||||
// }
|
||||
|
||||
bytecode := "0x608060405234801561001057600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a260d08061004d6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063eb8ac92114602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506062565b005b8160008190555080827ff3ca124a697ba07e8c5e80bebcfcc48991fc16a63170e8a9206e30508960d00360405160405180910390a3505056fea265627a7a723158201d94d2187aaf3a6790527b615fcc40970febf0385fa6d72a2344848ebd0df3e964736f6c63430005110032"
|
||||
bytecode := "0x608060405234801561001057600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a260d08061004d6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063eb8ac92114602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506062565b005b8160008190555080827ff3ca124a697ba07e8c5e80bebcfcc48991fc16a63170e8a9206e30508960d00360405160405180910390a3505056fea265627a7a723158201d94d2187aaf3a6790527b615fcc40970febf0385fa6d72a2344848ebd0df3e964736f6c63430005110032" //nolint:lll
|
||||
|
||||
param := make([]map[string]string, 1)
|
||||
param[0] = make(map[string]string)
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/stretchr/testify/require"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
HOST_WS = os.Getenv("HOST_WS")
|
||||
wsUrl string
|
||||
)
|
||||
|
||||
func init() {
|
||||
if HOST_WS == "" {
|
||||
HOST_WS = "localhost:8542"
|
||||
}
|
||||
|
||||
u := url.URL{Scheme: "ws", Host: HOST_WS, Path: ""}
|
||||
wsUrl = u.String()
|
||||
}
|
||||
|
||||
func TestWsSingleRequest(t *testing.T) {
|
||||
t.Log("test simple rpc request net_version with Websocket")
|
||||
|
||||
wc, _, err := websocket.DefaultDialer.Dial(wsUrl, nil)
|
||||
require.NoError(t, err)
|
||||
defer wc.Close()
|
||||
|
||||
wsWriteMessage(t, wc, `{"jsonrpc":"2.0","method":"net_version","params":[],"id":1}`)
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
mb := readMessage(t, wc)
|
||||
msg := jsonUnmarshal(t, mb)
|
||||
result, ok := msg["result"].(string)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "9000", result)
|
||||
}
|
||||
|
||||
func TestWsBatchRequest(t *testing.T) {
|
||||
t.Log("test batch request with Websocket")
|
||||
|
||||
wc, _, err := websocket.DefaultDialer.Dial(wsUrl, nil)
|
||||
require.NoError(t, err)
|
||||
defer wc.Close()
|
||||
|
||||
wsWriteMessage(t, wc, `[{"jsonrpc":"2.0","method":"net_version","params":[],"id":1},{"jsonrpc":"2.0","method":"eth_protocolVersion","params":[],"id":2}]`)
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
mb := readMessage(t, wc)
|
||||
|
||||
var msg []map[string]interface{}
|
||||
err = json.Unmarshal(mb, &msg)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(msg))
|
||||
|
||||
// net_version
|
||||
resNetVersion := msg[0]
|
||||
result, ok := resNetVersion["result"].(string)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "9000", result)
|
||||
id, ok := resNetVersion["id"].(float64)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, 1, int(id))
|
||||
|
||||
// eth_protocolVersion
|
||||
resEthProtocolVersion := msg[1]
|
||||
result, ok = resEthProtocolVersion["result"].(string)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, "0x41", result)
|
||||
id, ok = resEthProtocolVersion["id"].(float64)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, 2, int(id))
|
||||
}
|
||||
|
||||
func TestWsEth_subscribe_newHeads(t *testing.T) {
|
||||
t.Log("test eth_subscribe newHeads with Websocket")
|
||||
|
||||
wc, _, err := websocket.DefaultDialer.Dial(wsUrl, nil)
|
||||
require.NoError(t, err)
|
||||
defer wc.Close()
|
||||
|
||||
wsWriteMessage(t, wc, `{"id":1,"method":"eth_subscribe","params":["newHeads",{}]}`)
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
mb := readMessage(t, wc)
|
||||
msg := jsonUnmarshal(t, mb)
|
||||
subscribeId, ok := msg["result"].(string)
|
||||
require.True(t, ok)
|
||||
require.True(t, strings.HasPrefix(subscribeId, "0x"))
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
mb = readMessage(t, wc)
|
||||
msg = jsonUnmarshal(t, mb)
|
||||
method, ok := msg["method"].(string)
|
||||
require.Equal(t, "eth_subscription", method)
|
||||
|
||||
// id should not exist with eth_subscription event
|
||||
_, ok = msg["id"].(float64)
|
||||
require.False(t, ok)
|
||||
|
||||
wsUnsubscribe(t, wc, subscribeId) // eth_unsubscribe
|
||||
}
|
||||
|
||||
func TestWsEth_subscribe_log(t *testing.T) {
|
||||
t.Log("test eth_subscribe log with websocket")
|
||||
|
||||
wc, _, err := websocket.DefaultDialer.Dial(wsUrl, nil)
|
||||
require.NoError(t, err)
|
||||
defer wc.Close()
|
||||
|
||||
wsWriteMessage(t, wc, fmt.Sprintf(`{"jsonrpc":"2.0","id":1,"method":"eth_subscribe","params":["logs",{"topics":["%s", "%s"]}]}`, helloTopic, worldTopic))
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
mb := readMessage(t, wc)
|
||||
msg := jsonUnmarshal(t, mb)
|
||||
subscribeId, ok := msg["result"].(string)
|
||||
require.True(t, ok)
|
||||
require.True(t, strings.HasPrefix(subscribeId, "0x"))
|
||||
|
||||
// do something here to receive subscription messages
|
||||
deployTestContractWithFunction(t)
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
mb = readMessage(t, wc)
|
||||
msg = jsonUnmarshal(t, mb)
|
||||
method, ok := msg["method"].(string)
|
||||
require.Equal(t, "eth_subscription", method)
|
||||
|
||||
// id should not exist with eth_subscription event
|
||||
_, ok = msg["id"].(float64)
|
||||
require.False(t, ok)
|
||||
|
||||
wsUnsubscribe(t, wc, subscribeId) // eth_unsubscribe
|
||||
}
|
||||
|
||||
func wsWriteMessage(t *testing.T, wc *websocket.Conn, jsonStr string) {
|
||||
t.Logf("send: %s", jsonStr)
|
||||
err := wc.WriteMessage(websocket.TextMessage, []byte(jsonStr))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func wsUnsubscribe(t *testing.T, wc *websocket.Conn, subscribeId string) {
|
||||
t.Logf("eth_unsubscribe %s", subscribeId)
|
||||
wsWriteMessage(t, wc, fmt.Sprintf(`{"id":1,"method":"eth_unsubscribe","params":["%s"]}`, subscribeId))
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
mb := readMessage(t, wc)
|
||||
msg := jsonUnmarshal(t, mb)
|
||||
|
||||
result, ok := msg["result"].(bool)
|
||||
require.True(t, ok)
|
||||
require.True(t, result)
|
||||
}
|
||||
|
||||
func readMessage(t *testing.T, wc *websocket.Conn) []byte {
|
||||
_, mb, err := wc.ReadMessage()
|
||||
require.NoError(t, err)
|
||||
t.Logf("recv: %s", mb)
|
||||
return mb
|
||||
}
|
||||
|
||||
func jsonUnmarshal(t *testing.T, mb []byte) map[string]interface{} {
|
||||
var msg map[string]interface{}
|
||||
err := json.Unmarshal(mb, &msg)
|
||||
require.NoError(t, err)
|
||||
return msg
|
||||
}
|
||||
@@ -16,7 +16,7 @@ Increasingly difficult tests are provided:
|
||||
|
||||
To run the tests, you can use the `test-helper.js` utility to test all suites under `ganache` or `ethermint` network. The `test-helper.js` will help you spawn an `ethermintd` process before running the tests.
|
||||
|
||||
You can simply run `yarn test --network ethermint` to run all tests with ethermint network, or you can run `yarn test --network ganache` to use ganache shipped with truffle. In most cases, there two networks should produce identical test results.
|
||||
You can simply run `yarn test --network ethermint` to run all tests with ethermint network, or you can run `yarn test --network ganache` to use ganache shipped with truffle. In most cases, there two networks should produce identical test results.
|
||||
|
||||
If you only want to run a few test cases, append the name of tests following by the command line. For example, use `yarn test --network ethermint basic` to run the `basic` test under `ethermint` network.
|
||||
|
||||
@@ -32,13 +32,12 @@ You will now have three ethereum accounts unlocked in the test node:
|
||||
- `0xddd64b4712f7c8f1ace3c145c950339eddaf221d` (User 1)
|
||||
- `0x0f54f47bf9b8e317b214ccd6a7c3e38b893cd7f0` (user 2)
|
||||
|
||||
|
||||
Keep the terminal window open, go into any of the tests and run `yarn test-ethermint`. You should see `ethermintd` accepting transactions and producing blocks. You should be able to query for any transaction via:
|
||||
|
||||
- `ethermintd query tx <cosmos-sdk tx>`
|
||||
- `curl localhost:8545 -H "Content-Type:application/json" -X POST --data '{"jsonrpc":"2.0","method":"eth_getTransactionByHash","params":["<ethereum tx>"],"id":1}'`
|
||||
|
||||
From here, in your other available terminal,
|
||||
From here, in your other available terminal,
|
||||
And obviously more, via the Ethereum JSON-RPC API).
|
||||
|
||||
When in doubt, you can also run the tests against a Ganache instance via `yarn test-ganache`, to make sure they are behaving correctly.
|
||||
@@ -48,11 +47,11 @@ When in doubt, you can also run the tests against a Ganache instance via `yarn t
|
||||
The [`init-test-node.sh`](./init-test-node.sh) script sets up ethermint with the following accounts:
|
||||
|
||||
- `ethm10jmp6sgh4cc6zt3e8gw05wavvejgr5pwtu750w` (Validator)
|
||||
- `0x7cB61D4117AE31a12E393a1Cfa3BaC666481D02E`
|
||||
- `0x7cB61D4117AE31a12E393a1Cfa3BaC666481D02E`
|
||||
- `ethm1cml96vmptgw99syqrrz8az79xer2pcgp767p9e` (User 1)
|
||||
- `0xC6Fe5D33615a1C52c08018c47E8Bc53646A0E101`
|
||||
- `0xC6Fe5D33615a1C52c08018c47E8Bc53646A0E101`
|
||||
- `ethm1jcltmuhplrdcwp7stlr4hlhlhgd4htqhgjpff2` (user 2)
|
||||
- `0x963EBDf2e1f8DB8707D05FC75bfeFFBa1B5BaC17`
|
||||
- `0x963EBDf2e1f8DB8707D05FC75bfeFFBa1B5BaC17`
|
||||
|
||||
Each with roughly 100 ETH available (1e18 photon).
|
||||
|
||||
|
||||
@@ -24,38 +24,38 @@ USER4_KEY="user4"
|
||||
USER4_MNEMONIC="doll midnight silk carpet brush boring pluck office gown inquiry duck chief aim exit gain never tennis crime fragile ship cloud surface exotic patch"
|
||||
|
||||
# remove existing daemon and client
|
||||
rm -rf ~/.laconic*
|
||||
rm -rf ~/.ethermint*
|
||||
|
||||
# Import keys from mnemonics
|
||||
echo $VAL_MNEMONIC | laconicd keys add $VAL_KEY --recover --keyring-backend test --algo "eth_secp256k1"
|
||||
echo $USER1_MNEMONIC | laconicd keys add $USER1_KEY --recover --keyring-backend test --algo "eth_secp256k1"
|
||||
echo $USER2_MNEMONIC | laconicd keys add $USER2_KEY --recover --keyring-backend test --algo "eth_secp256k1"
|
||||
echo $USER3_MNEMONIC | laconicd keys add $USER3_KEY --recover --keyring-backend test --algo "eth_secp256k1"
|
||||
echo $USER4_MNEMONIC | laconicd keys add $USER4_KEY --recover --keyring-backend test --algo "eth_secp256k1"
|
||||
echo $VAL_MNEMONIC | ethermintd keys add $VAL_KEY --recover --keyring-backend test --algo "eth_secp256k1"
|
||||
echo $USER1_MNEMONIC | ethermintd keys add $USER1_KEY --recover --keyring-backend test --algo "eth_secp256k1"
|
||||
echo $USER2_MNEMONIC | ethermintd keys add $USER2_KEY --recover --keyring-backend test --algo "eth_secp256k1"
|
||||
echo $USER3_MNEMONIC | ethermintd keys add $USER3_KEY --recover --keyring-backend test --algo "eth_secp256k1"
|
||||
echo $USER4_MNEMONIC | ethermintd keys add $USER4_KEY --recover --keyring-backend test --algo "eth_secp256k1"
|
||||
|
||||
laconicd init $MONIKER --chain-id $CHAINID
|
||||
ethermintd init $MONIKER --chain-id $CHAINID
|
||||
|
||||
# Set gas limit in genesis
|
||||
cat $HOME/.laconicd/config/genesis.json | jq '.consensus_params["block"]["max_gas"]="10000000"' > $HOME/.laconicd/config/tmp_genesis.json && mv $HOME/.laconicd/config/tmp_genesis.json $HOME/.laconicd/config/genesis.json
|
||||
cat $HOME/.ethermintd/config/genesis.json | jq '.consensus_params["block"]["max_gas"]="10000000"' > $HOME/.ethermintd/config/tmp_genesis.json && mv $HOME/.ethermintd/config/tmp_genesis.json $HOME/.ethermintd/config/genesis.json
|
||||
|
||||
# Reduce the block time to 1s
|
||||
sed -i -e '/^timeout_commit =/ s/= .*/= "850ms"/' $HOME/.laconicd/config/config.toml
|
||||
sed -i -e '/^timeout_commit =/ s/= .*/= "850ms"/' $HOME/.ethermintd/config/config.toml
|
||||
|
||||
# Allocate genesis accounts (cosmos formatted addresses)
|
||||
laconicd add-genesis-account "$(laconicd keys show $VAL_KEY -a --keyring-backend test)" 1000000000000000000000aphoton,1000000000000000000stake --keyring-backend test
|
||||
laconicd add-genesis-account "$(laconicd keys show $USER1_KEY -a --keyring-backend test)" 1000000000000000000000aphoton,1000000000000000000stake --keyring-backend test
|
||||
laconicd add-genesis-account "$(laconicd keys show $USER2_KEY -a --keyring-backend test)" 1000000000000000000000aphoton,1000000000000000000stake --keyring-backend test
|
||||
laconicd add-genesis-account "$(laconicd keys show $USER3_KEY -a --keyring-backend test)" 1000000000000000000000aphoton,1000000000000000000stake --keyring-backend test
|
||||
laconicd add-genesis-account "$(laconicd keys show $USER4_KEY -a --keyring-backend test)" 1000000000000000000000aphoton,1000000000000000000stake --keyring-backend test
|
||||
ethermintd add-genesis-account "$(ethermintd keys show $VAL_KEY -a --keyring-backend test)" 1000000000000000000000aphoton,1000000000000000000stake --keyring-backend test
|
||||
ethermintd add-genesis-account "$(ethermintd keys show $USER1_KEY -a --keyring-backend test)" 1000000000000000000000aphoton,1000000000000000000stake --keyring-backend test
|
||||
ethermintd add-genesis-account "$(ethermintd keys show $USER2_KEY -a --keyring-backend test)" 1000000000000000000000aphoton,1000000000000000000stake --keyring-backend test
|
||||
ethermintd add-genesis-account "$(ethermintd keys show $USER3_KEY -a --keyring-backend test)" 1000000000000000000000aphoton,1000000000000000000stake --keyring-backend test
|
||||
ethermintd add-genesis-account "$(ethermintd keys show $USER4_KEY -a --keyring-backend test)" 1000000000000000000000aphoton,1000000000000000000stake --keyring-backend test
|
||||
|
||||
# Sign genesis transaction
|
||||
laconicd gentx $VAL_KEY 1000000000000000000stake --amount=1000000000000000000000aphoton --chain-id $CHAINID --keyring-backend test
|
||||
ethermintd gentx $VAL_KEY 1000000000000000000stake --amount=1000000000000000000000aphoton --chain-id $CHAINID --keyring-backend test
|
||||
|
||||
# Collect genesis tx
|
||||
laconicd collect-gentxs
|
||||
ethermintd collect-gentxs
|
||||
|
||||
# Run this to ensure everything worked and that the genesis file is setup correctly
|
||||
laconicd validate-genesis
|
||||
ethermintd validate-genesis
|
||||
|
||||
# Start the node (remove the --pruning=nothing flag if historical queries are not needed)
|
||||
laconicd start --pruning=nothing --rpc.unsafe --keyring-backend test --log_level info --json-rpc.api eth,txpool,personal,net,debug,web3 --api.enable --mode validator
|
||||
ethermintd start --pruning=nothing --rpc.unsafe --keyring-backend test --log_level info --json-rpc.api eth,txpool,personal,net,debug,web3 --api.enable
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"truffle": "5.4.14",
|
||||
"truffle": "5.5.8",
|
||||
"yargs": "^17.0.1",
|
||||
"patch-package": "^6.4.7"
|
||||
},
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
"truffle-assertions": "^0.9.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@truffle/hdwallet-provider": "^1.5.1-alpha.1"
|
||||
"@truffle/hdwallet-provider": "^1.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@truffle/hdwallet-provider": "^1.4.1",
|
||||
"@truffle/hdwallet-provider": "^1.6.0",
|
||||
"concurrently": "^6.2.0",
|
||||
"truffle-assertions": "^0.9.2"
|
||||
},
|
||||
|
||||
@@ -181,7 +181,7 @@ function setupNetwork({ runConfig, timeout }) {
|
||||
stdio: ['ignore', runConfig.verboseLog ? 'pipe' : 'ignore', 'pipe'],
|
||||
});
|
||||
|
||||
logger.info(`Starting Laconicd process... timeout: ${timeout}ms`);
|
||||
logger.info(`Starting Ethermintd process... timeout: ${timeout}ms`);
|
||||
if (runConfig.verboseLog) {
|
||||
ethermintdProc.stdout.pipe(process.stdout);
|
||||
}
|
||||
@@ -191,14 +191,14 @@ function setupNetwork({ runConfig, timeout }) {
|
||||
process.stdout.write(oLine);
|
||||
}
|
||||
if (oLine.indexOf('Starting JSON-RPC server') !== -1) {
|
||||
logger.info('Laconicd started');
|
||||
logger.info('Ethermintd started');
|
||||
resolve(ethermintdProc);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const timeoutPromise = new Promise((resolve, reject) => {
|
||||
setTimeout(() => reject(new Error('Start laconicd timeout!')), timeout);
|
||||
setTimeout(() => reject(new Error('Start ethermintd timeout!')), timeout);
|
||||
});
|
||||
return Promise.race([spawnPromise, timeoutPromise]);
|
||||
}
|
||||
|
||||
+1000
-139
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user