diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index 6a975bcc79..b2e78ed380 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -599,7 +599,7 @@ var AllE2ETests = []runner.E2ETest{ */ runner.NewE2ETest( TestZetaPrecompilesPrototypeName, - "call stateful precompiled contract", + "test stateful precompiled contracts prototype", []runner.ArgDefinition{}, TestPrecompilesRegular, ), diff --git a/precompiles/prototype/IPrototype.sol b/precompiles/prototype/IPrototype.sol index d65779cc5f..34a8892e3c 100644 --- a/precompiles/prototype/IPrototype.sol +++ b/precompiles/prototype/IPrototype.sol @@ -1,4 +1,3 @@ -// SPDX-License-Identifier: LGPL-3.0-only pragma solidity ^0.8.26; /// @dev The IRegular contract's address. diff --git a/precompiles/prototype/prototype.go b/precompiles/prototype/prototype.go index ddfb8531f3..6152601c3f 100644 --- a/precompiles/prototype/prototype.go +++ b/precompiles/prototype/prototype.go @@ -152,7 +152,11 @@ func (c *Contract) Bech32ify(method *abi.Method, args []interface{}) ([]byte, er } cfg := sdk.GetConfig() - prefix, _ := args[0].(string) + prefix, ok := args[0].(string) + if !ok { + return nil, fmt.Errorf("invalid bech32 human readable prefix (HRP): %v", args[0]) + } + if strings.TrimSpace(prefix) == "" { return nil, fmt.Errorf( "invalid bech32 human readable prefix (HRP). Please provide a either an account, validator or consensus address prefix (eg: %s, %s, %s)", @@ -167,7 +171,7 @@ func (c *Contract) Bech32ify(method *abi.Method, args []interface{}) ([]byte, er return nil, fmt.Errorf("invalid hex address") } - // NOTE: safety check, should not happen given that the address is is 20 bytes. + // NOTE: safety check, should not happen given that the address is 20 bytes. if err := sdk.VerifyAddressFormat(address.Bytes()); err != nil { return nil, err } diff --git a/precompiles/prototype/prototype_test.go b/precompiles/prototype/prototype_test.go new file mode 100644 index 0000000000..1519693e91 --- /dev/null +++ b/precompiles/prototype/prototype_test.go @@ -0,0 +1,137 @@ +package prototype + +import ( + "testing" + + storetypes "github.com/cosmos/cosmos-sdk/store/types" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + ethermint "github.com/zeta-chain/ethermint/types" + "github.com/zeta-chain/zetacore/testutil/keeper" +) + +func Test_IPrototypeContract(t *testing.T) { + /* + Configuration + */ + var encoding ethermint.EncodingConfig + appCodec := encoding.Codec + k, ctx, _, _ := keeper.FungibleKeeper(t) + gasConfig := storetypes.TransientGasConfig() + + /* + Contract and ABI tests + */ + + // Create a new IPrototypeContract instance and get Address and Abi. + contract := NewIPrototypeContract(ctx, k, appCodec, gasConfig) + require.NotNil(t, contract, "NewIPrototypeContract() should not return a nil contract") + + address := contract.Address() + require.Equal(t, ContractAddress, address, "contract address should match the precompiled address") + + abi := contract.Abi() + require.NotNil(t, abi, "contract ABI should not be nil") + + // Check all methods are present in the ABI. + bech32ToHex := abi.Methods[Bech32ToHexAddrMethodName] + require.NotNil(t, bech32ToHex, "bech32ToHexAddr method should be present in the ABI") + + bech32ify := abi.Methods[Bech32ifyMethodName] + require.NotNil(t, bech32ify, "bech32ify method should be present in the ABI") + + getGasStabilityPoolBalance := abi.Methods[GetGasStabilityPoolBalanceName] + require.NotNil(t, getGasStabilityPoolBalance, "getGasStabilityPoolBalance method should be present in the ABI") + + /* + Gas tests + */ + + // Check all methods use the correct gas amount. + var method [4]byte + + gasBech32ToHex := contract.RequiredGas(bech32ToHex.ID) + copy(method[:], bech32ToHex.ID[:4]) + baseCost := uint64(len(method)) * gasConfig.WriteCostPerByte + require.Equal( + t, + GasRequiredByMethod[method] + baseCost, + gasBech32ToHex, + "bech32ToHexAddr method should require %d gas, got %d", + GasRequiredByMethod[method] + baseCost, + gasBech32ToHex) + + gasBech32ify := contract.RequiredGas(bech32ify.ID) + copy(method[:], bech32ify.ID[:4]) + baseCost = uint64(len(method)) * gasConfig.WriteCostPerByte + require.Equal( + t, + GasRequiredByMethod[method] + baseCost, + gasBech32ify, + "bech32ify method should require %d gas, got %d", + GasRequiredByMethod[method] + baseCost, + gasBech32ify) + + gasGetGasStabilityPoolBalance := contract.RequiredGas(getGasStabilityPoolBalance.ID) + copy(method[:], getGasStabilityPoolBalance.ID[:4]) + baseCost = uint64(len(method)) * gasConfig.WriteCostPerByte + require.Equal( + t, + GasRequiredByMethod[method] + baseCost, + gasGetGasStabilityPoolBalance, + "getGasStabilityPoolBalance method should require %d gas, got %d", + GasRequiredByMethod[method] + baseCost, + gasGetGasStabilityPoolBalance) + + /* + Methods tests + */ + + // Test Bech32HexAddr method. + methodID := abi.Methods[Bech32ToHexAddrMethodName] + args := make([]interface{}, 0) + args = append(args, "zeta1h8duy2dltz9xz0qqhm5wvcnj02upy887fyn43u") + + rawBytes, err := contract.Bech32ToHexAddr(&methodID, args) + require.NoError(t, err, "Bech32ToHexAddr should not return an error") + + // Discard the first 12 bytes, the address is the last 20 bytes. + addr := common.BytesToAddress(rawBytes[12:]) + require.Equal( + t, + common.HexToAddress("0xB9Dbc229Bf588A613C00BEE8e662727AB8121cfE"), + addr, + "Bech32ToHexAddr should return the correct address, got: %v", + addr, + ) + + // Test Bech32ify method. + methodID = abi.Methods[Bech32ifyMethodName] + args = make([]interface{}, 0) + args = append(args, "zeta") + args = append(args, common.HexToAddress("0xB9Dbc229Bf588A613C00BEE8e662727AB8121cfE")) + + rawBytes, err = contract.Bech32ify(&methodID, args) + require.NoError(t, err, "Bech32ify should not return an error") + + // Manually extract the address from the raw bytes. + zetaAddr := string(rawBytes[64:107]) + require.Equal( + t, + "zeta1h8duy2dltz9xz0qqhm5wvcnj02upy887fyn43u", + string(zetaAddr), + "Bech32ify should return the correct address, got: %v", + zetaAddr, + ) + + // Test GetGasStabilityPoolBalance method. + // Only check the function is called correctly inside the contract, and it returns the expected error. + // Configuring a local environment for this contract would require deploying system contracts and gas pools. + // This method is tested thoroughly in the e2e tests. + methodID = abi.Methods[GetGasStabilityPoolBalanceName] + args = make([]interface{}, 0) + args = append(args, int64(1337)) + + rawBytes, err = contract.GetGasStabilityPoolBalance(ctx, &methodID, args) + require.Error(t, err, "error calling fungible keeper: failed to get system contract variable: state variable not found") +} diff --git a/precompiles/types/types.go b/precompiles/types/types.go index 92b5dc0c82..7bd4a57bfa 100644 --- a/precompiles/types/types.go +++ b/precompiles/types/types.go @@ -9,7 +9,7 @@ import ( "github.com/zeta-chain/ethermint/x/evm/statedb" ) -// Interface compliance +// Interface compliance. var _ ExtStateDB = (*statedb.StateDB)(nil) var _ Registrable = (*baseContract)(nil) var _ BaseContract = (*baseContract)(nil)