diff --git a/precompiles/common/expected_keepers.go b/precompiles/common/expected_keepers.go index f1ec4cfa54..ecebf3aba8 100644 --- a/precompiles/common/expected_keepers.go +++ b/precompiles/common/expected_keepers.go @@ -31,6 +31,12 @@ type EVMKeeper interface { IsCodeHashWhitelistedForBankSend(ctx sdk.Context, h common.Hash) bool GetPriorityNormalizer(ctx sdk.Context) sdk.Dec GetBaseDenom(ctx sdk.Context) string + SetERC20NativePointer(ctx sdk.Context, token string, addr common.Address) error + GetERC20NativePointer(ctx sdk.Context, token string) (addr common.Address, version uint16, exists bool) + SetERC20CW20Pointer(ctx sdk.Context, cw20Address string, addr common.Address) error + GetERC20CW20Pointer(ctx sdk.Context, cw20Address string) (addr common.Address, version uint16, exists bool) + SetERC721CW721Pointer(ctx sdk.Context, cw721Address string, addr common.Address) error + GetERC721CW721Pointer(ctx sdk.Context, cw721Address string) (addr common.Address, version uint16, exists bool) } type OracleKeeper interface { diff --git a/precompiles/ibc/ibc.go b/precompiles/ibc/ibc.go index cbef497485..b83b0792b3 100644 --- a/precompiles/ibc/ibc.go +++ b/precompiles/ibc/ibc.go @@ -9,7 +9,7 @@ import ( "github.com/cosmos/cosmos-sdk/types/bech32" - "github.com/sei-protocol/sei-chain/precompiles/wasmd" + "github.com/sei-protocol/sei-chain/utils" sdk "github.com/cosmos/cosmos-sdk/types" clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" @@ -100,8 +100,8 @@ func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, calli gasMultiplier := p.evmKeeper.GetPriorityNormalizer(ctx) gasLimitBigInt := new(big.Int).Mul(new(big.Int).SetUint64(suppliedGas), gasMultiplier.RoundInt().BigInt()) - if gasLimitBigInt.Cmp(wasmd.MaxUint64BigInt) > 0 { - gasLimitBigInt = wasmd.MaxUint64BigInt + if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { + gasLimitBigInt = utils.BigMaxU64 } ctx = ctx.WithGasMeter(sdk.NewGasMeter(gasLimitBigInt.Uint64())) diff --git a/precompiles/pointer/Pointer.sol b/precompiles/pointer/Pointer.sol new file mode 100644 index 0000000000..d85893949c --- /dev/null +++ b/precompiles/pointer/Pointer.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +address constant POINTER_PRECOMPILE_ADDRESS = 0x000000000000000000000000000000000000100b; + +IPointer constant POINTER_CONTRACT = IPointer(POINTER_PRECOMPILE_ADDRESS); + +interface IPointer { + function addNativePointer( + string memory token + ) payable external returns (address ret); + + function addCW20Pointer( + string memory cwAddr + ) payable external returns (address ret); + + function addCW721Pointer( + string memory cwAddr + ) payable external returns (address ret); +} diff --git a/precompiles/pointer/abi.json b/precompiles/pointer/abi.json new file mode 100644 index 0000000000..8fb8238da0 --- /dev/null +++ b/precompiles/pointer/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"cwAddr","type":"string"}],"name":"addCW20Pointer","outputs":[{"internalType":"address","name":"ret","type":"address"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"cwAddr","type":"string"}],"name":"addCW721Pointer","outputs":[{"internalType":"address","name":"ret","type":"address"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"token","type":"string"}],"name":"addNativePointer","outputs":[{"internalType":"address","name":"ret","type":"address"}],"stateMutability":"payable","type":"function"}] \ No newline at end of file diff --git a/precompiles/pointer/pointer.go b/precompiles/pointer/pointer.go new file mode 100644 index 0000000000..9bacf5d12c --- /dev/null +++ b/precompiles/pointer/pointer.go @@ -0,0 +1,243 @@ +package pointer + +import ( + "bytes" + "embed" + "encoding/json" + "fmt" + "math" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + ethabi "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common" + "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/cw20" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/cw721" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/native" +) + +const ( + AddNativePointer = "addNativePointer" + AddCW20Pointer = "addCW20Pointer" + AddCW721Pointer = "addCW721Pointer" +) + +const PointerAddress = "0x000000000000000000000000000000000000100b" + +var _ vm.PrecompiledContract = &Precompile{} + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type Precompile struct { + pcommon.Precompile + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + wasmdKeeper pcommon.WasmdViewKeeper + address common.Address + + AddNativePointerID []byte + AddCW20PointerID []byte + AddCW721PointerID []byte +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper, wasmdKeeper pcommon.WasmdViewKeeper) (*Precompile, error) { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + return nil, fmt.Errorf("error loading the pointer ABI %s", err) + } + + newAbi, err := ethabi.JSON(bytes.NewReader(abiBz)) + if err != nil { + return nil, err + } + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + evmKeeper: evmKeeper, + bankKeeper: bankKeeper, + wasmdKeeper: wasmdKeeper, + address: common.HexToAddress(PointerAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case AddNativePointer: + p.AddNativePointerID = m.ID + case AddCW20Pointer: + p.AddCW20PointerID = m.ID + case AddCW721Pointer: + p.AddCW721PointerID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + // gas is calculated dynamically + return 0 +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, _ common.Address, input []byte, suppliedGas uint64, value *big.Int) (ret []byte, remainingGas uint64, err error) { + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, 0, err + } + + switch method.Name { + case AddNativePointer: + return p.AddNative(ctx, method, caller, args, value, evm, suppliedGas) + case AddCW20Pointer: + return p.AddCW20(ctx, method, caller, args, value, evm, suppliedGas) + case AddCW721Pointer: + return p.AddCW721(ctx, method, caller, args, value, evm, suppliedGas) + default: + err = fmt.Errorf("unknown method %s", method.Name) + } + return +} + +func (p Precompile) Run(*vm.EVM, common.Address, []byte, *big.Int) ([]byte, error) { + panic("static gas Run is not implemented for dynamic gas precompile") +} + +func (p Precompile) AddNative(ctx sdk.Context, method *ethabi.Method, caller common.Address, args []interface{}, value *big.Int, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + pcommon.AssertArgsLength(args, 1) + token := args[0].(string) + existingAddr, existingVersion, exists := p.evmKeeper.GetERC20NativePointer(ctx, token) + if exists && existingVersion >= native.CurrentVersion { + return nil, 0, fmt.Errorf("pointer at %s with version %d exists when trying to set pointer for version %d", existingAddr.Hex(), existingVersion, native.CurrentVersion) + } + metadata, metadataExists := p.bankKeeper.GetDenomMetaData(ctx, token) + if !metadataExists { + return nil, 0, fmt.Errorf("denom %s does not have metadata stored and thus can only have its pointer set through gov proposal", token) + } + name := metadata.Name + symbol := metadata.Symbol + var decimals uint8 + for _, denomUnit := range metadata.DenomUnits { + if denomUnit.Exponent > uint32(decimals) && denomUnit.Exponent <= math.MaxUint8 { + decimals = uint8(denomUnit.Exponent) + name = denomUnit.Denom + symbol = denomUnit.Denom + if len(denomUnit.Aliases) > 0 { + name = denomUnit.Aliases[0] + } + } + } + constructorArguments := []interface{}{ + token, name, symbol, decimals, + } + + packedArgs, err := native.GetParsedABI().Pack("", constructorArguments...) + if err != nil { + panic(err) + } + bin := append(native.GetBin(), packedArgs...) + if value == nil { + value = utils.Big0 + } + ret, contractAddr, remainingGas, err := evm.Create(vm.AccountRef(caller), bin, suppliedGas, value) + if err != nil { + return + } + err = p.evmKeeper.SetERC20NativePointer(ctx, token, contractAddr) + if err != nil { + return + } + ret, err = method.Outputs.Pack(contractAddr) + return +} + +func (p Precompile) AddCW20(ctx sdk.Context, method *ethabi.Method, caller common.Address, args []interface{}, value *big.Int, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + pcommon.AssertArgsLength(args, 1) + cwAddr := args[0].(string) + existingAddr, existingVersion, exists := p.evmKeeper.GetERC20CW20Pointer(ctx, cwAddr) + if exists && existingVersion >= cw20.CurrentVersion { + return nil, 0, fmt.Errorf("pointer at %s with version %d exists when trying to set pointer for version %d", existingAddr.Hex(), existingVersion, cw20.CurrentVersion) + } + res, err := p.wasmdKeeper.QuerySmart(ctx, sdk.MustAccAddressFromBech32(cwAddr), []byte("{\"token_info\":{}}")) + if err != nil { + return nil, 0, err + } + formattedRes := map[string]interface{}{} + if err := json.Unmarshal(res, &formattedRes); err != nil { + return nil, 0, err + } + name := formattedRes["name"].(string) + symbol := formattedRes["symbol"].(string) + constructorArguments := []interface{}{ + cwAddr, name, symbol, + } + + packedArgs, err := cw20.GetParsedABI().Pack("", constructorArguments...) + if err != nil { + panic(err) + } + bin := append(cw20.GetBin(), packedArgs...) + if value == nil { + value = utils.Big0 + } + ret, contractAddr, remainingGas, err := evm.Create(vm.AccountRef(caller), bin, suppliedGas, value) + if err != nil { + return + } + err = p.evmKeeper.SetERC20CW20Pointer(ctx, cwAddr, contractAddr) + if err != nil { + return + } + ret, err = method.Outputs.Pack(contractAddr) + return +} + +func (p Precompile) AddCW721(ctx sdk.Context, method *ethabi.Method, caller common.Address, args []interface{}, value *big.Int, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + pcommon.AssertArgsLength(args, 1) + cwAddr := args[0].(string) + existingAddr, existingVersion, exists := p.evmKeeper.GetERC721CW721Pointer(ctx, cwAddr) + if exists && existingVersion >= cw721.CurrentVersion { + return nil, 0, fmt.Errorf("pointer at %s with version %d exists when trying to set pointer for version %d", existingAddr.Hex(), existingVersion, cw721.CurrentVersion) + } + res, err := p.wasmdKeeper.QuerySmart(ctx, sdk.MustAccAddressFromBech32(cwAddr), []byte("{\"contract_info\":{}}")) + if err != nil { + return nil, 0, err + } + formattedRes := map[string]interface{}{} + if err := json.Unmarshal(res, &formattedRes); err != nil { + return nil, 0, err + } + name := formattedRes["name"].(string) + symbol := formattedRes["symbol"].(string) + constructorArguments := []interface{}{ + cwAddr, name, symbol, + } + + packedArgs, err := cw721.GetParsedABI().Pack("", constructorArguments...) + if err != nil { + panic(err) + } + bin := append(cw721.GetBin(), packedArgs...) + if value == nil { + value = utils.Big0 + } + ret, contractAddr, remainingGas, err := evm.Create(vm.AccountRef(caller), bin, suppliedGas, value) + if err != nil { + return + } + err = p.evmKeeper.SetERC721CW721Pointer(ctx, cwAddr, contractAddr) + if err != nil { + return + } + ret, err = method.Outputs.Pack(contractAddr) + return +} diff --git a/precompiles/pointer/pointer_test.go b/precompiles/pointer/pointer_test.go new file mode 100644 index 0000000000..11060b7963 --- /dev/null +++ b/precompiles/pointer/pointer_test.go @@ -0,0 +1,73 @@ +package pointer_test + +import ( + "testing" + "time" + + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/sei-protocol/sei-chain/app" + "github.com/sei-protocol/sei-chain/precompiles/pointer" + testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/native" + "github.com/sei-protocol/sei-chain/x/evm/state" + "github.com/sei-protocol/sei-chain/x/evm/types" + "github.com/stretchr/testify/require" +) + +func TestAddNative(t *testing.T) { + testApp := app.Setup(false, false) + p, err := pointer.NewPrecompile(&testApp.EvmKeeper, testApp.BankKeeper, testApp.WasmKeeper) + require.Nil(t, err) + ctx := testApp.GetContextForDeliverTx([]byte{}).WithBlockTime(time.Now()) + _, caller := testkeeper.MockAddressPair() + suppliedGas := uint64(10000000) + cfg := types.DefaultChainConfig().EthereumConfig(testApp.EvmKeeper.ChainID(ctx)) + + // token has no metadata + m, err := p.ABI.MethodById(p.AddNativePointerID) + require.Nil(t, err) + args, err := m.Inputs.Pack("test") + require.Nil(t, err) + statedb := state.NewDBImpl(ctx, &testApp.EvmKeeper, true) + blockCtx, _ := testApp.EvmKeeper.GetVMBlockContext(ctx, core.GasPool(suppliedGas)) + evm := vm.NewEVM(*blockCtx, vm.TxContext{}, statedb, cfg, vm.Config{}) + _, g, err := p.RunAndCalculateGas(evm, caller, caller, append(p.AddNativePointerID, args...), suppliedGas, nil) + require.NotNil(t, err) + require.Equal(t, uint64(0), g) + _, _, exists := testApp.EvmKeeper.GetERC20NativePointer(statedb.Ctx(), "test") + require.False(t, exists) + + // token has metadata + testApp.BankKeeper.SetDenomMetaData(ctx, banktypes.Metadata{ + Base: "test", + Name: "base_name", + Symbol: "base_symbol", + DenomUnits: []*banktypes.DenomUnit{{ + Exponent: 6, + Denom: "denom", + Aliases: []string{"DENOM"}, + }}, + }) + statedb = state.NewDBImpl(ctx, &testApp.EvmKeeper, true) + evm = vm.NewEVM(*blockCtx, vm.TxContext{}, statedb, cfg, vm.Config{}) + ret, g, err := p.RunAndCalculateGas(evm, caller, caller, append(p.AddNativePointerID, args...), suppliedGas, nil) + require.Nil(t, err) + require.Equal(t, uint64(8907806), g) + outputs, err := m.Outputs.Unpack(ret) + require.Nil(t, err) + addr := outputs[0].(common.Address) + pointerAddr, version, exists := testApp.EvmKeeper.GetERC20NativePointer(statedb.Ctx(), "test") + require.Equal(t, addr, pointerAddr) + require.Equal(t, native.CurrentVersion, version) + require.True(t, exists) + + // pointer already exists + statedb = state.NewDBImpl(statedb.Ctx(), &testApp.EvmKeeper, true) + evm = vm.NewEVM(*blockCtx, vm.TxContext{}, statedb, cfg, vm.Config{}) + _, g, err = p.RunAndCalculateGas(evm, caller, caller, append(p.AddNativePointerID, args...), suppliedGas, nil) + require.NotNil(t, err) + require.Equal(t, uint64(0), g) +} diff --git a/precompiles/pointerview/Pointerview.sol b/precompiles/pointerview/Pointerview.sol new file mode 100644 index 0000000000..497c977fdc --- /dev/null +++ b/precompiles/pointerview/Pointerview.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +address constant POINTERVIEW_PRECOMPILE_ADDRESS = 0x000000000000000000000000000000000000100A; + +IPointerview constant POINTERVIEW_CONTRACT = IPointerview(POINTERVIEW_PRECOMPILE_ADDRESS); + +interface IPointerview { + function getNativePointer( + string memory token + ) view external returns (address addr, uint16 version, bool exists); + + function getCW20Pointer( + string memory cwAddr + ) view external returns (address addr, uint16 version, bool exists); + + function getCW721Pointer( + string memory cwAddr + ) view external returns (address addr, uint16 version, bool exists); +} diff --git a/precompiles/pointerview/abi.json b/precompiles/pointerview/abi.json new file mode 100644 index 0000000000..a469eff127 --- /dev/null +++ b/precompiles/pointerview/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"cwAddr","type":"string"}],"name":"getCW20Pointer","outputs":[{"internalType":"address","name":"addr","type":"address"},{"internalType":"uint16","name":"version","type":"uint16"},{"internalType":"bool","name":"exists","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"cwAddr","type":"string"}],"name":"getCW721Pointer","outputs":[{"internalType":"address","name":"addr","type":"address"},{"internalType":"uint16","name":"version","type":"uint16"},{"internalType":"bool","name":"exists","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"token","type":"string"}],"name":"getNativePointer","outputs":[{"internalType":"address","name":"addr","type":"address"},{"internalType":"uint16","name":"version","type":"uint16"},{"internalType":"bool","name":"exists","type":"bool"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/pointerview/pointerview.go b/precompiles/pointerview/pointerview.go new file mode 100644 index 0000000000..937d9e9688 --- /dev/null +++ b/precompiles/pointerview/pointerview.go @@ -0,0 +1,119 @@ +package pointerview + +import ( + "bytes" + "embed" + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common" +) + +const ( + GetNativePointer = "getNativePointer" + GetCW20Pointer = "getCW20Pointer" + GetCW721Pointer = "getCW721Pointer" +) + +const PointerViewAddress = "0x000000000000000000000000000000000000100A" + +var _ vm.PrecompiledContract = &Precompile{} + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type Precompile struct { + pcommon.Precompile + evmKeeper pcommon.EVMKeeper + address common.Address + + GetNativePointerID []byte + GetCW20PointerID []byte + GetCW721PointerID []byte +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper) (*Precompile, error) { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + return nil, fmt.Errorf("error loading the pointer ABI %s", err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + return nil, err + } + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + evmKeeper: evmKeeper, + address: common.HexToAddress(PointerViewAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case GetNativePointer: + p.GetNativePointerID = m.ID + case GetCW20Pointer: + p.GetCW20PointerID = m.ID + case GetCW721Pointer: + p.GetCW721PointerID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + return 2000 +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) Run(evm *vm.EVM, _ common.Address, input []byte, _ *big.Int) (ret []byte, err error) { + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, err + } + + switch method.Name { + case GetNativePointer: + return p.GetNative(ctx, method, args) + case GetCW20Pointer: + return p.GetCW20(ctx, method, args) + case GetCW721Pointer: + return p.GetCW721(ctx, method, args) + default: + err = fmt.Errorf("unknown method %s", method.Name) + } + return +} + +func (p Precompile) GetNative(ctx sdk.Context, method *abi.Method, args []interface{}) (ret []byte, err error) { + pcommon.AssertArgsLength(args, 1) + token := args[0].(string) + existingAddr, existingVersion, exists := p.evmKeeper.GetERC20NativePointer(ctx, token) + return method.Outputs.Pack(existingAddr, existingVersion, exists) +} + +func (p Precompile) GetCW20(ctx sdk.Context, method *abi.Method, args []interface{}) (ret []byte, err error) { + pcommon.AssertArgsLength(args, 1) + addr := args[0].(string) + existingAddr, existingVersion, exists := p.evmKeeper.GetERC20CW20Pointer(ctx, addr) + return method.Outputs.Pack(existingAddr, existingVersion, exists) +} + +func (p Precompile) GetCW721(ctx sdk.Context, method *abi.Method, args []interface{}) (ret []byte, err error) { + pcommon.AssertArgsLength(args, 1) + addr := args[0].(string) + existingAddr, existingVersion, exists := p.evmKeeper.GetERC721CW721Pointer(ctx, addr) + return method.Outputs.Pack(existingAddr, existingVersion, exists) +} diff --git a/precompiles/pointerview/pointerview_test.go b/precompiles/pointerview/pointerview_test.go new file mode 100644 index 0000000000..ad6d28bff3 --- /dev/null +++ b/precompiles/pointerview/pointerview_test.go @@ -0,0 +1,67 @@ +package pointerview_test + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/sei-protocol/sei-chain/precompiles/pointerview" + testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/cw20" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/cw721" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/native" + "github.com/stretchr/testify/require" +) + +func TestPointerView(t *testing.T) { + k, ctx := testkeeper.MockEVMKeeper() + p, err := pointerview.NewPrecompile(k) + require.Nil(t, err) + _, pointer := testkeeper.MockAddressPair() + k.SetERC20NativePointer(ctx, "test", pointer) + k.SetERC20CW20Pointer(ctx, "test", pointer) + k.SetERC721CW721Pointer(ctx, "test", pointer) + m, err := p.ABI.MethodById(p.GetNativePointerID) + require.Nil(t, err) + ret, err := p.GetNative(ctx, m, []interface{}{"test"}) + require.Nil(t, err) + outputs, err := m.Outputs.Unpack(ret) + require.Nil(t, err) + require.Equal(t, pointer, outputs[0].(common.Address)) + require.Equal(t, native.CurrentVersion, outputs[1].(uint16)) + require.True(t, outputs[2].(bool)) + ret, err = p.GetNative(ctx, m, []interface{}{"test2"}) + require.Nil(t, err) + outputs, err = m.Outputs.Unpack(ret) + require.Nil(t, err) + require.False(t, outputs[2].(bool)) + + m, err = p.ABI.MethodById(p.GetCW20PointerID) + require.Nil(t, err) + ret, err = p.GetCW20(ctx, m, []interface{}{"test"}) + require.Nil(t, err) + outputs, err = m.Outputs.Unpack(ret) + require.Nil(t, err) + require.Equal(t, pointer, outputs[0].(common.Address)) + require.Equal(t, cw20.CurrentVersion, outputs[1].(uint16)) + require.True(t, outputs[2].(bool)) + ret, err = p.GetCW20(ctx, m, []interface{}{"test2"}) + require.Nil(t, err) + outputs, err = m.Outputs.Unpack(ret) + require.Nil(t, err) + require.False(t, outputs[2].(bool)) + + m, err = p.ABI.MethodById(p.GetCW721PointerID) + require.Nil(t, err) + ret, err = p.GetCW721(ctx, m, []interface{}{"test"}) + require.Nil(t, err) + outputs, err = m.Outputs.Unpack(ret) + require.Nil(t, err) + require.Equal(t, pointer, outputs[0].(common.Address)) + require.Equal(t, cw721.CurrentVersion, outputs[1].(uint16)) + require.True(t, outputs[2].(bool)) + ret, err = p.GetCW721(ctx, m, []interface{}{"test2"}) + require.Nil(t, err) + outputs, err = m.Outputs.Unpack(ret) + require.Nil(t, err) + require.False(t, outputs[2].(bool)) +} diff --git a/precompiles/setup.go b/precompiles/setup.go index c72869fac7..a0908d4963 100644 --- a/precompiles/setup.go +++ b/precompiles/setup.go @@ -13,6 +13,8 @@ import ( "github.com/sei-protocol/sei-chain/precompiles/ibc" "github.com/sei-protocol/sei-chain/precompiles/json" "github.com/sei-protocol/sei-chain/precompiles/oracle" + "github.com/sei-protocol/sei-chain/precompiles/pointer" + "github.com/sei-protocol/sei-chain/precompiles/pointerview" "github.com/sei-protocol/sei-chain/precompiles/staking" "github.com/sei-protocol/sei-chain/precompiles/wasmd" ) @@ -81,6 +83,16 @@ func InitializePrecompiles( return err } addPrecompileToVM(ibcp, ibcp.Address()) + pointerp, err := pointer.NewPrecompile(evmKeeper, bankKeeper, wasmdViewKeeper) + if err != nil { + return err + } + addPrecompileToVM(pointerp, pointerp.Address()) + pointerviewp, err := pointerview.NewPrecompile(evmKeeper) + if err != nil { + return err + } + addPrecompileToVM(pointerviewp, pointerviewp.Address()) Initialized = true return nil } diff --git a/precompiles/wasmd/wasmd.go b/precompiles/wasmd/wasmd.go index 72acb075e2..213f1b5c14 100644 --- a/precompiles/wasmd/wasmd.go +++ b/precompiles/wasmd/wasmd.go @@ -6,7 +6,6 @@ import ( "encoding/json" "errors" "fmt" - "math" "math/big" sdk "github.com/cosmos/cosmos-sdk/types" @@ -14,6 +13,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" pcommon "github.com/sei-protocol/sei-chain/precompiles/common" + "github.com/sei-protocol/sei-chain/utils" ) const ( @@ -31,8 +31,6 @@ var _ vm.PrecompiledContract = &Precompile{} //go:embed abi.json var f embed.FS -var MaxUint64BigInt = new(big.Int).SetUint64(math.MaxUint64) - type Precompile struct { pcommon.Precompile evmKeeper pcommon.EVMKeeper @@ -118,8 +116,8 @@ func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, calli } gasMultipler := p.evmKeeper.GetPriorityNormalizer(ctx) gasLimitBigInt := new(big.Int).Mul(new(big.Int).SetUint64(suppliedGas), gasMultipler.RoundInt().BigInt()) - if gasLimitBigInt.Cmp(MaxUint64BigInt) > 0 { - gasLimitBigInt = MaxUint64BigInt + if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { + gasLimitBigInt = utils.BigMaxU64 } ctx = ctx.WithGasMeter(sdk.NewGasMeter(gasLimitBigInt.Uint64())) diff --git a/utils/constants.go b/utils/constants.go index 36232c00f8..f25c156e1e 100644 --- a/utils/constants.go +++ b/utils/constants.go @@ -14,5 +14,6 @@ var Big8 = big.NewInt(8) var Big27 = big.NewInt(27) var Big35 = big.NewInt(35) var BigMaxI64 = big.NewInt(math.MaxInt64) +var BigMaxU64 = new(big.Int).SetUint64(math.MaxUint64) var Sdk0 = sdk.NewInt(0) diff --git a/x/evm/artifacts/cw20/artifacts.go b/x/evm/artifacts/cw20/artifacts.go index 9d8092ad12..073971723e 100644 --- a/x/evm/artifacts/cw20/artifacts.go +++ b/x/evm/artifacts/cw20/artifacts.go @@ -5,8 +5,13 @@ import ( "embed" "encoding/hex" "fmt" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" ) +const CurrentVersion uint16 = 1 + //go:embed CW20ERC20Pointer.abi //go:embed CW20ERC20Pointer.bin //go:embed legacy.bin @@ -14,6 +19,7 @@ var f embed.FS var cachedBin []byte var cachedLegacyBin []byte +var cachedABI *abi.ABI func GetABI() []byte { bz, err := f.ReadFile("CW20ERC20Pointer.abi") @@ -23,6 +29,18 @@ func GetABI() []byte { return bz } +func GetParsedABI() *abi.ABI { + if cachedABI != nil { + return cachedABI + } + parsedABI, err := abi.JSON(strings.NewReader(string(GetABI()))) + if err != nil { + panic(err) + } + cachedABI = &parsedABI + return cachedABI +} + func GetBin() []byte { if cachedBin != nil { return cachedBin diff --git a/x/evm/artifacts/cw721/artifacts.go b/x/evm/artifacts/cw721/artifacts.go index 27d37be4d0..a2d0b56979 100644 --- a/x/evm/artifacts/cw721/artifacts.go +++ b/x/evm/artifacts/cw721/artifacts.go @@ -5,8 +5,13 @@ import ( "embed" "encoding/hex" "fmt" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" ) +const CurrentVersion uint16 = 1 + //go:embed CW721ERC721Pointer.abi //go:embed CW721ERC721Pointer.bin //go:embed legacy.bin @@ -14,6 +19,7 @@ var f embed.FS var cachedBin []byte var cachedLegacyBin []byte +var cachedABI *abi.ABI func GetABI() []byte { bz, err := f.ReadFile("CW721ERC721Pointer.abi") @@ -23,6 +29,18 @@ func GetABI() []byte { return bz } +func GetParsedABI() *abi.ABI { + if cachedABI != nil { + return cachedABI + } + parsedABI, err := abi.JSON(strings.NewReader(string(GetABI()))) + if err != nil { + panic(err) + } + cachedABI = &parsedABI + return cachedABI +} + func GetBin() []byte { if cachedBin != nil { return cachedBin diff --git a/x/evm/artifacts/native/artifacts.go b/x/evm/artifacts/native/artifacts.go index d624097d98..801f66f45d 100644 --- a/x/evm/artifacts/native/artifacts.go +++ b/x/evm/artifacts/native/artifacts.go @@ -5,13 +5,19 @@ import ( "embed" "encoding/hex" "fmt" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" ) +const CurrentVersion uint16 = 1 + //go:embed NativeSeiTokensERC20.abi //go:embed NativeSeiTokensERC20.bin var f embed.FS var cachedBin []byte +var cachedABI *abi.ABI func GetABI() []byte { bz, err := f.ReadFile("NativeSeiTokensERC20.abi") @@ -21,6 +27,18 @@ func GetABI() []byte { return bz } +func GetParsedABI() *abi.ABI { + if cachedABI != nil { + return cachedABI + } + parsedABI, err := abi.JSON(strings.NewReader(string(GetABI()))) + if err != nil { + panic(err) + } + cachedABI = &parsedABI + return cachedABI +} + func GetBin() []byte { if cachedBin != nil { return cachedBin diff --git a/x/evm/client/cli/tx.go b/x/evm/client/cli/tx.go index c94cb99baf..5e31c220c5 100644 --- a/x/evm/client/cli/tx.go +++ b/x/evm/client/cli/tx.go @@ -14,7 +14,6 @@ import ( "strconv" "strings" - ethabi "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" @@ -239,17 +238,11 @@ func CmdDeployErc20() *cobra.Command { } bytecode := native.GetBin() - abi := native.GetABI() - parsedABI, err := ethabi.JSON(strings.NewReader(string(abi))) - if err != nil { - fmt.Println("failed at parsing abi") - return err - } constructorArguments := []interface{}{ denom, name, symbol, uint8(decimal), } - packedArgs, err := parsedABI.Pack("", constructorArguments...) + packedArgs, err := native.GetParsedABI().Pack("", constructorArguments...) if err != nil { return err } @@ -325,17 +318,11 @@ func CmdDeployErcCw20() *cobra.Command { } bytecode := cw20.GetBin() - abi := cw20.GetABI() - parsedABI, err := ethabi.JSON(strings.NewReader(string(abi))) - if err != nil { - fmt.Println("failed at parsing abi") - return err - } constructorArguments := []interface{}{ args[0], args[1], args[2], } - packedArgs, err := parsedABI.Pack("", constructorArguments...) + packedArgs, err := cw20.GetParsedABI().Pack("", constructorArguments...) if err != nil { return err } @@ -411,17 +398,11 @@ func CmdDeployErcCw721() *cobra.Command { } bytecode := cw721.GetBin() - abi := cw721.GetABI() - parsedABI, err := ethabi.JSON(strings.NewReader(string(abi))) - if err != nil { - fmt.Println("failed at parsing abi") - return err - } constructorArguments := []interface{}{ args[0], args[1], args[2], } - packedArgs, err := parsedABI.Pack("", constructorArguments...) + packedArgs, err := cw721.GetParsedABI().Pack("", constructorArguments...) if err != nil { return err } diff --git a/x/evm/keeper/pointer.go b/x/evm/keeper/pointer.go new file mode 100644 index 0000000000..0d5231830b --- /dev/null +++ b/x/evm/keeper/pointer.go @@ -0,0 +1,62 @@ +package keeper + +import ( + "encoding/binary" + "fmt" + + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/cw20" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/native" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +func (k *Keeper) SetERC20NativePointer(ctx sdk.Context, token string, addr common.Address) error { + return k.SetPointerInfo(ctx, types.PointerERC20NativeKey(token), addr, native.CurrentVersion) +} + +func (k *Keeper) GetERC20NativePointer(ctx sdk.Context, token string) (addr common.Address, version uint16, exists bool) { + return k.GetPointerInfo(ctx, types.PointerERC20NativeKey(token)) +} + +func (k *Keeper) SetERC20CW20Pointer(ctx sdk.Context, cw20Address string, addr common.Address) error { + return k.SetPointerInfo(ctx, types.PointerERC20CW20Key(cw20Address), addr, cw20.CurrentVersion) +} + +func (k *Keeper) GetERC20CW20Pointer(ctx sdk.Context, cw20Address string) (addr common.Address, version uint16, exists bool) { + return k.GetPointerInfo(ctx, types.PointerERC20CW20Key(cw20Address)) +} + +func (k *Keeper) SetERC721CW721Pointer(ctx sdk.Context, cw721Address string, addr common.Address) error { + return k.SetPointerInfo(ctx, types.PointerERC721CW721Key(cw721Address), addr, cw20.CurrentVersion) +} + +func (k *Keeper) GetERC721CW721Pointer(ctx sdk.Context, cw721Address string) (addr common.Address, version uint16, exists bool) { + return k.GetPointerInfo(ctx, types.PointerERC721CW721Key(cw721Address)) +} + +func (k *Keeper) GetPointerInfo(ctx sdk.Context, pref []byte) (addr common.Address, version uint16, exists bool) { + store := prefix.NewStore(ctx.KVStore(k.GetStoreKey()), pref) + iter := store.ReverseIterator(nil, nil) + defer iter.Close() + exists = iter.Valid() + if !exists { + return + } + version = binary.BigEndian.Uint16(iter.Key()) + addr = common.BytesToAddress(iter.Value()) + return +} + +func (k *Keeper) SetPointerInfo(ctx sdk.Context, pref []byte, addr common.Address, version uint16) error { + existingAddr, existingVersion, exists := k.GetPointerInfo(ctx, pref) + if exists && existingVersion >= version { + return fmt.Errorf("pointer at %s with version %d exists when trying to set pointer for version %d", existingAddr.Hex(), existingVersion, version) + } + store := prefix.NewStore(ctx.KVStore(k.GetStoreKey()), pref) + versionBz := make([]byte, 2) + binary.BigEndian.PutUint16(versionBz, version) + store.Set(versionBz, addr[:]) + return nil +} diff --git a/x/evm/keeper/pointer_test.go b/x/evm/keeper/pointer_test.go new file mode 100644 index 0000000000..e4048af692 --- /dev/null +++ b/x/evm/keeper/pointer_test.go @@ -0,0 +1,21 @@ +package keeper_test + +import ( + "testing" + + testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" + "github.com/stretchr/testify/require" +) + +func TestSetPointer(t *testing.T) { + k, ctx := testkeeper.MockEVMKeeper() + _, pointer := testkeeper.MockAddressPair() + cw20, _ := testkeeper.MockAddressPair() + cw721, _ := testkeeper.MockAddressPair() + require.Nil(t, k.SetERC20NativePointer(ctx, "test", pointer)) + require.NotNil(t, k.SetERC20NativePointer(ctx, "test", pointer)) // already set + require.Nil(t, k.SetERC20CW20Pointer(ctx, cw20.String(), pointer)) + require.NotNil(t, k.SetERC20CW20Pointer(ctx, cw20.String(), pointer)) // already set + require.Nil(t, k.SetERC721CW721Pointer(ctx, cw721.String(), pointer)) + require.NotNil(t, k.SetERC721CW721Pointer(ctx, cw721.String(), pointer)) // already set +} diff --git a/x/evm/types/keys.go b/x/evm/types/keys.go index 2d1d94043d..3a64d211d8 100644 --- a/x/evm/types/keys.go +++ b/x/evm/types/keys.go @@ -45,6 +45,14 @@ var ( ReplaySeenAddrPrefix = []byte{0x12} ReplayedHeight = []byte{0x13} ReplayInitialHeight = []byte{0x14} + + PointerRegistryPrefix = []byte{0x15} +) + +var ( + PointerERC20NativePrefix = []byte{0x0} + PointerERC20CW20Prefix = []byte{0x1} + PointerERC721CW721Prefix = []byte{0x2} ) func EVMAddressToSeiAddressKey(evmAddress common.Address) []byte { @@ -74,3 +82,24 @@ func TxHashesKey(height int64) []byte { binary.BigEndian.PutUint64(bz, uint64(height)) return append(TxHashesPrefix, bz...) } + +func PointerERC20NativeKey(token string) []byte { + return append( + append(PointerRegistryPrefix, PointerERC20NativePrefix...), + []byte(token)..., + ) +} + +func PointerERC20CW20Key(cw20Address string) []byte { + return append( + append(PointerRegistryPrefix, PointerERC20CW20Prefix...), + []byte(cw20Address)..., + ) +} + +func PointerERC721CW721Key(cw721Address string) []byte { + return append( + append(PointerRegistryPrefix, PointerERC721CW721Prefix...), + []byte(cw721Address)..., + ) +}