mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-25 15:55:18 +00:00
support initialization of evm state in e2e tests (#1524)
* check receipt status for failed txs from evm * make EvmSigner's Auth public * setup evm state initialization for e2e * add a dummy Greeter contract, deployed on start * move WaitForEvmTxReceipt to from account to util * add tests for interacting with the contract * add ContractAddrs map to Chain
This commit is contained in:
parent
6a1438fbe9
commit
735d44ba32
1
tests/e2e/contracts/greeter/Greeter.abi
Normal file
1
tests/e2e/contracts/greeter/Greeter.abi
Normal file
@ -0,0 +1 @@
|
||||
[{"inputs":[{"internalType":"string","name":"_greeting","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"greet","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_greeting","type":"string"}],"name":"setGreeting","outputs":[],"stateMutability":"nonpayable","type":"function"}]
|
1
tests/e2e/contracts/greeter/Greeter.bin
Normal file
1
tests/e2e/contracts/greeter/Greeter.bin
Normal file
File diff suppressed because one or more lines are too long
18
tests/e2e/contracts/greeter/Greeter.sol
Normal file
18
tests/e2e/contracts/greeter/Greeter.sol
Normal file
@ -0,0 +1,18 @@
|
||||
//SPDX-License-Identifier: Unlicense
|
||||
pragma solidity ^0.8.3;
|
||||
|
||||
contract Greeter {
|
||||
string greeting;
|
||||
|
||||
constructor(string memory _greeting) {
|
||||
greeting = _greeting;
|
||||
}
|
||||
|
||||
function greet() public view returns (string memory) {
|
||||
return greeting;
|
||||
}
|
||||
|
||||
function setGreeting(string memory _greeting) public {
|
||||
greeting = _greeting;
|
||||
}
|
||||
}
|
255
tests/e2e/contracts/greeter/main.go
Normal file
255
tests/e2e/contracts/greeter/main.go
Normal file
File diff suppressed because one or more lines are too long
24
tests/e2e/contracts/readme.md
Normal file
24
tests/e2e/contracts/readme.md
Normal file
@ -0,0 +1,24 @@
|
||||
This directory contains contract interfaces used by the e2e test suite.
|
||||
|
||||
# Prereq
|
||||
* Abigen: https://geth.ethereum.org/docs/tools/abigen
|
||||
* Solidity: https://docs.soliditylang.org/en/latest/installing-solidity.html
|
||||
|
||||
# Create Contract Interfaces for Go
|
||||
|
||||
If you have the compiled ABI, you can skip directly to step 4.
|
||||
|
||||
To create new go interfaces to contracts:
|
||||
1. add the solidity file: `<filename>.sol`
|
||||
2. decide on a package name. this will be the name of the package you'll import into go (`<pkg-name>`)
|
||||
3. compile the abi & bin for the contract: `solc -o <pkg-name> --abi --bin <filename>.sol`
|
||||
* run from this directory
|
||||
* note that `-o` is the output directory. this will generate `<pkg-name>/<filename>.abi`
|
||||
4. generate the golang interface:
|
||||
`abigen --abi=<pkg-name>/<filename>.abi --bin=<pkg-name>/<filename>.bin --pkg=<pkg-name> --out=<pkg-name>/main.go`
|
||||
5. import and use the contract in Go.
|
||||
|
||||
By including the bin, the generated interface will have a `Deploy*` method. If you only need to interact with an existing contract, you can exclude the `--bin` and only an interaction method interface will be generated.
|
||||
|
||||
# Resources
|
||||
* https://geth.ethereum.org/docs/developers/dapp-developer/native-bindings
|
37
tests/e2e/e2e_evm_contracts_test.go
Normal file
37
tests/e2e/e2e_evm_contracts_test.go
Normal file
@ -0,0 +1,37 @@
|
||||
package e2e_test
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/kava-labs/kava/tests/e2e/contracts/greeter"
|
||||
"github.com/kava-labs/kava/tests/util"
|
||||
)
|
||||
|
||||
func (suite *IntegrationTestSuite) TestEthCallToGreeterContract() {
|
||||
// this test manipulates state of the Greeter contract which means other tests shouldn't use it.
|
||||
|
||||
// setup funded account to interact with contract
|
||||
user := suite.Kava.NewFundedAccount("greeter-contract-user", sdk.NewCoins(ukava(10e6)))
|
||||
|
||||
greeterAddr := suite.Kava.ContractAddrs["greeter"]
|
||||
contract, err := greeter.NewGreeter(greeterAddr, suite.Kava.EvmClient)
|
||||
suite.NoError(err)
|
||||
|
||||
beforeGreeting, err := contract.Greet(nil)
|
||||
suite.NoError(err)
|
||||
|
||||
updatedGreeting := "look at me, using the evm"
|
||||
tx, err := contract.SetGreeting(user.EvmAuth, updatedGreeting)
|
||||
suite.NoError(err)
|
||||
|
||||
_, err = util.WaitForEvmTxReceipt(suite.Kava.EvmClient, tx.Hash(), 10*time.Second)
|
||||
suite.NoError(err)
|
||||
|
||||
afterGreeting, err := contract.Greet(nil)
|
||||
suite.NoError(err)
|
||||
|
||||
suite.Equal("what's up!", beforeGreeting)
|
||||
suite.Equal(updatedGreeting, afterGreeting)
|
||||
}
|
@ -1 +1 @@
|
||||
Subproject commit 8153036b95a930c7830c6969d1653165afa65f2c
|
||||
Subproject commit af9629a2b97475d4d324e4578b58676efa7f07ed
|
@ -1,9 +1,7 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
@ -13,7 +11,7 @@ import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||
"github.com/cosmos/go-bip39"
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
@ -25,8 +23,6 @@ import (
|
||||
"github.com/kava-labs/kava/tests/util"
|
||||
)
|
||||
|
||||
var ErrBroadcastTimeout = errors.New("timed out waiting for tx to be committed to block")
|
||||
|
||||
type SigningAccount struct {
|
||||
name string
|
||||
mnemonic string
|
||||
@ -39,6 +35,8 @@ type SigningAccount struct {
|
||||
sdkReqChan chan<- util.KavaMsgRequest
|
||||
sdkResChan <-chan util.KavaMsgResponse
|
||||
|
||||
EvmAuth *bind.TransactOpts
|
||||
|
||||
EvmAddress common.Address
|
||||
SdkAddress sdk.AccAddress
|
||||
|
||||
@ -109,6 +107,8 @@ func (chain *Chain) AddNewSigningAccount(name string, hdPath *hd.BIP44Params, ch
|
||||
sdkReqChan: sdkReqChan,
|
||||
sdkResChan: sdkResChan,
|
||||
|
||||
EvmAuth: evmSigner.Auth,
|
||||
|
||||
EvmAddress: evmSigner.Address(),
|
||||
SdkAddress: kavaSigner.Address(),
|
||||
}
|
||||
@ -162,21 +162,7 @@ func (a *SigningAccount) SignAndBroadcastEvmTx(req util.EvmTxRequest) EvmTxRespo
|
||||
}
|
||||
|
||||
// if we don't have a tx receipt within a given timeout, fail the request
|
||||
timeout := time.After(10 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case <-timeout:
|
||||
response.Err = ErrBroadcastTimeout
|
||||
default:
|
||||
response.Receipt, response.Err = a.evmSigner.EvmClient.TransactionReceipt(context.Background(), res.TxHash)
|
||||
if errors.Is(response.Err, ethereum.NotFound) {
|
||||
// tx still not committed to a block. retry!
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
response.Receipt, response.Err = util.WaitForEvmTxReceipt(a.evmSigner.EvmClient, res.TxHash, 10*time.Second)
|
||||
|
||||
return response
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
txtypes "github.com/cosmos/cosmos-sdk/types/tx"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@ -31,7 +32,8 @@ type Chain struct {
|
||||
StakingDenom string
|
||||
ChainId string
|
||||
|
||||
EvmClient *ethclient.Client
|
||||
EvmClient *ethclient.Client
|
||||
ContractAddrs map[string]common.Address
|
||||
|
||||
Auth authtypes.QueryClient
|
||||
Bank banktypes.QueryClient
|
||||
@ -46,9 +48,10 @@ type Chain struct {
|
||||
// code as "whale" and it is used to supply funds to all new accounts.
|
||||
func NewChain(t *testing.T, details *runner.ChainDetails, fundedAccountMnemonic string) (*Chain, error) {
|
||||
chain := &Chain{
|
||||
t: t,
|
||||
StakingDenom: details.StakingDenom,
|
||||
ChainId: details.ChainId,
|
||||
t: t,
|
||||
StakingDenom: details.StakingDenom,
|
||||
ChainId: details.ChainId,
|
||||
ContractAddrs: make(map[string]common.Address),
|
||||
}
|
||||
chain.encodingConfig = app.MakeEncodingConfig()
|
||||
|
||||
|
18
tests/e2e/testutil/init_evm.go
Normal file
18
tests/e2e/testutil/init_evm.go
Normal file
@ -0,0 +1,18 @@
|
||||
package testutil
|
||||
|
||||
import "github.com/kava-labs/kava/tests/e2e/contracts/greeter"
|
||||
|
||||
// InitKavaEvmData is run after the chain is running, but before the tests are run.
|
||||
// It is used to initialize some EVM state, such as deploying contracts.
|
||||
func (suite *E2eTestSuite) InitKavaEvmData() {
|
||||
whale := suite.Kava.GetAccount(FundedAccountName)
|
||||
|
||||
// deploy an example contract
|
||||
greeterAddr, _, _, err := greeter.DeployGreeter(
|
||||
whale.evmSigner.Auth,
|
||||
whale.evmSigner.EvmClient,
|
||||
"what's up!",
|
||||
)
|
||||
suite.NoError(err)
|
||||
suite.Kava.ContractAddrs["greeter"] = greeterAddr
|
||||
}
|
@ -73,6 +73,8 @@ func (suite *E2eTestSuite) SetupSuite() {
|
||||
suite.T().Fatalf("failed to create ibc chain querier: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
suite.InitKavaEvmData()
|
||||
}
|
||||
|
||||
func (suite *E2eTestSuite) TearDownSuite() {
|
||||
|
@ -3,9 +3,12 @@ package util
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
@ -13,6 +16,14 @@ import (
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrEvmBroadcastTimeout = errors.New("timed out waiting for tx to be committed to block")
|
||||
// ErrEvmTxFailed is returned when a tx is committed to a block, but the receipt status is 0.
|
||||
// this means the tx failed. we don't have debug_traceTransaction RPC command so the best way
|
||||
// to determine the problem is to attempt to make the tx manually.
|
||||
ErrEvmTxFailed = errors.New("transaction was committed but failed. likely an execution revert by contract code")
|
||||
)
|
||||
|
||||
type EvmTxRequest struct {
|
||||
Tx *ethtypes.Transaction
|
||||
Data interface{}
|
||||
@ -38,8 +49,8 @@ func (e ErrEvmFailedToBroadcast) Error() string {
|
||||
// EvmSigner manages signing and broadcasting requests to transfer Erc20 tokens
|
||||
// Will work for calling all contracts that have func signature `transfer(address,uint256)`
|
||||
type EvmSigner struct {
|
||||
auth *bind.TransactOpts
|
||||
signerAddress common.Address
|
||||
Auth *bind.TransactOpts
|
||||
EvmClient *ethclient.Client
|
||||
}
|
||||
|
||||
@ -60,7 +71,7 @@ func NewEvmSigner(
|
||||
}
|
||||
|
||||
return &EvmSigner{
|
||||
auth: auth,
|
||||
Auth: auth,
|
||||
signerAddress: crypto.PubkeyToAddress(*publicKeyECDSA),
|
||||
EvmClient: evmClient,
|
||||
}, nil
|
||||
@ -77,7 +88,7 @@ func (s *EvmSigner) Run(requests <-chan EvmTxRequest) <-chan EvmTxResponse {
|
||||
// wait for incoming request
|
||||
req := <-requests
|
||||
|
||||
signedTx, err := s.auth.Signer(s.signerAddress, req.Tx)
|
||||
signedTx, err := s.Auth.Signer(s.signerAddress, req.Tx)
|
||||
if err != nil {
|
||||
err = ErrEvmFailedToSign{Err: err}
|
||||
} else {
|
||||
@ -101,3 +112,30 @@ func (s *EvmSigner) Run(requests <-chan EvmTxRequest) <-chan EvmTxResponse {
|
||||
func (s *EvmSigner) Address() common.Address {
|
||||
return s.signerAddress
|
||||
}
|
||||
|
||||
// WaitForEvmTxReceipt polls for a tx receipt and errors on timeout.
|
||||
// If the receipt comes back, but with status 0 (failed), an error is returned.
|
||||
func WaitForEvmTxReceipt(client *ethclient.Client, txHash common.Hash, timeout time.Duration) (*ethtypes.Receipt, error) {
|
||||
var receipt *ethtypes.Receipt
|
||||
var err error
|
||||
outOfTime := time.After(timeout)
|
||||
for {
|
||||
select {
|
||||
case <-outOfTime:
|
||||
err = ErrEvmBroadcastTimeout
|
||||
default:
|
||||
receipt, err = client.TransactionReceipt(context.Background(), txHash)
|
||||
if errors.Is(err, ethereum.NotFound) {
|
||||
// tx still not committed to a block. retry!
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
// a response status of 0 means the tx was successfully committed but failed to execute
|
||||
if receipt.Status == 0 {
|
||||
err = ErrEvmTxFailed
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
return receipt, err
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user