mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-27 00:35:18 +00:00
ab3cf7c994
* feat!(precompile): Add registry and genesis tests Based on evgeniy-scherbina's work, this adds a new precompile module which defines a contract moudule with an example noop contract that will be will be used for implementing test functions. In addition, it defines a registry module that instantiates stateful precompile contracts and associates them with an address in a global registry defined in kava-labs/go-ethereum. See precompile/README.md for more information. The kava-labs/go-ethereum and kava-labs/etheremint replace statements are updated to support these changes as well as an update to kvtool which includes genesis state for the registry.NoopContractAddress and initializes the contract's EthAccount with a non-zero sequence and codehash set to keccak256(0x01), and sets the contract code to 0x01. See tests/e2e/e2e_precompile_genesis_test.go for an overview of the expected genesis state for an enabled precompile. Co-authored-by: evgeniy-scherbina <evgeniy.shcherbina.es@gmail.com> * chore: Precompile readme improvements This fixes a typo (import -> important) and uses package terminology instead of unclear module terminology. This aligns best with golang terminology were modules and packages are distinctly different and modules are defined using go.mod. * chore: Improve noop contract godoc Add a more meaningful godoc where the noop contract is constructed. * chore(e2e): Improve comments around query checks Improve the clarity of comments around where the error is checked for accounts and why it is not checked directly. In addition, improve comment on why both grpc and rpc code is fetched and where they are used. --------- Co-authored-by: evgeniy-scherbina <evgeniy.shcherbina.es@gmail.com>
174 lines
6.1 KiB
Go
174 lines
6.1 KiB
Go
package e2e_test
|
|
|
|
import (
|
|
"context"
|
|
"math/big"
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
ethermint "github.com/evmos/ethermint/types"
|
|
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
|
|
|
"github.com/kava-labs/kava/precompile/registry"
|
|
)
|
|
|
|
// TestPrecompileGenesis tests that the the following is true for enabled precompiles:
|
|
//
|
|
// - An enabled precompile has an EthAccount with matching code hash,
|
|
// sequence of 1, and no public key
|
|
// - An enabled precompile has code equal to 0x01
|
|
// - An enabled precompile has a nonce of 1
|
|
//
|
|
// This is important to ensure the genesis setup for precompiles is correct.
|
|
func (suite *IntegrationTestSuite) TestPrecompileGenesis() {
|
|
type fixture struct {
|
|
address string
|
|
expectIsEnabled bool
|
|
expectIsEthAccount bool
|
|
expectCode []byte
|
|
expectNonce uint64
|
|
}
|
|
|
|
// enabled represnets the expected state for a registered precompile
|
|
// that is enabled at genesis
|
|
enabled := func(address string) func() fixture {
|
|
return func() fixture {
|
|
return fixture{
|
|
address: address,
|
|
expectIsEnabled: true,
|
|
expectIsEthAccount: true,
|
|
expectCode: []byte{0x01},
|
|
expectNonce: uint64(1),
|
|
}
|
|
}
|
|
}
|
|
|
|
// disabled represents the expected state for a registered precompile
|
|
// that is not enabled at genesis
|
|
disabled := func(address string) func() fixture {
|
|
return func() fixture {
|
|
return fixture{
|
|
address: address,
|
|
expectIsEnabled: false,
|
|
expectIsEthAccount: false,
|
|
expectCode: []byte{},
|
|
expectNonce: uint64(0),
|
|
}
|
|
}
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
genFixture func() fixture
|
|
}{
|
|
{
|
|
name: "noop contract address is enabled and initialized",
|
|
genFixture: enabled(registry.NoopContractAddress),
|
|
},
|
|
{
|
|
name: "noop contract address second address is disabled and not initialized",
|
|
genFixture: disabled(registry.NoopContractAddress2),
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
suite.Run(tc.name, func() {
|
|
tf := tc.genFixture()
|
|
|
|
//
|
|
// Addresses
|
|
//
|
|
evmAddress := common.HexToAddress(tf.address)
|
|
sdkAddress := sdk.AccAddress(evmAddress.Bytes())
|
|
|
|
//
|
|
// Heights
|
|
//
|
|
// We ensure all queries happen at block 1 after genesis
|
|
// and help ensure determisitc behavior
|
|
genesisHeight := big.NewInt(1)
|
|
grpcGenesisContext := suite.Kava.Grpc.CtxAtHeight(genesisHeight.Int64())
|
|
|
|
//
|
|
// Queries
|
|
//
|
|
evmParamsResp, err := suite.Kava.Grpc.Query.Evm.Params(grpcGenesisContext, &evmtypes.QueryParamsRequest{})
|
|
suite.Require().NoError(err)
|
|
|
|
// accountErr is checked during in the assertions below if tf.expectIsEthAccount is true
|
|
// This is due to the service returning a not found error for address that are not enabled,
|
|
// which can be ignored when we do not expect an eth account to exist.
|
|
accountResponse, accountErr := suite.Kava.Grpc.Query.Auth.Account(
|
|
grpcGenesisContext, &authtypes.QueryAccountRequest{Address: sdkAddress.String()})
|
|
var account authtypes.AccountI
|
|
if accountErr == nil {
|
|
err = suite.Kava.EncodingConfig.Marshaler.UnpackAny(accountResponse.Account, &account)
|
|
suite.Require().NoError(err)
|
|
}
|
|
|
|
// We ensure both the evm json rpc and x/evm grpc code endpoints return the same value
|
|
// in the assertions below
|
|
grpcCodeResponse, err := suite.Kava.Grpc.Query.Evm.Code(grpcGenesisContext,
|
|
&evmtypes.QueryCodeRequest{Address: evmAddress.String()})
|
|
suite.Require().NoError(err)
|
|
rpcCode, err := suite.Kava.EvmClient.CodeAt(context.Background(), evmAddress, genesisHeight)
|
|
suite.Require().NoError(err)
|
|
|
|
nonce, err := suite.Kava.EvmClient.NonceAt(context.Background(), evmAddress, genesisHeight)
|
|
suite.Require().NoError(err)
|
|
|
|
//
|
|
// Assertions
|
|
//
|
|
if tf.expectIsEnabled {
|
|
suite.Containsf(evmParamsResp.Params.EnabledPrecompiles, tf.address,
|
|
"expected %s to be enabled in evm params", tf.address)
|
|
} else {
|
|
suite.NotContainsf(evmParamsResp.Params.EnabledPrecompiles, tf.address,
|
|
"expected %s to not be enabled in evm params", tf.address)
|
|
}
|
|
|
|
// Fail if fixuture configuration is invalid
|
|
if tf.expectIsEthAccount && len(tf.expectCode) == 0 {
|
|
suite.Failf("an eth account must have expected code for address %s", tf.address)
|
|
}
|
|
|
|
// Run addition EthAccount assertions
|
|
if tf.expectIsEthAccount {
|
|
suite.Require().NoErrorf(accountErr, "expected account query to not error for address %s", tf.address)
|
|
|
|
// All contracts including precompiles must be EthAccount's
|
|
ethAccount, isEthAccount := account.(*ethermint.EthAccount)
|
|
suite.Require().Truef(isEthAccount, "expected account at address %s to be an eth account", tf.address)
|
|
|
|
// Code hash must always match the EthAccount
|
|
codeHash := ethAccount.GetCodeHash()
|
|
suite.Equalf(crypto.Keccak256Hash(tf.expectCode), codeHash,
|
|
"expected codehash for account %s to match expected code", tf.address)
|
|
|
|
// A precompile (and contract) should never have a public key set
|
|
suite.Nilf(ethAccount.PubKey, "expected account %s to have no public key", tf.address)
|
|
|
|
// Assert the account sequence matches the expected nonce
|
|
// This a duplicate of the nonce assertion below, but also helps ensure these
|
|
// two sources agree
|
|
suite.Equal(tf.expectNonce, ethAccount.GetSequence())
|
|
}
|
|
|
|
// We assert both methods of code retrieval report the same value
|
|
suite.Equalf(tf.expectCode, rpcCode, "expected code for address %s to match expected", tf.address)
|
|
// The GRPC endpoint returns []byte(nil) when the code is not in state, which is different from
|
|
// the rpc endpoint that returns []byte{}.
|
|
grpcExpectedCode := tf.expectCode
|
|
if len(grpcExpectedCode) == 0 {
|
|
grpcExpectedCode = []byte(nil)
|
|
}
|
|
suite.Equalf(grpcExpectedCode, grpcCodeResponse.Code, "expected code for address %s to match expected", tf.address)
|
|
// We assert this outside of the account context since the evm rpc always returns a nonce
|
|
suite.Equalf(tf.expectNonce, nonce, "expected nonce for address %s to match expected", tf.address)
|
|
})
|
|
}
|
|
}
|