From 2b559a6c146417a4496de0749b30eace183ebdd4 Mon Sep 17 00:00:00 2001 From: Tony Chen Date: Thu, 3 Apr 2025 15:52:50 +0800 Subject: [PATCH 1/4] Add back legacy precompile versions --- precompiles/addr/legacy/v520/abi.json | 1 + precompiles/addr/legacy/v520/addr.go | 149 +++++ precompiles/addr/legacy/v555/abi.json | 1 + precompiles/addr/legacy/v555/addr.go | 155 ++++++ precompiles/addr/legacy/v562/abi.json | 1 + precompiles/addr/legacy/v562/addr.go | 123 +++++ precompiles/addr/legacy/v575/abi.json | 1 + precompiles/addr/legacy/v575/addr.go | 207 +++++++ precompiles/addr/legacy/v600/abi.json | 1 + precompiles/addr/legacy/v600/addr.go | 261 +++++++++ precompiles/addr/legacy/v602/abi.json | 1 + precompiles/addr/legacy/v602/addr.go | 268 +++++++++ precompiles/addr/legacy/v603/abi.json | 1 + precompiles/addr/legacy/v603/addr.go | 274 +++++++++ precompiles/bank/legacy/v520/abi.json | 1 + precompiles/bank/legacy/v520/bank.go | 374 +++++++++++++ precompiles/bank/legacy/v552/abi.json | 1 + precompiles/bank/legacy/v552/bank.go | 380 +++++++++++++ precompiles/bank/legacy/v555/abi.json | 1 + precompiles/bank/legacy/v555/bank.go | 380 +++++++++++++ precompiles/bank/legacy/v562/abi.json | 1 + precompiles/bank/legacy/v562/bank.go | 353 ++++++++++++ precompiles/bank/legacy/v580/abi.json | 1 + precompiles/bank/legacy/v580/bank.go | 339 ++++++++++++ precompiles/bank/legacy/v600/abi.json | 1 + precompiles/bank/legacy/v600/bank.go | 339 ++++++++++++ precompiles/bank/legacy/v602/abi.json | 1 + precompiles/bank/legacy/v602/bank.go | 370 +++++++++++++ precompiles/bank/legacy/v603/abi.json | 1 + precompiles/bank/legacy/v603/bank.go | 376 +++++++++++++ .../common/legacy/v520/expected_keepers.go | 81 +++ precompiles/common/legacy/v520/precompiles.go | 119 ++++ .../common/legacy/v530/expected_keepers.go | 98 ++++ precompiles/common/legacy/v530/precompiles.go | 119 ++++ .../common/legacy/v555/expected_keepers.go | 89 +++ precompiles/common/legacy/v555/precompiles.go | 119 ++++ .../common/legacy/v562/expected_keepers.go | 96 ++++ precompiles/common/legacy/v562/precompiles.go | 242 ++++++++ .../common/legacy/v575/expected_keepers.go | 119 ++++ precompiles/common/legacy/v575/precompiles.go | 282 ++++++++++ .../common/legacy/v580/expected_keepers.go | 121 ++++ precompiles/common/legacy/v580/precompiles.go | 274 +++++++++ .../common/legacy/v600/expected_keepers.go | 121 ++++ precompiles/common/legacy/v600/precompiles.go | 276 +++++++++ precompiles/distribution/legacy/v520/abi.json | 1 + .../distribution/legacy/v520/distribution.go | 180 ++++++ precompiles/distribution/legacy/v552/abi.json | 1 + .../distribution/legacy/v552/distribution.go | 186 +++++++ precompiles/distribution/legacy/v555/abi.json | 1 + .../distribution/legacy/v555/distribution.go | 298 ++++++++++ precompiles/distribution/legacy/v562/abi.json | 1 + .../distribution/legacy/v562/distribution.go | 242 ++++++++ precompiles/distribution/legacy/v580/abi.json | 1 + .../distribution/legacy/v580/distribution.go | 329 +++++++++++ precompiles/gov/legacy/v520/abi.json | 1 + precompiles/gov/legacy/v520/gov.go | 169 ++++++ precompiles/gov/legacy/v555/abi.json | 1 + precompiles/gov/legacy/v555/gov.go | 175 ++++++ precompiles/gov/legacy/v562/abi.json | 1 + precompiles/gov/legacy/v562/gov.go | 148 +++++ precompiles/gov/legacy/v580/abi.json | 1 + precompiles/gov/legacy/v580/gov.go | 135 +++++ precompiles/ibc/legacy/v501/abi.json | 56 ++ precompiles/ibc/legacy/v501/ibc.go | 268 +++++++++ precompiles/ibc/legacy/v510/abi.json | 56 ++ precompiles/ibc/legacy/v510/ibc.go | 268 +++++++++ precompiles/ibc/legacy/v520/abi.json | 56 ++ precompiles/ibc/legacy/v520/ibc.go | 268 +++++++++ precompiles/ibc/legacy/v530/abi.json | 95 ++++ precompiles/ibc/legacy/v530/ibc.go | 454 +++++++++++++++ precompiles/ibc/legacy/v555/abi.json | 1 + precompiles/ibc/legacy/v555/ibc.go | 485 ++++++++++++++++ precompiles/ibc/legacy/v562/abi.json | 1 + precompiles/ibc/legacy/v562/ibc.go | 423 ++++++++++++++ precompiles/ibc/legacy/v580/abi.json | 1 + precompiles/ibc/legacy/v580/ibc.go | 409 ++++++++++++++ precompiles/ibc/legacy/v602/abi.json | 1 + precompiles/ibc/legacy/v602/ibc.go | 409 ++++++++++++++ precompiles/ibc/legacy/v603/abi.json | 1 + precompiles/ibc/legacy/v603/ibc.go | 400 ++++++++++++++ precompiles/json/legacy/v520/abi.json | 1 + precompiles/json/legacy/v520/json.go | 211 +++++++ precompiles/json/legacy/v530/abi.json | 1 + precompiles/json/legacy/v530/json.go | 217 ++++++++ precompiles/json/legacy/v555/abi.json | 1 + precompiles/json/legacy/v555/json.go | 225 ++++++++ precompiles/json/legacy/v562/abi.json | 1 + precompiles/json/legacy/v562/json.go | 191 +++++++ precompiles/json/legacy/v603/abi.json | 1 + precompiles/json/legacy/v603/json.go | 174 ++++++ precompiles/oracle/legacy/v520/abi.json | 1 + precompiles/oracle/legacy/v520/oracle.go | 175 ++++++ precompiles/oracle/legacy/v555/abi.json | 1 + precompiles/oracle/legacy/v555/json.go | 181 ++++++ precompiles/oracle/legacy/v562/abi.json | 1 + precompiles/oracle/legacy/v562/json.go | 145 +++++ precompiles/oracle/legacy/v600/abi.json | 1 + precompiles/oracle/legacy/v600/oracle.go | 131 +++++ precompiles/oracle/legacy/v602/abi.json | 1 + precompiles/oracle/legacy/v602/oracle.go | 137 +++++ precompiles/oracle/legacy/v603/abi.json | 1 + precompiles/oracle/legacy/v603/oracle.go | 144 +++++ precompiles/pointer/legacy/v520/abi.json | 1 + precompiles/pointer/legacy/v520/pointer.go | 283 ++++++++++ precompiles/pointer/legacy/v522/abi.json | 1 + precompiles/pointer/legacy/v522/pointer.go | 289 ++++++++++ precompiles/pointer/legacy/v530/abi.json | 1 + precompiles/pointer/legacy/v530/pointer.go | 299 ++++++++++ precompiles/pointer/legacy/v555/abi.json | 1 + precompiles/pointer/legacy/v555/pointer.go | 307 ++++++++++ precompiles/pointer/legacy/v562/abi.json | 1 + precompiles/pointer/legacy/v562/pointer.go | 275 +++++++++ precompiles/pointer/legacy/v575/abi.json | 1 + precompiles/pointer/legacy/v575/pointer.go | 182 ++++++ precompiles/pointer/legacy/v580/abi.json | 1 + precompiles/pointer/legacy/v580/pointer.go | 185 +++++++ precompiles/pointer/legacy/v600/abi.json | 1 + precompiles/pointer/legacy/v600/pointer.go | 185 +++++++ precompiles/pointerview/legacy/v520/abi.json | 1 + .../pointerview/legacy/v520/pointerview.go | 129 +++++ precompiles/pointerview/legacy/v555/abi.json | 1 + .../pointerview/legacy/v555/pointerview.go | 135 +++++ precompiles/pointerview/legacy/v562/abi.json | 1 + .../pointerview/legacy/v562/pointerview.go | 110 ++++ precompiles/setup.go | 4 + precompiles/staking/legacy/v520/abi.json | 1 + precompiles/staking/legacy/v520/staking.go | 215 ++++++++ precompiles/staking/legacy/v555/abi.json | 1 + precompiles/staking/legacy/v555/staking.go | 221 ++++++++ precompiles/staking/legacy/v562/abi.json | 1 + precompiles/staking/legacy/v562/staking.go | 194 +++++++ precompiles/staking/legacy/v580/abi.json | 1 + precompiles/staking/legacy/v580/staking.go | 252 +++++++++ precompiles/wasmd/legacy/v501/abi.json | 1 + precompiles/wasmd/legacy/v501/wasmd.go | 503 +++++++++++++++++ precompiles/wasmd/legacy/v510/abi.json | 1 + precompiles/wasmd/legacy/v510/wasmd.go | 503 +++++++++++++++++ precompiles/wasmd/legacy/v520/abi.json | 1 + precompiles/wasmd/legacy/v520/wasmd.go | 503 +++++++++++++++++ precompiles/wasmd/legacy/v522/abi.json | 1 + precompiles/wasmd/legacy/v522/wasmd.go | 508 +++++++++++++++++ precompiles/wasmd/legacy/v530/abi.json | 1 + precompiles/wasmd/legacy/v530/wasmd.go | 515 +++++++++++++++++ precompiles/wasmd/legacy/v555/abi.json | 1 + precompiles/wasmd/legacy/v555/wasmd.go | 522 ++++++++++++++++++ precompiles/wasmd/legacy/v562/abi.json | 1 + precompiles/wasmd/legacy/v562/wasmd.go | 463 ++++++++++++++++ precompiles/wasmd/legacy/v575/abi.json | 1 + precompiles/wasmd/legacy/v575/wasmd.go | 454 +++++++++++++++ precompiles/wasmd/legacy/v580/abi.json | 1 + precompiles/wasmd/legacy/v580/wasmd.go | 464 ++++++++++++++++ precompiles/wasmd/legacy/v600/abi.json | 1 + precompiles/wasmd/legacy/v600/wasmd.go | 464 ++++++++++++++++ utils/helpers/legacy/v575/address.go | 65 +++ utils/helpers/legacy/v575/associate.go | 50 ++ utils/helpers/legacy/v600/address.go | 69 +++ utils/helpers/legacy/v600/asssociate.go | 50 ++ 157 files changed, 22407 insertions(+) create mode 100644 precompiles/addr/legacy/v520/abi.json create mode 100644 precompiles/addr/legacy/v520/addr.go create mode 100644 precompiles/addr/legacy/v555/abi.json create mode 100644 precompiles/addr/legacy/v555/addr.go create mode 100644 precompiles/addr/legacy/v562/abi.json create mode 100644 precompiles/addr/legacy/v562/addr.go create mode 100644 precompiles/addr/legacy/v575/abi.json create mode 100644 precompiles/addr/legacy/v575/addr.go create mode 100644 precompiles/addr/legacy/v600/abi.json create mode 100644 precompiles/addr/legacy/v600/addr.go create mode 100644 precompiles/addr/legacy/v602/abi.json create mode 100644 precompiles/addr/legacy/v602/addr.go create mode 100644 precompiles/addr/legacy/v603/abi.json create mode 100644 precompiles/addr/legacy/v603/addr.go create mode 100644 precompiles/bank/legacy/v520/abi.json create mode 100644 precompiles/bank/legacy/v520/bank.go create mode 100644 precompiles/bank/legacy/v552/abi.json create mode 100644 precompiles/bank/legacy/v552/bank.go create mode 100644 precompiles/bank/legacy/v555/abi.json create mode 100644 precompiles/bank/legacy/v555/bank.go create mode 100644 precompiles/bank/legacy/v562/abi.json create mode 100644 precompiles/bank/legacy/v562/bank.go create mode 100644 precompiles/bank/legacy/v580/abi.json create mode 100644 precompiles/bank/legacy/v580/bank.go create mode 100644 precompiles/bank/legacy/v600/abi.json create mode 100644 precompiles/bank/legacy/v600/bank.go create mode 100644 precompiles/bank/legacy/v602/abi.json create mode 100644 precompiles/bank/legacy/v602/bank.go create mode 100644 precompiles/bank/legacy/v603/abi.json create mode 100644 precompiles/bank/legacy/v603/bank.go create mode 100644 precompiles/common/legacy/v520/expected_keepers.go create mode 100644 precompiles/common/legacy/v520/precompiles.go create mode 100644 precompiles/common/legacy/v530/expected_keepers.go create mode 100644 precompiles/common/legacy/v530/precompiles.go create mode 100644 precompiles/common/legacy/v555/expected_keepers.go create mode 100644 precompiles/common/legacy/v555/precompiles.go create mode 100644 precompiles/common/legacy/v562/expected_keepers.go create mode 100644 precompiles/common/legacy/v562/precompiles.go create mode 100644 precompiles/common/legacy/v575/expected_keepers.go create mode 100644 precompiles/common/legacy/v575/precompiles.go create mode 100644 precompiles/common/legacy/v580/expected_keepers.go create mode 100644 precompiles/common/legacy/v580/precompiles.go create mode 100644 precompiles/common/legacy/v600/expected_keepers.go create mode 100644 precompiles/common/legacy/v600/precompiles.go create mode 100644 precompiles/distribution/legacy/v520/abi.json create mode 100644 precompiles/distribution/legacy/v520/distribution.go create mode 100644 precompiles/distribution/legacy/v552/abi.json create mode 100644 precompiles/distribution/legacy/v552/distribution.go create mode 100644 precompiles/distribution/legacy/v555/abi.json create mode 100644 precompiles/distribution/legacy/v555/distribution.go create mode 100644 precompiles/distribution/legacy/v562/abi.json create mode 100644 precompiles/distribution/legacy/v562/distribution.go create mode 100644 precompiles/distribution/legacy/v580/abi.json create mode 100644 precompiles/distribution/legacy/v580/distribution.go create mode 100644 precompiles/gov/legacy/v520/abi.json create mode 100644 precompiles/gov/legacy/v520/gov.go create mode 100644 precompiles/gov/legacy/v555/abi.json create mode 100644 precompiles/gov/legacy/v555/gov.go create mode 100644 precompiles/gov/legacy/v562/abi.json create mode 100644 precompiles/gov/legacy/v562/gov.go create mode 100644 precompiles/gov/legacy/v580/abi.json create mode 100644 precompiles/gov/legacy/v580/gov.go create mode 100644 precompiles/ibc/legacy/v501/abi.json create mode 100644 precompiles/ibc/legacy/v501/ibc.go create mode 100644 precompiles/ibc/legacy/v510/abi.json create mode 100644 precompiles/ibc/legacy/v510/ibc.go create mode 100644 precompiles/ibc/legacy/v520/abi.json create mode 100644 precompiles/ibc/legacy/v520/ibc.go create mode 100644 precompiles/ibc/legacy/v530/abi.json create mode 100644 precompiles/ibc/legacy/v530/ibc.go create mode 100644 precompiles/ibc/legacy/v555/abi.json create mode 100644 precompiles/ibc/legacy/v555/ibc.go create mode 100644 precompiles/ibc/legacy/v562/abi.json create mode 100644 precompiles/ibc/legacy/v562/ibc.go create mode 100644 precompiles/ibc/legacy/v580/abi.json create mode 100644 precompiles/ibc/legacy/v580/ibc.go create mode 100644 precompiles/ibc/legacy/v602/abi.json create mode 100644 precompiles/ibc/legacy/v602/ibc.go create mode 100644 precompiles/ibc/legacy/v603/abi.json create mode 100644 precompiles/ibc/legacy/v603/ibc.go create mode 100644 precompiles/json/legacy/v520/abi.json create mode 100644 precompiles/json/legacy/v520/json.go create mode 100644 precompiles/json/legacy/v530/abi.json create mode 100644 precompiles/json/legacy/v530/json.go create mode 100644 precompiles/json/legacy/v555/abi.json create mode 100644 precompiles/json/legacy/v555/json.go create mode 100644 precompiles/json/legacy/v562/abi.json create mode 100644 precompiles/json/legacy/v562/json.go create mode 100644 precompiles/json/legacy/v603/abi.json create mode 100644 precompiles/json/legacy/v603/json.go create mode 100644 precompiles/oracle/legacy/v520/abi.json create mode 100644 precompiles/oracle/legacy/v520/oracle.go create mode 100644 precompiles/oracle/legacy/v555/abi.json create mode 100644 precompiles/oracle/legacy/v555/json.go create mode 100644 precompiles/oracle/legacy/v562/abi.json create mode 100644 precompiles/oracle/legacy/v562/json.go create mode 100644 precompiles/oracle/legacy/v600/abi.json create mode 100644 precompiles/oracle/legacy/v600/oracle.go create mode 100644 precompiles/oracle/legacy/v602/abi.json create mode 100644 precompiles/oracle/legacy/v602/oracle.go create mode 100644 precompiles/oracle/legacy/v603/abi.json create mode 100644 precompiles/oracle/legacy/v603/oracle.go create mode 100644 precompiles/pointer/legacy/v520/abi.json create mode 100644 precompiles/pointer/legacy/v520/pointer.go create mode 100644 precompiles/pointer/legacy/v522/abi.json create mode 100644 precompiles/pointer/legacy/v522/pointer.go create mode 100644 precompiles/pointer/legacy/v530/abi.json create mode 100644 precompiles/pointer/legacy/v530/pointer.go create mode 100644 precompiles/pointer/legacy/v555/abi.json create mode 100644 precompiles/pointer/legacy/v555/pointer.go create mode 100644 precompiles/pointer/legacy/v562/abi.json create mode 100644 precompiles/pointer/legacy/v562/pointer.go create mode 100644 precompiles/pointer/legacy/v575/abi.json create mode 100644 precompiles/pointer/legacy/v575/pointer.go create mode 100644 precompiles/pointer/legacy/v580/abi.json create mode 100644 precompiles/pointer/legacy/v580/pointer.go create mode 100644 precompiles/pointer/legacy/v600/abi.json create mode 100644 precompiles/pointer/legacy/v600/pointer.go create mode 100644 precompiles/pointerview/legacy/v520/abi.json create mode 100644 precompiles/pointerview/legacy/v520/pointerview.go create mode 100644 precompiles/pointerview/legacy/v555/abi.json create mode 100644 precompiles/pointerview/legacy/v555/pointerview.go create mode 100644 precompiles/pointerview/legacy/v562/abi.json create mode 100644 precompiles/pointerview/legacy/v562/pointerview.go create mode 100644 precompiles/staking/legacy/v520/abi.json create mode 100644 precompiles/staking/legacy/v520/staking.go create mode 100644 precompiles/staking/legacy/v555/abi.json create mode 100644 precompiles/staking/legacy/v555/staking.go create mode 100644 precompiles/staking/legacy/v562/abi.json create mode 100644 precompiles/staking/legacy/v562/staking.go create mode 100644 precompiles/staking/legacy/v580/abi.json create mode 100644 precompiles/staking/legacy/v580/staking.go create mode 100644 precompiles/wasmd/legacy/v501/abi.json create mode 100644 precompiles/wasmd/legacy/v501/wasmd.go create mode 100644 precompiles/wasmd/legacy/v510/abi.json create mode 100644 precompiles/wasmd/legacy/v510/wasmd.go create mode 100644 precompiles/wasmd/legacy/v520/abi.json create mode 100644 precompiles/wasmd/legacy/v520/wasmd.go create mode 100644 precompiles/wasmd/legacy/v522/abi.json create mode 100644 precompiles/wasmd/legacy/v522/wasmd.go create mode 100644 precompiles/wasmd/legacy/v530/abi.json create mode 100644 precompiles/wasmd/legacy/v530/wasmd.go create mode 100644 precompiles/wasmd/legacy/v555/abi.json create mode 100644 precompiles/wasmd/legacy/v555/wasmd.go create mode 100644 precompiles/wasmd/legacy/v562/abi.json create mode 100644 precompiles/wasmd/legacy/v562/wasmd.go create mode 100644 precompiles/wasmd/legacy/v575/abi.json create mode 100644 precompiles/wasmd/legacy/v575/wasmd.go create mode 100644 precompiles/wasmd/legacy/v580/abi.json create mode 100644 precompiles/wasmd/legacy/v580/wasmd.go create mode 100644 precompiles/wasmd/legacy/v600/abi.json create mode 100644 precompiles/wasmd/legacy/v600/wasmd.go create mode 100644 utils/helpers/legacy/v575/address.go create mode 100644 utils/helpers/legacy/v575/associate.go create mode 100644 utils/helpers/legacy/v600/address.go create mode 100644 utils/helpers/legacy/v600/asssociate.go diff --git a/precompiles/addr/legacy/v520/abi.json b/precompiles/addr/legacy/v520/abi.json new file mode 100644 index 0000000000..b9a6de2907 --- /dev/null +++ b/precompiles/addr/legacy/v520/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"addr","type":"string"}],"name":"getEvmAddr","outputs":[{"internalType":"address","name":"response","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getSeiAddr","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/addr/legacy/v520/addr.go b/precompiles/addr/legacy/v520/addr.go new file mode 100644 index 0000000000..1628e2b2d4 --- /dev/null +++ b/precompiles/addr/legacy/v520/addr.go @@ -0,0 +1,149 @@ +package v520 + +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/legacy/v520" +) + +const ( + GetSeiAddressMethod = "getSeiAddr" + GetEvmAddressMethod = "getEvmAddr" +) + +const ( + AddrAddress = "0x0000000000000000000000000000000000001004" +) + +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 + + GetSeiAddressID []byte + GetEvmAddressID []byte +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper) (*Precompile, error) { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + return nil, fmt.Errorf("error loading the staking 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(AddrAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case GetSeiAddressMethod: + p.GetSeiAddressID = m.ID + case GetEvmAddressMethod: + p.GetEvmAddressID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return pcommon.UnknownMethodCallGas + } + + method, err := p.ABI.MethodById(methodID) + if err != nil { + // This should never happen since this method is going to fail during Run + return pcommon.UnknownMethodCallGas + } + + return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "addr" +} + +func (p Precompile) Run(evm *vm.EVM, _ common.Address, _ common.Address, input []byte, value *big.Int, _ bool, _ bool) (bz []byte, err error) { + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, err + } + + switch method.Name { + case GetSeiAddressMethod: + return p.getSeiAddr(ctx, method, args, value) + case GetEvmAddressMethod: + return p.getEvmAddr(ctx, method, args, value) + } + return +} + +func (p Precompile) getSeiAddr(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, args[0].(common.Address)) + if !found { + return nil, fmt.Errorf("EVM address %s is not associated", args[0].(common.Address).Hex()) + } + return method.Outputs.Pack(seiAddr.String()) +} + +func (p Precompile) getEvmAddr(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + seiAddr, err := sdk.AccAddressFromBech32(args[0].(string)) + if err != nil { + return nil, err + } + + evmAddr, found := p.evmKeeper.GetEVMAddress(ctx, seiAddr) + if !found { + return nil, fmt.Errorf("sei address %s is not associated", args[0].(string)) + } + return method.Outputs.Pack(evmAddr) +} + +func (Precompile) IsTransaction(string) bool { + return false +} diff --git a/precompiles/addr/legacy/v555/abi.json b/precompiles/addr/legacy/v555/abi.json new file mode 100644 index 0000000000..b9a6de2907 --- /dev/null +++ b/precompiles/addr/legacy/v555/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"addr","type":"string"}],"name":"getEvmAddr","outputs":[{"internalType":"address","name":"response","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getSeiAddr","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/addr/legacy/v555/addr.go b/precompiles/addr/legacy/v555/addr.go new file mode 100644 index 0000000000..e4fabc97d8 --- /dev/null +++ b/precompiles/addr/legacy/v555/addr.go @@ -0,0 +1,155 @@ +package v555 + +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/legacy/v555" + "github.com/sei-protocol/sei-chain/x/evm/state" +) + +const ( + GetSeiAddressMethod = "getSeiAddr" + GetEvmAddressMethod = "getEvmAddr" +) + +const ( + AddrAddress = "0x0000000000000000000000000000000000001004" +) + +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 + + GetSeiAddressID []byte + GetEvmAddressID []byte +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper) (*Precompile, error) { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + return nil, fmt.Errorf("error loading the staking 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(AddrAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case GetSeiAddressMethod: + p.GetSeiAddressID = m.ID + case GetEvmAddressMethod: + p.GetEvmAddressID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return pcommon.UnknownMethodCallGas + } + + method, err := p.ABI.MethodById(methodID) + if err != nil { + // This should never happen since this method is going to fail during Run + return pcommon.UnknownMethodCallGas + } + + return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "addr" +} + +func (p Precompile) Run(evm *vm.EVM, _ common.Address, _ common.Address, input []byte, value *big.Int, _ bool, _ bool) (bz []byte, err error) { + defer func() { + if err != nil { + evm.StateDB.(*state.DBImpl).SetPrecompileError(err) + } + }() + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, err + } + + switch method.Name { + case GetSeiAddressMethod: + return p.getSeiAddr(ctx, method, args, value) + case GetEvmAddressMethod: + return p.getEvmAddr(ctx, method, args, value) + } + return +} + +func (p Precompile) getSeiAddr(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, args[0].(common.Address)) + if !found { + return nil, fmt.Errorf("EVM address %s is not associated", args[0].(common.Address).Hex()) + } + return method.Outputs.Pack(seiAddr.String()) +} + +func (p Precompile) getEvmAddr(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + seiAddr, err := sdk.AccAddressFromBech32(args[0].(string)) + if err != nil { + return nil, err + } + + evmAddr, found := p.evmKeeper.GetEVMAddress(ctx, seiAddr) + if !found { + return nil, fmt.Errorf("sei address %s is not associated", args[0].(string)) + } + return method.Outputs.Pack(evmAddr) +} + +func (Precompile) IsTransaction(string) bool { + return false +} diff --git a/precompiles/addr/legacy/v562/abi.json b/precompiles/addr/legacy/v562/abi.json new file mode 100644 index 0000000000..b9a6de2907 --- /dev/null +++ b/precompiles/addr/legacy/v562/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"addr","type":"string"}],"name":"getEvmAddr","outputs":[{"internalType":"address","name":"response","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getSeiAddr","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/addr/legacy/v562/addr.go b/precompiles/addr/legacy/v562/addr.go new file mode 100644 index 0000000000..32d4d67757 --- /dev/null +++ b/precompiles/addr/legacy/v562/addr.go @@ -0,0 +1,123 @@ +package v562 + +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/legacy/v562" + "github.com/sei-protocol/sei-chain/utils/metrics" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const ( + GetSeiAddressMethod = "getSeiAddr" + GetEvmAddressMethod = "getEvmAddr" +) + +const ( + AddrAddress = "0x0000000000000000000000000000000000001004" +) + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + evmKeeper pcommon.EVMKeeper + + GetSeiAddressID []byte + GetEvmAddressID []byte +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper) (*pcommon.Precompile, error) { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + return nil, fmt.Errorf("error loading the addr ABI %s", err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + return nil, err + } + + p := &PrecompileExecutor{ + evmKeeper: evmKeeper, + } + + for name, m := range newAbi.Methods { + switch name { + case GetSeiAddressMethod: + p.GetSeiAddressID = m.ID + case GetEvmAddressMethod: + p.GetEvmAddressID = m.ID + } + } + + return pcommon.NewPrecompile(newAbi, p, common.HexToAddress(AddrAddress), "addr"), nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p PrecompileExecutor) RequiredGas(input []byte, method *abi.Method) uint64 { + return pcommon.DefaultGasCost(input, p.IsTransaction(method.Name)) +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, _ common.Address, _ common.Address, args []interface{}, value *big.Int, _ bool, _ *vm.EVM) (bz []byte, err error) { + switch method.Name { + case GetSeiAddressMethod: + return p.getSeiAddr(ctx, method, args, value) + case GetEvmAddressMethod: + return p.getEvmAddr(ctx, method, args, value) + } + return +} + +func (p PrecompileExecutor) getSeiAddr(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, args[0].(common.Address)) + if !found { + metrics.IncrementAssociationError("getSeiAddr", types.NewAssociationMissingErr(args[0].(common.Address).Hex())) + return nil, fmt.Errorf("EVM address %s is not associated", args[0].(common.Address).Hex()) + } + return method.Outputs.Pack(seiAddr.String()) +} + +func (p PrecompileExecutor) getEvmAddr(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + seiAddr, err := sdk.AccAddressFromBech32(args[0].(string)) + if err != nil { + return nil, err + } + + evmAddr, found := p.evmKeeper.GetEVMAddress(ctx, seiAddr) + if !found { + metrics.IncrementAssociationError("getEvmAddr", types.NewAssociationMissingErr(args[0].(string))) + return nil, fmt.Errorf("sei address %s is not associated", args[0].(string)) + } + return method.Outputs.Pack(evmAddr) +} + +func (PrecompileExecutor) IsTransaction(string) bool { + return false +} diff --git a/precompiles/addr/legacy/v575/abi.json b/precompiles/addr/legacy/v575/abi.json new file mode 100644 index 0000000000..211faabec2 --- /dev/null +++ b/precompiles/addr/legacy/v575/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"v","type":"string"},{"internalType":"string","name":"r","type":"string"},{"internalType":"string","name":"s","type":"string"},{"internalType":"string","name":"customMessage","type":"string"}],"name":"associate","outputs":[{"internalType":"string","name":"seiAddr","type":"string"},{"internalType":"address","name":"evmAddr","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getSeiAddr","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"addr","type":"string"}],"name":"getEvmAddr","outputs":[{"internalType":"address","name":"response","type":"address"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/addr/legacy/v575/addr.go b/precompiles/addr/legacy/v575/addr.go new file mode 100644 index 0000000000..511cc40025 --- /dev/null +++ b/precompiles/addr/legacy/v575/addr.go @@ -0,0 +1,207 @@ +package v575 + +import ( + "bytes" + "embed" + "encoding/hex" + "fmt" + "strings" + + "math/big" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/sei-protocol/sei-chain/utils" + helpers "github.com/sei-protocol/sei-chain/utils/helpers/legacy/v575" + + 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/legacy/v575" + "github.com/sei-protocol/sei-chain/utils/metrics" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const ( + GetSeiAddressMethod = "getSeiAddr" + GetEvmAddressMethod = "getEvmAddr" + Associate = "associate" +) + +const ( + AddrAddress = "0x0000000000000000000000000000000000001004" +) + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + accountKeeper pcommon.AccountKeeper + + GetSeiAddressID []byte + GetEvmAddressID []byte + AssociateID []byte +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper, accountKeeper pcommon.AccountKeeper) (*pcommon.Precompile, error) { + + newAbi := pcommon.MustGetABI(f, "abi.json") + + p := &PrecompileExecutor{ + evmKeeper: evmKeeper, + bankKeeper: bankKeeper, + accountKeeper: accountKeeper, + } + + for name, m := range newAbi.Methods { + switch name { + case GetSeiAddressMethod: + p.GetSeiAddressID = m.ID + case GetEvmAddressMethod: + p.GetEvmAddressID = m.ID + case Associate: + p.AssociateID = m.ID + } + } + + return pcommon.NewPrecompile(newAbi, p, common.HexToAddress(AddrAddress), "addr"), nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p PrecompileExecutor) RequiredGas(input []byte, method *abi.Method) uint64 { + if bytes.Equal(method.ID, p.AssociateID) { + return 50000 + } + return pcommon.DefaultGasCost(input, p.IsTransaction(method.Name)) +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, _ common.Address, _ common.Address, args []interface{}, value *big.Int, _ bool, _ *vm.EVM) (bz []byte, err error) { + switch method.Name { + case GetSeiAddressMethod: + return p.getSeiAddr(ctx, method, args, value) + case GetEvmAddressMethod: + return p.getEvmAddr(ctx, method, args, value) + case Associate: + return p.associate(ctx, method, args, value) + } + return +} + +func (p PrecompileExecutor) getSeiAddr(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, args[0].(common.Address)) + if !found { + metrics.IncrementAssociationError("getSeiAddr", types.NewAssociationMissingErr(args[0].(common.Address).Hex())) + return nil, fmt.Errorf("EVM address %s is not associated", args[0].(common.Address).Hex()) + } + return method.Outputs.Pack(seiAddr.String()) +} + +func (p PrecompileExecutor) getEvmAddr(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + seiAddr, err := sdk.AccAddressFromBech32(args[0].(string)) + if err != nil { + return nil, err + } + + evmAddr, found := p.evmKeeper.GetEVMAddress(ctx, seiAddr) + if !found { + metrics.IncrementAssociationError("getEvmAddr", types.NewAssociationMissingErr(args[0].(string))) + return nil, fmt.Errorf("sei address %s is not associated", args[0].(string)) + } + return method.Outputs.Pack(evmAddr) +} + +func (p PrecompileExecutor) associate(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 4); err != nil { + return nil, err + } + + // v, r and s are components of a signature over the customMessage sent. + // We use the signature to construct the user's pubkey to obtain their addresses. + v := args[0].(string) + r := args[1].(string) + s := args[2].(string) + customMessage := args[3].(string) + + rBytes, err := decodeHexString(r) + if err != nil { + return nil, err + } + sBytes, err := decodeHexString(s) + if err != nil { + return nil, err + } + vBytes, err := decodeHexString(v) + if err != nil { + return nil, err + } + + vBig := new(big.Int).SetBytes(vBytes) + rBig := new(big.Int).SetBytes(rBytes) + sBig := new(big.Int).SetBytes(sBytes) + + // Derive addresses + vBig = new(big.Int).Add(vBig, utils.Big27) + + customMessageHash := crypto.Keccak256Hash([]byte(customMessage)) + evmAddr, seiAddr, pubkey, err := helpers.GetAddresses(vBig, rBig, sBig, customMessageHash) + if err != nil { + return nil, err + } + + // Check that address is not already associated + _, found := p.evmKeeper.GetEVMAddress(ctx, seiAddr) + if found { + return nil, fmt.Errorf("address %s is already associated with evm address %s", seiAddr, evmAddr) + } + + // Associate Addresses: + associationHelper := helpers.NewAssociationHelper(p.evmKeeper, p.bankKeeper, p.accountKeeper) + err = associationHelper.AssociateAddresses(ctx, seiAddr, evmAddr, pubkey) + if err != nil { + return nil, err + } + + return method.Outputs.Pack(seiAddr.String(), evmAddr) +} + +func (PrecompileExecutor) IsTransaction(method string) bool { + switch method { + case Associate: + return true + default: + return false + } +} + +func decodeHexString(hexString string) ([]byte, error) { + trimmed := strings.TrimPrefix(hexString, "0x") + if len(trimmed)%2 != 0 { + trimmed = "0" + trimmed + } + return hex.DecodeString(trimmed) +} diff --git a/precompiles/addr/legacy/v600/abi.json b/precompiles/addr/legacy/v600/abi.json new file mode 100644 index 0000000000..f0425eaf83 --- /dev/null +++ b/precompiles/addr/legacy/v600/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"v","type":"string"},{"internalType":"string","name":"r","type":"string"},{"internalType":"string","name":"s","type":"string"},{"internalType":"string","name":"customMessage","type":"string"}],"name":"associate","outputs":[{"internalType":"string","name":"seiAddr","type":"string"},{"internalType":"address","name":"evmAddr","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"pubKeyHex","type":"string"}],"name":"associatePubKey","outputs":[{"internalType":"string","name":"seiAddr","type":"string"},{"internalType":"address","name":"evmAddr","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getSeiAddr","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"addr","type":"string"}],"name":"getEvmAddr","outputs":[{"internalType":"address","name":"response","type":"address"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/addr/legacy/v600/addr.go b/precompiles/addr/legacy/v600/addr.go new file mode 100644 index 0000000000..d5c6fc46f2 --- /dev/null +++ b/precompiles/addr/legacy/v600/addr.go @@ -0,0 +1,261 @@ +package v600 + +import ( + "bytes" + "embed" + "encoding/hex" + "errors" + "fmt" + "strings" + + "github.com/btcsuite/btcd/btcec" + + "math/big" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/sei-protocol/sei-chain/utils" + helpers "github.com/sei-protocol/sei-chain/utils/helpers/legacy/v600" + + 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" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v600" + "github.com/sei-protocol/sei-chain/utils/metrics" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const ( + GetSeiAddressMethod = "getSeiAddr" + GetEvmAddressMethod = "getEvmAddr" + Associate = "associate" + AssociatePubKey = "associatePubKey" +) + +const ( + AddrAddress = "0x0000000000000000000000000000000000001004" +) + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + accountKeeper pcommon.AccountKeeper + + GetSeiAddressID []byte + GetEvmAddressID []byte + AssociateID []byte + AssociatePubKeyID []byte +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper, accountKeeper pcommon.AccountKeeper) (*pcommon.Precompile, error) { + + newAbi := pcommon.MustGetABI(f, "abi.json") + + p := &PrecompileExecutor{ + evmKeeper: evmKeeper, + bankKeeper: bankKeeper, + accountKeeper: accountKeeper, + } + + for name, m := range newAbi.Methods { + switch name { + case GetSeiAddressMethod: + p.GetSeiAddressID = m.ID + case GetEvmAddressMethod: + p.GetEvmAddressID = m.ID + case Associate: + p.AssociateID = m.ID + case AssociatePubKey: + p.AssociatePubKeyID = m.ID + } + } + + return pcommon.NewPrecompile(newAbi, p, common.HexToAddress(AddrAddress), "addr"), nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p PrecompileExecutor) RequiredGas(input []byte, method *abi.Method) uint64 { + if bytes.Equal(method.ID, p.AssociateID) || bytes.Equal(method.ID, p.AssociatePubKeyID) { + return 50000 + } + return pcommon.DefaultGasCost(input, p.IsTransaction(method.Name)) +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, _ common.Address, _ common.Address, args []interface{}, value *big.Int, readOnly bool, _ *vm.EVM) (bz []byte, err error) { + switch method.Name { + case GetSeiAddressMethod: + return p.getSeiAddr(ctx, method, args, value) + case GetEvmAddressMethod: + return p.getEvmAddr(ctx, method, args, value) + case Associate: + if readOnly { + return nil, errors.New("cannot call associate precompile from staticcall") + } + return p.associate(ctx, method, args, value) + case AssociatePubKey: + if readOnly { + return nil, errors.New("cannot call associate pub key precompile from staticcall") + } + return p.associatePublicKey(ctx, method, args, value) + } + return +} + +func (p PrecompileExecutor) getSeiAddr(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, args[0].(common.Address)) + if !found { + metrics.IncrementAssociationError("getSeiAddr", types.NewAssociationMissingErr(args[0].(common.Address).Hex())) + return nil, fmt.Errorf("EVM address %s is not associated", args[0].(common.Address).Hex()) + } + return method.Outputs.Pack(seiAddr.String()) +} + +func (p PrecompileExecutor) getEvmAddr(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + seiAddr, err := sdk.AccAddressFromBech32(args[0].(string)) + if err != nil { + return nil, err + } + + evmAddr, found := p.evmKeeper.GetEVMAddress(ctx, seiAddr) + if !found { + metrics.IncrementAssociationError("getEvmAddr", types.NewAssociationMissingErr(args[0].(string))) + return nil, fmt.Errorf("sei address %s is not associated", args[0].(string)) + } + return method.Outputs.Pack(evmAddr) +} + +func (p PrecompileExecutor) associate(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 4); err != nil { + return nil, err + } + + // v, r and s are components of a signature over the customMessage sent. + // We use the signature to construct the user's pubkey to obtain their addresses. + v := args[0].(string) + r := args[1].(string) + s := args[2].(string) + customMessage := args[3].(string) + + rBytes, err := decodeHexString(r) + if err != nil { + return nil, err + } + sBytes, err := decodeHexString(s) + if err != nil { + return nil, err + } + vBytes, err := decodeHexString(v) + if err != nil { + return nil, err + } + + vBig := new(big.Int).SetBytes(vBytes) + rBig := new(big.Int).SetBytes(rBytes) + sBig := new(big.Int).SetBytes(sBytes) + + // Derive addresses + vBig = new(big.Int).Add(vBig, utils.Big27) + + customMessageHash := crypto.Keccak256Hash([]byte(customMessage)) + evmAddr, seiAddr, pubkey, err := helpers.GetAddresses(vBig, rBig, sBig, customMessageHash) + if err != nil { + return nil, err + } + + return p.associateAddresses(ctx, method, evmAddr, seiAddr, pubkey) +} + +func (p PrecompileExecutor) associatePublicKey(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + // Takes a single argument, a compressed pubkey in hex format, excluding the '0x' + pubKeyHex := args[0].(string) + + pubKeyBytes, err := hex.DecodeString(pubKeyHex) + if err != nil { + return nil, err + } + + // Parse the compressed public key + pubKey, err := btcec.ParsePubKey(pubKeyBytes, btcec.S256()) + if err != nil { + return nil, err + } + + // Convert to uncompressed public key + uncompressedPubKey := pubKey.SerializeUncompressed() + + evmAddr, seiAddr, pubkey, err := helpers.GetAddressesFromPubkeyBytes(uncompressedPubKey) + if err != nil { + return nil, err + } + + return p.associateAddresses(ctx, method, evmAddr, seiAddr, pubkey) +} + +func (p PrecompileExecutor) associateAddresses(ctx sdk.Context, method *abi.Method, evmAddr common.Address, seiAddr sdk.AccAddress, pubkey cryptotypes.PubKey) ([]byte, error) { + // Check that address is not already associated + _, found := p.evmKeeper.GetEVMAddress(ctx, seiAddr) + if found { + return nil, fmt.Errorf("address %s is already associated with evm address %s", seiAddr, evmAddr) + } + + // Associate Addresses: + associationHelper := helpers.NewAssociationHelper(p.evmKeeper, p.bankKeeper, p.accountKeeper) + err := associationHelper.AssociateAddresses(ctx, seiAddr, evmAddr, pubkey) + if err != nil { + return nil, err + } + + return method.Outputs.Pack(seiAddr.String(), evmAddr) +} + +func (PrecompileExecutor) IsTransaction(method string) bool { + switch method { + case Associate: + return true + default: + return false + } +} + +func decodeHexString(hexString string) ([]byte, error) { + trimmed := strings.TrimPrefix(hexString, "0x") + if len(trimmed)%2 != 0 { + trimmed = "0" + trimmed + } + return hex.DecodeString(trimmed) +} diff --git a/precompiles/addr/legacy/v602/abi.json b/precompiles/addr/legacy/v602/abi.json new file mode 100644 index 0000000000..f0425eaf83 --- /dev/null +++ b/precompiles/addr/legacy/v602/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"v","type":"string"},{"internalType":"string","name":"r","type":"string"},{"internalType":"string","name":"s","type":"string"},{"internalType":"string","name":"customMessage","type":"string"}],"name":"associate","outputs":[{"internalType":"string","name":"seiAddr","type":"string"},{"internalType":"address","name":"evmAddr","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"pubKeyHex","type":"string"}],"name":"associatePubKey","outputs":[{"internalType":"string","name":"seiAddr","type":"string"},{"internalType":"address","name":"evmAddr","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getSeiAddr","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"addr","type":"string"}],"name":"getEvmAddr","outputs":[{"internalType":"address","name":"response","type":"address"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/addr/legacy/v602/addr.go b/precompiles/addr/legacy/v602/addr.go new file mode 100644 index 0000000000..e7ad6b9145 --- /dev/null +++ b/precompiles/addr/legacy/v602/addr.go @@ -0,0 +1,268 @@ +package v602 + +import ( + "bytes" + "embed" + "encoding/hex" + "errors" + "fmt" + "strings" + + "github.com/btcsuite/btcd/btcec" + + "math/big" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/utils/helpers" + + 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" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common" + "github.com/sei-protocol/sei-chain/utils/metrics" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const ( + GetSeiAddressMethod = "getSeiAddr" + GetEvmAddressMethod = "getEvmAddr" + Associate = "associate" + AssociatePubKey = "associatePubKey" +) + +const ( + AddrAddress = "0x0000000000000000000000000000000000001004" +) + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + accountKeeper pcommon.AccountKeeper + + GetSeiAddressID []byte + GetEvmAddressID []byte + AssociateID []byte + AssociatePubKeyID []byte +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper, accountKeeper pcommon.AccountKeeper) (*pcommon.DynamicGasPrecompile, error) { + + newAbi := pcommon.MustGetABI(f, "abi.json") + + p := &PrecompileExecutor{ + evmKeeper: evmKeeper, + bankKeeper: bankKeeper, + accountKeeper: accountKeeper, + } + + for name, m := range newAbi.Methods { + switch name { + case GetSeiAddressMethod: + p.GetSeiAddressID = m.ID + case GetEvmAddressMethod: + p.GetEvmAddressID = m.ID + case Associate: + p.AssociateID = m.ID + case AssociatePubKey: + p.AssociatePubKeyID = m.ID + } + } + + return pcommon.NewDynamicGasPrecompile(newAbi, p, common.HexToAddress(AddrAddress), "addr"), nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p PrecompileExecutor) RequiredGas(input []byte, method *abi.Method) uint64 { + if bytes.Equal(method.ID, p.AssociateID) || bytes.Equal(method.ID, p.AssociatePubKeyID) { + return 50000 + } + return pcommon.DefaultGasCost(input, p.IsTransaction(method.Name)) +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, _ common.Address, _ common.Address, args []interface{}, value *big.Int, readOnly bool, _ *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + switch method.Name { + case GetSeiAddressMethod: + return p.getSeiAddr(ctx, method, args, value) + case GetEvmAddressMethod: + return p.getEvmAddr(ctx, method, args, value) + case Associate: + if readOnly { + return nil, 0, errors.New("cannot call associate precompile from staticcall") + } + return p.associate(ctx, method, args, value) + case AssociatePubKey: + if readOnly { + return nil, 0, errors.New("cannot call associate pub key precompile from staticcall") + } + return p.associatePublicKey(ctx, method, args, value) + } + return +} + +func (p PrecompileExecutor) getSeiAddr(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, err error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + + seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, args[0].(common.Address)) + if !found { + metrics.IncrementAssociationError("getSeiAddr", types.NewAssociationMissingErr(args[0].(common.Address).Hex())) + return nil, 0, fmt.Errorf("EVM address %s is not associated", args[0].(common.Address).Hex()) + } + ret, err = method.Outputs.Pack(seiAddr.String()) + return ret, pcommon.GetRemainingGas(ctx, p.evmKeeper), err +} + +func (p PrecompileExecutor) getEvmAddr(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, err error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + + seiAddr, err := sdk.AccAddressFromBech32(args[0].(string)) + if err != nil { + return nil, 0, err + } + + evmAddr, found := p.evmKeeper.GetEVMAddress(ctx, seiAddr) + if !found { + metrics.IncrementAssociationError("getEvmAddr", types.NewAssociationMissingErr(args[0].(string))) + return nil, 0, fmt.Errorf("sei address %s is not associated", args[0].(string)) + } + ret, err = method.Outputs.Pack(evmAddr) + return ret, pcommon.GetRemainingGas(ctx, p.evmKeeper), err +} + +func (p PrecompileExecutor) associate(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, err error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 4); err != nil { + return nil, 0, err + } + + // v, r and s are components of a signature over the customMessage sent. + // We use the signature to construct the user's pubkey to obtain their addresses. + v := args[0].(string) + r := args[1].(string) + s := args[2].(string) + customMessage := args[3].(string) + + rBytes, err := decodeHexString(r) + if err != nil { + return nil, 0, err + } + sBytes, err := decodeHexString(s) + if err != nil { + return nil, 0, err + } + vBytes, err := decodeHexString(v) + if err != nil { + return nil, 0, err + } + + vBig := new(big.Int).SetBytes(vBytes) + rBig := new(big.Int).SetBytes(rBytes) + sBig := new(big.Int).SetBytes(sBytes) + + // Derive addresses + vBig = new(big.Int).Add(vBig, utils.Big27) + + customMessageHash := crypto.Keccak256Hash([]byte(customMessage)) + evmAddr, seiAddr, pubkey, err := helpers.GetAddresses(vBig, rBig, sBig, customMessageHash) + if err != nil { + return nil, 0, err + } + + return p.associateAddresses(ctx, method, evmAddr, seiAddr, pubkey) +} + +func (p PrecompileExecutor) associatePublicKey(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, err error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + + // Takes a single argument, a compressed pubkey in hex format, excluding the '0x' + pubKeyHex := args[0].(string) + + pubKeyBytes, err := hex.DecodeString(pubKeyHex) + if err != nil { + return nil, 0, err + } + + // Parse the compressed public key + pubKey, err := btcec.ParsePubKey(pubKeyBytes, btcec.S256()) + if err != nil { + return nil, 0, err + } + + // Convert to uncompressed public key + uncompressedPubKey := pubKey.SerializeUncompressed() + + evmAddr, seiAddr, pubkey, err := helpers.GetAddressesFromPubkeyBytes(uncompressedPubKey) + if err != nil { + return nil, 0, err + } + + return p.associateAddresses(ctx, method, evmAddr, seiAddr, pubkey) +} + +func (p PrecompileExecutor) associateAddresses(ctx sdk.Context, method *abi.Method, evmAddr common.Address, seiAddr sdk.AccAddress, pubkey cryptotypes.PubKey) (ret []byte, remainingGas uint64, err error) { + // Check that address is not already associated + _, found := p.evmKeeper.GetEVMAddress(ctx, seiAddr) + if found { + return nil, 0, fmt.Errorf("address %s is already associated with evm address %s", seiAddr, evmAddr) + } + + // Associate Addresses: + associationHelper := helpers.NewAssociationHelper(p.evmKeeper, p.bankKeeper, p.accountKeeper) + err = associationHelper.AssociateAddresses(ctx, seiAddr, evmAddr, pubkey) + if err != nil { + return nil, 0, err + } + + ret, err = method.Outputs.Pack(seiAddr.String(), evmAddr) + return ret, pcommon.GetRemainingGas(ctx, p.evmKeeper), err +} + +func (PrecompileExecutor) IsTransaction(method string) bool { + switch method { + case Associate: + return true + default: + return false + } +} + +func (p PrecompileExecutor) EVMKeeper() pcommon.EVMKeeper { + return p.evmKeeper +} + +func decodeHexString(hexString string) ([]byte, error) { + trimmed := strings.TrimPrefix(hexString, "0x") + if len(trimmed)%2 != 0 { + trimmed = "0" + trimmed + } + return hex.DecodeString(trimmed) +} diff --git a/precompiles/addr/legacy/v603/abi.json b/precompiles/addr/legacy/v603/abi.json new file mode 100644 index 0000000000..f0425eaf83 --- /dev/null +++ b/precompiles/addr/legacy/v603/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"v","type":"string"},{"internalType":"string","name":"r","type":"string"},{"internalType":"string","name":"s","type":"string"},{"internalType":"string","name":"customMessage","type":"string"}],"name":"associate","outputs":[{"internalType":"string","name":"seiAddr","type":"string"},{"internalType":"address","name":"evmAddr","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"pubKeyHex","type":"string"}],"name":"associatePubKey","outputs":[{"internalType":"string","name":"seiAddr","type":"string"},{"internalType":"address","name":"evmAddr","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getSeiAddr","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"addr","type":"string"}],"name":"getEvmAddr","outputs":[{"internalType":"address","name":"response","type":"address"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/addr/legacy/v603/addr.go b/precompiles/addr/legacy/v603/addr.go new file mode 100644 index 0000000000..c0f9e442c9 --- /dev/null +++ b/precompiles/addr/legacy/v603/addr.go @@ -0,0 +1,274 @@ +package v603 + +import ( + "bytes" + "embed" + "encoding/hex" + "errors" + "fmt" + "strings" + + "github.com/btcsuite/btcd/btcec" + + "math/big" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/utils/helpers" + + 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" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common" + "github.com/sei-protocol/sei-chain/utils/metrics" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const ( + GetSeiAddressMethod = "getSeiAddr" + GetEvmAddressMethod = "getEvmAddr" + Associate = "associate" + AssociatePubKey = "associatePubKey" +) + +const ( + AddrAddress = "0x0000000000000000000000000000000000001004" +) + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + accountKeeper pcommon.AccountKeeper + + GetSeiAddressID []byte + GetEvmAddressID []byte + AssociateID []byte + AssociatePubKeyID []byte +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper, accountKeeper pcommon.AccountKeeper) (*pcommon.DynamicGasPrecompile, error) { + + newAbi := pcommon.MustGetABI(f, "abi.json") + + p := &PrecompileExecutor{ + evmKeeper: evmKeeper, + bankKeeper: bankKeeper, + accountKeeper: accountKeeper, + } + + for name, m := range newAbi.Methods { + switch name { + case GetSeiAddressMethod: + p.GetSeiAddressID = m.ID + case GetEvmAddressMethod: + p.GetEvmAddressID = m.ID + case Associate: + p.AssociateID = m.ID + case AssociatePubKey: + p.AssociatePubKeyID = m.ID + } + } + + return pcommon.NewDynamicGasPrecompile(newAbi, p, common.HexToAddress(AddrAddress), "addr"), nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p PrecompileExecutor) RequiredGas(input []byte, method *abi.Method) uint64 { + if bytes.Equal(method.ID, p.AssociateID) || bytes.Equal(method.ID, p.AssociatePubKeyID) { + return 50000 + } + return pcommon.DefaultGasCost(input, p.IsTransaction(method.Name)) +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, _ common.Address, _ common.Address, args []interface{}, value *big.Int, readOnly bool, _ *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + // Needed to catch gas meter panics + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("execution reverted: %v", r) + } + }() + switch method.Name { + case GetSeiAddressMethod: + return p.getSeiAddr(ctx, method, args, value) + case GetEvmAddressMethod: + return p.getEvmAddr(ctx, method, args, value) + case Associate: + if readOnly { + return nil, 0, errors.New("cannot call associate precompile from staticcall") + } + return p.associate(ctx, method, args, value) + case AssociatePubKey: + if readOnly { + return nil, 0, errors.New("cannot call associate pub key precompile from staticcall") + } + return p.associatePublicKey(ctx, method, args, value) + } + return +} + +func (p PrecompileExecutor) getSeiAddr(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, err error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + + seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, args[0].(common.Address)) + if !found { + metrics.IncrementAssociationError("getSeiAddr", types.NewAssociationMissingErr(args[0].(common.Address).Hex())) + return nil, 0, fmt.Errorf("EVM address %s is not associated", args[0].(common.Address).Hex()) + } + ret, err = method.Outputs.Pack(seiAddr.String()) + return ret, pcommon.GetRemainingGas(ctx, p.evmKeeper), err +} + +func (p PrecompileExecutor) getEvmAddr(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, err error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + + seiAddr, err := sdk.AccAddressFromBech32(args[0].(string)) + if err != nil { + return nil, 0, err + } + + evmAddr, found := p.evmKeeper.GetEVMAddress(ctx, seiAddr) + if !found { + metrics.IncrementAssociationError("getEvmAddr", types.NewAssociationMissingErr(args[0].(string))) + return nil, 0, fmt.Errorf("sei address %s is not associated", args[0].(string)) + } + ret, err = method.Outputs.Pack(evmAddr) + return ret, pcommon.GetRemainingGas(ctx, p.evmKeeper), err +} + +func (p PrecompileExecutor) associate(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, err error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 4); err != nil { + return nil, 0, err + } + + // v, r and s are components of a signature over the customMessage sent. + // We use the signature to construct the user's pubkey to obtain their addresses. + v := args[0].(string) + r := args[1].(string) + s := args[2].(string) + customMessage := args[3].(string) + + rBytes, err := decodeHexString(r) + if err != nil { + return nil, 0, err + } + sBytes, err := decodeHexString(s) + if err != nil { + return nil, 0, err + } + vBytes, err := decodeHexString(v) + if err != nil { + return nil, 0, err + } + + vBig := new(big.Int).SetBytes(vBytes) + rBig := new(big.Int).SetBytes(rBytes) + sBig := new(big.Int).SetBytes(sBytes) + + // Derive addresses + vBig = new(big.Int).Add(vBig, utils.Big27) + + customMessageHash := crypto.Keccak256Hash([]byte(customMessage)) + evmAddr, seiAddr, pubkey, err := helpers.GetAddresses(vBig, rBig, sBig, customMessageHash) + if err != nil { + return nil, 0, err + } + + return p.associateAddresses(ctx, method, evmAddr, seiAddr, pubkey) +} + +func (p PrecompileExecutor) associatePublicKey(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, err error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + + // Takes a single argument, a compressed pubkey in hex format, excluding the '0x' + pubKeyHex := args[0].(string) + + pubKeyBytes, err := hex.DecodeString(pubKeyHex) + if err != nil { + return nil, 0, err + } + + // Parse the compressed public key + pubKey, err := btcec.ParsePubKey(pubKeyBytes, btcec.S256()) + if err != nil { + return nil, 0, err + } + + // Convert to uncompressed public key + uncompressedPubKey := pubKey.SerializeUncompressed() + + evmAddr, seiAddr, pubkey, err := helpers.GetAddressesFromPubkeyBytes(uncompressedPubKey) + if err != nil { + return nil, 0, err + } + + return p.associateAddresses(ctx, method, evmAddr, seiAddr, pubkey) +} + +func (p PrecompileExecutor) associateAddresses(ctx sdk.Context, method *abi.Method, evmAddr common.Address, seiAddr sdk.AccAddress, pubkey cryptotypes.PubKey) (ret []byte, remainingGas uint64, err error) { + // Check that address is not already associated + _, found := p.evmKeeper.GetEVMAddress(ctx, seiAddr) + if found { + return nil, 0, fmt.Errorf("address %s is already associated with evm address %s", seiAddr, evmAddr) + } + + // Associate Addresses: + associationHelper := helpers.NewAssociationHelper(p.evmKeeper, p.bankKeeper, p.accountKeeper) + err = associationHelper.AssociateAddresses(ctx, seiAddr, evmAddr, pubkey) + if err != nil { + return nil, 0, err + } + + ret, err = method.Outputs.Pack(seiAddr.String(), evmAddr) + return ret, pcommon.GetRemainingGas(ctx, p.evmKeeper), err +} + +func (PrecompileExecutor) IsTransaction(method string) bool { + switch method { + case Associate: + return true + default: + return false + } +} + +func (p PrecompileExecutor) EVMKeeper() pcommon.EVMKeeper { + return p.evmKeeper +} + +func decodeHexString(hexString string) ([]byte, error) { + trimmed := strings.TrimPrefix(hexString, "0x") + if len(trimmed)%2 != 0 { + trimmed = "0" + trimmed + } + return hex.DecodeString(trimmed) +} diff --git a/precompiles/bank/legacy/v520/abi.json b/precompiles/bank/legacy/v520/abi.json new file mode 100644 index 0000000000..844ac48943 --- /dev/null +++ b/precompiles/bank/legacy/v520/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"acc","type":"address"}],"name":"all_balances","outputs":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"denom","type":"string"}],"internalType":"struct IBank.Coin[]","name":"response","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"acc","type":"address"},{"internalType":"string","name":"denom","type":"string"}],"name":"balance","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"decimals","outputs":[{"internalType":"uint8","name":"response","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"name","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"fromAddress","type":"address"},{"internalType":"address","name":"toAddress","type":"address"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"send","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"toNativeAddress","type":"string"}],"name":"sendNative","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"supply","outputs":[{"internalType":"uint256","name":"response","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"symbol","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/bank/legacy/v520/bank.go b/precompiles/bank/legacy/v520/bank.go new file mode 100644 index 0000000000..47d564c8fe --- /dev/null +++ b/precompiles/bank/legacy/v520/bank.go @@ -0,0 +1,374 @@ +package v520 + +import ( + "bytes" + "embed" + "errors" + "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/legacy/v520" + "github.com/sei-protocol/sei-chain/utils" + "github.com/tendermint/tendermint/libs/log" +) + +const ( + SendMethod = "send" + SendNativeMethod = "sendNative" + BalanceMethod = "balance" + AllBalancesMethod = "all_balances" + NameMethod = "name" + SymbolMethod = "symbol" + DecimalsMethod = "decimals" + SupplyMethod = "supply" +) + +const ( + BankAddress = "0x0000000000000000000000000000000000001001" +) + +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 + +func GetABI() abi.ABI { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +type Precompile struct { + pcommon.Precompile + bankKeeper pcommon.BankKeeper + evmKeeper pcommon.EVMKeeper + address common.Address + + SendID []byte + SendNativeID []byte + BalanceID []byte + AllBalancesID []byte + NameID []byte + SymbolID []byte + DecimalsID []byte + SupplyID []byte +} + +type CoinBalance struct { + Amount *big.Int + Denom string +} + +func NewPrecompile(bankKeeper pcommon.BankKeeper, evmKeeper pcommon.EVMKeeper) (*Precompile, error) { + newAbi := GetABI() + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + bankKeeper: bankKeeper, + evmKeeper: evmKeeper, + address: common.HexToAddress(BankAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case SendMethod: + p.SendID = m.ID + case SendNativeMethod: + p.SendNativeID = m.ID + case BalanceMethod: + p.BalanceID = m.ID + case AllBalancesMethod: + p.AllBalancesID = m.ID + case NameMethod: + p.NameID = m.ID + case SymbolMethod: + p.SymbolID = m.ID + case DecimalsMethod: + p.DecimalsID = m.ID + case SupplyMethod: + p.SupplyID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return pcommon.UnknownMethodCallGas + } + + method, err := p.ABI.MethodById(methodID) + if err != nil { + // This should never happen since this method is going to fail during Run + return pcommon.UnknownMethodCallGas + } + + return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "bank" +} + +func (p Precompile) Run(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, value *big.Int, readOnly bool, _ bool) (bz []byte, err error) { + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, err + } + + switch method.Name { + case SendMethod: + return p.send(ctx, caller, method, args, value, readOnly) + case SendNativeMethod: + return p.sendNative(ctx, method, args, caller, callingContract, value, readOnly) + case BalanceMethod: + return p.balance(ctx, method, args, value) + case AllBalancesMethod: + return p.all_balances(ctx, method, args, value) + case NameMethod: + return p.name(ctx, method, args, value) + case SymbolMethod: + return p.symbol(ctx, method, args, value) + case DecimalsMethod: + return p.decimals(ctx, method, args, value) + case SupplyMethod: + return p.totalSupply(ctx, method, args, value) + } + return +} + +func (p Precompile) send(ctx sdk.Context, caller common.Address, method *abi.Method, args []interface{}, value *big.Int, readOnly bool) ([]byte, error) { + if readOnly { + return nil, errors.New("cannot call send from staticcall") + } + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 4); err != nil { + return nil, err + } + denom := args[2].(string) + if denom == "" { + return nil, errors.New("invalid denom") + } + pointer, _, exists := p.evmKeeper.GetERC20NativePointer(ctx, denom) + if !exists || pointer.Cmp(caller) != 0 { + return nil, fmt.Errorf("only pointer %s can send %s but got %s", pointer.Hex(), denom, caller.Hex()) + } + amount := args[3].(*big.Int) + if amount.Cmp(utils.Big0) == 0 { + // short circuit + return method.Outputs.Pack(true) + } + // TODO: it's possible to extend evm module's balance to handle non-usei tokens as well + senderSeiAddr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + return nil, err + } + receiverSeiAddr, err := p.accAddressFromArg(ctx, args[1]) + if err != nil { + return nil, err + } + + if err := p.bankKeeper.SendCoins(ctx, senderSeiAddr, receiverSeiAddr, sdk.NewCoins(sdk.NewCoin(denom, sdk.NewIntFromBigInt(amount)))); err != nil { + return nil, err + } + + return method.Outputs.Pack(true) +} + +func (p Precompile) sendNative(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address, callingContract common.Address, value *big.Int, readOnly bool) ([]byte, error) { + if readOnly { + return nil, errors.New("cannot call sendNative from staticcall") + } + if caller.Cmp(callingContract) != 0 { + return nil, errors.New("cannot delegatecall sendNative") + } + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + if value == nil || value.Sign() == 0 { + return nil, errors.New("set `value` field to non-zero to send") + } + + senderSeiAddr, ok := p.evmKeeper.GetSeiAddress(ctx, caller) + if !ok { + return nil, errors.New("invalid addr") + } + + receiverAddr, ok := (args[0]).(string) + if !ok || receiverAddr == "" { + return nil, errors.New("invalid addr") + } + + receiverSeiAddr, err := sdk.AccAddressFromBech32(receiverAddr) + if err != nil { + return nil, err + } + + usei, wei, err := pcommon.HandlePaymentUseiWei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderSeiAddr, value, p.bankKeeper) + if err != nil { + return nil, err + } + + if err := p.bankKeeper.SendCoinsAndWei(ctx, senderSeiAddr, receiverSeiAddr, usei, wei); err != nil { + return nil, err + } + + return method.Outputs.Pack(true) +} + +func (p Precompile) balance(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + + addr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + return nil, err + } + denom := args[1].(string) + if denom == "" { + return nil, errors.New("invalid denom") + } + + return method.Outputs.Pack(p.bankKeeper.GetBalance(ctx, addr, denom).Amount.BigInt()) +} + +func (p Precompile) all_balances(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + addr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + return nil, err + } + + coins := p.bankKeeper.GetAllBalances(ctx, addr) + + // convert to coin balance structs + coinBalances := make([]CoinBalance, 0, len(coins)) + + for _, coin := range coins { + coinBalances = append(coinBalances, CoinBalance{ + Amount: coin.Amount.BigInt(), + Denom: coin.Denom, + }) + } + + return method.Outputs.Pack(coinBalances) +} + +func (p Precompile) name(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + denom := args[0].(string) + metadata, found := p.bankKeeper.GetDenomMetaData(ctx, denom) + if !found { + return nil, fmt.Errorf("denom %s not found", denom) + } + return method.Outputs.Pack(metadata.Name) +} + +func (p Precompile) symbol(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + denom := args[0].(string) + metadata, found := p.bankKeeper.GetDenomMetaData(ctx, denom) + if !found { + return nil, fmt.Errorf("denom %s not found", denom) + } + return method.Outputs.Pack(metadata.Symbol) +} + +func (p Precompile) decimals(_ sdk.Context, method *abi.Method, _ []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + // all native tokens are integer-based, returns decimals for microdenom (usei) + return method.Outputs.Pack(uint8(0)) +} + +func (p Precompile) totalSupply(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + denom := args[0].(string) + coin := p.bankKeeper.GetSupply(ctx, denom) + return method.Outputs.Pack(coin.Amount.BigInt()) +} + +func (p Precompile) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { + addr := arg.(common.Address) + if addr == (common.Address{}) { + return nil, errors.New("invalid addr") + } + seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, addr) + if !found { + return nil, fmt.Errorf("EVM address %s is not associated", addr.Hex()) + } + return seiAddr, nil +} + +func (Precompile) IsTransaction(method string) bool { + switch method { + case SendMethod: + return true + case SendNativeMethod: + return true + default: + return false + } +} + +func (p Precompile) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("precompile", "bank") +} diff --git a/precompiles/bank/legacy/v552/abi.json b/precompiles/bank/legacy/v552/abi.json new file mode 100644 index 0000000000..844ac48943 --- /dev/null +++ b/precompiles/bank/legacy/v552/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"acc","type":"address"}],"name":"all_balances","outputs":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"denom","type":"string"}],"internalType":"struct IBank.Coin[]","name":"response","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"acc","type":"address"},{"internalType":"string","name":"denom","type":"string"}],"name":"balance","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"decimals","outputs":[{"internalType":"uint8","name":"response","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"name","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"fromAddress","type":"address"},{"internalType":"address","name":"toAddress","type":"address"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"send","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"toNativeAddress","type":"string"}],"name":"sendNative","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"supply","outputs":[{"internalType":"uint256","name":"response","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"symbol","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/bank/legacy/v552/bank.go b/precompiles/bank/legacy/v552/bank.go new file mode 100644 index 0000000000..70ca29b1fc --- /dev/null +++ b/precompiles/bank/legacy/v552/bank.go @@ -0,0 +1,380 @@ +package v552 + +import ( + "bytes" + "embed" + "errors" + "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/legacy/v555" + "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/x/evm/state" + "github.com/tendermint/tendermint/libs/log" +) + +const ( + SendMethod = "send" + SendNativeMethod = "sendNative" + BalanceMethod = "balance" + AllBalancesMethod = "all_balances" + NameMethod = "name" + SymbolMethod = "symbol" + DecimalsMethod = "decimals" + SupplyMethod = "supply" +) + +const ( + BankAddress = "0x0000000000000000000000000000000000001001" +) + +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 + +func GetABI() abi.ABI { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +type Precompile struct { + pcommon.Precompile + bankKeeper pcommon.BankKeeper + evmKeeper pcommon.EVMKeeper + address common.Address + + SendID []byte + SendNativeID []byte + BalanceID []byte + AllBalancesID []byte + NameID []byte + SymbolID []byte + DecimalsID []byte + SupplyID []byte +} + +type CoinBalance struct { + Amount *big.Int + Denom string +} + +func NewPrecompile(bankKeeper pcommon.BankKeeper, evmKeeper pcommon.EVMKeeper) (*Precompile, error) { + newAbi := GetABI() + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + bankKeeper: bankKeeper, + evmKeeper: evmKeeper, + address: common.HexToAddress(BankAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case SendMethod: + p.SendID = m.ID + case SendNativeMethod: + p.SendNativeID = m.ID + case BalanceMethod: + p.BalanceID = m.ID + case AllBalancesMethod: + p.AllBalancesID = m.ID + case NameMethod: + p.NameID = m.ID + case SymbolMethod: + p.SymbolID = m.ID + case DecimalsMethod: + p.DecimalsID = m.ID + case SupplyMethod: + p.SupplyID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return pcommon.UnknownMethodCallGas + } + + method, err := p.ABI.MethodById(methodID) + if err != nil { + // This should never happen since this method is going to fail during Run + return pcommon.UnknownMethodCallGas + } + + return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "bank" +} + +func (p Precompile) Run(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, value *big.Int, readOnly bool, _ bool) (bz []byte, err error) { + defer func() { + if err != nil { + evm.StateDB.(*state.DBImpl).SetPrecompileError(err) + } + }() + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, err + } + + switch method.Name { + case SendMethod: + return p.send(ctx, caller, method, args, value, readOnly) + case SendNativeMethod: + return p.sendNative(ctx, method, args, caller, callingContract, value, readOnly) + case BalanceMethod: + return p.balance(ctx, method, args, value) + case AllBalancesMethod: + return p.all_balances(ctx, method, args, value) + case NameMethod: + return p.name(ctx, method, args, value) + case SymbolMethod: + return p.symbol(ctx, method, args, value) + case DecimalsMethod: + return p.decimals(ctx, method, args, value) + case SupplyMethod: + return p.totalSupply(ctx, method, args, value) + } + return +} + +func (p Precompile) send(ctx sdk.Context, caller common.Address, method *abi.Method, args []interface{}, value *big.Int, readOnly bool) ([]byte, error) { + if readOnly { + return nil, errors.New("cannot call send from staticcall") + } + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 4); err != nil { + return nil, err + } + denom := args[2].(string) + if denom == "" { + return nil, errors.New("invalid denom") + } + pointer, _, exists := p.evmKeeper.GetERC20NativePointer(ctx, denom) + if !exists || pointer.Cmp(caller) != 0 { + return nil, fmt.Errorf("only pointer %s can send %s but got %s", pointer.Hex(), denom, caller.Hex()) + } + amount := args[3].(*big.Int) + if amount.Cmp(utils.Big0) == 0 { + // short circuit + return method.Outputs.Pack(true) + } + // TODO: it's possible to extend evm module's balance to handle non-usei tokens as well + senderSeiAddr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + return nil, err + } + receiverSeiAddr, err := p.accAddressFromArg(ctx, args[1]) + if err != nil { + return nil, err + } + + if err := p.bankKeeper.SendCoins(ctx, senderSeiAddr, receiverSeiAddr, sdk.NewCoins(sdk.NewCoin(denom, sdk.NewIntFromBigInt(amount)))); err != nil { + return nil, err + } + + return method.Outputs.Pack(true) +} + +func (p Precompile) sendNative(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address, callingContract common.Address, value *big.Int, readOnly bool) ([]byte, error) { + if readOnly { + return nil, errors.New("cannot call sendNative from staticcall") + } + if caller.Cmp(callingContract) != 0 { + return nil, errors.New("cannot delegatecall sendNative") + } + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + if value == nil || value.Sign() == 0 { + return nil, errors.New("set `value` field to non-zero to send") + } + + senderSeiAddr, ok := p.evmKeeper.GetSeiAddress(ctx, caller) + if !ok { + return nil, errors.New("invalid addr") + } + + receiverAddr, ok := (args[0]).(string) + if !ok || receiverAddr == "" { + return nil, errors.New("invalid addr") + } + + receiverSeiAddr, err := sdk.AccAddressFromBech32(receiverAddr) + if err != nil { + return nil, err + } + + usei, wei, err := pcommon.HandlePaymentUseiWei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderSeiAddr, value, p.bankKeeper) + if err != nil { + return nil, err + } + + if err := p.bankKeeper.SendCoinsAndWei(ctx, senderSeiAddr, receiverSeiAddr, usei, wei); err != nil { + return nil, err + } + + return method.Outputs.Pack(true) +} + +func (p Precompile) balance(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + + addr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + return nil, err + } + denom := args[1].(string) + if denom == "" { + return nil, errors.New("invalid denom") + } + + return method.Outputs.Pack(p.bankKeeper.GetBalance(ctx, addr, denom).Amount.BigInt()) +} + +func (p Precompile) all_balances(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + addr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + return nil, err + } + + coins := p.bankKeeper.GetAllBalances(ctx, addr) + + // convert to coin balance structs + coinBalances := make([]CoinBalance, 0, len(coins)) + + for _, coin := range coins { + coinBalances = append(coinBalances, CoinBalance{ + Amount: coin.Amount.BigInt(), + Denom: coin.Denom, + }) + } + + return method.Outputs.Pack(coinBalances) +} + +func (p Precompile) name(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + denom := args[0].(string) + metadata, found := p.bankKeeper.GetDenomMetaData(ctx, denom) + if !found { + return nil, fmt.Errorf("denom %s not found", denom) + } + return method.Outputs.Pack(metadata.Name) +} + +func (p Precompile) symbol(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + denom := args[0].(string) + metadata, found := p.bankKeeper.GetDenomMetaData(ctx, denom) + if !found { + return nil, fmt.Errorf("denom %s not found", denom) + } + return method.Outputs.Pack(metadata.Symbol) +} + +func (p Precompile) decimals(_ sdk.Context, method *abi.Method, _ []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + // all native tokens are integer-based, returns decimals for microdenom (usei) + return method.Outputs.Pack(uint8(0)) +} + +func (p Precompile) totalSupply(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + denom := args[0].(string) + coin := p.bankKeeper.GetSupply(ctx, denom) + return method.Outputs.Pack(coin.Amount.BigInt()) +} + +func (p Precompile) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { + addr := arg.(common.Address) + if addr == (common.Address{}) { + return nil, errors.New("invalid addr") + } + seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, addr) + if !found { + return nil, fmt.Errorf("EVM address %s is not associated", addr.Hex()) + } + return seiAddr, nil +} + +func (Precompile) IsTransaction(method string) bool { + switch method { + case SendMethod: + return true + case SendNativeMethod: + return true + default: + return false + } +} + +func (p Precompile) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("precompile", "bank") +} diff --git a/precompiles/bank/legacy/v555/abi.json b/precompiles/bank/legacy/v555/abi.json new file mode 100644 index 0000000000..844ac48943 --- /dev/null +++ b/precompiles/bank/legacy/v555/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"acc","type":"address"}],"name":"all_balances","outputs":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"denom","type":"string"}],"internalType":"struct IBank.Coin[]","name":"response","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"acc","type":"address"},{"internalType":"string","name":"denom","type":"string"}],"name":"balance","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"decimals","outputs":[{"internalType":"uint8","name":"response","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"name","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"fromAddress","type":"address"},{"internalType":"address","name":"toAddress","type":"address"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"send","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"toNativeAddress","type":"string"}],"name":"sendNative","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"supply","outputs":[{"internalType":"uint256","name":"response","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"symbol","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/bank/legacy/v555/bank.go b/precompiles/bank/legacy/v555/bank.go new file mode 100644 index 0000000000..b32fdf3314 --- /dev/null +++ b/precompiles/bank/legacy/v555/bank.go @@ -0,0 +1,380 @@ +package v555 + +import ( + "bytes" + "embed" + "errors" + "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/legacy/v555" + "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/x/evm/state" + "github.com/tendermint/tendermint/libs/log" +) + +const ( + SendMethod = "send" + SendNativeMethod = "sendNative" + BalanceMethod = "balance" + AllBalancesMethod = "all_balances" + NameMethod = "name" + SymbolMethod = "symbol" + DecimalsMethod = "decimals" + SupplyMethod = "supply" +) + +const ( + BankAddress = "0x0000000000000000000000000000000000001001" +) + +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 + +func GetABI() abi.ABI { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +type Precompile struct { + pcommon.Precompile + bankKeeper pcommon.BankKeeper + evmKeeper pcommon.EVMKeeper + address common.Address + + SendID []byte + SendNativeID []byte + BalanceID []byte + AllBalancesID []byte + NameID []byte + SymbolID []byte + DecimalsID []byte + SupplyID []byte +} + +type CoinBalance struct { + Amount *big.Int + Denom string +} + +func NewPrecompile(bankKeeper pcommon.BankKeeper, evmKeeper pcommon.EVMKeeper) (*Precompile, error) { + newAbi := GetABI() + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + bankKeeper: bankKeeper, + evmKeeper: evmKeeper, + address: common.HexToAddress(BankAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case SendMethod: + p.SendID = m.ID + case SendNativeMethod: + p.SendNativeID = m.ID + case BalanceMethod: + p.BalanceID = m.ID + case AllBalancesMethod: + p.AllBalancesID = m.ID + case NameMethod: + p.NameID = m.ID + case SymbolMethod: + p.SymbolID = m.ID + case DecimalsMethod: + p.DecimalsID = m.ID + case SupplyMethod: + p.SupplyID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return pcommon.UnknownMethodCallGas + } + + method, err := p.ABI.MethodById(methodID) + if err != nil { + // This should never happen since this method is going to fail during Run + return pcommon.UnknownMethodCallGas + } + + return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "bank" +} + +func (p Precompile) Run(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, value *big.Int, readOnly bool, _ bool) (bz []byte, err error) { + defer func() { + if err != nil { + evm.StateDB.(*state.DBImpl).SetPrecompileError(err) + } + }() + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, err + } + + switch method.Name { + case SendMethod: + return p.send(ctx, caller, method, args, value, readOnly) + case SendNativeMethod: + return p.sendNative(ctx, method, args, caller, callingContract, value, readOnly) + case BalanceMethod: + return p.balance(ctx, method, args, value) + case AllBalancesMethod: + return p.all_balances(ctx, method, args, value) + case NameMethod: + return p.name(ctx, method, args, value) + case SymbolMethod: + return p.symbol(ctx, method, args, value) + case DecimalsMethod: + return p.decimals(ctx, method, args, value) + case SupplyMethod: + return p.totalSupply(ctx, method, args, value) + } + return +} + +func (p Precompile) send(ctx sdk.Context, caller common.Address, method *abi.Method, args []interface{}, value *big.Int, readOnly bool) ([]byte, error) { + if readOnly { + return nil, errors.New("cannot call send from staticcall") + } + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 4); err != nil { + return nil, err + } + denom := args[2].(string) + if denom == "" { + return nil, errors.New("invalid denom") + } + pointer, _, exists := p.evmKeeper.GetERC20NativePointer(ctx, denom) + if !exists || pointer.Cmp(caller) != 0 { + return nil, fmt.Errorf("only pointer %s can send %s but got %s", pointer.Hex(), denom, caller.Hex()) + } + amount := args[3].(*big.Int) + if amount.Cmp(utils.Big0) == 0 { + // short circuit + return method.Outputs.Pack(true) + } + senderSeiAddr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + return nil, err + } + receiverSeiAddr, err := p.accAddressFromArg(ctx, args[1]) + if err != nil { + return nil, err + } + + if err := p.bankKeeper.SendCoins(ctx, senderSeiAddr, receiverSeiAddr, sdk.NewCoins(sdk.NewCoin(denom, sdk.NewIntFromBigInt(amount)))); err != nil { + return nil, err + } + + return method.Outputs.Pack(true) +} + +func (p Precompile) sendNative(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address, callingContract common.Address, value *big.Int, readOnly bool) ([]byte, error) { + if readOnly { + return nil, errors.New("cannot call sendNative from staticcall") + } + if caller.Cmp(callingContract) != 0 { + return nil, errors.New("cannot delegatecall sendNative") + } + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + if value == nil || value.Sign() == 0 { + return nil, errors.New("set `value` field to non-zero to send") + } + + senderSeiAddr, ok := p.evmKeeper.GetSeiAddress(ctx, caller) + if !ok { + return nil, errors.New("invalid addr") + } + + receiverAddr, ok := (args[0]).(string) + if !ok || receiverAddr == "" { + return nil, errors.New("invalid addr") + } + + receiverSeiAddr, err := sdk.AccAddressFromBech32(receiverAddr) + if err != nil { + return nil, err + } + + usei, wei, err := pcommon.HandlePaymentUseiWei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderSeiAddr, value, p.bankKeeper) + if err != nil { + return nil, err + } + + if err := p.bankKeeper.SendCoinsAndWei(ctx, senderSeiAddr, receiverSeiAddr, usei, wei); err != nil { + return nil, err + } + + return method.Outputs.Pack(true) +} + +func (p Precompile) balance(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + + addr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + return nil, err + } + denom := args[1].(string) + if denom == "" { + return nil, errors.New("invalid denom") + } + + return method.Outputs.Pack(p.bankKeeper.GetBalance(ctx, addr, denom).Amount.BigInt()) +} + +func (p Precompile) all_balances(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + addr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + return nil, err + } + + coins := p.bankKeeper.GetAllBalances(ctx, addr) + + // convert to coin balance structs + coinBalances := make([]CoinBalance, 0, len(coins)) + + for _, coin := range coins { + coinBalances = append(coinBalances, CoinBalance{ + Amount: coin.Amount.BigInt(), + Denom: coin.Denom, + }) + } + + return method.Outputs.Pack(coinBalances) +} + +func (p Precompile) name(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + denom := args[0].(string) + metadata, found := p.bankKeeper.GetDenomMetaData(ctx, denom) + if !found { + return nil, fmt.Errorf("denom %s not found", denom) + } + return method.Outputs.Pack(metadata.Name) +} + +func (p Precompile) symbol(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + denom := args[0].(string) + metadata, found := p.bankKeeper.GetDenomMetaData(ctx, denom) + if !found { + return nil, fmt.Errorf("denom %s not found", denom) + } + return method.Outputs.Pack(metadata.Symbol) +} + +func (p Precompile) decimals(_ sdk.Context, method *abi.Method, _ []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + // all native tokens are integer-based, returns decimals for microdenom (usei) + return method.Outputs.Pack(uint8(0)) +} + +func (p Precompile) totalSupply(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + denom := args[0].(string) + coin := p.bankKeeper.GetSupply(ctx, denom) + return method.Outputs.Pack(coin.Amount.BigInt()) +} + +func (p Precompile) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { + addr := arg.(common.Address) + if addr == (common.Address{}) { + return nil, errors.New("invalid addr") + } + seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, addr) + if !found { + // return the casted version instead + return sdk.AccAddress(addr[:]), nil + } + return seiAddr, nil +} + +func (Precompile) IsTransaction(method string) bool { + switch method { + case SendMethod: + return true + case SendNativeMethod: + return true + default: + return false + } +} + +func (p Precompile) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("precompile", "bank") +} diff --git a/precompiles/bank/legacy/v562/abi.json b/precompiles/bank/legacy/v562/abi.json new file mode 100644 index 0000000000..844ac48943 --- /dev/null +++ b/precompiles/bank/legacy/v562/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"acc","type":"address"}],"name":"all_balances","outputs":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"denom","type":"string"}],"internalType":"struct IBank.Coin[]","name":"response","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"acc","type":"address"},{"internalType":"string","name":"denom","type":"string"}],"name":"balance","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"decimals","outputs":[{"internalType":"uint8","name":"response","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"name","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"fromAddress","type":"address"},{"internalType":"address","name":"toAddress","type":"address"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"send","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"toNativeAddress","type":"string"}],"name":"sendNative","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"supply","outputs":[{"internalType":"uint256","name":"response","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"symbol","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/bank/legacy/v562/bank.go b/precompiles/bank/legacy/v562/bank.go new file mode 100644 index 0000000000..6dd12128aa --- /dev/null +++ b/precompiles/bank/legacy/v562/bank.go @@ -0,0 +1,353 @@ +package v562 + +import ( + "bytes" + "embed" + "errors" + "fmt" + "math/big" + + "github.com/cosmos/cosmos-sdk/telemetry" + 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/legacy/v562" + "github.com/sei-protocol/sei-chain/utils" + "github.com/tendermint/tendermint/libs/log" +) + +const ( + SendMethod = "send" + SendNativeMethod = "sendNative" + BalanceMethod = "balance" + AllBalancesMethod = "all_balances" + NameMethod = "name" + SymbolMethod = "symbol" + DecimalsMethod = "decimals" + SupplyMethod = "supply" +) + +const ( + BankAddress = "0x0000000000000000000000000000000000001001" +) + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +func GetABI() abi.ABI { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +type PrecompileExecutor struct { + accountKeeper pcommon.AccountKeeper + bankKeeper pcommon.BankKeeper + evmKeeper pcommon.EVMKeeper + address common.Address + + SendID []byte + SendNativeID []byte + BalanceID []byte + AllBalancesID []byte + NameID []byte + SymbolID []byte + DecimalsID []byte + SupplyID []byte +} + +type CoinBalance struct { + Amount *big.Int + Denom string +} + +func NewPrecompile(bankKeeper pcommon.BankKeeper, evmKeeper pcommon.EVMKeeper, accountKeeper pcommon.AccountKeeper) (*pcommon.Precompile, error) { + newAbi := GetABI() + p := &PrecompileExecutor{ + bankKeeper: bankKeeper, + evmKeeper: evmKeeper, + accountKeeper: accountKeeper, + address: common.HexToAddress(BankAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case SendMethod: + p.SendID = m.ID + case SendNativeMethod: + p.SendNativeID = m.ID + case BalanceMethod: + p.BalanceID = m.ID + case AllBalancesMethod: + p.AllBalancesID = m.ID + case NameMethod: + p.NameID = m.ID + case SymbolMethod: + p.SymbolID = m.ID + case DecimalsMethod: + p.DecimalsID = m.ID + case SupplyMethod: + p.SupplyID = m.ID + } + } + + return pcommon.NewPrecompile(newAbi, p, p.address, "bank"), nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p PrecompileExecutor) RequiredGas(input []byte, method *abi.Method) uint64 { + return pcommon.DefaultGasCost(input, p.IsTransaction(method.Name)) +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM) (bz []byte, err error) { + switch method.Name { + case SendMethod: + return p.send(ctx, caller, method, args, value, readOnly) + case SendNativeMethod: + return p.sendNative(ctx, method, args, caller, callingContract, value, readOnly) + case BalanceMethod: + return p.balance(ctx, method, args, value) + case AllBalancesMethod: + return p.all_balances(ctx, method, args, value) + case NameMethod: + return p.name(ctx, method, args, value) + case SymbolMethod: + return p.symbol(ctx, method, args, value) + case DecimalsMethod: + return p.decimals(ctx, method, args, value) + case SupplyMethod: + return p.totalSupply(ctx, method, args, value) + } + return +} + +func (p PrecompileExecutor) send(ctx sdk.Context, caller common.Address, method *abi.Method, args []interface{}, value *big.Int, readOnly bool) ([]byte, error) { + if readOnly { + return nil, errors.New("cannot call send from staticcall") + } + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 4); err != nil { + return nil, err + } + denom := args[2].(string) + if denom == "" { + return nil, errors.New("invalid denom") + } + pointer, _, exists := p.evmKeeper.GetERC20NativePointer(ctx, denom) + if !exists || pointer.Cmp(caller) != 0 { + return nil, fmt.Errorf("only pointer %s can send %s but got %s", pointer.Hex(), denom, caller.Hex()) + } + amount := args[3].(*big.Int) + if amount.Cmp(utils.Big0) == 0 { + // short circuit + return method.Outputs.Pack(true) + } + senderSeiAddr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + return nil, err + } + receiverSeiAddr, err := p.accAddressFromArg(ctx, args[1]) + if err != nil { + return nil, err + } + + if err := p.bankKeeper.SendCoins(ctx, senderSeiAddr, receiverSeiAddr, sdk.NewCoins(sdk.NewCoin(denom, sdk.NewIntFromBigInt(amount)))); err != nil { + return nil, err + } + + return method.Outputs.Pack(true) +} + +func (p PrecompileExecutor) sendNative(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address, callingContract common.Address, value *big.Int, readOnly bool) ([]byte, error) { + if readOnly { + return nil, errors.New("cannot call sendNative from staticcall") + } + if caller.Cmp(callingContract) != 0 { + return nil, errors.New("cannot delegatecall sendNative") + } + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + if value == nil || value.Sign() == 0 { + return nil, errors.New("set `value` field to non-zero to send") + } + + senderSeiAddr, ok := p.evmKeeper.GetSeiAddress(ctx, caller) + if !ok { + return nil, errors.New("invalid addr") + } + + receiverAddr, ok := (args[0]).(string) + if !ok || receiverAddr == "" { + return nil, errors.New("invalid addr") + } + + receiverSeiAddr, err := sdk.AccAddressFromBech32(receiverAddr) + if err != nil { + return nil, err + } + + usei, wei, err := pcommon.HandlePaymentUseiWei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderSeiAddr, value, p.bankKeeper) + if err != nil { + return nil, err + } + + if err := p.bankKeeper.SendCoinsAndWei(ctx, senderSeiAddr, receiverSeiAddr, usei, wei); err != nil { + return nil, err + } + accExists := p.accountKeeper.HasAccount(ctx, receiverSeiAddr) + if !accExists { + defer telemetry.IncrCounter(1, "new", "account") + p.accountKeeper.SetAccount(ctx, p.accountKeeper.NewAccountWithAddress(ctx, receiverSeiAddr)) + } + + return method.Outputs.Pack(true) +} + +func (p PrecompileExecutor) balance(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + + addr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + return nil, err + } + denom := args[1].(string) + if denom == "" { + return nil, errors.New("invalid denom") + } + + return method.Outputs.Pack(p.bankKeeper.GetBalance(ctx, addr, denom).Amount.BigInt()) +} + +func (p PrecompileExecutor) all_balances(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + addr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + return nil, err + } + + coins := p.bankKeeper.GetAllBalances(ctx, addr) + + // convert to coin balance structs + coinBalances := make([]CoinBalance, 0, len(coins)) + + for _, coin := range coins { + coinBalances = append(coinBalances, CoinBalance{ + Amount: coin.Amount.BigInt(), + Denom: coin.Denom, + }) + } + + return method.Outputs.Pack(coinBalances) +} + +func (p PrecompileExecutor) name(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + denom := args[0].(string) + metadata, found := p.bankKeeper.GetDenomMetaData(ctx, denom) + if !found { + return nil, fmt.Errorf("denom %s not found", denom) + } + return method.Outputs.Pack(metadata.Name) +} + +func (p PrecompileExecutor) symbol(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + denom := args[0].(string) + metadata, found := p.bankKeeper.GetDenomMetaData(ctx, denom) + if !found { + return nil, fmt.Errorf("denom %s not found", denom) + } + return method.Outputs.Pack(metadata.Symbol) +} + +func (p PrecompileExecutor) decimals(_ sdk.Context, method *abi.Method, _ []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + // all native tokens are integer-based, returns decimals for microdenom (usei) + return method.Outputs.Pack(uint8(0)) +} + +func (p PrecompileExecutor) totalSupply(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + denom := args[0].(string) + coin := p.bankKeeper.GetSupply(ctx, denom) + return method.Outputs.Pack(coin.Amount.BigInt()) +} + +func (p PrecompileExecutor) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { + addr := arg.(common.Address) + if addr == (common.Address{}) { + return nil, errors.New("invalid addr") + } + seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, addr) + if !found { + // return the casted version instead + return sdk.AccAddress(addr[:]), nil + } + return seiAddr, nil +} + +func (PrecompileExecutor) IsTransaction(method string) bool { + switch method { + case SendMethod: + return true + case SendNativeMethod: + return true + default: + return false + } +} + +func (p PrecompileExecutor) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("precompile", "bank") +} diff --git a/precompiles/bank/legacy/v580/abi.json b/precompiles/bank/legacy/v580/abi.json new file mode 100644 index 0000000000..844ac48943 --- /dev/null +++ b/precompiles/bank/legacy/v580/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"acc","type":"address"}],"name":"all_balances","outputs":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"denom","type":"string"}],"internalType":"struct IBank.Coin[]","name":"response","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"acc","type":"address"},{"internalType":"string","name":"denom","type":"string"}],"name":"balance","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"decimals","outputs":[{"internalType":"uint8","name":"response","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"name","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"fromAddress","type":"address"},{"internalType":"address","name":"toAddress","type":"address"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"send","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"toNativeAddress","type":"string"}],"name":"sendNative","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"supply","outputs":[{"internalType":"uint256","name":"response","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"symbol","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/bank/legacy/v580/bank.go b/precompiles/bank/legacy/v580/bank.go new file mode 100644 index 0000000000..8c4e725158 --- /dev/null +++ b/precompiles/bank/legacy/v580/bank.go @@ -0,0 +1,339 @@ +package v580 + +import ( + "embed" + "errors" + "fmt" + "math/big" + + "github.com/cosmos/cosmos-sdk/telemetry" + 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/legacy/v580" + "github.com/sei-protocol/sei-chain/utils" + "github.com/tendermint/tendermint/libs/log" +) + +const ( + SendMethod = "send" + SendNativeMethod = "sendNative" + BalanceMethod = "balance" + AllBalancesMethod = "all_balances" + NameMethod = "name" + SymbolMethod = "symbol" + DecimalsMethod = "decimals" + SupplyMethod = "supply" +) + +const ( + BankAddress = "0x0000000000000000000000000000000000001001" +) + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + accountKeeper pcommon.AccountKeeper + bankKeeper pcommon.BankKeeper + evmKeeper pcommon.EVMKeeper + address common.Address + + SendID []byte + SendNativeID []byte + BalanceID []byte + AllBalancesID []byte + NameID []byte + SymbolID []byte + DecimalsID []byte + SupplyID []byte +} + +type CoinBalance struct { + Amount *big.Int + Denom string +} + +func NewPrecompile(bankKeeper pcommon.BankKeeper, evmKeeper pcommon.EVMKeeper, accountKeeper pcommon.AccountKeeper) (*pcommon.Precompile, error) { + newAbi := pcommon.MustGetABI(f, "abi.json") + p := &PrecompileExecutor{ + bankKeeper: bankKeeper, + evmKeeper: evmKeeper, + accountKeeper: accountKeeper, + address: common.HexToAddress(BankAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case SendMethod: + p.SendID = m.ID + case SendNativeMethod: + p.SendNativeID = m.ID + case BalanceMethod: + p.BalanceID = m.ID + case AllBalancesMethod: + p.AllBalancesID = m.ID + case NameMethod: + p.NameID = m.ID + case SymbolMethod: + p.SymbolID = m.ID + case DecimalsMethod: + p.DecimalsID = m.ID + case SupplyMethod: + p.SupplyID = m.ID + } + } + + return pcommon.NewPrecompile(newAbi, p, p.address, "bank"), nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p PrecompileExecutor) RequiredGas(input []byte, method *abi.Method) uint64 { + return pcommon.DefaultGasCost(input, p.IsTransaction(method.Name)) +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM) (bz []byte, err error) { + switch method.Name { + case SendMethod: + return p.send(ctx, caller, method, args, value, readOnly) + case SendNativeMethod: + return p.sendNative(ctx, method, args, caller, callingContract, value, readOnly) + case BalanceMethod: + return p.balance(ctx, method, args, value) + case AllBalancesMethod: + return p.all_balances(ctx, method, args, value) + case NameMethod: + return p.name(ctx, method, args, value) + case SymbolMethod: + return p.symbol(ctx, method, args, value) + case DecimalsMethod: + return p.decimals(ctx, method, args, value) + case SupplyMethod: + return p.totalSupply(ctx, method, args, value) + } + return +} + +func (p PrecompileExecutor) send(ctx sdk.Context, caller common.Address, method *abi.Method, args []interface{}, value *big.Int, readOnly bool) ([]byte, error) { + if readOnly { + return nil, errors.New("cannot call send from staticcall") + } + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 4); err != nil { + return nil, err + } + denom := args[2].(string) + if denom == "" { + return nil, errors.New("invalid denom") + } + pointer, _, exists := p.evmKeeper.GetERC20NativePointer(ctx, denom) + if !exists || pointer.Cmp(caller) != 0 { + return nil, fmt.Errorf("only pointer %s can send %s but got %s", pointer.Hex(), denom, caller.Hex()) + } + amount := args[3].(*big.Int) + if amount.Cmp(utils.Big0) == 0 { + // short circuit + return method.Outputs.Pack(true) + } + senderSeiAddr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + return nil, err + } + receiverSeiAddr, err := p.accAddressFromArg(ctx, args[1]) + if err != nil { + return nil, err + } + + if err := p.bankKeeper.SendCoins(ctx, senderSeiAddr, receiverSeiAddr, sdk.NewCoins(sdk.NewCoin(denom, sdk.NewIntFromBigInt(amount)))); err != nil { + return nil, err + } + + return method.Outputs.Pack(true) +} + +func (p PrecompileExecutor) sendNative(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address, callingContract common.Address, value *big.Int, readOnly bool) ([]byte, error) { + if readOnly { + return nil, errors.New("cannot call sendNative from staticcall") + } + if caller.Cmp(callingContract) != 0 { + return nil, errors.New("cannot delegatecall sendNative") + } + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + if value == nil || value.Sign() == 0 { + return nil, errors.New("set `value` field to non-zero to send") + } + + senderSeiAddr, ok := p.evmKeeper.GetSeiAddress(ctx, caller) + if !ok { + return nil, errors.New("invalid addr") + } + + receiverAddr, ok := (args[0]).(string) + if !ok || receiverAddr == "" { + return nil, errors.New("invalid addr") + } + + receiverSeiAddr, err := sdk.AccAddressFromBech32(receiverAddr) + if err != nil { + return nil, err + } + + usei, wei, err := pcommon.HandlePaymentUseiWei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderSeiAddr, value, p.bankKeeper) + if err != nil { + return nil, err + } + + if err := p.bankKeeper.SendCoinsAndWei(ctx, senderSeiAddr, receiverSeiAddr, usei, wei); err != nil { + return nil, err + } + accExists := p.accountKeeper.HasAccount(ctx, receiverSeiAddr) + if !accExists { + defer telemetry.IncrCounter(1, "new", "account") + p.accountKeeper.SetAccount(ctx, p.accountKeeper.NewAccountWithAddress(ctx, receiverSeiAddr)) + } + + return method.Outputs.Pack(true) +} + +func (p PrecompileExecutor) balance(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + + addr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + return nil, err + } + denom := args[1].(string) + if denom == "" { + return nil, errors.New("invalid denom") + } + + return method.Outputs.Pack(p.bankKeeper.GetBalance(ctx, addr, denom).Amount.BigInt()) +} + +func (p PrecompileExecutor) all_balances(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + addr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + return nil, err + } + + coins := p.bankKeeper.GetAllBalances(ctx, addr) + + // convert to coin balance structs + coinBalances := make([]CoinBalance, 0, len(coins)) + + for _, coin := range coins { + coinBalances = append(coinBalances, CoinBalance{ + Amount: coin.Amount.BigInt(), + Denom: coin.Denom, + }) + } + + return method.Outputs.Pack(coinBalances) +} + +func (p PrecompileExecutor) name(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + denom := args[0].(string) + metadata, found := p.bankKeeper.GetDenomMetaData(ctx, denom) + if !found { + return nil, fmt.Errorf("denom %s not found", denom) + } + return method.Outputs.Pack(metadata.Name) +} + +func (p PrecompileExecutor) symbol(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + denom := args[0].(string) + metadata, found := p.bankKeeper.GetDenomMetaData(ctx, denom) + if !found { + return nil, fmt.Errorf("denom %s not found", denom) + } + return method.Outputs.Pack(metadata.Symbol) +} + +func (p PrecompileExecutor) decimals(_ sdk.Context, method *abi.Method, _ []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + // all native tokens are integer-based, returns decimals for microdenom (usei) + return method.Outputs.Pack(uint8(0)) +} + +func (p PrecompileExecutor) totalSupply(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + denom := args[0].(string) + coin := p.bankKeeper.GetSupply(ctx, denom) + return method.Outputs.Pack(coin.Amount.BigInt()) +} + +func (p PrecompileExecutor) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { + addr := arg.(common.Address) + if addr == (common.Address{}) { + return nil, errors.New("invalid addr") + } + seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, addr) + if !found { + // return the casted version instead + return sdk.AccAddress(addr[:]), nil + } + return seiAddr, nil +} + +func (PrecompileExecutor) IsTransaction(method string) bool { + switch method { + case SendMethod: + return true + case SendNativeMethod: + return true + default: + return false + } +} + +func (p PrecompileExecutor) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("precompile", "bank") +} diff --git a/precompiles/bank/legacy/v600/abi.json b/precompiles/bank/legacy/v600/abi.json new file mode 100644 index 0000000000..844ac48943 --- /dev/null +++ b/precompiles/bank/legacy/v600/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"acc","type":"address"}],"name":"all_balances","outputs":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"denom","type":"string"}],"internalType":"struct IBank.Coin[]","name":"response","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"acc","type":"address"},{"internalType":"string","name":"denom","type":"string"}],"name":"balance","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"decimals","outputs":[{"internalType":"uint8","name":"response","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"name","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"fromAddress","type":"address"},{"internalType":"address","name":"toAddress","type":"address"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"send","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"toNativeAddress","type":"string"}],"name":"sendNative","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"supply","outputs":[{"internalType":"uint256","name":"response","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"symbol","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/bank/legacy/v600/bank.go b/precompiles/bank/legacy/v600/bank.go new file mode 100644 index 0000000000..c1fa1c8ec5 --- /dev/null +++ b/precompiles/bank/legacy/v600/bank.go @@ -0,0 +1,339 @@ +package v600 + +import ( + "embed" + "errors" + "fmt" + "math/big" + + "github.com/cosmos/cosmos-sdk/telemetry" + 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/legacy/v600" + "github.com/sei-protocol/sei-chain/utils" + "github.com/tendermint/tendermint/libs/log" +) + +const ( + SendMethod = "send" + SendNativeMethod = "sendNative" + BalanceMethod = "balance" + AllBalancesMethod = "all_balances" + NameMethod = "name" + SymbolMethod = "symbol" + DecimalsMethod = "decimals" + SupplyMethod = "supply" +) + +const ( + BankAddress = "0x0000000000000000000000000000000000001001" +) + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + accountKeeper pcommon.AccountKeeper + bankKeeper pcommon.BankKeeper + evmKeeper pcommon.EVMKeeper + address common.Address + + SendID []byte + SendNativeID []byte + BalanceID []byte + AllBalancesID []byte + NameID []byte + SymbolID []byte + DecimalsID []byte + SupplyID []byte +} + +type CoinBalance struct { + Amount *big.Int + Denom string +} + +func NewPrecompile(bankKeeper pcommon.BankKeeper, evmKeeper pcommon.EVMKeeper, accountKeeper pcommon.AccountKeeper) (*pcommon.Precompile, error) { + newAbi := pcommon.MustGetABI(f, "abi.json") + p := &PrecompileExecutor{ + bankKeeper: bankKeeper, + evmKeeper: evmKeeper, + accountKeeper: accountKeeper, + address: common.HexToAddress(BankAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case SendMethod: + p.SendID = m.ID + case SendNativeMethod: + p.SendNativeID = m.ID + case BalanceMethod: + p.BalanceID = m.ID + case AllBalancesMethod: + p.AllBalancesID = m.ID + case NameMethod: + p.NameID = m.ID + case SymbolMethod: + p.SymbolID = m.ID + case DecimalsMethod: + p.DecimalsID = m.ID + case SupplyMethod: + p.SupplyID = m.ID + } + } + + return pcommon.NewPrecompile(newAbi, p, p.address, "bank"), nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p PrecompileExecutor) RequiredGas(input []byte, method *abi.Method) uint64 { + return pcommon.DefaultGasCost(input, p.IsTransaction(method.Name)) +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM) (bz []byte, err error) { + switch method.Name { + case SendMethod: + return p.send(ctx, caller, method, args, value, readOnly) + case SendNativeMethod: + return p.sendNative(ctx, method, args, caller, callingContract, value, readOnly) + case BalanceMethod: + return p.balance(ctx, method, args, value) + case AllBalancesMethod: + return p.all_balances(ctx, method, args, value) + case NameMethod: + return p.name(ctx, method, args, value) + case SymbolMethod: + return p.symbol(ctx, method, args, value) + case DecimalsMethod: + return p.decimals(ctx, method, args, value) + case SupplyMethod: + return p.totalSupply(ctx, method, args, value) + } + return +} + +func (p PrecompileExecutor) send(ctx sdk.Context, caller common.Address, method *abi.Method, args []interface{}, value *big.Int, readOnly bool) ([]byte, error) { + if readOnly { + return nil, errors.New("cannot call send from staticcall") + } + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 4); err != nil { + return nil, err + } + denom := args[2].(string) + if denom == "" { + return nil, errors.New("invalid denom") + } + pointer, _, exists := p.evmKeeper.GetERC20NativePointer(ctx, denom) + if !exists || pointer.Cmp(caller) != 0 { + return nil, fmt.Errorf("only pointer %s can send %s but got %s", pointer.Hex(), denom, caller.Hex()) + } + amount := args[3].(*big.Int) + if amount.Cmp(utils.Big0) == 0 { + // short circuit + return method.Outputs.Pack(true) + } + senderSeiAddr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + return nil, err + } + receiverSeiAddr, err := p.accAddressFromArg(ctx, args[1]) + if err != nil { + return nil, err + } + + if err := p.bankKeeper.SendCoins(ctx, senderSeiAddr, receiverSeiAddr, sdk.NewCoins(sdk.NewCoin(denom, sdk.NewIntFromBigInt(amount)))); err != nil { + return nil, err + } + + return method.Outputs.Pack(true) +} + +func (p PrecompileExecutor) sendNative(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address, callingContract common.Address, value *big.Int, readOnly bool) ([]byte, error) { + if readOnly { + return nil, errors.New("cannot call sendNative from staticcall") + } + if ctx.EVMPrecompileCalledFromDelegateCall() { + return nil, errors.New("cannot delegatecall sendNative") + } + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + if value == nil || value.Sign() == 0 { + return nil, errors.New("set `value` field to non-zero to send") + } + + senderSeiAddr, ok := p.evmKeeper.GetSeiAddress(ctx, caller) + if !ok { + return nil, errors.New("invalid addr") + } + + receiverAddr, ok := (args[0]).(string) + if !ok || receiverAddr == "" { + return nil, errors.New("invalid addr") + } + + receiverSeiAddr, err := sdk.AccAddressFromBech32(receiverAddr) + if err != nil { + return nil, err + } + + usei, wei, err := pcommon.HandlePaymentUseiWei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderSeiAddr, value, p.bankKeeper) + if err != nil { + return nil, err + } + + if err := p.bankKeeper.SendCoinsAndWei(ctx, senderSeiAddr, receiverSeiAddr, usei, wei); err != nil { + return nil, err + } + accExists := p.accountKeeper.HasAccount(ctx, receiverSeiAddr) + if !accExists { + defer telemetry.IncrCounter(1, "new", "account") + p.accountKeeper.SetAccount(ctx, p.accountKeeper.NewAccountWithAddress(ctx, receiverSeiAddr)) + } + + return method.Outputs.Pack(true) +} + +func (p PrecompileExecutor) balance(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + + addr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + return nil, err + } + denom := args[1].(string) + if denom == "" { + return nil, errors.New("invalid denom") + } + + return method.Outputs.Pack(p.bankKeeper.GetBalance(ctx, addr, denom).Amount.BigInt()) +} + +func (p PrecompileExecutor) all_balances(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + addr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + return nil, err + } + + coins := p.bankKeeper.GetAllBalances(ctx, addr) + + // convert to coin balance structs + coinBalances := make([]CoinBalance, 0, len(coins)) + + for _, coin := range coins { + coinBalances = append(coinBalances, CoinBalance{ + Amount: coin.Amount.BigInt(), + Denom: coin.Denom, + }) + } + + return method.Outputs.Pack(coinBalances) +} + +func (p PrecompileExecutor) name(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + denom := args[0].(string) + metadata, found := p.bankKeeper.GetDenomMetaData(ctx, denom) + if !found { + return nil, fmt.Errorf("denom %s not found", denom) + } + return method.Outputs.Pack(metadata.Name) +} + +func (p PrecompileExecutor) symbol(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + denom := args[0].(string) + metadata, found := p.bankKeeper.GetDenomMetaData(ctx, denom) + if !found { + return nil, fmt.Errorf("denom %s not found", denom) + } + return method.Outputs.Pack(metadata.Symbol) +} + +func (p PrecompileExecutor) decimals(_ sdk.Context, method *abi.Method, _ []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + // all native tokens are integer-based, returns decimals for microdenom (usei) + return method.Outputs.Pack(uint8(0)) +} + +func (p PrecompileExecutor) totalSupply(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + + denom := args[0].(string) + coin := p.bankKeeper.GetSupply(ctx, denom) + return method.Outputs.Pack(coin.Amount.BigInt()) +} + +func (p PrecompileExecutor) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { + addr := arg.(common.Address) + if addr == (common.Address{}) { + return nil, errors.New("invalid addr") + } + seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, addr) + if !found { + // return the casted version instead + return sdk.AccAddress(addr[:]), nil + } + return seiAddr, nil +} + +func (PrecompileExecutor) IsTransaction(method string) bool { + switch method { + case SendMethod: + return true + case SendNativeMethod: + return true + default: + return false + } +} + +func (p PrecompileExecutor) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("precompile", "bank") +} diff --git a/precompiles/bank/legacy/v602/abi.json b/precompiles/bank/legacy/v602/abi.json new file mode 100644 index 0000000000..844ac48943 --- /dev/null +++ b/precompiles/bank/legacy/v602/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"acc","type":"address"}],"name":"all_balances","outputs":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"denom","type":"string"}],"internalType":"struct IBank.Coin[]","name":"response","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"acc","type":"address"},{"internalType":"string","name":"denom","type":"string"}],"name":"balance","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"decimals","outputs":[{"internalType":"uint8","name":"response","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"name","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"fromAddress","type":"address"},{"internalType":"address","name":"toAddress","type":"address"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"send","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"toNativeAddress","type":"string"}],"name":"sendNative","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"supply","outputs":[{"internalType":"uint256","name":"response","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"symbol","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/bank/legacy/v602/bank.go b/precompiles/bank/legacy/v602/bank.go new file mode 100644 index 0000000000..4b1ded8b33 --- /dev/null +++ b/precompiles/bank/legacy/v602/bank.go @@ -0,0 +1,370 @@ +package v602 + +import ( + "embed" + "errors" + "fmt" + "math/big" + + "github.com/cosmos/cosmos-sdk/telemetry" + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/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" + "github.com/sei-protocol/sei-chain/utils" + "github.com/tendermint/tendermint/libs/log" +) + +const ( + SendMethod = "send" + SendNativeMethod = "sendNative" + BalanceMethod = "balance" + AllBalancesMethod = "all_balances" + NameMethod = "name" + SymbolMethod = "symbol" + DecimalsMethod = "decimals" + SupplyMethod = "supply" +) + +const ( + BankAddress = "0x0000000000000000000000000000000000001001" +) + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + accountKeeper pcommon.AccountKeeper + bankKeeper pcommon.BankKeeper + bankMsgServer pcommon.BankMsgServer + evmKeeper pcommon.EVMKeeper + address common.Address + + SendID []byte + SendNativeID []byte + BalanceID []byte + AllBalancesID []byte + NameID []byte + SymbolID []byte + DecimalsID []byte + SupplyID []byte +} + +type CoinBalance struct { + Amount *big.Int + Denom string +} + +func GetABI() abi.ABI { + return pcommon.MustGetABI(f, "abi.json") +} + +func NewPrecompile(bankKeeper pcommon.BankKeeper, bankMsgServer pcommon.BankMsgServer, evmKeeper pcommon.EVMKeeper, accountKeeper pcommon.AccountKeeper) (*pcommon.DynamicGasPrecompile, error) { + newAbi := GetABI() + p := &PrecompileExecutor{ + bankKeeper: bankKeeper, + bankMsgServer: bankMsgServer, + evmKeeper: evmKeeper, + accountKeeper: accountKeeper, + address: common.HexToAddress(BankAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case SendMethod: + p.SendID = m.ID + case SendNativeMethod: + p.SendNativeID = m.ID + case BalanceMethod: + p.BalanceID = m.ID + case AllBalancesMethod: + p.AllBalancesID = m.ID + case NameMethod: + p.NameID = m.ID + case SymbolMethod: + p.SymbolID = m.ID + case DecimalsMethod: + p.DecimalsID = m.ID + case SupplyMethod: + p.SupplyID = m.ID + } + } + + return pcommon.NewDynamicGasPrecompile(newAbi, p, p.address, "bank"), nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p PrecompileExecutor) RequiredGas(input []byte, method *abi.Method) uint64 { + return pcommon.DefaultGasCost(input, p.IsTransaction(method.Name)) +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + switch method.Name { + case SendMethod: + return p.send(ctx, caller, method, args, value, readOnly) + case SendNativeMethod: + return p.sendNative(ctx, method, args, caller, callingContract, value, readOnly) + case BalanceMethod: + return p.balance(ctx, method, args, value) + case AllBalancesMethod: + return p.all_balances(ctx, method, args, value) + case NameMethod: + return p.name(ctx, method, args, value) + case SymbolMethod: + return p.symbol(ctx, method, args, value) + case DecimalsMethod: + return p.decimals(ctx, method, args, value) + case SupplyMethod: + return p.totalSupply(ctx, method, args, value) + } + return +} + +func (p PrecompileExecutor) send(ctx sdk.Context, caller common.Address, method *abi.Method, args []interface{}, value *big.Int, readOnly bool) ([]byte, uint64, error) { + if readOnly { + return nil, 0, errors.New("cannot call send from staticcall") + } + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 4); err != nil { + return nil, 0, err + } + denom := args[2].(string) + if denom == "" { + return nil, 0, errors.New("invalid denom") + } + pointer, _, exists := p.evmKeeper.GetERC20NativePointer(ctx, denom) + if !exists || pointer.Cmp(caller) != 0 { + return nil, 0, fmt.Errorf("only pointer %s can send %s but got %s", pointer.Hex(), denom, caller.Hex()) + } + amount := args[3].(*big.Int) + if amount.Cmp(utils.Big0) == 0 { + // short circuit + bz, err := method.Outputs.Pack(true) + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), err + } + senderSeiAddr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + return nil, 0, err + } + receiverSeiAddr, err := p.accAddressFromArg(ctx, args[1]) + if err != nil { + return nil, 0, err + } + + msg := &banktypes.MsgSend{ + FromAddress: senderSeiAddr.String(), + ToAddress: receiverSeiAddr.String(), + Amount: sdk.NewCoins(sdk.NewCoin(denom, sdk.NewIntFromBigInt(amount))), + } + + err = msg.ValidateBasic() + if err != nil { + return nil, 0, err + } + + if _, err = p.bankMsgServer.Send(sdk.WrapSDKContext(ctx), msg); err != nil { + return nil, 0, err + } + + bz, err := method.Outputs.Pack(true) + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), err +} + +func (p PrecompileExecutor) sendNative(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address, callingContract common.Address, value *big.Int, readOnly bool) ([]byte, uint64, error) { + if readOnly { + return nil, 0, errors.New("cannot call sendNative from staticcall") + } + if ctx.EVMPrecompileCalledFromDelegateCall() { + return nil, 0, errors.New("cannot delegatecall sendNative") + } + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + if value == nil || value.Sign() == 0 { + return nil, 0, errors.New("set `value` field to non-zero to send") + } + + senderSeiAddr, ok := p.evmKeeper.GetSeiAddress(ctx, caller) + if !ok { + return nil, 0, errors.New("invalid addr") + } + + receiverAddr, ok := (args[0]).(string) + if !ok || receiverAddr == "" { + return nil, 0, errors.New("invalid addr") + } + + receiverSeiAddr, err := sdk.AccAddressFromBech32(receiverAddr) + if err != nil { + return nil, 0, err + } + + usei, wei, err := pcommon.HandlePaymentUseiWei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderSeiAddr, value, p.bankKeeper) + if err != nil { + return nil, 0, err + } + + if err := p.bankKeeper.SendCoinsAndWei(ctx, senderSeiAddr, receiverSeiAddr, usei, wei); err != nil { + return nil, 0, err + } + accExists := p.accountKeeper.HasAccount(ctx, receiverSeiAddr) + if !accExists { + defer telemetry.IncrCounter(1, "new", "account") + p.accountKeeper.SetAccount(ctx, p.accountKeeper.NewAccountWithAddress(ctx, receiverSeiAddr)) + } + + bz, err := method.Outputs.Pack(true) + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), err +} + +func (p PrecompileExecutor) balance(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, 0, err + } + + addr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + return nil, 0, err + } + denom := args[1].(string) + if denom == "" { + return nil, 0, errors.New("invalid denom") + } + + bz, err := method.Outputs.Pack(p.bankKeeper.GetBalance(ctx, addr, denom).Amount.BigInt()) + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), err +} + +func (p PrecompileExecutor) all_balances(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + + addr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + return nil, 0, err + } + + coins := p.bankKeeper.GetAllBalances(ctx, addr) + + // convert to coin balance structs + coinBalances := make([]CoinBalance, 0, len(coins)) + + for _, coin := range coins { + coinBalances = append(coinBalances, CoinBalance{ + Amount: coin.Amount.BigInt(), + Denom: coin.Denom, + }) + } + + bz, err := method.Outputs.Pack(coinBalances) + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), err +} + +func (p PrecompileExecutor) name(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + + denom := args[0].(string) + metadata, found := p.bankKeeper.GetDenomMetaData(ctx, denom) + if !found { + return nil, 0, fmt.Errorf("denom %s not found", denom) + } + bz, err := method.Outputs.Pack(metadata.Name) + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), err +} + +func (p PrecompileExecutor) symbol(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + + denom := args[0].(string) + metadata, found := p.bankKeeper.GetDenomMetaData(ctx, denom) + if !found { + return nil, 0, fmt.Errorf("denom %s not found", denom) + } + bz, err := method.Outputs.Pack(metadata.Symbol) + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), err +} + +func (p PrecompileExecutor) decimals(ctx sdk.Context, method *abi.Method, _ []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + // all native tokens are integer-based, returns decimals for microdenom (usei) + bz, err := method.Outputs.Pack(uint8(0)) + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), err +} + +func (p PrecompileExecutor) totalSupply(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + + denom := args[0].(string) + coin := p.bankKeeper.GetSupply(ctx, denom) + bz, err := method.Outputs.Pack(coin.Amount.BigInt()) + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), err +} + +func (p PrecompileExecutor) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { + addr := arg.(common.Address) + if addr == (common.Address{}) { + return nil, errors.New("invalid addr") + } + seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, addr) + if !found { + // return the casted version instead + return sdk.AccAddress(addr[:]), nil + } + return seiAddr, nil +} + +func (PrecompileExecutor) IsTransaction(method string) bool { + switch method { + case SendMethod: + return true + case SendNativeMethod: + return true + default: + return false + } +} + +func (p PrecompileExecutor) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("precompile", "bank") +} + +func (p PrecompileExecutor) EVMKeeper() pcommon.EVMKeeper { + return p.evmKeeper +} diff --git a/precompiles/bank/legacy/v603/abi.json b/precompiles/bank/legacy/v603/abi.json new file mode 100644 index 0000000000..844ac48943 --- /dev/null +++ b/precompiles/bank/legacy/v603/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"acc","type":"address"}],"name":"all_balances","outputs":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"denom","type":"string"}],"internalType":"struct IBank.Coin[]","name":"response","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"acc","type":"address"},{"internalType":"string","name":"denom","type":"string"}],"name":"balance","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"decimals","outputs":[{"internalType":"uint8","name":"response","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"name","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"fromAddress","type":"address"},{"internalType":"address","name":"toAddress","type":"address"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"send","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"toNativeAddress","type":"string"}],"name":"sendNative","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"supply","outputs":[{"internalType":"uint256","name":"response","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"symbol","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/bank/legacy/v603/bank.go b/precompiles/bank/legacy/v603/bank.go new file mode 100644 index 0000000000..fad78aab18 --- /dev/null +++ b/precompiles/bank/legacy/v603/bank.go @@ -0,0 +1,376 @@ +package v603 + +import ( + "embed" + "errors" + "fmt" + "math/big" + + "github.com/cosmos/cosmos-sdk/telemetry" + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/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" + "github.com/sei-protocol/sei-chain/utils" + "github.com/tendermint/tendermint/libs/log" +) + +const ( + SendMethod = "send" + SendNativeMethod = "sendNative" + BalanceMethod = "balance" + AllBalancesMethod = "all_balances" + NameMethod = "name" + SymbolMethod = "symbol" + DecimalsMethod = "decimals" + SupplyMethod = "supply" +) + +const ( + BankAddress = "0x0000000000000000000000000000000000001001" +) + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + accountKeeper pcommon.AccountKeeper + bankKeeper pcommon.BankKeeper + bankMsgServer pcommon.BankMsgServer + evmKeeper pcommon.EVMKeeper + address common.Address + + SendID []byte + SendNativeID []byte + BalanceID []byte + AllBalancesID []byte + NameID []byte + SymbolID []byte + DecimalsID []byte + SupplyID []byte +} + +type CoinBalance struct { + Amount *big.Int + Denom string +} + +func GetABI() abi.ABI { + return pcommon.MustGetABI(f, "abi.json") +} + +func NewPrecompile(bankKeeper pcommon.BankKeeper, bankMsgServer pcommon.BankMsgServer, evmKeeper pcommon.EVMKeeper, accountKeeper pcommon.AccountKeeper) (*pcommon.DynamicGasPrecompile, error) { + newAbi := GetABI() + p := &PrecompileExecutor{ + bankKeeper: bankKeeper, + bankMsgServer: bankMsgServer, + evmKeeper: evmKeeper, + accountKeeper: accountKeeper, + address: common.HexToAddress(BankAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case SendMethod: + p.SendID = m.ID + case SendNativeMethod: + p.SendNativeID = m.ID + case BalanceMethod: + p.BalanceID = m.ID + case AllBalancesMethod: + p.AllBalancesID = m.ID + case NameMethod: + p.NameID = m.ID + case SymbolMethod: + p.SymbolID = m.ID + case DecimalsMethod: + p.DecimalsID = m.ID + case SupplyMethod: + p.SupplyID = m.ID + } + } + + return pcommon.NewDynamicGasPrecompile(newAbi, p, p.address, "bank"), nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p PrecompileExecutor) RequiredGas(input []byte, method *abi.Method) uint64 { + return pcommon.DefaultGasCost(input, p.IsTransaction(method.Name)) +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + // Needed to catch gas meter panics + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("execution reverted: %v", r) + } + }() + switch method.Name { + case SendMethod: + return p.send(ctx, caller, method, args, value, readOnly) + case SendNativeMethod: + return p.sendNative(ctx, method, args, caller, callingContract, value, readOnly) + case BalanceMethod: + return p.balance(ctx, method, args, value) + case AllBalancesMethod: + return p.all_balances(ctx, method, args, value) + case NameMethod: + return p.name(ctx, method, args, value) + case SymbolMethod: + return p.symbol(ctx, method, args, value) + case DecimalsMethod: + return p.decimals(ctx, method, args, value) + case SupplyMethod: + return p.totalSupply(ctx, method, args, value) + } + return +} + +func (p PrecompileExecutor) send(ctx sdk.Context, caller common.Address, method *abi.Method, args []interface{}, value *big.Int, readOnly bool) ([]byte, uint64, error) { + if readOnly { + return nil, 0, errors.New("cannot call send from staticcall") + } + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 4); err != nil { + return nil, 0, err + } + denom := args[2].(string) + if denom == "" { + return nil, 0, errors.New("invalid denom") + } + pointer, _, exists := p.evmKeeper.GetERC20NativePointer(ctx, denom) + if !exists || pointer.Cmp(caller) != 0 { + return nil, 0, fmt.Errorf("only pointer %s can send %s but got %s", pointer.Hex(), denom, caller.Hex()) + } + amount := args[3].(*big.Int) + if amount.Cmp(utils.Big0) == 0 { + // short circuit + bz, err := method.Outputs.Pack(true) + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), err + } + senderSeiAddr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + return nil, 0, err + } + receiverSeiAddr, err := p.accAddressFromArg(ctx, args[1]) + if err != nil { + return nil, 0, err + } + + msg := &banktypes.MsgSend{ + FromAddress: senderSeiAddr.String(), + ToAddress: receiverSeiAddr.String(), + Amount: sdk.NewCoins(sdk.NewCoin(denom, sdk.NewIntFromBigInt(amount))), + } + + err = msg.ValidateBasic() + if err != nil { + return nil, 0, err + } + + if _, err = p.bankMsgServer.Send(sdk.WrapSDKContext(ctx), msg); err != nil { + return nil, 0, err + } + + bz, err := method.Outputs.Pack(true) + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), err +} + +func (p PrecompileExecutor) sendNative(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address, callingContract common.Address, value *big.Int, readOnly bool) ([]byte, uint64, error) { + if readOnly { + return nil, 0, errors.New("cannot call sendNative from staticcall") + } + if ctx.EVMPrecompileCalledFromDelegateCall() { + return nil, 0, errors.New("cannot delegatecall sendNative") + } + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + if value == nil || value.Sign() == 0 { + return nil, 0, errors.New("set `value` field to non-zero to send") + } + + senderSeiAddr, ok := p.evmKeeper.GetSeiAddress(ctx, caller) + if !ok { + return nil, 0, errors.New("invalid addr") + } + + receiverAddr, ok := (args[0]).(string) + if !ok || receiverAddr == "" { + return nil, 0, errors.New("invalid addr") + } + + receiverSeiAddr, err := sdk.AccAddressFromBech32(receiverAddr) + if err != nil { + return nil, 0, err + } + + usei, wei, err := pcommon.HandlePaymentUseiWei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderSeiAddr, value, p.bankKeeper) + if err != nil { + return nil, 0, err + } + + if err := p.bankKeeper.SendCoinsAndWei(ctx, senderSeiAddr, receiverSeiAddr, usei, wei); err != nil { + return nil, 0, err + } + accExists := p.accountKeeper.HasAccount(ctx, receiverSeiAddr) + if !accExists { + defer telemetry.IncrCounter(1, "new", "account") + p.accountKeeper.SetAccount(ctx, p.accountKeeper.NewAccountWithAddress(ctx, receiverSeiAddr)) + } + + bz, err := method.Outputs.Pack(true) + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), err +} + +func (p PrecompileExecutor) balance(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, 0, err + } + + addr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + return nil, 0, err + } + denom := args[1].(string) + if denom == "" { + return nil, 0, errors.New("invalid denom") + } + + bz, err := method.Outputs.Pack(p.bankKeeper.GetBalance(ctx, addr, denom).Amount.BigInt()) + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), err +} + +func (p PrecompileExecutor) all_balances(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + + addr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + return nil, 0, err + } + + coins := p.bankKeeper.GetAllBalances(ctx, addr) + + // convert to coin balance structs + coinBalances := make([]CoinBalance, 0, len(coins)) + + for _, coin := range coins { + coinBalances = append(coinBalances, CoinBalance{ + Amount: coin.Amount.BigInt(), + Denom: coin.Denom, + }) + } + + bz, err := method.Outputs.Pack(coinBalances) + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), err +} + +func (p PrecompileExecutor) name(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + + denom := args[0].(string) + metadata, found := p.bankKeeper.GetDenomMetaData(ctx, denom) + if !found { + return nil, 0, fmt.Errorf("denom %s not found", denom) + } + bz, err := method.Outputs.Pack(metadata.Name) + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), err +} + +func (p PrecompileExecutor) symbol(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + + denom := args[0].(string) + metadata, found := p.bankKeeper.GetDenomMetaData(ctx, denom) + if !found { + return nil, 0, fmt.Errorf("denom %s not found", denom) + } + bz, err := method.Outputs.Pack(metadata.Symbol) + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), err +} + +func (p PrecompileExecutor) decimals(ctx sdk.Context, method *abi.Method, _ []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + // all native tokens are integer-based, returns decimals for microdenom (usei) + bz, err := method.Outputs.Pack(uint8(0)) + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), err +} + +func (p PrecompileExecutor) totalSupply(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + + denom := args[0].(string) + coin := p.bankKeeper.GetSupply(ctx, denom) + bz, err := method.Outputs.Pack(coin.Amount.BigInt()) + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), err +} + +func (p PrecompileExecutor) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { + addr := arg.(common.Address) + if addr == (common.Address{}) { + return nil, errors.New("invalid addr") + } + seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, addr) + if !found { + // return the casted version instead + return sdk.AccAddress(addr[:]), nil + } + return seiAddr, nil +} + +func (PrecompileExecutor) IsTransaction(method string) bool { + switch method { + case SendMethod: + return true + case SendNativeMethod: + return true + default: + return false + } +} + +func (p PrecompileExecutor) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("precompile", "bank") +} + +func (p PrecompileExecutor) EVMKeeper() pcommon.EVMKeeper { + return p.evmKeeper +} diff --git a/precompiles/common/legacy/v520/expected_keepers.go b/precompiles/common/legacy/v520/expected_keepers.go new file mode 100644 index 0000000000..b558d5c30f --- /dev/null +++ b/precompiles/common/legacy/v520/expected_keepers.go @@ -0,0 +1,81 @@ +package v520 + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + "github.com/ethereum/go-ethereum/common" + oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" +) + +type BankKeeper interface { + SendCoins(sdk.Context, sdk.AccAddress, sdk.AccAddress, sdk.Coins) error + SendCoinsAndWei(ctx sdk.Context, from sdk.AccAddress, to sdk.AccAddress, amt sdk.Int, wei sdk.Int) error + GetBalance(sdk.Context, sdk.AccAddress, string) sdk.Coin + GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + GetWeiBalance(ctx sdk.Context, addr sdk.AccAddress) sdk.Int + GetDenomMetaData(ctx sdk.Context, denom string) (banktypes.Metadata, bool) + GetSupply(ctx sdk.Context, denom string) sdk.Coin +} + +type EVMKeeper interface { + GetSeiAddress(sdk.Context, common.Address) (sdk.AccAddress, bool) + GetSeiAddressOrDefault(ctx sdk.Context, evmAddress common.Address) sdk.AccAddress // only used for getting precompile Sei addresses + GetEVMAddress(sdk.Context, sdk.AccAddress) (common.Address, bool) + GetCodeHash(sdk.Context, common.Address) common.Hash + 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 { + IterateBaseExchangeRates(ctx sdk.Context, handler func(denom string, exchangeRate oracletypes.OracleExchangeRate) (stop bool)) + CalculateTwaps(ctx sdk.Context, lookbackSeconds uint64) (oracletypes.OracleTwaps, error) +} + +type WasmdKeeper interface { + Instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.AccAddress, initMsg []byte, label string, deposit sdk.Coins) (sdk.AccAddress, []byte, error) + Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, msg []byte, coins sdk.Coins) ([]byte, error) +} + +type WasmdViewKeeper interface { + QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) ([]byte, error) +} + +type StakingKeeper interface { + Delegate(goCtx context.Context, msg *stakingtypes.MsgDelegate) (*stakingtypes.MsgDelegateResponse, error) + BeginRedelegate(goCtx context.Context, msg *stakingtypes.MsgBeginRedelegate) (*stakingtypes.MsgBeginRedelegateResponse, error) + Undelegate(goCtx context.Context, msg *stakingtypes.MsgUndelegate) (*stakingtypes.MsgUndelegateResponse, error) +} + +type GovKeeper interface { + AddVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, options govtypes.WeightedVoteOptions) error + AddDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress, depositAmount sdk.Coins) (bool, error) +} + +type DistributionKeeper interface { + SetWithdrawAddr(ctx sdk.Context, delegatorAddr sdk.AccAddress, withdrawAddr sdk.AccAddress) error + WithdrawDelegationRewards(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (sdk.Coins, error) +} + +type TransferKeeper interface { + SendTransfer( + ctx sdk.Context, + sourcePort, + sourceChannel string, + token sdk.Coin, + sender sdk.AccAddress, + receiver string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + ) error +} diff --git a/precompiles/common/legacy/v520/precompiles.go b/precompiles/common/legacy/v520/precompiles.go new file mode 100644 index 0000000000..5336aa29a0 --- /dev/null +++ b/precompiles/common/legacy/v520/precompiles.go @@ -0,0 +1,119 @@ +package v520 + +import ( + "errors" + "fmt" + "math/big" + + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/sei-protocol/sei-chain/x/evm/state" +) + +const UnknownMethodCallGas uint64 = 3000 + +type Contexter interface { + Ctx() sdk.Context +} + +type Precompile struct { + abi.ABI +} + +func (p Precompile) RequiredGas(input []byte, isTransaction bool) uint64 { + argsBz := input[4:] // first four bytes are method ID + + if isTransaction { + return storetypes.KVGasConfig().WriteCostFlat + (storetypes.KVGasConfig().WriteCostPerByte * uint64(len(argsBz))) + } + + return storetypes.KVGasConfig().ReadCostFlat + (storetypes.KVGasConfig().ReadCostPerByte * uint64(len(argsBz))) +} + +func (p Precompile) Prepare(evm *vm.EVM, input []byte) (sdk.Context, *abi.Method, []interface{}, error) { + ctxer, ok := evm.StateDB.(Contexter) + if !ok { + return sdk.Context{}, nil, nil, errors.New("cannot get context from EVM") + } + methodID, err := ExtractMethodID(input) + if err != nil { + return sdk.Context{}, nil, nil, err + } + method, err := p.ABI.MethodById(methodID) + if err != nil { + return sdk.Context{}, nil, nil, err + } + + argsBz := input[4:] + args, err := method.Inputs.Unpack(argsBz) + if err != nil { + return sdk.Context{}, nil, nil, err + } + + return ctxer.Ctx(), method, args, nil +} + +func (p Precompile) GetABI() abi.ABI { + return p.ABI +} + +func ValidateArgsLength(args []interface{}, length int) error { + if len(args) != length { + return fmt.Errorf("expected %d arguments but got %d", length, len(args)) + } + + return nil +} + +func ValidateNonPayable(value *big.Int) error { + if value != nil && value.Sign() != 0 { + return errors.New("sending funds to a non-payable function") + } + + return nil +} + +func HandlePaymentUsei(ctx sdk.Context, precompileAddr sdk.AccAddress, payer sdk.AccAddress, value *big.Int, bankKeeper BankKeeper) (sdk.Coin, error) { + usei, wei := state.SplitUseiWeiAmount(value) + if !wei.IsZero() { + return sdk.Coin{}, fmt.Errorf("selected precompile function does not allow payment with non-zero wei remainder: received %s", value) + } + coin := sdk.NewCoin(sdk.MustGetBaseDenom(), usei) + // refund payer because the following precompile logic will debit the payments from payer's account + // this creates a new event manager to avoid surfacing these as cosmos events + if err := bankKeeper.SendCoins(ctx.WithEventManager(sdk.NewEventManager()), precompileAddr, payer, sdk.NewCoins(coin)); err != nil { + return sdk.Coin{}, err + } + return coin, nil +} + +func HandlePaymentUseiWei(ctx sdk.Context, precompileAddr sdk.AccAddress, payer sdk.AccAddress, value *big.Int, bankKeeper BankKeeper) (sdk.Int, sdk.Int, error) { + usei, wei := state.SplitUseiWeiAmount(value) + // refund payer because the following precompile logic will debit the payments from payer's account + // this creates a new event manager to avoid surfacing these as cosmos events + if err := bankKeeper.SendCoinsAndWei(ctx.WithEventManager(sdk.NewEventManager()), precompileAddr, payer, usei, wei); err != nil { + return sdk.Int{}, sdk.Int{}, err + } + return usei, wei, nil +} + +/* +* +sei gas = evm gas * multiplier +sei gas price = fee / sei gas = fee / (evm gas * multiplier) = evm gas / multiplier +*/ +func GetRemainingGas(ctx sdk.Context, evmKeeper EVMKeeper) uint64 { + gasMultipler := evmKeeper.GetPriorityNormalizer(ctx) + seiGasRemaining := ctx.GasMeter().Limit() - ctx.GasMeter().GasConsumedToLimit() + return sdk.NewDecFromInt(sdk.NewIntFromUint64(seiGasRemaining)).Quo(gasMultipler).TruncateInt().Uint64() +} + +func ExtractMethodID(input []byte) ([]byte, error) { + // Check if the input has at least the length needed for methodID + if len(input) < 4 { + return nil, errors.New("input too short to extract method ID") + } + return input[:4], nil +} diff --git a/precompiles/common/legacy/v530/expected_keepers.go b/precompiles/common/legacy/v530/expected_keepers.go new file mode 100644 index 0000000000..52ec641e56 --- /dev/null +++ b/precompiles/common/legacy/v530/expected_keepers.go @@ -0,0 +1,98 @@ +package v530 + +import ( + "context" + + connectiontypes "github.com/cosmos/ibc-go/v3/modules/core/03-connection/types" + "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + "github.com/ethereum/go-ethereum/common" + oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" +) + +type BankKeeper interface { + SendCoins(sdk.Context, sdk.AccAddress, sdk.AccAddress, sdk.Coins) error + SendCoinsAndWei(ctx sdk.Context, from sdk.AccAddress, to sdk.AccAddress, amt sdk.Int, wei sdk.Int) error + GetBalance(sdk.Context, sdk.AccAddress, string) sdk.Coin + GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + GetWeiBalance(ctx sdk.Context, addr sdk.AccAddress) sdk.Int + GetDenomMetaData(ctx sdk.Context, denom string) (banktypes.Metadata, bool) + GetSupply(ctx sdk.Context, denom string) sdk.Coin +} + +type EVMKeeper interface { + GetSeiAddress(sdk.Context, common.Address) (sdk.AccAddress, bool) + GetSeiAddressOrDefault(ctx sdk.Context, evmAddress common.Address) sdk.AccAddress // only used for getting precompile Sei addresses + GetEVMAddress(sdk.Context, sdk.AccAddress) (common.Address, bool) + GetCodeHash(sdk.Context, common.Address) common.Hash + 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 { + IterateBaseExchangeRates(ctx sdk.Context, handler func(denom string, exchangeRate oracletypes.OracleExchangeRate) (stop bool)) + CalculateTwaps(ctx sdk.Context, lookbackSeconds uint64) (oracletypes.OracleTwaps, error) +} + +type WasmdKeeper interface { + Instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.AccAddress, initMsg []byte, label string, deposit sdk.Coins) (sdk.AccAddress, []byte, error) + Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, msg []byte, coins sdk.Coins) ([]byte, error) +} + +type WasmdViewKeeper interface { + QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) ([]byte, error) +} + +type StakingKeeper interface { + Delegate(goCtx context.Context, msg *stakingtypes.MsgDelegate) (*stakingtypes.MsgDelegateResponse, error) + BeginRedelegate(goCtx context.Context, msg *stakingtypes.MsgBeginRedelegate) (*stakingtypes.MsgBeginRedelegateResponse, error) + Undelegate(goCtx context.Context, msg *stakingtypes.MsgUndelegate) (*stakingtypes.MsgUndelegateResponse, error) +} + +type GovKeeper interface { + AddVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, options govtypes.WeightedVoteOptions) error + AddDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress, depositAmount sdk.Coins) (bool, error) +} + +type DistributionKeeper interface { + SetWithdrawAddr(ctx sdk.Context, delegatorAddr sdk.AccAddress, withdrawAddr sdk.AccAddress) error + WithdrawDelegationRewards(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (sdk.Coins, error) +} + +type TransferKeeper interface { + SendTransfer( + ctx sdk.Context, + sourcePort, + sourceChannel string, + token sdk.Coin, + sender sdk.AccAddress, + receiver string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + ) error +} + +type ClientKeeper interface { + GetClientState(ctx sdk.Context, clientID string) (exported.ClientState, bool) + GetClientConsensusState(ctx sdk.Context, clientID string, height exported.Height) (exported.ConsensusState, bool) +} + +type ConnectionKeeper interface { + GetConnection(ctx sdk.Context, connectionID string) (connectiontypes.ConnectionEnd, bool) +} + +type ChannelKeeper interface { + GetChannel(ctx sdk.Context, portID, channelID string) (types.Channel, bool) +} diff --git a/precompiles/common/legacy/v530/precompiles.go b/precompiles/common/legacy/v530/precompiles.go new file mode 100644 index 0000000000..31823f6331 --- /dev/null +++ b/precompiles/common/legacy/v530/precompiles.go @@ -0,0 +1,119 @@ +package v530 + +import ( + "errors" + "fmt" + "math/big" + + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/sei-protocol/sei-chain/x/evm/state" +) + +const UnknownMethodCallGas uint64 = 3000 + +type Contexter interface { + Ctx() sdk.Context +} + +type Precompile struct { + abi.ABI +} + +func (p Precompile) RequiredGas(input []byte, isTransaction bool) uint64 { + argsBz := input[4:] // first four bytes are method ID + + if isTransaction { + return storetypes.KVGasConfig().WriteCostFlat + (storetypes.KVGasConfig().WriteCostPerByte * uint64(len(argsBz))) + } + + return storetypes.KVGasConfig().ReadCostFlat + (storetypes.KVGasConfig().ReadCostPerByte * uint64(len(argsBz))) +} + +func (p Precompile) Prepare(evm *vm.EVM, input []byte) (sdk.Context, *abi.Method, []interface{}, error) { + ctxer, ok := evm.StateDB.(Contexter) + if !ok { + return sdk.Context{}, nil, nil, errors.New("cannot get context from EVM") + } + methodID, err := ExtractMethodID(input) + if err != nil { + return sdk.Context{}, nil, nil, err + } + method, err := p.ABI.MethodById(methodID) + if err != nil { + return sdk.Context{}, nil, nil, err + } + + argsBz := input[4:] + args, err := method.Inputs.Unpack(argsBz) + if err != nil { + return sdk.Context{}, nil, nil, err + } + + return ctxer.Ctx(), method, args, nil +} + +func (p Precompile) GetABI() abi.ABI { + return p.ABI +} + +func ValidateArgsLength(args []interface{}, length int) error { + if len(args) != length { + return fmt.Errorf("expected %d arguments but got %d", length, len(args)) + } + + return nil +} + +func ValidateNonPayable(value *big.Int) error { + if value != nil && value.Sign() != 0 { + return errors.New("sending funds to a non-payable function") + } + + return nil +} + +func HandlePaymentUsei(ctx sdk.Context, precompileAddr sdk.AccAddress, payer sdk.AccAddress, value *big.Int, bankKeeper BankKeeper) (sdk.Coin, error) { + usei, wei := state.SplitUseiWeiAmount(value) + if !wei.IsZero() { + return sdk.Coin{}, fmt.Errorf("selected precompile function does not allow payment with non-zero wei remainder: received %s", value) + } + coin := sdk.NewCoin(sdk.MustGetBaseDenom(), usei) + // refund payer because the following precompile logic will debit the payments from payer's account + // this creates a new event manager to avoid surfacing these as cosmos events + if err := bankKeeper.SendCoins(ctx.WithEventManager(sdk.NewEventManager()), precompileAddr, payer, sdk.NewCoins(coin)); err != nil { + return sdk.Coin{}, err + } + return coin, nil +} + +func HandlePaymentUseiWei(ctx sdk.Context, precompileAddr sdk.AccAddress, payer sdk.AccAddress, value *big.Int, bankKeeper BankKeeper) (sdk.Int, sdk.Int, error) { + usei, wei := state.SplitUseiWeiAmount(value) + // refund payer because the following precompile logic will debit the payments from payer's account + // this creates a new event manager to avoid surfacing these as cosmos events + if err := bankKeeper.SendCoinsAndWei(ctx.WithEventManager(sdk.NewEventManager()), precompileAddr, payer, usei, wei); err != nil { + return sdk.Int{}, sdk.Int{}, err + } + return usei, wei, nil +} + +/* +* +sei gas = evm gas * multiplier +sei gas price = fee / sei gas = fee / (evm gas * multiplier) = evm gas / multiplier +*/ +func GetRemainingGas(ctx sdk.Context, evmKeeper EVMKeeper) uint64 { + gasMultipler := evmKeeper.GetPriorityNormalizer(ctx) + seiGasRemaining := ctx.GasMeter().Limit() - ctx.GasMeter().GasConsumedToLimit() + return sdk.NewDecFromInt(sdk.NewIntFromUint64(seiGasRemaining)).Quo(gasMultipler).TruncateInt().Uint64() +} + +func ExtractMethodID(input []byte) ([]byte, error) { + // Check if the input has at least the length needed for methodID + if len(input) < 4 { + return nil, errors.New("input too short to extract method ID") + } + return input[:4], nil +} diff --git a/precompiles/common/legacy/v555/expected_keepers.go b/precompiles/common/legacy/v555/expected_keepers.go new file mode 100644 index 0000000000..ab1feaaae3 --- /dev/null +++ b/precompiles/common/legacy/v555/expected_keepers.go @@ -0,0 +1,89 @@ +package v555 + +import ( + "context" + + connectiontypes "github.com/cosmos/ibc-go/v3/modules/core/03-connection/types" + "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + ibctypes "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" + "github.com/ethereum/go-ethereum/common" + oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" +) + +type BankKeeper interface { + SendCoins(sdk.Context, sdk.AccAddress, sdk.AccAddress, sdk.Coins) error + SendCoinsAndWei(ctx sdk.Context, from sdk.AccAddress, to sdk.AccAddress, amt sdk.Int, wei sdk.Int) error + GetBalance(sdk.Context, sdk.AccAddress, string) sdk.Coin + GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + GetWeiBalance(ctx sdk.Context, addr sdk.AccAddress) sdk.Int + GetDenomMetaData(ctx sdk.Context, denom string) (banktypes.Metadata, bool) + GetSupply(ctx sdk.Context, denom string) sdk.Coin +} + +type EVMKeeper interface { + GetSeiAddress(sdk.Context, common.Address) (sdk.AccAddress, bool) + GetSeiAddressOrDefault(ctx sdk.Context, evmAddress common.Address) sdk.AccAddress // only used for getting precompile Sei addresses + GetEVMAddress(sdk.Context, sdk.AccAddress) (common.Address, bool) + GetCodeHash(sdk.Context, common.Address) common.Hash + 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 { + IterateBaseExchangeRates(ctx sdk.Context, handler func(denom string, exchangeRate oracletypes.OracleExchangeRate) (stop bool)) + CalculateTwaps(ctx sdk.Context, lookbackSeconds uint64) (oracletypes.OracleTwaps, error) +} + +type WasmdKeeper interface { + Instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.AccAddress, initMsg []byte, label string, deposit sdk.Coins) (sdk.AccAddress, []byte, error) + Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, msg []byte, coins sdk.Coins) ([]byte, error) +} + +type WasmdViewKeeper interface { + QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) ([]byte, error) +} + +type StakingKeeper interface { + Delegate(goCtx context.Context, msg *stakingtypes.MsgDelegate) (*stakingtypes.MsgDelegateResponse, error) + BeginRedelegate(goCtx context.Context, msg *stakingtypes.MsgBeginRedelegate) (*stakingtypes.MsgBeginRedelegateResponse, error) + Undelegate(goCtx context.Context, msg *stakingtypes.MsgUndelegate) (*stakingtypes.MsgUndelegateResponse, error) +} + +type GovKeeper interface { + AddVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, options govtypes.WeightedVoteOptions) error + AddDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress, depositAmount sdk.Coins) (bool, error) +} + +type DistributionKeeper interface { + SetWithdrawAddr(ctx sdk.Context, delegatorAddr sdk.AccAddress, withdrawAddr sdk.AccAddress) error + WithdrawDelegationRewards(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (sdk.Coins, error) +} + +type TransferKeeper interface { + Transfer(goCtx context.Context, msg *ibctypes.MsgTransfer) (*ibctypes.MsgTransferResponse, error) +} + +type ClientKeeper interface { + GetClientState(ctx sdk.Context, clientID string) (exported.ClientState, bool) + GetClientConsensusState(ctx sdk.Context, clientID string, height exported.Height) (exported.ConsensusState, bool) +} + +type ConnectionKeeper interface { + GetConnection(ctx sdk.Context, connectionID string) (connectiontypes.ConnectionEnd, bool) +} + +type ChannelKeeper interface { + GetChannel(ctx sdk.Context, portID, channelID string) (types.Channel, bool) +} diff --git a/precompiles/common/legacy/v555/precompiles.go b/precompiles/common/legacy/v555/precompiles.go new file mode 100644 index 0000000000..ec48b380b1 --- /dev/null +++ b/precompiles/common/legacy/v555/precompiles.go @@ -0,0 +1,119 @@ +package v555 + +import ( + "errors" + "fmt" + "math/big" + + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/sei-protocol/sei-chain/x/evm/state" +) + +const UnknownMethodCallGas uint64 = 3000 + +type Contexter interface { + Ctx() sdk.Context +} + +type Precompile struct { + abi.ABI +} + +func (p Precompile) RequiredGas(input []byte, isTransaction bool) uint64 { + argsBz := input[4:] // first four bytes are method ID + + if isTransaction { + return storetypes.KVGasConfig().WriteCostFlat + (storetypes.KVGasConfig().WriteCostPerByte * uint64(len(argsBz))) + } + + return storetypes.KVGasConfig().ReadCostFlat + (storetypes.KVGasConfig().ReadCostPerByte * uint64(len(argsBz))) +} + +func (p Precompile) Prepare(evm *vm.EVM, input []byte) (sdk.Context, *abi.Method, []interface{}, error) { + ctxer, ok := evm.StateDB.(Contexter) + if !ok { + return sdk.Context{}, nil, nil, errors.New("cannot get context from EVM") + } + methodID, err := ExtractMethodID(input) + if err != nil { + return sdk.Context{}, nil, nil, err + } + method, err := p.ABI.MethodById(methodID) + if err != nil { + return sdk.Context{}, nil, nil, err + } + + argsBz := input[4:] + args, err := method.Inputs.Unpack(argsBz) + if err != nil { + return sdk.Context{}, nil, nil, err + } + + return ctxer.Ctx(), method, args, nil +} + +func (p Precompile) GetABI() abi.ABI { + return p.ABI +} + +func ValidateArgsLength(args []interface{}, length int) error { + if len(args) != length { + return fmt.Errorf("expected %d arguments but got %d", length, len(args)) + } + + return nil +} + +func ValidateNonPayable(value *big.Int) error { + if value != nil && value.Sign() != 0 { + return errors.New("sending funds to a non-payable function") + } + + return nil +} + +func HandlePaymentUsei(ctx sdk.Context, precompileAddr sdk.AccAddress, payer sdk.AccAddress, value *big.Int, bankKeeper BankKeeper) (sdk.Coin, error) { + usei, wei := state.SplitUseiWeiAmount(value) + if !wei.IsZero() { + return sdk.Coin{}, fmt.Errorf("selected precompile function does not allow payment with non-zero wei remainder: received %s", value) + } + coin := sdk.NewCoin(sdk.MustGetBaseDenom(), usei) + // refund payer because the following precompile logic will debit the payments from payer's account + // this creates a new event manager to avoid surfacing these as cosmos events + if err := bankKeeper.SendCoins(ctx.WithEventManager(sdk.NewEventManager()), precompileAddr, payer, sdk.NewCoins(coin)); err != nil { + return sdk.Coin{}, err + } + return coin, nil +} + +func HandlePaymentUseiWei(ctx sdk.Context, precompileAddr sdk.AccAddress, payer sdk.AccAddress, value *big.Int, bankKeeper BankKeeper) (sdk.Int, sdk.Int, error) { + usei, wei := state.SplitUseiWeiAmount(value) + // refund payer because the following precompile logic will debit the payments from payer's account + // this creates a new event manager to avoid surfacing these as cosmos events + if err := bankKeeper.SendCoinsAndWei(ctx.WithEventManager(sdk.NewEventManager()), precompileAddr, payer, usei, wei); err != nil { + return sdk.Int{}, sdk.Int{}, err + } + return usei, wei, nil +} + +/* +* +sei gas = evm gas * multiplier +sei gas price = fee / sei gas = fee / (evm gas * multiplier) = evm gas / multiplier +*/ +func GetRemainingGas(ctx sdk.Context, evmKeeper EVMKeeper) uint64 { + gasMultipler := evmKeeper.GetPriorityNormalizer(ctx) + seiGasRemaining := ctx.GasMeter().Limit() - ctx.GasMeter().GasConsumedToLimit() + return sdk.NewDecFromInt(sdk.NewIntFromUint64(seiGasRemaining)).Quo(gasMultipler).TruncateInt().Uint64() +} + +func ExtractMethodID(input []byte) ([]byte, error) { + // Check if the input has at least the length needed for methodID + if len(input) < 4 { + return nil, errors.New("input too short to extract method ID") + } + return input[:4], nil +} diff --git a/precompiles/common/legacy/v562/expected_keepers.go b/precompiles/common/legacy/v562/expected_keepers.go new file mode 100644 index 0000000000..b1fc5a5ba8 --- /dev/null +++ b/precompiles/common/legacy/v562/expected_keepers.go @@ -0,0 +1,96 @@ +package v562 + +import ( + "context" + + connectiontypes "github.com/cosmos/ibc-go/v3/modules/core/03-connection/types" + "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + ibctypes "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" + "github.com/ethereum/go-ethereum/common" + oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" +) + +type BankKeeper interface { + SendCoins(sdk.Context, sdk.AccAddress, sdk.AccAddress, sdk.Coins) error + SendCoinsAndWei(ctx sdk.Context, from sdk.AccAddress, to sdk.AccAddress, amt sdk.Int, wei sdk.Int) error + GetBalance(sdk.Context, sdk.AccAddress, string) sdk.Coin + GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + GetWeiBalance(ctx sdk.Context, addr sdk.AccAddress) sdk.Int + GetDenomMetaData(ctx sdk.Context, denom string) (banktypes.Metadata, bool) + GetSupply(ctx sdk.Context, denom string) sdk.Coin +} + +type EVMKeeper interface { + GetSeiAddress(sdk.Context, common.Address) (sdk.AccAddress, bool) + GetSeiAddressOrDefault(ctx sdk.Context, evmAddress common.Address) sdk.AccAddress // only used for getting precompile Sei addresses + GetEVMAddress(sdk.Context, sdk.AccAddress) (common.Address, bool) + GetCodeHash(sdk.Context, common.Address) common.Hash + 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 AccountKeeper interface { + HasAccount(ctx sdk.Context, addr sdk.AccAddress) bool + SetAccount(ctx sdk.Context, acc authtypes.AccountI) + NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI +} + +type OracleKeeper interface { + IterateBaseExchangeRates(ctx sdk.Context, handler func(denom string, exchangeRate oracletypes.OracleExchangeRate) (stop bool)) + CalculateTwaps(ctx sdk.Context, lookbackSeconds uint64) (oracletypes.OracleTwaps, error) +} + +type WasmdKeeper interface { + Instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.AccAddress, initMsg []byte, label string, deposit sdk.Coins) (sdk.AccAddress, []byte, error) + Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, msg []byte, coins sdk.Coins) ([]byte, error) +} + +type WasmdViewKeeper interface { + QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) ([]byte, error) +} + +type StakingKeeper interface { + Delegate(goCtx context.Context, msg *stakingtypes.MsgDelegate) (*stakingtypes.MsgDelegateResponse, error) + BeginRedelegate(goCtx context.Context, msg *stakingtypes.MsgBeginRedelegate) (*stakingtypes.MsgBeginRedelegateResponse, error) + Undelegate(goCtx context.Context, msg *stakingtypes.MsgUndelegate) (*stakingtypes.MsgUndelegateResponse, error) +} + +type GovKeeper interface { + AddVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, options govtypes.WeightedVoteOptions) error + AddDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress, depositAmount sdk.Coins) (bool, error) +} + +type DistributionKeeper interface { + SetWithdrawAddr(ctx sdk.Context, delegatorAddr sdk.AccAddress, withdrawAddr sdk.AccAddress) error + WithdrawDelegationRewards(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (sdk.Coins, error) +} + +type TransferKeeper interface { + Transfer(goCtx context.Context, msg *ibctypes.MsgTransfer) (*ibctypes.MsgTransferResponse, error) +} + +type ClientKeeper interface { + GetClientState(ctx sdk.Context, clientID string) (exported.ClientState, bool) + GetClientConsensusState(ctx sdk.Context, clientID string, height exported.Height) (exported.ConsensusState, bool) +} + +type ConnectionKeeper interface { + GetConnection(ctx sdk.Context, connectionID string) (connectiontypes.ConnectionEnd, bool) +} + +type ChannelKeeper interface { + GetChannel(ctx sdk.Context, portID, channelID string) (types.Channel, bool) +} diff --git a/precompiles/common/legacy/v562/precompiles.go b/precompiles/common/legacy/v562/precompiles.go new file mode 100644 index 0000000000..2b99e8fd86 --- /dev/null +++ b/precompiles/common/legacy/v562/precompiles.go @@ -0,0 +1,242 @@ +package v562 + +import ( + "errors" + "fmt" + "math/big" + + storetypes "github.com/cosmos/cosmos-sdk/store/types" + 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/tracing" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/utils/metrics" + "github.com/sei-protocol/sei-chain/x/evm/state" +) + +const UnknownMethodCallGas uint64 = 3000 + +type Contexter interface { + Ctx() sdk.Context +} + +type PrecompileExecutor interface { + RequiredGas([]byte, *abi.Method) uint64 + Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM) ([]byte, error) +} + +type Precompile struct { + abi.ABI + address common.Address + name string + executor PrecompileExecutor +} + +var _ vm.PrecompiledContract = &Precompile{} + +func NewPrecompile(a abi.ABI, executor PrecompileExecutor, address common.Address, name string) *Precompile { + return &Precompile{ABI: a, executor: executor, address: address, name: name} +} + +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := ExtractMethodID(input) + if err != nil { + return UnknownMethodCallGas + } + + method, err := p.ABI.MethodById(methodID) + if err != nil { + // This should never happen since this method is going to fail during Run + return UnknownMethodCallGas + } + return p.executor.RequiredGas(input[4:], method) +} + +func (p Precompile) Run(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, value *big.Int, readOnly bool, _ bool) (bz []byte, err error) { + operation := fmt.Sprintf("%s_unknown", p.name) + defer func() { + HandlePrecompileError(err, evm, operation) + }() + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, err + } + + operation = method.Name + em := ctx.EventManager() + ctx = ctx.WithEventManager(sdk.NewEventManager()) + bz, err = p.executor.Execute(ctx, method, caller, callingContract, args, value, readOnly, evm) + if err != nil { + return bz, err + } + events := ctx.EventManager().Events() + if len(events) > 0 { + em.EmitEvents(ctx.EventManager().Events()) + } + return bz, err +} + +func HandlePrecompileError(err error, evm *vm.EVM, operation string) { + if err != nil { + evm.StateDB.(*state.DBImpl).SetPrecompileError(err) + metrics.IncrementErrorMetrics(operation, err) + } +} + +func (p Precompile) Prepare(evm *vm.EVM, input []byte) (sdk.Context, *abi.Method, []interface{}, error) { + ctxer, ok := evm.StateDB.(Contexter) + if !ok { + return sdk.Context{}, nil, nil, errors.New("cannot get context from EVM") + } + methodID, err := ExtractMethodID(input) + if err != nil { + return sdk.Context{}, nil, nil, err + } + method, err := p.ABI.MethodById(methodID) + if err != nil { + return sdk.Context{}, nil, nil, err + } + + argsBz := input[4:] + args, err := method.Inputs.Unpack(argsBz) + if err != nil { + return sdk.Context{}, nil, nil, err + } + + return ctxer.Ctx(), method, args, nil +} + +func (p Precompile) GetABI() abi.ABI { + return p.ABI +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return p.name +} + +func (p Precompile) GetExecutor() PrecompileExecutor { + return p.executor +} + +type DynamicGasPrecompileExecutor interface { + Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) + EVMKeeper() EVMKeeper +} + +type DynamicGasPrecompile struct { + *Precompile + executor DynamicGasPrecompileExecutor +} + +var _ vm.DynamicGasPrecompiledContract = &DynamicGasPrecompile{} + +func NewDynamicGasPrecompile(a abi.ABI, executor DynamicGasPrecompileExecutor, address common.Address, name string) *DynamicGasPrecompile { + return &DynamicGasPrecompile{Precompile: NewPrecompile(a, nil, address, name), executor: executor} +} + +func (d DynamicGasPrecompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, hooks *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { + operation := fmt.Sprintf("%s_unknown", d.name) + defer func() { + HandlePrecompileError(err, evm, operation) + }() + ctx, method, args, err := d.Prepare(evm, input) + if err != nil { + return nil, 0, err + } + gasMultipler := d.executor.EVMKeeper().GetPriorityNormalizer(ctx) + gasLimitBigInt := sdk.NewDecFromInt(sdk.NewIntFromUint64(suppliedGas)).Mul(gasMultipler).TruncateInt().BigInt() + if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { + gasLimitBigInt = utils.BigMaxU64 + } + ctx = ctx.WithGasMeter(sdk.NewGasMeterWithMultiplier(ctx, gasLimitBigInt.Uint64())) + + operation = method.Name + em := ctx.EventManager() + ctx = ctx.WithEventManager(sdk.NewEventManager()) + ret, remainingGas, err = d.executor.Execute(ctx, method, caller, callingContract, args, value, readOnly, evm, suppliedGas) + if err != nil { + return ret, remainingGas, err + } + events := ctx.EventManager().Events() + if len(events) > 0 { + em.EmitEvents(ctx.EventManager().Events()) + } + return ret, remainingGas, err +} + +func (d DynamicGasPrecompile) GetExecutor() DynamicGasPrecompileExecutor { + return d.executor +} + +func ValidateArgsLength(args []interface{}, length int) error { + if len(args) != length { + return fmt.Errorf("expected %d arguments but got %d", length, len(args)) + } + + return nil +} + +func ValidateNonPayable(value *big.Int) error { + if value != nil && value.Sign() != 0 { + return errors.New("sending funds to a non-payable function") + } + + return nil +} + +func HandlePaymentUsei(ctx sdk.Context, precompileAddr sdk.AccAddress, payer sdk.AccAddress, value *big.Int, bankKeeper BankKeeper) (sdk.Coin, error) { + usei, wei := state.SplitUseiWeiAmount(value) + if !wei.IsZero() { + return sdk.Coin{}, fmt.Errorf("selected precompile function does not allow payment with non-zero wei remainder: received %s", value) + } + coin := sdk.NewCoin(sdk.MustGetBaseDenom(), usei) + // refund payer because the following precompile logic will debit the payments from payer's account + // this creates a new event manager to avoid surfacing these as cosmos events + if err := bankKeeper.SendCoins(ctx.WithEventManager(sdk.NewEventManager()), precompileAddr, payer, sdk.NewCoins(coin)); err != nil { + return sdk.Coin{}, err + } + return coin, nil +} + +func HandlePaymentUseiWei(ctx sdk.Context, precompileAddr sdk.AccAddress, payer sdk.AccAddress, value *big.Int, bankKeeper BankKeeper) (sdk.Int, sdk.Int, error) { + usei, wei := state.SplitUseiWeiAmount(value) + // refund payer because the following precompile logic will debit the payments from payer's account + // this creates a new event manager to avoid surfacing these as cosmos events + if err := bankKeeper.SendCoinsAndWei(ctx.WithEventManager(sdk.NewEventManager()), precompileAddr, payer, usei, wei); err != nil { + return sdk.Int{}, sdk.Int{}, err + } + return usei, wei, nil +} + +/* +* +sei gas = evm gas * multiplier +sei gas price = fee / sei gas = fee / (evm gas * multiplier) = evm gas / multiplier +*/ +func GetRemainingGas(ctx sdk.Context, evmKeeper EVMKeeper) uint64 { + gasMultipler := evmKeeper.GetPriorityNormalizer(ctx) + seiGasRemaining := ctx.GasMeter().Limit() - ctx.GasMeter().GasConsumedToLimit() + return sdk.NewDecFromInt(sdk.NewIntFromUint64(seiGasRemaining)).Quo(gasMultipler).TruncateInt().Uint64() +} + +func ExtractMethodID(input []byte) ([]byte, error) { + // Check if the input has at least the length needed for methodID + if len(input) < 4 { + return nil, errors.New("input too short to extract method ID") + } + return input[:4], nil +} + +func DefaultGasCost(input []byte, isTransaction bool) uint64 { + if isTransaction { + return storetypes.KVGasConfig().WriteCostFlat + (storetypes.KVGasConfig().WriteCostPerByte * uint64(len(input))) + } + + return storetypes.KVGasConfig().ReadCostFlat + (storetypes.KVGasConfig().ReadCostPerByte * uint64(len(input))) +} diff --git a/precompiles/common/legacy/v575/expected_keepers.go b/precompiles/common/legacy/v575/expected_keepers.go new file mode 100644 index 0000000000..8082530950 --- /dev/null +++ b/precompiles/common/legacy/v575/expected_keepers.go @@ -0,0 +1,119 @@ +package v575 + +import ( + "context" + + connectiontypes "github.com/cosmos/ibc-go/v3/modules/core/03-connection/types" + "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + ibctypes "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/sei-protocol/sei-chain/utils" + oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" +) + +type BankKeeper interface { + SendCoins(sdk.Context, sdk.AccAddress, sdk.AccAddress, sdk.Coins) error + SendCoinsAndWei(ctx sdk.Context, from sdk.AccAddress, to sdk.AccAddress, amt sdk.Int, wei sdk.Int) error + GetBalance(sdk.Context, sdk.AccAddress, string) sdk.Coin + GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + GetWeiBalance(ctx sdk.Context, addr sdk.AccAddress) sdk.Int + GetDenomMetaData(ctx sdk.Context, denom string) (banktypes.Metadata, bool) + GetSupply(ctx sdk.Context, denom string) sdk.Coin + LockedCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins +} + +type EVMKeeper interface { + GetSeiAddress(sdk.Context, common.Address) (sdk.AccAddress, bool) + GetSeiAddressOrDefault(ctx sdk.Context, evmAddress common.Address) sdk.AccAddress // only used for getting precompile Sei addresses + GetEVMAddress(sdk.Context, sdk.AccAddress) (common.Address, bool) + SetAddressMapping(sdk.Context, sdk.AccAddress, common.Address) + GetCodeHash(sdk.Context, common.Address) common.Hash + 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) + SetCode(ctx sdk.Context, addr common.Address, code []byte) + UpsertERCNativePointer( + ctx sdk.Context, evm *vm.EVM, suppliedGas uint64, token string, metadata utils.ERCMetadata, + ) (contractAddr common.Address, remainingGas uint64, err error) + UpsertERCCW20Pointer( + ctx sdk.Context, evm *vm.EVM, suppliedGas uint64, cw20Addr string, metadata utils.ERCMetadata, + ) (contractAddr common.Address, remainingGas uint64, err error) + UpsertERCCW721Pointer( + ctx sdk.Context, evm *vm.EVM, suppliedGas uint64, cw721Addr string, metadata utils.ERCMetadata, + ) (contractAddr common.Address, remainingGas uint64, err error) +} + +type AccountKeeper interface { + GetAccount(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI + HasAccount(ctx sdk.Context, addr sdk.AccAddress) bool + SetAccount(ctx sdk.Context, acc authtypes.AccountI) + RemoveAccount(ctx sdk.Context, acc authtypes.AccountI) + NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI +} + +type OracleKeeper interface { + IterateBaseExchangeRates(ctx sdk.Context, handler func(denom string, exchangeRate oracletypes.OracleExchangeRate) (stop bool)) + CalculateTwaps(ctx sdk.Context, lookbackSeconds uint64) (oracletypes.OracleTwaps, error) +} + +type WasmdKeeper interface { + Instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.AccAddress, initMsg []byte, label string, deposit sdk.Coins) (sdk.AccAddress, []byte, error) + Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, msg []byte, coins sdk.Coins) ([]byte, error) +} + +type WasmdViewKeeper interface { + QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) ([]byte, error) +} + +type StakingKeeper interface { + Delegate(goCtx context.Context, msg *stakingtypes.MsgDelegate) (*stakingtypes.MsgDelegateResponse, error) + BeginRedelegate(goCtx context.Context, msg *stakingtypes.MsgBeginRedelegate) (*stakingtypes.MsgBeginRedelegateResponse, error) + Undelegate(goCtx context.Context, msg *stakingtypes.MsgUndelegate) (*stakingtypes.MsgUndelegateResponse, error) +} + +type StakingQuerier interface { + Delegation(c context.Context, req *stakingtypes.QueryDelegationRequest) (*stakingtypes.QueryDelegationResponse, error) +} + +type GovKeeper interface { + AddVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, options govtypes.WeightedVoteOptions) error + AddDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress, depositAmount sdk.Coins) (bool, error) +} + +type DistributionKeeper interface { + SetWithdrawAddr(ctx sdk.Context, delegatorAddr sdk.AccAddress, withdrawAddr sdk.AccAddress) error + WithdrawDelegationRewards(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (sdk.Coins, error) + DelegationTotalRewards(c context.Context, req *distrtypes.QueryDelegationTotalRewardsRequest) (*distrtypes.QueryDelegationTotalRewardsResponse, error) +} + +type TransferKeeper interface { + Transfer(goCtx context.Context, msg *ibctypes.MsgTransfer) (*ibctypes.MsgTransferResponse, error) +} + +type ClientKeeper interface { + GetClientState(ctx sdk.Context, clientID string) (exported.ClientState, bool) + GetClientConsensusState(ctx sdk.Context, clientID string, height exported.Height) (exported.ConsensusState, bool) +} + +type ConnectionKeeper interface { + GetConnection(ctx sdk.Context, connectionID string) (connectiontypes.ConnectionEnd, bool) +} + +type ChannelKeeper interface { + GetChannel(ctx sdk.Context, portID, channelID string) (types.Channel, bool) +} diff --git a/precompiles/common/legacy/v575/precompiles.go b/precompiles/common/legacy/v575/precompiles.go new file mode 100644 index 0000000000..ddf0b501fd --- /dev/null +++ b/precompiles/common/legacy/v575/precompiles.go @@ -0,0 +1,282 @@ +package v575 + +import ( + "bytes" + "embed" + "errors" + "fmt" + "math/big" + + storetypes "github.com/cosmos/cosmos-sdk/store/types" + 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/tracing" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/utils/metrics" + "github.com/sei-protocol/sei-chain/x/evm/state" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const UnknownMethodCallGas uint64 = 3000 + +type Contexter interface { + Ctx() sdk.Context +} + +type PrecompileExecutor interface { + RequiredGas([]byte, *abi.Method) uint64 + Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM) ([]byte, error) +} + +type Precompile struct { + abi.ABI + address common.Address + name string + executor PrecompileExecutor +} + +var _ vm.PrecompiledContract = &Precompile{} + +func NewPrecompile(a abi.ABI, executor PrecompileExecutor, address common.Address, name string) *Precompile { + return &Precompile{ABI: a, executor: executor, address: address, name: name} +} + +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := ExtractMethodID(input) + if err != nil { + return UnknownMethodCallGas + } + + method, err := p.ABI.MethodById(methodID) + if err != nil { + // This should never happen since this method is going to fail during Run + return UnknownMethodCallGas + } + return p.executor.RequiredGas(input[4:], method) +} + +func (p Precompile) Run(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, value *big.Int, readOnly bool, _ bool) (bz []byte, err error) { + operation := fmt.Sprintf("%s_unknown", p.name) + defer func() { + HandlePrecompileError(err, evm, operation) + if err != nil { + bz = []byte(err.Error()) + err = vm.ErrExecutionReverted + } + }() + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, err + } + + operation = method.Name + em := ctx.EventManager() + ctx = ctx.WithEventManager(sdk.NewEventManager()) + bz, err = p.executor.Execute(ctx, method, caller, callingContract, args, value, readOnly, evm) + if err != nil { + return bz, err + } + events := ctx.EventManager().Events() + if len(events) > 0 { + em.EmitEvents(ctx.EventManager().Events()) + } + return bz, err +} + +func HandlePrecompileError(err error, evm *vm.EVM, operation string) { + if err != nil { + evm.StateDB.(*state.DBImpl).SetPrecompileError(err) + metrics.IncrementErrorMetrics(operation, err) + } +} + +func (p Precompile) Prepare(evm *vm.EVM, input []byte) (sdk.Context, *abi.Method, []interface{}, error) { + ctxer, ok := evm.StateDB.(Contexter) + if !ok { + return sdk.Context{}, nil, nil, errors.New("cannot get context from EVM") + } + methodID, err := ExtractMethodID(input) + if err != nil { + return sdk.Context{}, nil, nil, err + } + method, err := p.ABI.MethodById(methodID) + if err != nil { + return sdk.Context{}, nil, nil, err + } + + argsBz := input[4:] + args, err := method.Inputs.Unpack(argsBz) + if err != nil { + return sdk.Context{}, nil, nil, err + } + + return ctxer.Ctx(), method, args, nil +} + +func (p Precompile) GetABI() abi.ABI { + return p.ABI +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return p.name +} + +func (p Precompile) GetExecutor() PrecompileExecutor { + return p.executor +} + +type DynamicGasPrecompileExecutor interface { + Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) + EVMKeeper() EVMKeeper +} + +type DynamicGasPrecompile struct { + *Precompile + executor DynamicGasPrecompileExecutor +} + +var _ vm.DynamicGasPrecompiledContract = &DynamicGasPrecompile{} + +func NewDynamicGasPrecompile(a abi.ABI, executor DynamicGasPrecompileExecutor, address common.Address, name string) *DynamicGasPrecompile { + return &DynamicGasPrecompile{Precompile: NewPrecompile(a, nil, address, name), executor: executor} +} + +func (d DynamicGasPrecompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, hooks *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { + operation := fmt.Sprintf("%s_unknown", d.name) + defer func() { + HandlePrecompileError(err, evm, operation) + if err != nil { + ret = []byte(err.Error()) + err = vm.ErrExecutionReverted + } + }() + ctx, method, args, err := d.Prepare(evm, input) + if err != nil { + return nil, 0, err + } + gasMultipler := d.executor.EVMKeeper().GetPriorityNormalizer(ctx) + gasLimitBigInt := sdk.NewDecFromInt(sdk.NewIntFromUint64(suppliedGas)).Mul(gasMultipler).TruncateInt().BigInt() + if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { + gasLimitBigInt = utils.BigMaxU64 + } + ctx = ctx.WithGasMeter(sdk.NewGasMeterWithMultiplier(ctx, gasLimitBigInt.Uint64())) + + operation = method.Name + em := ctx.EventManager() + ctx = ctx.WithEventManager(sdk.NewEventManager()) + ret, remainingGas, err = d.executor.Execute(ctx, method, caller, callingContract, args, value, readOnly, evm, suppliedGas) + if err != nil { + return ret, remainingGas, err + } + events := ctx.EventManager().Events() + if len(events) > 0 { + em.EmitEvents(ctx.EventManager().Events()) + } + return ret, remainingGas, err +} + +func (d DynamicGasPrecompile) GetExecutor() DynamicGasPrecompileExecutor { + return d.executor +} + +func ValidateArgsLength(args []interface{}, length int) error { + if len(args) != length { + return fmt.Errorf("expected %d arguments but got %d", length, len(args)) + } + + return nil +} + +func ValidateNonPayable(value *big.Int) error { + if value != nil && value.Sign() != 0 { + return errors.New("sending funds to a non-payable function") + } + + return nil +} + +func HandlePaymentUsei(ctx sdk.Context, precompileAddr sdk.AccAddress, payer sdk.AccAddress, value *big.Int, bankKeeper BankKeeper) (sdk.Coin, error) { + usei, wei := state.SplitUseiWeiAmount(value) + if !wei.IsZero() { + return sdk.Coin{}, fmt.Errorf("selected precompile function does not allow payment with non-zero wei remainder: received %s", value) + } + coin := sdk.NewCoin(sdk.MustGetBaseDenom(), usei) + // refund payer because the following precompile logic will debit the payments from payer's account + // this creates a new event manager to avoid surfacing these as cosmos events + if err := bankKeeper.SendCoins(ctx.WithEventManager(sdk.NewEventManager()), precompileAddr, payer, sdk.NewCoins(coin)); err != nil { + return sdk.Coin{}, err + } + return coin, nil +} + +func HandlePaymentUseiWei(ctx sdk.Context, precompileAddr sdk.AccAddress, payer sdk.AccAddress, value *big.Int, bankKeeper BankKeeper) (sdk.Int, sdk.Int, error) { + usei, wei := state.SplitUseiWeiAmount(value) + // refund payer because the following precompile logic will debit the payments from payer's account + // this creates a new event manager to avoid surfacing these as cosmos events + if err := bankKeeper.SendCoinsAndWei(ctx.WithEventManager(sdk.NewEventManager()), precompileAddr, payer, usei, wei); err != nil { + return sdk.Int{}, sdk.Int{}, err + } + return usei, wei, nil +} + +/* +* +sei gas = evm gas * multiplier +sei gas price = fee / sei gas = fee / (evm gas * multiplier) = evm gas / multiplier +*/ +func GetRemainingGas(ctx sdk.Context, evmKeeper EVMKeeper) uint64 { + gasMultipler := evmKeeper.GetPriorityNormalizer(ctx) + seiGasRemaining := ctx.GasMeter().Limit() - ctx.GasMeter().GasConsumedToLimit() + return sdk.NewDecFromInt(sdk.NewIntFromUint64(seiGasRemaining)).Quo(gasMultipler).TruncateInt().Uint64() +} + +func ExtractMethodID(input []byte) ([]byte, error) { + // Check if the input has at least the length needed for methodID + if len(input) < 4 { + return nil, errors.New("input too short to extract method ID") + } + return input[:4], nil +} + +func DefaultGasCost(input []byte, isTransaction bool) uint64 { + if isTransaction { + return storetypes.KVGasConfig().WriteCostFlat + (storetypes.KVGasConfig().WriteCostPerByte * uint64(len(input))) + } + + return storetypes.KVGasConfig().ReadCostFlat + (storetypes.KVGasConfig().ReadCostPerByte * uint64(len(input))) +} + +func MustGetABI(f embed.FS, filename string) abi.ABI { + abiBz, err := f.ReadFile(filename) + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +func GetSeiAddressByEvmAddress(ctx sdk.Context, evmAddress common.Address, evmKeeper EVMKeeper) (sdk.AccAddress, error) { + seiAddr, associated := evmKeeper.GetSeiAddress(ctx, evmAddress) + if !associated { + return nil, types.NewAssociationMissingErr(evmAddress.Hex()) + } + return seiAddr, nil +} + +func GetSeiAddressFromArg(ctx sdk.Context, arg interface{}, evmKeeper EVMKeeper) (sdk.AccAddress, error) { + addr := arg.(common.Address) + if addr == (common.Address{}) { + return nil, errors.New("invalid addr") + } + return GetSeiAddressByEvmAddress(ctx, addr, evmKeeper) +} diff --git a/precompiles/common/legacy/v580/expected_keepers.go b/precompiles/common/legacy/v580/expected_keepers.go new file mode 100644 index 0000000000..70e3d1726f --- /dev/null +++ b/precompiles/common/legacy/v580/expected_keepers.go @@ -0,0 +1,121 @@ +package v580 + +import ( + "context" + + connectiontypes "github.com/cosmos/ibc-go/v3/modules/core/03-connection/types" + "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + ibctypes "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/sei-protocol/sei-chain/utils" + oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" +) + +type BankKeeper interface { + SendCoins(sdk.Context, sdk.AccAddress, sdk.AccAddress, sdk.Coins) error + SendCoinsAndWei(ctx sdk.Context, from sdk.AccAddress, to sdk.AccAddress, amt sdk.Int, wei sdk.Int) error + GetBalance(sdk.Context, sdk.AccAddress, string) sdk.Coin + GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + GetWeiBalance(ctx sdk.Context, addr sdk.AccAddress) sdk.Int + GetDenomMetaData(ctx sdk.Context, denom string) (banktypes.Metadata, bool) + GetSupply(ctx sdk.Context, denom string) sdk.Coin + LockedCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins +} + +type EVMKeeper interface { + GetSeiAddress(sdk.Context, common.Address) (sdk.AccAddress, bool) + GetSeiAddressOrDefault(ctx sdk.Context, evmAddress common.Address) sdk.AccAddress // only used for getting precompile Sei addresses + GetEVMAddress(sdk.Context, sdk.AccAddress) (common.Address, bool) + SetAddressMapping(sdk.Context, sdk.AccAddress, common.Address) + GetCodeHash(sdk.Context, common.Address) common.Hash + 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) + SetCode(ctx sdk.Context, addr common.Address, code []byte) + UpsertERCNativePointer( + ctx sdk.Context, evm *vm.EVM, token string, metadata utils.ERCMetadata, + ) (contractAddr common.Address, err error) + UpsertERCCW20Pointer( + ctx sdk.Context, evm *vm.EVM, cw20Addr string, metadata utils.ERCMetadata, + ) (contractAddr common.Address, err error) + UpsertERCCW721Pointer( + ctx sdk.Context, evm *vm.EVM, cw721Addr string, metadata utils.ERCMetadata, + ) (contractAddr common.Address, err error) + GetEVMGasLimitFromCtx(ctx sdk.Context) uint64 + GetCosmosGasLimitFromEVMGas(ctx sdk.Context, evmGas uint64) uint64 +} + +type AccountKeeper interface { + GetAccount(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI + HasAccount(ctx sdk.Context, addr sdk.AccAddress) bool + SetAccount(ctx sdk.Context, acc authtypes.AccountI) + RemoveAccount(ctx sdk.Context, acc authtypes.AccountI) + NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI +} + +type OracleKeeper interface { + IterateBaseExchangeRates(ctx sdk.Context, handler func(denom string, exchangeRate oracletypes.OracleExchangeRate) (stop bool)) + CalculateTwaps(ctx sdk.Context, lookbackSeconds uint64) (oracletypes.OracleTwaps, error) +} + +type WasmdKeeper interface { + Instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.AccAddress, initMsg []byte, label string, deposit sdk.Coins) (sdk.AccAddress, []byte, error) + Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, msg []byte, coins sdk.Coins) ([]byte, error) +} + +type WasmdViewKeeper interface { + QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) ([]byte, error) +} + +type StakingKeeper interface { + Delegate(goCtx context.Context, msg *stakingtypes.MsgDelegate) (*stakingtypes.MsgDelegateResponse, error) + BeginRedelegate(goCtx context.Context, msg *stakingtypes.MsgBeginRedelegate) (*stakingtypes.MsgBeginRedelegateResponse, error) + Undelegate(goCtx context.Context, msg *stakingtypes.MsgUndelegate) (*stakingtypes.MsgUndelegateResponse, error) +} + +type StakingQuerier interface { + Delegation(c context.Context, req *stakingtypes.QueryDelegationRequest) (*stakingtypes.QueryDelegationResponse, error) +} + +type GovKeeper interface { + AddVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, options govtypes.WeightedVoteOptions) error + AddDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress, depositAmount sdk.Coins) (bool, error) +} + +type DistributionKeeper interface { + SetWithdrawAddr(ctx sdk.Context, delegatorAddr sdk.AccAddress, withdrawAddr sdk.AccAddress) error + WithdrawDelegationRewards(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (sdk.Coins, error) + DelegationTotalRewards(c context.Context, req *distrtypes.QueryDelegationTotalRewardsRequest) (*distrtypes.QueryDelegationTotalRewardsResponse, error) +} + +type TransferKeeper interface { + Transfer(goCtx context.Context, msg *ibctypes.MsgTransfer) (*ibctypes.MsgTransferResponse, error) +} + +type ClientKeeper interface { + GetClientState(ctx sdk.Context, clientID string) (exported.ClientState, bool) + GetClientConsensusState(ctx sdk.Context, clientID string, height exported.Height) (exported.ConsensusState, bool) +} + +type ConnectionKeeper interface { + GetConnection(ctx sdk.Context, connectionID string) (connectiontypes.ConnectionEnd, bool) +} + +type ChannelKeeper interface { + GetChannel(ctx sdk.Context, portID, channelID string) (types.Channel, bool) +} diff --git a/precompiles/common/legacy/v580/precompiles.go b/precompiles/common/legacy/v580/precompiles.go new file mode 100644 index 0000000000..2be6a9f668 --- /dev/null +++ b/precompiles/common/legacy/v580/precompiles.go @@ -0,0 +1,274 @@ +package v580 + +import ( + "bytes" + "embed" + "errors" + "fmt" + "math/big" + + storetypes "github.com/cosmos/cosmos-sdk/store/types" + 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/tracing" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/sei-protocol/sei-chain/utils/metrics" + "github.com/sei-protocol/sei-chain/x/evm/state" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const UnknownMethodCallGas uint64 = 3000 + +type Contexter interface { + Ctx() sdk.Context +} + +type PrecompileExecutor interface { + RequiredGas([]byte, *abi.Method) uint64 + Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM) ([]byte, error) +} + +type Precompile struct { + abi.ABI + address common.Address + name string + executor PrecompileExecutor +} + +var _ vm.PrecompiledContract = &Precompile{} + +func NewPrecompile(a abi.ABI, executor PrecompileExecutor, address common.Address, name string) *Precompile { + return &Precompile{ABI: a, executor: executor, address: address, name: name} +} + +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := ExtractMethodID(input) + if err != nil { + return UnknownMethodCallGas + } + + method, err := p.ABI.MethodById(methodID) + if err != nil { + // This should never happen since this method is going to fail during Run + return UnknownMethodCallGas + } + return p.executor.RequiredGas(input[4:], method) +} + +func (p Precompile) Run(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, value *big.Int, readOnly bool, _ bool) (bz []byte, err error) { + operation := fmt.Sprintf("%s_unknown", p.name) + defer func() { + HandlePrecompileError(err, evm, operation) + if err != nil { + bz = []byte(err.Error()) + err = vm.ErrExecutionReverted + } + }() + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, err + } + + operation = method.Name + em := ctx.EventManager() + ctx = ctx.WithEventManager(sdk.NewEventManager()) + bz, err = p.executor.Execute(ctx, method, caller, callingContract, args, value, readOnly, evm) + if err != nil { + return bz, err + } + events := ctx.EventManager().Events() + if len(events) > 0 { + em.EmitEvents(ctx.EventManager().Events()) + } + return bz, err +} + +func HandlePrecompileError(err error, evm *vm.EVM, operation string) { + if err != nil { + evm.StateDB.(*state.DBImpl).SetPrecompileError(err) + metrics.IncrementErrorMetrics(operation, err) + } +} + +func (p Precompile) Prepare(evm *vm.EVM, input []byte) (sdk.Context, *abi.Method, []interface{}, error) { + ctxer, ok := evm.StateDB.(Contexter) + if !ok { + return sdk.Context{}, nil, nil, errors.New("cannot get context from EVM") + } + methodID, err := ExtractMethodID(input) + if err != nil { + return sdk.Context{}, nil, nil, err + } + method, err := p.ABI.MethodById(methodID) + if err != nil { + return sdk.Context{}, nil, nil, err + } + + argsBz := input[4:] + args, err := method.Inputs.Unpack(argsBz) + if err != nil { + return sdk.Context{}, nil, nil, err + } + + return ctxer.Ctx(), method, args, nil +} + +func (p Precompile) GetABI() abi.ABI { + return p.ABI +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return p.name +} + +func (p Precompile) GetExecutor() PrecompileExecutor { + return p.executor +} + +type DynamicGasPrecompileExecutor interface { + Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) + EVMKeeper() EVMKeeper +} + +type DynamicGasPrecompile struct { + *Precompile + executor DynamicGasPrecompileExecutor +} + +var _ vm.DynamicGasPrecompiledContract = &DynamicGasPrecompile{} + +func NewDynamicGasPrecompile(a abi.ABI, executor DynamicGasPrecompileExecutor, address common.Address, name string) *DynamicGasPrecompile { + return &DynamicGasPrecompile{Precompile: NewPrecompile(a, nil, address, name), executor: executor} +} + +func (d DynamicGasPrecompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, hooks *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { + operation := fmt.Sprintf("%s_unknown", d.name) + defer func() { + HandlePrecompileError(err, evm, operation) + if err != nil { + ret = []byte(err.Error()) + err = vm.ErrExecutionReverted + } + }() + ctx, method, args, err := d.Prepare(evm, input) + if err != nil { + return nil, 0, err + } + ctx = ctx.WithGasMeter(sdk.NewGasMeterWithMultiplier(ctx, d.executor.EVMKeeper().GetCosmosGasLimitFromEVMGas(ctx, suppliedGas))) + + operation = method.Name + em := ctx.EventManager() + ctx = ctx.WithEventManager(sdk.NewEventManager()) + ret, remainingGas, err = d.executor.Execute(ctx, method, caller, callingContract, args, value, readOnly, evm, suppliedGas) + if err != nil { + return ret, remainingGas, err + } + events := ctx.EventManager().Events() + if len(events) > 0 { + em.EmitEvents(ctx.EventManager().Events()) + } + return ret, remainingGas, err +} + +func (d DynamicGasPrecompile) GetExecutor() DynamicGasPrecompileExecutor { + return d.executor +} + +func ValidateArgsLength(args []interface{}, length int) error { + if len(args) != length { + return fmt.Errorf("expected %d arguments but got %d", length, len(args)) + } + + return nil +} + +func ValidateNonPayable(value *big.Int) error { + if value != nil && value.Sign() != 0 { + return errors.New("sending funds to a non-payable function") + } + + return nil +} + +func HandlePaymentUsei(ctx sdk.Context, precompileAddr sdk.AccAddress, payer sdk.AccAddress, value *big.Int, bankKeeper BankKeeper) (sdk.Coin, error) { + usei, wei := state.SplitUseiWeiAmount(value) + if !wei.IsZero() { + return sdk.Coin{}, fmt.Errorf("selected precompile function does not allow payment with non-zero wei remainder: received %s", value) + } + coin := sdk.NewCoin(sdk.MustGetBaseDenom(), usei) + // refund payer because the following precompile logic will debit the payments from payer's account + // this creates a new event manager to avoid surfacing these as cosmos events + if err := bankKeeper.SendCoins(ctx.WithEventManager(sdk.NewEventManager()), precompileAddr, payer, sdk.NewCoins(coin)); err != nil { + return sdk.Coin{}, err + } + return coin, nil +} + +func HandlePaymentUseiWei(ctx sdk.Context, precompileAddr sdk.AccAddress, payer sdk.AccAddress, value *big.Int, bankKeeper BankKeeper) (sdk.Int, sdk.Int, error) { + usei, wei := state.SplitUseiWeiAmount(value) + // refund payer because the following precompile logic will debit the payments from payer's account + // this creates a new event manager to avoid surfacing these as cosmos events + if err := bankKeeper.SendCoinsAndWei(ctx.WithEventManager(sdk.NewEventManager()), precompileAddr, payer, usei, wei); err != nil { + return sdk.Int{}, sdk.Int{}, err + } + return usei, wei, nil +} + +/* +* +sei gas = evm gas * multiplier +sei gas price = fee / sei gas = fee / (evm gas * multiplier) = evm gas / multiplier +*/ +func GetRemainingGas(ctx sdk.Context, evmKeeper EVMKeeper) uint64 { + return evmKeeper.GetEVMGasLimitFromCtx(ctx) +} + +func ExtractMethodID(input []byte) ([]byte, error) { + // Check if the input has at least the length needed for methodID + if len(input) < 4 { + return nil, errors.New("input too short to extract method ID") + } + return input[:4], nil +} + +func DefaultGasCost(input []byte, isTransaction bool) uint64 { + if isTransaction { + return storetypes.KVGasConfig().WriteCostFlat + (storetypes.KVGasConfig().WriteCostPerByte * uint64(len(input))) + } + + return storetypes.KVGasConfig().ReadCostFlat + (storetypes.KVGasConfig().ReadCostPerByte * uint64(len(input))) +} + +func MustGetABI(f embed.FS, filename string) abi.ABI { + abiBz, err := f.ReadFile(filename) + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +func GetSeiAddressByEvmAddress(ctx sdk.Context, evmAddress common.Address, evmKeeper EVMKeeper) (sdk.AccAddress, error) { + seiAddr, associated := evmKeeper.GetSeiAddress(ctx, evmAddress) + if !associated { + return nil, types.NewAssociationMissingErr(evmAddress.Hex()) + } + return seiAddr, nil +} + +func GetSeiAddressFromArg(ctx sdk.Context, arg interface{}, evmKeeper EVMKeeper) (sdk.AccAddress, error) { + addr := arg.(common.Address) + if addr == (common.Address{}) { + return nil, errors.New("invalid addr") + } + return GetSeiAddressByEvmAddress(ctx, addr, evmKeeper) +} diff --git a/precompiles/common/legacy/v600/expected_keepers.go b/precompiles/common/legacy/v600/expected_keepers.go new file mode 100644 index 0000000000..9e2daee9f5 --- /dev/null +++ b/precompiles/common/legacy/v600/expected_keepers.go @@ -0,0 +1,121 @@ +package v600 + +import ( + "context" + + connectiontypes "github.com/cosmos/ibc-go/v3/modules/core/03-connection/types" + "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v3/modules/core/exported" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + ibctypes "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/sei-protocol/sei-chain/utils" + oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" +) + +type BankKeeper interface { + SendCoins(sdk.Context, sdk.AccAddress, sdk.AccAddress, sdk.Coins) error + SendCoinsAndWei(ctx sdk.Context, from sdk.AccAddress, to sdk.AccAddress, amt sdk.Int, wei sdk.Int) error + GetBalance(sdk.Context, sdk.AccAddress, string) sdk.Coin + GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + GetWeiBalance(ctx sdk.Context, addr sdk.AccAddress) sdk.Int + GetDenomMetaData(ctx sdk.Context, denom string) (banktypes.Metadata, bool) + GetSupply(ctx sdk.Context, denom string) sdk.Coin + LockedCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins +} + +type EVMKeeper interface { + GetSeiAddress(sdk.Context, common.Address) (sdk.AccAddress, bool) + GetSeiAddressOrDefault(ctx sdk.Context, evmAddress common.Address) sdk.AccAddress // only used for getting precompile Sei addresses + GetEVMAddress(sdk.Context, sdk.AccAddress) (common.Address, bool) + SetAddressMapping(sdk.Context, sdk.AccAddress, common.Address) + GetCodeHash(sdk.Context, common.Address) common.Hash + 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) + SetCode(ctx sdk.Context, addr common.Address, code []byte) + UpsertERCNativePointer( + ctx sdk.Context, evm *vm.EVM, token string, metadata utils.ERCMetadata, + ) (contractAddr common.Address, err error) + UpsertERCCW20Pointer( + ctx sdk.Context, evm *vm.EVM, cw20Addr string, metadata utils.ERCMetadata, + ) (contractAddr common.Address, err error) + UpsertERCCW721Pointer( + ctx sdk.Context, evm *vm.EVM, cw721Addr string, metadata utils.ERCMetadata, + ) (contractAddr common.Address, err error) + GetEVMGasLimitFromCtx(ctx sdk.Context) uint64 + GetCosmosGasLimitFromEVMGas(ctx sdk.Context, evmGas uint64) uint64 +} + +type AccountKeeper interface { + GetAccount(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI + HasAccount(ctx sdk.Context, addr sdk.AccAddress) bool + SetAccount(ctx sdk.Context, acc authtypes.AccountI) + RemoveAccount(ctx sdk.Context, acc authtypes.AccountI) + NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI +} + +type OracleKeeper interface { + IterateBaseExchangeRates(ctx sdk.Context, handler func(denom string, exchangeRate oracletypes.OracleExchangeRate) (stop bool)) + CalculateTwaps(ctx sdk.Context, lookbackSeconds uint64) (oracletypes.OracleTwaps, error) +} + +type WasmdKeeper interface { + Instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.AccAddress, initMsg []byte, label string, deposit sdk.Coins) (sdk.AccAddress, []byte, error) + Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, msg []byte, coins sdk.Coins) ([]byte, error) +} + +type WasmdViewKeeper interface { + QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) ([]byte, error) +} + +type StakingKeeper interface { + Delegate(goCtx context.Context, msg *stakingtypes.MsgDelegate) (*stakingtypes.MsgDelegateResponse, error) + BeginRedelegate(goCtx context.Context, msg *stakingtypes.MsgBeginRedelegate) (*stakingtypes.MsgBeginRedelegateResponse, error) + Undelegate(goCtx context.Context, msg *stakingtypes.MsgUndelegate) (*stakingtypes.MsgUndelegateResponse, error) +} + +type StakingQuerier interface { + Delegation(c context.Context, req *stakingtypes.QueryDelegationRequest) (*stakingtypes.QueryDelegationResponse, error) +} + +type GovKeeper interface { + AddVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, options govtypes.WeightedVoteOptions) error + AddDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress, depositAmount sdk.Coins) (bool, error) +} + +type DistributionKeeper interface { + SetWithdrawAddr(ctx sdk.Context, delegatorAddr sdk.AccAddress, withdrawAddr sdk.AccAddress) error + WithdrawDelegationRewards(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (sdk.Coins, error) + DelegationTotalRewards(c context.Context, req *distrtypes.QueryDelegationTotalRewardsRequest) (*distrtypes.QueryDelegationTotalRewardsResponse, error) +} + +type TransferKeeper interface { + Transfer(goCtx context.Context, msg *ibctypes.MsgTransfer) (*ibctypes.MsgTransferResponse, error) +} + +type ClientKeeper interface { + GetClientState(ctx sdk.Context, clientID string) (exported.ClientState, bool) + GetClientConsensusState(ctx sdk.Context, clientID string, height exported.Height) (exported.ConsensusState, bool) +} + +type ConnectionKeeper interface { + GetConnection(ctx sdk.Context, connectionID string) (connectiontypes.ConnectionEnd, bool) +} + +type ChannelKeeper interface { + GetChannel(ctx sdk.Context, portID, channelID string) (types.Channel, bool) +} diff --git a/precompiles/common/legacy/v600/precompiles.go b/precompiles/common/legacy/v600/precompiles.go new file mode 100644 index 0000000000..3b7cbf3029 --- /dev/null +++ b/precompiles/common/legacy/v600/precompiles.go @@ -0,0 +1,276 @@ +package v600 + +import ( + "bytes" + "embed" + "errors" + "fmt" + "math/big" + + storetypes "github.com/cosmos/cosmos-sdk/store/types" + 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/tracing" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/sei-protocol/sei-chain/utils/metrics" + "github.com/sei-protocol/sei-chain/x/evm/state" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const UnknownMethodCallGas uint64 = 3000 + +type Contexter interface { + Ctx() sdk.Context +} + +type PrecompileExecutor interface { + RequiredGas([]byte, *abi.Method) uint64 + Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM) ([]byte, error) +} + +type Precompile struct { + abi.ABI + address common.Address + name string + executor PrecompileExecutor +} + +var _ vm.PrecompiledContract = &Precompile{} + +func NewPrecompile(a abi.ABI, executor PrecompileExecutor, address common.Address, name string) *Precompile { + return &Precompile{ABI: a, executor: executor, address: address, name: name} +} + +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := ExtractMethodID(input) + if err != nil { + return UnknownMethodCallGas + } + + method, err := p.ABI.MethodById(methodID) + if err != nil { + // This should never happen since this method is going to fail during Run + return UnknownMethodCallGas + } + return p.executor.RequiredGas(input[4:], method) +} + +func (p Precompile) Run(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, value *big.Int, readOnly bool, isFromDelegateCall bool) (bz []byte, err error) { + operation := fmt.Sprintf("%s_unknown", p.name) + defer func() { + HandlePrecompileError(err, evm, operation) + if err != nil { + bz = []byte(err.Error()) + err = vm.ErrExecutionReverted + } + }() + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, err + } + + operation = method.Name + em := ctx.EventManager() + ctx = ctx.WithEventManager(sdk.NewEventManager()) + ctx = ctx.WithEVMPrecompileCalledFromDelegateCall(isFromDelegateCall) + bz, err = p.executor.Execute(ctx, method, caller, callingContract, args, value, readOnly, evm) + if err != nil { + return bz, err + } + events := ctx.EventManager().Events() + if len(events) > 0 { + em.EmitEvents(ctx.EventManager().Events()) + } + return bz, err +} + +func HandlePrecompileError(err error, evm *vm.EVM, operation string) { + if err != nil { + evm.StateDB.(*state.DBImpl).SetPrecompileError(err) + metrics.IncrementErrorMetrics(operation, err) + } +} + +func (p Precompile) Prepare(evm *vm.EVM, input []byte) (sdk.Context, *abi.Method, []interface{}, error) { + ctxer, ok := evm.StateDB.(Contexter) + if !ok { + return sdk.Context{}, nil, nil, errors.New("cannot get context from EVM") + } + methodID, err := ExtractMethodID(input) + if err != nil { + return sdk.Context{}, nil, nil, err + } + method, err := p.ABI.MethodById(methodID) + if err != nil { + return sdk.Context{}, nil, nil, err + } + + argsBz := input[4:] + args, err := method.Inputs.Unpack(argsBz) + if err != nil { + return sdk.Context{}, nil, nil, err + } + + return ctxer.Ctx(), method, args, nil +} + +func (p Precompile) GetABI() abi.ABI { + return p.ABI +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return p.name +} + +func (p Precompile) GetExecutor() PrecompileExecutor { + return p.executor +} + +type DynamicGasPrecompileExecutor interface { + Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) + EVMKeeper() EVMKeeper +} + +type DynamicGasPrecompile struct { + *Precompile + executor DynamicGasPrecompileExecutor +} + +var _ vm.DynamicGasPrecompiledContract = &DynamicGasPrecompile{} + +func NewDynamicGasPrecompile(a abi.ABI, executor DynamicGasPrecompileExecutor, address common.Address, name string) *DynamicGasPrecompile { + return &DynamicGasPrecompile{Precompile: NewPrecompile(a, nil, address, name), executor: executor} +} + +func (d DynamicGasPrecompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, hooks *tracing.Hooks, readOnly bool, isFromDelegateCall bool) (ret []byte, remainingGas uint64, err error) { + operation := fmt.Sprintf("%s_unknown", d.name) + defer func() { + HandlePrecompileError(err, evm, operation) + if err != nil { + ret = []byte(err.Error()) + err = vm.ErrExecutionReverted + } + }() + ctx, method, args, err := d.Prepare(evm, input) + if err != nil { + return nil, 0, err + } + ctx = ctx.WithGasMeter(sdk.NewGasMeterWithMultiplier(ctx, d.executor.EVMKeeper().GetCosmosGasLimitFromEVMGas(ctx, suppliedGas))) + + operation = method.Name + em := ctx.EventManager() + ctx = ctx.WithEventManager(sdk.NewEventManager()) + ctx = ctx.WithEVMPrecompileCalledFromDelegateCall(isFromDelegateCall) + ret, remainingGas, err = d.executor.Execute(ctx, method, caller, callingContract, args, value, readOnly, evm, suppliedGas) + if err != nil { + return ret, remainingGas, err + } + events := ctx.EventManager().Events() + if len(events) > 0 { + em.EmitEvents(ctx.EventManager().Events()) + } + return ret, remainingGas, err +} + +func (d DynamicGasPrecompile) GetExecutor() DynamicGasPrecompileExecutor { + return d.executor +} + +func ValidateArgsLength(args []interface{}, length int) error { + if len(args) != length { + return fmt.Errorf("expected %d arguments but got %d", length, len(args)) + } + + return nil +} + +func ValidateNonPayable(value *big.Int) error { + if value != nil && value.Sign() != 0 { + return errors.New("sending funds to a non-payable function") + } + + return nil +} + +func HandlePaymentUsei(ctx sdk.Context, precompileAddr sdk.AccAddress, payer sdk.AccAddress, value *big.Int, bankKeeper BankKeeper) (sdk.Coin, error) { + usei, wei := state.SplitUseiWeiAmount(value) + if !wei.IsZero() { + return sdk.Coin{}, fmt.Errorf("selected precompile function does not allow payment with non-zero wei remainder: received %s", value) + } + coin := sdk.NewCoin(sdk.MustGetBaseDenom(), usei) + // refund payer because the following precompile logic will debit the payments from payer's account + // this creates a new event manager to avoid surfacing these as cosmos events + if err := bankKeeper.SendCoins(ctx.WithEventManager(sdk.NewEventManager()), precompileAddr, payer, sdk.NewCoins(coin)); err != nil { + return sdk.Coin{}, err + } + return coin, nil +} + +func HandlePaymentUseiWei(ctx sdk.Context, precompileAddr sdk.AccAddress, payer sdk.AccAddress, value *big.Int, bankKeeper BankKeeper) (sdk.Int, sdk.Int, error) { + usei, wei := state.SplitUseiWeiAmount(value) + // refund payer because the following precompile logic will debit the payments from payer's account + // this creates a new event manager to avoid surfacing these as cosmos events + if err := bankKeeper.SendCoinsAndWei(ctx.WithEventManager(sdk.NewEventManager()), precompileAddr, payer, usei, wei); err != nil { + return sdk.Int{}, sdk.Int{}, err + } + return usei, wei, nil +} + +/* +* +sei gas = evm gas * multiplier +sei gas price = fee / sei gas = fee / (evm gas * multiplier) = evm gas / multiplier +*/ +func GetRemainingGas(ctx sdk.Context, evmKeeper EVMKeeper) uint64 { + return evmKeeper.GetEVMGasLimitFromCtx(ctx) +} + +func ExtractMethodID(input []byte) ([]byte, error) { + // Check if the input has at least the length needed for methodID + if len(input) < 4 { + return nil, errors.New("input too short to extract method ID") + } + return input[:4], nil +} + +func DefaultGasCost(input []byte, isTransaction bool) uint64 { + if isTransaction { + return storetypes.KVGasConfig().WriteCostFlat + (storetypes.KVGasConfig().WriteCostPerByte * uint64(len(input))) + } + + return storetypes.KVGasConfig().ReadCostFlat + (storetypes.KVGasConfig().ReadCostPerByte * uint64(len(input))) +} + +func MustGetABI(f embed.FS, filename string) abi.ABI { + abiBz, err := f.ReadFile(filename) + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +func GetSeiAddressByEvmAddress(ctx sdk.Context, evmAddress common.Address, evmKeeper EVMKeeper) (sdk.AccAddress, error) { + seiAddr, associated := evmKeeper.GetSeiAddress(ctx, evmAddress) + if !associated { + return nil, types.NewAssociationMissingErr(evmAddress.Hex()) + } + return seiAddr, nil +} + +func GetSeiAddressFromArg(ctx sdk.Context, arg interface{}, evmKeeper EVMKeeper) (sdk.AccAddress, error) { + addr := arg.(common.Address) + if addr == (common.Address{}) { + return nil, errors.New("invalid addr") + } + return GetSeiAddressByEvmAddress(ctx, addr, evmKeeper) +} diff --git a/precompiles/distribution/legacy/v520/abi.json b/precompiles/distribution/legacy/v520/abi.json new file mode 100644 index 0000000000..23b0abd2df --- /dev/null +++ b/precompiles/distribution/legacy/v520/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"withdrawAddr","type":"address"}],"name":"setWithdrawAddress","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"validator","type":"string"}],"name":"withdrawDelegationRewards","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/precompiles/distribution/legacy/v520/distribution.go b/precompiles/distribution/legacy/v520/distribution.go new file mode 100644 index 0000000000..f81dccf2b9 --- /dev/null +++ b/precompiles/distribution/legacy/v520/distribution.go @@ -0,0 +1,180 @@ +package v520 + +import ( + "bytes" + "embed" + "errors" + "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/legacy/v520" +) + +const ( + SetWithdrawAddressMethod = "setWithdrawAddress" + WithdrawDelegationRewardsMethod = "withdrawDelegationRewards" +) + +const ( + DistrAddress = "0x0000000000000000000000000000000000001007" +) + +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 + +func GetABI() abi.ABI { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +type Precompile struct { + pcommon.Precompile + distrKeeper pcommon.DistributionKeeper + evmKeeper pcommon.EVMKeeper + address common.Address + + SetWithdrawAddrID []byte + WithdrawDelegationRewardsID []byte +} + +func NewPrecompile(distrKeeper pcommon.DistributionKeeper, evmKeeper pcommon.EVMKeeper) (*Precompile, error) { + newAbi := GetABI() + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + distrKeeper: distrKeeper, + evmKeeper: evmKeeper, + address: common.HexToAddress(DistrAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case SetWithdrawAddressMethod: + p.SetWithdrawAddrID = m.ID + case WithdrawDelegationRewardsMethod: + p.WithdrawDelegationRewardsID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return pcommon.UnknownMethodCallGas + } + + if bytes.Equal(methodID, p.SetWithdrawAddrID) { + return 30000 + } else if bytes.Equal(methodID, p.WithdrawDelegationRewardsID) { + return 50000 + } + + // This should never happen since this is going to fail during Run + return pcommon.UnknownMethodCallGas +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "distribution" +} + +func (p Precompile) Run(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, value *big.Int, readOnly bool, _ bool) (bz []byte, err error) { + if readOnly { + return nil, errors.New("cannot call distr precompile from staticcall") + } + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, err + } + if caller.Cmp(callingContract) != 0 { + return nil, errors.New("cannot delegatecall distr") + } + + switch method.Name { + case SetWithdrawAddressMethod: + return p.setWithdrawAddress(ctx, method, caller, args, value) + case WithdrawDelegationRewardsMethod: + return p.withdrawDelegationRewards(ctx, method, caller, args, value) + } + return +} + +func (p Precompile) setWithdrawAddress(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + delegator, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + return nil, fmt.Errorf("delegator %s is not associated", caller.Hex()) + } + withdrawAddr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + return nil, err + } + err = p.distrKeeper.SetWithdrawAddr(ctx, delegator, withdrawAddr) + if err != nil { + return nil, err + } + return method.Outputs.Pack(true) +} + +func (p Precompile) withdrawDelegationRewards(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + delegator, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + return nil, fmt.Errorf("delegator %s is not associated", caller.Hex()) + } + validator, err := sdk.ValAddressFromBech32(args[0].(string)) + if err != nil { + return nil, err + } + _, err = p.distrKeeper.WithdrawDelegationRewards(ctx, delegator, validator) + if err != nil { + return nil, err + } + return method.Outputs.Pack(true) +} + +func (p Precompile) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { + addr := arg.(common.Address) + if addr == (common.Address{}) { + return nil, errors.New("invalid addr") + } + seiAddr, associated := p.evmKeeper.GetSeiAddress(ctx, addr) + if !associated { + return nil, errors.New("cannot use an unassociated address as withdraw address") + } + return seiAddr, nil +} diff --git a/precompiles/distribution/legacy/v552/abi.json b/precompiles/distribution/legacy/v552/abi.json new file mode 100644 index 0000000000..23b0abd2df --- /dev/null +++ b/precompiles/distribution/legacy/v552/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"withdrawAddr","type":"address"}],"name":"setWithdrawAddress","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"validator","type":"string"}],"name":"withdrawDelegationRewards","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/precompiles/distribution/legacy/v552/distribution.go b/precompiles/distribution/legacy/v552/distribution.go new file mode 100644 index 0000000000..e8e222ef32 --- /dev/null +++ b/precompiles/distribution/legacy/v552/distribution.go @@ -0,0 +1,186 @@ +package v552 + +import ( + "bytes" + "embed" + "errors" + "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/legacy/v555" + "github.com/sei-protocol/sei-chain/x/evm/state" +) + +const ( + SetWithdrawAddressMethod = "setWithdrawAddress" + WithdrawDelegationRewardsMethod = "withdrawDelegationRewards" +) + +const ( + DistrAddress = "0x0000000000000000000000000000000000001007" +) + +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 + +func GetABI() abi.ABI { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +type Precompile struct { + pcommon.Precompile + distrKeeper pcommon.DistributionKeeper + evmKeeper pcommon.EVMKeeper + address common.Address + + SetWithdrawAddrID []byte + WithdrawDelegationRewardsID []byte +} + +func NewPrecompile(distrKeeper pcommon.DistributionKeeper, evmKeeper pcommon.EVMKeeper) (*Precompile, error) { + newAbi := GetABI() + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + distrKeeper: distrKeeper, + evmKeeper: evmKeeper, + address: common.HexToAddress(DistrAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case SetWithdrawAddressMethod: + p.SetWithdrawAddrID = m.ID + case WithdrawDelegationRewardsMethod: + p.WithdrawDelegationRewardsID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return pcommon.UnknownMethodCallGas + } + + if bytes.Equal(methodID, p.SetWithdrawAddrID) { + return 30000 + } else if bytes.Equal(methodID, p.WithdrawDelegationRewardsID) { + return 50000 + } + + // This should never happen since this is going to fail during Run + return pcommon.UnknownMethodCallGas +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "distribution" +} + +func (p Precompile) Run(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, value *big.Int, readOnly bool, _ bool) (bz []byte, err error) { + defer func() { + if err != nil { + evm.StateDB.(*state.DBImpl).SetPrecompileError(err) + } + }() + if readOnly { + return nil, errors.New("cannot call distr precompile from staticcall") + } + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, err + } + if caller.Cmp(callingContract) != 0 { + return nil, errors.New("cannot delegatecall distr") + } + + switch method.Name { + case SetWithdrawAddressMethod: + return p.setWithdrawAddress(ctx, method, caller, args, value) + case WithdrawDelegationRewardsMethod: + return p.withdrawDelegationRewards(ctx, method, caller, args, value) + } + return +} + +func (p Precompile) setWithdrawAddress(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + delegator, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + return nil, fmt.Errorf("delegator %s is not associated", caller.Hex()) + } + withdrawAddr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + return nil, err + } + err = p.distrKeeper.SetWithdrawAddr(ctx, delegator, withdrawAddr) + if err != nil { + return nil, err + } + return method.Outputs.Pack(true) +} + +func (p Precompile) withdrawDelegationRewards(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + delegator, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + return nil, fmt.Errorf("delegator %s is not associated", caller.Hex()) + } + validator, err := sdk.ValAddressFromBech32(args[0].(string)) + if err != nil { + return nil, err + } + _, err = p.distrKeeper.WithdrawDelegationRewards(ctx, delegator, validator) + if err != nil { + return nil, err + } + return method.Outputs.Pack(true) +} + +func (p Precompile) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { + addr := arg.(common.Address) + if addr == (common.Address{}) { + return nil, errors.New("invalid addr") + } + seiAddr, associated := p.evmKeeper.GetSeiAddress(ctx, addr) + if !associated { + return nil, errors.New("cannot use an unassociated address as withdraw address") + } + return seiAddr, nil +} diff --git a/precompiles/distribution/legacy/v555/abi.json b/precompiles/distribution/legacy/v555/abi.json new file mode 100644 index 0000000000..9b0760de83 --- /dev/null +++ b/precompiles/distribution/legacy/v555/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"withdrawAddr","type":"address"}],"name":"setWithdrawAddress","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"validator","type":"string"}],"name":"withdrawDelegationRewards","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string[]","name":"validators","type":"string[]"}],"name":"withdrawMultipleDelegationRewards","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/precompiles/distribution/legacy/v555/distribution.go b/precompiles/distribution/legacy/v555/distribution.go new file mode 100644 index 0000000000..10578fdef7 --- /dev/null +++ b/precompiles/distribution/legacy/v555/distribution.go @@ -0,0 +1,298 @@ +package v555 + +import ( + "bytes" + "embed" + "errors" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/sei-protocol/sei-chain/utils" + + 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/legacy/v555" + "github.com/sei-protocol/sei-chain/x/evm/state" +) + +const ( + SetWithdrawAddressMethod = "setWithdrawAddress" + WithdrawDelegationRewardsMethod = "withdrawDelegationRewards" + WithdrawMultipleDelegationRewardsMethod = "withdrawMultipleDelegationRewards" +) + +const ( + DistrAddress = "0x0000000000000000000000000000000000001007" +) + +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 + +func GetABI() abi.ABI { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +type Precompile struct { + pcommon.Precompile + distrKeeper pcommon.DistributionKeeper + evmKeeper pcommon.EVMKeeper + address common.Address + + SetWithdrawAddrID []byte + WithdrawDelegationRewardsID []byte + WithdrawMultipleDelegationRewardsID []byte +} + +func NewPrecompile(distrKeeper pcommon.DistributionKeeper, evmKeeper pcommon.EVMKeeper) (*Precompile, error) { + newAbi := GetABI() + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + distrKeeper: distrKeeper, + evmKeeper: evmKeeper, + address: common.HexToAddress(DistrAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case SetWithdrawAddressMethod: + p.SetWithdrawAddrID = m.ID + case WithdrawDelegationRewardsMethod: + p.WithdrawDelegationRewardsID = m.ID + case WithdrawMultipleDelegationRewardsMethod: + p.WithdrawMultipleDelegationRewardsID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return pcommon.UnknownMethodCallGas + } + + method, err := p.ABI.MethodById(methodID) + if err != nil { + // This should never happen since this method is going to fail during Run + return pcommon.UnknownMethodCallGas + } + + return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) +} + +func (Precompile) IsTransaction(method string) bool { + switch method { + case SetWithdrawAddressMethod: + return true + case WithdrawDelegationRewardsMethod: + return true + case WithdrawMultipleDelegationRewardsMethod: + return true + default: + return false + } +} + +func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, _ common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, _ bool, _ bool) (ret []byte, remainingGas uint64, err error) { + defer func() { + if err != nil { + evm.StateDB.(*state.DBImpl).SetPrecompileError(err) + } + }() + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, 0, err + } + + gasMultiplier := p.evmKeeper.GetPriorityNormalizer(ctx) + gasLimitBigInt := sdk.NewDecFromInt(sdk.NewIntFromUint64(suppliedGas)).Mul(gasMultiplier).TruncateInt().BigInt() + if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { + gasLimitBigInt = utils.BigMaxU64 + } + ctx = ctx.WithGasMeter(sdk.NewGasMeterWithMultiplier(ctx, gasLimitBigInt.Uint64())) + + switch method.Name { + case SetWithdrawAddressMethod: + return p.setWithdrawAddress(ctx, method, caller, args, value) + case WithdrawDelegationRewardsMethod: + return p.withdrawDelegationRewards(ctx, method, caller, args, value) + case WithdrawMultipleDelegationRewardsMethod: + return p.withdrawMultipleDelegationRewards(ctx, method, caller, args, value) + + } + return +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "distribution" +} + +func (p Precompile) Run(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, value *big.Int, readOnly bool, _ bool) (bz []byte, err error) { + panic("static gas Run is not implemented for dynamic gas precompile") +} + +func (p Precompile) setWithdrawAddress(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if err := pcommon.ValidateNonPayable(value); err != nil { + rerr = err + return + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + rerr = err + return + } + delegator, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + rerr = fmt.Errorf("delegator %s is not associated", caller.Hex()) + return + } + withdrawAddr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + rerr = err + return + } + err = p.distrKeeper.SetWithdrawAddr(ctx, delegator, withdrawAddr) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(true) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p Precompile) withdrawDelegationRewards(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + err := p.validateInput(value, args, 1) + if err != nil { + rerr = err + return + } + + delegator, err := p.getDelegator(ctx, caller) + if err != nil { + rerr = err + return + } + _, err = p.withdraw(ctx, delegator, args[0].(string)) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(true) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p Precompile) validateInput(value *big.Int, args []interface{}, expectedArgsLength int) error { + if err := pcommon.ValidateNonPayable(value); err != nil { + return err + } + + if err := pcommon.ValidateArgsLength(args, expectedArgsLength); err != nil { + return err + } + + return nil +} + +func (p Precompile) withdraw(ctx sdk.Context, delegator sdk.AccAddress, validatorAddress string) (sdk.Coins, error) { + validator, err := sdk.ValAddressFromBech32(validatorAddress) + if err != nil { + return nil, err + } + return p.distrKeeper.WithdrawDelegationRewards(ctx, delegator, validator) +} + +func (p Precompile) getDelegator(ctx sdk.Context, caller common.Address) (sdk.AccAddress, error) { + delegator, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + return nil, fmt.Errorf("delegator %s is not associated", caller.Hex()) + } + + return delegator, nil +} + +func (p Precompile) withdrawMultipleDelegationRewards(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + err := p.validateInput(value, args, 1) + if err != nil { + rerr = err + return + } + + delegator, err := p.getDelegator(ctx, caller) + if err != nil { + rerr = err + return + } + validators := args[0].([]string) + for _, valAddr := range validators { + _, err := p.withdraw(ctx, delegator, valAddr) + if err != nil { + rerr = err + return + } + } + + ret, rerr = method.Outputs.Pack(true) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p Precompile) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { + addr := arg.(common.Address) + if addr == (common.Address{}) { + return nil, errors.New("invalid addr") + } + seiAddr, associated := p.evmKeeper.GetSeiAddress(ctx, addr) + if !associated { + return nil, errors.New("cannot use an unassociated address as withdraw address") + } + return seiAddr, nil +} diff --git a/precompiles/distribution/legacy/v562/abi.json b/precompiles/distribution/legacy/v562/abi.json new file mode 100644 index 0000000000..9b0760de83 --- /dev/null +++ b/precompiles/distribution/legacy/v562/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"withdrawAddr","type":"address"}],"name":"setWithdrawAddress","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"validator","type":"string"}],"name":"withdrawDelegationRewards","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string[]","name":"validators","type":"string[]"}],"name":"withdrawMultipleDelegationRewards","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/precompiles/distribution/legacy/v562/distribution.go b/precompiles/distribution/legacy/v562/distribution.go new file mode 100644 index 0000000000..495110f32c --- /dev/null +++ b/precompiles/distribution/legacy/v562/distribution.go @@ -0,0 +1,242 @@ +package v562 + +import ( + "bytes" + "embed" + "errors" + "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/legacy/v562" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const ( + SetWithdrawAddressMethod = "setWithdrawAddress" + WithdrawDelegationRewardsMethod = "withdrawDelegationRewards" + WithdrawMultipleDelegationRewardsMethod = "withdrawMultipleDelegationRewards" +) + +const ( + DistrAddress = "0x0000000000000000000000000000000000001007" +) + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +func GetABI() abi.ABI { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +type PrecompileExecutor struct { + distrKeeper pcommon.DistributionKeeper + evmKeeper pcommon.EVMKeeper + address common.Address + + SetWithdrawAddrID []byte + WithdrawDelegationRewardsID []byte + WithdrawMultipleDelegationRewardsID []byte +} + +func NewPrecompile(distrKeeper pcommon.DistributionKeeper, evmKeeper pcommon.EVMKeeper) (*pcommon.DynamicGasPrecompile, error) { + newAbi := GetABI() + + p := &PrecompileExecutor{ + distrKeeper: distrKeeper, + evmKeeper: evmKeeper, + address: common.HexToAddress(DistrAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case SetWithdrawAddressMethod: + p.SetWithdrawAddrID = m.ID + case WithdrawDelegationRewardsMethod: + p.WithdrawDelegationRewardsID = m.ID + case WithdrawMultipleDelegationRewardsMethod: + p.WithdrawMultipleDelegationRewardsID = m.ID + } + } + + return pcommon.NewDynamicGasPrecompile(newAbi, p, p.address, "distribution"), nil +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + switch method.Name { + case SetWithdrawAddressMethod: + return p.setWithdrawAddress(ctx, method, caller, args, value) + case WithdrawDelegationRewardsMethod: + return p.withdrawDelegationRewards(ctx, method, caller, args, value) + case WithdrawMultipleDelegationRewardsMethod: + return p.withdrawMultipleDelegationRewards(ctx, method, caller, args, value) + + } + return +} + +func (p PrecompileExecutor) EVMKeeper() pcommon.EVMKeeper { + return p.evmKeeper +} + +func (p PrecompileExecutor) GetName() string { + return "distribution" +} + +func (p PrecompileExecutor) setWithdrawAddress(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if err := pcommon.ValidateNonPayable(value); err != nil { + rerr = err + return + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + rerr = err + return + } + delegator, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + rerr = types.NewAssociationMissingErr(caller.Hex()) + return + } + withdrawAddr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + rerr = err + return + } + err = p.distrKeeper.SetWithdrawAddr(ctx, delegator, withdrawAddr) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(true) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p PrecompileExecutor) withdrawDelegationRewards(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + err := p.validateInput(value, args, 1) + if err != nil { + rerr = err + return + } + + delegator, err := p.getDelegator(ctx, caller) + if err != nil { + rerr = err + return + } + _, err = p.withdraw(ctx, delegator, args[0].(string)) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(true) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p PrecompileExecutor) validateInput(value *big.Int, args []interface{}, expectedArgsLength int) error { + if err := pcommon.ValidateNonPayable(value); err != nil { + return err + } + + if err := pcommon.ValidateArgsLength(args, expectedArgsLength); err != nil { + return err + } + + return nil +} + +func (p PrecompileExecutor) withdraw(ctx sdk.Context, delegator sdk.AccAddress, validatorAddress string) (sdk.Coins, error) { + validator, err := sdk.ValAddressFromBech32(validatorAddress) + if err != nil { + return nil, err + } + return p.distrKeeper.WithdrawDelegationRewards(ctx, delegator, validator) +} + +func (p PrecompileExecutor) getDelegator(ctx sdk.Context, caller common.Address) (sdk.AccAddress, error) { + delegator, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + return nil, types.NewAssociationMissingErr(caller.Hex()) + } + + return delegator, nil +} + +func (p PrecompileExecutor) withdrawMultipleDelegationRewards(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + err := p.validateInput(value, args, 1) + if err != nil { + rerr = err + return + } + + delegator, err := p.getDelegator(ctx, caller) + if err != nil { + rerr = err + return + } + validators := args[0].([]string) + for _, valAddr := range validators { + _, err := p.withdraw(ctx, delegator, valAddr) + if err != nil { + rerr = err + return + } + } + + ret, rerr = method.Outputs.Pack(true) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p PrecompileExecutor) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { + addr := arg.(common.Address) + if addr == (common.Address{}) { + return nil, errors.New("invalid addr") + } + seiAddr, associated := p.evmKeeper.GetSeiAddress(ctx, addr) + if !associated { + return nil, errors.New("cannot use an unassociated address as withdraw address") + } + return seiAddr, nil +} diff --git a/precompiles/distribution/legacy/v580/abi.json b/precompiles/distribution/legacy/v580/abi.json new file mode 100644 index 0000000000..739390f50a --- /dev/null +++ b/precompiles/distribution/legacy/v580/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"withdrawAddr","type":"address"}],"name":"setWithdrawAddress","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"validator","type":"string"}],"name":"withdrawDelegationRewards","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string[]","name":"validators","type":"string[]"}],"name":"withdrawMultipleDelegationRewards","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"delegatorAddress","type":"address"}],"name":"rewards","outputs":[{"components":[{"components":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"decimals","type":"uint256"},{"internalType":"string","name":"denom","type":"string"}],"internalType":"struct Coin[]","name":"coins","type":"tuple[]"},{"internalType":"string","name":"validator_address","type":"string"}],"internalType":"struct Reward[]","name":"rewards","type":"tuple[]"},{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"decimals","type":"uint256"},{"internalType":"string","name":"denom","type":"string"}],"internalType":"struct Coin[]","name":"total","type":"tuple[]"}],"internalType":"struct Rewards","name":"rewards","type":"tuple"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/distribution/legacy/v580/distribution.go b/precompiles/distribution/legacy/v580/distribution.go new file mode 100644 index 0000000000..d2a0bc7dc7 --- /dev/null +++ b/precompiles/distribution/legacy/v580/distribution.go @@ -0,0 +1,329 @@ +package v580 + +import ( + "embed" + "errors" + "fmt" + "math/big" + + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + + 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/legacy/v580" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const ( + SetWithdrawAddressMethod = "setWithdrawAddress" + WithdrawDelegationRewardsMethod = "withdrawDelegationRewards" + WithdrawMultipleDelegationRewardsMethod = "withdrawMultipleDelegationRewards" + RewardsMethod = "rewards" +) + +const ( + DistrAddress = "0x0000000000000000000000000000000000001007" +) + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + distrKeeper pcommon.DistributionKeeper + evmKeeper pcommon.EVMKeeper + address common.Address + + SetWithdrawAddrID []byte + WithdrawDelegationRewardsID []byte + WithdrawMultipleDelegationRewardsID []byte + RewardsID []byte +} + +func NewPrecompile(distrKeeper pcommon.DistributionKeeper, evmKeeper pcommon.EVMKeeper) (*pcommon.DynamicGasPrecompile, error) { + newAbi := pcommon.MustGetABI(f, "abi.json") + + p := &PrecompileExecutor{ + distrKeeper: distrKeeper, + evmKeeper: evmKeeper, + address: common.HexToAddress(DistrAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case SetWithdrawAddressMethod: + p.SetWithdrawAddrID = m.ID + case WithdrawDelegationRewardsMethod: + p.WithdrawDelegationRewardsID = m.ID + case WithdrawMultipleDelegationRewardsMethod: + p.WithdrawMultipleDelegationRewardsID = m.ID + case RewardsMethod: + p.RewardsID = m.ID + } + } + + return pcommon.NewDynamicGasPrecompile(newAbi, p, p.address, "distribution"), nil +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + if caller.Cmp(callingContract) != 0 { + return nil, 0, errors.New("cannot delegatecall distr") + } + switch method.Name { + case SetWithdrawAddressMethod: + if readOnly { + return nil, 0, errors.New("cannot call distr precompile from staticcall") + } + return p.setWithdrawAddress(ctx, method, caller, args, value) + case WithdrawDelegationRewardsMethod: + if readOnly { + return nil, 0, errors.New("cannot call distr precompile from staticcall") + } + return p.withdrawDelegationRewards(ctx, method, caller, args, value) + case WithdrawMultipleDelegationRewardsMethod: + if readOnly { + return nil, 0, errors.New("cannot call distr precompile from staticcall") + } + return p.withdrawMultipleDelegationRewards(ctx, method, caller, args, value) + case RewardsMethod: + return p.rewards(ctx, method, args) + } + return +} + +func (p PrecompileExecutor) EVMKeeper() pcommon.EVMKeeper { + return p.evmKeeper +} + +func (p PrecompileExecutor) setWithdrawAddress(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if err := pcommon.ValidateNonPayable(value); err != nil { + rerr = err + return + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + rerr = err + return + } + delegator, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + rerr = types.NewAssociationMissingErr(caller.Hex()) + return + } + withdrawAddr, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + rerr = err + return + } + err = p.distrKeeper.SetWithdrawAddr(ctx, delegator, withdrawAddr) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(true) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p PrecompileExecutor) withdrawDelegationRewards(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + err := p.validateInput(value, args, 1) + if err != nil { + rerr = err + return + } + + delegator, err := p.getDelegator(ctx, caller) + if err != nil { + rerr = err + return + } + _, err = p.withdraw(ctx, delegator, args[0].(string)) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(true) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p PrecompileExecutor) validateInput(value *big.Int, args []interface{}, expectedArgsLength int) error { + if err := pcommon.ValidateNonPayable(value); err != nil { + return err + } + + if err := pcommon.ValidateArgsLength(args, expectedArgsLength); err != nil { + return err + } + + return nil +} + +func (p PrecompileExecutor) withdraw(ctx sdk.Context, delegator sdk.AccAddress, validatorAddress string) (sdk.Coins, error) { + validator, err := sdk.ValAddressFromBech32(validatorAddress) + if err != nil { + return nil, err + } + return p.distrKeeper.WithdrawDelegationRewards(ctx, delegator, validator) +} + +func (p PrecompileExecutor) getDelegator(ctx sdk.Context, caller common.Address) (sdk.AccAddress, error) { + delegator, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + return nil, types.NewAssociationMissingErr(caller.Hex()) + } + + return delegator, nil +} + +func (p PrecompileExecutor) withdrawMultipleDelegationRewards(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + err := p.validateInput(value, args, 1) + if err != nil { + rerr = err + return + } + + delegator, err := p.getDelegator(ctx, caller) + if err != nil { + rerr = err + return + } + validators := args[0].([]string) + for _, valAddr := range validators { + _, err := p.withdraw(ctx, delegator, valAddr) + if err != nil { + rerr = err + return + } + } + + ret, rerr = method.Outputs.Pack(true) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p PrecompileExecutor) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { + addr := arg.(common.Address) + if addr == (common.Address{}) { + return nil, errors.New("invalid addr") + } + seiAddr, associated := p.evmKeeper.GetSeiAddress(ctx, addr) + if !associated { + return nil, errors.New("cannot use an unassociated address as withdraw address") + } + return seiAddr, nil +} + +type Coin struct { + Amount *big.Int + Denom string + Decimals *big.Int +} + +type Reward struct { + ValidatorAddress string + Coins []Coin +} + +type Rewards struct { + Rewards []Reward + Total []Coin +} + +func (p PrecompileExecutor) rewards(ctx sdk.Context, method *abi.Method, args []interface{}) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + rerr = err + return + } + + seiDelegatorAddress, err := p.accAddressFromArg(ctx, args[0]) + if err != nil { + rerr = err + return + } + + req := &distrtypes.QueryDelegationTotalRewardsRequest{ + DelegatorAddress: seiDelegatorAddress.String(), + } + + wrappedC := sdk.WrapSDKContext(ctx) + response, err := p.distrKeeper.DelegationTotalRewards(wrappedC, req) + if err != nil { + rerr = err + return + } + + rewardsOutput := getResponseOutput(response) + ret, rerr = method.Outputs.Pack(rewardsOutput) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func getResponseOutput(response *distrtypes.QueryDelegationTotalRewardsResponse) Rewards { + rewards := make([]Reward, 0, len(response.Rewards)) + for _, rewardInfo := range response.Rewards { + coins := make([]Coin, 0, len(rewardInfo.Reward)) + for _, coin := range rewardInfo.Reward { + coins = append(coins, Coin{ + Amount: coin.Amount.BigInt(), + Denom: coin.Denom, + Decimals: big.NewInt(sdk.Precision), + }) + } + rewards = append(rewards, Reward{ + ValidatorAddress: rewardInfo.ValidatorAddress, + Coins: coins, + }) + } + + totalCoins := make([]Coin, 0, len(response.Total)) + for _, coin := range response.Total { + totalCoins = append(totalCoins, Coin{ + Amount: coin.Amount.BigInt(), + Denom: coin.Denom, + Decimals: big.NewInt(sdk.Precision), + }) + } + + return Rewards{ + Rewards: rewards, + Total: totalCoins, + } +} diff --git a/precompiles/gov/legacy/v520/abi.json b/precompiles/gov/legacy/v520/abi.json new file mode 100644 index 0000000000..c74f5397c5 --- /dev/null +++ b/precompiles/gov/legacy/v520/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"uint64","name":"proposalID","type":"uint64"}],"name":"deposit","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint64","name":"proposalID","type":"uint64"},{"internalType":"int32","name":"option","type":"int32"}],"name":"vote","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/precompiles/gov/legacy/v520/gov.go b/precompiles/gov/legacy/v520/gov.go new file mode 100644 index 0000000000..0b7c70f2a5 --- /dev/null +++ b/precompiles/gov/legacy/v520/gov.go @@ -0,0 +1,169 @@ +package v520 + +import ( + "bytes" + "embed" + "errors" + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/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/legacy/v520" +) + +const ( + VoteMethod = "vote" + DepositMethod = "deposit" +) + +const ( + GovAddress = "0x0000000000000000000000000000000000001006" +) + +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 + +func GetABI() abi.ABI { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +type Precompile struct { + pcommon.Precompile + govKeeper pcommon.GovKeeper + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + address common.Address + + VoteID []byte + DepositID []byte +} + +func NewPrecompile(govKeeper pcommon.GovKeeper, evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper) (*Precompile, error) { + newAbi := GetABI() + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + govKeeper: govKeeper, + evmKeeper: evmKeeper, + address: common.HexToAddress(GovAddress), + bankKeeper: bankKeeper, + } + + for name, m := range newAbi.Methods { + switch name { + case VoteMethod: + p.VoteID = m.ID + case DepositMethod: + p.DepositID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return pcommon.UnknownMethodCallGas + } + + if bytes.Equal(methodID, p.VoteID) { + return 30000 + } else if bytes.Equal(methodID, p.DepositID) { + return 30000 + } + + // This should never happen since this is going to fail during Run + return pcommon.UnknownMethodCallGas +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "gov" +} + +func (p Precompile) Run(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, value *big.Int, readOnly bool, _ bool) (bz []byte, err error) { + if readOnly { + return nil, errors.New("cannot call gov precompile from staticcall") + } + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, err + } + if caller.Cmp(callingContract) != 0 { + return nil, errors.New("cannot delegatecall gov") + } + + switch method.Name { + case VoteMethod: + return p.vote(ctx, method, caller, args, value) + case DepositMethod: + return p.deposit(ctx, method, caller, args, value) + } + return +} + +func (p Precompile) vote(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + voter, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + return nil, fmt.Errorf("voter %s is not associated", caller.Hex()) + } + proposalID := args[0].(uint64) + voteOption := args[1].(int32) + err := p.govKeeper.AddVote(ctx, proposalID, voter, govtypes.NewNonSplitVoteOption(govtypes.VoteOption(voteOption))) + if err != nil { + return nil, err + } + return method.Outputs.Pack(true) +} + +func (p Precompile) deposit(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + depositor, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + return nil, fmt.Errorf("depositor %s is not associated", caller.Hex()) + } + proposalID := args[0].(uint64) + if value == nil || value.Sign() == 0 { + return nil, errors.New("set `value` field to non-zero to deposit fund") + } + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), depositor, value, p.bankKeeper) + if err != nil { + return nil, err + } + res, err := p.govKeeper.AddDeposit(ctx, proposalID, depositor, sdk.NewCoins(coin)) + if err != nil { + return nil, err + } + return method.Outputs.Pack(res) +} diff --git a/precompiles/gov/legacy/v555/abi.json b/precompiles/gov/legacy/v555/abi.json new file mode 100644 index 0000000000..c74f5397c5 --- /dev/null +++ b/precompiles/gov/legacy/v555/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"uint64","name":"proposalID","type":"uint64"}],"name":"deposit","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint64","name":"proposalID","type":"uint64"},{"internalType":"int32","name":"option","type":"int32"}],"name":"vote","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/precompiles/gov/legacy/v555/gov.go b/precompiles/gov/legacy/v555/gov.go new file mode 100644 index 0000000000..1844f13de2 --- /dev/null +++ b/precompiles/gov/legacy/v555/gov.go @@ -0,0 +1,175 @@ +package v555 + +import ( + "bytes" + "embed" + "errors" + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/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/legacy/v555" + "github.com/sei-protocol/sei-chain/x/evm/state" +) + +const ( + VoteMethod = "vote" + DepositMethod = "deposit" +) + +const ( + GovAddress = "0x0000000000000000000000000000000000001006" +) + +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 + +func GetABI() abi.ABI { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +type Precompile struct { + pcommon.Precompile + govKeeper pcommon.GovKeeper + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + address common.Address + + VoteID []byte + DepositID []byte +} + +func NewPrecompile(govKeeper pcommon.GovKeeper, evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper) (*Precompile, error) { + newAbi := GetABI() + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + govKeeper: govKeeper, + evmKeeper: evmKeeper, + address: common.HexToAddress(GovAddress), + bankKeeper: bankKeeper, + } + + for name, m := range newAbi.Methods { + switch name { + case VoteMethod: + p.VoteID = m.ID + case DepositMethod: + p.DepositID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return pcommon.UnknownMethodCallGas + } + + if bytes.Equal(methodID, p.VoteID) { + return 30000 + } else if bytes.Equal(methodID, p.DepositID) { + return 30000 + } + + // This should never happen since this is going to fail during Run + return pcommon.UnknownMethodCallGas +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "gov" +} + +func (p Precompile) Run(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, value *big.Int, readOnly bool, _ bool) (bz []byte, err error) { + defer func() { + if err != nil { + evm.StateDB.(*state.DBImpl).SetPrecompileError(err) + } + }() + if readOnly { + return nil, errors.New("cannot call gov precompile from staticcall") + } + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, err + } + if caller.Cmp(callingContract) != 0 { + return nil, errors.New("cannot delegatecall gov") + } + + switch method.Name { + case VoteMethod: + return p.vote(ctx, method, caller, args, value) + case DepositMethod: + return p.deposit(ctx, method, caller, args, value) + } + return +} + +func (p Precompile) vote(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + voter, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + return nil, fmt.Errorf("voter %s is not associated", caller.Hex()) + } + proposalID := args[0].(uint64) + voteOption := args[1].(int32) + err := p.govKeeper.AddVote(ctx, proposalID, voter, govtypes.NewNonSplitVoteOption(govtypes.VoteOption(voteOption))) + if err != nil { + return nil, err + } + return method.Outputs.Pack(true) +} + +func (p Precompile) deposit(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + depositor, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + return nil, fmt.Errorf("depositor %s is not associated", caller.Hex()) + } + proposalID := args[0].(uint64) + if value == nil || value.Sign() == 0 { + return nil, errors.New("set `value` field to non-zero to deposit fund") + } + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), depositor, value, p.bankKeeper) + if err != nil { + return nil, err + } + res, err := p.govKeeper.AddDeposit(ctx, proposalID, depositor, sdk.NewCoins(coin)) + if err != nil { + return nil, err + } + return method.Outputs.Pack(res) +} diff --git a/precompiles/gov/legacy/v562/abi.json b/precompiles/gov/legacy/v562/abi.json new file mode 100644 index 0000000000..c74f5397c5 --- /dev/null +++ b/precompiles/gov/legacy/v562/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"uint64","name":"proposalID","type":"uint64"}],"name":"deposit","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint64","name":"proposalID","type":"uint64"},{"internalType":"int32","name":"option","type":"int32"}],"name":"vote","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/precompiles/gov/legacy/v562/gov.go b/precompiles/gov/legacy/v562/gov.go new file mode 100644 index 0000000000..23a3a8ddba --- /dev/null +++ b/precompiles/gov/legacy/v562/gov.go @@ -0,0 +1,148 @@ +package v562 + +import ( + "bytes" + "embed" + "errors" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/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/legacy/v562" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const ( + VoteMethod = "vote" + DepositMethod = "deposit" +) + +const ( + GovAddress = "0x0000000000000000000000000000000000001006" +) + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +func GetABI() abi.ABI { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +type PrecompileExecutor struct { + govKeeper pcommon.GovKeeper + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + address common.Address + + VoteID []byte + DepositID []byte +} + +func NewPrecompile(govKeeper pcommon.GovKeeper, evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper) (*pcommon.Precompile, error) { + newAbi := GetABI() + + p := &PrecompileExecutor{ + govKeeper: govKeeper, + evmKeeper: evmKeeper, + address: common.HexToAddress(GovAddress), + bankKeeper: bankKeeper, + } + + for name, m := range newAbi.Methods { + switch name { + case VoteMethod: + p.VoteID = m.ID + case DepositMethod: + p.DepositID = m.ID + } + } + + return pcommon.NewPrecompile(newAbi, p, p.address, "gov"), nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p PrecompileExecutor) RequiredGas(input []byte, method *abi.Method) uint64 { + if bytes.Equal(method.ID, p.VoteID) { + return 30000 + } else if bytes.Equal(method.ID, p.DepositID) { + return 30000 + } + + // This should never happen since this is going to fail during Run + return pcommon.UnknownMethodCallGas +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM) (bz []byte, err error) { + if readOnly { + return nil, errors.New("cannot call gov precompile from staticcall") + } + if caller.Cmp(callingContract) != 0 { + return nil, errors.New("cannot delegatecall gov") + } + + switch method.Name { + case VoteMethod: + return p.vote(ctx, method, caller, args, value) + case DepositMethod: + return p.deposit(ctx, method, caller, args, value) + } + return +} + +func (p PrecompileExecutor) vote(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + voter, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + return nil, types.NewAssociationMissingErr(caller.Hex()) + } + proposalID := args[0].(uint64) + voteOption := args[1].(int32) + err := p.govKeeper.AddVote(ctx, proposalID, voter, govtypes.NewNonSplitVoteOption(govtypes.VoteOption(voteOption))) + if err != nil { + return nil, err + } + return method.Outputs.Pack(true) +} + +func (p PrecompileExecutor) deposit(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + depositor, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + return nil, types.NewAssociationMissingErr(caller.Hex()) + } + proposalID := args[0].(uint64) + if value == nil || value.Sign() == 0 { + return nil, errors.New("set `value` field to non-zero to deposit fund") + } + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), depositor, value, p.bankKeeper) + if err != nil { + return nil, err + } + res, err := p.govKeeper.AddDeposit(ctx, proposalID, depositor, sdk.NewCoins(coin)) + if err != nil { + return nil, err + } + return method.Outputs.Pack(res) +} diff --git a/precompiles/gov/legacy/v580/abi.json b/precompiles/gov/legacy/v580/abi.json new file mode 100644 index 0000000000..c74f5397c5 --- /dev/null +++ b/precompiles/gov/legacy/v580/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"uint64","name":"proposalID","type":"uint64"}],"name":"deposit","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint64","name":"proposalID","type":"uint64"},{"internalType":"int32","name":"option","type":"int32"}],"name":"vote","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/precompiles/gov/legacy/v580/gov.go b/precompiles/gov/legacy/v580/gov.go new file mode 100644 index 0000000000..238d8f6ec2 --- /dev/null +++ b/precompiles/gov/legacy/v580/gov.go @@ -0,0 +1,135 @@ +package v580 + +import ( + "bytes" + "embed" + "errors" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/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/legacy/v580" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const ( + VoteMethod = "vote" + DepositMethod = "deposit" +) + +const ( + GovAddress = "0x0000000000000000000000000000000000001006" +) + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + govKeeper pcommon.GovKeeper + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + address common.Address + + VoteID []byte + DepositID []byte +} + +func NewPrecompile(govKeeper pcommon.GovKeeper, evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper) (*pcommon.Precompile, error) { + newAbi := pcommon.MustGetABI(f, "abi.json") + + p := &PrecompileExecutor{ + govKeeper: govKeeper, + evmKeeper: evmKeeper, + address: common.HexToAddress(GovAddress), + bankKeeper: bankKeeper, + } + + for name, m := range newAbi.Methods { + switch name { + case VoteMethod: + p.VoteID = m.ID + case DepositMethod: + p.DepositID = m.ID + } + } + + return pcommon.NewPrecompile(newAbi, p, p.address, "gov"), nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p PrecompileExecutor) RequiredGas(input []byte, method *abi.Method) uint64 { + if bytes.Equal(method.ID, p.VoteID) { + return 30000 + } else if bytes.Equal(method.ID, p.DepositID) { + return 30000 + } + + // This should never happen since this is going to fail during Run + return pcommon.UnknownMethodCallGas +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM) (bz []byte, err error) { + if readOnly { + return nil, errors.New("cannot call gov precompile from staticcall") + } + if caller.Cmp(callingContract) != 0 { + return nil, errors.New("cannot delegatecall gov") + } + + switch method.Name { + case VoteMethod: + return p.vote(ctx, method, caller, args, value) + case DepositMethod: + return p.deposit(ctx, method, caller, args, value) + } + return +} + +func (p PrecompileExecutor) vote(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + voter, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + return nil, types.NewAssociationMissingErr(caller.Hex()) + } + proposalID := args[0].(uint64) + voteOption := args[1].(int32) + err := p.govKeeper.AddVote(ctx, proposalID, voter, govtypes.NewNonSplitVoteOption(govtypes.VoteOption(voteOption))) + if err != nil { + return nil, err + } + return method.Outputs.Pack(true) +} + +func (p PrecompileExecutor) deposit(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + depositor, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + return nil, types.NewAssociationMissingErr(caller.Hex()) + } + proposalID := args[0].(uint64) + if value == nil || value.Sign() == 0 { + return nil, errors.New("set `value` field to non-zero to deposit fund") + } + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), depositor, value, p.bankKeeper) + if err != nil { + return nil, err + } + res, err := p.govKeeper.AddDeposit(ctx, proposalID, depositor, sdk.NewCoins(coin)) + if err != nil { + return nil, err + } + return method.Outputs.Pack(res) +} diff --git a/precompiles/ibc/legacy/v501/abi.json b/precompiles/ibc/legacy/v501/abi.json new file mode 100644 index 0000000000..4e2f045b42 --- /dev/null +++ b/precompiles/ibc/legacy/v501/abi.json @@ -0,0 +1,56 @@ +[ + { + "inputs": [ + { + "internalType": "string", + "name": "toAddress", + "type": "string" + }, + { + "internalType": "string", + "name": "port", + "type": "string" + }, + { + "internalType": "string", + "name": "channel", + "type": "string" + }, + { + "internalType": "string", + "name": "denom", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "revisionNumber", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "revisionHeight", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "timeoutTimestamp", + "type": "uint64" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } + ] \ No newline at end of file diff --git a/precompiles/ibc/legacy/v501/ibc.go b/precompiles/ibc/legacy/v501/ibc.go new file mode 100644 index 0000000000..af62622fe9 --- /dev/null +++ b/precompiles/ibc/legacy/v501/ibc.go @@ -0,0 +1,268 @@ +package v501 + +import ( + "bytes" + "embed" + "errors" + "fmt" + "math/big" + + "github.com/cosmos/cosmos-sdk/types/bech32" + + "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" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/vm" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v520" +) + +const ( + TransferMethod = "transfer" +) + +const ( + IBCAddress = "0x0000000000000000000000000000000000001009" +) + +var _ vm.PrecompiledContract = &Precompile{} +var _ vm.DynamicGasPrecompiledContract = &Precompile{} + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +func GetABI() abi.ABI { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +type Precompile struct { + pcommon.Precompile + address common.Address + transferKeeper pcommon.TransferKeeper + evmKeeper pcommon.EVMKeeper + + TransferID []byte +} + +func NewPrecompile(transferKeeper pcommon.TransferKeeper, evmKeeper pcommon.EVMKeeper) (*Precompile, error) { + newAbi := GetABI() + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + address: common.HexToAddress(IBCAddress), + transferKeeper: transferKeeper, + evmKeeper: evmKeeper, + } + + for name, m := range newAbi.Methods { + switch name { + case TransferMethod: + p.TransferID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return pcommon.UnknownMethodCallGas + } + + method, err := p.ABI.MethodById(methodID) + if err != nil { + // This should never happen since this method is going to fail during Run + return pcommon.UnknownMethodCallGas + } + + return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) +} + +func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { + if readOnly { + return nil, 0, errors.New("cannot call IBC precompile from staticcall") + } + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, 0, err + } + if caller.Cmp(callingContract) != 0 { + return nil, 0, errors.New("cannot delegatecall IBC") + } + + gasMultiplier := p.evmKeeper.GetPriorityNormalizer(ctx) + gasLimitBigInt := new(big.Int).Mul(new(big.Int).SetUint64(suppliedGas), gasMultiplier.TruncateInt().BigInt()) + if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { + gasLimitBigInt = utils.BigMaxU64 + } + ctx = ctx.WithGasMeter(sdk.NewGasMeter(gasLimitBigInt.Uint64(), 1, 1)) + + switch method.Name { + case TransferMethod: + return p.transfer(ctx, method, args, caller) + } + return +} + +func (p Precompile) Run(*vm.EVM, common.Address, common.Address, []byte, *big.Int, bool, bool) (bz []byte, err error) { + panic("static gas Run is not implemented for dynamic gas precompile") +} + +func (p Precompile) transfer(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + + if err := pcommon.ValidateArgsLength(args, 8); err != nil { + rerr = err + return + } + senderSeiAddr, ok := p.evmKeeper.GetSeiAddress(ctx, caller) + if !ok { + rerr = errors.New("caller is not a valid SEI address") + return + } + + receiverAddressString, ok := args[0].(string) + if !ok { + rerr = errors.New("receiverAddress is not a string") + return + } + _, bz, err := bech32.DecodeAndConvert(receiverAddressString) + if err != nil { + rerr = err + return + } + err = sdk.VerifyAddressFormat(bz) + if err != nil { + rerr = err + return + } + + port, ok := args[1].(string) + if !ok { + rerr = errors.New("port is not a string") + return + } + if port == "" { + rerr = errors.New("port cannot be empty") + return + } + + channelID, ok := args[2].(string) + if !ok { + rerr = errors.New("channelID is not a string") + return + } + if channelID == "" { + rerr = errors.New("channelID cannot be empty") + return + } + + denom := args[3].(string) + if denom == "" { + rerr = errors.New("invalid denom") + return + } + + amount, ok := args[4].(*big.Int) + if !ok { + rerr = errors.New("amount is not a big.Int") + return + } + + if amount.Cmp(big.NewInt(0)) == 0 { + // short circuit + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return + } + + coin := sdk.Coin{ + Denom: denom, + Amount: sdk.NewIntFromBigInt(amount), + } + + revisionNumber, ok := args[5].(uint64) + if !ok { + rerr = errors.New("revisionNumber is not a uint64") + return + } + + revisionHeight, ok := args[6].(uint64) + if !ok { + rerr = errors.New("revisionHeight is not a uint64") + return + } + + height := clienttypes.Height{ + RevisionNumber: revisionNumber, + RevisionHeight: revisionHeight, + } + + timeoutTimestamp, ok := args[7].(uint64) + if !ok { + rerr = errors.New("timeoutTimestamp is not a uint64") + return + } + + err = p.transferKeeper.SendTransfer(ctx, port, channelID, coin, senderSeiAddr, receiverAddressString, height, timeoutTimestamp) + + if err != nil { + rerr = err + return + } + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return +} + +func (Precompile) IsTransaction(method string) bool { + switch method { + case TransferMethod: + return true + default: + return false + } +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "ibc" +} + +func (p Precompile) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { + addr := arg.(common.Address) + if addr == (common.Address{}) { + return nil, errors.New("invalid addr") + } + seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, addr) + if !found { + return nil, fmt.Errorf("EVM address %s is not associated", addr.Hex()) + } + return seiAddr, nil +} diff --git a/precompiles/ibc/legacy/v510/abi.json b/precompiles/ibc/legacy/v510/abi.json new file mode 100644 index 0000000000..4e2f045b42 --- /dev/null +++ b/precompiles/ibc/legacy/v510/abi.json @@ -0,0 +1,56 @@ +[ + { + "inputs": [ + { + "internalType": "string", + "name": "toAddress", + "type": "string" + }, + { + "internalType": "string", + "name": "port", + "type": "string" + }, + { + "internalType": "string", + "name": "channel", + "type": "string" + }, + { + "internalType": "string", + "name": "denom", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "revisionNumber", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "revisionHeight", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "timeoutTimestamp", + "type": "uint64" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } + ] \ No newline at end of file diff --git a/precompiles/ibc/legacy/v510/ibc.go b/precompiles/ibc/legacy/v510/ibc.go new file mode 100644 index 0000000000..eb72912bb8 --- /dev/null +++ b/precompiles/ibc/legacy/v510/ibc.go @@ -0,0 +1,268 @@ +package v510 + +import ( + "bytes" + "embed" + "errors" + "fmt" + "math/big" + + "github.com/cosmos/cosmos-sdk/types/bech32" + + "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" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/vm" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v520" +) + +const ( + TransferMethod = "transfer" +) + +const ( + IBCAddress = "0x0000000000000000000000000000000000001009" +) + +var _ vm.PrecompiledContract = &Precompile{} +var _ vm.DynamicGasPrecompiledContract = &Precompile{} + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +func GetABI() abi.ABI { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +type Precompile struct { + pcommon.Precompile + address common.Address + transferKeeper pcommon.TransferKeeper + evmKeeper pcommon.EVMKeeper + + TransferID []byte +} + +func NewPrecompile(transferKeeper pcommon.TransferKeeper, evmKeeper pcommon.EVMKeeper) (*Precompile, error) { + newAbi := GetABI() + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + address: common.HexToAddress(IBCAddress), + transferKeeper: transferKeeper, + evmKeeper: evmKeeper, + } + + for name, m := range newAbi.Methods { + switch name { + case TransferMethod: + p.TransferID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return pcommon.UnknownMethodCallGas + } + + method, err := p.ABI.MethodById(methodID) + if err != nil { + // This should never happen since this method is going to fail during Run + return pcommon.UnknownMethodCallGas + } + + return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) +} + +func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { + if readOnly { + return nil, 0, errors.New("cannot call IBC precompile from staticcall") + } + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, 0, err + } + if caller.Cmp(callingContract) != 0 { + return nil, 0, errors.New("cannot delegatecall IBC") + } + + gasMultiplier := p.evmKeeper.GetPriorityNormalizer(ctx) + gasLimitBigInt := new(big.Int).Mul(new(big.Int).SetUint64(suppliedGas), gasMultiplier.TruncateInt().BigInt()) + if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { + gasLimitBigInt = utils.BigMaxU64 + } + ctx = ctx.WithGasMeter(sdk.NewGasMeterWithMultiplier(ctx, gasLimitBigInt.Uint64())) + + switch method.Name { + case TransferMethod: + return p.transfer(ctx, method, args, caller) + } + return +} + +func (p Precompile) Run(*vm.EVM, common.Address, common.Address, []byte, *big.Int, bool, bool) (bz []byte, err error) { + panic("static gas Run is not implemented for dynamic gas precompile") +} + +func (p Precompile) transfer(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + + if err := pcommon.ValidateArgsLength(args, 8); err != nil { + rerr = err + return + } + senderSeiAddr, ok := p.evmKeeper.GetSeiAddress(ctx, caller) + if !ok { + rerr = errors.New("caller is not a valid SEI address") + return + } + + receiverAddressString, ok := args[0].(string) + if !ok { + rerr = errors.New("receiverAddress is not a string") + return + } + _, bz, err := bech32.DecodeAndConvert(receiverAddressString) + if err != nil { + rerr = err + return + } + err = sdk.VerifyAddressFormat(bz) + if err != nil { + rerr = err + return + } + + port, ok := args[1].(string) + if !ok { + rerr = errors.New("port is not a string") + return + } + if port == "" { + rerr = errors.New("port cannot be empty") + return + } + + channelID, ok := args[2].(string) + if !ok { + rerr = errors.New("channelID is not a string") + return + } + if channelID == "" { + rerr = errors.New("channelID cannot be empty") + return + } + + denom := args[3].(string) + if denom == "" { + rerr = errors.New("invalid denom") + return + } + + amount, ok := args[4].(*big.Int) + if !ok { + rerr = errors.New("amount is not a big.Int") + return + } + + if amount.Cmp(big.NewInt(0)) == 0 { + // short circuit + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return + } + + coin := sdk.Coin{ + Denom: denom, + Amount: sdk.NewIntFromBigInt(amount), + } + + revisionNumber, ok := args[5].(uint64) + if !ok { + rerr = errors.New("revisionNumber is not a uint64") + return + } + + revisionHeight, ok := args[6].(uint64) + if !ok { + rerr = errors.New("revisionHeight is not a uint64") + return + } + + height := clienttypes.Height{ + RevisionNumber: revisionNumber, + RevisionHeight: revisionHeight, + } + + timeoutTimestamp, ok := args[7].(uint64) + if !ok { + rerr = errors.New("timeoutTimestamp is not a uint64") + return + } + + err = p.transferKeeper.SendTransfer(ctx, port, channelID, coin, senderSeiAddr, receiverAddressString, height, timeoutTimestamp) + + if err != nil { + rerr = err + return + } + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return +} + +func (Precompile) IsTransaction(method string) bool { + switch method { + case TransferMethod: + return true + default: + return false + } +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "ibc" +} + +func (p Precompile) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { + addr := arg.(common.Address) + if addr == (common.Address{}) { + return nil, errors.New("invalid addr") + } + seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, addr) + if !found { + return nil, fmt.Errorf("EVM address %s is not associated", addr.Hex()) + } + return seiAddr, nil +} diff --git a/precompiles/ibc/legacy/v520/abi.json b/precompiles/ibc/legacy/v520/abi.json new file mode 100644 index 0000000000..4e2f045b42 --- /dev/null +++ b/precompiles/ibc/legacy/v520/abi.json @@ -0,0 +1,56 @@ +[ + { + "inputs": [ + { + "internalType": "string", + "name": "toAddress", + "type": "string" + }, + { + "internalType": "string", + "name": "port", + "type": "string" + }, + { + "internalType": "string", + "name": "channel", + "type": "string" + }, + { + "internalType": "string", + "name": "denom", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "revisionNumber", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "revisionHeight", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "timeoutTimestamp", + "type": "uint64" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } + ] \ No newline at end of file diff --git a/precompiles/ibc/legacy/v520/ibc.go b/precompiles/ibc/legacy/v520/ibc.go new file mode 100644 index 0000000000..e2b13a0ffb --- /dev/null +++ b/precompiles/ibc/legacy/v520/ibc.go @@ -0,0 +1,268 @@ +package v520 + +import ( + "bytes" + "embed" + "errors" + "fmt" + "math/big" + + "github.com/cosmos/cosmos-sdk/types/bech32" + + "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" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/vm" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v520" +) + +const ( + TransferMethod = "transfer" +) + +const ( + IBCAddress = "0x0000000000000000000000000000000000001009" +) + +var _ vm.PrecompiledContract = &Precompile{} +var _ vm.DynamicGasPrecompiledContract = &Precompile{} + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +func GetABI() abi.ABI { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +type Precompile struct { + pcommon.Precompile + address common.Address + transferKeeper pcommon.TransferKeeper + evmKeeper pcommon.EVMKeeper + + TransferID []byte +} + +func NewPrecompile(transferKeeper pcommon.TransferKeeper, evmKeeper pcommon.EVMKeeper) (*Precompile, error) { + newAbi := GetABI() + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + address: common.HexToAddress(IBCAddress), + transferKeeper: transferKeeper, + evmKeeper: evmKeeper, + } + + for name, m := range newAbi.Methods { + switch name { + case TransferMethod: + p.TransferID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return pcommon.UnknownMethodCallGas + } + + method, err := p.ABI.MethodById(methodID) + if err != nil { + // This should never happen since this method is going to fail during Run + return pcommon.UnknownMethodCallGas + } + + return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) +} + +func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { + if readOnly { + return nil, 0, errors.New("cannot call IBC precompile from staticcall") + } + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, 0, err + } + if caller.Cmp(callingContract) != 0 { + return nil, 0, errors.New("cannot delegatecall IBC") + } + + gasMultiplier := p.evmKeeper.GetPriorityNormalizer(ctx) + gasLimitBigInt := new(big.Int).Mul(new(big.Int).SetUint64(suppliedGas), gasMultiplier.TruncateInt().BigInt()) + if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { + gasLimitBigInt = utils.BigMaxU64 + } + ctx = ctx.WithGasMeter(sdk.NewGasMeterWithMultiplier(ctx, gasLimitBigInt.Uint64())) + + switch method.Name { + case TransferMethod: + return p.transfer(ctx, method, args, caller) + } + return +} + +func (p Precompile) Run(*vm.EVM, common.Address, common.Address, []byte, *big.Int, bool, bool) (bz []byte, err error) { + panic("static gas Run is not implemented for dynamic gas precompile") +} + +func (p Precompile) transfer(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + + if err := pcommon.ValidateArgsLength(args, 8); err != nil { + rerr = err + return + } + senderSeiAddr, ok := p.evmKeeper.GetSeiAddress(ctx, caller) + if !ok { + rerr = errors.New("caller is not a valid SEI address") + return + } + + receiverAddressString, ok := args[0].(string) + if !ok { + rerr = errors.New("receiverAddress is not a string") + return + } + _, bz, err := bech32.DecodeAndConvert(receiverAddressString) + if err != nil { + rerr = err + return + } + err = sdk.VerifyAddressFormat(bz) + if err != nil { + rerr = err + return + } + + port, ok := args[1].(string) + if !ok { + rerr = errors.New("port is not a string") + return + } + if port == "" { + rerr = errors.New("port cannot be empty") + return + } + + channelID, ok := args[2].(string) + if !ok { + rerr = errors.New("channelID is not a string") + return + } + if channelID == "" { + rerr = errors.New("channelID cannot be empty") + return + } + + denom := args[3].(string) + if denom == "" { + rerr = errors.New("invalid denom") + return + } + + amount, ok := args[4].(*big.Int) + if !ok { + rerr = errors.New("amount is not a big.Int") + return + } + + if amount.Cmp(big.NewInt(0)) == 0 { + // short circuit + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return + } + + coin := sdk.Coin{ + Denom: denom, + Amount: sdk.NewIntFromBigInt(amount), + } + + revisionNumber, ok := args[5].(uint64) + if !ok { + rerr = errors.New("revisionNumber is not a uint64") + return + } + + revisionHeight, ok := args[6].(uint64) + if !ok { + rerr = errors.New("revisionHeight is not a uint64") + return + } + + height := clienttypes.Height{ + RevisionNumber: revisionNumber, + RevisionHeight: revisionHeight, + } + + timeoutTimestamp, ok := args[7].(uint64) + if !ok { + rerr = errors.New("timeoutTimestamp is not a uint64") + return + } + + err = p.transferKeeper.SendTransfer(ctx, port, channelID, coin, senderSeiAddr, receiverAddressString, height, timeoutTimestamp) + + if err != nil { + rerr = err + return + } + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return +} + +func (Precompile) IsTransaction(method string) bool { + switch method { + case TransferMethod: + return true + default: + return false + } +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "ibc" +} + +func (p Precompile) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { + addr := arg.(common.Address) + if addr == (common.Address{}) { + return nil, errors.New("invalid addr") + } + seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, addr) + if !found { + return nil, fmt.Errorf("EVM address %s is not associated", addr.Hex()) + } + return seiAddr, nil +} diff --git a/precompiles/ibc/legacy/v530/abi.json b/precompiles/ibc/legacy/v530/abi.json new file mode 100644 index 0000000000..40f9be1ac6 --- /dev/null +++ b/precompiles/ibc/legacy/v530/abi.json @@ -0,0 +1,95 @@ +[ + { + "inputs": [ + { + "internalType": "string", + "name": "toAddress", + "type": "string" + }, + { + "internalType": "string", + "name": "port", + "type": "string" + }, + { + "internalType": "string", + "name": "channel", + "type": "string" + }, + { + "internalType": "string", + "name": "denom", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint64", + "name": "revisionNumber", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "revisionHeight", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "timeoutTimestamp", + "type": "uint64" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "toAddress", + "type": "string" + }, + { + "internalType": "string", + "name": "port", + "type": "string" + }, + { + "internalType": "string", + "name": "channel", + "type": "string" + }, + { + "internalType": "string", + "name": "denom", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferWithDefaultTimeout", + "outputs": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "payable", + "type": "function" + } + ] \ No newline at end of file diff --git a/precompiles/ibc/legacy/v530/ibc.go b/precompiles/ibc/legacy/v530/ibc.go new file mode 100644 index 0000000000..8767c26850 --- /dev/null +++ b/precompiles/ibc/legacy/v530/ibc.go @@ -0,0 +1,454 @@ +package v530 + +import ( + "bytes" + "embed" + "errors" + "fmt" + "math/big" + + "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" + + "github.com/cosmos/cosmos-sdk/types/bech32" + + "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/x/evm/state" + + sdk "github.com/cosmos/cosmos-sdk/types" + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v3/modules/core/03-connection/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/vm" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v530" +) + +const ( + TransferMethod = "transfer" + TransferWithDefaultTimeoutMethod = "transferWithDefaultTimeout" +) + +const ( + IBCAddress = "0x0000000000000000000000000000000000001009" +) + +var _ vm.PrecompiledContract = &Precompile{} +var _ vm.DynamicGasPrecompiledContract = &Precompile{} + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +func GetABI() abi.ABI { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +type Precompile struct { + pcommon.Precompile + address common.Address + transferKeeper pcommon.TransferKeeper + evmKeeper pcommon.EVMKeeper + clientKeeper pcommon.ClientKeeper + connectionKeeper pcommon.ConnectionKeeper + channelKeeper pcommon.ChannelKeeper + + TransferID []byte + TransferWithDefaultTimeoutID []byte +} + +func NewPrecompile( + transferKeeper pcommon.TransferKeeper, + evmKeeper pcommon.EVMKeeper, + clientKeeper pcommon.ClientKeeper, + connectionKeeper pcommon.ConnectionKeeper, + channelKeeper pcommon.ChannelKeeper) (*Precompile, error) { + newAbi := GetABI() + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + address: common.HexToAddress(IBCAddress), + transferKeeper: transferKeeper, + evmKeeper: evmKeeper, + clientKeeper: clientKeeper, + connectionKeeper: connectionKeeper, + channelKeeper: channelKeeper, + } + + for name, m := range newAbi.Methods { + switch name { + case TransferMethod: + p.TransferID = m.ID + case TransferWithDefaultTimeoutMethod: + p.TransferWithDefaultTimeoutID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return pcommon.UnknownMethodCallGas + } + + method, err := p.ABI.MethodById(methodID) + if err != nil { + // This should never happen since this method is going to fail during Run + return pcommon.UnknownMethodCallGas + } + + return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) +} + +func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { + defer func() { + if err != nil { + evm.StateDB.(*state.DBImpl).SetPrecompileError(err) + } + }() + if readOnly { + return nil, 0, errors.New("cannot call IBC precompile from staticcall") + } + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, 0, err + } + if caller.Cmp(callingContract) != 0 { + return nil, 0, errors.New("cannot delegatecall IBC") + } + + gasMultiplier := p.evmKeeper.GetPriorityNormalizer(ctx) + gasLimitBigInt := new(big.Int).Mul(new(big.Int).SetUint64(suppliedGas), gasMultiplier.TruncateInt().BigInt()) + if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { + gasLimitBigInt = utils.BigMaxU64 + } + ctx = ctx.WithGasMeter(sdk.NewGasMeterWithMultiplier(ctx, gasLimitBigInt.Uint64())) + + switch method.Name { + case TransferMethod: + return p.transfer(ctx, method, args, caller) + case TransferWithDefaultTimeoutMethod: + return p.transferWithDefaultTimeout(ctx, method, args, caller) + } + return +} + +func (p Precompile) Run(*vm.EVM, common.Address, common.Address, []byte, *big.Int, bool, bool) (bz []byte, err error) { + panic("static gas Run is not implemented for dynamic gas precompile") +} + +func (p Precompile) transfer(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + + if err := pcommon.ValidateArgsLength(args, 8); err != nil { + rerr = err + return + } + validatedArgs, err := p.validateCommonArgs(ctx, args, caller) + if err != nil { + rerr = err + return + } + + if validatedArgs.amount.Cmp(big.NewInt(0)) == 0 { + // short circuit + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return + } + + coin := sdk.Coin{ + Denom: validatedArgs.denom, + Amount: sdk.NewIntFromBigInt(validatedArgs.amount), + } + + revisionNumber, ok := args[5].(uint64) + if !ok { + rerr = errors.New("revisionNumber is not a uint64") + return + } + + revisionHeight, ok := args[6].(uint64) + if !ok { + rerr = errors.New("revisionHeight is not a uint64") + return + } + + height := clienttypes.Height{ + RevisionNumber: revisionNumber, + RevisionHeight: revisionHeight, + } + + timeoutTimestamp, ok := args[7].(uint64) + if !ok { + rerr = errors.New("timeoutTimestamp is not a uint64") + return + } + + err = p.transferKeeper.SendTransfer( + ctx, + validatedArgs.port, + validatedArgs.channelID, + coin, + validatedArgs.senderSeiAddr, + validatedArgs.receiverAddressString, + height, + timeoutTimestamp) + + if err != nil { + rerr = err + return + } + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return +} + +func (p Precompile) transferWithDefaultTimeout(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + + if err := pcommon.ValidateArgsLength(args, 5); err != nil { + rerr = err + return + } + validatedArgs, err := p.validateCommonArgs(ctx, args, caller) + if err != nil { + rerr = err + return + } + + if validatedArgs.amount.Cmp(big.NewInt(0)) == 0 { + // short circuit + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return + } + + coin := sdk.Coin{ + Denom: validatedArgs.denom, + Amount: sdk.NewIntFromBigInt(validatedArgs.amount), + } + + connection, err := p.getChannelConnection(ctx, validatedArgs.port, validatedArgs.channelID) + + if err != nil { + rerr = err + return + } + + latestConsensusHeight, err := p.getConsensusLatestHeight(ctx, *connection) + if err != nil { + rerr = err + return + } + + height, err := GetAdjustedHeight(*latestConsensusHeight) + if err != nil { + rerr = err + return + } + + timeoutTimestamp, err := p.GetAdjustedTimestamp(ctx, connection.ClientId, *latestConsensusHeight) + if err != nil { + rerr = err + return + } + + err = p.transferKeeper.SendTransfer( + ctx, + validatedArgs.port, + validatedArgs.channelID, + coin, + validatedArgs.senderSeiAddr, + validatedArgs.receiverAddressString, + height, + timeoutTimestamp) + + if err != nil { + rerr = err + return + } + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return +} + +func (Precompile) IsTransaction(method string) bool { + switch method { + case TransferMethod: + return true + default: + return false + } +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "ibc" +} + +func (p Precompile) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { + addr := arg.(common.Address) + if addr == (common.Address{}) { + return nil, errors.New("invalid addr") + } + seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, addr) + if !found { + return nil, fmt.Errorf("EVM address %s is not associated", addr.Hex()) + } + return seiAddr, nil +} + +func (p Precompile) getChannelConnection(ctx sdk.Context, port string, channelID string) (*connectiontypes.ConnectionEnd, error) { + channel, found := p.channelKeeper.GetChannel(ctx, port, channelID) + if !found { + return nil, errors.New("channel not found") + } + + connection, found := p.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + + if !found { + return nil, errors.New("connection not found") + } + return &connection, nil +} + +func (p Precompile) getConsensusLatestHeight(ctx sdk.Context, connection connectiontypes.ConnectionEnd) (*clienttypes.Height, error) { + clientState, found := p.clientKeeper.GetClientState(ctx, connection.ClientId) + + if !found { + return nil, errors.New("could not get the client state") + } + + latestHeight := clientState.GetLatestHeight() + return &clienttypes.Height{ + RevisionNumber: latestHeight.GetRevisionNumber(), + RevisionHeight: latestHeight.GetRevisionHeight(), + }, nil +} + +func GetAdjustedHeight(latestConsensusHeight clienttypes.Height) (clienttypes.Height, error) { + defaultTimeoutHeight, err := clienttypes.ParseHeight(types.DefaultRelativePacketTimeoutHeight) + if err != nil { + return clienttypes.Height{}, err + } + + absoluteHeight := latestConsensusHeight + absoluteHeight.RevisionNumber += defaultTimeoutHeight.RevisionNumber + absoluteHeight.RevisionHeight += defaultTimeoutHeight.RevisionHeight + return absoluteHeight, nil +} + +func (p Precompile) GetAdjustedTimestamp(ctx sdk.Context, clientId string, height clienttypes.Height) (uint64, error) { + consensusState, found := p.clientKeeper.GetClientConsensusState(ctx, clientId, height) + var consensusStateTimestamp uint64 + if found { + consensusStateTimestamp = consensusState.GetTimestamp() + } + + defaultRelativePacketTimeoutTimestamp := types.DefaultRelativePacketTimeoutTimestamp + blockTime := ctx.BlockTime().UnixNano() + if blockTime > 0 { + now := uint64(blockTime) + if now > consensusStateTimestamp { + return now + defaultRelativePacketTimeoutTimestamp, nil + } else { + return consensusStateTimestamp + defaultRelativePacketTimeoutTimestamp, nil + } + } else { + return 0, errors.New("block time is not greater than Jan 1st, 1970 12:00 AM") + } +} + +type ValidatedArgs struct { + senderSeiAddr sdk.AccAddress + receiverAddressString string + port string + channelID string + denom string + amount *big.Int +} + +func (p Precompile) validateCommonArgs(ctx sdk.Context, args []interface{}, caller common.Address) (*ValidatedArgs, error) { + senderSeiAddr, ok := p.evmKeeper.GetSeiAddress(ctx, caller) + if !ok { + return nil, errors.New("caller is not a valid SEI address") + } + + receiverAddressString, ok := args[0].(string) + if !ok { + return nil, errors.New("receiverAddress is not a string") + } + _, bz, err := bech32.DecodeAndConvert(receiverAddressString) + if err != nil { + return nil, err + } + err = sdk.VerifyAddressFormat(bz) + if err != nil { + return nil, err + } + + port, ok := args[1].(string) + if !ok { + return nil, errors.New("port is not a string") + } + if port == "" { + return nil, errors.New("port cannot be empty") + } + + channelID, ok := args[2].(string) + if !ok { + return nil, errors.New("channelID is not a string") + } + if channelID == "" { + return nil, errors.New("channelID cannot be empty") + } + + denom := args[3].(string) + if denom == "" { + return nil, errors.New("invalid denom") + } + + amount, ok := args[4].(*big.Int) + if !ok { + return nil, errors.New("amount is not a big.Int") + } + return &ValidatedArgs{ + senderSeiAddr: senderSeiAddr, + receiverAddressString: receiverAddressString, + port: port, + channelID: channelID, + denom: denom, + amount: amount, + }, nil +} diff --git a/precompiles/ibc/legacy/v555/abi.json b/precompiles/ibc/legacy/v555/abi.json new file mode 100644 index 0000000000..2114833792 --- /dev/null +++ b/precompiles/ibc/legacy/v555/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"port","type":"string"},{"internalType":"string","name":"channel","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint64","name":"revisionNumber","type":"uint64"},{"internalType":"uint64","name":"revisionHeight","type":"uint64"},{"internalType":"uint64","name":"timeoutTimestamp","type":"uint64"},{"internalType":"string","name":"memo","type":"string"}],"name":"transfer","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"port","type":"string"},{"internalType":"string","name":"channel","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"memo","type":"string"}],"name":"transferWithDefaultTimeout","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"}] \ No newline at end of file diff --git a/precompiles/ibc/legacy/v555/ibc.go b/precompiles/ibc/legacy/v555/ibc.go new file mode 100644 index 0000000000..8573f5f6c7 --- /dev/null +++ b/precompiles/ibc/legacy/v555/ibc.go @@ -0,0 +1,485 @@ +package v555 + +import ( + "bytes" + "embed" + "errors" + "fmt" + "math/big" + + "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" + + "github.com/cosmos/cosmos-sdk/types/bech32" + + "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/x/evm/state" + + sdk "github.com/cosmos/cosmos-sdk/types" + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v3/modules/core/03-connection/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/vm" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v555" +) + +const ( + TransferMethod = "transfer" + TransferWithDefaultTimeoutMethod = "transferWithDefaultTimeout" +) + +const ( + IBCAddress = "0x0000000000000000000000000000000000001009" +) + +var _ vm.PrecompiledContract = &Precompile{} +var _ vm.DynamicGasPrecompiledContract = &Precompile{} + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +func GetABI() abi.ABI { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +type Precompile struct { + pcommon.Precompile + address common.Address + transferKeeper pcommon.TransferKeeper + evmKeeper pcommon.EVMKeeper + clientKeeper pcommon.ClientKeeper + connectionKeeper pcommon.ConnectionKeeper + channelKeeper pcommon.ChannelKeeper + + TransferID []byte + TransferWithDefaultTimeoutID []byte +} + +func NewPrecompile( + transferKeeper pcommon.TransferKeeper, + evmKeeper pcommon.EVMKeeper, + clientKeeper pcommon.ClientKeeper, + connectionKeeper pcommon.ConnectionKeeper, + channelKeeper pcommon.ChannelKeeper) (*Precompile, error) { + newAbi := GetABI() + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + address: common.HexToAddress(IBCAddress), + transferKeeper: transferKeeper, + evmKeeper: evmKeeper, + clientKeeper: clientKeeper, + connectionKeeper: connectionKeeper, + channelKeeper: channelKeeper, + } + + for name, m := range newAbi.Methods { + switch name { + case TransferMethod: + p.TransferID = m.ID + case TransferWithDefaultTimeoutMethod: + p.TransferWithDefaultTimeoutID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return pcommon.UnknownMethodCallGas + } + + method, err := p.ABI.MethodById(methodID) + if err != nil { + // This should never happen since this method is going to fail during Run + return pcommon.UnknownMethodCallGas + } + + return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) +} + +func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, _ *big.Int, _ *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { + defer func() { + if err != nil { + evm.StateDB.(*state.DBImpl).SetPrecompileError(err) + } + }() + if readOnly { + return nil, 0, errors.New("cannot call IBC precompile from staticcall") + } + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, 0, err + } + if caller.Cmp(callingContract) != 0 { + return nil, 0, errors.New("cannot delegatecall IBC") + } + + gasMultiplier := p.evmKeeper.GetPriorityNormalizer(ctx) + gasLimitBigInt := new(big.Int).Mul(new(big.Int).SetUint64(suppliedGas), gasMultiplier.TruncateInt().BigInt()) + if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { + gasLimitBigInt = utils.BigMaxU64 + } + ctx = ctx.WithGasMeter(sdk.NewGasMeterWithMultiplier(ctx, gasLimitBigInt.Uint64())) + + switch method.Name { + case TransferMethod: + return p.transfer(ctx, method, args, caller) + case TransferWithDefaultTimeoutMethod: + return p.transferWithDefaultTimeout(ctx, method, args, caller) + } + return +} + +func (p Precompile) Run(*vm.EVM, common.Address, common.Address, []byte, *big.Int, bool, bool) (bz []byte, err error) { + panic("static gas Run is not implemented for dynamic gas precompile") +} + +func (p Precompile) transfer(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + + if err := pcommon.ValidateArgsLength(args, 9); err != nil { + rerr = err + return + } + validatedArgs, err := p.validateCommonArgs(ctx, args, caller) + if err != nil { + rerr = err + return + } + + if validatedArgs.amount.Cmp(big.NewInt(0)) == 0 { + // short circuit + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return + } + + coin := sdk.Coin{ + Denom: validatedArgs.denom, + Amount: sdk.NewIntFromBigInt(validatedArgs.amount), + } + + revisionNumber, ok := args[5].(uint64) + if !ok { + rerr = errors.New("revisionNumber is not a uint64") + return + } + + revisionHeight, ok := args[6].(uint64) + if !ok { + rerr = errors.New("revisionHeight is not a uint64") + return + } + + height := clienttypes.Height{ + RevisionNumber: revisionNumber, + RevisionHeight: revisionHeight, + } + + timeoutTimestamp, ok := args[7].(uint64) + if !ok { + rerr = errors.New("timeoutTimestamp is not a uint64") + return + } + + msg := types.MsgTransfer{ + SourcePort: validatedArgs.port, + SourceChannel: validatedArgs.channelID, + Token: coin, + Sender: validatedArgs.senderSeiAddr.String(), + Receiver: validatedArgs.receiverAddressString, + TimeoutHeight: height, + TimeoutTimestamp: timeoutTimestamp, + } + + msg = addMemo(args[8], msg) + + err = msg.ValidateBasic() + if err != nil { + rerr = err + return + } + + _, err = p.transferKeeper.Transfer(sdk.WrapSDKContext(ctx), &msg) + + if err != nil { + rerr = err + return + } + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return +} + +func (p Precompile) transferWithDefaultTimeout(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + + if err := pcommon.ValidateArgsLength(args, 6); err != nil { + rerr = err + return + } + validatedArgs, err := p.validateCommonArgs(ctx, args, caller) + if err != nil { + rerr = err + return + } + + if validatedArgs.amount.Cmp(big.NewInt(0)) == 0 { + // short circuit + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return + } + + coin := sdk.Coin{ + Denom: validatedArgs.denom, + Amount: sdk.NewIntFromBigInt(validatedArgs.amount), + } + + connection, err := p.getChannelConnection(ctx, validatedArgs.port, validatedArgs.channelID) + + if err != nil { + rerr = err + return + } + + latestConsensusHeight, err := p.getConsensusLatestHeight(ctx, *connection) + if err != nil { + rerr = err + return + } + + height, err := GetAdjustedHeight(*latestConsensusHeight) + if err != nil { + rerr = err + return + } + + timeoutTimestamp, err := p.GetAdjustedTimestamp(ctx, connection.ClientId, *latestConsensusHeight) + if err != nil { + rerr = err + return + } + + msg := types.MsgTransfer{ + SourcePort: validatedArgs.port, + SourceChannel: validatedArgs.channelID, + Token: coin, + Sender: validatedArgs.senderSeiAddr.String(), + Receiver: validatedArgs.receiverAddressString, + TimeoutHeight: height, + TimeoutTimestamp: timeoutTimestamp, + } + + msg = addMemo(args[5], msg) + + err = msg.ValidateBasic() + if err != nil { + rerr = err + return + } + + _, err = p.transferKeeper.Transfer(sdk.WrapSDKContext(ctx), &msg) + + if err != nil { + rerr = err + return + } + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return +} + +func (Precompile) IsTransaction(method string) bool { + switch method { + case TransferMethod: + return true + case TransferWithDefaultTimeoutMethod: + return true + default: + return false + } +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "ibc" +} + +func (p Precompile) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { + addr := arg.(common.Address) + if addr == (common.Address{}) { + return nil, errors.New("invalid addr") + } + seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, addr) + if !found { + return nil, fmt.Errorf("EVM address %s is not associated", addr.Hex()) + } + return seiAddr, nil +} + +func (p Precompile) getChannelConnection(ctx sdk.Context, port string, channelID string) (*connectiontypes.ConnectionEnd, error) { + channel, found := p.channelKeeper.GetChannel(ctx, port, channelID) + if !found { + return nil, errors.New("channel not found") + } + + connection, found := p.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + + if !found { + return nil, errors.New("connection not found") + } + return &connection, nil +} + +func (p Precompile) getConsensusLatestHeight(ctx sdk.Context, connection connectiontypes.ConnectionEnd) (*clienttypes.Height, error) { + clientState, found := p.clientKeeper.GetClientState(ctx, connection.ClientId) + + if !found { + return nil, errors.New("could not get the client state") + } + + latestHeight := clientState.GetLatestHeight() + return &clienttypes.Height{ + RevisionNumber: latestHeight.GetRevisionNumber(), + RevisionHeight: latestHeight.GetRevisionHeight(), + }, nil +} + +func GetAdjustedHeight(latestConsensusHeight clienttypes.Height) (clienttypes.Height, error) { + defaultTimeoutHeight, err := clienttypes.ParseHeight(types.DefaultRelativePacketTimeoutHeight) + if err != nil { + return clienttypes.Height{}, err + } + + absoluteHeight := latestConsensusHeight + absoluteHeight.RevisionNumber += defaultTimeoutHeight.RevisionNumber + absoluteHeight.RevisionHeight += defaultTimeoutHeight.RevisionHeight + return absoluteHeight, nil +} + +func (p Precompile) GetAdjustedTimestamp(ctx sdk.Context, clientId string, height clienttypes.Height) (uint64, error) { + consensusState, found := p.clientKeeper.GetClientConsensusState(ctx, clientId, height) + var consensusStateTimestamp uint64 + if found { + consensusStateTimestamp = consensusState.GetTimestamp() + } + + defaultRelativePacketTimeoutTimestamp := types.DefaultRelativePacketTimeoutTimestamp + blockTime := ctx.BlockTime().UnixNano() + if blockTime > 0 { + now := uint64(blockTime) + if now > consensusStateTimestamp { + return now + defaultRelativePacketTimeoutTimestamp, nil + } else { + return consensusStateTimestamp + defaultRelativePacketTimeoutTimestamp, nil + } + } else { + return 0, errors.New("block time is not greater than Jan 1st, 1970 12:00 AM") + } +} + +type ValidatedArgs struct { + senderSeiAddr sdk.AccAddress + receiverAddressString string + port string + channelID string + denom string + amount *big.Int +} + +func (p Precompile) validateCommonArgs(ctx sdk.Context, args []interface{}, caller common.Address) (*ValidatedArgs, error) { + senderSeiAddr, ok := p.evmKeeper.GetSeiAddress(ctx, caller) + if !ok { + return nil, errors.New("caller is not a valid SEI address") + } + + receiverAddressString, ok := args[0].(string) + if !ok { + return nil, errors.New("receiverAddress is not a string") + } + _, bz, err := bech32.DecodeAndConvert(receiverAddressString) + if err != nil { + return nil, err + } + err = sdk.VerifyAddressFormat(bz) + if err != nil { + return nil, err + } + + port, ok := args[1].(string) + if !ok { + return nil, errors.New("port is not a string") + } + if port == "" { + return nil, errors.New("port cannot be empty") + } + + channelID, ok := args[2].(string) + if !ok { + return nil, errors.New("channelID is not a string") + } + if channelID == "" { + return nil, errors.New("channelID cannot be empty") + } + + denom := args[3].(string) + if denom == "" { + return nil, errors.New("invalid denom") + } + + amount, ok := args[4].(*big.Int) + if !ok { + return nil, errors.New("amount is not a big.Int") + } + return &ValidatedArgs{ + senderSeiAddr: senderSeiAddr, + receiverAddressString: receiverAddressString, + port: port, + channelID: channelID, + denom: denom, + amount: amount, + }, nil +} + +func addMemo(memoArg interface{}, transferMsg types.MsgTransfer) types.MsgTransfer { + memo := "" + if memoArg != nil { + memo = memoArg.(string) + } + transferMsg.Memo = memo + return transferMsg +} diff --git a/precompiles/ibc/legacy/v562/abi.json b/precompiles/ibc/legacy/v562/abi.json new file mode 100644 index 0000000000..2114833792 --- /dev/null +++ b/precompiles/ibc/legacy/v562/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"port","type":"string"},{"internalType":"string","name":"channel","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint64","name":"revisionNumber","type":"uint64"},{"internalType":"uint64","name":"revisionHeight","type":"uint64"},{"internalType":"uint64","name":"timeoutTimestamp","type":"uint64"},{"internalType":"string","name":"memo","type":"string"}],"name":"transfer","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"port","type":"string"},{"internalType":"string","name":"channel","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"memo","type":"string"}],"name":"transferWithDefaultTimeout","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"}] \ No newline at end of file diff --git a/precompiles/ibc/legacy/v562/ibc.go b/precompiles/ibc/legacy/v562/ibc.go new file mode 100644 index 0000000000..53b34107c1 --- /dev/null +++ b/precompiles/ibc/legacy/v562/ibc.go @@ -0,0 +1,423 @@ +package v562 + +import ( + "bytes" + "embed" + "errors" + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/bech32" + "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v3/modules/core/03-connection/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/legacy/v562" + evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const ( + TransferMethod = "transfer" + TransferWithDefaultTimeoutMethod = "transferWithDefaultTimeout" +) + +const ( + IBCAddress = "0x0000000000000000000000000000000000001009" +) + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +func GetABI() abi.ABI { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +type PrecompileExecutor struct { + transferKeeper pcommon.TransferKeeper + evmKeeper pcommon.EVMKeeper + clientKeeper pcommon.ClientKeeper + connectionKeeper pcommon.ConnectionKeeper + channelKeeper pcommon.ChannelKeeper + + TransferID []byte + TransferWithDefaultTimeoutID []byte +} + +func NewPrecompile( + transferKeeper pcommon.TransferKeeper, + evmKeeper pcommon.EVMKeeper, + clientKeeper pcommon.ClientKeeper, + connectionKeeper pcommon.ConnectionKeeper, + channelKeeper pcommon.ChannelKeeper) (*pcommon.DynamicGasPrecompile, error) { + newAbi := GetABI() + + p := &PrecompileExecutor{ + transferKeeper: transferKeeper, + evmKeeper: evmKeeper, + clientKeeper: clientKeeper, + connectionKeeper: connectionKeeper, + channelKeeper: channelKeeper, + } + + for name, m := range newAbi.Methods { + switch name { + case TransferMethod: + p.TransferID = m.ID + case TransferWithDefaultTimeoutMethod: + p.TransferWithDefaultTimeoutID = m.ID + } + } + + return pcommon.NewDynamicGasPrecompile(newAbi, p, common.HexToAddress(IBCAddress), "ibc"), nil +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + if readOnly { + return nil, 0, errors.New("cannot call IBC precompile from staticcall") + } + if caller.Cmp(callingContract) != 0 { + return nil, 0, errors.New("cannot delegatecall IBC") + } + + switch method.Name { + case TransferMethod: + return p.transfer(ctx, method, args, caller) + case TransferWithDefaultTimeoutMethod: + return p.transferWithDefaultTimeout(ctx, method, args, caller) + } + return +} + +func (p PrecompileExecutor) EVMKeeper() pcommon.EVMKeeper { + return p.evmKeeper +} + +func (p PrecompileExecutor) transfer(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + + if err := pcommon.ValidateArgsLength(args, 9); err != nil { + rerr = err + return + } + validatedArgs, err := p.validateCommonArgs(ctx, args, caller) + if err != nil { + rerr = err + return + } + + if validatedArgs.amount.Cmp(big.NewInt(0)) == 0 { + // short circuit + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return + } + + coin := sdk.Coin{ + Denom: validatedArgs.denom, + Amount: sdk.NewIntFromBigInt(validatedArgs.amount), + } + + revisionNumber, ok := args[5].(uint64) + if !ok { + rerr = errors.New("revisionNumber is not a uint64") + return + } + + revisionHeight, ok := args[6].(uint64) + if !ok { + rerr = errors.New("revisionHeight is not a uint64") + return + } + + height := clienttypes.Height{ + RevisionNumber: revisionNumber, + RevisionHeight: revisionHeight, + } + + timeoutTimestamp, ok := args[7].(uint64) + if !ok { + rerr = errors.New("timeoutTimestamp is not a uint64") + return + } + + msg := types.MsgTransfer{ + SourcePort: validatedArgs.port, + SourceChannel: validatedArgs.channelID, + Token: coin, + Sender: validatedArgs.senderSeiAddr.String(), + Receiver: validatedArgs.receiverAddressString, + TimeoutHeight: height, + TimeoutTimestamp: timeoutTimestamp, + } + + msg = addMemo(args[8], msg) + + err = msg.ValidateBasic() + if err != nil { + rerr = err + return + } + + _, err = p.transferKeeper.Transfer(sdk.WrapSDKContext(ctx), &msg) + + if err != nil { + rerr = err + return + } + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return +} + +func (p PrecompileExecutor) transferWithDefaultTimeout(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + + if err := pcommon.ValidateArgsLength(args, 6); err != nil { + rerr = err + return + } + validatedArgs, err := p.validateCommonArgs(ctx, args, caller) + if err != nil { + rerr = err + return + } + + if validatedArgs.amount.Cmp(big.NewInt(0)) == 0 { + // short circuit + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return + } + + coin := sdk.Coin{ + Denom: validatedArgs.denom, + Amount: sdk.NewIntFromBigInt(validatedArgs.amount), + } + + connection, err := p.getChannelConnection(ctx, validatedArgs.port, validatedArgs.channelID) + + if err != nil { + rerr = err + return + } + + latestConsensusHeight, err := p.getConsensusLatestHeight(ctx, *connection) + if err != nil { + rerr = err + return + } + + height, err := GetAdjustedHeight(*latestConsensusHeight) + if err != nil { + rerr = err + return + } + + timeoutTimestamp, err := p.GetAdjustedTimestamp(ctx, connection.ClientId, *latestConsensusHeight) + if err != nil { + rerr = err + return + } + + msg := types.MsgTransfer{ + SourcePort: validatedArgs.port, + SourceChannel: validatedArgs.channelID, + Token: coin, + Sender: validatedArgs.senderSeiAddr.String(), + Receiver: validatedArgs.receiverAddressString, + TimeoutHeight: height, + TimeoutTimestamp: timeoutTimestamp, + } + + msg = addMemo(args[5], msg) + + err = msg.ValidateBasic() + if err != nil { + rerr = err + return + } + + _, err = p.transferKeeper.Transfer(sdk.WrapSDKContext(ctx), &msg) + + if err != nil { + rerr = err + return + } + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return +} + +func (p PrecompileExecutor) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { + addr := arg.(common.Address) + if addr == (common.Address{}) { + return nil, errors.New("invalid addr") + } + seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, addr) + if !found { + return nil, evmtypes.NewAssociationMissingErr(addr.Hex()) + } + return seiAddr, nil +} + +func (p PrecompileExecutor) getChannelConnection(ctx sdk.Context, port string, channelID string) (*connectiontypes.ConnectionEnd, error) { + channel, found := p.channelKeeper.GetChannel(ctx, port, channelID) + if !found { + return nil, errors.New("channel not found") + } + + connection, found := p.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + + if !found { + return nil, errors.New("connection not found") + } + return &connection, nil +} + +func (p PrecompileExecutor) getConsensusLatestHeight(ctx sdk.Context, connection connectiontypes.ConnectionEnd) (*clienttypes.Height, error) { + clientState, found := p.clientKeeper.GetClientState(ctx, connection.ClientId) + + if !found { + return nil, errors.New("could not get the client state") + } + + latestHeight := clientState.GetLatestHeight() + return &clienttypes.Height{ + RevisionNumber: latestHeight.GetRevisionNumber(), + RevisionHeight: latestHeight.GetRevisionHeight(), + }, nil +} + +func GetAdjustedHeight(latestConsensusHeight clienttypes.Height) (clienttypes.Height, error) { + defaultTimeoutHeight, err := clienttypes.ParseHeight(types.DefaultRelativePacketTimeoutHeight) + if err != nil { + return clienttypes.Height{}, err + } + + absoluteHeight := latestConsensusHeight + absoluteHeight.RevisionNumber += defaultTimeoutHeight.RevisionNumber + absoluteHeight.RevisionHeight += defaultTimeoutHeight.RevisionHeight + return absoluteHeight, nil +} + +func (p PrecompileExecutor) GetAdjustedTimestamp(ctx sdk.Context, clientId string, height clienttypes.Height) (uint64, error) { + consensusState, found := p.clientKeeper.GetClientConsensusState(ctx, clientId, height) + var consensusStateTimestamp uint64 + if found { + consensusStateTimestamp = consensusState.GetTimestamp() + } + + defaultRelativePacketTimeoutTimestamp := types.DefaultRelativePacketTimeoutTimestamp + blockTime := ctx.BlockTime().UnixNano() + if blockTime > 0 { + now := uint64(blockTime) + if now > consensusStateTimestamp { + return now + defaultRelativePacketTimeoutTimestamp, nil + } else { + return consensusStateTimestamp + defaultRelativePacketTimeoutTimestamp, nil + } + } else { + return 0, errors.New("block time is not greater than Jan 1st, 1970 12:00 AM") + } +} + +type ValidatedArgs struct { + senderSeiAddr sdk.AccAddress + receiverAddressString string + port string + channelID string + denom string + amount *big.Int +} + +func (p PrecompileExecutor) validateCommonArgs(ctx sdk.Context, args []interface{}, caller common.Address) (*ValidatedArgs, error) { + senderSeiAddr, ok := p.evmKeeper.GetSeiAddress(ctx, caller) + if !ok { + return nil, errors.New("caller is not a valid SEI address") + } + + receiverAddressString, ok := args[0].(string) + if !ok { + return nil, errors.New("receiverAddress is not a string") + } + _, bz, err := bech32.DecodeAndConvert(receiverAddressString) + if err != nil { + return nil, err + } + err = sdk.VerifyAddressFormat(bz) + if err != nil { + return nil, err + } + + port, ok := args[1].(string) + if !ok { + return nil, errors.New("port is not a string") + } + if port == "" { + return nil, errors.New("port cannot be empty") + } + + channelID, ok := args[2].(string) + if !ok { + return nil, errors.New("channelID is not a string") + } + if channelID == "" { + return nil, errors.New("channelID cannot be empty") + } + + denom := args[3].(string) + if denom == "" { + return nil, errors.New("invalid denom") + } + + amount, ok := args[4].(*big.Int) + if !ok { + return nil, errors.New("amount is not a big.Int") + } + return &ValidatedArgs{ + senderSeiAddr: senderSeiAddr, + receiverAddressString: receiverAddressString, + port: port, + channelID: channelID, + denom: denom, + amount: amount, + }, nil +} + +func addMemo(memoArg interface{}, transferMsg types.MsgTransfer) types.MsgTransfer { + memo := "" + if memoArg != nil { + memo = memoArg.(string) + } + transferMsg.Memo = memo + return transferMsg +} diff --git a/precompiles/ibc/legacy/v580/abi.json b/precompiles/ibc/legacy/v580/abi.json new file mode 100644 index 0000000000..2114833792 --- /dev/null +++ b/precompiles/ibc/legacy/v580/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"port","type":"string"},{"internalType":"string","name":"channel","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint64","name":"revisionNumber","type":"uint64"},{"internalType":"uint64","name":"revisionHeight","type":"uint64"},{"internalType":"uint64","name":"timeoutTimestamp","type":"uint64"},{"internalType":"string","name":"memo","type":"string"}],"name":"transfer","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"port","type":"string"},{"internalType":"string","name":"channel","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"memo","type":"string"}],"name":"transferWithDefaultTimeout","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"}] \ No newline at end of file diff --git a/precompiles/ibc/legacy/v580/ibc.go b/precompiles/ibc/legacy/v580/ibc.go new file mode 100644 index 0000000000..8b61fd236e --- /dev/null +++ b/precompiles/ibc/legacy/v580/ibc.go @@ -0,0 +1,409 @@ +package v580 + +import ( + "embed" + "errors" + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/bech32" + "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v3/modules/core/03-connection/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/legacy/v580" + evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const ( + TransferMethod = "transfer" + TransferWithDefaultTimeoutMethod = "transferWithDefaultTimeout" +) + +const ( + IBCAddress = "0x0000000000000000000000000000000000001009" +) + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + transferKeeper pcommon.TransferKeeper + evmKeeper pcommon.EVMKeeper + clientKeeper pcommon.ClientKeeper + connectionKeeper pcommon.ConnectionKeeper + channelKeeper pcommon.ChannelKeeper + + TransferID []byte + TransferWithDefaultTimeoutID []byte +} + +func NewPrecompile( + transferKeeper pcommon.TransferKeeper, + evmKeeper pcommon.EVMKeeper, + clientKeeper pcommon.ClientKeeper, + connectionKeeper pcommon.ConnectionKeeper, + channelKeeper pcommon.ChannelKeeper) (*pcommon.DynamicGasPrecompile, error) { + newAbi := pcommon.MustGetABI(f, "abi.json") + + p := &PrecompileExecutor{ + transferKeeper: transferKeeper, + evmKeeper: evmKeeper, + clientKeeper: clientKeeper, + connectionKeeper: connectionKeeper, + channelKeeper: channelKeeper, + } + + for name, m := range newAbi.Methods { + switch name { + case TransferMethod: + p.TransferID = m.ID + case TransferWithDefaultTimeoutMethod: + p.TransferWithDefaultTimeoutID = m.ID + } + } + + return pcommon.NewDynamicGasPrecompile(newAbi, p, common.HexToAddress(IBCAddress), "ibc"), nil +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + if readOnly { + return nil, 0, errors.New("cannot call IBC precompile from staticcall") + } + if caller.Cmp(callingContract) != 0 { + return nil, 0, errors.New("cannot delegatecall IBC") + } + + switch method.Name { + case TransferMethod: + return p.transfer(ctx, method, args, caller) + case TransferWithDefaultTimeoutMethod: + return p.transferWithDefaultTimeout(ctx, method, args, caller) + } + return +} + +func (p PrecompileExecutor) EVMKeeper() pcommon.EVMKeeper { + return p.evmKeeper +} + +func (p PrecompileExecutor) transfer(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + + if err := pcommon.ValidateArgsLength(args, 9); err != nil { + rerr = err + return + } + validatedArgs, err := p.validateCommonArgs(ctx, args, caller) + if err != nil { + rerr = err + return + } + + if validatedArgs.amount.Cmp(big.NewInt(0)) == 0 { + // short circuit + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return + } + + coin := sdk.Coin{ + Denom: validatedArgs.denom, + Amount: sdk.NewIntFromBigInt(validatedArgs.amount), + } + + revisionNumber, ok := args[5].(uint64) + if !ok { + rerr = errors.New("revisionNumber is not a uint64") + return + } + + revisionHeight, ok := args[6].(uint64) + if !ok { + rerr = errors.New("revisionHeight is not a uint64") + return + } + + height := clienttypes.Height{ + RevisionNumber: revisionNumber, + RevisionHeight: revisionHeight, + } + + timeoutTimestamp, ok := args[7].(uint64) + if !ok { + rerr = errors.New("timeoutTimestamp is not a uint64") + return + } + + msg := types.MsgTransfer{ + SourcePort: validatedArgs.port, + SourceChannel: validatedArgs.channelID, + Token: coin, + Sender: validatedArgs.senderSeiAddr.String(), + Receiver: validatedArgs.receiverAddressString, + TimeoutHeight: height, + TimeoutTimestamp: timeoutTimestamp, + } + + msg = addMemo(args[8], msg) + + err = msg.ValidateBasic() + if err != nil { + rerr = err + return + } + + _, err = p.transferKeeper.Transfer(sdk.WrapSDKContext(ctx), &msg) + + if err != nil { + rerr = err + return + } + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return +} + +func (p PrecompileExecutor) transferWithDefaultTimeout(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + + if err := pcommon.ValidateArgsLength(args, 6); err != nil { + rerr = err + return + } + validatedArgs, err := p.validateCommonArgs(ctx, args, caller) + if err != nil { + rerr = err + return + } + + if validatedArgs.amount.Cmp(big.NewInt(0)) == 0 { + // short circuit + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return + } + + coin := sdk.Coin{ + Denom: validatedArgs.denom, + Amount: sdk.NewIntFromBigInt(validatedArgs.amount), + } + + connection, err := p.getChannelConnection(ctx, validatedArgs.port, validatedArgs.channelID) + + if err != nil { + rerr = err + return + } + + latestConsensusHeight, err := p.getConsensusLatestHeight(ctx, *connection) + if err != nil { + rerr = err + return + } + + height, err := GetAdjustedHeight(*latestConsensusHeight) + if err != nil { + rerr = err + return + } + + timeoutTimestamp, err := p.GetAdjustedTimestamp(ctx, connection.ClientId, *latestConsensusHeight) + if err != nil { + rerr = err + return + } + + msg := types.MsgTransfer{ + SourcePort: validatedArgs.port, + SourceChannel: validatedArgs.channelID, + Token: coin, + Sender: validatedArgs.senderSeiAddr.String(), + Receiver: validatedArgs.receiverAddressString, + TimeoutHeight: height, + TimeoutTimestamp: timeoutTimestamp, + } + + msg = addMemo(args[5], msg) + + err = msg.ValidateBasic() + if err != nil { + rerr = err + return + } + + _, err = p.transferKeeper.Transfer(sdk.WrapSDKContext(ctx), &msg) + + if err != nil { + rerr = err + return + } + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return +} + +func (p PrecompileExecutor) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { + addr := arg.(common.Address) + if addr == (common.Address{}) { + return nil, errors.New("invalid addr") + } + seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, addr) + if !found { + return nil, evmtypes.NewAssociationMissingErr(addr.Hex()) + } + return seiAddr, nil +} + +func (p PrecompileExecutor) getChannelConnection(ctx sdk.Context, port string, channelID string) (*connectiontypes.ConnectionEnd, error) { + channel, found := p.channelKeeper.GetChannel(ctx, port, channelID) + if !found { + return nil, errors.New("channel not found") + } + + connection, found := p.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + + if !found { + return nil, errors.New("connection not found") + } + return &connection, nil +} + +func (p PrecompileExecutor) getConsensusLatestHeight(ctx sdk.Context, connection connectiontypes.ConnectionEnd) (*clienttypes.Height, error) { + clientState, found := p.clientKeeper.GetClientState(ctx, connection.ClientId) + + if !found { + return nil, errors.New("could not get the client state") + } + + latestHeight := clientState.GetLatestHeight() + return &clienttypes.Height{ + RevisionNumber: latestHeight.GetRevisionNumber(), + RevisionHeight: latestHeight.GetRevisionHeight(), + }, nil +} + +func GetAdjustedHeight(latestConsensusHeight clienttypes.Height) (clienttypes.Height, error) { + defaultTimeoutHeight, err := clienttypes.ParseHeight(types.DefaultRelativePacketTimeoutHeight) + if err != nil { + return clienttypes.Height{}, err + } + + absoluteHeight := latestConsensusHeight + absoluteHeight.RevisionNumber += defaultTimeoutHeight.RevisionNumber + absoluteHeight.RevisionHeight += defaultTimeoutHeight.RevisionHeight + return absoluteHeight, nil +} + +func (p PrecompileExecutor) GetAdjustedTimestamp(ctx sdk.Context, clientId string, height clienttypes.Height) (uint64, error) { + consensusState, found := p.clientKeeper.GetClientConsensusState(ctx, clientId, height) + var consensusStateTimestamp uint64 + if found { + consensusStateTimestamp = consensusState.GetTimestamp() + } + + defaultRelativePacketTimeoutTimestamp := types.DefaultRelativePacketTimeoutTimestamp + blockTime := ctx.BlockTime().UnixNano() + if blockTime > 0 { + now := uint64(blockTime) + if now > consensusStateTimestamp { + return now + defaultRelativePacketTimeoutTimestamp, nil + } else { + return consensusStateTimestamp + defaultRelativePacketTimeoutTimestamp, nil + } + } else { + return 0, errors.New("block time is not greater than Jan 1st, 1970 12:00 AM") + } +} + +type ValidatedArgs struct { + senderSeiAddr sdk.AccAddress + receiverAddressString string + port string + channelID string + denom string + amount *big.Int +} + +func (p PrecompileExecutor) validateCommonArgs(ctx sdk.Context, args []interface{}, caller common.Address) (*ValidatedArgs, error) { + senderSeiAddr, ok := p.evmKeeper.GetSeiAddress(ctx, caller) + if !ok { + return nil, errors.New("caller is not a valid SEI address") + } + + receiverAddressString, ok := args[0].(string) + if !ok { + return nil, errors.New("receiverAddress is not a string") + } + _, bz, err := bech32.DecodeAndConvert(receiverAddressString) + if err != nil { + return nil, err + } + err = sdk.VerifyAddressFormat(bz) + if err != nil { + return nil, err + } + + port, ok := args[1].(string) + if !ok { + return nil, errors.New("port is not a string") + } + if port == "" { + return nil, errors.New("port cannot be empty") + } + + channelID, ok := args[2].(string) + if !ok { + return nil, errors.New("channelID is not a string") + } + if channelID == "" { + return nil, errors.New("channelID cannot be empty") + } + + denom := args[3].(string) + if denom == "" { + return nil, errors.New("invalid denom") + } + + amount, ok := args[4].(*big.Int) + if !ok { + return nil, errors.New("amount is not a big.Int") + } + return &ValidatedArgs{ + senderSeiAddr: senderSeiAddr, + receiverAddressString: receiverAddressString, + port: port, + channelID: channelID, + denom: denom, + amount: amount, + }, nil +} + +func addMemo(memoArg interface{}, transferMsg types.MsgTransfer) types.MsgTransfer { + memo := "" + if memoArg != nil { + memo = memoArg.(string) + } + transferMsg.Memo = memo + return transferMsg +} diff --git a/precompiles/ibc/legacy/v602/abi.json b/precompiles/ibc/legacy/v602/abi.json new file mode 100644 index 0000000000..2114833792 --- /dev/null +++ b/precompiles/ibc/legacy/v602/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"port","type":"string"},{"internalType":"string","name":"channel","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint64","name":"revisionNumber","type":"uint64"},{"internalType":"uint64","name":"revisionHeight","type":"uint64"},{"internalType":"uint64","name":"timeoutTimestamp","type":"uint64"},{"internalType":"string","name":"memo","type":"string"}],"name":"transfer","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"port","type":"string"},{"internalType":"string","name":"channel","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"memo","type":"string"}],"name":"transferWithDefaultTimeout","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"}] \ No newline at end of file diff --git a/precompiles/ibc/legacy/v602/ibc.go b/precompiles/ibc/legacy/v602/ibc.go new file mode 100644 index 0000000000..aafdd328d4 --- /dev/null +++ b/precompiles/ibc/legacy/v602/ibc.go @@ -0,0 +1,409 @@ +package v602 + +import ( + "embed" + "errors" + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/bech32" + "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v3/modules/core/03-connection/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" + evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const ( + TransferMethod = "transfer" + TransferWithDefaultTimeoutMethod = "transferWithDefaultTimeout" +) + +const ( + IBCAddress = "0x0000000000000000000000000000000000001009" +) + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + transferKeeper pcommon.TransferKeeper + evmKeeper pcommon.EVMKeeper + clientKeeper pcommon.ClientKeeper + connectionKeeper pcommon.ConnectionKeeper + channelKeeper pcommon.ChannelKeeper + + TransferID []byte + TransferWithDefaultTimeoutID []byte +} + +func NewPrecompile( + transferKeeper pcommon.TransferKeeper, + evmKeeper pcommon.EVMKeeper, + clientKeeper pcommon.ClientKeeper, + connectionKeeper pcommon.ConnectionKeeper, + channelKeeper pcommon.ChannelKeeper) (*pcommon.DynamicGasPrecompile, error) { + newAbi := pcommon.MustGetABI(f, "abi.json") + + p := &PrecompileExecutor{ + transferKeeper: transferKeeper, + evmKeeper: evmKeeper, + clientKeeper: clientKeeper, + connectionKeeper: connectionKeeper, + channelKeeper: channelKeeper, + } + + for name, m := range newAbi.Methods { + switch name { + case TransferMethod: + p.TransferID = m.ID + case TransferWithDefaultTimeoutMethod: + p.TransferWithDefaultTimeoutID = m.ID + } + } + + return pcommon.NewDynamicGasPrecompile(newAbi, p, common.HexToAddress(IBCAddress), "ibc"), nil +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + if readOnly { + return nil, 0, errors.New("cannot call IBC precompile from staticcall") + } + if ctx.EVMPrecompileCalledFromDelegateCall() { + return nil, 0, errors.New("cannot delegatecall IBC") + } + + switch method.Name { + case TransferMethod: + return p.transfer(ctx, method, args, caller) + case TransferWithDefaultTimeoutMethod: + return p.transferWithDefaultTimeout(ctx, method, args, caller) + } + return +} + +func (p PrecompileExecutor) EVMKeeper() pcommon.EVMKeeper { + return p.evmKeeper +} + +func (p PrecompileExecutor) transfer(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + + if err := pcommon.ValidateArgsLength(args, 9); err != nil { + rerr = err + return + } + validatedArgs, err := p.validateCommonArgs(ctx, args, caller) + if err != nil { + rerr = err + return + } + + if validatedArgs.amount.Cmp(big.NewInt(0)) == 0 { + // short circuit + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return + } + + coin := sdk.Coin{ + Denom: validatedArgs.denom, + Amount: sdk.NewIntFromBigInt(validatedArgs.amount), + } + + revisionNumber, ok := args[5].(uint64) + if !ok { + rerr = errors.New("revisionNumber is not a uint64") + return + } + + revisionHeight, ok := args[6].(uint64) + if !ok { + rerr = errors.New("revisionHeight is not a uint64") + return + } + + height := clienttypes.Height{ + RevisionNumber: revisionNumber, + RevisionHeight: revisionHeight, + } + + timeoutTimestamp, ok := args[7].(uint64) + if !ok { + rerr = errors.New("timeoutTimestamp is not a uint64") + return + } + + msg := types.MsgTransfer{ + SourcePort: validatedArgs.port, + SourceChannel: validatedArgs.channelID, + Token: coin, + Sender: validatedArgs.senderSeiAddr.String(), + Receiver: validatedArgs.receiverAddressString, + TimeoutHeight: height, + TimeoutTimestamp: timeoutTimestamp, + } + + msg = addMemo(args[8], msg) + + err = msg.ValidateBasic() + if err != nil { + rerr = err + return + } + + _, err = p.transferKeeper.Transfer(sdk.WrapSDKContext(ctx), &msg) + + if err != nil { + rerr = err + return + } + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return +} + +func (p PrecompileExecutor) transferWithDefaultTimeout(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + + if err := pcommon.ValidateArgsLength(args, 6); err != nil { + rerr = err + return + } + validatedArgs, err := p.validateCommonArgs(ctx, args, caller) + if err != nil { + rerr = err + return + } + + if validatedArgs.amount.Cmp(big.NewInt(0)) == 0 { + // short circuit + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return + } + + coin := sdk.Coin{ + Denom: validatedArgs.denom, + Amount: sdk.NewIntFromBigInt(validatedArgs.amount), + } + + connection, err := p.getChannelConnection(ctx, validatedArgs.port, validatedArgs.channelID) + + if err != nil { + rerr = err + return + } + + latestConsensusHeight, err := p.getConsensusLatestHeight(ctx, *connection) + if err != nil { + rerr = err + return + } + + height, err := GetAdjustedHeight(*latestConsensusHeight) + if err != nil { + rerr = err + return + } + + timeoutTimestamp, err := p.GetAdjustedTimestamp(ctx, connection.ClientId, *latestConsensusHeight) + if err != nil { + rerr = err + return + } + + msg := types.MsgTransfer{ + SourcePort: validatedArgs.port, + SourceChannel: validatedArgs.channelID, + Token: coin, + Sender: validatedArgs.senderSeiAddr.String(), + Receiver: validatedArgs.receiverAddressString, + TimeoutHeight: height, + TimeoutTimestamp: timeoutTimestamp, + } + + msg = addMemo(args[5], msg) + + err = msg.ValidateBasic() + if err != nil { + rerr = err + return + } + + _, err = p.transferKeeper.Transfer(sdk.WrapSDKContext(ctx), &msg) + + if err != nil { + rerr = err + return + } + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return +} + +func (p PrecompileExecutor) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { + addr := arg.(common.Address) + if addr == (common.Address{}) { + return nil, errors.New("invalid addr") + } + seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, addr) + if !found { + return nil, evmtypes.NewAssociationMissingErr(addr.Hex()) + } + return seiAddr, nil +} + +func (p PrecompileExecutor) getChannelConnection(ctx sdk.Context, port string, channelID string) (*connectiontypes.ConnectionEnd, error) { + channel, found := p.channelKeeper.GetChannel(ctx, port, channelID) + if !found { + return nil, errors.New("channel not found") + } + + connection, found := p.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + + if !found { + return nil, errors.New("connection not found") + } + return &connection, nil +} + +func (p PrecompileExecutor) getConsensusLatestHeight(ctx sdk.Context, connection connectiontypes.ConnectionEnd) (*clienttypes.Height, error) { + clientState, found := p.clientKeeper.GetClientState(ctx, connection.ClientId) + + if !found { + return nil, errors.New("could not get the client state") + } + + latestHeight := clientState.GetLatestHeight() + return &clienttypes.Height{ + RevisionNumber: latestHeight.GetRevisionNumber(), + RevisionHeight: latestHeight.GetRevisionHeight(), + }, nil +} + +func GetAdjustedHeight(latestConsensusHeight clienttypes.Height) (clienttypes.Height, error) { + defaultTimeoutHeight, err := clienttypes.ParseHeight(types.DefaultRelativePacketTimeoutHeight) + if err != nil { + return clienttypes.Height{}, err + } + + absoluteHeight := latestConsensusHeight + absoluteHeight.RevisionNumber += defaultTimeoutHeight.RevisionNumber + absoluteHeight.RevisionHeight += defaultTimeoutHeight.RevisionHeight + return absoluteHeight, nil +} + +func (p PrecompileExecutor) GetAdjustedTimestamp(ctx sdk.Context, clientId string, height clienttypes.Height) (uint64, error) { + consensusState, found := p.clientKeeper.GetClientConsensusState(ctx, clientId, height) + var consensusStateTimestamp uint64 + if found { + consensusStateTimestamp = consensusState.GetTimestamp() + } + + defaultRelativePacketTimeoutTimestamp := types.DefaultRelativePacketTimeoutTimestamp + blockTime := ctx.BlockTime().UnixNano() + if blockTime > 0 { + now := uint64(blockTime) + if now > consensusStateTimestamp { + return now + defaultRelativePacketTimeoutTimestamp, nil + } else { + return consensusStateTimestamp + defaultRelativePacketTimeoutTimestamp, nil + } + } else { + return 0, errors.New("block time is not greater than Jan 1st, 1970 12:00 AM") + } +} + +type ValidatedArgs struct { + senderSeiAddr sdk.AccAddress + receiverAddressString string + port string + channelID string + denom string + amount *big.Int +} + +func (p PrecompileExecutor) validateCommonArgs(ctx sdk.Context, args []interface{}, caller common.Address) (*ValidatedArgs, error) { + senderSeiAddr, ok := p.evmKeeper.GetSeiAddress(ctx, caller) + if !ok { + return nil, errors.New("caller is not a valid SEI address") + } + + receiverAddressString, ok := args[0].(string) + if !ok { + return nil, errors.New("receiverAddress is not a string") + } + _, bz, err := bech32.DecodeAndConvert(receiverAddressString) + if err != nil { + return nil, err + } + err = sdk.VerifyAddressFormat(bz) + if err != nil { + return nil, err + } + + port, ok := args[1].(string) + if !ok { + return nil, errors.New("port is not a string") + } + if port == "" { + return nil, errors.New("port cannot be empty") + } + + channelID, ok := args[2].(string) + if !ok { + return nil, errors.New("channelID is not a string") + } + if channelID == "" { + return nil, errors.New("channelID cannot be empty") + } + + denom := args[3].(string) + if denom == "" { + return nil, errors.New("invalid denom") + } + + amount, ok := args[4].(*big.Int) + if !ok { + return nil, errors.New("amount is not a big.Int") + } + return &ValidatedArgs{ + senderSeiAddr: senderSeiAddr, + receiverAddressString: receiverAddressString, + port: port, + channelID: channelID, + denom: denom, + amount: amount, + }, nil +} + +func addMemo(memoArg interface{}, transferMsg types.MsgTransfer) types.MsgTransfer { + memo := "" + if memoArg != nil { + memo = memoArg.(string) + } + transferMsg.Memo = memo + return transferMsg +} diff --git a/precompiles/ibc/legacy/v603/abi.json b/precompiles/ibc/legacy/v603/abi.json new file mode 100644 index 0000000000..2114833792 --- /dev/null +++ b/precompiles/ibc/legacy/v603/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"port","type":"string"},{"internalType":"string","name":"channel","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint64","name":"revisionNumber","type":"uint64"},{"internalType":"uint64","name":"revisionHeight","type":"uint64"},{"internalType":"uint64","name":"timeoutTimestamp","type":"uint64"},{"internalType":"string","name":"memo","type":"string"}],"name":"transfer","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"port","type":"string"},{"internalType":"string","name":"channel","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"memo","type":"string"}],"name":"transferWithDefaultTimeout","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"}] \ No newline at end of file diff --git a/precompiles/ibc/legacy/v603/ibc.go b/precompiles/ibc/legacy/v603/ibc.go new file mode 100644 index 0000000000..8b53307480 --- /dev/null +++ b/precompiles/ibc/legacy/v603/ibc.go @@ -0,0 +1,400 @@ +package v603 + +import ( + "embed" + "errors" + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v3/modules/core/03-connection/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" + evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const ( + TransferMethod = "transfer" + TransferWithDefaultTimeoutMethod = "transferWithDefaultTimeout" +) + +const ( + IBCAddress = "0x0000000000000000000000000000000000001009" +) + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + transferKeeper pcommon.TransferKeeper + evmKeeper pcommon.EVMKeeper + clientKeeper pcommon.ClientKeeper + connectionKeeper pcommon.ConnectionKeeper + channelKeeper pcommon.ChannelKeeper + + TransferID []byte + TransferWithDefaultTimeoutID []byte +} + +func NewPrecompile( + transferKeeper pcommon.TransferKeeper, + evmKeeper pcommon.EVMKeeper, + clientKeeper pcommon.ClientKeeper, + connectionKeeper pcommon.ConnectionKeeper, + channelKeeper pcommon.ChannelKeeper) (*pcommon.DynamicGasPrecompile, error) { + newAbi := pcommon.MustGetABI(f, "abi.json") + + p := &PrecompileExecutor{ + transferKeeper: transferKeeper, + evmKeeper: evmKeeper, + clientKeeper: clientKeeper, + connectionKeeper: connectionKeeper, + channelKeeper: channelKeeper, + } + + for name, m := range newAbi.Methods { + switch name { + case TransferMethod: + p.TransferID = m.ID + case TransferWithDefaultTimeoutMethod: + p.TransferWithDefaultTimeoutID = m.ID + } + } + + return pcommon.NewDynamicGasPrecompile(newAbi, p, common.HexToAddress(IBCAddress), "ibc"), nil +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + if readOnly { + return nil, 0, errors.New("cannot call IBC precompile from staticcall") + } + if ctx.EVMPrecompileCalledFromDelegateCall() { + return nil, 0, errors.New("cannot delegatecall IBC") + } + + switch method.Name { + case TransferMethod: + return p.transfer(ctx, method, args, caller) + case TransferWithDefaultTimeoutMethod: + return p.transferWithDefaultTimeout(ctx, method, args, caller) + } + return +} + +func (p PrecompileExecutor) EVMKeeper() pcommon.EVMKeeper { + return p.evmKeeper +} + +func (p PrecompileExecutor) transfer(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + + if err := pcommon.ValidateArgsLength(args, 9); err != nil { + rerr = err + return + } + validatedArgs, err := p.validateCommonArgs(ctx, args, caller) + if err != nil { + rerr = err + return + } + + if validatedArgs.amount.Cmp(big.NewInt(0)) == 0 { + // short circuit + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return + } + + coin := sdk.Coin{ + Denom: validatedArgs.denom, + Amount: sdk.NewIntFromBigInt(validatedArgs.amount), + } + + revisionNumber, ok := args[5].(uint64) + if !ok { + rerr = errors.New("revisionNumber is not a uint64") + return + } + + revisionHeight, ok := args[6].(uint64) + if !ok { + rerr = errors.New("revisionHeight is not a uint64") + return + } + + height := clienttypes.Height{ + RevisionNumber: revisionNumber, + RevisionHeight: revisionHeight, + } + + timeoutTimestamp, ok := args[7].(uint64) + if !ok { + rerr = errors.New("timeoutTimestamp is not a uint64") + return + } + + msg := types.MsgTransfer{ + SourcePort: validatedArgs.port, + SourceChannel: validatedArgs.channelID, + Token: coin, + Sender: validatedArgs.senderSeiAddr.String(), + Receiver: validatedArgs.receiverAddressString, + TimeoutHeight: height, + TimeoutTimestamp: timeoutTimestamp, + } + + msg = addMemo(args[8], msg) + + err = msg.ValidateBasic() + if err != nil { + rerr = err + return + } + + _, err = p.transferKeeper.Transfer(sdk.WrapSDKContext(ctx), &msg) + + if err != nil { + rerr = err + return + } + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return +} + +func (p PrecompileExecutor) transferWithDefaultTimeout(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + + if err := pcommon.ValidateArgsLength(args, 6); err != nil { + rerr = err + return + } + validatedArgs, err := p.validateCommonArgs(ctx, args, caller) + if err != nil { + rerr = err + return + } + + if validatedArgs.amount.Cmp(big.NewInt(0)) == 0 { + // short circuit + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return + } + + coin := sdk.Coin{ + Denom: validatedArgs.denom, + Amount: sdk.NewIntFromBigInt(validatedArgs.amount), + } + + connection, err := p.getChannelConnection(ctx, validatedArgs.port, validatedArgs.channelID) + + if err != nil { + rerr = err + return + } + + latestConsensusHeight, err := p.getConsensusLatestHeight(ctx, *connection) + if err != nil { + rerr = err + return + } + + height, err := GetAdjustedHeight(*latestConsensusHeight) + if err != nil { + rerr = err + return + } + + timeoutTimestamp, err := p.GetAdjustedTimestamp(ctx, connection.ClientId, *latestConsensusHeight) + if err != nil { + rerr = err + return + } + + msg := types.MsgTransfer{ + SourcePort: validatedArgs.port, + SourceChannel: validatedArgs.channelID, + Token: coin, + Sender: validatedArgs.senderSeiAddr.String(), + Receiver: validatedArgs.receiverAddressString, + TimeoutHeight: height, + TimeoutTimestamp: timeoutTimestamp, + } + + msg = addMemo(args[5], msg) + + err = msg.ValidateBasic() + if err != nil { + rerr = err + return + } + + _, err = p.transferKeeper.Transfer(sdk.WrapSDKContext(ctx), &msg) + + if err != nil { + rerr = err + return + } + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + ret, rerr = method.Outputs.Pack(true) + return +} + +func (p PrecompileExecutor) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { + addr := arg.(common.Address) + if addr == (common.Address{}) { + return nil, errors.New("invalid addr") + } + seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, addr) + if !found { + return nil, evmtypes.NewAssociationMissingErr(addr.Hex()) + } + return seiAddr, nil +} + +func (p PrecompileExecutor) getChannelConnection(ctx sdk.Context, port string, channelID string) (*connectiontypes.ConnectionEnd, error) { + channel, found := p.channelKeeper.GetChannel(ctx, port, channelID) + if !found { + return nil, errors.New("channel not found") + } + + connection, found := p.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + + if !found { + return nil, errors.New("connection not found") + } + return &connection, nil +} + +func (p PrecompileExecutor) getConsensusLatestHeight(ctx sdk.Context, connection connectiontypes.ConnectionEnd) (*clienttypes.Height, error) { + clientState, found := p.clientKeeper.GetClientState(ctx, connection.ClientId) + + if !found { + return nil, errors.New("could not get the client state") + } + + latestHeight := clientState.GetLatestHeight() + return &clienttypes.Height{ + RevisionNumber: latestHeight.GetRevisionNumber(), + RevisionHeight: latestHeight.GetRevisionHeight(), + }, nil +} + +func GetAdjustedHeight(latestConsensusHeight clienttypes.Height) (clienttypes.Height, error) { + defaultTimeoutHeight, err := clienttypes.ParseHeight(types.DefaultRelativePacketTimeoutHeight) + if err != nil { + return clienttypes.Height{}, err + } + + absoluteHeight := latestConsensusHeight + absoluteHeight.RevisionNumber += defaultTimeoutHeight.RevisionNumber + absoluteHeight.RevisionHeight += defaultTimeoutHeight.RevisionHeight + return absoluteHeight, nil +} + +func (p PrecompileExecutor) GetAdjustedTimestamp(ctx sdk.Context, clientId string, height clienttypes.Height) (uint64, error) { + consensusState, found := p.clientKeeper.GetClientConsensusState(ctx, clientId, height) + var consensusStateTimestamp uint64 + if found { + consensusStateTimestamp = consensusState.GetTimestamp() + } + + defaultRelativePacketTimeoutTimestamp := types.DefaultRelativePacketTimeoutTimestamp + blockTime := ctx.BlockTime().UnixNano() + if blockTime > 0 { + now := uint64(blockTime) + if now > consensusStateTimestamp { + return now + defaultRelativePacketTimeoutTimestamp, nil + } else { + return consensusStateTimestamp + defaultRelativePacketTimeoutTimestamp, nil + } + } else { + return 0, errors.New("block time is not greater than Jan 1st, 1970 12:00 AM") + } +} + +type ValidatedArgs struct { + senderSeiAddr sdk.AccAddress + receiverAddressString string + port string + channelID string + denom string + amount *big.Int +} + +func (p PrecompileExecutor) validateCommonArgs(ctx sdk.Context, args []interface{}, caller common.Address) (*ValidatedArgs, error) { + senderSeiAddr, ok := p.evmKeeper.GetSeiAddress(ctx, caller) + if !ok { + return nil, errors.New("caller is not a valid SEI address") + } + + receiverAddressString, ok := args[0].(string) + if !ok || receiverAddressString == "" { + return nil, errors.New("receiverAddress is not a string or empty") + } + + port, ok := args[1].(string) + if !ok { + return nil, errors.New("port is not a string") + } + if port == "" { + return nil, errors.New("port cannot be empty") + } + + channelID, ok := args[2].(string) + if !ok { + return nil, errors.New("channelID is not a string") + } + if channelID == "" { + return nil, errors.New("channelID cannot be empty") + } + + denom := args[3].(string) + if denom == "" { + return nil, errors.New("invalid denom") + } + + amount, ok := args[4].(*big.Int) + if !ok { + return nil, errors.New("amount is not a big.Int") + } + return &ValidatedArgs{ + senderSeiAddr: senderSeiAddr, + receiverAddressString: receiverAddressString, + port: port, + channelID: channelID, + denom: denom, + amount: amount, + }, nil +} + +func addMemo(memoArg interface{}, transferMsg types.MsgTransfer) types.MsgTransfer { + memo := "" + if memoArg != nil { + memo = memoArg.(string) + } + transferMsg.Memo = memo + return transferMsg +} diff --git a/precompiles/json/legacy/v520/abi.json b/precompiles/json/legacy/v520/abi.json new file mode 100644 index 0000000000..0170dd5a9d --- /dev/null +++ b/precompiles/json/legacy/v520/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"bytes","name":"input","type":"bytes"},{"internalType":"string","name":"key","type":"string"}],"name":"extractAsBytes","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"input","type":"bytes"},{"internalType":"string","name":"key","type":"string"}],"name":"extractAsBytesList","outputs":[{"internalType":"bytes[]","name":"response","type":"bytes[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"input","type":"bytes"},{"internalType":"string","name":"key","type":"string"}],"name":"extractAsUint256","outputs":[{"internalType":"uint256","name":"response","type":"uint256"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/json/legacy/v520/json.go b/precompiles/json/legacy/v520/json.go new file mode 100644 index 0000000000..705cfbe3b5 --- /dev/null +++ b/precompiles/json/legacy/v520/json.go @@ -0,0 +1,211 @@ +package v520 + +import ( + "bytes" + "embed" + gjson "encoding/json" + "errors" + "fmt" + "math/big" + "strings" + + 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/legacy/v520" + "github.com/sei-protocol/sei-chain/utils" +) + +const ( + ExtractAsBytesMethod = "extractAsBytes" + ExtractAsBytesListMethod = "extractAsBytesList" + ExtractAsUint256Method = "extractAsUint256" +) + +const JSONAddress = "0x0000000000000000000000000000000000001003" +const GasCostPerByte = 100 // TODO: parameterize + +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 + address common.Address + + ExtractAsBytesID []byte + ExtractAsBytesListID []byte + ExtractAsUint256ID []byte +} + +func NewPrecompile() (*Precompile, error) { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + return nil, fmt.Errorf("error loading the staking ABI %s", err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + return nil, err + } + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + address: common.HexToAddress(JSONAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case ExtractAsBytesMethod: + p.ExtractAsBytesID = m.ID + case ExtractAsBytesListMethod: + p.ExtractAsBytesListID = m.ID + case ExtractAsUint256Method: + p.ExtractAsUint256ID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + if len(input) < 4 { + return pcommon.UnknownMethodCallGas + } + return uint64(GasCostPerByte * (len(input) - 4)) +} + +func (Precompile) IsTransaction(string) bool { + return false +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "json" +} + +func (p Precompile) Run(evm *vm.EVM, _ common.Address, _ common.Address, input []byte, value *big.Int, _ bool, _ bool) (bz []byte, err error) { + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, err + } + + switch method.Name { + case ExtractAsBytesMethod: + return p.extractAsBytes(ctx, method, args, value) + case ExtractAsBytesListMethod: + return p.extractAsBytesList(ctx, method, args, value) + case ExtractAsUint256Method: + byteArr := make([]byte, 32) + uint_, err := p.ExtractAsUint256(ctx, method, args, value) + if err != nil { + return nil, err + } + + if uint_.BitLen() > 256 { + return nil, errors.New("value does not fit in 32 bytes") + } + + uint_.FillBytes(byteArr) + return byteArr, nil + } + return +} + +func (p Precompile) extractAsBytes(_ sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + bz := args[0].([]byte) + decoded := map[string]gjson.RawMessage{} + if err := gjson.Unmarshal(bz, &decoded); err != nil { + return nil, err + } + key := args[1].(string) + result, ok := decoded[key] + if !ok { + return nil, fmt.Errorf("input does not contain key %s", key) + } + // in the case of a string value, remove the quotes + if len(result) >= 2 && result[0] == '"' && result[len(result)-1] == '"' { + result = result[1 : len(result)-1] + } + + return method.Outputs.Pack([]byte(result)) +} + +func (p Precompile) extractAsBytesList(_ sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + bz := args[0].([]byte) + decoded := map[string]gjson.RawMessage{} + if err := gjson.Unmarshal(bz, &decoded); err != nil { + return nil, err + } + key := args[1].(string) + result, ok := decoded[key] + if !ok { + return nil, fmt.Errorf("input does not contain key %s", key) + } + decodedResult := []gjson.RawMessage{} + if err := gjson.Unmarshal(result, &decodedResult); err != nil { + return nil, err + } + + return method.Outputs.Pack(utils.Map(decodedResult, func(r gjson.RawMessage) []byte { return []byte(r) })) +} + +func (p Precompile) ExtractAsUint256(_ sdk.Context, _ *abi.Method, args []interface{}, value *big.Int) (*big.Int, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + bz := args[0].([]byte) + decoded := map[string]gjson.RawMessage{} + if err := gjson.Unmarshal(bz, &decoded); err != nil { + return nil, err + } + key := args[1].(string) + result, ok := decoded[key] + if !ok { + return nil, fmt.Errorf("input does not contain key %s", key) + } + + // Assuming result is your byte slice + // Convert byte slice to string and trim quotation marks + strValue := strings.Trim(string(result), "\"") + + // Convert the string to big.Int + value, success := new(big.Int).SetString(strValue, 10) + if !success { + return nil, fmt.Errorf("failed to convert %s to big.Int", strValue) + } + + return value, nil +} diff --git a/precompiles/json/legacy/v530/abi.json b/precompiles/json/legacy/v530/abi.json new file mode 100644 index 0000000000..0170dd5a9d --- /dev/null +++ b/precompiles/json/legacy/v530/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"bytes","name":"input","type":"bytes"},{"internalType":"string","name":"key","type":"string"}],"name":"extractAsBytes","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"input","type":"bytes"},{"internalType":"string","name":"key","type":"string"}],"name":"extractAsBytesList","outputs":[{"internalType":"bytes[]","name":"response","type":"bytes[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"input","type":"bytes"},{"internalType":"string","name":"key","type":"string"}],"name":"extractAsUint256","outputs":[{"internalType":"uint256","name":"response","type":"uint256"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/json/legacy/v530/json.go b/precompiles/json/legacy/v530/json.go new file mode 100644 index 0000000000..5e29a98b66 --- /dev/null +++ b/precompiles/json/legacy/v530/json.go @@ -0,0 +1,217 @@ +package v530 + +import ( + "bytes" + "embed" + gjson "encoding/json" + "errors" + "fmt" + "math/big" + "strings" + + 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/legacy/v530" + "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/x/evm/state" +) + +const ( + ExtractAsBytesMethod = "extractAsBytes" + ExtractAsBytesListMethod = "extractAsBytesList" + ExtractAsUint256Method = "extractAsUint256" +) + +const JSONAddress = "0x0000000000000000000000000000000000001003" +const GasCostPerByte = 100 // TODO: parameterize + +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 + address common.Address + + ExtractAsBytesID []byte + ExtractAsBytesListID []byte + ExtractAsUint256ID []byte +} + +func NewPrecompile() (*Precompile, error) { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + return nil, fmt.Errorf("error loading the staking ABI %s", err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + return nil, err + } + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + address: common.HexToAddress(JSONAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case ExtractAsBytesMethod: + p.ExtractAsBytesID = m.ID + case ExtractAsBytesListMethod: + p.ExtractAsBytesListID = m.ID + case ExtractAsUint256Method: + p.ExtractAsUint256ID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + if len(input) < 4 { + return pcommon.UnknownMethodCallGas + } + return uint64(GasCostPerByte * (len(input) - 4)) +} + +func (Precompile) IsTransaction(string) bool { + return false +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "json" +} + +func (p Precompile) Run(evm *vm.EVM, _ common.Address, _ common.Address, input []byte, value *big.Int, _ bool, _ bool) (bz []byte, err error) { + defer func() { + if err != nil { + evm.StateDB.(*state.DBImpl).SetPrecompileError(err) + } + }() + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, err + } + + switch method.Name { + case ExtractAsBytesMethod: + return p.extractAsBytes(ctx, method, args, value) + case ExtractAsBytesListMethod: + return p.extractAsBytesList(ctx, method, args, value) + case ExtractAsUint256Method: + byteArr := make([]byte, 32) + uint_, err := p.ExtractAsUint256(ctx, method, args, value) + if err != nil { + return nil, err + } + + if uint_.BitLen() > 256 { + return nil, errors.New("value does not fit in 32 bytes") + } + + uint_.FillBytes(byteArr) + return byteArr, nil + } + return +} + +func (p Precompile) extractAsBytes(_ sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + bz := args[0].([]byte) + decoded := map[string]gjson.RawMessage{} + if err := gjson.Unmarshal(bz, &decoded); err != nil { + return nil, err + } + key := args[1].(string) + result, ok := decoded[key] + if !ok { + return nil, fmt.Errorf("input does not contain key %s", key) + } + // in the case of a string value, remove the quotes + if len(result) >= 2 && result[0] == '"' && result[len(result)-1] == '"' { + result = result[1 : len(result)-1] + } + + return method.Outputs.Pack([]byte(result)) +} + +func (p Precompile) extractAsBytesList(_ sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + bz := args[0].([]byte) + decoded := map[string]gjson.RawMessage{} + if err := gjson.Unmarshal(bz, &decoded); err != nil { + return nil, err + } + key := args[1].(string) + result, ok := decoded[key] + if !ok { + return nil, fmt.Errorf("input does not contain key %s", key) + } + decodedResult := []gjson.RawMessage{} + if err := gjson.Unmarshal(result, &decodedResult); err != nil { + return nil, err + } + + return method.Outputs.Pack(utils.Map(decodedResult, func(r gjson.RawMessage) []byte { return []byte(r) })) +} + +func (p Precompile) ExtractAsUint256(_ sdk.Context, _ *abi.Method, args []interface{}, value *big.Int) (*big.Int, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + bz := args[0].([]byte) + decoded := map[string]gjson.RawMessage{} + if err := gjson.Unmarshal(bz, &decoded); err != nil { + return nil, err + } + key := args[1].(string) + result, ok := decoded[key] + if !ok { + return nil, fmt.Errorf("input does not contain key %s", key) + } + + // Assuming result is your byte slice + // Convert byte slice to string and trim quotation marks + strValue := strings.Trim(string(result), "\"") + + // Convert the string to big.Int + value, success := new(big.Int).SetString(strValue, 10) + if !success { + return nil, fmt.Errorf("failed to convert %s to big.Int", strValue) + } + + return value, nil +} diff --git a/precompiles/json/legacy/v555/abi.json b/precompiles/json/legacy/v555/abi.json new file mode 100644 index 0000000000..0170dd5a9d --- /dev/null +++ b/precompiles/json/legacy/v555/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"bytes","name":"input","type":"bytes"},{"internalType":"string","name":"key","type":"string"}],"name":"extractAsBytes","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"input","type":"bytes"},{"internalType":"string","name":"key","type":"string"}],"name":"extractAsBytesList","outputs":[{"internalType":"bytes[]","name":"response","type":"bytes[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"input","type":"bytes"},{"internalType":"string","name":"key","type":"string"}],"name":"extractAsUint256","outputs":[{"internalType":"uint256","name":"response","type":"uint256"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/json/legacy/v555/json.go b/precompiles/json/legacy/v555/json.go new file mode 100644 index 0000000000..1573b26b41 --- /dev/null +++ b/precompiles/json/legacy/v555/json.go @@ -0,0 +1,225 @@ +package v555 + +import ( + "bytes" + "embed" + gjson "encoding/json" + "errors" + "fmt" + "math/big" + "strings" + + 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/legacy/v555" + "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/x/evm/state" +) + +const ( + ExtractAsBytesMethod = "extractAsBytes" + ExtractAsBytesListMethod = "extractAsBytesList" + ExtractAsUint256Method = "extractAsUint256" +) + +const JSONAddress = "0x0000000000000000000000000000000000001003" +const GasCostPerByte = 100 // TODO: parameterize + +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 + address common.Address + + ExtractAsBytesID []byte + ExtractAsBytesListID []byte + ExtractAsUint256ID []byte +} + +func ABI() (*abi.ABI, error) { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + return nil, fmt.Errorf("error loading the json ABI %s", err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + return nil, err + } + return &newAbi, nil +} + +func NewPrecompile() (*Precompile, error) { + newAbi, err := ABI() + if err != nil { + return nil, err + } + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: *newAbi}, + address: common.HexToAddress(JSONAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case ExtractAsBytesMethod: + p.ExtractAsBytesID = m.ID + case ExtractAsBytesListMethod: + p.ExtractAsBytesListID = m.ID + case ExtractAsUint256Method: + p.ExtractAsUint256ID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + if len(input) < 4 { + return pcommon.UnknownMethodCallGas + } + return uint64(GasCostPerByte * (len(input) - 4)) +} + +func (Precompile) IsTransaction(string) bool { + return false +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "json" +} + +func (p Precompile) Run(evm *vm.EVM, _ common.Address, _ common.Address, input []byte, value *big.Int, _ bool, _ bool) (bz []byte, err error) { + defer func() { + if err != nil { + evm.StateDB.(*state.DBImpl).SetPrecompileError(err) + } + }() + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, err + } + + switch method.Name { + case ExtractAsBytesMethod: + return p.extractAsBytes(ctx, method, args, value) + case ExtractAsBytesListMethod: + return p.extractAsBytesList(ctx, method, args, value) + case ExtractAsUint256Method: + byteArr := make([]byte, 32) + uint_, err := p.ExtractAsUint256(ctx, method, args, value) + if err != nil { + return nil, err + } + + if uint_.BitLen() > 256 { + return nil, errors.New("value does not fit in 32 bytes") + } + + uint_.FillBytes(byteArr) + return byteArr, nil + } + return +} + +func (p Precompile) extractAsBytes(_ sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + bz := args[0].([]byte) + decoded := map[string]gjson.RawMessage{} + if err := gjson.Unmarshal(bz, &decoded); err != nil { + return nil, err + } + key := args[1].(string) + result, ok := decoded[key] + if !ok { + return nil, fmt.Errorf("input does not contain key %s", key) + } + // in the case of a string value, remove the quotes + if len(result) >= 2 && result[0] == '"' && result[len(result)-1] == '"' { + result = result[1 : len(result)-1] + } + + return method.Outputs.Pack([]byte(result)) +} + +func (p Precompile) extractAsBytesList(_ sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + bz := args[0].([]byte) + decoded := map[string]gjson.RawMessage{} + if err := gjson.Unmarshal(bz, &decoded); err != nil { + return nil, err + } + key := args[1].(string) + result, ok := decoded[key] + if !ok { + return nil, fmt.Errorf("input does not contain key %s", key) + } + decodedResult := []gjson.RawMessage{} + if err := gjson.Unmarshal(result, &decodedResult); err != nil { + return nil, err + } + + return method.Outputs.Pack(utils.Map(decodedResult, func(r gjson.RawMessage) []byte { return []byte(r) })) +} + +func (p Precompile) ExtractAsUint256(_ sdk.Context, _ *abi.Method, args []interface{}, value *big.Int) (*big.Int, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + bz := args[0].([]byte) + decoded := map[string]gjson.RawMessage{} + if err := gjson.Unmarshal(bz, &decoded); err != nil { + return nil, err + } + key := args[1].(string) + result, ok := decoded[key] + if !ok { + return nil, fmt.Errorf("input does not contain key %s", key) + } + + // Assuming result is your byte slice + // Convert byte slice to string and trim quotation marks + strValue := strings.Trim(string(result), "\"") + + // Convert the string to big.Int + value, success := new(big.Int).SetString(strValue, 10) + if !success { + return nil, fmt.Errorf("failed to convert %s to big.Int", strValue) + } + + return value, nil +} diff --git a/precompiles/json/legacy/v562/abi.json b/precompiles/json/legacy/v562/abi.json new file mode 100644 index 0000000000..0170dd5a9d --- /dev/null +++ b/precompiles/json/legacy/v562/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"bytes","name":"input","type":"bytes"},{"internalType":"string","name":"key","type":"string"}],"name":"extractAsBytes","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"input","type":"bytes"},{"internalType":"string","name":"key","type":"string"}],"name":"extractAsBytesList","outputs":[{"internalType":"bytes[]","name":"response","type":"bytes[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"input","type":"bytes"},{"internalType":"string","name":"key","type":"string"}],"name":"extractAsUint256","outputs":[{"internalType":"uint256","name":"response","type":"uint256"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/json/legacy/v562/json.go b/precompiles/json/legacy/v562/json.go new file mode 100644 index 0000000000..3e3ca0d1c5 --- /dev/null +++ b/precompiles/json/legacy/v562/json.go @@ -0,0 +1,191 @@ +package v562 + +import ( + "bytes" + "embed" + gjson "encoding/json" + "errors" + "fmt" + "math/big" + "strings" + + 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/legacy/v562" + "github.com/sei-protocol/sei-chain/utils" +) + +const ( + ExtractAsBytesMethod = "extractAsBytes" + ExtractAsBytesListMethod = "extractAsBytesList" + ExtractAsUint256Method = "extractAsUint256" +) + +const JSONAddress = "0x0000000000000000000000000000000000001003" +const GasCostPerByte = 100 // TODO: parameterize + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + ExtractAsBytesID []byte + ExtractAsBytesListID []byte + ExtractAsUint256ID []byte +} + +func ABI() (*abi.ABI, error) { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + return nil, fmt.Errorf("error loading the json ABI %s", err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + return nil, err + } + return &newAbi, nil +} + +func NewPrecompile() (*pcommon.Precompile, error) { + newAbi, err := ABI() + if err != nil { + return nil, err + } + + p := &PrecompileExecutor{} + + for name, m := range newAbi.Methods { + switch name { + case ExtractAsBytesMethod: + p.ExtractAsBytesID = m.ID + case ExtractAsBytesListMethod: + p.ExtractAsBytesListID = m.ID + case ExtractAsUint256Method: + p.ExtractAsUint256ID = m.ID + } + } + + return pcommon.NewPrecompile(*newAbi, p, common.HexToAddress(JSONAddress), "json"), nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p PrecompileExecutor) RequiredGas(input []byte, method *abi.Method) uint64 { + return uint64(GasCostPerByte * len(input)) +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM) (bz []byte, err error) { + switch method.Name { + case ExtractAsBytesMethod: + return p.extractAsBytes(ctx, method, args, value) + case ExtractAsBytesListMethod: + return p.extractAsBytesList(ctx, method, args, value) + case ExtractAsUint256Method: + byteArr := make([]byte, 32) + uint_, err := p.ExtractAsUint256(ctx, method, args, value) + if err != nil { + return nil, err + } + + if uint_.BitLen() > 256 { + return nil, errors.New("value does not fit in 32 bytes") + } + + uint_.FillBytes(byteArr) + return byteArr, nil + } + return +} + +func (p PrecompileExecutor) extractAsBytes(_ sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + bz := args[0].([]byte) + decoded := map[string]gjson.RawMessage{} + if err := gjson.Unmarshal(bz, &decoded); err != nil { + return nil, err + } + key := args[1].(string) + result, ok := decoded[key] + if !ok { + return nil, fmt.Errorf("input does not contain key %s", key) + } + // in the case of a string value, remove the quotes + if len(result) >= 2 && result[0] == '"' && result[len(result)-1] == '"' { + result = result[1 : len(result)-1] + } + + return method.Outputs.Pack([]byte(result)) +} + +func (p PrecompileExecutor) extractAsBytesList(_ sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + bz := args[0].([]byte) + decoded := map[string]gjson.RawMessage{} + if err := gjson.Unmarshal(bz, &decoded); err != nil { + return nil, err + } + key := args[1].(string) + result, ok := decoded[key] + if !ok { + return nil, fmt.Errorf("input does not contain key %s", key) + } + decodedResult := []gjson.RawMessage{} + if err := gjson.Unmarshal(result, &decodedResult); err != nil { + return nil, err + } + + return method.Outputs.Pack(utils.Map(decodedResult, func(r gjson.RawMessage) []byte { return []byte(r) })) +} + +func (p PrecompileExecutor) ExtractAsUint256(_ sdk.Context, _ *abi.Method, args []interface{}, value *big.Int) (*big.Int, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + bz := args[0].([]byte) + decoded := map[string]gjson.RawMessage{} + if err := gjson.Unmarshal(bz, &decoded); err != nil { + return nil, err + } + key := args[1].(string) + result, ok := decoded[key] + if !ok { + return nil, fmt.Errorf("input does not contain key %s", key) + } + + // Assuming result is your byte slice + // Convert byte slice to string and trim quotation marks + strValue := strings.Trim(string(result), "\"") + + // Convert the string to big.Int + value, success := new(big.Int).SetString(strValue, 10) + if !success { + return nil, fmt.Errorf("failed to convert %s to big.Int", strValue) + } + + return value, nil +} diff --git a/precompiles/json/legacy/v603/abi.json b/precompiles/json/legacy/v603/abi.json new file mode 100644 index 0000000000..0170dd5a9d --- /dev/null +++ b/precompiles/json/legacy/v603/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"bytes","name":"input","type":"bytes"},{"internalType":"string","name":"key","type":"string"}],"name":"extractAsBytes","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"input","type":"bytes"},{"internalType":"string","name":"key","type":"string"}],"name":"extractAsBytesList","outputs":[{"internalType":"bytes[]","name":"response","type":"bytes[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"input","type":"bytes"},{"internalType":"string","name":"key","type":"string"}],"name":"extractAsUint256","outputs":[{"internalType":"uint256","name":"response","type":"uint256"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/json/legacy/v603/json.go b/precompiles/json/legacy/v603/json.go new file mode 100644 index 0000000000..6a193a2b06 --- /dev/null +++ b/precompiles/json/legacy/v603/json.go @@ -0,0 +1,174 @@ +package v603 + +import ( + "embed" + gjson "encoding/json" + "errors" + "fmt" + "math/big" + "strings" + + 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" + "github.com/sei-protocol/sei-chain/utils" +) + +const ( + ExtractAsBytesMethod = "extractAsBytes" + ExtractAsBytesListMethod = "extractAsBytesList" + ExtractAsUint256Method = "extractAsUint256" +) + +const JSONAddress = "0x0000000000000000000000000000000000001003" +const GasCostPerByte = 100 // TODO: parameterize + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + ExtractAsBytesID []byte + ExtractAsBytesListID []byte + ExtractAsUint256ID []byte +} + +func NewPrecompile() (*pcommon.Precompile, error) { + newAbi := pcommon.MustGetABI(f, "abi.json") + + p := &PrecompileExecutor{} + + for name, m := range newAbi.Methods { + switch name { + case ExtractAsBytesMethod: + p.ExtractAsBytesID = m.ID + case ExtractAsBytesListMethod: + p.ExtractAsBytesListID = m.ID + case ExtractAsUint256Method: + p.ExtractAsUint256ID = m.ID + } + } + + return pcommon.NewPrecompile(newAbi, p, common.HexToAddress(JSONAddress), "json"), nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p PrecompileExecutor) RequiredGas(input []byte, method *abi.Method) uint64 { + return uint64(GasCostPerByte * len(input)) +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM) (bz []byte, err error) { + switch method.Name { + case ExtractAsBytesMethod: + return p.extractAsBytes(ctx, method, args, value) + case ExtractAsBytesListMethod: + return p.extractAsBytesList(ctx, method, args, value) + case ExtractAsUint256Method: + byteArr := make([]byte, 32) + uint_, err := p.ExtractAsUint256(ctx, method, args, value) + if err != nil { + return nil, err + } + + if uint_.BitLen() > 256 { + return nil, errors.New("value does not fit in 32 bytes") + } + + uint_.FillBytes(byteArr) + return byteArr, nil + } + return +} + +func (p PrecompileExecutor) extractAsBytes(_ sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + bz := args[0].([]byte) + decoded := map[string]gjson.RawMessage{} + if err := gjson.Unmarshal(bz, &decoded); err != nil { + return nil, err + } + key := args[1].(string) + result, ok := decoded[key] + if !ok { + return nil, fmt.Errorf("input does not contain key %s", key) + } + // in the case of a string value, remove the quotes + if len(result) >= 2 && result[0] == '"' && result[len(result)-1] == '"' { + result = result[1 : len(result)-1] + } + + return method.Outputs.Pack([]byte(result)) +} + +func (p PrecompileExecutor) extractAsBytesList(_ sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + bz := args[0].([]byte) + decoded := map[string]gjson.RawMessage{} + if err := gjson.Unmarshal(bz, &decoded); err != nil { + return nil, err + } + key := args[1].(string) + result, ok := decoded[key] + if !ok { + return nil, fmt.Errorf("input does not contain key %s", key) + } + decodedResult := []gjson.RawMessage{} + if err := gjson.Unmarshal(result, &decodedResult); err != nil { + return nil, err + } + + return method.Outputs.Pack(utils.Map(decodedResult, func(r gjson.RawMessage) []byte { return []byte(r) })) +} + +func (p PrecompileExecutor) ExtractAsUint256(_ sdk.Context, _ *abi.Method, args []interface{}, value *big.Int) (*big.Int, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + bz := args[0].([]byte) + decoded := map[string]gjson.RawMessage{} + if err := gjson.Unmarshal(bz, &decoded); err != nil { + return nil, err + } + key := args[1].(string) + result, ok := decoded[key] + if !ok { + return nil, fmt.Errorf("input does not contain key %s", key) + } + + // Assuming result is your byte slice + // Convert byte slice to string and trim quotation marks + strValue := strings.Trim(string(result), "\"") + + // Convert the string to big.Int + value, success := new(big.Int).SetString(strValue, 10) + if !success { + return nil, fmt.Errorf("failed to convert %s to big.Int", strValue) + } + + return value, nil +} diff --git a/precompiles/oracle/legacy/v520/abi.json b/precompiles/oracle/legacy/v520/abi.json new file mode 100644 index 0000000000..1bb91d96aa --- /dev/null +++ b/precompiles/oracle/legacy/v520/abi.json @@ -0,0 +1 @@ +[{"inputs":[],"name":"getExchangeRates","outputs":[{"components":[{"internalType":"string","name":"denom","type":"string"},{"components":[{"internalType":"string","name":"exchangeRate","type":"string"},{"internalType":"string","name":"lastUpdate","type":"string"},{"internalType":"int64","name":"lastUpdateTimestamp","type":"int64"}],"internalType":"struct IOracle.OracleExchangeRate","name":"oracleExchangeRateVal","type":"tuple"}],"internalType":"struct IOracle.DenomOracleExchangeRatePair[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64","name":"lookback_seconds","type":"uint64"}],"name":"getOracleTwaps","outputs":[{"components":[{"internalType":"string","name":"denom","type":"string"},{"internalType":"string","name":"twap","type":"string"},{"internalType":"int64","name":"lookbackSeconds","type":"int64"}],"internalType":"struct IOracle.OracleTwap[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/oracle/legacy/v520/oracle.go b/precompiles/oracle/legacy/v520/oracle.go new file mode 100644 index 0000000000..3156e2df0c --- /dev/null +++ b/precompiles/oracle/legacy/v520/oracle.go @@ -0,0 +1,175 @@ +package v520 + +import ( + "bytes" + "embed" + "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/legacy/v520" + "github.com/sei-protocol/sei-chain/x/oracle/types" +) + +const ( + GetExchangeRatesMethod = "getExchangeRates" + GetOracleTwapsMethod = "getOracleTwaps" +) + +const ( + OracleAddress = "0x0000000000000000000000000000000000001008" +) + +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 + +func GetABI() abi.ABI { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +type Precompile struct { + pcommon.Precompile + evmKeeper pcommon.EVMKeeper + oracleKeeper pcommon.OracleKeeper + address common.Address + + GetExchangeRatesId []byte + GetOracleTwapsId []byte +} + +// Define types which deviate slightly from cosmos types (ExchangeRate string vs sdk.Dec) +type OracleExchangeRate struct { + ExchangeRate string `json:"exchangeRate"` + LastUpdate string `json:"lastUpdate"` + LastUpdateTimestamp int64 `json:"lastUpdateTimestamp"` +} + +type DenomOracleExchangeRatePair struct { + Denom string `json:"denom"` + OracleExchangeRateVal OracleExchangeRate `json:"oracleExchangeRateVal"` +} + +type OracleTwap struct { + Denom string `json:"denom"` + Twap string `json:"twap"` + LookbackSeconds int64 `json:"lookbackSeconds"` +} + +func NewPrecompile(oracleKeeper pcommon.OracleKeeper, evmKeeper pcommon.EVMKeeper) (*Precompile, error) { + newAbi := GetABI() + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + evmKeeper: evmKeeper, + address: common.HexToAddress(OracleAddress), + oracleKeeper: oracleKeeper, + } + + for name, m := range newAbi.Methods { + switch name { + case GetExchangeRatesMethod: + p.GetExchangeRatesId = m.ID + case GetOracleTwapsMethod: + p.GetOracleTwapsId = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return pcommon.UnknownMethodCallGas + } + + method, err := p.ABI.MethodById(methodID) + if err != nil { + // This should never happen since this method is going to fail during Run + return pcommon.UnknownMethodCallGas + } + + return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "oracle" +} + +func (p Precompile) Run(evm *vm.EVM, _ common.Address, _ common.Address, input []byte, value *big.Int, _ bool, _ bool) (bz []byte, err error) { + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, err + } + + switch method.Name { + case GetExchangeRatesMethod: + return p.getExchangeRates(ctx, method, args, value) + case GetOracleTwapsMethod: + return p.getOracleTwaps(ctx, method, args, value) + } + return +} + +func (p Precompile) getExchangeRates(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 0); err != nil { + return nil, err + } + exchangeRates := []DenomOracleExchangeRatePair{} + p.oracleKeeper.IterateBaseExchangeRates(ctx, func(denom string, rate types.OracleExchangeRate) (stop bool) { + exchangeRates = append(exchangeRates, DenomOracleExchangeRatePair{Denom: denom, OracleExchangeRateVal: OracleExchangeRate{ExchangeRate: rate.ExchangeRate.String(), LastUpdate: rate.LastUpdate.String(), LastUpdateTimestamp: rate.LastUpdateTimestamp}}) + return false + }) + + return method.Outputs.Pack(exchangeRates) +} + +func (p Precompile) getOracleTwaps(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + lookbackSeconds := args[0].(uint64) + twaps, err := p.oracleKeeper.CalculateTwaps(ctx, lookbackSeconds) + if err != nil { + return nil, err + } + // Convert twap to string + oracleTwaps := make([]OracleTwap, 0, len(twaps)) + for _, twap := range twaps { + oracleTwaps = append(oracleTwaps, OracleTwap{Denom: twap.Denom, Twap: twap.Twap.String(), LookbackSeconds: twap.LookbackSeconds}) + } + return method.Outputs.Pack(oracleTwaps) +} + +func (Precompile) IsTransaction(string) bool { + return false +} diff --git a/precompiles/oracle/legacy/v555/abi.json b/precompiles/oracle/legacy/v555/abi.json new file mode 100644 index 0000000000..1bb91d96aa --- /dev/null +++ b/precompiles/oracle/legacy/v555/abi.json @@ -0,0 +1 @@ +[{"inputs":[],"name":"getExchangeRates","outputs":[{"components":[{"internalType":"string","name":"denom","type":"string"},{"components":[{"internalType":"string","name":"exchangeRate","type":"string"},{"internalType":"string","name":"lastUpdate","type":"string"},{"internalType":"int64","name":"lastUpdateTimestamp","type":"int64"}],"internalType":"struct IOracle.OracleExchangeRate","name":"oracleExchangeRateVal","type":"tuple"}],"internalType":"struct IOracle.DenomOracleExchangeRatePair[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64","name":"lookback_seconds","type":"uint64"}],"name":"getOracleTwaps","outputs":[{"components":[{"internalType":"string","name":"denom","type":"string"},{"internalType":"string","name":"twap","type":"string"},{"internalType":"int64","name":"lookbackSeconds","type":"int64"}],"internalType":"struct IOracle.OracleTwap[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/oracle/legacy/v555/json.go b/precompiles/oracle/legacy/v555/json.go new file mode 100644 index 0000000000..d35bf5c663 --- /dev/null +++ b/precompiles/oracle/legacy/v555/json.go @@ -0,0 +1,181 @@ +package v555 + +import ( + "bytes" + "embed" + "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/legacy/v555" + "github.com/sei-protocol/sei-chain/x/evm/state" + "github.com/sei-protocol/sei-chain/x/oracle/types" +) + +const ( + GetExchangeRatesMethod = "getExchangeRates" + GetOracleTwapsMethod = "getOracleTwaps" +) + +const ( + OracleAddress = "0x0000000000000000000000000000000000001008" +) + +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 + +func GetABI() abi.ABI { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +type Precompile struct { + pcommon.Precompile + evmKeeper pcommon.EVMKeeper + oracleKeeper pcommon.OracleKeeper + address common.Address + + GetExchangeRatesId []byte + GetOracleTwapsId []byte +} + +// Define types which deviate slightly from cosmos types (ExchangeRate string vs sdk.Dec) +type OracleExchangeRate struct { + ExchangeRate string `json:"exchangeRate"` + LastUpdate string `json:"lastUpdate"` + LastUpdateTimestamp int64 `json:"lastUpdateTimestamp"` +} + +type DenomOracleExchangeRatePair struct { + Denom string `json:"denom"` + OracleExchangeRateVal OracleExchangeRate `json:"oracleExchangeRateVal"` +} + +type OracleTwap struct { + Denom string `json:"denom"` + Twap string `json:"twap"` + LookbackSeconds int64 `json:"lookbackSeconds"` +} + +func NewPrecompile(oracleKeeper pcommon.OracleKeeper, evmKeeper pcommon.EVMKeeper) (*Precompile, error) { + newAbi := GetABI() + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + evmKeeper: evmKeeper, + address: common.HexToAddress(OracleAddress), + oracleKeeper: oracleKeeper, + } + + for name, m := range newAbi.Methods { + switch name { + case GetExchangeRatesMethod: + p.GetExchangeRatesId = m.ID + case GetOracleTwapsMethod: + p.GetOracleTwapsId = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return pcommon.UnknownMethodCallGas + } + + method, err := p.ABI.MethodById(methodID) + if err != nil { + // This should never happen since this method is going to fail during Run + return pcommon.UnknownMethodCallGas + } + + return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "oracle" +} + +func (p Precompile) Run(evm *vm.EVM, _ common.Address, _ common.Address, input []byte, value *big.Int, _ bool, _ bool) (bz []byte, err error) { + defer func() { + if err != nil { + evm.StateDB.(*state.DBImpl).SetPrecompileError(err) + } + }() + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, err + } + + switch method.Name { + case GetExchangeRatesMethod: + return p.getExchangeRates(ctx, method, args, value) + case GetOracleTwapsMethod: + return p.getOracleTwaps(ctx, method, args, value) + } + return +} + +func (p Precompile) getExchangeRates(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 0); err != nil { + return nil, err + } + exchangeRates := []DenomOracleExchangeRatePair{} + p.oracleKeeper.IterateBaseExchangeRates(ctx, func(denom string, rate types.OracleExchangeRate) (stop bool) { + exchangeRates = append(exchangeRates, DenomOracleExchangeRatePair{Denom: denom, OracleExchangeRateVal: OracleExchangeRate{ExchangeRate: rate.ExchangeRate.String(), LastUpdate: rate.LastUpdate.String(), LastUpdateTimestamp: rate.LastUpdateTimestamp}}) + return false + }) + + return method.Outputs.Pack(exchangeRates) +} + +func (p Precompile) getOracleTwaps(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + lookbackSeconds := args[0].(uint64) + twaps, err := p.oracleKeeper.CalculateTwaps(ctx, lookbackSeconds) + if err != nil { + return nil, err + } + // Convert twap to string + oracleTwaps := make([]OracleTwap, 0, len(twaps)) + for _, twap := range twaps { + oracleTwaps = append(oracleTwaps, OracleTwap{Denom: twap.Denom, Twap: twap.Twap.String(), LookbackSeconds: twap.LookbackSeconds}) + } + return method.Outputs.Pack(oracleTwaps) +} + +func (Precompile) IsTransaction(string) bool { + return false +} diff --git a/precompiles/oracle/legacy/v562/abi.json b/precompiles/oracle/legacy/v562/abi.json new file mode 100644 index 0000000000..1bb91d96aa --- /dev/null +++ b/precompiles/oracle/legacy/v562/abi.json @@ -0,0 +1 @@ +[{"inputs":[],"name":"getExchangeRates","outputs":[{"components":[{"internalType":"string","name":"denom","type":"string"},{"components":[{"internalType":"string","name":"exchangeRate","type":"string"},{"internalType":"string","name":"lastUpdate","type":"string"},{"internalType":"int64","name":"lastUpdateTimestamp","type":"int64"}],"internalType":"struct IOracle.OracleExchangeRate","name":"oracleExchangeRateVal","type":"tuple"}],"internalType":"struct IOracle.DenomOracleExchangeRatePair[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64","name":"lookback_seconds","type":"uint64"}],"name":"getOracleTwaps","outputs":[{"components":[{"internalType":"string","name":"denom","type":"string"},{"internalType":"string","name":"twap","type":"string"},{"internalType":"int64","name":"lookbackSeconds","type":"int64"}],"internalType":"struct IOracle.OracleTwap[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/oracle/legacy/v562/json.go b/precompiles/oracle/legacy/v562/json.go new file mode 100644 index 0000000000..c499ef00df --- /dev/null +++ b/precompiles/oracle/legacy/v562/json.go @@ -0,0 +1,145 @@ +package v562 + +import ( + "bytes" + "embed" + "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/legacy/v562" + "github.com/sei-protocol/sei-chain/x/oracle/types" +) + +const ( + GetExchangeRatesMethod = "getExchangeRates" + GetOracleTwapsMethod = "getOracleTwaps" +) + +const ( + OracleAddress = "0x0000000000000000000000000000000000001008" +) + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +func GetABI() abi.ABI { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +type PrecompileExecutor struct { + evmKeeper pcommon.EVMKeeper + oracleKeeper pcommon.OracleKeeper + + GetExchangeRatesId []byte + GetOracleTwapsId []byte +} + +// Define types which deviate slightly from cosmos types (ExchangeRate string vs sdk.Dec) +type OracleExchangeRate struct { + ExchangeRate string `json:"exchangeRate"` + LastUpdate string `json:"lastUpdate"` + LastUpdateTimestamp int64 `json:"lastUpdateTimestamp"` +} + +type DenomOracleExchangeRatePair struct { + Denom string `json:"denom"` + OracleExchangeRateVal OracleExchangeRate `json:"oracleExchangeRateVal"` +} + +type OracleTwap struct { + Denom string `json:"denom"` + Twap string `json:"twap"` + LookbackSeconds int64 `json:"lookbackSeconds"` +} + +func NewPrecompile(oracleKeeper pcommon.OracleKeeper, evmKeeper pcommon.EVMKeeper) (*pcommon.Precompile, error) { + newAbi := GetABI() + + p := &PrecompileExecutor{ + evmKeeper: evmKeeper, + oracleKeeper: oracleKeeper, + } + + for name, m := range newAbi.Methods { + switch name { + case GetExchangeRatesMethod: + p.GetExchangeRatesId = m.ID + case GetOracleTwapsMethod: + p.GetOracleTwapsId = m.ID + } + } + + return pcommon.NewPrecompile(newAbi, p, common.HexToAddress(OracleAddress), "oracle"), nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p PrecompileExecutor) RequiredGas(input []byte, method *abi.Method) uint64 { + return pcommon.DefaultGasCost(input, p.IsTransaction(method.Name)) +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM) (bz []byte, err error) { + switch method.Name { + case GetExchangeRatesMethod: + return p.getExchangeRates(ctx, method, args, value) + case GetOracleTwapsMethod: + return p.getOracleTwaps(ctx, method, args, value) + } + return +} + +func (p PrecompileExecutor) getExchangeRates(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 0); err != nil { + return nil, err + } + exchangeRates := []DenomOracleExchangeRatePair{} + p.oracleKeeper.IterateBaseExchangeRates(ctx, func(denom string, rate types.OracleExchangeRate) (stop bool) { + exchangeRates = append(exchangeRates, DenomOracleExchangeRatePair{Denom: denom, OracleExchangeRateVal: OracleExchangeRate{ExchangeRate: rate.ExchangeRate.String(), LastUpdate: rate.LastUpdate.String(), LastUpdateTimestamp: rate.LastUpdateTimestamp}}) + return false + }) + + return method.Outputs.Pack(exchangeRates) +} + +func (p PrecompileExecutor) getOracleTwaps(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + lookbackSeconds := args[0].(uint64) + twaps, err := p.oracleKeeper.CalculateTwaps(ctx, lookbackSeconds) + if err != nil { + return nil, err + } + // Convert twap to string + oracleTwaps := make([]OracleTwap, 0, len(twaps)) + for _, twap := range twaps { + oracleTwaps = append(oracleTwaps, OracleTwap{Denom: twap.Denom, Twap: twap.Twap.String(), LookbackSeconds: twap.LookbackSeconds}) + } + return method.Outputs.Pack(oracleTwaps) +} + +func (PrecompileExecutor) IsTransaction(string) bool { + return false +} diff --git a/precompiles/oracle/legacy/v600/abi.json b/precompiles/oracle/legacy/v600/abi.json new file mode 100644 index 0000000000..1bb91d96aa --- /dev/null +++ b/precompiles/oracle/legacy/v600/abi.json @@ -0,0 +1 @@ +[{"inputs":[],"name":"getExchangeRates","outputs":[{"components":[{"internalType":"string","name":"denom","type":"string"},{"components":[{"internalType":"string","name":"exchangeRate","type":"string"},{"internalType":"string","name":"lastUpdate","type":"string"},{"internalType":"int64","name":"lastUpdateTimestamp","type":"int64"}],"internalType":"struct IOracle.OracleExchangeRate","name":"oracleExchangeRateVal","type":"tuple"}],"internalType":"struct IOracle.DenomOracleExchangeRatePair[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64","name":"lookback_seconds","type":"uint64"}],"name":"getOracleTwaps","outputs":[{"components":[{"internalType":"string","name":"denom","type":"string"},{"internalType":"string","name":"twap","type":"string"},{"internalType":"int64","name":"lookbackSeconds","type":"int64"}],"internalType":"struct IOracle.OracleTwap[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/oracle/legacy/v600/oracle.go b/precompiles/oracle/legacy/v600/oracle.go new file mode 100644 index 0000000000..cb4214e804 --- /dev/null +++ b/precompiles/oracle/legacy/v600/oracle.go @@ -0,0 +1,131 @@ +package v600 + +import ( + "embed" + "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/legacy/v600" + "github.com/sei-protocol/sei-chain/x/oracle/types" +) + +const ( + GetExchangeRatesMethod = "getExchangeRates" + GetOracleTwapsMethod = "getOracleTwaps" +) + +const ( + OracleAddress = "0x0000000000000000000000000000000000001008" +) + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + evmKeeper pcommon.EVMKeeper + oracleKeeper pcommon.OracleKeeper + + GetExchangeRatesId []byte + GetOracleTwapsId []byte +} + +// Define types which deviate slightly from cosmos types (ExchangeRate string vs sdk.Dec) +type OracleExchangeRate struct { + ExchangeRate string `json:"exchangeRate"` + LastUpdate string `json:"lastUpdate"` + LastUpdateTimestamp int64 `json:"lastUpdateTimestamp"` +} + +type DenomOracleExchangeRatePair struct { + Denom string `json:"denom"` + OracleExchangeRateVal OracleExchangeRate `json:"oracleExchangeRateVal"` +} + +type OracleTwap struct { + Denom string `json:"denom"` + Twap string `json:"twap"` + LookbackSeconds int64 `json:"lookbackSeconds"` +} + +func NewPrecompile(oracleKeeper pcommon.OracleKeeper, evmKeeper pcommon.EVMKeeper) (*pcommon.Precompile, error) { + newAbi := pcommon.MustGetABI(f, "abi.json") + + p := &PrecompileExecutor{ + evmKeeper: evmKeeper, + oracleKeeper: oracleKeeper, + } + + for name, m := range newAbi.Methods { + switch name { + case GetExchangeRatesMethod: + p.GetExchangeRatesId = m.ID + case GetOracleTwapsMethod: + p.GetOracleTwapsId = m.ID + } + } + + return pcommon.NewPrecompile(newAbi, p, common.HexToAddress(OracleAddress), "oracle"), nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p PrecompileExecutor) RequiredGas(input []byte, method *abi.Method) uint64 { + return pcommon.DefaultGasCost(input, p.IsTransaction(method.Name)) +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM) (bz []byte, err error) { + switch method.Name { + case GetExchangeRatesMethod: + return p.getExchangeRates(ctx, method, args, value) + case GetOracleTwapsMethod: + return p.getOracleTwaps(ctx, method, args, value) + } + return +} + +func (p PrecompileExecutor) getExchangeRates(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 0); err != nil { + return nil, err + } + exchangeRates := []DenomOracleExchangeRatePair{} + p.oracleKeeper.IterateBaseExchangeRates(ctx, func(denom string, rate types.OracleExchangeRate) (stop bool) { + exchangeRates = append(exchangeRates, DenomOracleExchangeRatePair{Denom: denom, OracleExchangeRateVal: OracleExchangeRate{ExchangeRate: rate.ExchangeRate.String(), LastUpdate: rate.LastUpdate.String(), LastUpdateTimestamp: rate.LastUpdateTimestamp}}) + return false + }) + + return method.Outputs.Pack(exchangeRates) +} + +func (p PrecompileExecutor) getOracleTwaps(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + lookbackSeconds := args[0].(uint64) + twaps, err := p.oracleKeeper.CalculateTwaps(ctx, lookbackSeconds) + if err != nil { + return nil, err + } + // Convert twap to string + oracleTwaps := make([]OracleTwap, 0, len(twaps)) + for _, twap := range twaps { + oracleTwaps = append(oracleTwaps, OracleTwap{Denom: twap.Denom, Twap: twap.Twap.String(), LookbackSeconds: twap.LookbackSeconds}) + } + return method.Outputs.Pack(oracleTwaps) +} + +func (PrecompileExecutor) IsTransaction(string) bool { + return false +} diff --git a/precompiles/oracle/legacy/v602/abi.json b/precompiles/oracle/legacy/v602/abi.json new file mode 100644 index 0000000000..1bb91d96aa --- /dev/null +++ b/precompiles/oracle/legacy/v602/abi.json @@ -0,0 +1 @@ +[{"inputs":[],"name":"getExchangeRates","outputs":[{"components":[{"internalType":"string","name":"denom","type":"string"},{"components":[{"internalType":"string","name":"exchangeRate","type":"string"},{"internalType":"string","name":"lastUpdate","type":"string"},{"internalType":"int64","name":"lastUpdateTimestamp","type":"int64"}],"internalType":"struct IOracle.OracleExchangeRate","name":"oracleExchangeRateVal","type":"tuple"}],"internalType":"struct IOracle.DenomOracleExchangeRatePair[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64","name":"lookback_seconds","type":"uint64"}],"name":"getOracleTwaps","outputs":[{"components":[{"internalType":"string","name":"denom","type":"string"},{"internalType":"string","name":"twap","type":"string"},{"internalType":"int64","name":"lookbackSeconds","type":"int64"}],"internalType":"struct IOracle.OracleTwap[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/oracle/legacy/v602/oracle.go b/precompiles/oracle/legacy/v602/oracle.go new file mode 100644 index 0000000000..37cfa22332 --- /dev/null +++ b/precompiles/oracle/legacy/v602/oracle.go @@ -0,0 +1,137 @@ +package v602 + +import ( + "embed" + "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" + "github.com/sei-protocol/sei-chain/x/oracle/types" +) + +const ( + GetExchangeRatesMethod = "getExchangeRates" + GetOracleTwapsMethod = "getOracleTwaps" +) + +const ( + OracleAddress = "0x0000000000000000000000000000000000001008" +) + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + evmKeeper pcommon.EVMKeeper + oracleKeeper pcommon.OracleKeeper + + GetExchangeRatesId []byte + GetOracleTwapsId []byte +} + +// Define types which deviate slightly from cosmos types (ExchangeRate string vs sdk.Dec) +type OracleExchangeRate struct { + ExchangeRate string `json:"exchangeRate"` + LastUpdate string `json:"lastUpdate"` + LastUpdateTimestamp int64 `json:"lastUpdateTimestamp"` +} + +type DenomOracleExchangeRatePair struct { + Denom string `json:"denom"` + OracleExchangeRateVal OracleExchangeRate `json:"oracleExchangeRateVal"` +} + +type OracleTwap struct { + Denom string `json:"denom"` + Twap string `json:"twap"` + LookbackSeconds int64 `json:"lookbackSeconds"` +} + +func NewPrecompile(oracleKeeper pcommon.OracleKeeper, evmKeeper pcommon.EVMKeeper) (*pcommon.DynamicGasPrecompile, error) { + newAbi := pcommon.MustGetABI(f, "abi.json") + + p := &PrecompileExecutor{ + evmKeeper: evmKeeper, + oracleKeeper: oracleKeeper, + } + + for name, m := range newAbi.Methods { + switch name { + case GetExchangeRatesMethod: + p.GetExchangeRatesId = m.ID + case GetOracleTwapsMethod: + p.GetOracleTwapsId = m.ID + } + } + + return pcommon.NewDynamicGasPrecompile(newAbi, p, common.HexToAddress(OracleAddress), "oracle"), nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p PrecompileExecutor) RequiredGas(input []byte, method *abi.Method) uint64 { + return pcommon.DefaultGasCost(input, p.IsTransaction(method.Name)) +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, suppliedGas uint64) (bz []byte, remainingGas uint64, err error) { + switch method.Name { + case GetExchangeRatesMethod: + return p.getExchangeRates(ctx, method, args, value) + case GetOracleTwapsMethod: + return p.getOracleTwaps(ctx, method, args, value) + } + return +} + +func (p PrecompileExecutor) getExchangeRates(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 0); err != nil { + return nil, 0, err + } + exchangeRates := []DenomOracleExchangeRatePair{} + p.oracleKeeper.IterateBaseExchangeRates(ctx, func(denom string, rate types.OracleExchangeRate) (stop bool) { + exchangeRates = append(exchangeRates, DenomOracleExchangeRatePair{Denom: denom, OracleExchangeRateVal: OracleExchangeRate{ExchangeRate: rate.ExchangeRate.String(), LastUpdate: rate.LastUpdate.String(), LastUpdateTimestamp: rate.LastUpdateTimestamp}}) + return false + }) + + bz, err := method.Outputs.Pack(exchangeRates) + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), err +} + +func (p PrecompileExecutor) getOracleTwaps(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + lookbackSeconds := args[0].(uint64) + twaps, err := p.oracleKeeper.CalculateTwaps(ctx, lookbackSeconds) + if err != nil { + return nil, 0, err + } + // Convert twap to string + oracleTwaps := make([]OracleTwap, 0, len(twaps)) + for _, twap := range twaps { + oracleTwaps = append(oracleTwaps, OracleTwap{Denom: twap.Denom, Twap: twap.Twap.String(), LookbackSeconds: twap.LookbackSeconds}) + } + bz, err := method.Outputs.Pack(oracleTwaps) + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), err +} + +func (p PrecompileExecutor) EVMKeeper() pcommon.EVMKeeper { + return p.evmKeeper +} + +func (PrecompileExecutor) IsTransaction(string) bool { + return false +} diff --git a/precompiles/oracle/legacy/v603/abi.json b/precompiles/oracle/legacy/v603/abi.json new file mode 100644 index 0000000000..1bb91d96aa --- /dev/null +++ b/precompiles/oracle/legacy/v603/abi.json @@ -0,0 +1 @@ +[{"inputs":[],"name":"getExchangeRates","outputs":[{"components":[{"internalType":"string","name":"denom","type":"string"},{"components":[{"internalType":"string","name":"exchangeRate","type":"string"},{"internalType":"string","name":"lastUpdate","type":"string"},{"internalType":"int64","name":"lastUpdateTimestamp","type":"int64"}],"internalType":"struct IOracle.OracleExchangeRate","name":"oracleExchangeRateVal","type":"tuple"}],"internalType":"struct IOracle.DenomOracleExchangeRatePair[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64","name":"lookback_seconds","type":"uint64"}],"name":"getOracleTwaps","outputs":[{"components":[{"internalType":"string","name":"denom","type":"string"},{"internalType":"string","name":"twap","type":"string"},{"internalType":"int64","name":"lookbackSeconds","type":"int64"}],"internalType":"struct IOracle.OracleTwap[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/oracle/legacy/v603/oracle.go b/precompiles/oracle/legacy/v603/oracle.go new file mode 100644 index 0000000000..fa07ad1b42 --- /dev/null +++ b/precompiles/oracle/legacy/v603/oracle.go @@ -0,0 +1,144 @@ +package v603 + +import ( + "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" + "github.com/sei-protocol/sei-chain/x/oracle/types" +) + +const ( + GetExchangeRatesMethod = "getExchangeRates" + GetOracleTwapsMethod = "getOracleTwaps" +) + +const ( + OracleAddress = "0x0000000000000000000000000000000000001008" +) + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + evmKeeper pcommon.EVMKeeper + oracleKeeper pcommon.OracleKeeper + + GetExchangeRatesId []byte + GetOracleTwapsId []byte +} + +// Define types which deviate slightly from cosmos types (ExchangeRate string vs sdk.Dec) +type OracleExchangeRate struct { + ExchangeRate string `json:"exchangeRate"` + LastUpdate string `json:"lastUpdate"` + LastUpdateTimestamp int64 `json:"lastUpdateTimestamp"` +} + +type DenomOracleExchangeRatePair struct { + Denom string `json:"denom"` + OracleExchangeRateVal OracleExchangeRate `json:"oracleExchangeRateVal"` +} + +type OracleTwap struct { + Denom string `json:"denom"` + Twap string `json:"twap"` + LookbackSeconds int64 `json:"lookbackSeconds"` +} + +func NewPrecompile(oracleKeeper pcommon.OracleKeeper, evmKeeper pcommon.EVMKeeper) (*pcommon.DynamicGasPrecompile, error) { + newAbi := pcommon.MustGetABI(f, "abi.json") + + p := &PrecompileExecutor{ + evmKeeper: evmKeeper, + oracleKeeper: oracleKeeper, + } + + for name, m := range newAbi.Methods { + switch name { + case GetExchangeRatesMethod: + p.GetExchangeRatesId = m.ID + case GetOracleTwapsMethod: + p.GetOracleTwapsId = m.ID + } + } + + return pcommon.NewDynamicGasPrecompile(newAbi, p, common.HexToAddress(OracleAddress), "oracle"), nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p PrecompileExecutor) RequiredGas(input []byte, method *abi.Method) uint64 { + return pcommon.DefaultGasCost(input, p.IsTransaction(method.Name)) +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, suppliedGas uint64) (bz []byte, remainingGas uint64, err error) { + // Needed to catch gas meter panics + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("execution reverted: %v", r) + } + }() + switch method.Name { + case GetExchangeRatesMethod: + return p.getExchangeRates(ctx, method, args, value) + case GetOracleTwapsMethod: + return p.getOracleTwaps(ctx, method, args, value) + } + return +} + +func (p PrecompileExecutor) getExchangeRates(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 0); err != nil { + return nil, 0, err + } + exchangeRates := []DenomOracleExchangeRatePair{} + p.oracleKeeper.IterateBaseExchangeRates(ctx, func(denom string, rate types.OracleExchangeRate) (stop bool) { + exchangeRates = append(exchangeRates, DenomOracleExchangeRatePair{Denom: denom, OracleExchangeRateVal: OracleExchangeRate{ExchangeRate: rate.ExchangeRate.String(), LastUpdate: rate.LastUpdate.String(), LastUpdateTimestamp: rate.LastUpdateTimestamp}}) + return false + }) + + bz, err := method.Outputs.Pack(exchangeRates) + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), err +} + +func (p PrecompileExecutor) getOracleTwaps(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, uint64, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + lookbackSeconds := args[0].(uint64) + twaps, err := p.oracleKeeper.CalculateTwaps(ctx, lookbackSeconds) + if err != nil { + return nil, 0, err + } + // Convert twap to string + oracleTwaps := make([]OracleTwap, 0, len(twaps)) + for _, twap := range twaps { + oracleTwaps = append(oracleTwaps, OracleTwap{Denom: twap.Denom, Twap: twap.Twap.String(), LookbackSeconds: twap.LookbackSeconds}) + } + bz, err := method.Outputs.Pack(oracleTwaps) + return bz, pcommon.GetRemainingGas(ctx, p.evmKeeper), err +} + +func (p PrecompileExecutor) EVMKeeper() pcommon.EVMKeeper { + return p.evmKeeper +} + +func (PrecompileExecutor) IsTransaction(string) bool { + return false +} diff --git a/precompiles/pointer/legacy/v520/abi.json b/precompiles/pointer/legacy/v520/abi.json new file mode 100644 index 0000000000..8fb8238da0 --- /dev/null +++ b/precompiles/pointer/legacy/v520/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/legacy/v520/pointer.go b/precompiles/pointer/legacy/v520/pointer.go new file mode 100644 index 0000000000..21646f426f --- /dev/null +++ b/precompiles/pointer/legacy/v520/pointer.go @@ -0,0 +1,283 @@ +package v520 + +import ( + "bytes" + "embed" + "encoding/json" + "errors" + "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/tracing" + "github.com/ethereum/go-ethereum/core/vm" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v520" + "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" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const ( + AddNativePointer = "addNativePointer" + AddCW20Pointer = "addCW20Pointer" + AddCW721Pointer = "addCW721Pointer" +) + +const PointerAddress = "0x000000000000000000000000000000000000100b" + +var _ vm.PrecompiledContract = &Precompile{} +var _ vm.DynamicGasPrecompiledContract = &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 pcommon.UnknownMethodCallGas +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "pointer" +} + +func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { + if readOnly { + return nil, 0, errors.New("cannot call pointer precompile from staticcall") + } + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, 0, err + } + if caller.Cmp(callingContract) != 0 { + return nil, 0, errors.New("cannot delegatecall pointer") + } + + 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, common.Address, []byte, *big.Int, bool, bool) ([]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) { + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + 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 + } + ctx.EventManager().EmitEvent(sdk.NewEvent( + types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "native"), + sdk.NewAttribute(types.AttributeKeyPointerAddress, contractAddr.Hex()), sdk.NewAttribute(types.AttributeKeyPointee, token), + sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", native.CurrentVersion)))) + 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) { + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + cwAddr := args[0].(string) + existingAddr, existingVersion, exists := p.evmKeeper.GetERC20CW20Pointer(ctx, cwAddr) + if exists { + 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(ctx)) + } + cwAddress, err := sdk.AccAddressFromBech32(cwAddr) + if err != nil { + return nil, 0, err + } + res, err := p.wasmdKeeper.QuerySmart(ctx, cwAddress, []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 + } + ctx.EventManager().EmitEvent(sdk.NewEvent( + types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "cw20"), + sdk.NewAttribute(types.AttributeKeyPointerAddress, contractAddr.Hex()), sdk.NewAttribute(types.AttributeKeyPointee, cwAddr), + sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", cw20.CurrentVersion(ctx))))) + 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) { + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + 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) + } + cwAddress, err := sdk.AccAddressFromBech32(cwAddr) + if err != nil { + return nil, 0, err + } + res, err := p.wasmdKeeper.QuerySmart(ctx, cwAddress, []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 + } + ctx.EventManager().EmitEvent(sdk.NewEvent( + types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "cw721"), + sdk.NewAttribute(types.AttributeKeyPointerAddress, contractAddr.Hex()), sdk.NewAttribute(types.AttributeKeyPointee, cwAddr), + sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", cw721.CurrentVersion)))) + ret, err = method.Outputs.Pack(contractAddr) + return +} diff --git a/precompiles/pointer/legacy/v522/abi.json b/precompiles/pointer/legacy/v522/abi.json new file mode 100644 index 0000000000..8fb8238da0 --- /dev/null +++ b/precompiles/pointer/legacy/v522/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/legacy/v522/pointer.go b/precompiles/pointer/legacy/v522/pointer.go new file mode 100644 index 0000000000..dd9d41093d --- /dev/null +++ b/precompiles/pointer/legacy/v522/pointer.go @@ -0,0 +1,289 @@ +package v522 + +import ( + "bytes" + "embed" + "encoding/json" + "errors" + "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/tracing" + "github.com/ethereum/go-ethereum/core/vm" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v530" + "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" + "github.com/sei-protocol/sei-chain/x/evm/state" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const ( + AddNativePointer = "addNativePointer" + AddCW20Pointer = "addCW20Pointer" + AddCW721Pointer = "addCW721Pointer" +) + +const PointerAddress = "0x000000000000000000000000000000000000100b" + +var _ vm.PrecompiledContract = &Precompile{} +var _ vm.DynamicGasPrecompiledContract = &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 pcommon.UnknownMethodCallGas +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "pointer" +} + +func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { + defer func() { + if err != nil { + evm.StateDB.(*state.DBImpl).SetPrecompileError(err) + } + }() + if readOnly { + return nil, 0, errors.New("cannot call pointer precompile from staticcall") + } + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, 0, err + } + if caller.Cmp(callingContract) != 0 { + return nil, 0, errors.New("cannot delegatecall pointer") + } + + 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, common.Address, []byte, *big.Int, bool, bool) ([]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) { + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + 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 + } + ctx.EventManager().EmitEvent(sdk.NewEvent( + types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "native"), + sdk.NewAttribute(types.AttributeKeyPointerAddress, contractAddr.Hex()), sdk.NewAttribute(types.AttributeKeyPointee, token), + sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", native.CurrentVersion)))) + 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) { + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + cwAddr := args[0].(string) + existingAddr, existingVersion, exists := p.evmKeeper.GetERC20CW20Pointer(ctx, cwAddr) + if exists { + 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(ctx)) + } + cwAddress, err := sdk.AccAddressFromBech32(cwAddr) + if err != nil { + return nil, 0, err + } + res, err := p.wasmdKeeper.QuerySmart(ctx, cwAddress, []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 + } + ctx.EventManager().EmitEvent(sdk.NewEvent( + types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "cw20"), + sdk.NewAttribute(types.AttributeKeyPointerAddress, contractAddr.Hex()), sdk.NewAttribute(types.AttributeKeyPointee, cwAddr), + sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", cw20.CurrentVersion(ctx))))) + 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) { + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + 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) + } + cwAddress, err := sdk.AccAddressFromBech32(cwAddr) + if err != nil { + return nil, 0, err + } + res, err := p.wasmdKeeper.QuerySmart(ctx, cwAddress, []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 + } + ctx.EventManager().EmitEvent(sdk.NewEvent( + types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "cw721"), + sdk.NewAttribute(types.AttributeKeyPointerAddress, contractAddr.Hex()), sdk.NewAttribute(types.AttributeKeyPointee, cwAddr), + sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", cw721.CurrentVersion)))) + ret, err = method.Outputs.Pack(contractAddr) + return +} diff --git a/precompiles/pointer/legacy/v530/abi.json b/precompiles/pointer/legacy/v530/abi.json new file mode 100644 index 0000000000..8fb8238da0 --- /dev/null +++ b/precompiles/pointer/legacy/v530/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/legacy/v530/pointer.go b/precompiles/pointer/legacy/v530/pointer.go new file mode 100644 index 0000000000..c0aa93e979 --- /dev/null +++ b/precompiles/pointer/legacy/v530/pointer.go @@ -0,0 +1,299 @@ +package v530 + +import ( + "bytes" + "embed" + "encoding/json" + "errors" + "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/tracing" + "github.com/ethereum/go-ethereum/core/vm" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v530" + "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" + "github.com/sei-protocol/sei-chain/x/evm/state" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const ( + PrecompileName = "pointer" + AddNativePointer = "addNativePointer" + AddCW20Pointer = "addCW20Pointer" + AddCW721Pointer = "addCW721Pointer" +) + +const PointerAddress = "0x000000000000000000000000000000000000100b" + +var _ vm.PrecompiledContract = &Precompile{} +var _ vm.DynamicGasPrecompiledContract = &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 pcommon.UnknownMethodCallGas +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return PrecompileName +} + +func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { + defer func() { + if err != nil { + evm.StateDB.(*state.DBImpl).SetPrecompileError(err) + } + }() + if readOnly { + return nil, 0, errors.New("cannot call pointer precompile from staticcall") + } + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, 0, err + } + if caller.Cmp(callingContract) != 0 { + return nil, 0, errors.New("cannot delegatecall pointer") + } + + 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, common.Address, []byte, *big.Int, bool, bool) ([]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) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + 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 + } + ctx.EventManager().EmitEvent(sdk.NewEvent( + types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "native"), + sdk.NewAttribute(types.AttributeKeyPointerAddress, contractAddr.Hex()), sdk.NewAttribute(types.AttributeKeyPointee, token), + sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", native.CurrentVersion)))) + 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) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + cwAddr := args[0].(string) + existingAddr, existingVersion, exists := p.evmKeeper.GetERC20CW20Pointer(ctx, cwAddr) + if exists { + 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(ctx)) + } + cwAddress, err := sdk.AccAddressFromBech32(cwAddr) + if err != nil { + return nil, 0, err + } + res, err := p.wasmdKeeper.QuerySmart(ctx, cwAddress, []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 + } + ctx.EventManager().EmitEvent(sdk.NewEvent( + types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "cw20"), + sdk.NewAttribute(types.AttributeKeyPointerAddress, contractAddr.Hex()), sdk.NewAttribute(types.AttributeKeyPointee, cwAddr), + sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", cw20.CurrentVersion(ctx))))) + 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) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + 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) + } + cwAddress, err := sdk.AccAddressFromBech32(cwAddr) + if err != nil { + return nil, 0, err + } + res, err := p.wasmdKeeper.QuerySmart(ctx, cwAddress, []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 + } + ctx.EventManager().EmitEvent(sdk.NewEvent( + types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "cw721"), + sdk.NewAttribute(types.AttributeKeyPointerAddress, contractAddr.Hex()), sdk.NewAttribute(types.AttributeKeyPointee, cwAddr), + sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", cw721.CurrentVersion)))) + ret, err = method.Outputs.Pack(contractAddr) + return +} diff --git a/precompiles/pointer/legacy/v555/abi.json b/precompiles/pointer/legacy/v555/abi.json new file mode 100644 index 0000000000..8fb8238da0 --- /dev/null +++ b/precompiles/pointer/legacy/v555/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/legacy/v555/pointer.go b/precompiles/pointer/legacy/v555/pointer.go new file mode 100644 index 0000000000..48dfac19d4 --- /dev/null +++ b/precompiles/pointer/legacy/v555/pointer.go @@ -0,0 +1,307 @@ +package v555 + +import ( + "bytes" + "embed" + "encoding/json" + "errors" + "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/tracing" + "github.com/ethereum/go-ethereum/core/vm" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v555" + "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" + "github.com/sei-protocol/sei-chain/x/evm/state" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const ( + PrecompileName = "pointer" + AddNativePointer = "addNativePointer" + AddCW20Pointer = "addCW20Pointer" + AddCW721Pointer = "addCW721Pointer" +) + +const PointerAddress = "0x000000000000000000000000000000000000100b" + +var _ vm.PrecompiledContract = &Precompile{} +var _ vm.DynamicGasPrecompiledContract = &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 ABI() (*ethabi.ABI, 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 + } + return &newAbi, nil +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper, wasmdKeeper pcommon.WasmdViewKeeper) (*Precompile, error) { + newAbi, err := ABI() + 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 pcommon.UnknownMethodCallGas +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return PrecompileName +} + +func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { + defer func() { + if err != nil { + evm.StateDB.(*state.DBImpl).SetPrecompileError(err) + } + }() + if readOnly { + return nil, 0, errors.New("cannot call pointer precompile from staticcall") + } + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, 0, err + } + if caller.Cmp(callingContract) != 0 { + return nil, 0, errors.New("cannot delegatecall pointer") + } + + 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, common.Address, []byte, *big.Int, bool, bool) ([]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) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + 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 + } + ctx.EventManager().EmitEvent(sdk.NewEvent( + types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "native"), + sdk.NewAttribute(types.AttributeKeyPointerAddress, contractAddr.Hex()), sdk.NewAttribute(types.AttributeKeyPointee, token), + sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", native.CurrentVersion)))) + 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) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + cwAddr := args[0].(string) + existingAddr, existingVersion, exists := p.evmKeeper.GetERC20CW20Pointer(ctx, cwAddr) + if exists && existingVersion >= cw20.CurrentVersion(ctx) { + 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(ctx)) + } + cwAddress, err := sdk.AccAddressFromBech32(cwAddr) + if err != nil { + return nil, 0, err + } + res, err := p.wasmdKeeper.QuerySmart(ctx, cwAddress, []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 + } + ctx.EventManager().EmitEvent(sdk.NewEvent( + types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "cw20"), + sdk.NewAttribute(types.AttributeKeyPointerAddress, contractAddr.Hex()), sdk.NewAttribute(types.AttributeKeyPointee, cwAddr), + sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", cw20.CurrentVersion(ctx))))) + 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) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + 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) + } + cwAddress, err := sdk.AccAddressFromBech32(cwAddr) + if err != nil { + return nil, 0, err + } + res, err := p.wasmdKeeper.QuerySmart(ctx, cwAddress, []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 + } + ctx.EventManager().EmitEvent(sdk.NewEvent( + types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "cw721"), + sdk.NewAttribute(types.AttributeKeyPointerAddress, contractAddr.Hex()), sdk.NewAttribute(types.AttributeKeyPointee, cwAddr), + sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", cw721.CurrentVersion)))) + ret, err = method.Outputs.Pack(contractAddr) + return +} diff --git a/precompiles/pointer/legacy/v562/abi.json b/precompiles/pointer/legacy/v562/abi.json new file mode 100644 index 0000000000..8fb8238da0 --- /dev/null +++ b/precompiles/pointer/legacy/v562/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/legacy/v562/pointer.go b/precompiles/pointer/legacy/v562/pointer.go new file mode 100644 index 0000000000..9d45cb899c --- /dev/null +++ b/precompiles/pointer/legacy/v562/pointer.go @@ -0,0 +1,275 @@ +package v562 + +import ( + "bytes" + "embed" + "encoding/json" + "errors" + "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/legacy/v562" + "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" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const ( + PrecompileName = "pointer" + AddNativePointer = "addNativePointer" + AddCW20Pointer = "addCW20Pointer" + AddCW721Pointer = "addCW721Pointer" +) + +const PointerAddress = "0x000000000000000000000000000000000000100b" + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + wasmdKeeper pcommon.WasmdViewKeeper + + AddNativePointerID []byte + AddCW20PointerID []byte + AddCW721PointerID []byte +} + +func ABI() (*ethabi.ABI, 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 + } + return &newAbi, nil +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper, wasmdKeeper pcommon.WasmdViewKeeper) (*pcommon.DynamicGasPrecompile, error) { + newAbi, err := ABI() + if err != nil { + return nil, err + } + + p := &PrecompileExecutor{ + evmKeeper: evmKeeper, + bankKeeper: bankKeeper, + wasmdKeeper: wasmdKeeper, + } + + 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 pcommon.NewDynamicGasPrecompile(*newAbi, p, common.HexToAddress(PointerAddress), PrecompileName), nil +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *ethabi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + if readOnly { + return nil, 0, errors.New("cannot call pointer precompile from staticcall") + } + if caller.Cmp(callingContract) != 0 { + return nil, 0, errors.New("cannot delegatecall pointer") + } + + 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 PrecompileExecutor) EVMKeeper() pcommon.EVMKeeper { + return p.evmKeeper +} + +func (p PrecompileExecutor) 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) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + 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 + } + ctx.EventManager().EmitEvent(sdk.NewEvent( + types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "native"), + sdk.NewAttribute(types.AttributeKeyPointerAddress, contractAddr.Hex()), sdk.NewAttribute(types.AttributeKeyPointee, token), + sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", native.CurrentVersion)))) + ret, err = method.Outputs.Pack(contractAddr) + return +} + +func (p PrecompileExecutor) 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) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + cwAddr := args[0].(string) + existingAddr, existingVersion, exists := p.evmKeeper.GetERC20CW20Pointer(ctx, cwAddr) + if exists && existingVersion >= cw20.CurrentVersion(ctx) { + 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(ctx)) + } + cwAddress, err := sdk.AccAddressFromBech32(cwAddr) + if err != nil { + return nil, 0, err + } + res, err := p.wasmdKeeper.QuerySmart(ctx, cwAddress, []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 + } + ctx.EventManager().EmitEvent(sdk.NewEvent( + types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "cw20"), + sdk.NewAttribute(types.AttributeKeyPointerAddress, contractAddr.Hex()), sdk.NewAttribute(types.AttributeKeyPointee, cwAddr), + sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", cw20.CurrentVersion(ctx))))) + ret, err = method.Outputs.Pack(contractAddr) + return +} + +func (p PrecompileExecutor) 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) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + 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) + } + cwAddress, err := sdk.AccAddressFromBech32(cwAddr) + if err != nil { + return nil, 0, err + } + res, err := p.wasmdKeeper.QuerySmart(ctx, cwAddress, []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 + } + ctx.EventManager().EmitEvent(sdk.NewEvent( + types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "cw721"), + sdk.NewAttribute(types.AttributeKeyPointerAddress, contractAddr.Hex()), sdk.NewAttribute(types.AttributeKeyPointee, cwAddr), + sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", cw721.CurrentVersion)))) + ret, err = method.Outputs.Pack(contractAddr) + return +} diff --git a/precompiles/pointer/legacy/v575/abi.json b/precompiles/pointer/legacy/v575/abi.json new file mode 100644 index 0000000000..8fb8238da0 --- /dev/null +++ b/precompiles/pointer/legacy/v575/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/legacy/v575/pointer.go b/precompiles/pointer/legacy/v575/pointer.go new file mode 100644 index 0000000000..64fb54c066 --- /dev/null +++ b/precompiles/pointer/legacy/v575/pointer.go @@ -0,0 +1,182 @@ +package v575 + +import ( + "embed" + "encoding/json" + "errors" + "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/legacy/v575" + "github.com/sei-protocol/sei-chain/utils" +) + +const ( + PrecompileName = "pointer" + AddNativePointer = "addNativePointer" + AddCW20Pointer = "addCW20Pointer" + AddCW721Pointer = "addCW721Pointer" +) + +const PointerAddress = "0x000000000000000000000000000000000000100b" + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + wasmdKeeper pcommon.WasmdViewKeeper + + AddNativePointerID []byte + AddCW20PointerID []byte + AddCW721PointerID []byte +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper, wasmdKeeper pcommon.WasmdViewKeeper) (*pcommon.DynamicGasPrecompile, error) { + newAbi := pcommon.MustGetABI(f, "abi.json") + + p := &PrecompileExecutor{ + evmKeeper: evmKeeper, + bankKeeper: bankKeeper, + wasmdKeeper: wasmdKeeper, + } + + 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 pcommon.NewDynamicGasPrecompile(newAbi, p, common.HexToAddress(PointerAddress), PrecompileName), nil +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *ethabi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + if readOnly { + return nil, 0, errors.New("cannot call pointer precompile from staticcall") + } + if caller.Cmp(callingContract) != 0 { + return nil, 0, errors.New("cannot delegatecall pointer") + } + + 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 PrecompileExecutor) EVMKeeper() pcommon.EVMKeeper { + return p.evmKeeper +} + +func (p PrecompileExecutor) 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) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + token := args[0].(string) + 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] + } + } + } + contractAddr, remainingGas, err := p.evmKeeper.UpsertERCNativePointer(ctx, evm, suppliedGas, token, utils.ERCMetadata{Name: name, Symbol: symbol, Decimals: decimals}) + if err != nil { + return nil, 0, err + } + ret, err = method.Outputs.Pack(contractAddr) + return +} + +func (p PrecompileExecutor) 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) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + cwAddr := args[0].(string) + cwAddress, err := sdk.AccAddressFromBech32(cwAddr) + if err != nil { + return nil, 0, err + } + res, err := p.wasmdKeeper.QuerySmart(ctx, cwAddress, []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) + contractAddr, remainingGas, err := p.evmKeeper.UpsertERCCW20Pointer(ctx, evm, suppliedGas, cwAddr, utils.ERCMetadata{Name: name, Symbol: symbol}) + if err != nil { + return nil, 0, err + } + ret, err = method.Outputs.Pack(contractAddr) + return +} + +func (p PrecompileExecutor) 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) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + cwAddr := args[0].(string) + cwAddress, err := sdk.AccAddressFromBech32(cwAddr) + if err != nil { + return nil, 0, err + } + res, err := p.wasmdKeeper.QuerySmart(ctx, cwAddress, []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) + contractAddr, remainingGas, err := p.evmKeeper.UpsertERCCW721Pointer(ctx, evm, suppliedGas, cwAddr, utils.ERCMetadata{Name: name, Symbol: symbol}) + if err != nil { + return nil, 0, err + } + ret, err = method.Outputs.Pack(contractAddr) + return +} diff --git a/precompiles/pointer/legacy/v580/abi.json b/precompiles/pointer/legacy/v580/abi.json new file mode 100644 index 0000000000..8fb8238da0 --- /dev/null +++ b/precompiles/pointer/legacy/v580/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/legacy/v580/pointer.go b/precompiles/pointer/legacy/v580/pointer.go new file mode 100644 index 0000000000..24c6f76001 --- /dev/null +++ b/precompiles/pointer/legacy/v580/pointer.go @@ -0,0 +1,185 @@ +package v580 + +import ( + "embed" + "encoding/json" + "errors" + "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/legacy/v580" + "github.com/sei-protocol/sei-chain/utils" +) + +const ( + PrecompileName = "pointer" + AddNativePointer = "addNativePointer" + AddCW20Pointer = "addCW20Pointer" + AddCW721Pointer = "addCW721Pointer" +) + +const PointerAddress = "0x000000000000000000000000000000000000100b" + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + wasmdKeeper pcommon.WasmdViewKeeper + + AddNativePointerID []byte + AddCW20PointerID []byte + AddCW721PointerID []byte +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper, wasmdKeeper pcommon.WasmdViewKeeper) (*pcommon.DynamicGasPrecompile, error) { + newAbi := pcommon.MustGetABI(f, "abi.json") + + p := &PrecompileExecutor{ + evmKeeper: evmKeeper, + bankKeeper: bankKeeper, + wasmdKeeper: wasmdKeeper, + } + + 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 pcommon.NewDynamicGasPrecompile(newAbi, p, common.HexToAddress(PointerAddress), PrecompileName), nil +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *ethabi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + if readOnly { + return nil, 0, errors.New("cannot call pointer precompile from staticcall") + } + if caller.Cmp(callingContract) != 0 { + return nil, 0, errors.New("cannot delegatecall pointer") + } + + switch method.Name { + case AddNativePointer: + return p.AddNative(ctx, method, caller, args, value, evm) + case AddCW20Pointer: + return p.AddCW20(ctx, method, caller, args, value, evm) + case AddCW721Pointer: + return p.AddCW721(ctx, method, caller, args, value, evm) + default: + err = fmt.Errorf("unknown method %s", method.Name) + } + return +} + +func (p PrecompileExecutor) EVMKeeper() pcommon.EVMKeeper { + return p.evmKeeper +} + +func (p PrecompileExecutor) AddNative(ctx sdk.Context, method *ethabi.Method, caller common.Address, args []interface{}, value *big.Int, evm *vm.EVM) (ret []byte, remainingGas uint64, err error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + token := args[0].(string) + 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] + } + } + } + contractAddr, err := p.evmKeeper.UpsertERCNativePointer(ctx, evm, token, utils.ERCMetadata{Name: name, Symbol: symbol, Decimals: decimals}) + if err != nil { + return nil, 0, err + } + ret, err = method.Outputs.Pack(contractAddr) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p PrecompileExecutor) AddCW20(ctx sdk.Context, method *ethabi.Method, caller common.Address, args []interface{}, value *big.Int, evm *vm.EVM) (ret []byte, remainingGas uint64, err error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + cwAddr := args[0].(string) + cwAddress, err := sdk.AccAddressFromBech32(cwAddr) + if err != nil { + return nil, 0, err + } + res, err := p.wasmdKeeper.QuerySmart(ctx, cwAddress, []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) + contractAddr, err := p.evmKeeper.UpsertERCCW20Pointer(ctx, evm, cwAddr, utils.ERCMetadata{Name: name, Symbol: symbol}) + if err != nil { + return nil, 0, err + } + ret, err = method.Outputs.Pack(contractAddr) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p PrecompileExecutor) AddCW721(ctx sdk.Context, method *ethabi.Method, caller common.Address, args []interface{}, value *big.Int, evm *vm.EVM) (ret []byte, remainingGas uint64, err error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + cwAddr := args[0].(string) + cwAddress, err := sdk.AccAddressFromBech32(cwAddr) + if err != nil { + return nil, 0, err + } + res, err := p.wasmdKeeper.QuerySmart(ctx, cwAddress, []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) + contractAddr, err := p.evmKeeper.UpsertERCCW721Pointer(ctx, evm, cwAddr, utils.ERCMetadata{Name: name, Symbol: symbol}) + if err != nil { + return nil, 0, err + } + ret, err = method.Outputs.Pack(contractAddr) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} diff --git a/precompiles/pointer/legacy/v600/abi.json b/precompiles/pointer/legacy/v600/abi.json new file mode 100644 index 0000000000..8fb8238da0 --- /dev/null +++ b/precompiles/pointer/legacy/v600/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/legacy/v600/pointer.go b/precompiles/pointer/legacy/v600/pointer.go new file mode 100644 index 0000000000..1fbf379f39 --- /dev/null +++ b/precompiles/pointer/legacy/v600/pointer.go @@ -0,0 +1,185 @@ +package v600 + +import ( + "embed" + "encoding/json" + "errors" + "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/legacy/v600" + "github.com/sei-protocol/sei-chain/utils" +) + +const ( + PrecompileName = "pointer" + AddNativePointer = "addNativePointer" + AddCW20Pointer = "addCW20Pointer" + AddCW721Pointer = "addCW721Pointer" +) + +const PointerAddress = "0x000000000000000000000000000000000000100b" + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + wasmdKeeper pcommon.WasmdViewKeeper + + AddNativePointerID []byte + AddCW20PointerID []byte + AddCW721PointerID []byte +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper, wasmdKeeper pcommon.WasmdViewKeeper) (*pcommon.DynamicGasPrecompile, error) { + newAbi := pcommon.MustGetABI(f, "abi.json") + + p := &PrecompileExecutor{ + evmKeeper: evmKeeper, + bankKeeper: bankKeeper, + wasmdKeeper: wasmdKeeper, + } + + 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 pcommon.NewDynamicGasPrecompile(newAbi, p, common.HexToAddress(PointerAddress), PrecompileName), nil +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *ethabi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + if readOnly { + return nil, 0, errors.New("cannot call pointer precompile from staticcall") + } + if ctx.EVMPrecompileCalledFromDelegateCall() { + return nil, 0, errors.New("cannot delegatecall pointer") + } + + switch method.Name { + case AddNativePointer: + return p.AddNative(ctx, method, caller, args, value, evm) + case AddCW20Pointer: + return p.AddCW20(ctx, method, caller, args, value, evm) + case AddCW721Pointer: + return p.AddCW721(ctx, method, caller, args, value, evm) + default: + err = fmt.Errorf("unknown method %s", method.Name) + } + return +} + +func (p PrecompileExecutor) EVMKeeper() pcommon.EVMKeeper { + return p.evmKeeper +} + +func (p PrecompileExecutor) AddNative(ctx sdk.Context, method *ethabi.Method, caller common.Address, args []interface{}, value *big.Int, evm *vm.EVM) (ret []byte, remainingGas uint64, err error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + token := args[0].(string) + 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] + } + } + } + contractAddr, err := p.evmKeeper.UpsertERCNativePointer(ctx, evm, token, utils.ERCMetadata{Name: name, Symbol: symbol, Decimals: decimals}) + if err != nil { + return nil, 0, err + } + ret, err = method.Outputs.Pack(contractAddr) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p PrecompileExecutor) AddCW20(ctx sdk.Context, method *ethabi.Method, caller common.Address, args []interface{}, value *big.Int, evm *vm.EVM) (ret []byte, remainingGas uint64, err error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + cwAddr := args[0].(string) + cwAddress, err := sdk.AccAddressFromBech32(cwAddr) + if err != nil { + return nil, 0, err + } + res, err := p.wasmdKeeper.QuerySmart(ctx, cwAddress, []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) + contractAddr, err := p.evmKeeper.UpsertERCCW20Pointer(ctx, evm, cwAddr, utils.ERCMetadata{Name: name, Symbol: symbol}) + if err != nil { + return nil, 0, err + } + ret, err = method.Outputs.Pack(contractAddr) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p PrecompileExecutor) AddCW721(ctx sdk.Context, method *ethabi.Method, caller common.Address, args []interface{}, value *big.Int, evm *vm.EVM) (ret []byte, remainingGas uint64, err error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, 0, err + } + cwAddr := args[0].(string) + cwAddress, err := sdk.AccAddressFromBech32(cwAddr) + if err != nil { + return nil, 0, err + } + res, err := p.wasmdKeeper.QuerySmart(ctx, cwAddress, []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) + contractAddr, err := p.evmKeeper.UpsertERCCW721Pointer(ctx, evm, cwAddr, utils.ERCMetadata{Name: name, Symbol: symbol}) + if err != nil { + return nil, 0, err + } + ret, err = method.Outputs.Pack(contractAddr) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} diff --git a/precompiles/pointerview/legacy/v520/abi.json b/precompiles/pointerview/legacy/v520/abi.json new file mode 100644 index 0000000000..a469eff127 --- /dev/null +++ b/precompiles/pointerview/legacy/v520/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/legacy/v520/pointerview.go b/precompiles/pointerview/legacy/v520/pointerview.go new file mode 100644 index 0000000000..f7e20fb2f5 --- /dev/null +++ b/precompiles/pointerview/legacy/v520/pointerview.go @@ -0,0 +1,129 @@ +package v520 + +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/legacy/v520" +) + +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) GetName() string { + return "pointerview" +} + +func (p Precompile) Run(evm *vm.EVM, _ common.Address, _ common.Address, input []byte, _ *big.Int, _ bool, _ bool) (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) { + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + 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) { + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + 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) { + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + addr := args[0].(string) + existingAddr, existingVersion, exists := p.evmKeeper.GetERC721CW721Pointer(ctx, addr) + return method.Outputs.Pack(existingAddr, existingVersion, exists) +} diff --git a/precompiles/pointerview/legacy/v555/abi.json b/precompiles/pointerview/legacy/v555/abi.json new file mode 100644 index 0000000000..a469eff127 --- /dev/null +++ b/precompiles/pointerview/legacy/v555/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/legacy/v555/pointerview.go b/precompiles/pointerview/legacy/v555/pointerview.go new file mode 100644 index 0000000000..e9157d6cd0 --- /dev/null +++ b/precompiles/pointerview/legacy/v555/pointerview.go @@ -0,0 +1,135 @@ +package v555 + +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/legacy/v555" + "github.com/sei-protocol/sei-chain/x/evm/state" +) + +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) GetName() string { + return "pointerview" +} + +func (p Precompile) Run(evm *vm.EVM, _ common.Address, _ common.Address, input []byte, _ *big.Int, _ bool, _ bool) (ret []byte, err error) { + defer func() { + if err != nil { + evm.StateDB.(*state.DBImpl).SetPrecompileError(err) + } + }() + 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) { + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + 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) { + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + 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) { + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + addr := args[0].(string) + existingAddr, existingVersion, exists := p.evmKeeper.GetERC721CW721Pointer(ctx, addr) + return method.Outputs.Pack(existingAddr, existingVersion, exists) +} diff --git a/precompiles/pointerview/legacy/v562/abi.json b/precompiles/pointerview/legacy/v562/abi.json new file mode 100644 index 0000000000..a469eff127 --- /dev/null +++ b/precompiles/pointerview/legacy/v562/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/legacy/v562/pointerview.go b/precompiles/pointerview/legacy/v562/pointerview.go new file mode 100644 index 0000000000..b7c6576eca --- /dev/null +++ b/precompiles/pointerview/legacy/v562/pointerview.go @@ -0,0 +1,110 @@ +package v562 + +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/legacy/v562" +) + +const ( + GetNativePointer = "getNativePointer" + GetCW20Pointer = "getCW20Pointer" + GetCW721Pointer = "getCW721Pointer" +) + +const PointerViewAddress = "0x000000000000000000000000000000000000100A" + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + evmKeeper pcommon.EVMKeeper + + GetNativePointerID []byte + GetCW20PointerID []byte + GetCW721PointerID []byte +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper) (*pcommon.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 := &PrecompileExecutor{ + evmKeeper: evmKeeper, + } + + 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 pcommon.NewPrecompile(newAbi, p, common.HexToAddress(PointerViewAddress), "pointerview"), nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p PrecompileExecutor) RequiredGas([]byte, *abi.Method) uint64 { + return 2000 +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM) (ret []byte, err error) { + 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 PrecompileExecutor) GetNative(ctx sdk.Context, method *abi.Method, args []interface{}) (ret []byte, err error) { + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + token := args[0].(string) + existingAddr, existingVersion, exists := p.evmKeeper.GetERC20NativePointer(ctx, token) + return method.Outputs.Pack(existingAddr, existingVersion, exists) +} + +func (p PrecompileExecutor) GetCW20(ctx sdk.Context, method *abi.Method, args []interface{}) (ret []byte, err error) { + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + addr := args[0].(string) + existingAddr, existingVersion, exists := p.evmKeeper.GetERC20CW20Pointer(ctx, addr) + return method.Outputs.Pack(existingAddr, existingVersion, exists) +} + +func (p PrecompileExecutor) GetCW721(ctx sdk.Context, method *abi.Method, args []interface{}) (ret []byte, err error) { + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + addr := args[0].(string) + existingAddr, existingVersion, exists := p.evmKeeper.GetERC721CW721Pointer(ctx, addr) + return method.Outputs.Pack(existingAddr, existingVersion, exists) +} diff --git a/precompiles/setup.go b/precompiles/setup.go index 86e1a680ad..d58e3882dd 100644 --- a/precompiles/setup.go +++ b/precompiles/setup.go @@ -256,3 +256,7 @@ func addPrecompileToVM(p IPrecompile) { vm.PrecompiledAddressesBerlin = append(vm.PrecompiledAddressesBerlin, p.Address()) vm.PrecompiledAddressesCancun = append(vm.PrecompiledAddressesCancun, p.Address()) } + +var PrecompileLastUpgrade = map[string]int64{ + bank.BankAddress: 1, +} diff --git a/precompiles/staking/legacy/v520/abi.json b/precompiles/staking/legacy/v520/abi.json new file mode 100644 index 0000000000..0be519e4d8 --- /dev/null +++ b/precompiles/staking/legacy/v520/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"valAddress","type":"string"}],"name":"delegate","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"srcAddress","type":"string"},{"internalType":"string","name":"dstAddress","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"redelegate","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"valAddress","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"undelegate","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/precompiles/staking/legacy/v520/staking.go b/precompiles/staking/legacy/v520/staking.go new file mode 100644 index 0000000000..ccaed11aa6 --- /dev/null +++ b/precompiles/staking/legacy/v520/staking.go @@ -0,0 +1,215 @@ +package v520 + +import ( + "bytes" + "embed" + "errors" + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/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/legacy/v520" +) + +const ( + DelegateMethod = "delegate" + RedelegateMethod = "redelegate" + UndelegateMethod = "undelegate" +) + +const ( + StakingAddress = "0x0000000000000000000000000000000000001005" +) + +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 + +func GetABI() abi.ABI { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +type Precompile struct { + pcommon.Precompile + stakingKeeper pcommon.StakingKeeper + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + address common.Address + + DelegateID []byte + RedelegateID []byte + UndelegateID []byte +} + +func NewPrecompile(stakingKeeper pcommon.StakingKeeper, evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper) (*Precompile, error) { + newAbi := GetABI() + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + stakingKeeper: stakingKeeper, + evmKeeper: evmKeeper, + bankKeeper: bankKeeper, + address: common.HexToAddress(StakingAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case DelegateMethod: + p.DelegateID = m.ID + case RedelegateMethod: + p.RedelegateID = m.ID + case UndelegateMethod: + p.UndelegateID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return pcommon.UnknownMethodCallGas + } + + if bytes.Equal(methodID, p.DelegateID) { + return 50000 + } else if bytes.Equal(methodID, p.RedelegateID) { + return 70000 + } else if bytes.Equal(methodID, p.UndelegateID) { + return 50000 + } + + // This should never happen since this is going to fail during Run + return pcommon.UnknownMethodCallGas +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "staking" +} + +func (p Precompile) Run(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, value *big.Int, readOnly bool, _ bool) (bz []byte, err error) { + if readOnly { + return nil, errors.New("cannot call staking precompile from staticcall") + } + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, err + } + if caller.Cmp(callingContract) != 0 { + return nil, errors.New("cannot delegatecall staking") + } + + switch method.Name { + case DelegateMethod: + return p.delegate(ctx, method, caller, args, value) + case RedelegateMethod: + return p.redelegate(ctx, method, caller, args, value) + case UndelegateMethod: + return p.undelegate(ctx, method, caller, args, value) + } + return +} + +func (p Precompile) delegate(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + // if delegator is associated, then it must have Account set already + // if delegator is not associated, then it can't delegate anyway (since + // there is no good way to merge delegations if it becomes associated) + delegator, associated := p.evmKeeper.GetSeiAddress(ctx, caller) + if !associated { + return nil, fmt.Errorf("delegator %s is not associated/doesn't have an Account set yet", caller.Hex()) + } + validatorBech32 := args[0].(string) + if value == nil || value.Sign() == 0 { + return nil, errors.New("set `value` field to non-zero to send delegate fund") + } + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), delegator, value, p.bankKeeper) + if err != nil { + return nil, err + } + _, err = p.stakingKeeper.Delegate(sdk.WrapSDKContext(ctx), &stakingtypes.MsgDelegate{ + DelegatorAddress: delegator.String(), + ValidatorAddress: validatorBech32, + Amount: coin, + }) + if err != nil { + return nil, err + } + return method.Outputs.Pack(true) +} + +func (p Precompile) redelegate(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 3); err != nil { + return nil, err + } + delegator, associated := p.evmKeeper.GetSeiAddress(ctx, caller) + if !associated { + return nil, fmt.Errorf("redelegator %s is not associated/doesn't have an Account set yet", caller.Hex()) + } + srcValidatorBech32 := args[0].(string) + dstValidatorBech32 := args[1].(string) + amount := args[2].(*big.Int) + _, err := p.stakingKeeper.BeginRedelegate(sdk.WrapSDKContext(ctx), &stakingtypes.MsgBeginRedelegate{ + DelegatorAddress: delegator.String(), + ValidatorSrcAddress: srcValidatorBech32, + ValidatorDstAddress: dstValidatorBech32, + Amount: sdk.NewCoin(sdk.MustGetBaseDenom(), sdk.NewIntFromBigInt(amount)), + }) + if err != nil { + return nil, err + } + return method.Outputs.Pack(true) +} + +func (p Precompile) undelegate(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + delegator, associated := p.evmKeeper.GetSeiAddress(ctx, caller) + if !associated { + return nil, fmt.Errorf("undelegator %s is not associated/doesn't have an Account set yet", caller.Hex()) + } + validatorBech32 := args[0].(string) + amount := args[1].(*big.Int) + _, err := p.stakingKeeper.Undelegate(sdk.WrapSDKContext(ctx), &stakingtypes.MsgUndelegate{ + DelegatorAddress: delegator.String(), + ValidatorAddress: validatorBech32, + Amount: sdk.NewCoin(p.evmKeeper.GetBaseDenom(ctx), sdk.NewIntFromBigInt(amount)), + }) + if err != nil { + return nil, err + } + return method.Outputs.Pack(true) +} diff --git a/precompiles/staking/legacy/v555/abi.json b/precompiles/staking/legacy/v555/abi.json new file mode 100644 index 0000000000..0be519e4d8 --- /dev/null +++ b/precompiles/staking/legacy/v555/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"valAddress","type":"string"}],"name":"delegate","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"srcAddress","type":"string"},{"internalType":"string","name":"dstAddress","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"redelegate","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"valAddress","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"undelegate","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/precompiles/staking/legacy/v555/staking.go b/precompiles/staking/legacy/v555/staking.go new file mode 100644 index 0000000000..1a38696781 --- /dev/null +++ b/precompiles/staking/legacy/v555/staking.go @@ -0,0 +1,221 @@ +package v555 + +import ( + "bytes" + "embed" + "errors" + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/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/legacy/v555" + "github.com/sei-protocol/sei-chain/x/evm/state" +) + +const ( + DelegateMethod = "delegate" + RedelegateMethod = "redelegate" + UndelegateMethod = "undelegate" +) + +const ( + StakingAddress = "0x0000000000000000000000000000000000001005" +) + +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 + +func GetABI() abi.ABI { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +type Precompile struct { + pcommon.Precompile + stakingKeeper pcommon.StakingKeeper + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + address common.Address + + DelegateID []byte + RedelegateID []byte + UndelegateID []byte +} + +func NewPrecompile(stakingKeeper pcommon.StakingKeeper, evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper) (*Precompile, error) { + newAbi := GetABI() + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + stakingKeeper: stakingKeeper, + evmKeeper: evmKeeper, + bankKeeper: bankKeeper, + address: common.HexToAddress(StakingAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case DelegateMethod: + p.DelegateID = m.ID + case RedelegateMethod: + p.RedelegateID = m.ID + case UndelegateMethod: + p.UndelegateID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return pcommon.UnknownMethodCallGas + } + + if bytes.Equal(methodID, p.DelegateID) { + return 50000 + } else if bytes.Equal(methodID, p.RedelegateID) { + return 70000 + } else if bytes.Equal(methodID, p.UndelegateID) { + return 50000 + } + + // This should never happen since this is going to fail during Run + return pcommon.UnknownMethodCallGas +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "staking" +} + +func (p Precompile) Run(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, value *big.Int, readOnly bool, _ bool) (bz []byte, err error) { + defer func() { + if err != nil { + evm.StateDB.(*state.DBImpl).SetPrecompileError(err) + } + }() + if readOnly { + return nil, errors.New("cannot call staking precompile from staticcall") + } + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, err + } + if caller.Cmp(callingContract) != 0 { + return nil, errors.New("cannot delegatecall staking") + } + + switch method.Name { + case DelegateMethod: + return p.delegate(ctx, method, caller, args, value) + case RedelegateMethod: + return p.redelegate(ctx, method, caller, args, value) + case UndelegateMethod: + return p.undelegate(ctx, method, caller, args, value) + } + return +} + +func (p Precompile) delegate(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + // if delegator is associated, then it must have Account set already + // if delegator is not associated, then it can't delegate anyway (since + // there is no good way to merge delegations if it becomes associated) + delegator, associated := p.evmKeeper.GetSeiAddress(ctx, caller) + if !associated { + return nil, fmt.Errorf("delegator %s is not associated/doesn't have an Account set yet", caller.Hex()) + } + validatorBech32 := args[0].(string) + if value == nil || value.Sign() == 0 { + return nil, errors.New("set `value` field to non-zero to send delegate fund") + } + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), delegator, value, p.bankKeeper) + if err != nil { + return nil, err + } + _, err = p.stakingKeeper.Delegate(sdk.WrapSDKContext(ctx), &stakingtypes.MsgDelegate{ + DelegatorAddress: delegator.String(), + ValidatorAddress: validatorBech32, + Amount: coin, + }) + if err != nil { + return nil, err + } + return method.Outputs.Pack(true) +} + +func (p Precompile) redelegate(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 3); err != nil { + return nil, err + } + delegator, associated := p.evmKeeper.GetSeiAddress(ctx, caller) + if !associated { + return nil, fmt.Errorf("redelegator %s is not associated/doesn't have an Account set yet", caller.Hex()) + } + srcValidatorBech32 := args[0].(string) + dstValidatorBech32 := args[1].(string) + amount := args[2].(*big.Int) + _, err := p.stakingKeeper.BeginRedelegate(sdk.WrapSDKContext(ctx), &stakingtypes.MsgBeginRedelegate{ + DelegatorAddress: delegator.String(), + ValidatorSrcAddress: srcValidatorBech32, + ValidatorDstAddress: dstValidatorBech32, + Amount: sdk.NewCoin(sdk.MustGetBaseDenom(), sdk.NewIntFromBigInt(amount)), + }) + if err != nil { + return nil, err + } + return method.Outputs.Pack(true) +} + +func (p Precompile) undelegate(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + delegator, associated := p.evmKeeper.GetSeiAddress(ctx, caller) + if !associated { + return nil, fmt.Errorf("undelegator %s is not associated/doesn't have an Account set yet", caller.Hex()) + } + validatorBech32 := args[0].(string) + amount := args[1].(*big.Int) + _, err := p.stakingKeeper.Undelegate(sdk.WrapSDKContext(ctx), &stakingtypes.MsgUndelegate{ + DelegatorAddress: delegator.String(), + ValidatorAddress: validatorBech32, + Amount: sdk.NewCoin(p.evmKeeper.GetBaseDenom(ctx), sdk.NewIntFromBigInt(amount)), + }) + if err != nil { + return nil, err + } + return method.Outputs.Pack(true) +} diff --git a/precompiles/staking/legacy/v562/abi.json b/precompiles/staking/legacy/v562/abi.json new file mode 100644 index 0000000000..0be519e4d8 --- /dev/null +++ b/precompiles/staking/legacy/v562/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"valAddress","type":"string"}],"name":"delegate","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"srcAddress","type":"string"},{"internalType":"string","name":"dstAddress","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"redelegate","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"valAddress","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"undelegate","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/precompiles/staking/legacy/v562/staking.go b/precompiles/staking/legacy/v562/staking.go new file mode 100644 index 0000000000..b5a2e60026 --- /dev/null +++ b/precompiles/staking/legacy/v562/staking.go @@ -0,0 +1,194 @@ +package v562 + +import ( + "bytes" + "embed" + "errors" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/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/legacy/v562" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const ( + DelegateMethod = "delegate" + RedelegateMethod = "redelegate" + UndelegateMethod = "undelegate" +) + +const ( + StakingAddress = "0x0000000000000000000000000000000000001005" +) + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +func GetABI() abi.ABI { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + panic(err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + panic(err) + } + return newAbi +} + +type PrecompileExecutor struct { + stakingKeeper pcommon.StakingKeeper + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + address common.Address + + DelegateID []byte + RedelegateID []byte + UndelegateID []byte +} + +func NewPrecompile(stakingKeeper pcommon.StakingKeeper, evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper) (*pcommon.Precompile, error) { + newAbi := GetABI() + + p := &PrecompileExecutor{ + stakingKeeper: stakingKeeper, + evmKeeper: evmKeeper, + bankKeeper: bankKeeper, + address: common.HexToAddress(StakingAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case DelegateMethod: + p.DelegateID = m.ID + case RedelegateMethod: + p.RedelegateID = m.ID + case UndelegateMethod: + p.UndelegateID = m.ID + } + } + + return pcommon.NewPrecompile(newAbi, p, p.address, "staking"), nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p PrecompileExecutor) RequiredGas(input []byte, method *abi.Method) uint64 { + if bytes.Equal(method.ID, p.DelegateID) { + return 50000 + } else if bytes.Equal(method.ID, p.RedelegateID) { + return 70000 + } else if bytes.Equal(method.ID, p.UndelegateID) { + return 50000 + } + + // This should never happen since this is going to fail during Run + return pcommon.UnknownMethodCallGas +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM) (bz []byte, err error) { + if readOnly { + return nil, errors.New("cannot call staking precompile from staticcall") + } + if caller.Cmp(callingContract) != 0 { + return nil, errors.New("cannot delegatecall staking") + } + + switch method.Name { + case DelegateMethod: + return p.delegate(ctx, method, caller, args, value) + case RedelegateMethod: + return p.redelegate(ctx, method, caller, args, value) + case UndelegateMethod: + return p.undelegate(ctx, method, caller, args, value) + } + return +} + +func (p PrecompileExecutor) delegate(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + // if delegator is associated, then it must have Account set already + // if delegator is not associated, then it can't delegate anyway (since + // there is no good way to merge delegations if it becomes associated) + delegator, associated := p.evmKeeper.GetSeiAddress(ctx, caller) + if !associated { + return nil, types.NewAssociationMissingErr(caller.Hex()) + } + validatorBech32 := args[0].(string) + if value == nil || value.Sign() == 0 { + return nil, errors.New("set `value` field to non-zero to send delegate fund") + } + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), delegator, value, p.bankKeeper) + if err != nil { + return nil, err + } + _, err = p.stakingKeeper.Delegate(sdk.WrapSDKContext(ctx), &stakingtypes.MsgDelegate{ + DelegatorAddress: delegator.String(), + ValidatorAddress: validatorBech32, + Amount: coin, + }) + if err != nil { + return nil, err + } + return method.Outputs.Pack(true) +} + +func (p PrecompileExecutor) redelegate(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 3); err != nil { + return nil, err + } + delegator, associated := p.evmKeeper.GetSeiAddress(ctx, caller) + if !associated { + return nil, types.NewAssociationMissingErr(caller.Hex()) + } + srcValidatorBech32 := args[0].(string) + dstValidatorBech32 := args[1].(string) + amount := args[2].(*big.Int) + _, err := p.stakingKeeper.BeginRedelegate(sdk.WrapSDKContext(ctx), &stakingtypes.MsgBeginRedelegate{ + DelegatorAddress: delegator.String(), + ValidatorSrcAddress: srcValidatorBech32, + ValidatorDstAddress: dstValidatorBech32, + Amount: sdk.NewCoin(sdk.MustGetBaseDenom(), sdk.NewIntFromBigInt(amount)), + }) + if err != nil { + return nil, err + } + return method.Outputs.Pack(true) +} + +func (p PrecompileExecutor) undelegate(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + delegator, associated := p.evmKeeper.GetSeiAddress(ctx, caller) + if !associated { + return nil, types.NewAssociationMissingErr(caller.Hex()) + } + validatorBech32 := args[0].(string) + amount := args[1].(*big.Int) + _, err := p.stakingKeeper.Undelegate(sdk.WrapSDKContext(ctx), &stakingtypes.MsgUndelegate{ + DelegatorAddress: delegator.String(), + ValidatorAddress: validatorBech32, + Amount: sdk.NewCoin(p.evmKeeper.GetBaseDenom(ctx), sdk.NewIntFromBigInt(amount)), + }) + if err != nil { + return nil, err + } + return method.Outputs.Pack(true) +} diff --git a/precompiles/staking/legacy/v580/abi.json b/precompiles/staking/legacy/v580/abi.json new file mode 100644 index 0000000000..bec7f5df47 --- /dev/null +++ b/precompiles/staking/legacy/v580/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"valAddress","type":"string"}],"name":"delegate","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"srcAddress","type":"string"},{"internalType":"string","name":"dstAddress","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"redelegate","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"valAddress","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"undelegate","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"delegator","type":"address"},{"internalType":"string","name":"valAddress","type":"string"}],"name":"delegation","outputs":[{"components":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"denom","type":"string"}],"internalType":"struct Balance","name":"balance","type":"tuple"},{"components":[{"internalType":"string","name":"delegator_address","type":"string"},{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"uint256","name":"decimals","type":"uint256"},{"internalType":"string","name":"validator_address","type":"string"}],"internalType":"struct DelegationDetails","name":"delegation","type":"tuple"}],"internalType":"struct Delegation","name":"delegation","type":"tuple"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/staking/legacy/v580/staking.go b/precompiles/staking/legacy/v580/staking.go new file mode 100644 index 0000000000..017db0b47f --- /dev/null +++ b/precompiles/staking/legacy/v580/staking.go @@ -0,0 +1,252 @@ +package v580 + +import ( + "bytes" + "embed" + "errors" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/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/legacy/v580" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const ( + DelegateMethod = "delegate" + RedelegateMethod = "redelegate" + UndelegateMethod = "undelegate" + DelegationMethod = "delegation" +) + +const ( + StakingAddress = "0x0000000000000000000000000000000000001005" +) + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + stakingKeeper pcommon.StakingKeeper + stakingQuerier pcommon.StakingQuerier + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + address common.Address + + DelegateID []byte + RedelegateID []byte + UndelegateID []byte + DelegationID []byte +} + +func NewPrecompile(stakingKeeper pcommon.StakingKeeper, stakingQuerier pcommon.StakingQuerier, evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper) (*pcommon.Precompile, error) { + newAbi := pcommon.MustGetABI(f, "abi.json") + + p := &PrecompileExecutor{ + stakingKeeper: stakingKeeper, + stakingQuerier: stakingQuerier, + evmKeeper: evmKeeper, + bankKeeper: bankKeeper, + address: common.HexToAddress(StakingAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case DelegateMethod: + p.DelegateID = m.ID + case RedelegateMethod: + p.RedelegateID = m.ID + case UndelegateMethod: + p.UndelegateID = m.ID + case DelegationMethod: + p.DelegationID = m.ID + } + } + + return pcommon.NewPrecompile(newAbi, p, p.address, "staking"), nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p PrecompileExecutor) RequiredGas(input []byte, method *abi.Method) uint64 { + if bytes.Equal(method.ID, p.DelegateID) { + return 50000 + } else if bytes.Equal(method.ID, p.RedelegateID) { + return 70000 + } else if bytes.Equal(method.ID, p.UndelegateID) { + return 50000 + } + + // This should never happen since this is going to fail during Run + return pcommon.UnknownMethodCallGas +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM) (bz []byte, err error) { + if caller.Cmp(callingContract) != 0 { + return nil, errors.New("cannot delegatecall staking") + } + switch method.Name { + case DelegateMethod: + if readOnly { + return nil, errors.New("cannot call staking precompile from staticcall") + } + return p.delegate(ctx, method, caller, args, value) + case RedelegateMethod: + if readOnly { + return nil, errors.New("cannot call staking precompile from staticcall") + } + return p.redelegate(ctx, method, caller, args, value) + case UndelegateMethod: + if readOnly { + return nil, errors.New("cannot call staking precompile from staticcall") + } + return p.undelegate(ctx, method, caller, args, value) + case DelegationMethod: + return p.delegation(ctx, method, args, value) + } + return +} + +func (p PrecompileExecutor) delegate(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + // if delegator is associated, then it must have Account set already + // if delegator is not associated, then it can't delegate anyway (since + // there is no good way to merge delegations if it becomes associated) + delegator, associated := p.evmKeeper.GetSeiAddress(ctx, caller) + if !associated { + return nil, types.NewAssociationMissingErr(caller.Hex()) + } + validatorBech32 := args[0].(string) + if value == nil || value.Sign() == 0 { + return nil, errors.New("set `value` field to non-zero to send delegate fund") + } + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), delegator, value, p.bankKeeper) + if err != nil { + return nil, err + } + _, err = p.stakingKeeper.Delegate(sdk.WrapSDKContext(ctx), &stakingtypes.MsgDelegate{ + DelegatorAddress: delegator.String(), + ValidatorAddress: validatorBech32, + Amount: coin, + }) + if err != nil { + return nil, err + } + return method.Outputs.Pack(true) +} + +func (p PrecompileExecutor) redelegate(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 3); err != nil { + return nil, err + } + delegator, associated := p.evmKeeper.GetSeiAddress(ctx, caller) + if !associated { + return nil, types.NewAssociationMissingErr(caller.Hex()) + } + srcValidatorBech32 := args[0].(string) + dstValidatorBech32 := args[1].(string) + amount := args[2].(*big.Int) + _, err := p.stakingKeeper.BeginRedelegate(sdk.WrapSDKContext(ctx), &stakingtypes.MsgBeginRedelegate{ + DelegatorAddress: delegator.String(), + ValidatorSrcAddress: srcValidatorBech32, + ValidatorDstAddress: dstValidatorBech32, + Amount: sdk.NewCoin(sdk.MustGetBaseDenom(), sdk.NewIntFromBigInt(amount)), + }) + if err != nil { + return nil, err + } + return method.Outputs.Pack(true) +} + +func (p PrecompileExecutor) undelegate(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + delegator, associated := p.evmKeeper.GetSeiAddress(ctx, caller) + if !associated { + return nil, types.NewAssociationMissingErr(caller.Hex()) + } + validatorBech32 := args[0].(string) + amount := args[1].(*big.Int) + _, err := p.stakingKeeper.Undelegate(sdk.WrapSDKContext(ctx), &stakingtypes.MsgUndelegate{ + DelegatorAddress: delegator.String(), + ValidatorAddress: validatorBech32, + Amount: sdk.NewCoin(p.evmKeeper.GetBaseDenom(ctx), sdk.NewIntFromBigInt(amount)), + }) + if err != nil { + return nil, err + } + return method.Outputs.Pack(true) +} + +type Delegation struct { + Balance Balance + Delegation DelegationDetails +} + +type Balance struct { + Amount *big.Int + Denom string +} + +type DelegationDetails struct { + DelegatorAddress string + Shares *big.Int + Decimals *big.Int + ValidatorAddress string +} + +func (p PrecompileExecutor) delegation(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } + + seiDelegatorAddress, err := pcommon.GetSeiAddressFromArg(ctx, args[0], p.evmKeeper) + if err != nil { + return nil, err + } + + validatorBech32 := args[1].(string) + delegationRequest := &stakingtypes.QueryDelegationRequest{ + DelegatorAddr: seiDelegatorAddress.String(), + ValidatorAddr: validatorBech32, + } + + delegationResponse, err := p.stakingQuerier.Delegation(sdk.WrapSDKContext(ctx), delegationRequest) + if err != nil { + return nil, err + } + + delegation := Delegation{ + Balance: Balance{ + Amount: delegationResponse.GetDelegationResponse().GetBalance().Amount.BigInt(), + Denom: delegationResponse.GetDelegationResponse().GetBalance().Denom, + }, + Delegation: DelegationDetails{ + DelegatorAddress: delegationResponse.GetDelegationResponse().GetDelegation().DelegatorAddress, + Shares: delegationResponse.GetDelegationResponse().GetDelegation().Shares.BigInt(), + Decimals: big.NewInt(sdk.Precision), + ValidatorAddress: delegationResponse.GetDelegationResponse().GetDelegation().ValidatorAddress, + }, + } + + return method.Outputs.Pack(delegation) +} diff --git a/precompiles/wasmd/legacy/v501/abi.json b/precompiles/wasmd/legacy/v501/abi.json new file mode 100644 index 0000000000..c5e5b1b3f6 --- /dev/null +++ b/precompiles/wasmd/legacy/v501/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"execute","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"internalType":"struct IWasmd.ExecuteMsg[]","name":"executeMsgs","type":"tuple[]"}],"name":"execute_batch","outputs":[{"internalType":"bytes[]","name":"responses","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint64","name":"codeID","type":"uint64"},{"internalType":"string","name":"admin","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"string","name":"label","type":"string"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"instantiate","outputs":[{"internalType":"string","name":"contractAddr","type":"string"},{"internalType":"bytes","name":"data","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"req","type":"bytes"}],"name":"query","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/wasmd/legacy/v501/wasmd.go b/precompiles/wasmd/legacy/v501/wasmd.go new file mode 100644 index 0000000000..427a6de00b --- /dev/null +++ b/precompiles/wasmd/legacy/v501/wasmd.go @@ -0,0 +1,503 @@ +package v501 + +import ( + "bytes" + "embed" + "encoding/json" + "errors" + "fmt" + "math/big" + + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + 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/tracing" + "github.com/ethereum/go-ethereum/core/vm" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v520" + "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/x/evm/state" +) + +const ( + InstantiateMethod = "instantiate" + ExecuteMethod = "execute" + ExecuteBatchMethod = "execute_batch" + QueryMethod = "query" +) + +const WasmdAddress = "0x0000000000000000000000000000000000001002" + +var _ vm.PrecompiledContract = &Precompile{} +var _ vm.DynamicGasPrecompiledContract = &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.WasmdKeeper + wasmdViewKeeper pcommon.WasmdViewKeeper + address common.Address + + InstantiateID []byte + ExecuteID []byte + ExecuteBatchID []byte + QueryID []byte +} + +type ExecuteMsg struct { + ContractAddress string `json:"contractAddress"` + Msg []byte `json:"msg"` + Coins []byte `json:"coins"` +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper, wasmdKeeper pcommon.WasmdKeeper, wasmdViewKeeper pcommon.WasmdViewKeeper, bankKeeper pcommon.BankKeeper) (*Precompile, error) { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + return nil, fmt.Errorf("error loading the staking ABI %s", err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + return nil, err + } + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + wasmdKeeper: wasmdKeeper, + wasmdViewKeeper: wasmdViewKeeper, + evmKeeper: evmKeeper, + bankKeeper: bankKeeper, + address: common.HexToAddress(WasmdAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case InstantiateMethod: + p.InstantiateID = m.ID + case ExecuteMethod: + p.ExecuteID = m.ID + case ExecuteBatchMethod: + p.ExecuteBatchID = m.ID + case QueryMethod: + p.QueryID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return pcommon.UnknownMethodCallGas + } + + method, err := p.ABI.MethodById(methodID) + if err != nil { + // This should never happen since this method is going to fail during Run + return pcommon.UnknownMethodCallGas + } + + return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) +} + +func (Precompile) IsTransaction(method string) bool { + switch method { + case ExecuteMethod: + return true + case ExecuteBatchMethod: + return true + case InstantiateMethod: + return true + default: + return false + } +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "wasmd" +} + +func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, 0, err + } + gasMultipler := p.evmKeeper.GetPriorityNormalizer(ctx) + gasLimitBigInt := sdk.NewDecFromInt(sdk.NewIntFromUint64(suppliedGas)).Mul(gasMultipler).TruncateInt().BigInt() + if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { + gasLimitBigInt = utils.BigMaxU64 + } + ctx = ctx.WithGasMeter(sdk.NewGasMeter(gasLimitBigInt.Uint64(), 1, 1)) + + switch method.Name { + case InstantiateMethod: + return p.instantiate(ctx, method, caller, callingContract, args, value, readOnly) + case ExecuteMethod: + return p.execute(ctx, method, caller, callingContract, args, value, readOnly) + case ExecuteBatchMethod: + return p.execute_batch(ctx, method, caller, callingContract, args, value, readOnly) + case QueryMethod: + return p.query(ctx, method, args, value) + } + return +} + +func (p Precompile) Run(*vm.EVM, common.Address, common.Address, []byte, *big.Int, bool, bool) ([]byte, error) { + panic("static gas Run is not implemented for dynamic gas precompile") +} + +func (p Precompile) instantiate(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call instantiate from staticcall") + return + } + if err := pcommon.ValidateArgsLength(args, 5); err != nil { + rerr = err + return + } + if caller.Cmp(callingContract) != 0 { + rerr = errors.New("cannot delegatecall instantiate") + return + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + codeID := args[0].(uint64) + creatorAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + rerr = fmt.Errorf("creator %s is not associated", caller.Hex()) + return + } + var adminAddr sdk.AccAddress + adminAddrStr := args[1].(string) + if len(adminAddrStr) > 0 { + adminAddrDecoded, err := sdk.AccAddressFromBech32(adminAddrStr) + if err != nil { + rerr = err + return + } + adminAddr = adminAddrDecoded + } + msg := args[2].([]byte) + label := args[3].(string) + coins := sdk.NewCoins() + coinsBz := args[4].([]byte) + + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { + rerr = errors.New("coin amount must equal value specified") + return + } + + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgInstantiate := wasmtypes.MsgInstantiateContract{ + Sender: creatorAddr.String(), + CodeID: codeID, + Label: label, + Funds: coins, + Msg: msg, + Admin: adminAddrStr, + } + + if err := msgInstantiate.ValidateBasic(); err != nil { + rerr = err + return + } + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if value != nil && !useiAmt.IsZero() { + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), creatorAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + + addr, data, err := p.wasmdKeeper.Instantiate(ctx, codeID, creatorAddr, adminAddr, msg, label, coins) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(addr.String(), data) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p Precompile) execute_batch(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call execute from staticcall") + return + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + rerr = err + return + } + + executeMsgs := args[0].([]struct { + ContractAddress string `json:"contractAddress"` + Msg []byte `json:"msg"` + Coins []byte `json:"coins"` + }) + + responses := make([][]byte, 0, len(executeMsgs)) + + // validate coins add up to value + validateValue := big.NewInt(0) + for i := 0; i < len(executeMsgs); i++ { + executeMsg := ExecuteMsg(executeMsgs[i]) + coinsBz := executeMsg.Coins + coins := sdk.NewCoins() + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + messageAmount := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + validateValue.Add(validateValue, messageAmount) + } + // if validateValue is greater than zero, then value must be provided, and they must be equal + if (value == nil && validateValue.Sign() == 1) || (value != nil && validateValue.Cmp(value) != 0) { + rerr = errors.New("sum of coin amounts must equal value specified") + return + } + for i := 0; i < len(executeMsgs); i++ { + executeMsg := ExecuteMsg(executeMsgs[i]) + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + contractAddrStr := executeMsg.ContractAddress + if caller.Cmp(callingContract) != 0 { + erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) + erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) + if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { + return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) + } + } + + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + senderAddr := p.evmKeeper.GetSeiAddressOrDefault(ctx, caller) + msg := executeMsg.Msg + coinsBz := executeMsg.Coins + coins := sdk.NewCoins() + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if value != nil && !useiAmt.IsZero() { + // process coin amount from the value provided + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + value.Sub(value, useiAmtAsWei) + if value.Sign() == -1 { + rerr = errors.New("insufficient value provided for payment") + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgExecute := wasmtypes.MsgExecuteContract{ + Sender: senderAddr.String(), + Contract: contractAddr.String(), + Msg: msg, + Funds: coins, + } + if err := msgExecute.ValidateBasic(); err != nil { + rerr = err + return + } + + res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) + if err != nil { + rerr = err + return + } + responses = append(responses, res) + } + if value != nil && value.Sign() != 0 { + rerr = errors.New("value remaining after execution, must match provided amounts exactly") + return + } + ret, rerr = method.Outputs.Pack(responses) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p Precompile) execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call execute from staticcall") + return + } + if err := pcommon.ValidateArgsLength(args, 3); err != nil { + rerr = err + return + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + contractAddrStr := args[0].(string) + if caller.Cmp(callingContract) != 0 { + erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) + erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) + if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { + return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) + } + } + // addresses will be sent in Sei format + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + senderAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + rerr = fmt.Errorf("sender %s is not associated", caller.Hex()) + return + } + msg := args[1].([]byte) + coins := sdk.NewCoins() + coinsBz := args[2].([]byte) + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { + rerr = errors.New("coin amount must equal value specified") + return + } + + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgExecute := wasmtypes.MsgExecuteContract{ + Sender: senderAddr.String(), + Contract: contractAddr.String(), + Msg: msg, + Funds: coins, + } + + if err := msgExecute.ValidateBasic(); err != nil { + rerr = err + return + } + + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if value != nil && !useiAmt.IsZero() { + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(res) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p Precompile) query(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if err := pcommon.ValidateNonPayable(value); err != nil { + rerr = err + return + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + rerr = err + return + } + + contractAddrStr := args[0].(string) + // addresses will be sent in Sei format + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + req := args[1].([]byte) + + rawContractMessage := wasmtypes.RawContractMessage(req) + if err := rawContractMessage.ValidateBasic(); err != nil { + rerr = err + return + } + + res, err := p.wasmdViewKeeper.QuerySmart(ctx, contractAddr, req) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(res) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} diff --git a/precompiles/wasmd/legacy/v510/abi.json b/precompiles/wasmd/legacy/v510/abi.json new file mode 100644 index 0000000000..c5e5b1b3f6 --- /dev/null +++ b/precompiles/wasmd/legacy/v510/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"execute","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"internalType":"struct IWasmd.ExecuteMsg[]","name":"executeMsgs","type":"tuple[]"}],"name":"execute_batch","outputs":[{"internalType":"bytes[]","name":"responses","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint64","name":"codeID","type":"uint64"},{"internalType":"string","name":"admin","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"string","name":"label","type":"string"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"instantiate","outputs":[{"internalType":"string","name":"contractAddr","type":"string"},{"internalType":"bytes","name":"data","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"req","type":"bytes"}],"name":"query","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/wasmd/legacy/v510/wasmd.go b/precompiles/wasmd/legacy/v510/wasmd.go new file mode 100644 index 0000000000..00f2efe97a --- /dev/null +++ b/precompiles/wasmd/legacy/v510/wasmd.go @@ -0,0 +1,503 @@ +package v510 + +import ( + "bytes" + "embed" + "encoding/json" + "errors" + "fmt" + "math/big" + + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + 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/tracing" + "github.com/ethereum/go-ethereum/core/vm" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v520" + "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/x/evm/state" +) + +const ( + InstantiateMethod = "instantiate" + ExecuteMethod = "execute" + ExecuteBatchMethod = "execute_batch" + QueryMethod = "query" +) + +const WasmdAddress = "0x0000000000000000000000000000000000001002" + +var _ vm.PrecompiledContract = &Precompile{} +var _ vm.DynamicGasPrecompiledContract = &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.WasmdKeeper + wasmdViewKeeper pcommon.WasmdViewKeeper + address common.Address + + InstantiateID []byte + ExecuteID []byte + ExecuteBatchID []byte + QueryID []byte +} + +type ExecuteMsg struct { + ContractAddress string `json:"contractAddress"` + Msg []byte `json:"msg"` + Coins []byte `json:"coins"` +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper, wasmdKeeper pcommon.WasmdKeeper, wasmdViewKeeper pcommon.WasmdViewKeeper, bankKeeper pcommon.BankKeeper) (*Precompile, error) { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + return nil, fmt.Errorf("error loading the staking ABI %s", err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + return nil, err + } + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + wasmdKeeper: wasmdKeeper, + wasmdViewKeeper: wasmdViewKeeper, + evmKeeper: evmKeeper, + bankKeeper: bankKeeper, + address: common.HexToAddress(WasmdAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case InstantiateMethod: + p.InstantiateID = m.ID + case ExecuteMethod: + p.ExecuteID = m.ID + case ExecuteBatchMethod: + p.ExecuteBatchID = m.ID + case QueryMethod: + p.QueryID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return pcommon.UnknownMethodCallGas + } + + method, err := p.ABI.MethodById(methodID) + if err != nil { + // This should never happen since this method is going to fail during Run + return pcommon.UnknownMethodCallGas + } + + return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) +} + +func (Precompile) IsTransaction(method string) bool { + switch method { + case ExecuteMethod: + return true + case ExecuteBatchMethod: + return true + case InstantiateMethod: + return true + default: + return false + } +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "wasmd" +} + +func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, 0, err + } + gasMultipler := p.evmKeeper.GetPriorityNormalizer(ctx) + gasLimitBigInt := sdk.NewDecFromInt(sdk.NewIntFromUint64(suppliedGas)).Mul(gasMultipler).TruncateInt().BigInt() + if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { + gasLimitBigInt = utils.BigMaxU64 + } + ctx = ctx.WithGasMeter(sdk.NewGasMeterWithMultiplier(ctx, gasLimitBigInt.Uint64())) + + switch method.Name { + case InstantiateMethod: + return p.instantiate(ctx, method, caller, callingContract, args, value, readOnly) + case ExecuteMethod: + return p.execute(ctx, method, caller, callingContract, args, value, readOnly) + case ExecuteBatchMethod: + return p.execute_batch(ctx, method, caller, callingContract, args, value, readOnly) + case QueryMethod: + return p.query(ctx, method, args, value) + } + return +} + +func (p Precompile) Run(*vm.EVM, common.Address, common.Address, []byte, *big.Int, bool, bool) ([]byte, error) { + panic("static gas Run is not implemented for dynamic gas precompile") +} + +func (p Precompile) instantiate(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call instantiate from staticcall") + return + } + if err := pcommon.ValidateArgsLength(args, 5); err != nil { + rerr = err + return + } + if caller.Cmp(callingContract) != 0 { + rerr = errors.New("cannot delegatecall instantiate") + return + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + codeID := args[0].(uint64) + creatorAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + rerr = fmt.Errorf("creator %s is not associated", caller.Hex()) + return + } + var adminAddr sdk.AccAddress + adminAddrStr := args[1].(string) + if len(adminAddrStr) > 0 { + adminAddrDecoded, err := sdk.AccAddressFromBech32(adminAddrStr) + if err != nil { + rerr = err + return + } + adminAddr = adminAddrDecoded + } + msg := args[2].([]byte) + label := args[3].(string) + coins := sdk.NewCoins() + coinsBz := args[4].([]byte) + + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { + rerr = errors.New("coin amount must equal value specified") + return + } + + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgInstantiate := wasmtypes.MsgInstantiateContract{ + Sender: creatorAddr.String(), + CodeID: codeID, + Label: label, + Funds: coins, + Msg: msg, + Admin: adminAddrStr, + } + + if err := msgInstantiate.ValidateBasic(); err != nil { + rerr = err + return + } + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if value != nil && !useiAmt.IsZero() { + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), creatorAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + + addr, data, err := p.wasmdKeeper.Instantiate(ctx, codeID, creatorAddr, adminAddr, msg, label, coins) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(addr.String(), data) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p Precompile) execute_batch(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call execute from staticcall") + return + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + rerr = err + return + } + + executeMsgs := args[0].([]struct { + ContractAddress string `json:"contractAddress"` + Msg []byte `json:"msg"` + Coins []byte `json:"coins"` + }) + + responses := make([][]byte, 0, len(executeMsgs)) + + // validate coins add up to value + validateValue := big.NewInt(0) + for i := 0; i < len(executeMsgs); i++ { + executeMsg := ExecuteMsg(executeMsgs[i]) + coinsBz := executeMsg.Coins + coins := sdk.NewCoins() + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + messageAmount := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + validateValue.Add(validateValue, messageAmount) + } + // if validateValue is greater than zero, then value must be provided, and they must be equal + if (value == nil && validateValue.Sign() == 1) || (value != nil && validateValue.Cmp(value) != 0) { + rerr = errors.New("sum of coin amounts must equal value specified") + return + } + for i := 0; i < len(executeMsgs); i++ { + executeMsg := ExecuteMsg(executeMsgs[i]) + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + contractAddrStr := executeMsg.ContractAddress + if caller.Cmp(callingContract) != 0 { + erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) + erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) + if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { + return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) + } + } + + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + senderAddr := p.evmKeeper.GetSeiAddressOrDefault(ctx, caller) + msg := executeMsg.Msg + coinsBz := executeMsg.Coins + coins := sdk.NewCoins() + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if value != nil && !useiAmt.IsZero() { + // process coin amount from the value provided + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + value.Sub(value, useiAmtAsWei) + if value.Sign() == -1 { + rerr = errors.New("insufficient value provided for payment") + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgExecute := wasmtypes.MsgExecuteContract{ + Sender: senderAddr.String(), + Contract: contractAddr.String(), + Msg: msg, + Funds: coins, + } + if err := msgExecute.ValidateBasic(); err != nil { + rerr = err + return + } + + res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) + if err != nil { + rerr = err + return + } + responses = append(responses, res) + } + if value != nil && value.Sign() != 0 { + rerr = errors.New("value remaining after execution, must match provided amounts exactly") + return + } + ret, rerr = method.Outputs.Pack(responses) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p Precompile) execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call execute from staticcall") + return + } + if err := pcommon.ValidateArgsLength(args, 3); err != nil { + rerr = err + return + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + contractAddrStr := args[0].(string) + if caller.Cmp(callingContract) != 0 { + erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) + erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) + if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { + return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) + } + } + // addresses will be sent in Sei format + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + senderAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + rerr = fmt.Errorf("sender %s is not associated", caller.Hex()) + return + } + msg := args[1].([]byte) + coins := sdk.NewCoins() + coinsBz := args[2].([]byte) + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { + rerr = errors.New("coin amount must equal value specified") + return + } + + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgExecute := wasmtypes.MsgExecuteContract{ + Sender: senderAddr.String(), + Contract: contractAddr.String(), + Msg: msg, + Funds: coins, + } + + if err := msgExecute.ValidateBasic(); err != nil { + rerr = err + return + } + + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if value != nil && !useiAmt.IsZero() { + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(res) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p Precompile) query(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if err := pcommon.ValidateNonPayable(value); err != nil { + rerr = err + return + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + rerr = err + return + } + + contractAddrStr := args[0].(string) + // addresses will be sent in Sei format + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + req := args[1].([]byte) + + rawContractMessage := wasmtypes.RawContractMessage(req) + if err := rawContractMessage.ValidateBasic(); err != nil { + rerr = err + return + } + + res, err := p.wasmdViewKeeper.QuerySmart(ctx, contractAddr, req) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(res) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} diff --git a/precompiles/wasmd/legacy/v520/abi.json b/precompiles/wasmd/legacy/v520/abi.json new file mode 100644 index 0000000000..c5e5b1b3f6 --- /dev/null +++ b/precompiles/wasmd/legacy/v520/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"execute","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"internalType":"struct IWasmd.ExecuteMsg[]","name":"executeMsgs","type":"tuple[]"}],"name":"execute_batch","outputs":[{"internalType":"bytes[]","name":"responses","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint64","name":"codeID","type":"uint64"},{"internalType":"string","name":"admin","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"string","name":"label","type":"string"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"instantiate","outputs":[{"internalType":"string","name":"contractAddr","type":"string"},{"internalType":"bytes","name":"data","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"req","type":"bytes"}],"name":"query","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/wasmd/legacy/v520/wasmd.go b/precompiles/wasmd/legacy/v520/wasmd.go new file mode 100644 index 0000000000..e94a6c7f7e --- /dev/null +++ b/precompiles/wasmd/legacy/v520/wasmd.go @@ -0,0 +1,503 @@ +package v520 + +import ( + "bytes" + "embed" + "encoding/json" + "errors" + "fmt" + "math/big" + + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + 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/tracing" + "github.com/ethereum/go-ethereum/core/vm" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v520" + "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/x/evm/state" +) + +const ( + InstantiateMethod = "instantiate" + ExecuteMethod = "execute" + ExecuteBatchMethod = "execute_batch" + QueryMethod = "query" +) + +const WasmdAddress = "0x0000000000000000000000000000000000001002" + +var _ vm.PrecompiledContract = &Precompile{} +var _ vm.DynamicGasPrecompiledContract = &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.WasmdKeeper + wasmdViewKeeper pcommon.WasmdViewKeeper + address common.Address + + InstantiateID []byte + ExecuteID []byte + ExecuteBatchID []byte + QueryID []byte +} + +type ExecuteMsg struct { + ContractAddress string `json:"contractAddress"` + Msg []byte `json:"msg"` + Coins []byte `json:"coins"` +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper, wasmdKeeper pcommon.WasmdKeeper, wasmdViewKeeper pcommon.WasmdViewKeeper, bankKeeper pcommon.BankKeeper) (*Precompile, error) { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + return nil, fmt.Errorf("error loading the staking ABI %s", err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + return nil, err + } + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + wasmdKeeper: wasmdKeeper, + wasmdViewKeeper: wasmdViewKeeper, + evmKeeper: evmKeeper, + bankKeeper: bankKeeper, + address: common.HexToAddress(WasmdAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case InstantiateMethod: + p.InstantiateID = m.ID + case ExecuteMethod: + p.ExecuteID = m.ID + case ExecuteBatchMethod: + p.ExecuteBatchID = m.ID + case QueryMethod: + p.QueryID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return pcommon.UnknownMethodCallGas + } + + method, err := p.ABI.MethodById(methodID) + if err != nil { + // This should never happen since this method is going to fail during Run + return pcommon.UnknownMethodCallGas + } + + return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) +} + +func (Precompile) IsTransaction(method string) bool { + switch method { + case ExecuteMethod: + return true + case ExecuteBatchMethod: + return true + case InstantiateMethod: + return true + default: + return false + } +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "wasmd" +} + +func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, 0, err + } + gasMultipler := p.evmKeeper.GetPriorityNormalizer(ctx) + gasLimitBigInt := sdk.NewDecFromInt(sdk.NewIntFromUint64(suppliedGas)).Mul(gasMultipler).TruncateInt().BigInt() + if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { + gasLimitBigInt = utils.BigMaxU64 + } + ctx = ctx.WithGasMeter(sdk.NewGasMeterWithMultiplier(ctx, gasLimitBigInt.Uint64())) + + switch method.Name { + case InstantiateMethod: + return p.instantiate(ctx, method, caller, callingContract, args, value, readOnly) + case ExecuteMethod: + return p.execute(ctx, method, caller, callingContract, args, value, readOnly) + case ExecuteBatchMethod: + return p.execute_batch(ctx, method, caller, callingContract, args, value, readOnly) + case QueryMethod: + return p.query(ctx, method, args, value) + } + return +} + +func (p Precompile) Run(*vm.EVM, common.Address, common.Address, []byte, *big.Int, bool, bool) ([]byte, error) { + panic("static gas Run is not implemented for dynamic gas precompile") +} + +func (p Precompile) instantiate(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call instantiate from staticcall") + return + } + if err := pcommon.ValidateArgsLength(args, 5); err != nil { + rerr = err + return + } + if caller.Cmp(callingContract) != 0 { + rerr = errors.New("cannot delegatecall instantiate") + return + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + codeID := args[0].(uint64) + creatorAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + rerr = fmt.Errorf("creator %s is not associated", caller.Hex()) + return + } + var adminAddr sdk.AccAddress + adminAddrStr := args[1].(string) + if len(adminAddrStr) > 0 { + adminAddrDecoded, err := sdk.AccAddressFromBech32(adminAddrStr) + if err != nil { + rerr = err + return + } + adminAddr = adminAddrDecoded + } + msg := args[2].([]byte) + label := args[3].(string) + coins := sdk.NewCoins() + coinsBz := args[4].([]byte) + + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { + rerr = errors.New("coin amount must equal value specified") + return + } + + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgInstantiate := wasmtypes.MsgInstantiateContract{ + Sender: creatorAddr.String(), + CodeID: codeID, + Label: label, + Funds: coins, + Msg: msg, + Admin: adminAddrStr, + } + + if err := msgInstantiate.ValidateBasic(); err != nil { + rerr = err + return + } + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if value != nil && !useiAmt.IsZero() { + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), creatorAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + + addr, data, err := p.wasmdKeeper.Instantiate(ctx, codeID, creatorAddr, adminAddr, msg, label, coins) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(addr.String(), data) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p Precompile) execute_batch(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call execute from staticcall") + return + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + rerr = err + return + } + + executeMsgs := args[0].([]struct { + ContractAddress string `json:"contractAddress"` + Msg []byte `json:"msg"` + Coins []byte `json:"coins"` + }) + + responses := make([][]byte, 0, len(executeMsgs)) + + // validate coins add up to value + validateValue := big.NewInt(0) + for i := 0; i < len(executeMsgs); i++ { + executeMsg := ExecuteMsg(executeMsgs[i]) + coinsBz := executeMsg.Coins + coins := sdk.NewCoins() + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + messageAmount := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + validateValue.Add(validateValue, messageAmount) + } + // if validateValue is greater than zero, then value must be provided, and they must be equal + if (value == nil && validateValue.Sign() == 1) || (value != nil && validateValue.Cmp(value) != 0) { + rerr = errors.New("sum of coin amounts must equal value specified") + return + } + for i := 0; i < len(executeMsgs); i++ { + executeMsg := ExecuteMsg(executeMsgs[i]) + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + contractAddrStr := executeMsg.ContractAddress + if caller.Cmp(callingContract) != 0 { + erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) + erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) + if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { + return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) + } + } + + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + senderAddr := p.evmKeeper.GetSeiAddressOrDefault(ctx, caller) + msg := executeMsg.Msg + coinsBz := executeMsg.Coins + coins := sdk.NewCoins() + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if value != nil && !useiAmt.IsZero() { + // process coin amount from the value provided + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + value.Sub(value, useiAmtAsWei) + if value.Sign() == -1 { + rerr = errors.New("insufficient value provided for payment") + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgExecute := wasmtypes.MsgExecuteContract{ + Sender: senderAddr.String(), + Contract: contractAddr.String(), + Msg: msg, + Funds: coins, + } + if err := msgExecute.ValidateBasic(); err != nil { + rerr = err + return + } + + res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) + if err != nil { + rerr = err + return + } + responses = append(responses, res) + } + if value != nil && value.Sign() != 0 { + rerr = errors.New("value remaining after execution, must match provided amounts exactly") + return + } + ret, rerr = method.Outputs.Pack(responses) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p Precompile) execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call execute from staticcall") + return + } + if err := pcommon.ValidateArgsLength(args, 3); err != nil { + rerr = err + return + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + contractAddrStr := args[0].(string) + if caller.Cmp(callingContract) != 0 { + erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) + erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) + if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { + return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) + } + } + // addresses will be sent in Sei format + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + senderAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + rerr = fmt.Errorf("sender %s is not associated", caller.Hex()) + return + } + msg := args[1].([]byte) + coins := sdk.NewCoins() + coinsBz := args[2].([]byte) + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { + rerr = errors.New("coin amount must equal value specified") + return + } + + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgExecute := wasmtypes.MsgExecuteContract{ + Sender: senderAddr.String(), + Contract: contractAddr.String(), + Msg: msg, + Funds: coins, + } + + if err := msgExecute.ValidateBasic(); err != nil { + rerr = err + return + } + + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if value != nil && !useiAmt.IsZero() { + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(res) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p Precompile) query(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if err := pcommon.ValidateNonPayable(value); err != nil { + rerr = err + return + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + rerr = err + return + } + + contractAddrStr := args[0].(string) + // addresses will be sent in Sei format + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + req := args[1].([]byte) + + rawContractMessage := wasmtypes.RawContractMessage(req) + if err := rawContractMessage.ValidateBasic(); err != nil { + rerr = err + return + } + + res, err := p.wasmdViewKeeper.QuerySmart(ctx, contractAddr, req) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(res) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} diff --git a/precompiles/wasmd/legacy/v522/abi.json b/precompiles/wasmd/legacy/v522/abi.json new file mode 100644 index 0000000000..c5e5b1b3f6 --- /dev/null +++ b/precompiles/wasmd/legacy/v522/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"execute","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"internalType":"struct IWasmd.ExecuteMsg[]","name":"executeMsgs","type":"tuple[]"}],"name":"execute_batch","outputs":[{"internalType":"bytes[]","name":"responses","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint64","name":"codeID","type":"uint64"},{"internalType":"string","name":"admin","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"string","name":"label","type":"string"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"instantiate","outputs":[{"internalType":"string","name":"contractAddr","type":"string"},{"internalType":"bytes","name":"data","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"req","type":"bytes"}],"name":"query","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/wasmd/legacy/v522/wasmd.go b/precompiles/wasmd/legacy/v522/wasmd.go new file mode 100644 index 0000000000..1b0abfddb3 --- /dev/null +++ b/precompiles/wasmd/legacy/v522/wasmd.go @@ -0,0 +1,508 @@ +package v522 + +import ( + "bytes" + "embed" + "encoding/json" + "errors" + "fmt" + "math/big" + + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + 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/tracing" + "github.com/ethereum/go-ethereum/core/vm" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v530" + "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/x/evm/state" +) + +const ( + InstantiateMethod = "instantiate" + ExecuteMethod = "execute" + ExecuteBatchMethod = "execute_batch" + QueryMethod = "query" +) + +const WasmdAddress = "0x0000000000000000000000000000000000001002" + +var _ vm.PrecompiledContract = &Precompile{} +var _ vm.DynamicGasPrecompiledContract = &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.WasmdKeeper + wasmdViewKeeper pcommon.WasmdViewKeeper + address common.Address + + InstantiateID []byte + ExecuteID []byte + ExecuteBatchID []byte + QueryID []byte +} + +type ExecuteMsg struct { + ContractAddress string `json:"contractAddress"` + Msg []byte `json:"msg"` + Coins []byte `json:"coins"` +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper, wasmdKeeper pcommon.WasmdKeeper, wasmdViewKeeper pcommon.WasmdViewKeeper, bankKeeper pcommon.BankKeeper) (*Precompile, error) { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + return nil, fmt.Errorf("error loading the staking ABI %s", err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + return nil, err + } + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + wasmdKeeper: wasmdKeeper, + wasmdViewKeeper: wasmdViewKeeper, + evmKeeper: evmKeeper, + bankKeeper: bankKeeper, + address: common.HexToAddress(WasmdAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case InstantiateMethod: + p.InstantiateID = m.ID + case ExecuteMethod: + p.ExecuteID = m.ID + case ExecuteBatchMethod: + p.ExecuteBatchID = m.ID + case QueryMethod: + p.QueryID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return pcommon.UnknownMethodCallGas + } + + method, err := p.ABI.MethodById(methodID) + if err != nil { + // This should never happen since this method is going to fail during Run + return pcommon.UnknownMethodCallGas + } + + return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) +} + +func (Precompile) IsTransaction(method string) bool { + switch method { + case ExecuteMethod: + return true + case ExecuteBatchMethod: + return true + case InstantiateMethod: + return true + default: + return false + } +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "wasmd" +} + +func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { + defer func() { + if err != nil { + evm.StateDB.(*state.DBImpl).SetPrecompileError(err) + } + }() + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, 0, err + } + gasMultipler := p.evmKeeper.GetPriorityNormalizer(ctx) + gasLimitBigInt := sdk.NewDecFromInt(sdk.NewIntFromUint64(suppliedGas)).Mul(gasMultipler).TruncateInt().BigInt() + if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { + gasLimitBigInt = utils.BigMaxU64 + } + ctx = ctx.WithGasMeter(sdk.NewGasMeterWithMultiplier(ctx, gasLimitBigInt.Uint64())) + + switch method.Name { + case InstantiateMethod: + return p.instantiate(ctx, method, caller, callingContract, args, value, readOnly) + case ExecuteMethod: + return p.execute(ctx, method, caller, callingContract, args, value, readOnly) + case ExecuteBatchMethod: + return p.execute_batch(ctx, method, caller, callingContract, args, value, readOnly) + case QueryMethod: + return p.query(ctx, method, args, value) + } + return +} + +func (p Precompile) Run(*vm.EVM, common.Address, common.Address, []byte, *big.Int, bool, bool) ([]byte, error) { + panic("static gas Run is not implemented for dynamic gas precompile") +} + +func (p Precompile) instantiate(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call instantiate from staticcall") + return + } + if err := pcommon.ValidateArgsLength(args, 5); err != nil { + rerr = err + return + } + if caller.Cmp(callingContract) != 0 { + rerr = errors.New("cannot delegatecall instantiate") + return + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + codeID := args[0].(uint64) + creatorAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + rerr = fmt.Errorf("creator %s is not associated", caller.Hex()) + return + } + var adminAddr sdk.AccAddress + adminAddrStr := args[1].(string) + if len(adminAddrStr) > 0 { + adminAddrDecoded, err := sdk.AccAddressFromBech32(adminAddrStr) + if err != nil { + rerr = err + return + } + adminAddr = adminAddrDecoded + } + msg := args[2].([]byte) + label := args[3].(string) + coins := sdk.NewCoins() + coinsBz := args[4].([]byte) + + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { + rerr = errors.New("coin amount must equal value specified") + return + } + + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgInstantiate := wasmtypes.MsgInstantiateContract{ + Sender: creatorAddr.String(), + CodeID: codeID, + Label: label, + Funds: coins, + Msg: msg, + Admin: adminAddrStr, + } + + if err := msgInstantiate.ValidateBasic(); err != nil { + rerr = err + return + } + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if value != nil && !useiAmt.IsZero() { + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), creatorAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + + addr, data, err := p.wasmdKeeper.Instantiate(ctx, codeID, creatorAddr, adminAddr, msg, label, coins) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(addr.String(), data) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p Precompile) execute_batch(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call execute from staticcall") + return + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + rerr = err + return + } + + executeMsgs := args[0].([]struct { + ContractAddress string `json:"contractAddress"` + Msg []byte `json:"msg"` + Coins []byte `json:"coins"` + }) + + responses := make([][]byte, 0, len(executeMsgs)) + + // validate coins add up to value + validateValue := big.NewInt(0) + for i := 0; i < len(executeMsgs); i++ { + executeMsg := ExecuteMsg(executeMsgs[i]) + coinsBz := executeMsg.Coins + coins := sdk.NewCoins() + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + messageAmount := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + validateValue.Add(validateValue, messageAmount) + } + // if validateValue is greater than zero, then value must be provided, and they must be equal + if (value == nil && validateValue.Sign() == 1) || (value != nil && validateValue.Cmp(value) != 0) { + rerr = errors.New("sum of coin amounts must equal value specified") + return + } + for i := 0; i < len(executeMsgs); i++ { + executeMsg := ExecuteMsg(executeMsgs[i]) + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + contractAddrStr := executeMsg.ContractAddress + if caller.Cmp(callingContract) != 0 { + erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) + erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) + if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { + return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) + } + } + + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + senderAddr := p.evmKeeper.GetSeiAddressOrDefault(ctx, caller) + msg := executeMsg.Msg + coinsBz := executeMsg.Coins + coins := sdk.NewCoins() + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if value != nil && !useiAmt.IsZero() { + // process coin amount from the value provided + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + value.Sub(value, useiAmtAsWei) + if value.Sign() == -1 { + rerr = errors.New("insufficient value provided for payment") + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgExecute := wasmtypes.MsgExecuteContract{ + Sender: senderAddr.String(), + Contract: contractAddr.String(), + Msg: msg, + Funds: coins, + } + if err := msgExecute.ValidateBasic(); err != nil { + rerr = err + return + } + + res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) + if err != nil { + rerr = err + return + } + responses = append(responses, res) + } + if value != nil && value.Sign() != 0 { + rerr = errors.New("value remaining after execution, must match provided amounts exactly") + return + } + ret, rerr = method.Outputs.Pack(responses) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p Precompile) execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call execute from staticcall") + return + } + if err := pcommon.ValidateArgsLength(args, 3); err != nil { + rerr = err + return + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + contractAddrStr := args[0].(string) + if caller.Cmp(callingContract) != 0 { + erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) + erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) + if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { + return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) + } + } + // addresses will be sent in Sei format + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + senderAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + rerr = fmt.Errorf("sender %s is not associated", caller.Hex()) + return + } + msg := args[1].([]byte) + coins := sdk.NewCoins() + coinsBz := args[2].([]byte) + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { + rerr = errors.New("coin amount must equal value specified") + return + } + + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgExecute := wasmtypes.MsgExecuteContract{ + Sender: senderAddr.String(), + Contract: contractAddr.String(), + Msg: msg, + Funds: coins, + } + + if err := msgExecute.ValidateBasic(); err != nil { + rerr = err + return + } + + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if value != nil && !useiAmt.IsZero() { + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(res) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p Precompile) query(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if err := pcommon.ValidateNonPayable(value); err != nil { + rerr = err + return + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + rerr = err + return + } + + contractAddrStr := args[0].(string) + // addresses will be sent in Sei format + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + req := args[1].([]byte) + + rawContractMessage := wasmtypes.RawContractMessage(req) + if err := rawContractMessage.ValidateBasic(); err != nil { + rerr = err + return + } + + res, err := p.wasmdViewKeeper.QuerySmart(ctx, contractAddr, req) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(res) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} diff --git a/precompiles/wasmd/legacy/v530/abi.json b/precompiles/wasmd/legacy/v530/abi.json new file mode 100644 index 0000000000..c5e5b1b3f6 --- /dev/null +++ b/precompiles/wasmd/legacy/v530/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"execute","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"internalType":"struct IWasmd.ExecuteMsg[]","name":"executeMsgs","type":"tuple[]"}],"name":"execute_batch","outputs":[{"internalType":"bytes[]","name":"responses","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint64","name":"codeID","type":"uint64"},{"internalType":"string","name":"admin","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"string","name":"label","type":"string"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"instantiate","outputs":[{"internalType":"string","name":"contractAddr","type":"string"},{"internalType":"bytes","name":"data","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"req","type":"bytes"}],"name":"query","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/wasmd/legacy/v530/wasmd.go b/precompiles/wasmd/legacy/v530/wasmd.go new file mode 100644 index 0000000000..55f5d0532f --- /dev/null +++ b/precompiles/wasmd/legacy/v530/wasmd.go @@ -0,0 +1,515 @@ +package v530 + +import ( + "bytes" + "embed" + "encoding/json" + "errors" + "fmt" + "math/big" + + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + 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/tracing" + "github.com/ethereum/go-ethereum/core/vm" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v530" + "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/x/evm/state" +) + +const ( + InstantiateMethod = "instantiate" + ExecuteMethod = "execute" + ExecuteBatchMethod = "execute_batch" + QueryMethod = "query" +) + +const WasmdAddress = "0x0000000000000000000000000000000000001002" + +var _ vm.PrecompiledContract = &Precompile{} +var _ vm.DynamicGasPrecompiledContract = &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.WasmdKeeper + wasmdViewKeeper pcommon.WasmdViewKeeper + address common.Address + + InstantiateID []byte + ExecuteID []byte + ExecuteBatchID []byte + QueryID []byte +} + +type ExecuteMsg struct { + ContractAddress string `json:"contractAddress"` + Msg []byte `json:"msg"` + Coins []byte `json:"coins"` +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper, wasmdKeeper pcommon.WasmdKeeper, wasmdViewKeeper pcommon.WasmdViewKeeper, bankKeeper pcommon.BankKeeper) (*Precompile, error) { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + return nil, fmt.Errorf("error loading the staking ABI %s", err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + return nil, err + } + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + wasmdKeeper: wasmdKeeper, + wasmdViewKeeper: wasmdViewKeeper, + evmKeeper: evmKeeper, + bankKeeper: bankKeeper, + address: common.HexToAddress(WasmdAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case InstantiateMethod: + p.InstantiateID = m.ID + case ExecuteMethod: + p.ExecuteID = m.ID + case ExecuteBatchMethod: + p.ExecuteBatchID = m.ID + case QueryMethod: + p.QueryID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return pcommon.UnknownMethodCallGas + } + + method, err := p.ABI.MethodById(methodID) + if err != nil { + // This should never happen since this method is going to fail during Run + return pcommon.UnknownMethodCallGas + } + + return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) +} + +func (Precompile) IsTransaction(method string) bool { + switch method { + case ExecuteMethod: + return true + case ExecuteBatchMethod: + return true + case InstantiateMethod: + return true + default: + return false + } +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "wasmd" +} + +func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { + defer func() { + if err != nil { + evm.StateDB.(*state.DBImpl).SetPrecompileError(err) + } + }() + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, 0, err + } + if method.Name != QueryMethod && !ctx.IsEVM() { + return nil, 0, errors.New("sei does not support CW->EVM->CW call pattern") + } + gasMultipler := p.evmKeeper.GetPriorityNormalizer(ctx) + gasLimitBigInt := sdk.NewDecFromInt(sdk.NewIntFromUint64(suppliedGas)).Mul(gasMultipler).TruncateInt().BigInt() + if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { + gasLimitBigInt = utils.BigMaxU64 + } + ctx = ctx.WithGasMeter(sdk.NewGasMeterWithMultiplier(ctx, gasLimitBigInt.Uint64())) + + switch method.Name { + case InstantiateMethod: + return p.instantiate(ctx, method, caller, callingContract, args, value, readOnly) + case ExecuteMethod: + return p.execute(ctx, method, caller, callingContract, args, value, readOnly) + case ExecuteBatchMethod: + return p.executeBatch(ctx, method, caller, callingContract, args, value, readOnly) + case QueryMethod: + return p.query(ctx, method, args, value) + } + return +} + +func (p Precompile) Run(*vm.EVM, common.Address, common.Address, []byte, *big.Int, bool, bool) ([]byte, error) { + panic("static gas Run is not implemented for dynamic gas precompile") +} + +func (p Precompile) instantiate(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call instantiate from staticcall") + return + } + if err := pcommon.ValidateArgsLength(args, 5); err != nil { + rerr = err + return + } + if caller.Cmp(callingContract) != 0 { + rerr = errors.New("cannot delegatecall instantiate") + return + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + codeID := args[0].(uint64) + creatorAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + rerr = fmt.Errorf("creator %s is not associated", caller.Hex()) + return + } + var adminAddr sdk.AccAddress + adminAddrStr := args[1].(string) + if len(adminAddrStr) > 0 { + adminAddrDecoded, err := sdk.AccAddressFromBech32(adminAddrStr) + if err != nil { + rerr = err + return + } + adminAddr = adminAddrDecoded + } + msg := args[2].([]byte) + label := args[3].(string) + coins := sdk.NewCoins() + coinsBz := args[4].([]byte) + + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { + rerr = errors.New("coin amount must equal value specified") + return + } + + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgInstantiate := wasmtypes.MsgInstantiateContract{ + Sender: creatorAddr.String(), + CodeID: codeID, + Label: label, + Funds: coins, + Msg: msg, + Admin: adminAddrStr, + } + + if err := msgInstantiate.ValidateBasic(); err != nil { + rerr = err + return + } + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if value != nil && !useiAmt.IsZero() { + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), creatorAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + + addr, data, err := p.wasmdKeeper.Instantiate(ctx, codeID, creatorAddr, adminAddr, msg, label, coins) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(addr.String(), data) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p Precompile) executeBatch(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call execute from staticcall") + return + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + rerr = err + return + } + + executeMsgs := args[0].([]struct { + ContractAddress string `json:"contractAddress"` + Msg []byte `json:"msg"` + Coins []byte `json:"coins"` + }) + + responses := make([][]byte, 0, len(executeMsgs)) + + // validate coins add up to value + validateValue := big.NewInt(0) + for i := 0; i < len(executeMsgs); i++ { + executeMsg := ExecuteMsg(executeMsgs[i]) + coinsBz := executeMsg.Coins + coins := sdk.NewCoins() + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + messageAmount := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + validateValue.Add(validateValue, messageAmount) + } + // if validateValue is greater than zero, then value must be provided, and they must be equal + if (value == nil && validateValue.Sign() == 1) || (value != nil && validateValue.Cmp(value) != 0) { + rerr = errors.New("sum of coin amounts must equal value specified") + return + } + for i := 0; i < len(executeMsgs); i++ { + executeMsg := ExecuteMsg(executeMsgs[i]) + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + contractAddrStr := executeMsg.ContractAddress + if caller.Cmp(callingContract) != 0 { + erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) + erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) + if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { + return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) + } + } + + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + senderAddr, senderAssociated := p.evmKeeper.GetSeiAddress(ctx, caller) + if !senderAssociated { + rerr = fmt.Errorf("sender %s is not associated", caller.Hex()) + return + } + msg := executeMsg.Msg + coinsBz := executeMsg.Coins + coins := sdk.NewCoins() + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if value != nil && !useiAmt.IsZero() { + // process coin amount from the value provided + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + value.Sub(value, useiAmtAsWei) + if value.Sign() == -1 { + rerr = errors.New("insufficient value provided for payment") + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgExecute := wasmtypes.MsgExecuteContract{ + Sender: senderAddr.String(), + Contract: contractAddr.String(), + Msg: msg, + Funds: coins, + } + if err := msgExecute.ValidateBasic(); err != nil { + rerr = err + return + } + + res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) + if err != nil { + rerr = err + return + } + responses = append(responses, res) + } + if value != nil && value.Sign() != 0 { + rerr = errors.New("value remaining after execution, must match provided amounts exactly") + return + } + ret, rerr = method.Outputs.Pack(responses) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p Precompile) execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call execute from staticcall") + return + } + if err := pcommon.ValidateArgsLength(args, 3); err != nil { + rerr = err + return + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + contractAddrStr := args[0].(string) + if caller.Cmp(callingContract) != 0 { + erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) + erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) + if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { + return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) + } + } + // addresses will be sent in Sei format + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + senderAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + rerr = fmt.Errorf("sender %s is not associated", caller.Hex()) + return + } + msg := args[1].([]byte) + coins := sdk.NewCoins() + coinsBz := args[2].([]byte) + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { + rerr = errors.New("coin amount must equal value specified") + return + } + + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgExecute := wasmtypes.MsgExecuteContract{ + Sender: senderAddr.String(), + Contract: contractAddr.String(), + Msg: msg, + Funds: coins, + } + + if err := msgExecute.ValidateBasic(); err != nil { + rerr = err + return + } + + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if value != nil && !useiAmt.IsZero() { + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(res) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p Precompile) query(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if err := pcommon.ValidateNonPayable(value); err != nil { + rerr = err + return + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + rerr = err + return + } + + contractAddrStr := args[0].(string) + // addresses will be sent in Sei format + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + req := args[1].([]byte) + + rawContractMessage := wasmtypes.RawContractMessage(req) + if err := rawContractMessage.ValidateBasic(); err != nil { + rerr = err + return + } + + res, err := p.wasmdViewKeeper.QuerySmart(ctx, contractAddr, req) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(res) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} diff --git a/precompiles/wasmd/legacy/v555/abi.json b/precompiles/wasmd/legacy/v555/abi.json new file mode 100644 index 0000000000..c5e5b1b3f6 --- /dev/null +++ b/precompiles/wasmd/legacy/v555/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"execute","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"internalType":"struct IWasmd.ExecuteMsg[]","name":"executeMsgs","type":"tuple[]"}],"name":"execute_batch","outputs":[{"internalType":"bytes[]","name":"responses","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint64","name":"codeID","type":"uint64"},{"internalType":"string","name":"admin","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"string","name":"label","type":"string"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"instantiate","outputs":[{"internalType":"string","name":"contractAddr","type":"string"},{"internalType":"bytes","name":"data","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"req","type":"bytes"}],"name":"query","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/wasmd/legacy/v555/wasmd.go b/precompiles/wasmd/legacy/v555/wasmd.go new file mode 100644 index 0000000000..1966b369d5 --- /dev/null +++ b/precompiles/wasmd/legacy/v555/wasmd.go @@ -0,0 +1,522 @@ +package v555 + +import ( + "bytes" + "embed" + "encoding/json" + "errors" + "fmt" + "math/big" + + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + 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/tracing" + "github.com/ethereum/go-ethereum/core/vm" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v555" + "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/x/evm/state" +) + +const ( + InstantiateMethod = "instantiate" + ExecuteMethod = "execute" + ExecuteBatchMethod = "execute_batch" + QueryMethod = "query" +) + +const WasmdAddress = "0x0000000000000000000000000000000000001002" + +var _ vm.PrecompiledContract = &Precompile{} +var _ vm.DynamicGasPrecompiledContract = &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.WasmdKeeper + wasmdViewKeeper pcommon.WasmdViewKeeper + address common.Address + + InstantiateID []byte + ExecuteID []byte + ExecuteBatchID []byte + QueryID []byte +} + +type ExecuteMsg struct { + ContractAddress string `json:"contractAddress"` + Msg []byte `json:"msg"` + Coins []byte `json:"coins"` +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper, wasmdKeeper pcommon.WasmdKeeper, wasmdViewKeeper pcommon.WasmdViewKeeper, bankKeeper pcommon.BankKeeper) (*Precompile, error) { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + return nil, fmt.Errorf("error loading the staking ABI %s", err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + return nil, err + } + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + wasmdKeeper: wasmdKeeper, + wasmdViewKeeper: wasmdViewKeeper, + evmKeeper: evmKeeper, + bankKeeper: bankKeeper, + address: common.HexToAddress(WasmdAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case InstantiateMethod: + p.InstantiateID = m.ID + case ExecuteMethod: + p.ExecuteID = m.ID + case ExecuteBatchMethod: + p.ExecuteBatchID = m.ID + case QueryMethod: + p.QueryID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return pcommon.UnknownMethodCallGas + } + + method, err := p.ABI.MethodById(methodID) + if err != nil { + // This should never happen since this method is going to fail during Run + return pcommon.UnknownMethodCallGas + } + + return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) +} + +func (Precompile) IsTransaction(method string) bool { + switch method { + case ExecuteMethod: + return true + case ExecuteBatchMethod: + return true + case InstantiateMethod: + return true + default: + return false + } +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) GetName() string { + return "wasmd" +} + +func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { + defer func() { + if err != nil { + evm.StateDB.(*state.DBImpl).SetPrecompileError(err) + } + }() + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, 0, err + } + if method.Name != QueryMethod && !ctx.IsEVM() { + return nil, 0, errors.New("sei does not support CW->EVM->CW call pattern") + } + gasMultipler := p.evmKeeper.GetPriorityNormalizer(ctx) + gasLimitBigInt := sdk.NewDecFromInt(sdk.NewIntFromUint64(suppliedGas)).Mul(gasMultipler).TruncateInt().BigInt() + if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { + gasLimitBigInt = utils.BigMaxU64 + } + ctx = ctx.WithGasMeter(sdk.NewGasMeterWithMultiplier(ctx, gasLimitBigInt.Uint64())) + + switch method.Name { + case InstantiateMethod: + return p.instantiate(ctx, method, caller, callingContract, args, value, readOnly) + case ExecuteMethod: + return p.execute(ctx, method, caller, callingContract, args, value, readOnly) + case ExecuteBatchMethod: + return p.executeBatch(ctx, method, caller, callingContract, args, value, readOnly) + case QueryMethod: + return p.query(ctx, method, args, value) + } + return +} + +func (p Precompile) Run(*vm.EVM, common.Address, common.Address, []byte, *big.Int, bool, bool) ([]byte, error) { + panic("static gas Run is not implemented for dynamic gas precompile") +} + +func (p Precompile) instantiate(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call instantiate from staticcall") + return + } + if err := pcommon.ValidateArgsLength(args, 5); err != nil { + rerr = err + return + } + if caller.Cmp(callingContract) != 0 { + rerr = errors.New("cannot delegatecall instantiate") + return + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + codeID := args[0].(uint64) + creatorAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + rerr = fmt.Errorf("creator %s is not associated", caller.Hex()) + return + } + var adminAddr sdk.AccAddress + adminAddrStr := args[1].(string) + if len(adminAddrStr) > 0 { + adminAddrDecoded, err := sdk.AccAddressFromBech32(adminAddrStr) + if err != nil { + rerr = err + return + } + adminAddr = adminAddrDecoded + } + msg := args[2].([]byte) + label := args[3].(string) + coins := sdk.NewCoins() + coinsBz := args[4].([]byte) + + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { + rerr = errors.New("coin amount must equal value specified") + return + } + + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgInstantiate := wasmtypes.MsgInstantiateContract{ + Sender: creatorAddr.String(), + CodeID: codeID, + Label: label, + Funds: coins, + Msg: msg, + Admin: adminAddrStr, + } + + if err := msgInstantiate.ValidateBasic(); err != nil { + rerr = err + return + } + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if value != nil && !useiAmt.IsZero() { + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), creatorAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + + addr, data, err := p.wasmdKeeper.Instantiate(ctx, codeID, creatorAddr, adminAddr, msg, label, coins) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(addr.String(), data) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p Precompile) executeBatch(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call execute from staticcall") + return + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + rerr = err + return + } + + executeMsgs := args[0].([]struct { + ContractAddress string `json:"contractAddress"` + Msg []byte `json:"msg"` + Coins []byte `json:"coins"` + }) + + responses := make([][]byte, 0, len(executeMsgs)) + + // validate coins add up to value + validateValue := big.NewInt(0) + for i := 0; i < len(executeMsgs); i++ { + executeMsg := ExecuteMsg(executeMsgs[i]) + coinsBz := executeMsg.Coins + coins := sdk.NewCoins() + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + messageAmount := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + validateValue.Add(validateValue, messageAmount) + } + // if validateValue is greater than zero, then value must be provided, and they must be equal + if (value == nil && validateValue.Sign() == 1) || (value != nil && validateValue.Cmp(value) != 0) { + rerr = errors.New("sum of coin amounts must equal value specified") + return + } + // Copy to avoid modifying the original value + var valueCopy *big.Int + if value != nil { + valueCopy = new(big.Int).Set(value) + } else { + valueCopy = value + } + for i := 0; i < len(executeMsgs); i++ { + executeMsg := ExecuteMsg(executeMsgs[i]) + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + contractAddrStr := executeMsg.ContractAddress + if caller.Cmp(callingContract) != 0 { + erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) + erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) + if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { + return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) + } + } + + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + senderAddr, senderAssociated := p.evmKeeper.GetSeiAddress(ctx, caller) + if !senderAssociated { + rerr = fmt.Errorf("sender %s is not associated", caller.Hex()) + return + } + msg := executeMsg.Msg + coinsBz := executeMsg.Coins + coins := sdk.NewCoins() + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if valueCopy != nil && !useiAmt.IsZero() { + // process coin amount from the value provided + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + valueCopy.Sub(valueCopy, useiAmtAsWei) + if valueCopy.Sign() == -1 { + rerr = errors.New("insufficient value provided for payment") + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgExecute := wasmtypes.MsgExecuteContract{ + Sender: senderAddr.String(), + Contract: contractAddr.String(), + Msg: msg, + Funds: coins, + } + if err := msgExecute.ValidateBasic(); err != nil { + rerr = err + return + } + + res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) + if err != nil { + rerr = err + return + } + responses = append(responses, res) + } + if valueCopy != nil && valueCopy.Sign() != 0 { + rerr = errors.New("value remaining after execution, must match provided amounts exactly") + return + } + ret, rerr = method.Outputs.Pack(responses) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p Precompile) execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call execute from staticcall") + return + } + if err := pcommon.ValidateArgsLength(args, 3); err != nil { + rerr = err + return + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + contractAddrStr := args[0].(string) + if caller.Cmp(callingContract) != 0 { + erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) + erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) + if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { + return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) + } + } + // addresses will be sent in Sei format + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + senderAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + rerr = fmt.Errorf("sender %s is not associated", caller.Hex()) + return + } + msg := args[1].([]byte) + coins := sdk.NewCoins() + coinsBz := args[2].([]byte) + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { + rerr = errors.New("coin amount must equal value specified") + return + } + + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgExecute := wasmtypes.MsgExecuteContract{ + Sender: senderAddr.String(), + Contract: contractAddr.String(), + Msg: msg, + Funds: coins, + } + + if err := msgExecute.ValidateBasic(); err != nil { + rerr = err + return + } + + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if value != nil && !useiAmt.IsZero() { + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(res) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p Precompile) query(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if err := pcommon.ValidateNonPayable(value); err != nil { + rerr = err + return + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + rerr = err + return + } + + contractAddrStr := args[0].(string) + // addresses will be sent in Sei format + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + req := args[1].([]byte) + + rawContractMessage := wasmtypes.RawContractMessage(req) + if err := rawContractMessage.ValidateBasic(); err != nil { + rerr = err + return + } + + res, err := p.wasmdViewKeeper.QuerySmart(ctx, contractAddr, req) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(res) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} diff --git a/precompiles/wasmd/legacy/v562/abi.json b/precompiles/wasmd/legacy/v562/abi.json new file mode 100644 index 0000000000..c5e5b1b3f6 --- /dev/null +++ b/precompiles/wasmd/legacy/v562/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"execute","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"internalType":"struct IWasmd.ExecuteMsg[]","name":"executeMsgs","type":"tuple[]"}],"name":"execute_batch","outputs":[{"internalType":"bytes[]","name":"responses","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint64","name":"codeID","type":"uint64"},{"internalType":"string","name":"admin","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"string","name":"label","type":"string"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"instantiate","outputs":[{"internalType":"string","name":"contractAddr","type":"string"},{"internalType":"bytes","name":"data","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"req","type":"bytes"}],"name":"query","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/wasmd/legacy/v562/wasmd.go b/precompiles/wasmd/legacy/v562/wasmd.go new file mode 100644 index 0000000000..503a7b99b4 --- /dev/null +++ b/precompiles/wasmd/legacy/v562/wasmd.go @@ -0,0 +1,463 @@ +package v562 + +import ( + "bytes" + "embed" + "encoding/json" + "errors" + "fmt" + "math/big" + + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + 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/legacy/v562" + "github.com/sei-protocol/sei-chain/x/evm/state" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const ( + InstantiateMethod = "instantiate" + ExecuteMethod = "execute" + ExecuteBatchMethod = "execute_batch" + QueryMethod = "query" +) + +const WasmdAddress = "0x0000000000000000000000000000000000001002" + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + wasmdKeeper pcommon.WasmdKeeper + wasmdViewKeeper pcommon.WasmdViewKeeper + address common.Address + + InstantiateID []byte + ExecuteID []byte + ExecuteBatchID []byte + QueryID []byte +} + +type ExecuteMsg struct { + ContractAddress string `json:"contractAddress"` + Msg []byte `json:"msg"` + Coins []byte `json:"coins"` +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper, wasmdKeeper pcommon.WasmdKeeper, wasmdViewKeeper pcommon.WasmdViewKeeper, bankKeeper pcommon.BankKeeper) (*pcommon.DynamicGasPrecompile, error) { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + return nil, fmt.Errorf("error loading the wasmd ABI %s", err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + return nil, err + } + + executor := &PrecompileExecutor{ + wasmdKeeper: wasmdKeeper, + wasmdViewKeeper: wasmdViewKeeper, + evmKeeper: evmKeeper, + bankKeeper: bankKeeper, + address: common.HexToAddress(WasmdAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case InstantiateMethod: + executor.InstantiateID = m.ID + case ExecuteMethod: + executor.ExecuteID = m.ID + case ExecuteBatchMethod: + executor.ExecuteBatchID = m.ID + case QueryMethod: + executor.QueryID = m.ID + } + } + return pcommon.NewDynamicGasPrecompile(newAbi, executor, common.HexToAddress(WasmdAddress), "wasmd"), nil +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + if method.Name != QueryMethod && !ctx.IsEVM() { + return nil, 0, errors.New("sei does not support CW->EVM->CW call pattern") + } + switch method.Name { + case InstantiateMethod: + return p.instantiate(ctx, method, caller, callingContract, args, value, readOnly) + case ExecuteMethod: + return p.execute(ctx, method, caller, callingContract, args, value, readOnly) + case ExecuteBatchMethod: + return p.executeBatch(ctx, method, caller, callingContract, args, value, readOnly) + case QueryMethod: + return p.query(ctx, method, args, value) + } + return +} + +func (p PrecompileExecutor) EVMKeeper() pcommon.EVMKeeper { + return p.evmKeeper +} + +func (p PrecompileExecutor) instantiate(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call instantiate from staticcall") + return + } + if err := pcommon.ValidateArgsLength(args, 5); err != nil { + rerr = err + return + } + if caller.Cmp(callingContract) != 0 { + rerr = errors.New("cannot delegatecall instantiate") + return + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + codeID := args[0].(uint64) + creatorAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + rerr = types.NewAssociationMissingErr(caller.Hex()) + return + } + var adminAddr sdk.AccAddress + adminAddrStr := args[1].(string) + if len(adminAddrStr) > 0 { + adminAddrDecoded, err := sdk.AccAddressFromBech32(adminAddrStr) + if err != nil { + rerr = err + return + } + adminAddr = adminAddrDecoded + } + msg := args[2].([]byte) + label := args[3].(string) + coins := sdk.NewCoins() + coinsBz := args[4].([]byte) + + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { + rerr = errors.New("coin amount must equal value specified") + return + } + + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgInstantiate := wasmtypes.MsgInstantiateContract{ + Sender: creatorAddr.String(), + CodeID: codeID, + Label: label, + Funds: coins, + Msg: msg, + Admin: adminAddrStr, + } + + if err := msgInstantiate.ValidateBasic(); err != nil { + rerr = err + return + } + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if value != nil && !useiAmt.IsZero() { + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), creatorAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + + addr, data, err := p.wasmdKeeper.Instantiate(ctx, codeID, creatorAddr, adminAddr, msg, label, coins) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(addr.String(), data) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p PrecompileExecutor) executeBatch(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call execute from staticcall") + return + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + rerr = err + return + } + + executeMsgs := args[0].([]struct { + ContractAddress string `json:"contractAddress"` + Msg []byte `json:"msg"` + Coins []byte `json:"coins"` + }) + + responses := make([][]byte, 0, len(executeMsgs)) + + // validate coins add up to value + validateValue := big.NewInt(0) + for i := 0; i < len(executeMsgs); i++ { + executeMsg := ExecuteMsg(executeMsgs[i]) + coinsBz := executeMsg.Coins + coins := sdk.NewCoins() + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + messageAmount := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + validateValue.Add(validateValue, messageAmount) + } + // if validateValue is greater than zero, then value must be provided, and they must be equal + if (value == nil && validateValue.Sign() == 1) || (value != nil && validateValue.Cmp(value) != 0) { + rerr = errors.New("sum of coin amounts must equal value specified") + return + } + // Copy to avoid modifying the original value + var valueCopy *big.Int + if value != nil { + valueCopy = new(big.Int).Set(value) + } else { + valueCopy = value + } + for i := 0; i < len(executeMsgs); i++ { + executeMsg := ExecuteMsg(executeMsgs[i]) + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + contractAddrStr := executeMsg.ContractAddress + if caller.Cmp(callingContract) != 0 { + erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) + erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) + if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { + return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) + } + } + + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + senderAddr, senderAssociated := p.evmKeeper.GetSeiAddress(ctx, caller) + if !senderAssociated { + rerr = types.NewAssociationMissingErr(caller.Hex()) + return + } + msg := executeMsg.Msg + coinsBz := executeMsg.Coins + coins := sdk.NewCoins() + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if valueCopy != nil && !useiAmt.IsZero() { + // process coin amount from the value provided + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + valueCopy.Sub(valueCopy, useiAmtAsWei) + if valueCopy.Sign() == -1 { + rerr = errors.New("insufficient value provided for payment") + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgExecute := wasmtypes.MsgExecuteContract{ + Sender: senderAddr.String(), + Contract: contractAddr.String(), + Msg: msg, + Funds: coins, + } + if err := msgExecute.ValidateBasic(); err != nil { + rerr = err + return + } + + res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) + if err != nil { + rerr = err + return + } + responses = append(responses, res) + } + if valueCopy != nil && valueCopy.Sign() != 0 { + rerr = errors.New("value remaining after execution, must match provided amounts exactly") + return + } + ret, rerr = method.Outputs.Pack(responses) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p PrecompileExecutor) execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call execute from staticcall") + return + } + if err := pcommon.ValidateArgsLength(args, 3); err != nil { + rerr = err + return + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + contractAddrStr := args[0].(string) + if caller.Cmp(callingContract) != 0 { + erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) + erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) + if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { + return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) + } + } + // addresses will be sent in Sei format + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + senderAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + rerr = types.NewAssociationMissingErr(caller.Hex()) + return + } + msg := args[1].([]byte) + coins := sdk.NewCoins() + coinsBz := args[2].([]byte) + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { + rerr = errors.New("coin amount must equal value specified") + return + } + + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgExecute := wasmtypes.MsgExecuteContract{ + Sender: senderAddr.String(), + Contract: contractAddr.String(), + Msg: msg, + Funds: coins, + } + + if err := msgExecute.ValidateBasic(); err != nil { + rerr = err + return + } + + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if value != nil && !useiAmt.IsZero() { + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(res) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p PrecompileExecutor) query(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if err := pcommon.ValidateNonPayable(value); err != nil { + rerr = err + return + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + rerr = err + return + } + + contractAddrStr := args[0].(string) + // addresses will be sent in Sei format + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + req := args[1].([]byte) + + rawContractMessage := wasmtypes.RawContractMessage(req) + if err := rawContractMessage.ValidateBasic(); err != nil { + rerr = err + return + } + + res, err := p.wasmdViewKeeper.QuerySmart(ctx, contractAddr, req) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(res) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} diff --git a/precompiles/wasmd/legacy/v575/abi.json b/precompiles/wasmd/legacy/v575/abi.json new file mode 100644 index 0000000000..c5e5b1b3f6 --- /dev/null +++ b/precompiles/wasmd/legacy/v575/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"execute","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"internalType":"struct IWasmd.ExecuteMsg[]","name":"executeMsgs","type":"tuple[]"}],"name":"execute_batch","outputs":[{"internalType":"bytes[]","name":"responses","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint64","name":"codeID","type":"uint64"},{"internalType":"string","name":"admin","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"string","name":"label","type":"string"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"instantiate","outputs":[{"internalType":"string","name":"contractAddr","type":"string"},{"internalType":"bytes","name":"data","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"req","type":"bytes"}],"name":"query","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/wasmd/legacy/v575/wasmd.go b/precompiles/wasmd/legacy/v575/wasmd.go new file mode 100644 index 0000000000..fd1ed7b4f9 --- /dev/null +++ b/precompiles/wasmd/legacy/v575/wasmd.go @@ -0,0 +1,454 @@ +package v575 + +import ( + "embed" + "encoding/json" + "errors" + "fmt" + "math/big" + + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + 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/legacy/v575" + "github.com/sei-protocol/sei-chain/x/evm/state" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const ( + InstantiateMethod = "instantiate" + ExecuteMethod = "execute" + ExecuteBatchMethod = "execute_batch" + QueryMethod = "query" +) + +const WasmdAddress = "0x0000000000000000000000000000000000001002" + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + wasmdKeeper pcommon.WasmdKeeper + wasmdViewKeeper pcommon.WasmdViewKeeper + address common.Address + + InstantiateID []byte + ExecuteID []byte + ExecuteBatchID []byte + QueryID []byte +} + +type ExecuteMsg struct { + ContractAddress string `json:"contractAddress"` + Msg []byte `json:"msg"` + Coins []byte `json:"coins"` +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper, wasmdKeeper pcommon.WasmdKeeper, wasmdViewKeeper pcommon.WasmdViewKeeper, bankKeeper pcommon.BankKeeper) (*pcommon.DynamicGasPrecompile, error) { + newAbi := pcommon.MustGetABI(f, "abi.json") + + executor := &PrecompileExecutor{ + wasmdKeeper: wasmdKeeper, + wasmdViewKeeper: wasmdViewKeeper, + evmKeeper: evmKeeper, + bankKeeper: bankKeeper, + address: common.HexToAddress(WasmdAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case InstantiateMethod: + executor.InstantiateID = m.ID + case ExecuteMethod: + executor.ExecuteID = m.ID + case ExecuteBatchMethod: + executor.ExecuteBatchID = m.ID + case QueryMethod: + executor.QueryID = m.ID + } + } + return pcommon.NewDynamicGasPrecompile(newAbi, executor, common.HexToAddress(WasmdAddress), "wasmd"), nil +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + if method.Name != QueryMethod && !ctx.IsEVM() { + return nil, 0, errors.New("sei does not support CW->EVM->CW call pattern") + } + switch method.Name { + case InstantiateMethod: + return p.instantiate(ctx, method, caller, callingContract, args, value, readOnly) + case ExecuteMethod: + return p.execute(ctx, method, caller, callingContract, args, value, readOnly) + case ExecuteBatchMethod: + return p.executeBatch(ctx, method, caller, callingContract, args, value, readOnly) + case QueryMethod: + return p.query(ctx, method, args, value) + } + return +} + +func (p PrecompileExecutor) EVMKeeper() pcommon.EVMKeeper { + return p.evmKeeper +} + +func (p PrecompileExecutor) instantiate(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call instantiate from staticcall") + return + } + if err := pcommon.ValidateArgsLength(args, 5); err != nil { + rerr = err + return + } + if caller.Cmp(callingContract) != 0 { + rerr = errors.New("cannot delegatecall instantiate") + return + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + codeID := args[0].(uint64) + creatorAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + rerr = types.NewAssociationMissingErr(caller.Hex()) + return + } + var adminAddr sdk.AccAddress + adminAddrStr := args[1].(string) + if len(adminAddrStr) > 0 { + adminAddrDecoded, err := sdk.AccAddressFromBech32(adminAddrStr) + if err != nil { + rerr = err + return + } + adminAddr = adminAddrDecoded + } + msg := args[2].([]byte) + label := args[3].(string) + coins := sdk.NewCoins() + coinsBz := args[4].([]byte) + + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { + rerr = errors.New("coin amount must equal value specified") + return + } + + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgInstantiate := wasmtypes.MsgInstantiateContract{ + Sender: creatorAddr.String(), + CodeID: codeID, + Label: label, + Funds: coins, + Msg: msg, + Admin: adminAddrStr, + } + + if err := msgInstantiate.ValidateBasic(); err != nil { + rerr = err + return + } + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if value != nil && !useiAmt.IsZero() { + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), creatorAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + + addr, data, err := p.wasmdKeeper.Instantiate(ctx, codeID, creatorAddr, adminAddr, msg, label, coins) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(addr.String(), data) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p PrecompileExecutor) executeBatch(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call execute from staticcall") + return + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + rerr = err + return + } + + executeMsgs := args[0].([]struct { + ContractAddress string `json:"contractAddress"` + Msg []byte `json:"msg"` + Coins []byte `json:"coins"` + }) + + responses := make([][]byte, 0, len(executeMsgs)) + + // validate coins add up to value + validateValue := big.NewInt(0) + for i := 0; i < len(executeMsgs); i++ { + executeMsg := ExecuteMsg(executeMsgs[i]) + coinsBz := executeMsg.Coins + coins := sdk.NewCoins() + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + messageAmount := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + validateValue.Add(validateValue, messageAmount) + } + // if validateValue is greater than zero, then value must be provided, and they must be equal + if (value == nil && validateValue.Sign() == 1) || (value != nil && validateValue.Cmp(value) != 0) { + rerr = errors.New("sum of coin amounts must equal value specified") + return + } + // Copy to avoid modifying the original value + var valueCopy *big.Int + if value != nil { + valueCopy = new(big.Int).Set(value) + } else { + valueCopy = value + } + for i := 0; i < len(executeMsgs); i++ { + executeMsg := ExecuteMsg(executeMsgs[i]) + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + contractAddrStr := executeMsg.ContractAddress + if caller.Cmp(callingContract) != 0 { + erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) + erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) + if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { + return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) + } + } + + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + senderAddr, senderAssociated := p.evmKeeper.GetSeiAddress(ctx, caller) + if !senderAssociated { + rerr = types.NewAssociationMissingErr(caller.Hex()) + return + } + msg := executeMsg.Msg + coinsBz := executeMsg.Coins + coins := sdk.NewCoins() + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if valueCopy != nil && !useiAmt.IsZero() { + // process coin amount from the value provided + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + valueCopy.Sub(valueCopy, useiAmtAsWei) + if valueCopy.Sign() == -1 { + rerr = errors.New("insufficient value provided for payment") + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgExecute := wasmtypes.MsgExecuteContract{ + Sender: senderAddr.String(), + Contract: contractAddr.String(), + Msg: msg, + Funds: coins, + } + if err := msgExecute.ValidateBasic(); err != nil { + rerr = err + return + } + + res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) + if err != nil { + rerr = err + return + } + responses = append(responses, res) + } + if valueCopy != nil && valueCopy.Sign() != 0 { + rerr = errors.New("value remaining after execution, must match provided amounts exactly") + return + } + ret, rerr = method.Outputs.Pack(responses) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p PrecompileExecutor) execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call execute from staticcall") + return + } + if err := pcommon.ValidateArgsLength(args, 3); err != nil { + rerr = err + return + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + contractAddrStr := args[0].(string) + if caller.Cmp(callingContract) != 0 { + erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) + erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) + if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { + return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) + } + } + // addresses will be sent in Sei format + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + senderAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + rerr = types.NewAssociationMissingErr(caller.Hex()) + return + } + msg := args[1].([]byte) + coins := sdk.NewCoins() + coinsBz := args[2].([]byte) + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { + rerr = errors.New("coin amount must equal value specified") + return + } + + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgExecute := wasmtypes.MsgExecuteContract{ + Sender: senderAddr.String(), + Contract: contractAddr.String(), + Msg: msg, + Funds: coins, + } + + if err := msgExecute.ValidateBasic(); err != nil { + rerr = err + return + } + + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if value != nil && !useiAmt.IsZero() { + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(res) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p PrecompileExecutor) query(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if err := pcommon.ValidateNonPayable(value); err != nil { + rerr = err + return + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + rerr = err + return + } + + contractAddrStr := args[0].(string) + // addresses will be sent in Sei format + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + req := args[1].([]byte) + + rawContractMessage := wasmtypes.RawContractMessage(req) + if err := rawContractMessage.ValidateBasic(); err != nil { + rerr = err + return + } + + res, err := p.wasmdViewKeeper.QuerySmart(ctx, contractAddr, req) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(res) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} diff --git a/precompiles/wasmd/legacy/v580/abi.json b/precompiles/wasmd/legacy/v580/abi.json new file mode 100644 index 0000000000..c5e5b1b3f6 --- /dev/null +++ b/precompiles/wasmd/legacy/v580/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"execute","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"internalType":"struct IWasmd.ExecuteMsg[]","name":"executeMsgs","type":"tuple[]"}],"name":"execute_batch","outputs":[{"internalType":"bytes[]","name":"responses","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint64","name":"codeID","type":"uint64"},{"internalType":"string","name":"admin","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"string","name":"label","type":"string"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"instantiate","outputs":[{"internalType":"string","name":"contractAddr","type":"string"},{"internalType":"bytes","name":"data","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"req","type":"bytes"}],"name":"query","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/wasmd/legacy/v580/wasmd.go b/precompiles/wasmd/legacy/v580/wasmd.go new file mode 100644 index 0000000000..23379b7351 --- /dev/null +++ b/precompiles/wasmd/legacy/v580/wasmd.go @@ -0,0 +1,464 @@ +package v580 + +import ( + "embed" + "encoding/json" + "errors" + "fmt" + "math/big" + + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + 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/legacy/v580" + "github.com/sei-protocol/sei-chain/x/evm/state" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const ( + InstantiateMethod = "instantiate" + ExecuteMethod = "execute" + ExecuteBatchMethod = "execute_batch" + QueryMethod = "query" +) + +const WasmdAddress = "0x0000000000000000000000000000000000001002" + +var Address = common.HexToAddress(WasmdAddress) + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + wasmdKeeper pcommon.WasmdKeeper + wasmdViewKeeper pcommon.WasmdViewKeeper + address common.Address + + InstantiateID []byte + ExecuteID []byte + ExecuteBatchID []byte + QueryID []byte +} + +type ExecuteMsg struct { + ContractAddress string `json:"contractAddress"` + Msg []byte `json:"msg"` + Coins []byte `json:"coins"` +} + +func GetABI() abi.ABI { + return pcommon.MustGetABI(f, "abi.json") +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper, wasmdKeeper pcommon.WasmdKeeper, wasmdViewKeeper pcommon.WasmdViewKeeper, bankKeeper pcommon.BankKeeper) (*pcommon.DynamicGasPrecompile, error) { + newAbi := GetABI() + + executor := &PrecompileExecutor{ + wasmdKeeper: wasmdKeeper, + wasmdViewKeeper: wasmdViewKeeper, + evmKeeper: evmKeeper, + bankKeeper: bankKeeper, + address: Address, + } + + for name, m := range newAbi.Methods { + switch name { + case InstantiateMethod: + executor.InstantiateID = m.ID + case ExecuteMethod: + executor.ExecuteID = m.ID + case ExecuteBatchMethod: + executor.ExecuteBatchID = m.ID + case QueryMethod: + executor.QueryID = m.ID + } + } + return pcommon.NewDynamicGasPrecompile(newAbi, executor, Address, "wasmd"), nil +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + if method.Name != QueryMethod && !ctx.IsEVM() { + return nil, 0, errors.New("sei does not support CW->EVM->CW call pattern") + } + switch method.Name { + case InstantiateMethod: + return p.instantiate(ctx, method, caller, callingContract, args, value, readOnly) + case ExecuteMethod: + return p.execute(ctx, method, caller, callingContract, args, value, readOnly) + case ExecuteBatchMethod: + return p.executeBatch(ctx, method, caller, callingContract, args, value, readOnly) + case QueryMethod: + return p.query(ctx, method, args, value) + } + return +} + +func (p PrecompileExecutor) EVMKeeper() pcommon.EVMKeeper { + return p.evmKeeper +} + +func (p PrecompileExecutor) instantiate(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call instantiate from staticcall") + return + } + if err := pcommon.ValidateArgsLength(args, 5); err != nil { + rerr = err + return + } + if caller.Cmp(callingContract) != 0 { + rerr = errors.New("cannot delegatecall instantiate") + return + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + codeID := args[0].(uint64) + creatorAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + rerr = types.NewAssociationMissingErr(caller.Hex()) + return + } + var adminAddr sdk.AccAddress + adminAddrStr := args[1].(string) + if len(adminAddrStr) > 0 { + adminAddrDecoded, err := sdk.AccAddressFromBech32(adminAddrStr) + if err != nil { + rerr = err + return + } + adminAddr = adminAddrDecoded + } + msg := args[2].([]byte) + label := args[3].(string) + coins := sdk.NewCoins() + coinsBz := args[4].([]byte) + + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { + rerr = errors.New("coin amount must equal value specified") + return + } + + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgInstantiate := wasmtypes.MsgInstantiateContract{ + Sender: creatorAddr.String(), + CodeID: codeID, + Label: label, + Funds: coins, + Msg: msg, + Admin: adminAddrStr, + } + + if err := msgInstantiate.ValidateBasic(); err != nil { + rerr = err + return + } + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if value != nil && !useiAmt.IsZero() { + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), creatorAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + + addr, data, err := p.wasmdKeeper.Instantiate(ctx, codeID, creatorAddr, adminAddr, msg, label, coins) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(addr.String(), data) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p PrecompileExecutor) executeBatch(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call execute from staticcall") + return + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + rerr = err + return + } + + executeMsgs := args[0].([]struct { + ContractAddress string `json:"contractAddress"` + Msg []byte `json:"msg"` + Coins []byte `json:"coins"` + }) + + responses := make([][]byte, 0, len(executeMsgs)) + + // validate coins add up to value + validateValue := big.NewInt(0) + for i := 0; i < len(executeMsgs); i++ { + executeMsg := ExecuteMsg(executeMsgs[i]) + coinsBz := executeMsg.Coins + coins := sdk.NewCoins() + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + messageAmount := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + validateValue.Add(validateValue, messageAmount) + } + // if validateValue is greater than zero, then value must be provided, and they must be equal + if (value == nil && validateValue.Sign() == 1) || (value != nil && validateValue.Cmp(value) != 0) { + rerr = errors.New("sum of coin amounts must equal value specified") + return + } + // Copy to avoid modifying the original value + var valueCopy *big.Int + if value != nil { + valueCopy = new(big.Int).Set(value) + } else { + valueCopy = value + } + for i := 0; i < len(executeMsgs); i++ { + executeMsg := ExecuteMsg(executeMsgs[i]) + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + contractAddrStr := executeMsg.ContractAddress + if caller.Cmp(callingContract) != 0 { + erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) + erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) + if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { + return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) + } + } + + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + senderAddr, senderAssociated := p.evmKeeper.GetSeiAddress(ctx, caller) + if !senderAssociated { + rerr = types.NewAssociationMissingErr(caller.Hex()) + return + } + msg := executeMsg.Msg + coinsBz := executeMsg.Coins + coins := sdk.NewCoins() + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if valueCopy != nil && !useiAmt.IsZero() { + // process coin amount from the value provided + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + valueCopy.Sub(valueCopy, useiAmtAsWei) + if valueCopy.Sign() == -1 { + rerr = errors.New("insufficient value provided for payment") + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgExecute := wasmtypes.MsgExecuteContract{ + Sender: senderAddr.String(), + Contract: contractAddr.String(), + Msg: msg, + Funds: coins, + } + if err := msgExecute.ValidateBasic(); err != nil { + rerr = err + return + } + + res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) + if err != nil { + rerr = err + return + } + responses = append(responses, res) + } + if valueCopy != nil && valueCopy.Sign() != 0 { + rerr = errors.New("value remaining after execution, must match provided amounts exactly") + return + } + ret, rerr = method.Outputs.Pack(responses) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p PrecompileExecutor) execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call execute from staticcall") + return + } + if err := pcommon.ValidateArgsLength(args, 3); err != nil { + rerr = err + return + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + contractAddrStr := args[0].(string) + if caller.Cmp(callingContract) != 0 { + erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) + erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) + if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { + return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) + } + } + // addresses will be sent in Sei format + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + senderAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + rerr = types.NewAssociationMissingErr(caller.Hex()) + return + } + msg := args[1].([]byte) + coins := sdk.NewCoins() + coinsBz := args[2].([]byte) + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { + rerr = errors.New("coin amount must equal value specified") + return + } + + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgExecute := wasmtypes.MsgExecuteContract{ + Sender: senderAddr.String(), + Contract: contractAddr.String(), + Msg: msg, + Funds: coins, + } + + if err := msgExecute.ValidateBasic(); err != nil { + rerr = err + return + } + + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if value != nil && !useiAmt.IsZero() { + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(res) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p PrecompileExecutor) query(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if err := pcommon.ValidateNonPayable(value); err != nil { + rerr = err + return + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + rerr = err + return + } + + contractAddrStr := args[0].(string) + // addresses will be sent in Sei format + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + req := args[1].([]byte) + + rawContractMessage := wasmtypes.RawContractMessage(req) + if err := rawContractMessage.ValidateBasic(); err != nil { + rerr = err + return + } + + res, err := p.wasmdViewKeeper.QuerySmart(ctx, contractAddr, req) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(res) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func IsWasmdCall(to *common.Address) bool { + return to != nil && (to.Cmp(Address) == 0) +} diff --git a/precompiles/wasmd/legacy/v600/abi.json b/precompiles/wasmd/legacy/v600/abi.json new file mode 100644 index 0000000000..c5e5b1b3f6 --- /dev/null +++ b/precompiles/wasmd/legacy/v600/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"execute","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"internalType":"struct IWasmd.ExecuteMsg[]","name":"executeMsgs","type":"tuple[]"}],"name":"execute_batch","outputs":[{"internalType":"bytes[]","name":"responses","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint64","name":"codeID","type":"uint64"},{"internalType":"string","name":"admin","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"string","name":"label","type":"string"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"instantiate","outputs":[{"internalType":"string","name":"contractAddr","type":"string"},{"internalType":"bytes","name":"data","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"req","type":"bytes"}],"name":"query","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/wasmd/legacy/v600/wasmd.go b/precompiles/wasmd/legacy/v600/wasmd.go new file mode 100644 index 0000000000..1a2f4e270a --- /dev/null +++ b/precompiles/wasmd/legacy/v600/wasmd.go @@ -0,0 +1,464 @@ +package v600 + +import ( + "embed" + "encoding/json" + "errors" + "fmt" + "math/big" + + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + 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/legacy/v600" + "github.com/sei-protocol/sei-chain/x/evm/state" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +const ( + InstantiateMethod = "instantiate" + ExecuteMethod = "execute" + ExecuteBatchMethod = "execute_batch" + QueryMethod = "query" +) + +const WasmdAddress = "0x0000000000000000000000000000000000001002" + +var Address = common.HexToAddress(WasmdAddress) + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type PrecompileExecutor struct { + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + wasmdKeeper pcommon.WasmdKeeper + wasmdViewKeeper pcommon.WasmdViewKeeper + address common.Address + + InstantiateID []byte + ExecuteID []byte + ExecuteBatchID []byte + QueryID []byte +} + +type ExecuteMsg struct { + ContractAddress string `json:"contractAddress"` + Msg []byte `json:"msg"` + Coins []byte `json:"coins"` +} + +func GetABI() abi.ABI { + return pcommon.MustGetABI(f, "abi.json") +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper, wasmdKeeper pcommon.WasmdKeeper, wasmdViewKeeper pcommon.WasmdViewKeeper, bankKeeper pcommon.BankKeeper) (*pcommon.DynamicGasPrecompile, error) { + newAbi := GetABI() + + executor := &PrecompileExecutor{ + wasmdKeeper: wasmdKeeper, + wasmdViewKeeper: wasmdViewKeeper, + evmKeeper: evmKeeper, + bankKeeper: bankKeeper, + address: Address, + } + + for name, m := range newAbi.Methods { + switch name { + case InstantiateMethod: + executor.InstantiateID = m.ID + case ExecuteMethod: + executor.ExecuteID = m.ID + case ExecuteBatchMethod: + executor.ExecuteBatchID = m.ID + case QueryMethod: + executor.QueryID = m.ID + } + } + return pcommon.NewDynamicGasPrecompile(newAbi, executor, Address, "wasmd"), nil +} + +func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + if method.Name != QueryMethod && !ctx.IsEVM() { + return nil, 0, errors.New("sei does not support CW->EVM->CW call pattern") + } + switch method.Name { + case InstantiateMethod: + return p.instantiate(ctx, method, caller, callingContract, args, value, readOnly) + case ExecuteMethod: + return p.execute(ctx, method, caller, callingContract, args, value, readOnly) + case ExecuteBatchMethod: + return p.executeBatch(ctx, method, caller, callingContract, args, value, readOnly) + case QueryMethod: + return p.query(ctx, method, args, value) + } + return +} + +func (p PrecompileExecutor) EVMKeeper() pcommon.EVMKeeper { + return p.evmKeeper +} + +func (p PrecompileExecutor) instantiate(ctx sdk.Context, method *abi.Method, caller common.Address, _ common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call instantiate from staticcall") + return + } + if err := pcommon.ValidateArgsLength(args, 5); err != nil { + rerr = err + return + } + if ctx.EVMPrecompileCalledFromDelegateCall() { + rerr = errors.New("cannot delegatecall instantiate") + return + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + codeID := args[0].(uint64) + creatorAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + rerr = types.NewAssociationMissingErr(caller.Hex()) + return + } + var adminAddr sdk.AccAddress + adminAddrStr := args[1].(string) + if len(adminAddrStr) > 0 { + adminAddrDecoded, err := sdk.AccAddressFromBech32(adminAddrStr) + if err != nil { + rerr = err + return + } + adminAddr = adminAddrDecoded + } + msg := args[2].([]byte) + label := args[3].(string) + coins := sdk.NewCoins() + coinsBz := args[4].([]byte) + + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { + rerr = errors.New("coin amount must equal value specified") + return + } + + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgInstantiate := wasmtypes.MsgInstantiateContract{ + Sender: creatorAddr.String(), + CodeID: codeID, + Label: label, + Funds: coins, + Msg: msg, + Admin: adminAddrStr, + } + + if err := msgInstantiate.ValidateBasic(); err != nil { + rerr = err + return + } + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if value != nil && !useiAmt.IsZero() { + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), creatorAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + + addr, data, err := p.wasmdKeeper.Instantiate(ctx, codeID, creatorAddr, adminAddr, msg, label, coins) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(addr.String(), data) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p PrecompileExecutor) executeBatch(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call execute from staticcall") + return + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + rerr = err + return + } + + executeMsgs := args[0].([]struct { + ContractAddress string `json:"contractAddress"` + Msg []byte `json:"msg"` + Coins []byte `json:"coins"` + }) + + responses := make([][]byte, 0, len(executeMsgs)) + + // validate coins add up to value + validateValue := big.NewInt(0) + for i := 0; i < len(executeMsgs); i++ { + executeMsg := ExecuteMsg(executeMsgs[i]) + coinsBz := executeMsg.Coins + coins := sdk.NewCoins() + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + messageAmount := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + validateValue.Add(validateValue, messageAmount) + } + // if validateValue is greater than zero, then value must be provided, and they must be equal + if (value == nil && validateValue.Sign() == 1) || (value != nil && validateValue.Cmp(value) != 0) { + rerr = errors.New("sum of coin amounts must equal value specified") + return + } + // Copy to avoid modifying the original value + var valueCopy *big.Int + if value != nil { + valueCopy = new(big.Int).Set(value) + } else { + valueCopy = value + } + for i := 0; i < len(executeMsgs); i++ { + executeMsg := ExecuteMsg(executeMsgs[i]) + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + contractAddrStr := executeMsg.ContractAddress + if ctx.EVMPrecompileCalledFromDelegateCall() { + erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) + erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) + if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { + return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) + } + } + + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + senderAddr, senderAssociated := p.evmKeeper.GetSeiAddress(ctx, caller) + if !senderAssociated { + rerr = types.NewAssociationMissingErr(caller.Hex()) + return + } + msg := executeMsg.Msg + coinsBz := executeMsg.Coins + coins := sdk.NewCoins() + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if valueCopy != nil && !useiAmt.IsZero() { + // process coin amount from the value provided + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + valueCopy.Sub(valueCopy, useiAmtAsWei) + if valueCopy.Sign() == -1 { + rerr = errors.New("insufficient value provided for payment") + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgExecute := wasmtypes.MsgExecuteContract{ + Sender: senderAddr.String(), + Contract: contractAddr.String(), + Msg: msg, + Funds: coins, + } + if err := msgExecute.ValidateBasic(); err != nil { + rerr = err + return + } + + res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) + if err != nil { + rerr = err + return + } + responses = append(responses, res) + } + if valueCopy != nil && valueCopy.Sign() != 0 { + rerr = errors.New("value remaining after execution, must match provided amounts exactly") + return + } + ret, rerr = method.Outputs.Pack(responses) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p PrecompileExecutor) execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if readOnly { + rerr = errors.New("cannot call execute from staticcall") + return + } + if err := pcommon.ValidateArgsLength(args, 3); err != nil { + rerr = err + return + } + + // type assertion will always succeed because it's already validated in p.Prepare call in Run() + contractAddrStr := args[0].(string) + if ctx.EVMPrecompileCalledFromDelegateCall() { + erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) + erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) + if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { + return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) + } + } + // addresses will be sent in Sei format + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + senderAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) + if !found { + rerr = types.NewAssociationMissingErr(caller.Hex()) + return + } + msg := args[1].([]byte) + coins := sdk.NewCoins() + coinsBz := args[2].([]byte) + if err := json.Unmarshal(coinsBz, &coins); err != nil { + rerr = err + return + } + coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() + if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { + rerr = errors.New("coin amount must equal value specified") + return + } + + // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd + msgExecute := wasmtypes.MsgExecuteContract{ + Sender: senderAddr.String(), + Contract: contractAddr.String(), + Msg: msg, + Funds: coins, + } + + if err := msgExecute.ValidateBasic(); err != nil { + rerr = err + return + } + + useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) + if value != nil && !useiAmt.IsZero() { + useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() + coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) + if err != nil { + rerr = err + return + } + // sanity check coin amounts match + if !coin.Amount.Equal(useiAmt) { + rerr = errors.New("mismatch between coins and payment value") + return + } + } + res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(res) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func (p PrecompileExecutor) query(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + if err := pcommon.ValidateNonPayable(value); err != nil { + rerr = err + return + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + rerr = err + return + } + + contractAddrStr := args[0].(string) + // addresses will be sent in Sei format + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + rerr = err + return + } + req := args[1].([]byte) + + rawContractMessage := wasmtypes.RawContractMessage(req) + if err := rawContractMessage.ValidateBasic(); err != nil { + rerr = err + return + } + + res, err := p.wasmdViewKeeper.QuerySmart(ctx, contractAddr, req) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(res) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} + +func IsWasmdCall(to *common.Address) bool { + return to != nil && (to.Cmp(Address) == 0) +} diff --git a/utils/helpers/legacy/v575/address.go b/utils/helpers/legacy/v575/address.go new file mode 100644 index 0000000000..9d25172848 --- /dev/null +++ b/utils/helpers/legacy/v575/address.go @@ -0,0 +1,65 @@ +package v575 + +import ( + "errors" + "math/big" + + "github.com/btcsuite/btcd/btcec" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" +) + +func GetAddresses(V *big.Int, R *big.Int, S *big.Int, data common.Hash) (common.Address, sdk.AccAddress, cryptotypes.PubKey, error) { + pubkey, err := RecoverPubkey(data, R, S, V, true) + if err != nil { + return common.Address{}, sdk.AccAddress{}, nil, err + } + + evmAddr, err := PubkeyToEVMAddress(pubkey) + if err != nil { + return common.Address{}, sdk.AccAddress{}, nil, err + } + seiPubkey := PubkeyBytesToSeiPubKey(pubkey) + seiAddr := sdk.AccAddress(seiPubkey.Address()) + return evmAddr, seiAddr, &seiPubkey, nil +} + +// first half of go-ethereum/core/types/transaction_signing.go:recoverPlain +func RecoverPubkey(sighash common.Hash, R, S, Vb *big.Int, homestead bool) ([]byte, error) { + if Vb.BitLen() > 8 { + return []byte{}, ethtypes.ErrInvalidSig + } + V := byte(Vb.Uint64() - 27) + if !crypto.ValidateSignatureValues(V, R, S, homestead) { + return []byte{}, ethtypes.ErrInvalidSig + } + // encode the signature in uncompressed format + r, s := R.Bytes(), S.Bytes() + sig := make([]byte, crypto.SignatureLength) + copy(sig[32-len(r):32], r) + copy(sig[64-len(s):64], s) + sig[64] = V + + // recover the public key from the signature + return crypto.Ecrecover(sighash[:], sig) +} + +// second half of go-ethereum/core/types/transaction_signing.go:recoverPlain +func PubkeyToEVMAddress(pub []byte) (common.Address, error) { + if len(pub) == 0 || pub[0] != 4 { + return common.Address{}, errors.New("invalid public key") + } + var addr common.Address + copy(addr[:], crypto.Keccak256(pub[1:])[12:]) + return addr, nil +} + +func PubkeyBytesToSeiPubKey(pub []byte) secp256k1.PubKey { + pubKey, _ := crypto.UnmarshalPubkey(pub) + pubkeyObj := (*btcec.PublicKey)(pubKey) + return secp256k1.PubKey{Key: pubkeyObj.SerializeCompressed()} +} diff --git a/utils/helpers/legacy/v575/associate.go b/utils/helpers/legacy/v575/associate.go new file mode 100644 index 0000000000..02aa5aa825 --- /dev/null +++ b/utils/helpers/legacy/v575/associate.go @@ -0,0 +1,50 @@ +package v575 + +import ( + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/ethereum/go-ethereum/common" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v575" +) + +type AssociationHelper struct { + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + accountKeeper pcommon.AccountKeeper +} + +func NewAssociationHelper(evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper, accountKeeper pcommon.AccountKeeper) *AssociationHelper { + return &AssociationHelper{evmKeeper: evmKeeper, bankKeeper: bankKeeper, accountKeeper: accountKeeper} +} + +func (p AssociationHelper) AssociateAddresses(ctx sdk.Context, seiAddr sdk.AccAddress, evmAddr common.Address, pubkey cryptotypes.PubKey) error { + p.evmKeeper.SetAddressMapping(ctx, seiAddr, evmAddr) + if acc := p.accountKeeper.GetAccount(ctx, seiAddr); acc.GetPubKey() == nil { + if err := acc.SetPubKey(pubkey); err != nil { + return err + } + p.accountKeeper.SetAccount(ctx, acc) + } + return p.MigrateBalance(ctx, evmAddr, seiAddr) +} + +func (p AssociationHelper) MigrateBalance(ctx sdk.Context, evmAddr common.Address, seiAddr sdk.AccAddress) error { + castAddr := sdk.AccAddress(evmAddr[:]) + castAddrBalances := p.bankKeeper.SpendableCoins(ctx, castAddr) + if !castAddrBalances.IsZero() { + if err := p.bankKeeper.SendCoins(ctx, castAddr, seiAddr, castAddrBalances); err != nil { + return err + } + } + castAddrWei := p.bankKeeper.GetWeiBalance(ctx, castAddr) + if !castAddrWei.IsZero() { + if err := p.bankKeeper.SendCoinsAndWei(ctx, castAddr, seiAddr, sdk.ZeroInt(), castAddrWei); err != nil { + return err + } + } + if p.bankKeeper.LockedCoins(ctx, castAddr).IsZero() { + p.accountKeeper.RemoveAccount(ctx, authtypes.NewBaseAccountWithAddress(castAddr)) + } + return nil +} diff --git a/utils/helpers/legacy/v600/address.go b/utils/helpers/legacy/v600/address.go new file mode 100644 index 0000000000..b279cfffa3 --- /dev/null +++ b/utils/helpers/legacy/v600/address.go @@ -0,0 +1,69 @@ +package v600 + +import ( + "errors" + "math/big" + + "github.com/btcsuite/btcd/btcec" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" +) + +func GetAddresses(V *big.Int, R *big.Int, S *big.Int, data common.Hash) (common.Address, sdk.AccAddress, cryptotypes.PubKey, error) { + pubkey, err := RecoverPubkey(data, R, S, V, true) + if err != nil { + return common.Address{}, sdk.AccAddress{}, nil, err + } + + return GetAddressesFromPubkeyBytes(pubkey) +} + +func GetAddressesFromPubkeyBytes(pubkey []byte) (common.Address, sdk.AccAddress, cryptotypes.PubKey, error) { + evmAddr, err := PubkeyToEVMAddress(pubkey) + if err != nil { + return common.Address{}, sdk.AccAddress{}, nil, err + } + seiPubkey := PubkeyBytesToSeiPubKey(pubkey) + seiAddr := sdk.AccAddress(seiPubkey.Address()) + return evmAddr, seiAddr, &seiPubkey, nil +} + +// first half of go-ethereum/core/types/transaction_signing.go:recoverPlain +func RecoverPubkey(sighash common.Hash, R, S, Vb *big.Int, homestead bool) ([]byte, error) { + if Vb.BitLen() > 8 { + return []byte{}, ethtypes.ErrInvalidSig + } + V := byte(Vb.Uint64() - 27) + if !crypto.ValidateSignatureValues(V, R, S, homestead) { + return []byte{}, ethtypes.ErrInvalidSig + } + // encode the signature in uncompressed format + r, s := R.Bytes(), S.Bytes() + sig := make([]byte, crypto.SignatureLength) + copy(sig[32-len(r):32], r) + copy(sig[64-len(s):64], s) + sig[64] = V + + // recover the public key from the signature + return crypto.Ecrecover(sighash[:], sig) +} + +// second half of go-ethereum/core/types/transaction_signing.go:recoverPlain +func PubkeyToEVMAddress(pub []byte) (common.Address, error) { + if len(pub) == 0 || pub[0] != 4 { + return common.Address{}, errors.New("invalid public key") + } + var addr common.Address + copy(addr[:], crypto.Keccak256(pub[1:])[12:]) + return addr, nil +} + +func PubkeyBytesToSeiPubKey(pub []byte) secp256k1.PubKey { + pubKey, _ := crypto.UnmarshalPubkey(pub) + pubkeyObj := (*btcec.PublicKey)(pubKey) + return secp256k1.PubKey{Key: pubkeyObj.SerializeCompressed()} +} diff --git a/utils/helpers/legacy/v600/asssociate.go b/utils/helpers/legacy/v600/asssociate.go new file mode 100644 index 0000000000..b6741d8f43 --- /dev/null +++ b/utils/helpers/legacy/v600/asssociate.go @@ -0,0 +1,50 @@ +package v600 + +import ( + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/ethereum/go-ethereum/common" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v600" +) + +type AssociationHelper struct { + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + accountKeeper pcommon.AccountKeeper +} + +func NewAssociationHelper(evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper, accountKeeper pcommon.AccountKeeper) *AssociationHelper { + return &AssociationHelper{evmKeeper: evmKeeper, bankKeeper: bankKeeper, accountKeeper: accountKeeper} +} + +func (p AssociationHelper) AssociateAddresses(ctx sdk.Context, seiAddr sdk.AccAddress, evmAddr common.Address, pubkey cryptotypes.PubKey) error { + p.evmKeeper.SetAddressMapping(ctx, seiAddr, evmAddr) + if acc := p.accountKeeper.GetAccount(ctx, seiAddr); acc.GetPubKey() == nil { + if err := acc.SetPubKey(pubkey); err != nil { + return err + } + p.accountKeeper.SetAccount(ctx, acc) + } + return p.MigrateBalance(ctx, evmAddr, seiAddr) +} + +func (p AssociationHelper) MigrateBalance(ctx sdk.Context, evmAddr common.Address, seiAddr sdk.AccAddress) error { + castAddr := sdk.AccAddress(evmAddr[:]) + castAddrBalances := p.bankKeeper.SpendableCoins(ctx, castAddr) + if !castAddrBalances.IsZero() { + if err := p.bankKeeper.SendCoins(ctx, castAddr, seiAddr, castAddrBalances); err != nil { + return err + } + } + castAddrWei := p.bankKeeper.GetWeiBalance(ctx, castAddr) + if !castAddrWei.IsZero() { + if err := p.bankKeeper.SendCoinsAndWei(ctx, castAddr, seiAddr, sdk.ZeroInt(), castAddrWei); err != nil { + return err + } + } + if p.bankKeeper.LockedCoins(ctx, castAddr).IsZero() { + p.accountKeeper.RemoveAccount(ctx, authtypes.NewBaseAccountWithAddress(castAddr)) + } + return nil +} From bf2efcd963c9e4fbada164fbe48ff4f4461c99d5 Mon Sep 17 00:00:00 2001 From: Tony Chen Date: Mon, 7 Apr 2025 12:47:37 +0800 Subject: [PATCH 2/4] rebase --- .../addr/legacy/{v520 => v552}/abi.json | 0 .../addr/legacy/{v520 => v552}/addr.go | 4 +- precompiles/bank/legacy/v520/abi.json | 1 - precompiles/bank/legacy/v520/bank.go | 374 ------------- .../common/legacy/v520/expected_keepers.go | 81 --- precompiles/common/legacy/v520/precompiles.go | 119 ---- .../legacy/{v530 => v552}/expected_keepers.go | 2 +- .../legacy/{v530 => v552}/precompiles.go | 2 +- precompiles/distribution/legacy/v520/abi.json | 1 - .../distribution/legacy/v520/distribution.go | 180 ------- .../gov/legacy/{v520 => v552}/abi.json | 0 precompiles/gov/legacy/{v520 => v552}/gov.go | 4 +- precompiles/ibc/legacy/v501/abi.json | 56 -- precompiles/ibc/legacy/v501/ibc.go | 268 --------- precompiles/ibc/legacy/v510/abi.json | 56 -- precompiles/ibc/legacy/v510/ibc.go | 268 --------- precompiles/ibc/legacy/v520/abi.json | 56 -- precompiles/ibc/legacy/v520/ibc.go | 268 --------- .../ibc/legacy/{v530 => v552}/abi.json | 0 precompiles/ibc/legacy/{v530 => v552}/ibc.go | 4 +- precompiles/json/legacy/v520/json.go | 211 -------- precompiles/json/legacy/v530/abi.json | 1 - .../json/legacy/{v520 => v552}/abi.json | 0 .../json/legacy/{v530 => v552}/json.go | 4 +- .../oracle/legacy/{v520 => v552}/abi.json | 0 .../oracle/legacy/{v520 => v552}/oracle.go | 4 +- precompiles/pointer/legacy/v520/pointer.go | 283 ---------- precompiles/pointer/legacy/v522/abi.json | 1 - precompiles/pointer/legacy/v522/pointer.go | 289 ---------- precompiles/pointer/legacy/v530/abi.json | 1 - .../pointer/legacy/{v520 => v552}/abi.json | 0 .../pointer/legacy/{v530 => v552}/pointer.go | 4 +- .../legacy/{v520 => v552}/abi.json | 0 .../legacy/{v520 => v552}/pointerview.go | 4 +- .../staking/legacy/{v520 => v552}/abi.json | 0 .../staking/legacy/{v520 => v552}/staking.go | 4 +- precompiles/wasmd/legacy/v501/wasmd.go | 503 ----------------- precompiles/wasmd/legacy/v510/abi.json | 1 - precompiles/wasmd/legacy/v510/wasmd.go | 503 ----------------- precompiles/wasmd/legacy/v520/abi.json | 1 - precompiles/wasmd/legacy/v520/wasmd.go | 503 ----------------- precompiles/wasmd/legacy/v522/abi.json | 1 - precompiles/wasmd/legacy/v522/wasmd.go | 508 ------------------ precompiles/wasmd/legacy/v530/abi.json | 1 - .../wasmd/legacy/{v501 => v552}/abi.json | 0 .../wasmd/legacy/{v530 => v552}/wasmd.go | 4 +- 46 files changed, 20 insertions(+), 4555 deletions(-) rename precompiles/addr/legacy/{v520 => v552}/abi.json (100%) rename precompiles/addr/legacy/{v520 => v552}/addr.go (99%) delete mode 100644 precompiles/bank/legacy/v520/abi.json delete mode 100644 precompiles/bank/legacy/v520/bank.go delete mode 100644 precompiles/common/legacy/v520/expected_keepers.go delete mode 100644 precompiles/common/legacy/v520/precompiles.go rename precompiles/common/legacy/{v530 => v552}/expected_keepers.go (99%) rename precompiles/common/legacy/{v530 => v552}/precompiles.go (99%) delete mode 100644 precompiles/distribution/legacy/v520/abi.json delete mode 100644 precompiles/distribution/legacy/v520/distribution.go rename precompiles/gov/legacy/{v520 => v552}/abi.json (100%) rename precompiles/gov/legacy/{v520 => v552}/gov.go (99%) delete mode 100644 precompiles/ibc/legacy/v501/abi.json delete mode 100644 precompiles/ibc/legacy/v501/ibc.go delete mode 100644 precompiles/ibc/legacy/v510/abi.json delete mode 100644 precompiles/ibc/legacy/v510/ibc.go delete mode 100644 precompiles/ibc/legacy/v520/abi.json delete mode 100644 precompiles/ibc/legacy/v520/ibc.go rename precompiles/ibc/legacy/{v530 => v552}/abi.json (100%) rename precompiles/ibc/legacy/{v530 => v552}/ibc.go (99%) delete mode 100644 precompiles/json/legacy/v520/json.go delete mode 100644 precompiles/json/legacy/v530/abi.json rename precompiles/json/legacy/{v520 => v552}/abi.json (100%) rename precompiles/json/legacy/{v530 => v552}/json.go (99%) rename precompiles/oracle/legacy/{v520 => v552}/abi.json (100%) rename precompiles/oracle/legacy/{v520 => v552}/oracle.go (99%) delete mode 100644 precompiles/pointer/legacy/v520/pointer.go delete mode 100644 precompiles/pointer/legacy/v522/abi.json delete mode 100644 precompiles/pointer/legacy/v522/pointer.go delete mode 100644 precompiles/pointer/legacy/v530/abi.json rename precompiles/pointer/legacy/{v520 => v552}/abi.json (100%) rename precompiles/pointer/legacy/{v530 => v552}/pointer.go (99%) rename precompiles/pointerview/legacy/{v520 => v552}/abi.json (100%) rename precompiles/pointerview/legacy/{v520 => v552}/pointerview.go (99%) rename precompiles/staking/legacy/{v520 => v552}/abi.json (100%) rename precompiles/staking/legacy/{v520 => v552}/staking.go (99%) delete mode 100644 precompiles/wasmd/legacy/v501/wasmd.go delete mode 100644 precompiles/wasmd/legacy/v510/abi.json delete mode 100644 precompiles/wasmd/legacy/v510/wasmd.go delete mode 100644 precompiles/wasmd/legacy/v520/abi.json delete mode 100644 precompiles/wasmd/legacy/v520/wasmd.go delete mode 100644 precompiles/wasmd/legacy/v522/abi.json delete mode 100644 precompiles/wasmd/legacy/v522/wasmd.go delete mode 100644 precompiles/wasmd/legacy/v530/abi.json rename precompiles/wasmd/legacy/{v501 => v552}/abi.json (100%) rename precompiles/wasmd/legacy/{v530 => v552}/wasmd.go (99%) diff --git a/precompiles/addr/legacy/v520/abi.json b/precompiles/addr/legacy/v552/abi.json similarity index 100% rename from precompiles/addr/legacy/v520/abi.json rename to precompiles/addr/legacy/v552/abi.json diff --git a/precompiles/addr/legacy/v520/addr.go b/precompiles/addr/legacy/v552/addr.go similarity index 99% rename from precompiles/addr/legacy/v520/addr.go rename to precompiles/addr/legacy/v552/addr.go index 1628e2b2d4..32644abbf0 100644 --- a/precompiles/addr/legacy/v520/addr.go +++ b/precompiles/addr/legacy/v552/addr.go @@ -1,4 +1,4 @@ -package v520 +package v552 import ( "bytes" @@ -11,7 +11,7 @@ import ( "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/legacy/v520" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v552" ) const ( diff --git a/precompiles/bank/legacy/v520/abi.json b/precompiles/bank/legacy/v520/abi.json deleted file mode 100644 index 844ac48943..0000000000 --- a/precompiles/bank/legacy/v520/abi.json +++ /dev/null @@ -1 +0,0 @@ -[{"inputs":[{"internalType":"address","name":"acc","type":"address"}],"name":"all_balances","outputs":[{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"denom","type":"string"}],"internalType":"struct IBank.Coin[]","name":"response","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"acc","type":"address"},{"internalType":"string","name":"denom","type":"string"}],"name":"balance","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"decimals","outputs":[{"internalType":"uint8","name":"response","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"name","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"fromAddress","type":"address"},{"internalType":"address","name":"toAddress","type":"address"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"send","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"toNativeAddress","type":"string"}],"name":"sendNative","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"supply","outputs":[{"internalType":"uint256","name":"response","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"}],"name":"symbol","outputs":[{"internalType":"string","name":"response","type":"string"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/bank/legacy/v520/bank.go b/precompiles/bank/legacy/v520/bank.go deleted file mode 100644 index 47d564c8fe..0000000000 --- a/precompiles/bank/legacy/v520/bank.go +++ /dev/null @@ -1,374 +0,0 @@ -package v520 - -import ( - "bytes" - "embed" - "errors" - "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/legacy/v520" - "github.com/sei-protocol/sei-chain/utils" - "github.com/tendermint/tendermint/libs/log" -) - -const ( - SendMethod = "send" - SendNativeMethod = "sendNative" - BalanceMethod = "balance" - AllBalancesMethod = "all_balances" - NameMethod = "name" - SymbolMethod = "symbol" - DecimalsMethod = "decimals" - SupplyMethod = "supply" -) - -const ( - BankAddress = "0x0000000000000000000000000000000000001001" -) - -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 - -func GetABI() abi.ABI { - abiBz, err := f.ReadFile("abi.json") - if err != nil { - panic(err) - } - - newAbi, err := abi.JSON(bytes.NewReader(abiBz)) - if err != nil { - panic(err) - } - return newAbi -} - -type Precompile struct { - pcommon.Precompile - bankKeeper pcommon.BankKeeper - evmKeeper pcommon.EVMKeeper - address common.Address - - SendID []byte - SendNativeID []byte - BalanceID []byte - AllBalancesID []byte - NameID []byte - SymbolID []byte - DecimalsID []byte - SupplyID []byte -} - -type CoinBalance struct { - Amount *big.Int - Denom string -} - -func NewPrecompile(bankKeeper pcommon.BankKeeper, evmKeeper pcommon.EVMKeeper) (*Precompile, error) { - newAbi := GetABI() - - p := &Precompile{ - Precompile: pcommon.Precompile{ABI: newAbi}, - bankKeeper: bankKeeper, - evmKeeper: evmKeeper, - address: common.HexToAddress(BankAddress), - } - - for name, m := range newAbi.Methods { - switch name { - case SendMethod: - p.SendID = m.ID - case SendNativeMethod: - p.SendNativeID = m.ID - case BalanceMethod: - p.BalanceID = m.ID - case AllBalancesMethod: - p.AllBalancesID = m.ID - case NameMethod: - p.NameID = m.ID - case SymbolMethod: - p.SymbolID = m.ID - case DecimalsMethod: - p.DecimalsID = m.ID - case SupplyMethod: - p.SupplyID = m.ID - } - } - - return p, nil -} - -// RequiredGas returns the required bare minimum gas to execute the precompile. -func (p Precompile) RequiredGas(input []byte) uint64 { - methodID, err := pcommon.ExtractMethodID(input) - if err != nil { - return pcommon.UnknownMethodCallGas - } - - method, err := p.ABI.MethodById(methodID) - if err != nil { - // This should never happen since this method is going to fail during Run - return pcommon.UnknownMethodCallGas - } - - return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) -} - -func (p Precompile) Address() common.Address { - return p.address -} - -func (p Precompile) GetName() string { - return "bank" -} - -func (p Precompile) Run(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, value *big.Int, readOnly bool, _ bool) (bz []byte, err error) { - ctx, method, args, err := p.Prepare(evm, input) - if err != nil { - return nil, err - } - - switch method.Name { - case SendMethod: - return p.send(ctx, caller, method, args, value, readOnly) - case SendNativeMethod: - return p.sendNative(ctx, method, args, caller, callingContract, value, readOnly) - case BalanceMethod: - return p.balance(ctx, method, args, value) - case AllBalancesMethod: - return p.all_balances(ctx, method, args, value) - case NameMethod: - return p.name(ctx, method, args, value) - case SymbolMethod: - return p.symbol(ctx, method, args, value) - case DecimalsMethod: - return p.decimals(ctx, method, args, value) - case SupplyMethod: - return p.totalSupply(ctx, method, args, value) - } - return -} - -func (p Precompile) send(ctx sdk.Context, caller common.Address, method *abi.Method, args []interface{}, value *big.Int, readOnly bool) ([]byte, error) { - if readOnly { - return nil, errors.New("cannot call send from staticcall") - } - if err := pcommon.ValidateNonPayable(value); err != nil { - return nil, err - } - - if err := pcommon.ValidateArgsLength(args, 4); err != nil { - return nil, err - } - denom := args[2].(string) - if denom == "" { - return nil, errors.New("invalid denom") - } - pointer, _, exists := p.evmKeeper.GetERC20NativePointer(ctx, denom) - if !exists || pointer.Cmp(caller) != 0 { - return nil, fmt.Errorf("only pointer %s can send %s but got %s", pointer.Hex(), denom, caller.Hex()) - } - amount := args[3].(*big.Int) - if amount.Cmp(utils.Big0) == 0 { - // short circuit - return method.Outputs.Pack(true) - } - // TODO: it's possible to extend evm module's balance to handle non-usei tokens as well - senderSeiAddr, err := p.accAddressFromArg(ctx, args[0]) - if err != nil { - return nil, err - } - receiverSeiAddr, err := p.accAddressFromArg(ctx, args[1]) - if err != nil { - return nil, err - } - - if err := p.bankKeeper.SendCoins(ctx, senderSeiAddr, receiverSeiAddr, sdk.NewCoins(sdk.NewCoin(denom, sdk.NewIntFromBigInt(amount)))); err != nil { - return nil, err - } - - return method.Outputs.Pack(true) -} - -func (p Precompile) sendNative(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address, callingContract common.Address, value *big.Int, readOnly bool) ([]byte, error) { - if readOnly { - return nil, errors.New("cannot call sendNative from staticcall") - } - if caller.Cmp(callingContract) != 0 { - return nil, errors.New("cannot delegatecall sendNative") - } - if err := pcommon.ValidateArgsLength(args, 1); err != nil { - return nil, err - } - if value == nil || value.Sign() == 0 { - return nil, errors.New("set `value` field to non-zero to send") - } - - senderSeiAddr, ok := p.evmKeeper.GetSeiAddress(ctx, caller) - if !ok { - return nil, errors.New("invalid addr") - } - - receiverAddr, ok := (args[0]).(string) - if !ok || receiverAddr == "" { - return nil, errors.New("invalid addr") - } - - receiverSeiAddr, err := sdk.AccAddressFromBech32(receiverAddr) - if err != nil { - return nil, err - } - - usei, wei, err := pcommon.HandlePaymentUseiWei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderSeiAddr, value, p.bankKeeper) - if err != nil { - return nil, err - } - - if err := p.bankKeeper.SendCoinsAndWei(ctx, senderSeiAddr, receiverSeiAddr, usei, wei); err != nil { - return nil, err - } - - return method.Outputs.Pack(true) -} - -func (p Precompile) balance(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { - if err := pcommon.ValidateNonPayable(value); err != nil { - return nil, err - } - - if err := pcommon.ValidateArgsLength(args, 2); err != nil { - return nil, err - } - - addr, err := p.accAddressFromArg(ctx, args[0]) - if err != nil { - return nil, err - } - denom := args[1].(string) - if denom == "" { - return nil, errors.New("invalid denom") - } - - return method.Outputs.Pack(p.bankKeeper.GetBalance(ctx, addr, denom).Amount.BigInt()) -} - -func (p Precompile) all_balances(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { - if err := pcommon.ValidateNonPayable(value); err != nil { - return nil, err - } - - if err := pcommon.ValidateArgsLength(args, 1); err != nil { - return nil, err - } - - addr, err := p.accAddressFromArg(ctx, args[0]) - if err != nil { - return nil, err - } - - coins := p.bankKeeper.GetAllBalances(ctx, addr) - - // convert to coin balance structs - coinBalances := make([]CoinBalance, 0, len(coins)) - - for _, coin := range coins { - coinBalances = append(coinBalances, CoinBalance{ - Amount: coin.Amount.BigInt(), - Denom: coin.Denom, - }) - } - - return method.Outputs.Pack(coinBalances) -} - -func (p Precompile) name(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { - if err := pcommon.ValidateNonPayable(value); err != nil { - return nil, err - } - - if err := pcommon.ValidateArgsLength(args, 1); err != nil { - return nil, err - } - - denom := args[0].(string) - metadata, found := p.bankKeeper.GetDenomMetaData(ctx, denom) - if !found { - return nil, fmt.Errorf("denom %s not found", denom) - } - return method.Outputs.Pack(metadata.Name) -} - -func (p Precompile) symbol(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { - if err := pcommon.ValidateNonPayable(value); err != nil { - return nil, err - } - - if err := pcommon.ValidateArgsLength(args, 1); err != nil { - return nil, err - } - - denom := args[0].(string) - metadata, found := p.bankKeeper.GetDenomMetaData(ctx, denom) - if !found { - return nil, fmt.Errorf("denom %s not found", denom) - } - return method.Outputs.Pack(metadata.Symbol) -} - -func (p Precompile) decimals(_ sdk.Context, method *abi.Method, _ []interface{}, value *big.Int) ([]byte, error) { - if err := pcommon.ValidateNonPayable(value); err != nil { - return nil, err - } - - // all native tokens are integer-based, returns decimals for microdenom (usei) - return method.Outputs.Pack(uint8(0)) -} - -func (p Precompile) totalSupply(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { - if err := pcommon.ValidateNonPayable(value); err != nil { - return nil, err - } - - if err := pcommon.ValidateArgsLength(args, 1); err != nil { - return nil, err - } - - denom := args[0].(string) - coin := p.bankKeeper.GetSupply(ctx, denom) - return method.Outputs.Pack(coin.Amount.BigInt()) -} - -func (p Precompile) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { - addr := arg.(common.Address) - if addr == (common.Address{}) { - return nil, errors.New("invalid addr") - } - seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, addr) - if !found { - return nil, fmt.Errorf("EVM address %s is not associated", addr.Hex()) - } - return seiAddr, nil -} - -func (Precompile) IsTransaction(method string) bool { - switch method { - case SendMethod: - return true - case SendNativeMethod: - return true - default: - return false - } -} - -func (p Precompile) Logger(ctx sdk.Context) log.Logger { - return ctx.Logger().With("precompile", "bank") -} diff --git a/precompiles/common/legacy/v520/expected_keepers.go b/precompiles/common/legacy/v520/expected_keepers.go deleted file mode 100644 index b558d5c30f..0000000000 --- a/precompiles/common/legacy/v520/expected_keepers.go +++ /dev/null @@ -1,81 +0,0 @@ -package v520 - -import ( - "context" - - sdk "github.com/cosmos/cosmos-sdk/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" - "github.com/ethereum/go-ethereum/common" - oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" -) - -type BankKeeper interface { - SendCoins(sdk.Context, sdk.AccAddress, sdk.AccAddress, sdk.Coins) error - SendCoinsAndWei(ctx sdk.Context, from sdk.AccAddress, to sdk.AccAddress, amt sdk.Int, wei sdk.Int) error - GetBalance(sdk.Context, sdk.AccAddress, string) sdk.Coin - GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins - GetWeiBalance(ctx sdk.Context, addr sdk.AccAddress) sdk.Int - GetDenomMetaData(ctx sdk.Context, denom string) (banktypes.Metadata, bool) - GetSupply(ctx sdk.Context, denom string) sdk.Coin -} - -type EVMKeeper interface { - GetSeiAddress(sdk.Context, common.Address) (sdk.AccAddress, bool) - GetSeiAddressOrDefault(ctx sdk.Context, evmAddress common.Address) sdk.AccAddress // only used for getting precompile Sei addresses - GetEVMAddress(sdk.Context, sdk.AccAddress) (common.Address, bool) - GetCodeHash(sdk.Context, common.Address) common.Hash - 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 { - IterateBaseExchangeRates(ctx sdk.Context, handler func(denom string, exchangeRate oracletypes.OracleExchangeRate) (stop bool)) - CalculateTwaps(ctx sdk.Context, lookbackSeconds uint64) (oracletypes.OracleTwaps, error) -} - -type WasmdKeeper interface { - Instantiate(ctx sdk.Context, codeID uint64, creator, admin sdk.AccAddress, initMsg []byte, label string, deposit sdk.Coins) (sdk.AccAddress, []byte, error) - Execute(ctx sdk.Context, contractAddress sdk.AccAddress, caller sdk.AccAddress, msg []byte, coins sdk.Coins) ([]byte, error) -} - -type WasmdViewKeeper interface { - QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) ([]byte, error) -} - -type StakingKeeper interface { - Delegate(goCtx context.Context, msg *stakingtypes.MsgDelegate) (*stakingtypes.MsgDelegateResponse, error) - BeginRedelegate(goCtx context.Context, msg *stakingtypes.MsgBeginRedelegate) (*stakingtypes.MsgBeginRedelegateResponse, error) - Undelegate(goCtx context.Context, msg *stakingtypes.MsgUndelegate) (*stakingtypes.MsgUndelegateResponse, error) -} - -type GovKeeper interface { - AddVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress, options govtypes.WeightedVoteOptions) error - AddDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress, depositAmount sdk.Coins) (bool, error) -} - -type DistributionKeeper interface { - SetWithdrawAddr(ctx sdk.Context, delegatorAddr sdk.AccAddress, withdrawAddr sdk.AccAddress) error - WithdrawDelegationRewards(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (sdk.Coins, error) -} - -type TransferKeeper interface { - SendTransfer( - ctx sdk.Context, - sourcePort, - sourceChannel string, - token sdk.Coin, - sender sdk.AccAddress, - receiver string, - timeoutHeight clienttypes.Height, - timeoutTimestamp uint64, - ) error -} diff --git a/precompiles/common/legacy/v520/precompiles.go b/precompiles/common/legacy/v520/precompiles.go deleted file mode 100644 index 5336aa29a0..0000000000 --- a/precompiles/common/legacy/v520/precompiles.go +++ /dev/null @@ -1,119 +0,0 @@ -package v520 - -import ( - "errors" - "fmt" - "math/big" - - storetypes "github.com/cosmos/cosmos-sdk/store/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/sei-protocol/sei-chain/x/evm/state" -) - -const UnknownMethodCallGas uint64 = 3000 - -type Contexter interface { - Ctx() sdk.Context -} - -type Precompile struct { - abi.ABI -} - -func (p Precompile) RequiredGas(input []byte, isTransaction bool) uint64 { - argsBz := input[4:] // first four bytes are method ID - - if isTransaction { - return storetypes.KVGasConfig().WriteCostFlat + (storetypes.KVGasConfig().WriteCostPerByte * uint64(len(argsBz))) - } - - return storetypes.KVGasConfig().ReadCostFlat + (storetypes.KVGasConfig().ReadCostPerByte * uint64(len(argsBz))) -} - -func (p Precompile) Prepare(evm *vm.EVM, input []byte) (sdk.Context, *abi.Method, []interface{}, error) { - ctxer, ok := evm.StateDB.(Contexter) - if !ok { - return sdk.Context{}, nil, nil, errors.New("cannot get context from EVM") - } - methodID, err := ExtractMethodID(input) - if err != nil { - return sdk.Context{}, nil, nil, err - } - method, err := p.ABI.MethodById(methodID) - if err != nil { - return sdk.Context{}, nil, nil, err - } - - argsBz := input[4:] - args, err := method.Inputs.Unpack(argsBz) - if err != nil { - return sdk.Context{}, nil, nil, err - } - - return ctxer.Ctx(), method, args, nil -} - -func (p Precompile) GetABI() abi.ABI { - return p.ABI -} - -func ValidateArgsLength(args []interface{}, length int) error { - if len(args) != length { - return fmt.Errorf("expected %d arguments but got %d", length, len(args)) - } - - return nil -} - -func ValidateNonPayable(value *big.Int) error { - if value != nil && value.Sign() != 0 { - return errors.New("sending funds to a non-payable function") - } - - return nil -} - -func HandlePaymentUsei(ctx sdk.Context, precompileAddr sdk.AccAddress, payer sdk.AccAddress, value *big.Int, bankKeeper BankKeeper) (sdk.Coin, error) { - usei, wei := state.SplitUseiWeiAmount(value) - if !wei.IsZero() { - return sdk.Coin{}, fmt.Errorf("selected precompile function does not allow payment with non-zero wei remainder: received %s", value) - } - coin := sdk.NewCoin(sdk.MustGetBaseDenom(), usei) - // refund payer because the following precompile logic will debit the payments from payer's account - // this creates a new event manager to avoid surfacing these as cosmos events - if err := bankKeeper.SendCoins(ctx.WithEventManager(sdk.NewEventManager()), precompileAddr, payer, sdk.NewCoins(coin)); err != nil { - return sdk.Coin{}, err - } - return coin, nil -} - -func HandlePaymentUseiWei(ctx sdk.Context, precompileAddr sdk.AccAddress, payer sdk.AccAddress, value *big.Int, bankKeeper BankKeeper) (sdk.Int, sdk.Int, error) { - usei, wei := state.SplitUseiWeiAmount(value) - // refund payer because the following precompile logic will debit the payments from payer's account - // this creates a new event manager to avoid surfacing these as cosmos events - if err := bankKeeper.SendCoinsAndWei(ctx.WithEventManager(sdk.NewEventManager()), precompileAddr, payer, usei, wei); err != nil { - return sdk.Int{}, sdk.Int{}, err - } - return usei, wei, nil -} - -/* -* -sei gas = evm gas * multiplier -sei gas price = fee / sei gas = fee / (evm gas * multiplier) = evm gas / multiplier -*/ -func GetRemainingGas(ctx sdk.Context, evmKeeper EVMKeeper) uint64 { - gasMultipler := evmKeeper.GetPriorityNormalizer(ctx) - seiGasRemaining := ctx.GasMeter().Limit() - ctx.GasMeter().GasConsumedToLimit() - return sdk.NewDecFromInt(sdk.NewIntFromUint64(seiGasRemaining)).Quo(gasMultipler).TruncateInt().Uint64() -} - -func ExtractMethodID(input []byte) ([]byte, error) { - // Check if the input has at least the length needed for methodID - if len(input) < 4 { - return nil, errors.New("input too short to extract method ID") - } - return input[:4], nil -} diff --git a/precompiles/common/legacy/v530/expected_keepers.go b/precompiles/common/legacy/v552/expected_keepers.go similarity index 99% rename from precompiles/common/legacy/v530/expected_keepers.go rename to precompiles/common/legacy/v552/expected_keepers.go index 52ec641e56..ab9a3fac6d 100644 --- a/precompiles/common/legacy/v530/expected_keepers.go +++ b/precompiles/common/legacy/v552/expected_keepers.go @@ -1,4 +1,4 @@ -package v530 +package v552 import ( "context" diff --git a/precompiles/common/legacy/v530/precompiles.go b/precompiles/common/legacy/v552/precompiles.go similarity index 99% rename from precompiles/common/legacy/v530/precompiles.go rename to precompiles/common/legacy/v552/precompiles.go index 31823f6331..593d1e9a16 100644 --- a/precompiles/common/legacy/v530/precompiles.go +++ b/precompiles/common/legacy/v552/precompiles.go @@ -1,4 +1,4 @@ -package v530 +package v552 import ( "errors" diff --git a/precompiles/distribution/legacy/v520/abi.json b/precompiles/distribution/legacy/v520/abi.json deleted file mode 100644 index 23b0abd2df..0000000000 --- a/precompiles/distribution/legacy/v520/abi.json +++ /dev/null @@ -1 +0,0 @@ -[{"inputs":[{"internalType":"address","name":"withdrawAddr","type":"address"}],"name":"setWithdrawAddress","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"validator","type":"string"}],"name":"withdrawDelegationRewards","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/precompiles/distribution/legacy/v520/distribution.go b/precompiles/distribution/legacy/v520/distribution.go deleted file mode 100644 index f81dccf2b9..0000000000 --- a/precompiles/distribution/legacy/v520/distribution.go +++ /dev/null @@ -1,180 +0,0 @@ -package v520 - -import ( - "bytes" - "embed" - "errors" - "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/legacy/v520" -) - -const ( - SetWithdrawAddressMethod = "setWithdrawAddress" - WithdrawDelegationRewardsMethod = "withdrawDelegationRewards" -) - -const ( - DistrAddress = "0x0000000000000000000000000000000000001007" -) - -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 - -func GetABI() abi.ABI { - abiBz, err := f.ReadFile("abi.json") - if err != nil { - panic(err) - } - - newAbi, err := abi.JSON(bytes.NewReader(abiBz)) - if err != nil { - panic(err) - } - return newAbi -} - -type Precompile struct { - pcommon.Precompile - distrKeeper pcommon.DistributionKeeper - evmKeeper pcommon.EVMKeeper - address common.Address - - SetWithdrawAddrID []byte - WithdrawDelegationRewardsID []byte -} - -func NewPrecompile(distrKeeper pcommon.DistributionKeeper, evmKeeper pcommon.EVMKeeper) (*Precompile, error) { - newAbi := GetABI() - - p := &Precompile{ - Precompile: pcommon.Precompile{ABI: newAbi}, - distrKeeper: distrKeeper, - evmKeeper: evmKeeper, - address: common.HexToAddress(DistrAddress), - } - - for name, m := range newAbi.Methods { - switch name { - case SetWithdrawAddressMethod: - p.SetWithdrawAddrID = m.ID - case WithdrawDelegationRewardsMethod: - p.WithdrawDelegationRewardsID = m.ID - } - } - - return p, nil -} - -// RequiredGas returns the required bare minimum gas to execute the precompile. -func (p Precompile) RequiredGas(input []byte) uint64 { - methodID, err := pcommon.ExtractMethodID(input) - if err != nil { - return pcommon.UnknownMethodCallGas - } - - if bytes.Equal(methodID, p.SetWithdrawAddrID) { - return 30000 - } else if bytes.Equal(methodID, p.WithdrawDelegationRewardsID) { - return 50000 - } - - // This should never happen since this is going to fail during Run - return pcommon.UnknownMethodCallGas -} - -func (p Precompile) Address() common.Address { - return p.address -} - -func (p Precompile) GetName() string { - return "distribution" -} - -func (p Precompile) Run(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, value *big.Int, readOnly bool, _ bool) (bz []byte, err error) { - if readOnly { - return nil, errors.New("cannot call distr precompile from staticcall") - } - ctx, method, args, err := p.Prepare(evm, input) - if err != nil { - return nil, err - } - if caller.Cmp(callingContract) != 0 { - return nil, errors.New("cannot delegatecall distr") - } - - switch method.Name { - case SetWithdrawAddressMethod: - return p.setWithdrawAddress(ctx, method, caller, args, value) - case WithdrawDelegationRewardsMethod: - return p.withdrawDelegationRewards(ctx, method, caller, args, value) - } - return -} - -func (p Precompile) setWithdrawAddress(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { - if err := pcommon.ValidateNonPayable(value); err != nil { - return nil, err - } - - if err := pcommon.ValidateArgsLength(args, 1); err != nil { - return nil, err - } - delegator, found := p.evmKeeper.GetSeiAddress(ctx, caller) - if !found { - return nil, fmt.Errorf("delegator %s is not associated", caller.Hex()) - } - withdrawAddr, err := p.accAddressFromArg(ctx, args[0]) - if err != nil { - return nil, err - } - err = p.distrKeeper.SetWithdrawAddr(ctx, delegator, withdrawAddr) - if err != nil { - return nil, err - } - return method.Outputs.Pack(true) -} - -func (p Precompile) withdrawDelegationRewards(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { - if err := pcommon.ValidateNonPayable(value); err != nil { - return nil, err - } - - if err := pcommon.ValidateArgsLength(args, 1); err != nil { - return nil, err - } - delegator, found := p.evmKeeper.GetSeiAddress(ctx, caller) - if !found { - return nil, fmt.Errorf("delegator %s is not associated", caller.Hex()) - } - validator, err := sdk.ValAddressFromBech32(args[0].(string)) - if err != nil { - return nil, err - } - _, err = p.distrKeeper.WithdrawDelegationRewards(ctx, delegator, validator) - if err != nil { - return nil, err - } - return method.Outputs.Pack(true) -} - -func (p Precompile) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { - addr := arg.(common.Address) - if addr == (common.Address{}) { - return nil, errors.New("invalid addr") - } - seiAddr, associated := p.evmKeeper.GetSeiAddress(ctx, addr) - if !associated { - return nil, errors.New("cannot use an unassociated address as withdraw address") - } - return seiAddr, nil -} diff --git a/precompiles/gov/legacy/v520/abi.json b/precompiles/gov/legacy/v552/abi.json similarity index 100% rename from precompiles/gov/legacy/v520/abi.json rename to precompiles/gov/legacy/v552/abi.json diff --git a/precompiles/gov/legacy/v520/gov.go b/precompiles/gov/legacy/v552/gov.go similarity index 99% rename from precompiles/gov/legacy/v520/gov.go rename to precompiles/gov/legacy/v552/gov.go index 0b7c70f2a5..7491705913 100644 --- a/precompiles/gov/legacy/v520/gov.go +++ b/precompiles/gov/legacy/v552/gov.go @@ -1,4 +1,4 @@ -package v520 +package v552 import ( "bytes" @@ -12,7 +12,7 @@ import ( "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/legacy/v520" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v552" ) const ( diff --git a/precompiles/ibc/legacy/v501/abi.json b/precompiles/ibc/legacy/v501/abi.json deleted file mode 100644 index 4e2f045b42..0000000000 --- a/precompiles/ibc/legacy/v501/abi.json +++ /dev/null @@ -1,56 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "string", - "name": "toAddress", - "type": "string" - }, - { - "internalType": "string", - "name": "port", - "type": "string" - }, - { - "internalType": "string", - "name": "channel", - "type": "string" - }, - { - "internalType": "string", - "name": "denom", - "type": "string" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint64", - "name": "revisionNumber", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "revisionHeight", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "timeoutTimestamp", - "type": "uint64" - } - ], - "name": "transfer", - "outputs": [ - { - "internalType": "bool", - "name": "success", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - } - ] \ No newline at end of file diff --git a/precompiles/ibc/legacy/v501/ibc.go b/precompiles/ibc/legacy/v501/ibc.go deleted file mode 100644 index af62622fe9..0000000000 --- a/precompiles/ibc/legacy/v501/ibc.go +++ /dev/null @@ -1,268 +0,0 @@ -package v501 - -import ( - "bytes" - "embed" - "errors" - "fmt" - "math/big" - - "github.com/cosmos/cosmos-sdk/types/bech32" - - "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" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/tracing" - "github.com/ethereum/go-ethereum/core/vm" - pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v520" -) - -const ( - TransferMethod = "transfer" -) - -const ( - IBCAddress = "0x0000000000000000000000000000000000001009" -) - -var _ vm.PrecompiledContract = &Precompile{} -var _ vm.DynamicGasPrecompiledContract = &Precompile{} - -// Embed abi json file to the executable binary. Needed when importing as dependency. -// -//go:embed abi.json -var f embed.FS - -func GetABI() abi.ABI { - abiBz, err := f.ReadFile("abi.json") - if err != nil { - panic(err) - } - - newAbi, err := abi.JSON(bytes.NewReader(abiBz)) - if err != nil { - panic(err) - } - return newAbi -} - -type Precompile struct { - pcommon.Precompile - address common.Address - transferKeeper pcommon.TransferKeeper - evmKeeper pcommon.EVMKeeper - - TransferID []byte -} - -func NewPrecompile(transferKeeper pcommon.TransferKeeper, evmKeeper pcommon.EVMKeeper) (*Precompile, error) { - newAbi := GetABI() - - p := &Precompile{ - Precompile: pcommon.Precompile{ABI: newAbi}, - address: common.HexToAddress(IBCAddress), - transferKeeper: transferKeeper, - evmKeeper: evmKeeper, - } - - for name, m := range newAbi.Methods { - switch name { - case TransferMethod: - p.TransferID = m.ID - } - } - - return p, nil -} - -// RequiredGas returns the required bare minimum gas to execute the precompile. -func (p Precompile) RequiredGas(input []byte) uint64 { - methodID, err := pcommon.ExtractMethodID(input) - if err != nil { - return pcommon.UnknownMethodCallGas - } - - method, err := p.ABI.MethodById(methodID) - if err != nil { - // This should never happen since this method is going to fail during Run - return pcommon.UnknownMethodCallGas - } - - return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) -} - -func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { - if readOnly { - return nil, 0, errors.New("cannot call IBC precompile from staticcall") - } - ctx, method, args, err := p.Prepare(evm, input) - if err != nil { - return nil, 0, err - } - if caller.Cmp(callingContract) != 0 { - return nil, 0, errors.New("cannot delegatecall IBC") - } - - gasMultiplier := p.evmKeeper.GetPriorityNormalizer(ctx) - gasLimitBigInt := new(big.Int).Mul(new(big.Int).SetUint64(suppliedGas), gasMultiplier.TruncateInt().BigInt()) - if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { - gasLimitBigInt = utils.BigMaxU64 - } - ctx = ctx.WithGasMeter(sdk.NewGasMeter(gasLimitBigInt.Uint64(), 1, 1)) - - switch method.Name { - case TransferMethod: - return p.transfer(ctx, method, args, caller) - } - return -} - -func (p Precompile) Run(*vm.EVM, common.Address, common.Address, []byte, *big.Int, bool, bool) (bz []byte, err error) { - panic("static gas Run is not implemented for dynamic gas precompile") -} - -func (p Precompile) transfer(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address) (ret []byte, remainingGas uint64, rerr error) { - defer func() { - if err := recover(); err != nil { - ret = nil - remainingGas = 0 - rerr = fmt.Errorf("%s", err) - return - } - }() - - if err := pcommon.ValidateArgsLength(args, 8); err != nil { - rerr = err - return - } - senderSeiAddr, ok := p.evmKeeper.GetSeiAddress(ctx, caller) - if !ok { - rerr = errors.New("caller is not a valid SEI address") - return - } - - receiverAddressString, ok := args[0].(string) - if !ok { - rerr = errors.New("receiverAddress is not a string") - return - } - _, bz, err := bech32.DecodeAndConvert(receiverAddressString) - if err != nil { - rerr = err - return - } - err = sdk.VerifyAddressFormat(bz) - if err != nil { - rerr = err - return - } - - port, ok := args[1].(string) - if !ok { - rerr = errors.New("port is not a string") - return - } - if port == "" { - rerr = errors.New("port cannot be empty") - return - } - - channelID, ok := args[2].(string) - if !ok { - rerr = errors.New("channelID is not a string") - return - } - if channelID == "" { - rerr = errors.New("channelID cannot be empty") - return - } - - denom := args[3].(string) - if denom == "" { - rerr = errors.New("invalid denom") - return - } - - amount, ok := args[4].(*big.Int) - if !ok { - rerr = errors.New("amount is not a big.Int") - return - } - - if amount.Cmp(big.NewInt(0)) == 0 { - // short circuit - remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) - ret, rerr = method.Outputs.Pack(true) - return - } - - coin := sdk.Coin{ - Denom: denom, - Amount: sdk.NewIntFromBigInt(amount), - } - - revisionNumber, ok := args[5].(uint64) - if !ok { - rerr = errors.New("revisionNumber is not a uint64") - return - } - - revisionHeight, ok := args[6].(uint64) - if !ok { - rerr = errors.New("revisionHeight is not a uint64") - return - } - - height := clienttypes.Height{ - RevisionNumber: revisionNumber, - RevisionHeight: revisionHeight, - } - - timeoutTimestamp, ok := args[7].(uint64) - if !ok { - rerr = errors.New("timeoutTimestamp is not a uint64") - return - } - - err = p.transferKeeper.SendTransfer(ctx, port, channelID, coin, senderSeiAddr, receiverAddressString, height, timeoutTimestamp) - - if err != nil { - rerr = err - return - } - remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) - ret, rerr = method.Outputs.Pack(true) - return -} - -func (Precompile) IsTransaction(method string) bool { - switch method { - case TransferMethod: - return true - default: - return false - } -} - -func (p Precompile) Address() common.Address { - return p.address -} - -func (p Precompile) GetName() string { - return "ibc" -} - -func (p Precompile) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { - addr := arg.(common.Address) - if addr == (common.Address{}) { - return nil, errors.New("invalid addr") - } - seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, addr) - if !found { - return nil, fmt.Errorf("EVM address %s is not associated", addr.Hex()) - } - return seiAddr, nil -} diff --git a/precompiles/ibc/legacy/v510/abi.json b/precompiles/ibc/legacy/v510/abi.json deleted file mode 100644 index 4e2f045b42..0000000000 --- a/precompiles/ibc/legacy/v510/abi.json +++ /dev/null @@ -1,56 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "string", - "name": "toAddress", - "type": "string" - }, - { - "internalType": "string", - "name": "port", - "type": "string" - }, - { - "internalType": "string", - "name": "channel", - "type": "string" - }, - { - "internalType": "string", - "name": "denom", - "type": "string" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint64", - "name": "revisionNumber", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "revisionHeight", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "timeoutTimestamp", - "type": "uint64" - } - ], - "name": "transfer", - "outputs": [ - { - "internalType": "bool", - "name": "success", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - } - ] \ No newline at end of file diff --git a/precompiles/ibc/legacy/v510/ibc.go b/precompiles/ibc/legacy/v510/ibc.go deleted file mode 100644 index eb72912bb8..0000000000 --- a/precompiles/ibc/legacy/v510/ibc.go +++ /dev/null @@ -1,268 +0,0 @@ -package v510 - -import ( - "bytes" - "embed" - "errors" - "fmt" - "math/big" - - "github.com/cosmos/cosmos-sdk/types/bech32" - - "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" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/tracing" - "github.com/ethereum/go-ethereum/core/vm" - pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v520" -) - -const ( - TransferMethod = "transfer" -) - -const ( - IBCAddress = "0x0000000000000000000000000000000000001009" -) - -var _ vm.PrecompiledContract = &Precompile{} -var _ vm.DynamicGasPrecompiledContract = &Precompile{} - -// Embed abi json file to the executable binary. Needed when importing as dependency. -// -//go:embed abi.json -var f embed.FS - -func GetABI() abi.ABI { - abiBz, err := f.ReadFile("abi.json") - if err != nil { - panic(err) - } - - newAbi, err := abi.JSON(bytes.NewReader(abiBz)) - if err != nil { - panic(err) - } - return newAbi -} - -type Precompile struct { - pcommon.Precompile - address common.Address - transferKeeper pcommon.TransferKeeper - evmKeeper pcommon.EVMKeeper - - TransferID []byte -} - -func NewPrecompile(transferKeeper pcommon.TransferKeeper, evmKeeper pcommon.EVMKeeper) (*Precompile, error) { - newAbi := GetABI() - - p := &Precompile{ - Precompile: pcommon.Precompile{ABI: newAbi}, - address: common.HexToAddress(IBCAddress), - transferKeeper: transferKeeper, - evmKeeper: evmKeeper, - } - - for name, m := range newAbi.Methods { - switch name { - case TransferMethod: - p.TransferID = m.ID - } - } - - return p, nil -} - -// RequiredGas returns the required bare minimum gas to execute the precompile. -func (p Precompile) RequiredGas(input []byte) uint64 { - methodID, err := pcommon.ExtractMethodID(input) - if err != nil { - return pcommon.UnknownMethodCallGas - } - - method, err := p.ABI.MethodById(methodID) - if err != nil { - // This should never happen since this method is going to fail during Run - return pcommon.UnknownMethodCallGas - } - - return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) -} - -func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { - if readOnly { - return nil, 0, errors.New("cannot call IBC precompile from staticcall") - } - ctx, method, args, err := p.Prepare(evm, input) - if err != nil { - return nil, 0, err - } - if caller.Cmp(callingContract) != 0 { - return nil, 0, errors.New("cannot delegatecall IBC") - } - - gasMultiplier := p.evmKeeper.GetPriorityNormalizer(ctx) - gasLimitBigInt := new(big.Int).Mul(new(big.Int).SetUint64(suppliedGas), gasMultiplier.TruncateInt().BigInt()) - if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { - gasLimitBigInt = utils.BigMaxU64 - } - ctx = ctx.WithGasMeter(sdk.NewGasMeterWithMultiplier(ctx, gasLimitBigInt.Uint64())) - - switch method.Name { - case TransferMethod: - return p.transfer(ctx, method, args, caller) - } - return -} - -func (p Precompile) Run(*vm.EVM, common.Address, common.Address, []byte, *big.Int, bool, bool) (bz []byte, err error) { - panic("static gas Run is not implemented for dynamic gas precompile") -} - -func (p Precompile) transfer(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address) (ret []byte, remainingGas uint64, rerr error) { - defer func() { - if err := recover(); err != nil { - ret = nil - remainingGas = 0 - rerr = fmt.Errorf("%s", err) - return - } - }() - - if err := pcommon.ValidateArgsLength(args, 8); err != nil { - rerr = err - return - } - senderSeiAddr, ok := p.evmKeeper.GetSeiAddress(ctx, caller) - if !ok { - rerr = errors.New("caller is not a valid SEI address") - return - } - - receiverAddressString, ok := args[0].(string) - if !ok { - rerr = errors.New("receiverAddress is not a string") - return - } - _, bz, err := bech32.DecodeAndConvert(receiverAddressString) - if err != nil { - rerr = err - return - } - err = sdk.VerifyAddressFormat(bz) - if err != nil { - rerr = err - return - } - - port, ok := args[1].(string) - if !ok { - rerr = errors.New("port is not a string") - return - } - if port == "" { - rerr = errors.New("port cannot be empty") - return - } - - channelID, ok := args[2].(string) - if !ok { - rerr = errors.New("channelID is not a string") - return - } - if channelID == "" { - rerr = errors.New("channelID cannot be empty") - return - } - - denom := args[3].(string) - if denom == "" { - rerr = errors.New("invalid denom") - return - } - - amount, ok := args[4].(*big.Int) - if !ok { - rerr = errors.New("amount is not a big.Int") - return - } - - if amount.Cmp(big.NewInt(0)) == 0 { - // short circuit - remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) - ret, rerr = method.Outputs.Pack(true) - return - } - - coin := sdk.Coin{ - Denom: denom, - Amount: sdk.NewIntFromBigInt(amount), - } - - revisionNumber, ok := args[5].(uint64) - if !ok { - rerr = errors.New("revisionNumber is not a uint64") - return - } - - revisionHeight, ok := args[6].(uint64) - if !ok { - rerr = errors.New("revisionHeight is not a uint64") - return - } - - height := clienttypes.Height{ - RevisionNumber: revisionNumber, - RevisionHeight: revisionHeight, - } - - timeoutTimestamp, ok := args[7].(uint64) - if !ok { - rerr = errors.New("timeoutTimestamp is not a uint64") - return - } - - err = p.transferKeeper.SendTransfer(ctx, port, channelID, coin, senderSeiAddr, receiverAddressString, height, timeoutTimestamp) - - if err != nil { - rerr = err - return - } - remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) - ret, rerr = method.Outputs.Pack(true) - return -} - -func (Precompile) IsTransaction(method string) bool { - switch method { - case TransferMethod: - return true - default: - return false - } -} - -func (p Precompile) Address() common.Address { - return p.address -} - -func (p Precompile) GetName() string { - return "ibc" -} - -func (p Precompile) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { - addr := arg.(common.Address) - if addr == (common.Address{}) { - return nil, errors.New("invalid addr") - } - seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, addr) - if !found { - return nil, fmt.Errorf("EVM address %s is not associated", addr.Hex()) - } - return seiAddr, nil -} diff --git a/precompiles/ibc/legacy/v520/abi.json b/precompiles/ibc/legacy/v520/abi.json deleted file mode 100644 index 4e2f045b42..0000000000 --- a/precompiles/ibc/legacy/v520/abi.json +++ /dev/null @@ -1,56 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "string", - "name": "toAddress", - "type": "string" - }, - { - "internalType": "string", - "name": "port", - "type": "string" - }, - { - "internalType": "string", - "name": "channel", - "type": "string" - }, - { - "internalType": "string", - "name": "denom", - "type": "string" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint64", - "name": "revisionNumber", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "revisionHeight", - "type": "uint64" - }, - { - "internalType": "uint64", - "name": "timeoutTimestamp", - "type": "uint64" - } - ], - "name": "transfer", - "outputs": [ - { - "internalType": "bool", - "name": "success", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - } - ] \ No newline at end of file diff --git a/precompiles/ibc/legacy/v520/ibc.go b/precompiles/ibc/legacy/v520/ibc.go deleted file mode 100644 index e2b13a0ffb..0000000000 --- a/precompiles/ibc/legacy/v520/ibc.go +++ /dev/null @@ -1,268 +0,0 @@ -package v520 - -import ( - "bytes" - "embed" - "errors" - "fmt" - "math/big" - - "github.com/cosmos/cosmos-sdk/types/bech32" - - "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" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/tracing" - "github.com/ethereum/go-ethereum/core/vm" - pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v520" -) - -const ( - TransferMethod = "transfer" -) - -const ( - IBCAddress = "0x0000000000000000000000000000000000001009" -) - -var _ vm.PrecompiledContract = &Precompile{} -var _ vm.DynamicGasPrecompiledContract = &Precompile{} - -// Embed abi json file to the executable binary. Needed when importing as dependency. -// -//go:embed abi.json -var f embed.FS - -func GetABI() abi.ABI { - abiBz, err := f.ReadFile("abi.json") - if err != nil { - panic(err) - } - - newAbi, err := abi.JSON(bytes.NewReader(abiBz)) - if err != nil { - panic(err) - } - return newAbi -} - -type Precompile struct { - pcommon.Precompile - address common.Address - transferKeeper pcommon.TransferKeeper - evmKeeper pcommon.EVMKeeper - - TransferID []byte -} - -func NewPrecompile(transferKeeper pcommon.TransferKeeper, evmKeeper pcommon.EVMKeeper) (*Precompile, error) { - newAbi := GetABI() - - p := &Precompile{ - Precompile: pcommon.Precompile{ABI: newAbi}, - address: common.HexToAddress(IBCAddress), - transferKeeper: transferKeeper, - evmKeeper: evmKeeper, - } - - for name, m := range newAbi.Methods { - switch name { - case TransferMethod: - p.TransferID = m.ID - } - } - - return p, nil -} - -// RequiredGas returns the required bare minimum gas to execute the precompile. -func (p Precompile) RequiredGas(input []byte) uint64 { - methodID, err := pcommon.ExtractMethodID(input) - if err != nil { - return pcommon.UnknownMethodCallGas - } - - method, err := p.ABI.MethodById(methodID) - if err != nil { - // This should never happen since this method is going to fail during Run - return pcommon.UnknownMethodCallGas - } - - return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) -} - -func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { - if readOnly { - return nil, 0, errors.New("cannot call IBC precompile from staticcall") - } - ctx, method, args, err := p.Prepare(evm, input) - if err != nil { - return nil, 0, err - } - if caller.Cmp(callingContract) != 0 { - return nil, 0, errors.New("cannot delegatecall IBC") - } - - gasMultiplier := p.evmKeeper.GetPriorityNormalizer(ctx) - gasLimitBigInt := new(big.Int).Mul(new(big.Int).SetUint64(suppliedGas), gasMultiplier.TruncateInt().BigInt()) - if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { - gasLimitBigInt = utils.BigMaxU64 - } - ctx = ctx.WithGasMeter(sdk.NewGasMeterWithMultiplier(ctx, gasLimitBigInt.Uint64())) - - switch method.Name { - case TransferMethod: - return p.transfer(ctx, method, args, caller) - } - return -} - -func (p Precompile) Run(*vm.EVM, common.Address, common.Address, []byte, *big.Int, bool, bool) (bz []byte, err error) { - panic("static gas Run is not implemented for dynamic gas precompile") -} - -func (p Precompile) transfer(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address) (ret []byte, remainingGas uint64, rerr error) { - defer func() { - if err := recover(); err != nil { - ret = nil - remainingGas = 0 - rerr = fmt.Errorf("%s", err) - return - } - }() - - if err := pcommon.ValidateArgsLength(args, 8); err != nil { - rerr = err - return - } - senderSeiAddr, ok := p.evmKeeper.GetSeiAddress(ctx, caller) - if !ok { - rerr = errors.New("caller is not a valid SEI address") - return - } - - receiverAddressString, ok := args[0].(string) - if !ok { - rerr = errors.New("receiverAddress is not a string") - return - } - _, bz, err := bech32.DecodeAndConvert(receiverAddressString) - if err != nil { - rerr = err - return - } - err = sdk.VerifyAddressFormat(bz) - if err != nil { - rerr = err - return - } - - port, ok := args[1].(string) - if !ok { - rerr = errors.New("port is not a string") - return - } - if port == "" { - rerr = errors.New("port cannot be empty") - return - } - - channelID, ok := args[2].(string) - if !ok { - rerr = errors.New("channelID is not a string") - return - } - if channelID == "" { - rerr = errors.New("channelID cannot be empty") - return - } - - denom := args[3].(string) - if denom == "" { - rerr = errors.New("invalid denom") - return - } - - amount, ok := args[4].(*big.Int) - if !ok { - rerr = errors.New("amount is not a big.Int") - return - } - - if amount.Cmp(big.NewInt(0)) == 0 { - // short circuit - remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) - ret, rerr = method.Outputs.Pack(true) - return - } - - coin := sdk.Coin{ - Denom: denom, - Amount: sdk.NewIntFromBigInt(amount), - } - - revisionNumber, ok := args[5].(uint64) - if !ok { - rerr = errors.New("revisionNumber is not a uint64") - return - } - - revisionHeight, ok := args[6].(uint64) - if !ok { - rerr = errors.New("revisionHeight is not a uint64") - return - } - - height := clienttypes.Height{ - RevisionNumber: revisionNumber, - RevisionHeight: revisionHeight, - } - - timeoutTimestamp, ok := args[7].(uint64) - if !ok { - rerr = errors.New("timeoutTimestamp is not a uint64") - return - } - - err = p.transferKeeper.SendTransfer(ctx, port, channelID, coin, senderSeiAddr, receiverAddressString, height, timeoutTimestamp) - - if err != nil { - rerr = err - return - } - remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) - ret, rerr = method.Outputs.Pack(true) - return -} - -func (Precompile) IsTransaction(method string) bool { - switch method { - case TransferMethod: - return true - default: - return false - } -} - -func (p Precompile) Address() common.Address { - return p.address -} - -func (p Precompile) GetName() string { - return "ibc" -} - -func (p Precompile) accAddressFromArg(ctx sdk.Context, arg interface{}) (sdk.AccAddress, error) { - addr := arg.(common.Address) - if addr == (common.Address{}) { - return nil, errors.New("invalid addr") - } - seiAddr, found := p.evmKeeper.GetSeiAddress(ctx, addr) - if !found { - return nil, fmt.Errorf("EVM address %s is not associated", addr.Hex()) - } - return seiAddr, nil -} diff --git a/precompiles/ibc/legacy/v530/abi.json b/precompiles/ibc/legacy/v552/abi.json similarity index 100% rename from precompiles/ibc/legacy/v530/abi.json rename to precompiles/ibc/legacy/v552/abi.json diff --git a/precompiles/ibc/legacy/v530/ibc.go b/precompiles/ibc/legacy/v552/ibc.go similarity index 99% rename from precompiles/ibc/legacy/v530/ibc.go rename to precompiles/ibc/legacy/v552/ibc.go index 8767c26850..5305c43957 100644 --- a/precompiles/ibc/legacy/v530/ibc.go +++ b/precompiles/ibc/legacy/v552/ibc.go @@ -1,4 +1,4 @@ -package v530 +package v552 import ( "bytes" @@ -21,7 +21,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/vm" - pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v530" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v552" ) const ( diff --git a/precompiles/json/legacy/v520/json.go b/precompiles/json/legacy/v520/json.go deleted file mode 100644 index 705cfbe3b5..0000000000 --- a/precompiles/json/legacy/v520/json.go +++ /dev/null @@ -1,211 +0,0 @@ -package v520 - -import ( - "bytes" - "embed" - gjson "encoding/json" - "errors" - "fmt" - "math/big" - "strings" - - 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/legacy/v520" - "github.com/sei-protocol/sei-chain/utils" -) - -const ( - ExtractAsBytesMethod = "extractAsBytes" - ExtractAsBytesListMethod = "extractAsBytesList" - ExtractAsUint256Method = "extractAsUint256" -) - -const JSONAddress = "0x0000000000000000000000000000000000001003" -const GasCostPerByte = 100 // TODO: parameterize - -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 - address common.Address - - ExtractAsBytesID []byte - ExtractAsBytesListID []byte - ExtractAsUint256ID []byte -} - -func NewPrecompile() (*Precompile, error) { - abiBz, err := f.ReadFile("abi.json") - if err != nil { - return nil, fmt.Errorf("error loading the staking ABI %s", err) - } - - newAbi, err := abi.JSON(bytes.NewReader(abiBz)) - if err != nil { - return nil, err - } - - p := &Precompile{ - Precompile: pcommon.Precompile{ABI: newAbi}, - address: common.HexToAddress(JSONAddress), - } - - for name, m := range newAbi.Methods { - switch name { - case ExtractAsBytesMethod: - p.ExtractAsBytesID = m.ID - case ExtractAsBytesListMethod: - p.ExtractAsBytesListID = m.ID - case ExtractAsUint256Method: - p.ExtractAsUint256ID = m.ID - } - } - - return p, nil -} - -// RequiredGas returns the required bare minimum gas to execute the precompile. -func (p Precompile) RequiredGas(input []byte) uint64 { - if len(input) < 4 { - return pcommon.UnknownMethodCallGas - } - return uint64(GasCostPerByte * (len(input) - 4)) -} - -func (Precompile) IsTransaction(string) bool { - return false -} - -func (p Precompile) Address() common.Address { - return p.address -} - -func (p Precompile) GetName() string { - return "json" -} - -func (p Precompile) Run(evm *vm.EVM, _ common.Address, _ common.Address, input []byte, value *big.Int, _ bool, _ bool) (bz []byte, err error) { - ctx, method, args, err := p.Prepare(evm, input) - if err != nil { - return nil, err - } - - switch method.Name { - case ExtractAsBytesMethod: - return p.extractAsBytes(ctx, method, args, value) - case ExtractAsBytesListMethod: - return p.extractAsBytesList(ctx, method, args, value) - case ExtractAsUint256Method: - byteArr := make([]byte, 32) - uint_, err := p.ExtractAsUint256(ctx, method, args, value) - if err != nil { - return nil, err - } - - if uint_.BitLen() > 256 { - return nil, errors.New("value does not fit in 32 bytes") - } - - uint_.FillBytes(byteArr) - return byteArr, nil - } - return -} - -func (p Precompile) extractAsBytes(_ sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { - if err := pcommon.ValidateNonPayable(value); err != nil { - return nil, err - } - - if err := pcommon.ValidateArgsLength(args, 2); err != nil { - return nil, err - } - - // type assertion will always succeed because it's already validated in p.Prepare call in Run() - bz := args[0].([]byte) - decoded := map[string]gjson.RawMessage{} - if err := gjson.Unmarshal(bz, &decoded); err != nil { - return nil, err - } - key := args[1].(string) - result, ok := decoded[key] - if !ok { - return nil, fmt.Errorf("input does not contain key %s", key) - } - // in the case of a string value, remove the quotes - if len(result) >= 2 && result[0] == '"' && result[len(result)-1] == '"' { - result = result[1 : len(result)-1] - } - - return method.Outputs.Pack([]byte(result)) -} - -func (p Precompile) extractAsBytesList(_ sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { - if err := pcommon.ValidateNonPayable(value); err != nil { - return nil, err - } - - if err := pcommon.ValidateArgsLength(args, 2); err != nil { - return nil, err - } - - // type assertion will always succeed because it's already validated in p.Prepare call in Run() - bz := args[0].([]byte) - decoded := map[string]gjson.RawMessage{} - if err := gjson.Unmarshal(bz, &decoded); err != nil { - return nil, err - } - key := args[1].(string) - result, ok := decoded[key] - if !ok { - return nil, fmt.Errorf("input does not contain key %s", key) - } - decodedResult := []gjson.RawMessage{} - if err := gjson.Unmarshal(result, &decodedResult); err != nil { - return nil, err - } - - return method.Outputs.Pack(utils.Map(decodedResult, func(r gjson.RawMessage) []byte { return []byte(r) })) -} - -func (p Precompile) ExtractAsUint256(_ sdk.Context, _ *abi.Method, args []interface{}, value *big.Int) (*big.Int, error) { - if err := pcommon.ValidateNonPayable(value); err != nil { - return nil, err - } - - if err := pcommon.ValidateArgsLength(args, 2); err != nil { - return nil, err - } - - // type assertion will always succeed because it's already validated in p.Prepare call in Run() - bz := args[0].([]byte) - decoded := map[string]gjson.RawMessage{} - if err := gjson.Unmarshal(bz, &decoded); err != nil { - return nil, err - } - key := args[1].(string) - result, ok := decoded[key] - if !ok { - return nil, fmt.Errorf("input does not contain key %s", key) - } - - // Assuming result is your byte slice - // Convert byte slice to string and trim quotation marks - strValue := strings.Trim(string(result), "\"") - - // Convert the string to big.Int - value, success := new(big.Int).SetString(strValue, 10) - if !success { - return nil, fmt.Errorf("failed to convert %s to big.Int", strValue) - } - - return value, nil -} diff --git a/precompiles/json/legacy/v530/abi.json b/precompiles/json/legacy/v530/abi.json deleted file mode 100644 index 0170dd5a9d..0000000000 --- a/precompiles/json/legacy/v530/abi.json +++ /dev/null @@ -1 +0,0 @@ -[{"inputs":[{"internalType":"bytes","name":"input","type":"bytes"},{"internalType":"string","name":"key","type":"string"}],"name":"extractAsBytes","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"input","type":"bytes"},{"internalType":"string","name":"key","type":"string"}],"name":"extractAsBytesList","outputs":[{"internalType":"bytes[]","name":"response","type":"bytes[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"input","type":"bytes"},{"internalType":"string","name":"key","type":"string"}],"name":"extractAsUint256","outputs":[{"internalType":"uint256","name":"response","type":"uint256"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/json/legacy/v520/abi.json b/precompiles/json/legacy/v552/abi.json similarity index 100% rename from precompiles/json/legacy/v520/abi.json rename to precompiles/json/legacy/v552/abi.json diff --git a/precompiles/json/legacy/v530/json.go b/precompiles/json/legacy/v552/json.go similarity index 99% rename from precompiles/json/legacy/v530/json.go rename to precompiles/json/legacy/v552/json.go index 5e29a98b66..136ab80f63 100644 --- a/precompiles/json/legacy/v530/json.go +++ b/precompiles/json/legacy/v552/json.go @@ -1,4 +1,4 @@ -package v530 +package v552 import ( "bytes" @@ -13,7 +13,7 @@ import ( "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/legacy/v530" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v552" "github.com/sei-protocol/sei-chain/utils" "github.com/sei-protocol/sei-chain/x/evm/state" ) diff --git a/precompiles/oracle/legacy/v520/abi.json b/precompiles/oracle/legacy/v552/abi.json similarity index 100% rename from precompiles/oracle/legacy/v520/abi.json rename to precompiles/oracle/legacy/v552/abi.json diff --git a/precompiles/oracle/legacy/v520/oracle.go b/precompiles/oracle/legacy/v552/oracle.go similarity index 99% rename from precompiles/oracle/legacy/v520/oracle.go rename to precompiles/oracle/legacy/v552/oracle.go index 3156e2df0c..aee7230a69 100644 --- a/precompiles/oracle/legacy/v520/oracle.go +++ b/precompiles/oracle/legacy/v552/oracle.go @@ -1,4 +1,4 @@ -package v520 +package v552 import ( "bytes" @@ -10,7 +10,7 @@ import ( "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/legacy/v520" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v552" "github.com/sei-protocol/sei-chain/x/oracle/types" ) diff --git a/precompiles/pointer/legacy/v520/pointer.go b/precompiles/pointer/legacy/v520/pointer.go deleted file mode 100644 index 21646f426f..0000000000 --- a/precompiles/pointer/legacy/v520/pointer.go +++ /dev/null @@ -1,283 +0,0 @@ -package v520 - -import ( - "bytes" - "embed" - "encoding/json" - "errors" - "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/tracing" - "github.com/ethereum/go-ethereum/core/vm" - pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v520" - "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" - "github.com/sei-protocol/sei-chain/x/evm/types" -) - -const ( - AddNativePointer = "addNativePointer" - AddCW20Pointer = "addCW20Pointer" - AddCW721Pointer = "addCW721Pointer" -) - -const PointerAddress = "0x000000000000000000000000000000000000100b" - -var _ vm.PrecompiledContract = &Precompile{} -var _ vm.DynamicGasPrecompiledContract = &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 pcommon.UnknownMethodCallGas -} - -func (p Precompile) Address() common.Address { - return p.address -} - -func (p Precompile) GetName() string { - return "pointer" -} - -func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { - if readOnly { - return nil, 0, errors.New("cannot call pointer precompile from staticcall") - } - ctx, method, args, err := p.Prepare(evm, input) - if err != nil { - return nil, 0, err - } - if caller.Cmp(callingContract) != 0 { - return nil, 0, errors.New("cannot delegatecall pointer") - } - - 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, common.Address, []byte, *big.Int, bool, bool) ([]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) { - if err := pcommon.ValidateArgsLength(args, 1); err != nil { - return nil, 0, err - } - 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 - } - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "native"), - sdk.NewAttribute(types.AttributeKeyPointerAddress, contractAddr.Hex()), sdk.NewAttribute(types.AttributeKeyPointee, token), - sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", native.CurrentVersion)))) - 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) { - if err := pcommon.ValidateArgsLength(args, 1); err != nil { - return nil, 0, err - } - cwAddr := args[0].(string) - existingAddr, existingVersion, exists := p.evmKeeper.GetERC20CW20Pointer(ctx, cwAddr) - if exists { - 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(ctx)) - } - cwAddress, err := sdk.AccAddressFromBech32(cwAddr) - if err != nil { - return nil, 0, err - } - res, err := p.wasmdKeeper.QuerySmart(ctx, cwAddress, []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 - } - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "cw20"), - sdk.NewAttribute(types.AttributeKeyPointerAddress, contractAddr.Hex()), sdk.NewAttribute(types.AttributeKeyPointee, cwAddr), - sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", cw20.CurrentVersion(ctx))))) - 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) { - if err := pcommon.ValidateArgsLength(args, 1); err != nil { - return nil, 0, err - } - 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) - } - cwAddress, err := sdk.AccAddressFromBech32(cwAddr) - if err != nil { - return nil, 0, err - } - res, err := p.wasmdKeeper.QuerySmart(ctx, cwAddress, []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 - } - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "cw721"), - sdk.NewAttribute(types.AttributeKeyPointerAddress, contractAddr.Hex()), sdk.NewAttribute(types.AttributeKeyPointee, cwAddr), - sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", cw721.CurrentVersion)))) - ret, err = method.Outputs.Pack(contractAddr) - return -} diff --git a/precompiles/pointer/legacy/v522/abi.json b/precompiles/pointer/legacy/v522/abi.json deleted file mode 100644 index 8fb8238da0..0000000000 --- a/precompiles/pointer/legacy/v522/abi.json +++ /dev/null @@ -1 +0,0 @@ -[{"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/legacy/v522/pointer.go b/precompiles/pointer/legacy/v522/pointer.go deleted file mode 100644 index dd9d41093d..0000000000 --- a/precompiles/pointer/legacy/v522/pointer.go +++ /dev/null @@ -1,289 +0,0 @@ -package v522 - -import ( - "bytes" - "embed" - "encoding/json" - "errors" - "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/tracing" - "github.com/ethereum/go-ethereum/core/vm" - pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v530" - "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" - "github.com/sei-protocol/sei-chain/x/evm/state" - "github.com/sei-protocol/sei-chain/x/evm/types" -) - -const ( - AddNativePointer = "addNativePointer" - AddCW20Pointer = "addCW20Pointer" - AddCW721Pointer = "addCW721Pointer" -) - -const PointerAddress = "0x000000000000000000000000000000000000100b" - -var _ vm.PrecompiledContract = &Precompile{} -var _ vm.DynamicGasPrecompiledContract = &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 pcommon.UnknownMethodCallGas -} - -func (p Precompile) Address() common.Address { - return p.address -} - -func (p Precompile) GetName() string { - return "pointer" -} - -func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { - defer func() { - if err != nil { - evm.StateDB.(*state.DBImpl).SetPrecompileError(err) - } - }() - if readOnly { - return nil, 0, errors.New("cannot call pointer precompile from staticcall") - } - ctx, method, args, err := p.Prepare(evm, input) - if err != nil { - return nil, 0, err - } - if caller.Cmp(callingContract) != 0 { - return nil, 0, errors.New("cannot delegatecall pointer") - } - - 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, common.Address, []byte, *big.Int, bool, bool) ([]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) { - if err := pcommon.ValidateArgsLength(args, 1); err != nil { - return nil, 0, err - } - 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 - } - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "native"), - sdk.NewAttribute(types.AttributeKeyPointerAddress, contractAddr.Hex()), sdk.NewAttribute(types.AttributeKeyPointee, token), - sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", native.CurrentVersion)))) - 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) { - if err := pcommon.ValidateArgsLength(args, 1); err != nil { - return nil, 0, err - } - cwAddr := args[0].(string) - existingAddr, existingVersion, exists := p.evmKeeper.GetERC20CW20Pointer(ctx, cwAddr) - if exists { - 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(ctx)) - } - cwAddress, err := sdk.AccAddressFromBech32(cwAddr) - if err != nil { - return nil, 0, err - } - res, err := p.wasmdKeeper.QuerySmart(ctx, cwAddress, []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 - } - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "cw20"), - sdk.NewAttribute(types.AttributeKeyPointerAddress, contractAddr.Hex()), sdk.NewAttribute(types.AttributeKeyPointee, cwAddr), - sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", cw20.CurrentVersion(ctx))))) - 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) { - if err := pcommon.ValidateArgsLength(args, 1); err != nil { - return nil, 0, err - } - 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) - } - cwAddress, err := sdk.AccAddressFromBech32(cwAddr) - if err != nil { - return nil, 0, err - } - res, err := p.wasmdKeeper.QuerySmart(ctx, cwAddress, []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 - } - ctx.EventManager().EmitEvent(sdk.NewEvent( - types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "cw721"), - sdk.NewAttribute(types.AttributeKeyPointerAddress, contractAddr.Hex()), sdk.NewAttribute(types.AttributeKeyPointee, cwAddr), - sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", cw721.CurrentVersion)))) - ret, err = method.Outputs.Pack(contractAddr) - return -} diff --git a/precompiles/pointer/legacy/v530/abi.json b/precompiles/pointer/legacy/v530/abi.json deleted file mode 100644 index 8fb8238da0..0000000000 --- a/precompiles/pointer/legacy/v530/abi.json +++ /dev/null @@ -1 +0,0 @@ -[{"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/legacy/v520/abi.json b/precompiles/pointer/legacy/v552/abi.json similarity index 100% rename from precompiles/pointer/legacy/v520/abi.json rename to precompiles/pointer/legacy/v552/abi.json diff --git a/precompiles/pointer/legacy/v530/pointer.go b/precompiles/pointer/legacy/v552/pointer.go similarity index 99% rename from precompiles/pointer/legacy/v530/pointer.go rename to precompiles/pointer/legacy/v552/pointer.go index c0aa93e979..636e5746fb 100644 --- a/precompiles/pointer/legacy/v530/pointer.go +++ b/precompiles/pointer/legacy/v552/pointer.go @@ -1,4 +1,4 @@ -package v530 +package v552 import ( "bytes" @@ -14,7 +14,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/vm" - pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v530" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v552" "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" diff --git a/precompiles/pointerview/legacy/v520/abi.json b/precompiles/pointerview/legacy/v552/abi.json similarity index 100% rename from precompiles/pointerview/legacy/v520/abi.json rename to precompiles/pointerview/legacy/v552/abi.json diff --git a/precompiles/pointerview/legacy/v520/pointerview.go b/precompiles/pointerview/legacy/v552/pointerview.go similarity index 99% rename from precompiles/pointerview/legacy/v520/pointerview.go rename to precompiles/pointerview/legacy/v552/pointerview.go index f7e20fb2f5..f1704ed1d3 100644 --- a/precompiles/pointerview/legacy/v520/pointerview.go +++ b/precompiles/pointerview/legacy/v552/pointerview.go @@ -1,4 +1,4 @@ -package v520 +package v552 import ( "bytes" @@ -10,7 +10,7 @@ import ( "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/legacy/v520" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v552" ) const ( diff --git a/precompiles/staking/legacy/v520/abi.json b/precompiles/staking/legacy/v552/abi.json similarity index 100% rename from precompiles/staking/legacy/v520/abi.json rename to precompiles/staking/legacy/v552/abi.json diff --git a/precompiles/staking/legacy/v520/staking.go b/precompiles/staking/legacy/v552/staking.go similarity index 99% rename from precompiles/staking/legacy/v520/staking.go rename to precompiles/staking/legacy/v552/staking.go index ccaed11aa6..a83c399087 100644 --- a/precompiles/staking/legacy/v520/staking.go +++ b/precompiles/staking/legacy/v552/staking.go @@ -1,4 +1,4 @@ -package v520 +package v552 import ( "bytes" @@ -12,7 +12,7 @@ import ( "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/legacy/v520" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v552" ) const ( diff --git a/precompiles/wasmd/legacy/v501/wasmd.go b/precompiles/wasmd/legacy/v501/wasmd.go deleted file mode 100644 index 427a6de00b..0000000000 --- a/precompiles/wasmd/legacy/v501/wasmd.go +++ /dev/null @@ -1,503 +0,0 @@ -package v501 - -import ( - "bytes" - "embed" - "encoding/json" - "errors" - "fmt" - "math/big" - - wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" - 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/tracing" - "github.com/ethereum/go-ethereum/core/vm" - pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v520" - "github.com/sei-protocol/sei-chain/utils" - "github.com/sei-protocol/sei-chain/x/evm/state" -) - -const ( - InstantiateMethod = "instantiate" - ExecuteMethod = "execute" - ExecuteBatchMethod = "execute_batch" - QueryMethod = "query" -) - -const WasmdAddress = "0x0000000000000000000000000000000000001002" - -var _ vm.PrecompiledContract = &Precompile{} -var _ vm.DynamicGasPrecompiledContract = &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.WasmdKeeper - wasmdViewKeeper pcommon.WasmdViewKeeper - address common.Address - - InstantiateID []byte - ExecuteID []byte - ExecuteBatchID []byte - QueryID []byte -} - -type ExecuteMsg struct { - ContractAddress string `json:"contractAddress"` - Msg []byte `json:"msg"` - Coins []byte `json:"coins"` -} - -func NewPrecompile(evmKeeper pcommon.EVMKeeper, wasmdKeeper pcommon.WasmdKeeper, wasmdViewKeeper pcommon.WasmdViewKeeper, bankKeeper pcommon.BankKeeper) (*Precompile, error) { - abiBz, err := f.ReadFile("abi.json") - if err != nil { - return nil, fmt.Errorf("error loading the staking ABI %s", err) - } - - newAbi, err := abi.JSON(bytes.NewReader(abiBz)) - if err != nil { - return nil, err - } - - p := &Precompile{ - Precompile: pcommon.Precompile{ABI: newAbi}, - wasmdKeeper: wasmdKeeper, - wasmdViewKeeper: wasmdViewKeeper, - evmKeeper: evmKeeper, - bankKeeper: bankKeeper, - address: common.HexToAddress(WasmdAddress), - } - - for name, m := range newAbi.Methods { - switch name { - case InstantiateMethod: - p.InstantiateID = m.ID - case ExecuteMethod: - p.ExecuteID = m.ID - case ExecuteBatchMethod: - p.ExecuteBatchID = m.ID - case QueryMethod: - p.QueryID = m.ID - } - } - - return p, nil -} - -// RequiredGas returns the required bare minimum gas to execute the precompile. -func (p Precompile) RequiredGas(input []byte) uint64 { - methodID, err := pcommon.ExtractMethodID(input) - if err != nil { - return pcommon.UnknownMethodCallGas - } - - method, err := p.ABI.MethodById(methodID) - if err != nil { - // This should never happen since this method is going to fail during Run - return pcommon.UnknownMethodCallGas - } - - return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) -} - -func (Precompile) IsTransaction(method string) bool { - switch method { - case ExecuteMethod: - return true - case ExecuteBatchMethod: - return true - case InstantiateMethod: - return true - default: - return false - } -} - -func (p Precompile) Address() common.Address { - return p.address -} - -func (p Precompile) GetName() string { - return "wasmd" -} - -func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { - ctx, method, args, err := p.Prepare(evm, input) - if err != nil { - return nil, 0, err - } - gasMultipler := p.evmKeeper.GetPriorityNormalizer(ctx) - gasLimitBigInt := sdk.NewDecFromInt(sdk.NewIntFromUint64(suppliedGas)).Mul(gasMultipler).TruncateInt().BigInt() - if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { - gasLimitBigInt = utils.BigMaxU64 - } - ctx = ctx.WithGasMeter(sdk.NewGasMeter(gasLimitBigInt.Uint64(), 1, 1)) - - switch method.Name { - case InstantiateMethod: - return p.instantiate(ctx, method, caller, callingContract, args, value, readOnly) - case ExecuteMethod: - return p.execute(ctx, method, caller, callingContract, args, value, readOnly) - case ExecuteBatchMethod: - return p.execute_batch(ctx, method, caller, callingContract, args, value, readOnly) - case QueryMethod: - return p.query(ctx, method, args, value) - } - return -} - -func (p Precompile) Run(*vm.EVM, common.Address, common.Address, []byte, *big.Int, bool, bool) ([]byte, error) { - panic("static gas Run is not implemented for dynamic gas precompile") -} - -func (p Precompile) instantiate(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { - defer func() { - if err := recover(); err != nil { - ret = nil - remainingGas = 0 - rerr = fmt.Errorf("%s", err) - return - } - }() - if readOnly { - rerr = errors.New("cannot call instantiate from staticcall") - return - } - if err := pcommon.ValidateArgsLength(args, 5); err != nil { - rerr = err - return - } - if caller.Cmp(callingContract) != 0 { - rerr = errors.New("cannot delegatecall instantiate") - return - } - - // type assertion will always succeed because it's already validated in p.Prepare call in Run() - codeID := args[0].(uint64) - creatorAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) - if !found { - rerr = fmt.Errorf("creator %s is not associated", caller.Hex()) - return - } - var adminAddr sdk.AccAddress - adminAddrStr := args[1].(string) - if len(adminAddrStr) > 0 { - adminAddrDecoded, err := sdk.AccAddressFromBech32(adminAddrStr) - if err != nil { - rerr = err - return - } - adminAddr = adminAddrDecoded - } - msg := args[2].([]byte) - label := args[3].(string) - coins := sdk.NewCoins() - coinsBz := args[4].([]byte) - - if err := json.Unmarshal(coinsBz, &coins); err != nil { - rerr = err - return - } - coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() - if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { - rerr = errors.New("coin amount must equal value specified") - return - } - - // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd - msgInstantiate := wasmtypes.MsgInstantiateContract{ - Sender: creatorAddr.String(), - CodeID: codeID, - Label: label, - Funds: coins, - Msg: msg, - Admin: adminAddrStr, - } - - if err := msgInstantiate.ValidateBasic(); err != nil { - rerr = err - return - } - useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) - if value != nil && !useiAmt.IsZero() { - useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() - coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), creatorAddr, useiAmtAsWei, p.bankKeeper) - if err != nil { - rerr = err - return - } - // sanity check coin amounts match - if !coin.Amount.Equal(useiAmt) { - rerr = errors.New("mismatch between coins and payment value") - return - } - } - - addr, data, err := p.wasmdKeeper.Instantiate(ctx, codeID, creatorAddr, adminAddr, msg, label, coins) - if err != nil { - rerr = err - return - } - ret, rerr = method.Outputs.Pack(addr.String(), data) - remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) - return -} - -func (p Precompile) execute_batch(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { - defer func() { - if err := recover(); err != nil { - ret = nil - remainingGas = 0 - rerr = fmt.Errorf("%s", err) - return - } - }() - if readOnly { - rerr = errors.New("cannot call execute from staticcall") - return - } - - if err := pcommon.ValidateArgsLength(args, 1); err != nil { - rerr = err - return - } - - executeMsgs := args[0].([]struct { - ContractAddress string `json:"contractAddress"` - Msg []byte `json:"msg"` - Coins []byte `json:"coins"` - }) - - responses := make([][]byte, 0, len(executeMsgs)) - - // validate coins add up to value - validateValue := big.NewInt(0) - for i := 0; i < len(executeMsgs); i++ { - executeMsg := ExecuteMsg(executeMsgs[i]) - coinsBz := executeMsg.Coins - coins := sdk.NewCoins() - if err := json.Unmarshal(coinsBz, &coins); err != nil { - rerr = err - return - } - messageAmount := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() - validateValue.Add(validateValue, messageAmount) - } - // if validateValue is greater than zero, then value must be provided, and they must be equal - if (value == nil && validateValue.Sign() == 1) || (value != nil && validateValue.Cmp(value) != 0) { - rerr = errors.New("sum of coin amounts must equal value specified") - return - } - for i := 0; i < len(executeMsgs); i++ { - executeMsg := ExecuteMsg(executeMsgs[i]) - - // type assertion will always succeed because it's already validated in p.Prepare call in Run() - contractAddrStr := executeMsg.ContractAddress - if caller.Cmp(callingContract) != 0 { - erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) - erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) - if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { - return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) - } - } - - contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) - if err != nil { - rerr = err - return - } - senderAddr := p.evmKeeper.GetSeiAddressOrDefault(ctx, caller) - msg := executeMsg.Msg - coinsBz := executeMsg.Coins - coins := sdk.NewCoins() - if err := json.Unmarshal(coinsBz, &coins); err != nil { - rerr = err - return - } - useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) - if value != nil && !useiAmt.IsZero() { - // process coin amount from the value provided - useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() - coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) - if err != nil { - rerr = err - return - } - value.Sub(value, useiAmtAsWei) - if value.Sign() == -1 { - rerr = errors.New("insufficient value provided for payment") - return - } - // sanity check coin amounts match - if !coin.Amount.Equal(useiAmt) { - rerr = errors.New("mismatch between coins and payment value") - return - } - } - // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd - msgExecute := wasmtypes.MsgExecuteContract{ - Sender: senderAddr.String(), - Contract: contractAddr.String(), - Msg: msg, - Funds: coins, - } - if err := msgExecute.ValidateBasic(); err != nil { - rerr = err - return - } - - res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) - if err != nil { - rerr = err - return - } - responses = append(responses, res) - } - if value != nil && value.Sign() != 0 { - rerr = errors.New("value remaining after execution, must match provided amounts exactly") - return - } - ret, rerr = method.Outputs.Pack(responses) - remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) - return -} - -func (p Precompile) execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { - defer func() { - if err := recover(); err != nil { - ret = nil - remainingGas = 0 - rerr = fmt.Errorf("%s", err) - return - } - }() - if readOnly { - rerr = errors.New("cannot call execute from staticcall") - return - } - if err := pcommon.ValidateArgsLength(args, 3); err != nil { - rerr = err - return - } - - // type assertion will always succeed because it's already validated in p.Prepare call in Run() - contractAddrStr := args[0].(string) - if caller.Cmp(callingContract) != 0 { - erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) - erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) - if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { - return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) - } - } - // addresses will be sent in Sei format - contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) - if err != nil { - rerr = err - return - } - senderAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) - if !found { - rerr = fmt.Errorf("sender %s is not associated", caller.Hex()) - return - } - msg := args[1].([]byte) - coins := sdk.NewCoins() - coinsBz := args[2].([]byte) - if err := json.Unmarshal(coinsBz, &coins); err != nil { - rerr = err - return - } - coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() - if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { - rerr = errors.New("coin amount must equal value specified") - return - } - - // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd - msgExecute := wasmtypes.MsgExecuteContract{ - Sender: senderAddr.String(), - Contract: contractAddr.String(), - Msg: msg, - Funds: coins, - } - - if err := msgExecute.ValidateBasic(); err != nil { - rerr = err - return - } - - useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) - if value != nil && !useiAmt.IsZero() { - useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() - coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) - if err != nil { - rerr = err - return - } - // sanity check coin amounts match - if !coin.Amount.Equal(useiAmt) { - rerr = errors.New("mismatch between coins and payment value") - return - } - } - res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) - if err != nil { - rerr = err - return - } - ret, rerr = method.Outputs.Pack(res) - remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) - return -} - -func (p Precompile) query(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { - defer func() { - if err := recover(); err != nil { - ret = nil - remainingGas = 0 - rerr = fmt.Errorf("%s", err) - return - } - }() - if err := pcommon.ValidateNonPayable(value); err != nil { - rerr = err - return - } - - if err := pcommon.ValidateArgsLength(args, 2); err != nil { - rerr = err - return - } - - contractAddrStr := args[0].(string) - // addresses will be sent in Sei format - contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) - if err != nil { - rerr = err - return - } - req := args[1].([]byte) - - rawContractMessage := wasmtypes.RawContractMessage(req) - if err := rawContractMessage.ValidateBasic(); err != nil { - rerr = err - return - } - - res, err := p.wasmdViewKeeper.QuerySmart(ctx, contractAddr, req) - if err != nil { - rerr = err - return - } - ret, rerr = method.Outputs.Pack(res) - remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) - return -} diff --git a/precompiles/wasmd/legacy/v510/abi.json b/precompiles/wasmd/legacy/v510/abi.json deleted file mode 100644 index c5e5b1b3f6..0000000000 --- a/precompiles/wasmd/legacy/v510/abi.json +++ /dev/null @@ -1 +0,0 @@ -[{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"execute","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"internalType":"struct IWasmd.ExecuteMsg[]","name":"executeMsgs","type":"tuple[]"}],"name":"execute_batch","outputs":[{"internalType":"bytes[]","name":"responses","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint64","name":"codeID","type":"uint64"},{"internalType":"string","name":"admin","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"string","name":"label","type":"string"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"instantiate","outputs":[{"internalType":"string","name":"contractAddr","type":"string"},{"internalType":"bytes","name":"data","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"req","type":"bytes"}],"name":"query","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/wasmd/legacy/v510/wasmd.go b/precompiles/wasmd/legacy/v510/wasmd.go deleted file mode 100644 index 00f2efe97a..0000000000 --- a/precompiles/wasmd/legacy/v510/wasmd.go +++ /dev/null @@ -1,503 +0,0 @@ -package v510 - -import ( - "bytes" - "embed" - "encoding/json" - "errors" - "fmt" - "math/big" - - wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" - 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/tracing" - "github.com/ethereum/go-ethereum/core/vm" - pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v520" - "github.com/sei-protocol/sei-chain/utils" - "github.com/sei-protocol/sei-chain/x/evm/state" -) - -const ( - InstantiateMethod = "instantiate" - ExecuteMethod = "execute" - ExecuteBatchMethod = "execute_batch" - QueryMethod = "query" -) - -const WasmdAddress = "0x0000000000000000000000000000000000001002" - -var _ vm.PrecompiledContract = &Precompile{} -var _ vm.DynamicGasPrecompiledContract = &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.WasmdKeeper - wasmdViewKeeper pcommon.WasmdViewKeeper - address common.Address - - InstantiateID []byte - ExecuteID []byte - ExecuteBatchID []byte - QueryID []byte -} - -type ExecuteMsg struct { - ContractAddress string `json:"contractAddress"` - Msg []byte `json:"msg"` - Coins []byte `json:"coins"` -} - -func NewPrecompile(evmKeeper pcommon.EVMKeeper, wasmdKeeper pcommon.WasmdKeeper, wasmdViewKeeper pcommon.WasmdViewKeeper, bankKeeper pcommon.BankKeeper) (*Precompile, error) { - abiBz, err := f.ReadFile("abi.json") - if err != nil { - return nil, fmt.Errorf("error loading the staking ABI %s", err) - } - - newAbi, err := abi.JSON(bytes.NewReader(abiBz)) - if err != nil { - return nil, err - } - - p := &Precompile{ - Precompile: pcommon.Precompile{ABI: newAbi}, - wasmdKeeper: wasmdKeeper, - wasmdViewKeeper: wasmdViewKeeper, - evmKeeper: evmKeeper, - bankKeeper: bankKeeper, - address: common.HexToAddress(WasmdAddress), - } - - for name, m := range newAbi.Methods { - switch name { - case InstantiateMethod: - p.InstantiateID = m.ID - case ExecuteMethod: - p.ExecuteID = m.ID - case ExecuteBatchMethod: - p.ExecuteBatchID = m.ID - case QueryMethod: - p.QueryID = m.ID - } - } - - return p, nil -} - -// RequiredGas returns the required bare minimum gas to execute the precompile. -func (p Precompile) RequiredGas(input []byte) uint64 { - methodID, err := pcommon.ExtractMethodID(input) - if err != nil { - return pcommon.UnknownMethodCallGas - } - - method, err := p.ABI.MethodById(methodID) - if err != nil { - // This should never happen since this method is going to fail during Run - return pcommon.UnknownMethodCallGas - } - - return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) -} - -func (Precompile) IsTransaction(method string) bool { - switch method { - case ExecuteMethod: - return true - case ExecuteBatchMethod: - return true - case InstantiateMethod: - return true - default: - return false - } -} - -func (p Precompile) Address() common.Address { - return p.address -} - -func (p Precompile) GetName() string { - return "wasmd" -} - -func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { - ctx, method, args, err := p.Prepare(evm, input) - if err != nil { - return nil, 0, err - } - gasMultipler := p.evmKeeper.GetPriorityNormalizer(ctx) - gasLimitBigInt := sdk.NewDecFromInt(sdk.NewIntFromUint64(suppliedGas)).Mul(gasMultipler).TruncateInt().BigInt() - if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { - gasLimitBigInt = utils.BigMaxU64 - } - ctx = ctx.WithGasMeter(sdk.NewGasMeterWithMultiplier(ctx, gasLimitBigInt.Uint64())) - - switch method.Name { - case InstantiateMethod: - return p.instantiate(ctx, method, caller, callingContract, args, value, readOnly) - case ExecuteMethod: - return p.execute(ctx, method, caller, callingContract, args, value, readOnly) - case ExecuteBatchMethod: - return p.execute_batch(ctx, method, caller, callingContract, args, value, readOnly) - case QueryMethod: - return p.query(ctx, method, args, value) - } - return -} - -func (p Precompile) Run(*vm.EVM, common.Address, common.Address, []byte, *big.Int, bool, bool) ([]byte, error) { - panic("static gas Run is not implemented for dynamic gas precompile") -} - -func (p Precompile) instantiate(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { - defer func() { - if err := recover(); err != nil { - ret = nil - remainingGas = 0 - rerr = fmt.Errorf("%s", err) - return - } - }() - if readOnly { - rerr = errors.New("cannot call instantiate from staticcall") - return - } - if err := pcommon.ValidateArgsLength(args, 5); err != nil { - rerr = err - return - } - if caller.Cmp(callingContract) != 0 { - rerr = errors.New("cannot delegatecall instantiate") - return - } - - // type assertion will always succeed because it's already validated in p.Prepare call in Run() - codeID := args[0].(uint64) - creatorAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) - if !found { - rerr = fmt.Errorf("creator %s is not associated", caller.Hex()) - return - } - var adminAddr sdk.AccAddress - adminAddrStr := args[1].(string) - if len(adminAddrStr) > 0 { - adminAddrDecoded, err := sdk.AccAddressFromBech32(adminAddrStr) - if err != nil { - rerr = err - return - } - adminAddr = adminAddrDecoded - } - msg := args[2].([]byte) - label := args[3].(string) - coins := sdk.NewCoins() - coinsBz := args[4].([]byte) - - if err := json.Unmarshal(coinsBz, &coins); err != nil { - rerr = err - return - } - coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() - if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { - rerr = errors.New("coin amount must equal value specified") - return - } - - // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd - msgInstantiate := wasmtypes.MsgInstantiateContract{ - Sender: creatorAddr.String(), - CodeID: codeID, - Label: label, - Funds: coins, - Msg: msg, - Admin: adminAddrStr, - } - - if err := msgInstantiate.ValidateBasic(); err != nil { - rerr = err - return - } - useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) - if value != nil && !useiAmt.IsZero() { - useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() - coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), creatorAddr, useiAmtAsWei, p.bankKeeper) - if err != nil { - rerr = err - return - } - // sanity check coin amounts match - if !coin.Amount.Equal(useiAmt) { - rerr = errors.New("mismatch between coins and payment value") - return - } - } - - addr, data, err := p.wasmdKeeper.Instantiate(ctx, codeID, creatorAddr, adminAddr, msg, label, coins) - if err != nil { - rerr = err - return - } - ret, rerr = method.Outputs.Pack(addr.String(), data) - remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) - return -} - -func (p Precompile) execute_batch(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { - defer func() { - if err := recover(); err != nil { - ret = nil - remainingGas = 0 - rerr = fmt.Errorf("%s", err) - return - } - }() - if readOnly { - rerr = errors.New("cannot call execute from staticcall") - return - } - - if err := pcommon.ValidateArgsLength(args, 1); err != nil { - rerr = err - return - } - - executeMsgs := args[0].([]struct { - ContractAddress string `json:"contractAddress"` - Msg []byte `json:"msg"` - Coins []byte `json:"coins"` - }) - - responses := make([][]byte, 0, len(executeMsgs)) - - // validate coins add up to value - validateValue := big.NewInt(0) - for i := 0; i < len(executeMsgs); i++ { - executeMsg := ExecuteMsg(executeMsgs[i]) - coinsBz := executeMsg.Coins - coins := sdk.NewCoins() - if err := json.Unmarshal(coinsBz, &coins); err != nil { - rerr = err - return - } - messageAmount := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() - validateValue.Add(validateValue, messageAmount) - } - // if validateValue is greater than zero, then value must be provided, and they must be equal - if (value == nil && validateValue.Sign() == 1) || (value != nil && validateValue.Cmp(value) != 0) { - rerr = errors.New("sum of coin amounts must equal value specified") - return - } - for i := 0; i < len(executeMsgs); i++ { - executeMsg := ExecuteMsg(executeMsgs[i]) - - // type assertion will always succeed because it's already validated in p.Prepare call in Run() - contractAddrStr := executeMsg.ContractAddress - if caller.Cmp(callingContract) != 0 { - erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) - erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) - if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { - return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) - } - } - - contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) - if err != nil { - rerr = err - return - } - senderAddr := p.evmKeeper.GetSeiAddressOrDefault(ctx, caller) - msg := executeMsg.Msg - coinsBz := executeMsg.Coins - coins := sdk.NewCoins() - if err := json.Unmarshal(coinsBz, &coins); err != nil { - rerr = err - return - } - useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) - if value != nil && !useiAmt.IsZero() { - // process coin amount from the value provided - useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() - coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) - if err != nil { - rerr = err - return - } - value.Sub(value, useiAmtAsWei) - if value.Sign() == -1 { - rerr = errors.New("insufficient value provided for payment") - return - } - // sanity check coin amounts match - if !coin.Amount.Equal(useiAmt) { - rerr = errors.New("mismatch between coins and payment value") - return - } - } - // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd - msgExecute := wasmtypes.MsgExecuteContract{ - Sender: senderAddr.String(), - Contract: contractAddr.String(), - Msg: msg, - Funds: coins, - } - if err := msgExecute.ValidateBasic(); err != nil { - rerr = err - return - } - - res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) - if err != nil { - rerr = err - return - } - responses = append(responses, res) - } - if value != nil && value.Sign() != 0 { - rerr = errors.New("value remaining after execution, must match provided amounts exactly") - return - } - ret, rerr = method.Outputs.Pack(responses) - remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) - return -} - -func (p Precompile) execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { - defer func() { - if err := recover(); err != nil { - ret = nil - remainingGas = 0 - rerr = fmt.Errorf("%s", err) - return - } - }() - if readOnly { - rerr = errors.New("cannot call execute from staticcall") - return - } - if err := pcommon.ValidateArgsLength(args, 3); err != nil { - rerr = err - return - } - - // type assertion will always succeed because it's already validated in p.Prepare call in Run() - contractAddrStr := args[0].(string) - if caller.Cmp(callingContract) != 0 { - erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) - erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) - if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { - return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) - } - } - // addresses will be sent in Sei format - contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) - if err != nil { - rerr = err - return - } - senderAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) - if !found { - rerr = fmt.Errorf("sender %s is not associated", caller.Hex()) - return - } - msg := args[1].([]byte) - coins := sdk.NewCoins() - coinsBz := args[2].([]byte) - if err := json.Unmarshal(coinsBz, &coins); err != nil { - rerr = err - return - } - coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() - if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { - rerr = errors.New("coin amount must equal value specified") - return - } - - // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd - msgExecute := wasmtypes.MsgExecuteContract{ - Sender: senderAddr.String(), - Contract: contractAddr.String(), - Msg: msg, - Funds: coins, - } - - if err := msgExecute.ValidateBasic(); err != nil { - rerr = err - return - } - - useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) - if value != nil && !useiAmt.IsZero() { - useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() - coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) - if err != nil { - rerr = err - return - } - // sanity check coin amounts match - if !coin.Amount.Equal(useiAmt) { - rerr = errors.New("mismatch between coins and payment value") - return - } - } - res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) - if err != nil { - rerr = err - return - } - ret, rerr = method.Outputs.Pack(res) - remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) - return -} - -func (p Precompile) query(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { - defer func() { - if err := recover(); err != nil { - ret = nil - remainingGas = 0 - rerr = fmt.Errorf("%s", err) - return - } - }() - if err := pcommon.ValidateNonPayable(value); err != nil { - rerr = err - return - } - - if err := pcommon.ValidateArgsLength(args, 2); err != nil { - rerr = err - return - } - - contractAddrStr := args[0].(string) - // addresses will be sent in Sei format - contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) - if err != nil { - rerr = err - return - } - req := args[1].([]byte) - - rawContractMessage := wasmtypes.RawContractMessage(req) - if err := rawContractMessage.ValidateBasic(); err != nil { - rerr = err - return - } - - res, err := p.wasmdViewKeeper.QuerySmart(ctx, contractAddr, req) - if err != nil { - rerr = err - return - } - ret, rerr = method.Outputs.Pack(res) - remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) - return -} diff --git a/precompiles/wasmd/legacy/v520/abi.json b/precompiles/wasmd/legacy/v520/abi.json deleted file mode 100644 index c5e5b1b3f6..0000000000 --- a/precompiles/wasmd/legacy/v520/abi.json +++ /dev/null @@ -1 +0,0 @@ -[{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"execute","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"internalType":"struct IWasmd.ExecuteMsg[]","name":"executeMsgs","type":"tuple[]"}],"name":"execute_batch","outputs":[{"internalType":"bytes[]","name":"responses","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint64","name":"codeID","type":"uint64"},{"internalType":"string","name":"admin","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"string","name":"label","type":"string"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"instantiate","outputs":[{"internalType":"string","name":"contractAddr","type":"string"},{"internalType":"bytes","name":"data","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"req","type":"bytes"}],"name":"query","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/wasmd/legacy/v520/wasmd.go b/precompiles/wasmd/legacy/v520/wasmd.go deleted file mode 100644 index e94a6c7f7e..0000000000 --- a/precompiles/wasmd/legacy/v520/wasmd.go +++ /dev/null @@ -1,503 +0,0 @@ -package v520 - -import ( - "bytes" - "embed" - "encoding/json" - "errors" - "fmt" - "math/big" - - wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" - 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/tracing" - "github.com/ethereum/go-ethereum/core/vm" - pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v520" - "github.com/sei-protocol/sei-chain/utils" - "github.com/sei-protocol/sei-chain/x/evm/state" -) - -const ( - InstantiateMethod = "instantiate" - ExecuteMethod = "execute" - ExecuteBatchMethod = "execute_batch" - QueryMethod = "query" -) - -const WasmdAddress = "0x0000000000000000000000000000000000001002" - -var _ vm.PrecompiledContract = &Precompile{} -var _ vm.DynamicGasPrecompiledContract = &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.WasmdKeeper - wasmdViewKeeper pcommon.WasmdViewKeeper - address common.Address - - InstantiateID []byte - ExecuteID []byte - ExecuteBatchID []byte - QueryID []byte -} - -type ExecuteMsg struct { - ContractAddress string `json:"contractAddress"` - Msg []byte `json:"msg"` - Coins []byte `json:"coins"` -} - -func NewPrecompile(evmKeeper pcommon.EVMKeeper, wasmdKeeper pcommon.WasmdKeeper, wasmdViewKeeper pcommon.WasmdViewKeeper, bankKeeper pcommon.BankKeeper) (*Precompile, error) { - abiBz, err := f.ReadFile("abi.json") - if err != nil { - return nil, fmt.Errorf("error loading the staking ABI %s", err) - } - - newAbi, err := abi.JSON(bytes.NewReader(abiBz)) - if err != nil { - return nil, err - } - - p := &Precompile{ - Precompile: pcommon.Precompile{ABI: newAbi}, - wasmdKeeper: wasmdKeeper, - wasmdViewKeeper: wasmdViewKeeper, - evmKeeper: evmKeeper, - bankKeeper: bankKeeper, - address: common.HexToAddress(WasmdAddress), - } - - for name, m := range newAbi.Methods { - switch name { - case InstantiateMethod: - p.InstantiateID = m.ID - case ExecuteMethod: - p.ExecuteID = m.ID - case ExecuteBatchMethod: - p.ExecuteBatchID = m.ID - case QueryMethod: - p.QueryID = m.ID - } - } - - return p, nil -} - -// RequiredGas returns the required bare minimum gas to execute the precompile. -func (p Precompile) RequiredGas(input []byte) uint64 { - methodID, err := pcommon.ExtractMethodID(input) - if err != nil { - return pcommon.UnknownMethodCallGas - } - - method, err := p.ABI.MethodById(methodID) - if err != nil { - // This should never happen since this method is going to fail during Run - return pcommon.UnknownMethodCallGas - } - - return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) -} - -func (Precompile) IsTransaction(method string) bool { - switch method { - case ExecuteMethod: - return true - case ExecuteBatchMethod: - return true - case InstantiateMethod: - return true - default: - return false - } -} - -func (p Precompile) Address() common.Address { - return p.address -} - -func (p Precompile) GetName() string { - return "wasmd" -} - -func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { - ctx, method, args, err := p.Prepare(evm, input) - if err != nil { - return nil, 0, err - } - gasMultipler := p.evmKeeper.GetPriorityNormalizer(ctx) - gasLimitBigInt := sdk.NewDecFromInt(sdk.NewIntFromUint64(suppliedGas)).Mul(gasMultipler).TruncateInt().BigInt() - if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { - gasLimitBigInt = utils.BigMaxU64 - } - ctx = ctx.WithGasMeter(sdk.NewGasMeterWithMultiplier(ctx, gasLimitBigInt.Uint64())) - - switch method.Name { - case InstantiateMethod: - return p.instantiate(ctx, method, caller, callingContract, args, value, readOnly) - case ExecuteMethod: - return p.execute(ctx, method, caller, callingContract, args, value, readOnly) - case ExecuteBatchMethod: - return p.execute_batch(ctx, method, caller, callingContract, args, value, readOnly) - case QueryMethod: - return p.query(ctx, method, args, value) - } - return -} - -func (p Precompile) Run(*vm.EVM, common.Address, common.Address, []byte, *big.Int, bool, bool) ([]byte, error) { - panic("static gas Run is not implemented for dynamic gas precompile") -} - -func (p Precompile) instantiate(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { - defer func() { - if err := recover(); err != nil { - ret = nil - remainingGas = 0 - rerr = fmt.Errorf("%s", err) - return - } - }() - if readOnly { - rerr = errors.New("cannot call instantiate from staticcall") - return - } - if err := pcommon.ValidateArgsLength(args, 5); err != nil { - rerr = err - return - } - if caller.Cmp(callingContract) != 0 { - rerr = errors.New("cannot delegatecall instantiate") - return - } - - // type assertion will always succeed because it's already validated in p.Prepare call in Run() - codeID := args[0].(uint64) - creatorAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) - if !found { - rerr = fmt.Errorf("creator %s is not associated", caller.Hex()) - return - } - var adminAddr sdk.AccAddress - adminAddrStr := args[1].(string) - if len(adminAddrStr) > 0 { - adminAddrDecoded, err := sdk.AccAddressFromBech32(adminAddrStr) - if err != nil { - rerr = err - return - } - adminAddr = adminAddrDecoded - } - msg := args[2].([]byte) - label := args[3].(string) - coins := sdk.NewCoins() - coinsBz := args[4].([]byte) - - if err := json.Unmarshal(coinsBz, &coins); err != nil { - rerr = err - return - } - coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() - if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { - rerr = errors.New("coin amount must equal value specified") - return - } - - // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd - msgInstantiate := wasmtypes.MsgInstantiateContract{ - Sender: creatorAddr.String(), - CodeID: codeID, - Label: label, - Funds: coins, - Msg: msg, - Admin: adminAddrStr, - } - - if err := msgInstantiate.ValidateBasic(); err != nil { - rerr = err - return - } - useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) - if value != nil && !useiAmt.IsZero() { - useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() - coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), creatorAddr, useiAmtAsWei, p.bankKeeper) - if err != nil { - rerr = err - return - } - // sanity check coin amounts match - if !coin.Amount.Equal(useiAmt) { - rerr = errors.New("mismatch between coins and payment value") - return - } - } - - addr, data, err := p.wasmdKeeper.Instantiate(ctx, codeID, creatorAddr, adminAddr, msg, label, coins) - if err != nil { - rerr = err - return - } - ret, rerr = method.Outputs.Pack(addr.String(), data) - remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) - return -} - -func (p Precompile) execute_batch(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { - defer func() { - if err := recover(); err != nil { - ret = nil - remainingGas = 0 - rerr = fmt.Errorf("%s", err) - return - } - }() - if readOnly { - rerr = errors.New("cannot call execute from staticcall") - return - } - - if err := pcommon.ValidateArgsLength(args, 1); err != nil { - rerr = err - return - } - - executeMsgs := args[0].([]struct { - ContractAddress string `json:"contractAddress"` - Msg []byte `json:"msg"` - Coins []byte `json:"coins"` - }) - - responses := make([][]byte, 0, len(executeMsgs)) - - // validate coins add up to value - validateValue := big.NewInt(0) - for i := 0; i < len(executeMsgs); i++ { - executeMsg := ExecuteMsg(executeMsgs[i]) - coinsBz := executeMsg.Coins - coins := sdk.NewCoins() - if err := json.Unmarshal(coinsBz, &coins); err != nil { - rerr = err - return - } - messageAmount := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() - validateValue.Add(validateValue, messageAmount) - } - // if validateValue is greater than zero, then value must be provided, and they must be equal - if (value == nil && validateValue.Sign() == 1) || (value != nil && validateValue.Cmp(value) != 0) { - rerr = errors.New("sum of coin amounts must equal value specified") - return - } - for i := 0; i < len(executeMsgs); i++ { - executeMsg := ExecuteMsg(executeMsgs[i]) - - // type assertion will always succeed because it's already validated in p.Prepare call in Run() - contractAddrStr := executeMsg.ContractAddress - if caller.Cmp(callingContract) != 0 { - erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) - erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) - if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { - return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) - } - } - - contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) - if err != nil { - rerr = err - return - } - senderAddr := p.evmKeeper.GetSeiAddressOrDefault(ctx, caller) - msg := executeMsg.Msg - coinsBz := executeMsg.Coins - coins := sdk.NewCoins() - if err := json.Unmarshal(coinsBz, &coins); err != nil { - rerr = err - return - } - useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) - if value != nil && !useiAmt.IsZero() { - // process coin amount from the value provided - useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() - coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) - if err != nil { - rerr = err - return - } - value.Sub(value, useiAmtAsWei) - if value.Sign() == -1 { - rerr = errors.New("insufficient value provided for payment") - return - } - // sanity check coin amounts match - if !coin.Amount.Equal(useiAmt) { - rerr = errors.New("mismatch between coins and payment value") - return - } - } - // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd - msgExecute := wasmtypes.MsgExecuteContract{ - Sender: senderAddr.String(), - Contract: contractAddr.String(), - Msg: msg, - Funds: coins, - } - if err := msgExecute.ValidateBasic(); err != nil { - rerr = err - return - } - - res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) - if err != nil { - rerr = err - return - } - responses = append(responses, res) - } - if value != nil && value.Sign() != 0 { - rerr = errors.New("value remaining after execution, must match provided amounts exactly") - return - } - ret, rerr = method.Outputs.Pack(responses) - remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) - return -} - -func (p Precompile) execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { - defer func() { - if err := recover(); err != nil { - ret = nil - remainingGas = 0 - rerr = fmt.Errorf("%s", err) - return - } - }() - if readOnly { - rerr = errors.New("cannot call execute from staticcall") - return - } - if err := pcommon.ValidateArgsLength(args, 3); err != nil { - rerr = err - return - } - - // type assertion will always succeed because it's already validated in p.Prepare call in Run() - contractAddrStr := args[0].(string) - if caller.Cmp(callingContract) != 0 { - erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) - erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) - if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { - return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) - } - } - // addresses will be sent in Sei format - contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) - if err != nil { - rerr = err - return - } - senderAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) - if !found { - rerr = fmt.Errorf("sender %s is not associated", caller.Hex()) - return - } - msg := args[1].([]byte) - coins := sdk.NewCoins() - coinsBz := args[2].([]byte) - if err := json.Unmarshal(coinsBz, &coins); err != nil { - rerr = err - return - } - coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() - if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { - rerr = errors.New("coin amount must equal value specified") - return - } - - // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd - msgExecute := wasmtypes.MsgExecuteContract{ - Sender: senderAddr.String(), - Contract: contractAddr.String(), - Msg: msg, - Funds: coins, - } - - if err := msgExecute.ValidateBasic(); err != nil { - rerr = err - return - } - - useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) - if value != nil && !useiAmt.IsZero() { - useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() - coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) - if err != nil { - rerr = err - return - } - // sanity check coin amounts match - if !coin.Amount.Equal(useiAmt) { - rerr = errors.New("mismatch between coins and payment value") - return - } - } - res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) - if err != nil { - rerr = err - return - } - ret, rerr = method.Outputs.Pack(res) - remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) - return -} - -func (p Precompile) query(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { - defer func() { - if err := recover(); err != nil { - ret = nil - remainingGas = 0 - rerr = fmt.Errorf("%s", err) - return - } - }() - if err := pcommon.ValidateNonPayable(value); err != nil { - rerr = err - return - } - - if err := pcommon.ValidateArgsLength(args, 2); err != nil { - rerr = err - return - } - - contractAddrStr := args[0].(string) - // addresses will be sent in Sei format - contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) - if err != nil { - rerr = err - return - } - req := args[1].([]byte) - - rawContractMessage := wasmtypes.RawContractMessage(req) - if err := rawContractMessage.ValidateBasic(); err != nil { - rerr = err - return - } - - res, err := p.wasmdViewKeeper.QuerySmart(ctx, contractAddr, req) - if err != nil { - rerr = err - return - } - ret, rerr = method.Outputs.Pack(res) - remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) - return -} diff --git a/precompiles/wasmd/legacy/v522/abi.json b/precompiles/wasmd/legacy/v522/abi.json deleted file mode 100644 index c5e5b1b3f6..0000000000 --- a/precompiles/wasmd/legacy/v522/abi.json +++ /dev/null @@ -1 +0,0 @@ -[{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"execute","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"internalType":"struct IWasmd.ExecuteMsg[]","name":"executeMsgs","type":"tuple[]"}],"name":"execute_batch","outputs":[{"internalType":"bytes[]","name":"responses","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint64","name":"codeID","type":"uint64"},{"internalType":"string","name":"admin","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"string","name":"label","type":"string"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"instantiate","outputs":[{"internalType":"string","name":"contractAddr","type":"string"},{"internalType":"bytes","name":"data","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"req","type":"bytes"}],"name":"query","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/wasmd/legacy/v522/wasmd.go b/precompiles/wasmd/legacy/v522/wasmd.go deleted file mode 100644 index 1b0abfddb3..0000000000 --- a/precompiles/wasmd/legacy/v522/wasmd.go +++ /dev/null @@ -1,508 +0,0 @@ -package v522 - -import ( - "bytes" - "embed" - "encoding/json" - "errors" - "fmt" - "math/big" - - wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" - 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/tracing" - "github.com/ethereum/go-ethereum/core/vm" - pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v530" - "github.com/sei-protocol/sei-chain/utils" - "github.com/sei-protocol/sei-chain/x/evm/state" -) - -const ( - InstantiateMethod = "instantiate" - ExecuteMethod = "execute" - ExecuteBatchMethod = "execute_batch" - QueryMethod = "query" -) - -const WasmdAddress = "0x0000000000000000000000000000000000001002" - -var _ vm.PrecompiledContract = &Precompile{} -var _ vm.DynamicGasPrecompiledContract = &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.WasmdKeeper - wasmdViewKeeper pcommon.WasmdViewKeeper - address common.Address - - InstantiateID []byte - ExecuteID []byte - ExecuteBatchID []byte - QueryID []byte -} - -type ExecuteMsg struct { - ContractAddress string `json:"contractAddress"` - Msg []byte `json:"msg"` - Coins []byte `json:"coins"` -} - -func NewPrecompile(evmKeeper pcommon.EVMKeeper, wasmdKeeper pcommon.WasmdKeeper, wasmdViewKeeper pcommon.WasmdViewKeeper, bankKeeper pcommon.BankKeeper) (*Precompile, error) { - abiBz, err := f.ReadFile("abi.json") - if err != nil { - return nil, fmt.Errorf("error loading the staking ABI %s", err) - } - - newAbi, err := abi.JSON(bytes.NewReader(abiBz)) - if err != nil { - return nil, err - } - - p := &Precompile{ - Precompile: pcommon.Precompile{ABI: newAbi}, - wasmdKeeper: wasmdKeeper, - wasmdViewKeeper: wasmdViewKeeper, - evmKeeper: evmKeeper, - bankKeeper: bankKeeper, - address: common.HexToAddress(WasmdAddress), - } - - for name, m := range newAbi.Methods { - switch name { - case InstantiateMethod: - p.InstantiateID = m.ID - case ExecuteMethod: - p.ExecuteID = m.ID - case ExecuteBatchMethod: - p.ExecuteBatchID = m.ID - case QueryMethod: - p.QueryID = m.ID - } - } - - return p, nil -} - -// RequiredGas returns the required bare minimum gas to execute the precompile. -func (p Precompile) RequiredGas(input []byte) uint64 { - methodID, err := pcommon.ExtractMethodID(input) - if err != nil { - return pcommon.UnknownMethodCallGas - } - - method, err := p.ABI.MethodById(methodID) - if err != nil { - // This should never happen since this method is going to fail during Run - return pcommon.UnknownMethodCallGas - } - - return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) -} - -func (Precompile) IsTransaction(method string) bool { - switch method { - case ExecuteMethod: - return true - case ExecuteBatchMethod: - return true - case InstantiateMethod: - return true - default: - return false - } -} - -func (p Precompile) Address() common.Address { - return p.address -} - -func (p Precompile) GetName() string { - return "wasmd" -} - -func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly bool, _ bool) (ret []byte, remainingGas uint64, err error) { - defer func() { - if err != nil { - evm.StateDB.(*state.DBImpl).SetPrecompileError(err) - } - }() - ctx, method, args, err := p.Prepare(evm, input) - if err != nil { - return nil, 0, err - } - gasMultipler := p.evmKeeper.GetPriorityNormalizer(ctx) - gasLimitBigInt := sdk.NewDecFromInt(sdk.NewIntFromUint64(suppliedGas)).Mul(gasMultipler).TruncateInt().BigInt() - if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { - gasLimitBigInt = utils.BigMaxU64 - } - ctx = ctx.WithGasMeter(sdk.NewGasMeterWithMultiplier(ctx, gasLimitBigInt.Uint64())) - - switch method.Name { - case InstantiateMethod: - return p.instantiate(ctx, method, caller, callingContract, args, value, readOnly) - case ExecuteMethod: - return p.execute(ctx, method, caller, callingContract, args, value, readOnly) - case ExecuteBatchMethod: - return p.execute_batch(ctx, method, caller, callingContract, args, value, readOnly) - case QueryMethod: - return p.query(ctx, method, args, value) - } - return -} - -func (p Precompile) Run(*vm.EVM, common.Address, common.Address, []byte, *big.Int, bool, bool) ([]byte, error) { - panic("static gas Run is not implemented for dynamic gas precompile") -} - -func (p Precompile) instantiate(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { - defer func() { - if err := recover(); err != nil { - ret = nil - remainingGas = 0 - rerr = fmt.Errorf("%s", err) - return - } - }() - if readOnly { - rerr = errors.New("cannot call instantiate from staticcall") - return - } - if err := pcommon.ValidateArgsLength(args, 5); err != nil { - rerr = err - return - } - if caller.Cmp(callingContract) != 0 { - rerr = errors.New("cannot delegatecall instantiate") - return - } - - // type assertion will always succeed because it's already validated in p.Prepare call in Run() - codeID := args[0].(uint64) - creatorAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) - if !found { - rerr = fmt.Errorf("creator %s is not associated", caller.Hex()) - return - } - var adminAddr sdk.AccAddress - adminAddrStr := args[1].(string) - if len(adminAddrStr) > 0 { - adminAddrDecoded, err := sdk.AccAddressFromBech32(adminAddrStr) - if err != nil { - rerr = err - return - } - adminAddr = adminAddrDecoded - } - msg := args[2].([]byte) - label := args[3].(string) - coins := sdk.NewCoins() - coinsBz := args[4].([]byte) - - if err := json.Unmarshal(coinsBz, &coins); err != nil { - rerr = err - return - } - coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() - if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { - rerr = errors.New("coin amount must equal value specified") - return - } - - // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd - msgInstantiate := wasmtypes.MsgInstantiateContract{ - Sender: creatorAddr.String(), - CodeID: codeID, - Label: label, - Funds: coins, - Msg: msg, - Admin: adminAddrStr, - } - - if err := msgInstantiate.ValidateBasic(); err != nil { - rerr = err - return - } - useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) - if value != nil && !useiAmt.IsZero() { - useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() - coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), creatorAddr, useiAmtAsWei, p.bankKeeper) - if err != nil { - rerr = err - return - } - // sanity check coin amounts match - if !coin.Amount.Equal(useiAmt) { - rerr = errors.New("mismatch between coins and payment value") - return - } - } - - addr, data, err := p.wasmdKeeper.Instantiate(ctx, codeID, creatorAddr, adminAddr, msg, label, coins) - if err != nil { - rerr = err - return - } - ret, rerr = method.Outputs.Pack(addr.String(), data) - remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) - return -} - -func (p Precompile) execute_batch(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { - defer func() { - if err := recover(); err != nil { - ret = nil - remainingGas = 0 - rerr = fmt.Errorf("%s", err) - return - } - }() - if readOnly { - rerr = errors.New("cannot call execute from staticcall") - return - } - - if err := pcommon.ValidateArgsLength(args, 1); err != nil { - rerr = err - return - } - - executeMsgs := args[0].([]struct { - ContractAddress string `json:"contractAddress"` - Msg []byte `json:"msg"` - Coins []byte `json:"coins"` - }) - - responses := make([][]byte, 0, len(executeMsgs)) - - // validate coins add up to value - validateValue := big.NewInt(0) - for i := 0; i < len(executeMsgs); i++ { - executeMsg := ExecuteMsg(executeMsgs[i]) - coinsBz := executeMsg.Coins - coins := sdk.NewCoins() - if err := json.Unmarshal(coinsBz, &coins); err != nil { - rerr = err - return - } - messageAmount := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() - validateValue.Add(validateValue, messageAmount) - } - // if validateValue is greater than zero, then value must be provided, and they must be equal - if (value == nil && validateValue.Sign() == 1) || (value != nil && validateValue.Cmp(value) != 0) { - rerr = errors.New("sum of coin amounts must equal value specified") - return - } - for i := 0; i < len(executeMsgs); i++ { - executeMsg := ExecuteMsg(executeMsgs[i]) - - // type assertion will always succeed because it's already validated in p.Prepare call in Run() - contractAddrStr := executeMsg.ContractAddress - if caller.Cmp(callingContract) != 0 { - erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) - erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) - if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { - return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) - } - } - - contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) - if err != nil { - rerr = err - return - } - senderAddr := p.evmKeeper.GetSeiAddressOrDefault(ctx, caller) - msg := executeMsg.Msg - coinsBz := executeMsg.Coins - coins := sdk.NewCoins() - if err := json.Unmarshal(coinsBz, &coins); err != nil { - rerr = err - return - } - useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) - if value != nil && !useiAmt.IsZero() { - // process coin amount from the value provided - useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() - coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) - if err != nil { - rerr = err - return - } - value.Sub(value, useiAmtAsWei) - if value.Sign() == -1 { - rerr = errors.New("insufficient value provided for payment") - return - } - // sanity check coin amounts match - if !coin.Amount.Equal(useiAmt) { - rerr = errors.New("mismatch between coins and payment value") - return - } - } - // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd - msgExecute := wasmtypes.MsgExecuteContract{ - Sender: senderAddr.String(), - Contract: contractAddr.String(), - Msg: msg, - Funds: coins, - } - if err := msgExecute.ValidateBasic(); err != nil { - rerr = err - return - } - - res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) - if err != nil { - rerr = err - return - } - responses = append(responses, res) - } - if value != nil && value.Sign() != 0 { - rerr = errors.New("value remaining after execution, must match provided amounts exactly") - return - } - ret, rerr = method.Outputs.Pack(responses) - remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) - return -} - -func (p Precompile) execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int, readOnly bool) (ret []byte, remainingGas uint64, rerr error) { - defer func() { - if err := recover(); err != nil { - ret = nil - remainingGas = 0 - rerr = fmt.Errorf("%s", err) - return - } - }() - if readOnly { - rerr = errors.New("cannot call execute from staticcall") - return - } - if err := pcommon.ValidateArgsLength(args, 3); err != nil { - rerr = err - return - } - - // type assertion will always succeed because it's already validated in p.Prepare call in Run() - contractAddrStr := args[0].(string) - if caller.Cmp(callingContract) != 0 { - erc20pointer, _, erc20exists := p.evmKeeper.GetERC20CW20Pointer(ctx, contractAddrStr) - erc721pointer, _, erc721exists := p.evmKeeper.GetERC721CW721Pointer(ctx, contractAddrStr) - if (!erc20exists || erc20pointer.Cmp(callingContract) != 0) && (!erc721exists || erc721pointer.Cmp(callingContract) != 0) { - return nil, 0, fmt.Errorf("%s is not a pointer of %s", callingContract.Hex(), contractAddrStr) - } - } - // addresses will be sent in Sei format - contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) - if err != nil { - rerr = err - return - } - senderAddr, found := p.evmKeeper.GetSeiAddress(ctx, caller) - if !found { - rerr = fmt.Errorf("sender %s is not associated", caller.Hex()) - return - } - msg := args[1].([]byte) - coins := sdk.NewCoins() - coinsBz := args[2].([]byte) - if err := json.Unmarshal(coinsBz, &coins); err != nil { - rerr = err - return - } - coinsValue := coins.AmountOf(sdk.MustGetBaseDenom()).Mul(state.SdkUseiToSweiMultiplier).BigInt() - if (value == nil && coinsValue.Sign() == 1) || (value != nil && coinsValue.Cmp(value) != 0) { - rerr = errors.New("coin amount must equal value specified") - return - } - - // Run basic validation, can also just expose validateLabel and validate validateWasmCode in sei-wasmd - msgExecute := wasmtypes.MsgExecuteContract{ - Sender: senderAddr.String(), - Contract: contractAddr.String(), - Msg: msg, - Funds: coins, - } - - if err := msgExecute.ValidateBasic(); err != nil { - rerr = err - return - } - - useiAmt := coins.AmountOf(sdk.MustGetBaseDenom()) - if value != nil && !useiAmt.IsZero() { - useiAmtAsWei := useiAmt.Mul(state.SdkUseiToSweiMultiplier).BigInt() - coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, useiAmtAsWei, p.bankKeeper) - if err != nil { - rerr = err - return - } - // sanity check coin amounts match - if !coin.Amount.Equal(useiAmt) { - rerr = errors.New("mismatch between coins and payment value") - return - } - } - res, err := p.wasmdKeeper.Execute(ctx, contractAddr, senderAddr, msg, coins) - if err != nil { - rerr = err - return - } - ret, rerr = method.Outputs.Pack(res) - remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) - return -} - -func (p Precompile) query(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { - defer func() { - if err := recover(); err != nil { - ret = nil - remainingGas = 0 - rerr = fmt.Errorf("%s", err) - return - } - }() - if err := pcommon.ValidateNonPayable(value); err != nil { - rerr = err - return - } - - if err := pcommon.ValidateArgsLength(args, 2); err != nil { - rerr = err - return - } - - contractAddrStr := args[0].(string) - // addresses will be sent in Sei format - contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) - if err != nil { - rerr = err - return - } - req := args[1].([]byte) - - rawContractMessage := wasmtypes.RawContractMessage(req) - if err := rawContractMessage.ValidateBasic(); err != nil { - rerr = err - return - } - - res, err := p.wasmdViewKeeper.QuerySmart(ctx, contractAddr, req) - if err != nil { - rerr = err - return - } - ret, rerr = method.Outputs.Pack(res) - remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) - return -} diff --git a/precompiles/wasmd/legacy/v530/abi.json b/precompiles/wasmd/legacy/v530/abi.json deleted file mode 100644 index c5e5b1b3f6..0000000000 --- a/precompiles/wasmd/legacy/v530/abi.json +++ /dev/null @@ -1 +0,0 @@ -[{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"execute","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"bytes","name":"coins","type":"bytes"}],"internalType":"struct IWasmd.ExecuteMsg[]","name":"executeMsgs","type":"tuple[]"}],"name":"execute_batch","outputs":[{"internalType":"bytes[]","name":"responses","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint64","name":"codeID","type":"uint64"},{"internalType":"string","name":"admin","type":"string"},{"internalType":"bytes","name":"msg","type":"bytes"},{"internalType":"string","name":"label","type":"string"},{"internalType":"bytes","name":"coins","type":"bytes"}],"name":"instantiate","outputs":[{"internalType":"string","name":"contractAddr","type":"string"},{"internalType":"bytes","name":"data","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"contractAddress","type":"string"},{"internalType":"bytes","name":"req","type":"bytes"}],"name":"query","outputs":[{"internalType":"bytes","name":"response","type":"bytes"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/wasmd/legacy/v501/abi.json b/precompiles/wasmd/legacy/v552/abi.json similarity index 100% rename from precompiles/wasmd/legacy/v501/abi.json rename to precompiles/wasmd/legacy/v552/abi.json diff --git a/precompiles/wasmd/legacy/v530/wasmd.go b/precompiles/wasmd/legacy/v552/wasmd.go similarity index 99% rename from precompiles/wasmd/legacy/v530/wasmd.go rename to precompiles/wasmd/legacy/v552/wasmd.go index 55f5d0532f..d7a9cc2c13 100644 --- a/precompiles/wasmd/legacy/v530/wasmd.go +++ b/precompiles/wasmd/legacy/v552/wasmd.go @@ -1,4 +1,4 @@ -package v530 +package v552 import ( "bytes" @@ -14,7 +14,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/vm" - pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v530" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common/legacy/v552" "github.com/sei-protocol/sei-chain/utils" "github.com/sei-protocol/sei-chain/x/evm/state" ) From 77a1e42f635e755e16cfd8ead9e979f965f9e34e Mon Sep 17 00:00:00 2001 From: Tony Chen Date: Tue, 8 Apr 2025 11:33:52 +0800 Subject: [PATCH 3/4] fix forward compat check --- evmrpc/simulate.go | 2 +- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/evmrpc/simulate.go b/evmrpc/simulate.go index 57aa584be7..31a61f2b2f 100644 --- a/evmrpc/simulate.go +++ b/evmrpc/simulate.go @@ -442,7 +442,7 @@ func (b *Backend) getHeader(blockNumber *big.Int) *ethtypes.Header { return header } -func (b *Backend) GetCustomPrecompiles() map[common.Address]vm.PrecompiledContract { +func (b *Backend) GetCustomPrecompiles(int64) map[common.Address]vm.PrecompiledContract { return b.keeper.CustomPrecompiles() } diff --git a/go.mod b/go.mod index 7a8bdf701d..37910f2a91 100644 --- a/go.mod +++ b/go.mod @@ -360,7 +360,7 @@ replace ( github.com/cosmos/cosmos-sdk => github.com/sei-protocol/sei-cosmos v0.3.56-0.20250313190228-9fb9a4fd8636 github.com/cosmos/iavl => github.com/sei-protocol/sei-iavl v0.2.0 github.com/cosmos/ibc-go/v3 => github.com/sei-protocol/sei-ibc-go/v3 v3.3.5 - github.com/ethereum/go-ethereum => github.com/sei-protocol/go-ethereum v1.13.5-sei-28 + github.com/ethereum/go-ethereum => github.com/sei-protocol/go-ethereum v1.13.5-sei-29 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/sei-protocol/sei-db => github.com/sei-protocol/sei-db v0.0.48 // Latest goleveldb is broken, we have to stick to this version diff --git a/go.sum b/go.sum index 54a71dda0b..68d307c67e 100644 --- a/go.sum +++ b/go.sum @@ -1354,8 +1354,8 @@ github.com/securego/gosec/v2 v2.11.0/go.mod h1:SX8bptShuG8reGC0XS09+a4H2BoWSJi+f github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= github.com/sei-protocol/coinbase-kryptology v0.0.0-20241210171554-278d19024e41 h1:J68EXEY5o6eKFx0/8tD4hvZT3PJN6tabomwaBfx/sXg= github.com/sei-protocol/coinbase-kryptology v0.0.0-20241210171554-278d19024e41/go.mod h1:vAKKp7/qgfMtPXMseamOlZMqK7BytjfOm0rFKWph5c4= -github.com/sei-protocol/go-ethereum v1.13.5-sei-28 h1:V+NAzqD74lL+j57u57oGv9g7gmr4VzbZStYnh5THXvI= -github.com/sei-protocol/go-ethereum v1.13.5-sei-28/go.mod h1:kcRZmuzRn1lVejiFNTz4l4W7imnpq1bDAnuKS/RyhbQ= +github.com/sei-protocol/go-ethereum v1.13.5-sei-29 h1:xxJcaeJ7D8Eq7OBhBCDjftwvLVaQZF/7DLOlfoa5P3U= +github.com/sei-protocol/go-ethereum v1.13.5-sei-29/go.mod h1:kcRZmuzRn1lVejiFNTz4l4W7imnpq1bDAnuKS/RyhbQ= github.com/sei-protocol/goutils v0.0.2 h1:Bfa7Sv+4CVLNM20QcpvGb81B8C5HkQC/kW1CQpIbXDA= github.com/sei-protocol/goutils v0.0.2/go.mod h1:iYE2DuJfEnM+APPehr2gOUXfuLuPsVxorcDO+Tzq9q8= github.com/sei-protocol/sei-cosmos v0.3.56-0.20250313190228-9fb9a4fd8636 h1:9RMstipSzuAgpwz8IcM+LvZB2frXSBolu8CRj660fI4= From c1430e345f8581e66be4be97529f7d6eb80edd7d Mon Sep 17 00:00:00 2001 From: Tony Chen Date: Tue, 8 Apr 2025 12:27:41 +0800 Subject: [PATCH 4/4] bump --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 37910f2a91..ec0d5bdbc9 100644 --- a/go.mod +++ b/go.mod @@ -360,7 +360,7 @@ replace ( github.com/cosmos/cosmos-sdk => github.com/sei-protocol/sei-cosmos v0.3.56-0.20250313190228-9fb9a4fd8636 github.com/cosmos/iavl => github.com/sei-protocol/sei-iavl v0.2.0 github.com/cosmos/ibc-go/v3 => github.com/sei-protocol/sei-ibc-go/v3 v3.3.5 - github.com/ethereum/go-ethereum => github.com/sei-protocol/go-ethereum v1.13.5-sei-29 + github.com/ethereum/go-ethereum => github.com/sei-protocol/go-ethereum v1.13.5-sei-30 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/sei-protocol/sei-db => github.com/sei-protocol/sei-db v0.0.48 // Latest goleveldb is broken, we have to stick to this version diff --git a/go.sum b/go.sum index 68d307c67e..209ef50782 100644 --- a/go.sum +++ b/go.sum @@ -1354,8 +1354,8 @@ github.com/securego/gosec/v2 v2.11.0/go.mod h1:SX8bptShuG8reGC0XS09+a4H2BoWSJi+f github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= github.com/sei-protocol/coinbase-kryptology v0.0.0-20241210171554-278d19024e41 h1:J68EXEY5o6eKFx0/8tD4hvZT3PJN6tabomwaBfx/sXg= github.com/sei-protocol/coinbase-kryptology v0.0.0-20241210171554-278d19024e41/go.mod h1:vAKKp7/qgfMtPXMseamOlZMqK7BytjfOm0rFKWph5c4= -github.com/sei-protocol/go-ethereum v1.13.5-sei-29 h1:xxJcaeJ7D8Eq7OBhBCDjftwvLVaQZF/7DLOlfoa5P3U= -github.com/sei-protocol/go-ethereum v1.13.5-sei-29/go.mod h1:kcRZmuzRn1lVejiFNTz4l4W7imnpq1bDAnuKS/RyhbQ= +github.com/sei-protocol/go-ethereum v1.13.5-sei-30 h1:8aIlzdH1LpbVLTzXRJvO/CrmxhBCXbUHvp2DRbZnfPQ= +github.com/sei-protocol/go-ethereum v1.13.5-sei-30/go.mod h1:kcRZmuzRn1lVejiFNTz4l4W7imnpq1bDAnuKS/RyhbQ= github.com/sei-protocol/goutils v0.0.2 h1:Bfa7Sv+4CVLNM20QcpvGb81B8C5HkQC/kW1CQpIbXDA= github.com/sei-protocol/goutils v0.0.2/go.mod h1:iYE2DuJfEnM+APPehr2gOUXfuLuPsVxorcDO+Tzq9q8= github.com/sei-protocol/sei-cosmos v0.3.56-0.20250313190228-9fb9a4fd8636 h1:9RMstipSzuAgpwz8IcM+LvZB2frXSBolu8CRj660fI4=