From c63564147145c6fdd3c143a6f09d76fedb68dbed Mon Sep 17 00:00:00 2001 From: codchen Date: Fri, 29 Mar 2024 15:35:56 +0800 Subject: [PATCH 01/34] Add ERC pointer registry (#1490) --- precompiles/common/expected_keepers.go | 6 + precompiles/ibc/ibc.go | 6 +- precompiles/pointer/Pointer.sol | 20 ++ precompiles/pointer/abi.json | 1 + precompiles/pointer/pointer.go | 243 ++++++++++++++++++++ precompiles/pointer/pointer_test.go | 73 ++++++ precompiles/pointerview/Pointerview.sol | 20 ++ precompiles/pointerview/abi.json | 1 + precompiles/pointerview/pointerview.go | 119 ++++++++++ precompiles/pointerview/pointerview_test.go | 67 ++++++ precompiles/setup.go | 12 + precompiles/wasmd/wasmd.go | 8 +- utils/constants.go | 1 + x/evm/artifacts/cw20/artifacts.go | 18 ++ x/evm/artifacts/cw721/artifacts.go | 18 ++ x/evm/artifacts/native/artifacts.go | 18 ++ x/evm/client/cli/tx.go | 25 +- x/evm/keeper/pointer.go | 62 +++++ x/evm/keeper/pointer_test.go | 21 ++ x/evm/types/keys.go | 29 +++ 20 files changed, 738 insertions(+), 30 deletions(-) create mode 100644 precompiles/pointer/Pointer.sol create mode 100644 precompiles/pointer/abi.json create mode 100644 precompiles/pointer/pointer.go create mode 100644 precompiles/pointer/pointer_test.go create mode 100644 precompiles/pointerview/Pointerview.sol create mode 100644 precompiles/pointerview/abi.json create mode 100644 precompiles/pointerview/pointerview.go create mode 100644 precompiles/pointerview/pointerview_test.go create mode 100644 x/evm/keeper/pointer.go create mode 100644 x/evm/keeper/pointer_test.go diff --git a/precompiles/common/expected_keepers.go b/precompiles/common/expected_keepers.go index f1ec4cfa5..ecebf3aba 100644 --- a/precompiles/common/expected_keepers.go +++ b/precompiles/common/expected_keepers.go @@ -31,6 +31,12 @@ type EVMKeeper interface { IsCodeHashWhitelistedForBankSend(ctx sdk.Context, h common.Hash) bool GetPriorityNormalizer(ctx sdk.Context) sdk.Dec GetBaseDenom(ctx sdk.Context) string + SetERC20NativePointer(ctx sdk.Context, token string, addr common.Address) error + GetERC20NativePointer(ctx sdk.Context, token string) (addr common.Address, version uint16, exists bool) + SetERC20CW20Pointer(ctx sdk.Context, cw20Address string, addr common.Address) error + GetERC20CW20Pointer(ctx sdk.Context, cw20Address string) (addr common.Address, version uint16, exists bool) + SetERC721CW721Pointer(ctx sdk.Context, cw721Address string, addr common.Address) error + GetERC721CW721Pointer(ctx sdk.Context, cw721Address string) (addr common.Address, version uint16, exists bool) } type OracleKeeper interface { diff --git a/precompiles/ibc/ibc.go b/precompiles/ibc/ibc.go index cbef49748..b83b0792b 100644 --- a/precompiles/ibc/ibc.go +++ b/precompiles/ibc/ibc.go @@ -9,7 +9,7 @@ import ( "github.com/cosmos/cosmos-sdk/types/bech32" - "github.com/sei-protocol/sei-chain/precompiles/wasmd" + "github.com/sei-protocol/sei-chain/utils" sdk "github.com/cosmos/cosmos-sdk/types" clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" @@ -100,8 +100,8 @@ func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, calli gasMultiplier := p.evmKeeper.GetPriorityNormalizer(ctx) gasLimitBigInt := new(big.Int).Mul(new(big.Int).SetUint64(suppliedGas), gasMultiplier.RoundInt().BigInt()) - if gasLimitBigInt.Cmp(wasmd.MaxUint64BigInt) > 0 { - gasLimitBigInt = wasmd.MaxUint64BigInt + if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { + gasLimitBigInt = utils.BigMaxU64 } ctx = ctx.WithGasMeter(sdk.NewGasMeter(gasLimitBigInt.Uint64())) diff --git a/precompiles/pointer/Pointer.sol b/precompiles/pointer/Pointer.sol new file mode 100644 index 000000000..d85893949 --- /dev/null +++ b/precompiles/pointer/Pointer.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +address constant POINTER_PRECOMPILE_ADDRESS = 0x000000000000000000000000000000000000100b; + +IPointer constant POINTER_CONTRACT = IPointer(POINTER_PRECOMPILE_ADDRESS); + +interface IPointer { + function addNativePointer( + string memory token + ) payable external returns (address ret); + + function addCW20Pointer( + string memory cwAddr + ) payable external returns (address ret); + + function addCW721Pointer( + string memory cwAddr + ) payable external returns (address ret); +} diff --git a/precompiles/pointer/abi.json b/precompiles/pointer/abi.json new file mode 100644 index 000000000..8fb8238da --- /dev/null +++ b/precompiles/pointer/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"cwAddr","type":"string"}],"name":"addCW20Pointer","outputs":[{"internalType":"address","name":"ret","type":"address"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"cwAddr","type":"string"}],"name":"addCW721Pointer","outputs":[{"internalType":"address","name":"ret","type":"address"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"string","name":"token","type":"string"}],"name":"addNativePointer","outputs":[{"internalType":"address","name":"ret","type":"address"}],"stateMutability":"payable","type":"function"}] \ No newline at end of file diff --git a/precompiles/pointer/pointer.go b/precompiles/pointer/pointer.go new file mode 100644 index 000000000..9bacf5d12 --- /dev/null +++ b/precompiles/pointer/pointer.go @@ -0,0 +1,243 @@ +package pointer + +import ( + "bytes" + "embed" + "encoding/json" + "fmt" + "math" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + ethabi "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common" + "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/cw20" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/cw721" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/native" +) + +const ( + AddNativePointer = "addNativePointer" + AddCW20Pointer = "addCW20Pointer" + AddCW721Pointer = "addCW721Pointer" +) + +const PointerAddress = "0x000000000000000000000000000000000000100b" + +var _ vm.PrecompiledContract = &Precompile{} + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type Precompile struct { + pcommon.Precompile + evmKeeper pcommon.EVMKeeper + bankKeeper pcommon.BankKeeper + wasmdKeeper pcommon.WasmdViewKeeper + address common.Address + + AddNativePointerID []byte + AddCW20PointerID []byte + AddCW721PointerID []byte +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper, wasmdKeeper pcommon.WasmdViewKeeper) (*Precompile, error) { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + return nil, fmt.Errorf("error loading the pointer ABI %s", err) + } + + newAbi, err := ethabi.JSON(bytes.NewReader(abiBz)) + if err != nil { + return nil, err + } + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + evmKeeper: evmKeeper, + bankKeeper: bankKeeper, + wasmdKeeper: wasmdKeeper, + address: common.HexToAddress(PointerAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case AddNativePointer: + p.AddNativePointerID = m.ID + case AddCW20Pointer: + p.AddCW20PointerID = m.ID + case AddCW721Pointer: + p.AddCW721PointerID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + // gas is calculated dynamically + return 0 +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, _ common.Address, input []byte, suppliedGas uint64, value *big.Int) (ret []byte, remainingGas uint64, err error) { + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, 0, err + } + + switch method.Name { + case AddNativePointer: + return p.AddNative(ctx, method, caller, args, value, evm, suppliedGas) + case AddCW20Pointer: + return p.AddCW20(ctx, method, caller, args, value, evm, suppliedGas) + case AddCW721Pointer: + return p.AddCW721(ctx, method, caller, args, value, evm, suppliedGas) + default: + err = fmt.Errorf("unknown method %s", method.Name) + } + return +} + +func (p Precompile) Run(*vm.EVM, common.Address, []byte, *big.Int) ([]byte, error) { + panic("static gas Run is not implemented for dynamic gas precompile") +} + +func (p Precompile) AddNative(ctx sdk.Context, method *ethabi.Method, caller common.Address, args []interface{}, value *big.Int, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + pcommon.AssertArgsLength(args, 1) + token := args[0].(string) + existingAddr, existingVersion, exists := p.evmKeeper.GetERC20NativePointer(ctx, token) + if exists && existingVersion >= native.CurrentVersion { + return nil, 0, fmt.Errorf("pointer at %s with version %d exists when trying to set pointer for version %d", existingAddr.Hex(), existingVersion, native.CurrentVersion) + } + metadata, metadataExists := p.bankKeeper.GetDenomMetaData(ctx, token) + if !metadataExists { + return nil, 0, fmt.Errorf("denom %s does not have metadata stored and thus can only have its pointer set through gov proposal", token) + } + name := metadata.Name + symbol := metadata.Symbol + var decimals uint8 + for _, denomUnit := range metadata.DenomUnits { + if denomUnit.Exponent > uint32(decimals) && denomUnit.Exponent <= math.MaxUint8 { + decimals = uint8(denomUnit.Exponent) + name = denomUnit.Denom + symbol = denomUnit.Denom + if len(denomUnit.Aliases) > 0 { + name = denomUnit.Aliases[0] + } + } + } + constructorArguments := []interface{}{ + token, name, symbol, decimals, + } + + packedArgs, err := native.GetParsedABI().Pack("", constructorArguments...) + if err != nil { + panic(err) + } + bin := append(native.GetBin(), packedArgs...) + if value == nil { + value = utils.Big0 + } + ret, contractAddr, remainingGas, err := evm.Create(vm.AccountRef(caller), bin, suppliedGas, value) + if err != nil { + return + } + err = p.evmKeeper.SetERC20NativePointer(ctx, token, contractAddr) + if err != nil { + return + } + ret, err = method.Outputs.Pack(contractAddr) + return +} + +func (p Precompile) AddCW20(ctx sdk.Context, method *ethabi.Method, caller common.Address, args []interface{}, value *big.Int, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + pcommon.AssertArgsLength(args, 1) + cwAddr := args[0].(string) + existingAddr, existingVersion, exists := p.evmKeeper.GetERC20CW20Pointer(ctx, cwAddr) + if exists && existingVersion >= cw20.CurrentVersion { + return nil, 0, fmt.Errorf("pointer at %s with version %d exists when trying to set pointer for version %d", existingAddr.Hex(), existingVersion, cw20.CurrentVersion) + } + res, err := p.wasmdKeeper.QuerySmart(ctx, sdk.MustAccAddressFromBech32(cwAddr), []byte("{\"token_info\":{}}")) + if err != nil { + return nil, 0, err + } + formattedRes := map[string]interface{}{} + if err := json.Unmarshal(res, &formattedRes); err != nil { + return nil, 0, err + } + name := formattedRes["name"].(string) + symbol := formattedRes["symbol"].(string) + constructorArguments := []interface{}{ + cwAddr, name, symbol, + } + + packedArgs, err := cw20.GetParsedABI().Pack("", constructorArguments...) + if err != nil { + panic(err) + } + bin := append(cw20.GetBin(), packedArgs...) + if value == nil { + value = utils.Big0 + } + ret, contractAddr, remainingGas, err := evm.Create(vm.AccountRef(caller), bin, suppliedGas, value) + if err != nil { + return + } + err = p.evmKeeper.SetERC20CW20Pointer(ctx, cwAddr, contractAddr) + if err != nil { + return + } + ret, err = method.Outputs.Pack(contractAddr) + return +} + +func (p Precompile) AddCW721(ctx sdk.Context, method *ethabi.Method, caller common.Address, args []interface{}, value *big.Int, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { + pcommon.AssertArgsLength(args, 1) + cwAddr := args[0].(string) + existingAddr, existingVersion, exists := p.evmKeeper.GetERC721CW721Pointer(ctx, cwAddr) + if exists && existingVersion >= cw721.CurrentVersion { + return nil, 0, fmt.Errorf("pointer at %s with version %d exists when trying to set pointer for version %d", existingAddr.Hex(), existingVersion, cw721.CurrentVersion) + } + res, err := p.wasmdKeeper.QuerySmart(ctx, sdk.MustAccAddressFromBech32(cwAddr), []byte("{\"contract_info\":{}}")) + if err != nil { + return nil, 0, err + } + formattedRes := map[string]interface{}{} + if err := json.Unmarshal(res, &formattedRes); err != nil { + return nil, 0, err + } + name := formattedRes["name"].(string) + symbol := formattedRes["symbol"].(string) + constructorArguments := []interface{}{ + cwAddr, name, symbol, + } + + packedArgs, err := cw721.GetParsedABI().Pack("", constructorArguments...) + if err != nil { + panic(err) + } + bin := append(cw721.GetBin(), packedArgs...) + if value == nil { + value = utils.Big0 + } + ret, contractAddr, remainingGas, err := evm.Create(vm.AccountRef(caller), bin, suppliedGas, value) + if err != nil { + return + } + err = p.evmKeeper.SetERC721CW721Pointer(ctx, cwAddr, contractAddr) + if err != nil { + return + } + ret, err = method.Outputs.Pack(contractAddr) + return +} diff --git a/precompiles/pointer/pointer_test.go b/precompiles/pointer/pointer_test.go new file mode 100644 index 000000000..11060b796 --- /dev/null +++ b/precompiles/pointer/pointer_test.go @@ -0,0 +1,73 @@ +package pointer_test + +import ( + "testing" + "time" + + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/sei-protocol/sei-chain/app" + "github.com/sei-protocol/sei-chain/precompiles/pointer" + testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/native" + "github.com/sei-protocol/sei-chain/x/evm/state" + "github.com/sei-protocol/sei-chain/x/evm/types" + "github.com/stretchr/testify/require" +) + +func TestAddNative(t *testing.T) { + testApp := app.Setup(false, false) + p, err := pointer.NewPrecompile(&testApp.EvmKeeper, testApp.BankKeeper, testApp.WasmKeeper) + require.Nil(t, err) + ctx := testApp.GetContextForDeliverTx([]byte{}).WithBlockTime(time.Now()) + _, caller := testkeeper.MockAddressPair() + suppliedGas := uint64(10000000) + cfg := types.DefaultChainConfig().EthereumConfig(testApp.EvmKeeper.ChainID(ctx)) + + // token has no metadata + m, err := p.ABI.MethodById(p.AddNativePointerID) + require.Nil(t, err) + args, err := m.Inputs.Pack("test") + require.Nil(t, err) + statedb := state.NewDBImpl(ctx, &testApp.EvmKeeper, true) + blockCtx, _ := testApp.EvmKeeper.GetVMBlockContext(ctx, core.GasPool(suppliedGas)) + evm := vm.NewEVM(*blockCtx, vm.TxContext{}, statedb, cfg, vm.Config{}) + _, g, err := p.RunAndCalculateGas(evm, caller, caller, append(p.AddNativePointerID, args...), suppliedGas, nil) + require.NotNil(t, err) + require.Equal(t, uint64(0), g) + _, _, exists := testApp.EvmKeeper.GetERC20NativePointer(statedb.Ctx(), "test") + require.False(t, exists) + + // token has metadata + testApp.BankKeeper.SetDenomMetaData(ctx, banktypes.Metadata{ + Base: "test", + Name: "base_name", + Symbol: "base_symbol", + DenomUnits: []*banktypes.DenomUnit{{ + Exponent: 6, + Denom: "denom", + Aliases: []string{"DENOM"}, + }}, + }) + statedb = state.NewDBImpl(ctx, &testApp.EvmKeeper, true) + evm = vm.NewEVM(*blockCtx, vm.TxContext{}, statedb, cfg, vm.Config{}) + ret, g, err := p.RunAndCalculateGas(evm, caller, caller, append(p.AddNativePointerID, args...), suppliedGas, nil) + require.Nil(t, err) + require.Equal(t, uint64(8907806), g) + outputs, err := m.Outputs.Unpack(ret) + require.Nil(t, err) + addr := outputs[0].(common.Address) + pointerAddr, version, exists := testApp.EvmKeeper.GetERC20NativePointer(statedb.Ctx(), "test") + require.Equal(t, addr, pointerAddr) + require.Equal(t, native.CurrentVersion, version) + require.True(t, exists) + + // pointer already exists + statedb = state.NewDBImpl(statedb.Ctx(), &testApp.EvmKeeper, true) + evm = vm.NewEVM(*blockCtx, vm.TxContext{}, statedb, cfg, vm.Config{}) + _, g, err = p.RunAndCalculateGas(evm, caller, caller, append(p.AddNativePointerID, args...), suppliedGas, nil) + require.NotNil(t, err) + require.Equal(t, uint64(0), g) +} diff --git a/precompiles/pointerview/Pointerview.sol b/precompiles/pointerview/Pointerview.sol new file mode 100644 index 000000000..497c977fd --- /dev/null +++ b/precompiles/pointerview/Pointerview.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +address constant POINTERVIEW_PRECOMPILE_ADDRESS = 0x000000000000000000000000000000000000100A; + +IPointerview constant POINTERVIEW_CONTRACT = IPointerview(POINTERVIEW_PRECOMPILE_ADDRESS); + +interface IPointerview { + function getNativePointer( + string memory token + ) view external returns (address addr, uint16 version, bool exists); + + function getCW20Pointer( + string memory cwAddr + ) view external returns (address addr, uint16 version, bool exists); + + function getCW721Pointer( + string memory cwAddr + ) view external returns (address addr, uint16 version, bool exists); +} diff --git a/precompiles/pointerview/abi.json b/precompiles/pointerview/abi.json new file mode 100644 index 000000000..a469eff12 --- /dev/null +++ b/precompiles/pointerview/abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"string","name":"cwAddr","type":"string"}],"name":"getCW20Pointer","outputs":[{"internalType":"address","name":"addr","type":"address"},{"internalType":"uint16","name":"version","type":"uint16"},{"internalType":"bool","name":"exists","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"cwAddr","type":"string"}],"name":"getCW721Pointer","outputs":[{"internalType":"address","name":"addr","type":"address"},{"internalType":"uint16","name":"version","type":"uint16"},{"internalType":"bool","name":"exists","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"token","type":"string"}],"name":"getNativePointer","outputs":[{"internalType":"address","name":"addr","type":"address"},{"internalType":"uint16","name":"version","type":"uint16"},{"internalType":"bool","name":"exists","type":"bool"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/precompiles/pointerview/pointerview.go b/precompiles/pointerview/pointerview.go new file mode 100644 index 000000000..937d9e968 --- /dev/null +++ b/precompiles/pointerview/pointerview.go @@ -0,0 +1,119 @@ +package pointerview + +import ( + "bytes" + "embed" + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + pcommon "github.com/sei-protocol/sei-chain/precompiles/common" +) + +const ( + GetNativePointer = "getNativePointer" + GetCW20Pointer = "getCW20Pointer" + GetCW721Pointer = "getCW721Pointer" +) + +const PointerViewAddress = "0x000000000000000000000000000000000000100A" + +var _ vm.PrecompiledContract = &Precompile{} + +// Embed abi json file to the executable binary. Needed when importing as dependency. +// +//go:embed abi.json +var f embed.FS + +type Precompile struct { + pcommon.Precompile + evmKeeper pcommon.EVMKeeper + address common.Address + + GetNativePointerID []byte + GetCW20PointerID []byte + GetCW721PointerID []byte +} + +func NewPrecompile(evmKeeper pcommon.EVMKeeper) (*Precompile, error) { + abiBz, err := f.ReadFile("abi.json") + if err != nil { + return nil, fmt.Errorf("error loading the pointer ABI %s", err) + } + + newAbi, err := abi.JSON(bytes.NewReader(abiBz)) + if err != nil { + return nil, err + } + + p := &Precompile{ + Precompile: pcommon.Precompile{ABI: newAbi}, + evmKeeper: evmKeeper, + address: common.HexToAddress(PointerViewAddress), + } + + for name, m := range newAbi.Methods { + switch name { + case GetNativePointer: + p.GetNativePointerID = m.ID + case GetCW20Pointer: + p.GetCW20PointerID = m.ID + case GetCW721Pointer: + p.GetCW721PointerID = m.ID + } + } + + return p, nil +} + +// RequiredGas returns the required bare minimum gas to execute the precompile. +func (p Precompile) RequiredGas(input []byte) uint64 { + return 2000 +} + +func (p Precompile) Address() common.Address { + return p.address +} + +func (p Precompile) Run(evm *vm.EVM, _ common.Address, input []byte, _ *big.Int) (ret []byte, err error) { + ctx, method, args, err := p.Prepare(evm, input) + if err != nil { + return nil, err + } + + switch method.Name { + case GetNativePointer: + return p.GetNative(ctx, method, args) + case GetCW20Pointer: + return p.GetCW20(ctx, method, args) + case GetCW721Pointer: + return p.GetCW721(ctx, method, args) + default: + err = fmt.Errorf("unknown method %s", method.Name) + } + return +} + +func (p Precompile) GetNative(ctx sdk.Context, method *abi.Method, args []interface{}) (ret []byte, err error) { + pcommon.AssertArgsLength(args, 1) + token := args[0].(string) + existingAddr, existingVersion, exists := p.evmKeeper.GetERC20NativePointer(ctx, token) + return method.Outputs.Pack(existingAddr, existingVersion, exists) +} + +func (p Precompile) GetCW20(ctx sdk.Context, method *abi.Method, args []interface{}) (ret []byte, err error) { + pcommon.AssertArgsLength(args, 1) + addr := args[0].(string) + existingAddr, existingVersion, exists := p.evmKeeper.GetERC20CW20Pointer(ctx, addr) + return method.Outputs.Pack(existingAddr, existingVersion, exists) +} + +func (p Precompile) GetCW721(ctx sdk.Context, method *abi.Method, args []interface{}) (ret []byte, err error) { + pcommon.AssertArgsLength(args, 1) + addr := args[0].(string) + existingAddr, existingVersion, exists := p.evmKeeper.GetERC721CW721Pointer(ctx, addr) + return method.Outputs.Pack(existingAddr, existingVersion, exists) +} diff --git a/precompiles/pointerview/pointerview_test.go b/precompiles/pointerview/pointerview_test.go new file mode 100644 index 000000000..ad6d28bff --- /dev/null +++ b/precompiles/pointerview/pointerview_test.go @@ -0,0 +1,67 @@ +package pointerview_test + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/sei-protocol/sei-chain/precompiles/pointerview" + testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/cw20" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/cw721" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/native" + "github.com/stretchr/testify/require" +) + +func TestPointerView(t *testing.T) { + k, ctx := testkeeper.MockEVMKeeper() + p, err := pointerview.NewPrecompile(k) + require.Nil(t, err) + _, pointer := testkeeper.MockAddressPair() + k.SetERC20NativePointer(ctx, "test", pointer) + k.SetERC20CW20Pointer(ctx, "test", pointer) + k.SetERC721CW721Pointer(ctx, "test", pointer) + m, err := p.ABI.MethodById(p.GetNativePointerID) + require.Nil(t, err) + ret, err := p.GetNative(ctx, m, []interface{}{"test"}) + require.Nil(t, err) + outputs, err := m.Outputs.Unpack(ret) + require.Nil(t, err) + require.Equal(t, pointer, outputs[0].(common.Address)) + require.Equal(t, native.CurrentVersion, outputs[1].(uint16)) + require.True(t, outputs[2].(bool)) + ret, err = p.GetNative(ctx, m, []interface{}{"test2"}) + require.Nil(t, err) + outputs, err = m.Outputs.Unpack(ret) + require.Nil(t, err) + require.False(t, outputs[2].(bool)) + + m, err = p.ABI.MethodById(p.GetCW20PointerID) + require.Nil(t, err) + ret, err = p.GetCW20(ctx, m, []interface{}{"test"}) + require.Nil(t, err) + outputs, err = m.Outputs.Unpack(ret) + require.Nil(t, err) + require.Equal(t, pointer, outputs[0].(common.Address)) + require.Equal(t, cw20.CurrentVersion, outputs[1].(uint16)) + require.True(t, outputs[2].(bool)) + ret, err = p.GetCW20(ctx, m, []interface{}{"test2"}) + require.Nil(t, err) + outputs, err = m.Outputs.Unpack(ret) + require.Nil(t, err) + require.False(t, outputs[2].(bool)) + + m, err = p.ABI.MethodById(p.GetCW721PointerID) + require.Nil(t, err) + ret, err = p.GetCW721(ctx, m, []interface{}{"test"}) + require.Nil(t, err) + outputs, err = m.Outputs.Unpack(ret) + require.Nil(t, err) + require.Equal(t, pointer, outputs[0].(common.Address)) + require.Equal(t, cw721.CurrentVersion, outputs[1].(uint16)) + require.True(t, outputs[2].(bool)) + ret, err = p.GetCW721(ctx, m, []interface{}{"test2"}) + require.Nil(t, err) + outputs, err = m.Outputs.Unpack(ret) + require.Nil(t, err) + require.False(t, outputs[2].(bool)) +} diff --git a/precompiles/setup.go b/precompiles/setup.go index c72869fac..a0908d496 100644 --- a/precompiles/setup.go +++ b/precompiles/setup.go @@ -13,6 +13,8 @@ import ( "github.com/sei-protocol/sei-chain/precompiles/ibc" "github.com/sei-protocol/sei-chain/precompiles/json" "github.com/sei-protocol/sei-chain/precompiles/oracle" + "github.com/sei-protocol/sei-chain/precompiles/pointer" + "github.com/sei-protocol/sei-chain/precompiles/pointerview" "github.com/sei-protocol/sei-chain/precompiles/staking" "github.com/sei-protocol/sei-chain/precompiles/wasmd" ) @@ -81,6 +83,16 @@ func InitializePrecompiles( return err } addPrecompileToVM(ibcp, ibcp.Address()) + pointerp, err := pointer.NewPrecompile(evmKeeper, bankKeeper, wasmdViewKeeper) + if err != nil { + return err + } + addPrecompileToVM(pointerp, pointerp.Address()) + pointerviewp, err := pointerview.NewPrecompile(evmKeeper) + if err != nil { + return err + } + addPrecompileToVM(pointerviewp, pointerviewp.Address()) Initialized = true return nil } diff --git a/precompiles/wasmd/wasmd.go b/precompiles/wasmd/wasmd.go index 72acb075e..213f1b5c1 100644 --- a/precompiles/wasmd/wasmd.go +++ b/precompiles/wasmd/wasmd.go @@ -6,7 +6,6 @@ import ( "encoding/json" "errors" "fmt" - "math" "math/big" sdk "github.com/cosmos/cosmos-sdk/types" @@ -14,6 +13,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" pcommon "github.com/sei-protocol/sei-chain/precompiles/common" + "github.com/sei-protocol/sei-chain/utils" ) const ( @@ -31,8 +31,6 @@ var _ vm.PrecompiledContract = &Precompile{} //go:embed abi.json var f embed.FS -var MaxUint64BigInt = new(big.Int).SetUint64(math.MaxUint64) - type Precompile struct { pcommon.Precompile evmKeeper pcommon.EVMKeeper @@ -118,8 +116,8 @@ func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, calli } gasMultipler := p.evmKeeper.GetPriorityNormalizer(ctx) gasLimitBigInt := new(big.Int).Mul(new(big.Int).SetUint64(suppliedGas), gasMultipler.RoundInt().BigInt()) - if gasLimitBigInt.Cmp(MaxUint64BigInt) > 0 { - gasLimitBigInt = MaxUint64BigInt + if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { + gasLimitBigInt = utils.BigMaxU64 } ctx = ctx.WithGasMeter(sdk.NewGasMeter(gasLimitBigInt.Uint64())) diff --git a/utils/constants.go b/utils/constants.go index 36232c00f..f25c156e1 100644 --- a/utils/constants.go +++ b/utils/constants.go @@ -14,5 +14,6 @@ var Big8 = big.NewInt(8) var Big27 = big.NewInt(27) var Big35 = big.NewInt(35) var BigMaxI64 = big.NewInt(math.MaxInt64) +var BigMaxU64 = new(big.Int).SetUint64(math.MaxUint64) var Sdk0 = sdk.NewInt(0) diff --git a/x/evm/artifacts/cw20/artifacts.go b/x/evm/artifacts/cw20/artifacts.go index 9d8092ad1..073971723 100644 --- a/x/evm/artifacts/cw20/artifacts.go +++ b/x/evm/artifacts/cw20/artifacts.go @@ -5,8 +5,13 @@ import ( "embed" "encoding/hex" "fmt" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" ) +const CurrentVersion uint16 = 1 + //go:embed CW20ERC20Pointer.abi //go:embed CW20ERC20Pointer.bin //go:embed legacy.bin @@ -14,6 +19,7 @@ var f embed.FS var cachedBin []byte var cachedLegacyBin []byte +var cachedABI *abi.ABI func GetABI() []byte { bz, err := f.ReadFile("CW20ERC20Pointer.abi") @@ -23,6 +29,18 @@ func GetABI() []byte { return bz } +func GetParsedABI() *abi.ABI { + if cachedABI != nil { + return cachedABI + } + parsedABI, err := abi.JSON(strings.NewReader(string(GetABI()))) + if err != nil { + panic(err) + } + cachedABI = &parsedABI + return cachedABI +} + func GetBin() []byte { if cachedBin != nil { return cachedBin diff --git a/x/evm/artifacts/cw721/artifacts.go b/x/evm/artifacts/cw721/artifacts.go index 27d37be4d..a2d0b5697 100644 --- a/x/evm/artifacts/cw721/artifacts.go +++ b/x/evm/artifacts/cw721/artifacts.go @@ -5,8 +5,13 @@ import ( "embed" "encoding/hex" "fmt" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" ) +const CurrentVersion uint16 = 1 + //go:embed CW721ERC721Pointer.abi //go:embed CW721ERC721Pointer.bin //go:embed legacy.bin @@ -14,6 +19,7 @@ var f embed.FS var cachedBin []byte var cachedLegacyBin []byte +var cachedABI *abi.ABI func GetABI() []byte { bz, err := f.ReadFile("CW721ERC721Pointer.abi") @@ -23,6 +29,18 @@ func GetABI() []byte { return bz } +func GetParsedABI() *abi.ABI { + if cachedABI != nil { + return cachedABI + } + parsedABI, err := abi.JSON(strings.NewReader(string(GetABI()))) + if err != nil { + panic(err) + } + cachedABI = &parsedABI + return cachedABI +} + func GetBin() []byte { if cachedBin != nil { return cachedBin diff --git a/x/evm/artifacts/native/artifacts.go b/x/evm/artifacts/native/artifacts.go index d624097d9..801f66f45 100644 --- a/x/evm/artifacts/native/artifacts.go +++ b/x/evm/artifacts/native/artifacts.go @@ -5,13 +5,19 @@ import ( "embed" "encoding/hex" "fmt" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" ) +const CurrentVersion uint16 = 1 + //go:embed NativeSeiTokensERC20.abi //go:embed NativeSeiTokensERC20.bin var f embed.FS var cachedBin []byte +var cachedABI *abi.ABI func GetABI() []byte { bz, err := f.ReadFile("NativeSeiTokensERC20.abi") @@ -21,6 +27,18 @@ func GetABI() []byte { return bz } +func GetParsedABI() *abi.ABI { + if cachedABI != nil { + return cachedABI + } + parsedABI, err := abi.JSON(strings.NewReader(string(GetABI()))) + if err != nil { + panic(err) + } + cachedABI = &parsedABI + return cachedABI +} + func GetBin() []byte { if cachedBin != nil { return cachedBin diff --git a/x/evm/client/cli/tx.go b/x/evm/client/cli/tx.go index c94cb99ba..5e31c220c 100644 --- a/x/evm/client/cli/tx.go +++ b/x/evm/client/cli/tx.go @@ -14,7 +14,6 @@ import ( "strconv" "strings" - ethabi "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" @@ -239,17 +238,11 @@ func CmdDeployErc20() *cobra.Command { } bytecode := native.GetBin() - abi := native.GetABI() - parsedABI, err := ethabi.JSON(strings.NewReader(string(abi))) - if err != nil { - fmt.Println("failed at parsing abi") - return err - } constructorArguments := []interface{}{ denom, name, symbol, uint8(decimal), } - packedArgs, err := parsedABI.Pack("", constructorArguments...) + packedArgs, err := native.GetParsedABI().Pack("", constructorArguments...) if err != nil { return err } @@ -325,17 +318,11 @@ func CmdDeployErcCw20() *cobra.Command { } bytecode := cw20.GetBin() - abi := cw20.GetABI() - parsedABI, err := ethabi.JSON(strings.NewReader(string(abi))) - if err != nil { - fmt.Println("failed at parsing abi") - return err - } constructorArguments := []interface{}{ args[0], args[1], args[2], } - packedArgs, err := parsedABI.Pack("", constructorArguments...) + packedArgs, err := cw20.GetParsedABI().Pack("", constructorArguments...) if err != nil { return err } @@ -411,17 +398,11 @@ func CmdDeployErcCw721() *cobra.Command { } bytecode := cw721.GetBin() - abi := cw721.GetABI() - parsedABI, err := ethabi.JSON(strings.NewReader(string(abi))) - if err != nil { - fmt.Println("failed at parsing abi") - return err - } constructorArguments := []interface{}{ args[0], args[1], args[2], } - packedArgs, err := parsedABI.Pack("", constructorArguments...) + packedArgs, err := cw721.GetParsedABI().Pack("", constructorArguments...) if err != nil { return err } diff --git a/x/evm/keeper/pointer.go b/x/evm/keeper/pointer.go new file mode 100644 index 000000000..0d5231830 --- /dev/null +++ b/x/evm/keeper/pointer.go @@ -0,0 +1,62 @@ +package keeper + +import ( + "encoding/binary" + "fmt" + + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/cw20" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/native" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +func (k *Keeper) SetERC20NativePointer(ctx sdk.Context, token string, addr common.Address) error { + return k.SetPointerInfo(ctx, types.PointerERC20NativeKey(token), addr, native.CurrentVersion) +} + +func (k *Keeper) GetERC20NativePointer(ctx sdk.Context, token string) (addr common.Address, version uint16, exists bool) { + return k.GetPointerInfo(ctx, types.PointerERC20NativeKey(token)) +} + +func (k *Keeper) SetERC20CW20Pointer(ctx sdk.Context, cw20Address string, addr common.Address) error { + return k.SetPointerInfo(ctx, types.PointerERC20CW20Key(cw20Address), addr, cw20.CurrentVersion) +} + +func (k *Keeper) GetERC20CW20Pointer(ctx sdk.Context, cw20Address string) (addr common.Address, version uint16, exists bool) { + return k.GetPointerInfo(ctx, types.PointerERC20CW20Key(cw20Address)) +} + +func (k *Keeper) SetERC721CW721Pointer(ctx sdk.Context, cw721Address string, addr common.Address) error { + return k.SetPointerInfo(ctx, types.PointerERC721CW721Key(cw721Address), addr, cw20.CurrentVersion) +} + +func (k *Keeper) GetERC721CW721Pointer(ctx sdk.Context, cw721Address string) (addr common.Address, version uint16, exists bool) { + return k.GetPointerInfo(ctx, types.PointerERC721CW721Key(cw721Address)) +} + +func (k *Keeper) GetPointerInfo(ctx sdk.Context, pref []byte) (addr common.Address, version uint16, exists bool) { + store := prefix.NewStore(ctx.KVStore(k.GetStoreKey()), pref) + iter := store.ReverseIterator(nil, nil) + defer iter.Close() + exists = iter.Valid() + if !exists { + return + } + version = binary.BigEndian.Uint16(iter.Key()) + addr = common.BytesToAddress(iter.Value()) + return +} + +func (k *Keeper) SetPointerInfo(ctx sdk.Context, pref []byte, addr common.Address, version uint16) error { + existingAddr, existingVersion, exists := k.GetPointerInfo(ctx, pref) + if exists && existingVersion >= version { + return fmt.Errorf("pointer at %s with version %d exists when trying to set pointer for version %d", existingAddr.Hex(), existingVersion, version) + } + store := prefix.NewStore(ctx.KVStore(k.GetStoreKey()), pref) + versionBz := make([]byte, 2) + binary.BigEndian.PutUint16(versionBz, version) + store.Set(versionBz, addr[:]) + return nil +} diff --git a/x/evm/keeper/pointer_test.go b/x/evm/keeper/pointer_test.go new file mode 100644 index 000000000..e4048af69 --- /dev/null +++ b/x/evm/keeper/pointer_test.go @@ -0,0 +1,21 @@ +package keeper_test + +import ( + "testing" + + testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" + "github.com/stretchr/testify/require" +) + +func TestSetPointer(t *testing.T) { + k, ctx := testkeeper.MockEVMKeeper() + _, pointer := testkeeper.MockAddressPair() + cw20, _ := testkeeper.MockAddressPair() + cw721, _ := testkeeper.MockAddressPair() + require.Nil(t, k.SetERC20NativePointer(ctx, "test", pointer)) + require.NotNil(t, k.SetERC20NativePointer(ctx, "test", pointer)) // already set + require.Nil(t, k.SetERC20CW20Pointer(ctx, cw20.String(), pointer)) + require.NotNil(t, k.SetERC20CW20Pointer(ctx, cw20.String(), pointer)) // already set + require.Nil(t, k.SetERC721CW721Pointer(ctx, cw721.String(), pointer)) + require.NotNil(t, k.SetERC721CW721Pointer(ctx, cw721.String(), pointer)) // already set +} diff --git a/x/evm/types/keys.go b/x/evm/types/keys.go index 2d1d94043..3a64d211d 100644 --- a/x/evm/types/keys.go +++ b/x/evm/types/keys.go @@ -45,6 +45,14 @@ var ( ReplaySeenAddrPrefix = []byte{0x12} ReplayedHeight = []byte{0x13} ReplayInitialHeight = []byte{0x14} + + PointerRegistryPrefix = []byte{0x15} +) + +var ( + PointerERC20NativePrefix = []byte{0x0} + PointerERC20CW20Prefix = []byte{0x1} + PointerERC721CW721Prefix = []byte{0x2} ) func EVMAddressToSeiAddressKey(evmAddress common.Address) []byte { @@ -74,3 +82,24 @@ func TxHashesKey(height int64) []byte { binary.BigEndian.PutUint64(bz, uint64(height)) return append(TxHashesPrefix, bz...) } + +func PointerERC20NativeKey(token string) []byte { + return append( + append(PointerRegistryPrefix, PointerERC20NativePrefix...), + []byte(token)..., + ) +} + +func PointerERC20CW20Key(cw20Address string) []byte { + return append( + append(PointerRegistryPrefix, PointerERC20CW20Prefix...), + []byte(cw20Address)..., + ) +} + +func PointerERC721CW721Key(cw721Address string) []byte { + return append( + append(PointerRegistryPrefix, PointerERC721CW721Prefix...), + []byte(cw721Address)..., + ) +} From eb2080b137c7df7abd7b5e65d3772f794a0af3bf Mon Sep 17 00:00:00 2001 From: Matthieu Vachon Date: Fri, 29 Mar 2024 09:17:35 -0400 Subject: [PATCH 02/34] Update that brings `go-ethereum` with live tracer support (#1375) * Update that brings `go-ethereum` with live tracer support This only brings the dependency (will need to wait for a merge + tag of the branch before merging this PR) and the required changes to adapt `DBImpl` to support the new interface. Relates to: - https://github.com/sei-protocol/go-ethereum/pull/15 - https://github.com/sei-protocol/sei-chain/pull/1344 * Added missing instrumentation when state changes in `DBImpl` --- app/eth_replay.go | 3 ++- evmrpc/simulate.go | 4 +-- go.mod | 2 +- go.sum | 4 +-- x/evm/module_test.go | 15 ++++++----- x/evm/state/balance.go | 29 ++++++++++++++++---- x/evm/state/balance_test.go | 53 +++++++++++++++++++------------------ x/evm/state/check_test.go | 5 ++-- x/evm/state/code.go | 10 +++++++ x/evm/state/log.go | 4 +++ x/evm/state/nonce.go | 6 +++++ x/evm/state/state.go | 8 +++++- x/evm/state/state_test.go | 5 ++-- x/evm/state/statedb.go | 8 ++++++ 14 files changed, 107 insertions(+), 49 deletions(-) diff --git a/app/eth_replay.go b/app/eth_replay.go index 2414f3f46..a16bae7ed 100644 --- a/app/eth_replay.go +++ b/app/eth_replay.go @@ -10,6 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/core/tracing" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" ethtests "github.com/ethereum/go-ethereum/tests" @@ -81,7 +82,7 @@ func Replay(a *App) { for _, w := range b.Withdrawals() { amount := new(big.Int).SetUint64(w.Amount) amount = amount.Mul(amount, big.NewInt(params.GWei)) - s.AddBalance(w.Address, amount) + s.AddBalance(w.Address, amount, tracing.BalanceIncreaseWithdrawal) } _, _ = s.Finalize() for _, tx := range b.Txs { diff --git a/evmrpc/simulate.go b/evmrpc/simulate.go index 9f6718f89..8e82ef1b3 100644 --- a/evmrpc/simulate.go +++ b/evmrpc/simulate.go @@ -263,7 +263,7 @@ func (b *Backend) HeaderByNumber(ctx context.Context, bn rpc.BlockNumber) (*etht return b.getHeader(big.NewInt(height)), nil } -func (b *Backend) StateAtTransaction(ctx context.Context, block *ethtypes.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, vm.StateDB, tracers.StateReleaseFunc, error) { +func (b *Backend) StateAtTransaction(ctx context.Context, block *ethtypes.Block, txIndex int, reexec uint64) (*ethtypes.Transaction, vm.BlockContext, vm.StateDB, tracers.StateReleaseFunc, error) { emptyRelease := func() {} // Short circuit if it's genesis block. if block.Number().Int64() == 0 { @@ -289,7 +289,7 @@ func (b *Backend) StateAtTransaction(ctx context.Context, block *ethtypes.Block, blockContext.Time = block.Time() if idx == txIndex { - return msg, *blockContext, statedb, emptyRelease, nil + return tx, *blockContext, statedb, emptyRelease, nil } // Not yet the searched for transaction, execute on top of the current state vmenv := vm.NewEVM(*blockContext, txContext, statedb, b.ChainConfig(), vm.Config{}) diff --git a/go.mod b/go.mod index 1d0aa9be7..b5bd8d100 100644 --- a/go.mod +++ b/go.mod @@ -349,7 +349,7 @@ replace ( github.com/cosmos/cosmos-sdk => github.com/sei-protocol/sei-cosmos v0.2.79-seiv2-hotfix github.com/cosmos/iavl => github.com/sei-protocol/sei-iavl v0.1.9 github.com/cosmos/ibc-go/v3 => github.com/sei-protocol/sei-ibc-go/v3 v3.3.0 - github.com/ethereum/go-ethereum => github.com/sei-protocol/go-ethereum v1.13.5-sei-12 + github.com/ethereum/go-ethereum => github.com/sei-protocol/go-ethereum v1.13.5-sei-9.0.20240327165640-6ab0d196bac6 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.33 // Latest goleveldb is broken, we have to stick to this version diff --git a/go.sum b/go.sum index 15f98ab79..655919613 100644 --- a/go.sum +++ b/go.sum @@ -1343,8 +1343,8 @@ github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod github.com/securego/gosec/v2 v2.11.0 h1:+PDkpzR41OI2jrw1q6AdXZCbsNGNGT7pQjal0H0cArI= github.com/securego/gosec/v2 v2.11.0/go.mod h1:SX8bptShuG8reGC0XS09+a4H2BoWSJi+fscA+Pulbpo= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= -github.com/sei-protocol/go-ethereum v1.13.5-sei-12 h1:sWGD3sV9F3Ilgmn8le8lhpfAwSi6n57I921cYuNvLuo= -github.com/sei-protocol/go-ethereum v1.13.5-sei-12/go.mod h1:kcRZmuzRn1lVejiFNTz4l4W7imnpq1bDAnuKS/RyhbQ= +github.com/sei-protocol/go-ethereum v1.13.5-sei-9.0.20240327165640-6ab0d196bac6 h1:AU9VAvZsTGibuzzVmoX3taGOWjPJJct5ypXDg6vKKu0= +github.com/sei-protocol/go-ethereum v1.13.5-sei-9.0.20240327165640-6ab0d196bac6/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.2.79-seiv2-hotfix h1:Yx2W7/xrGk13rpZfs+bkjx229b9LCbcCpcwTrzbz26Y= diff --git a/x/evm/module_test.go b/x/evm/module_test.go index b7e173c41..ea9dd2b25 100644 --- a/x/evm/module_test.go +++ b/x/evm/module_test.go @@ -7,6 +7,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" ethtypes "github.com/ethereum/go-ethereum/core/types" testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" "github.com/sei-protocol/sei-chain/x/evm" @@ -28,19 +29,19 @@ func TestABCI(t *testing.T) { m.BeginBlock(ctx, abci.RequestBeginBlock{}) // 1st tx s := state.NewDBImpl(ctx.WithTxIndex(1), k, false) - s.SubBalance(evmAddr1, big.NewInt(10000000000000)) - s.AddBalance(evmAddr2, big.NewInt(8000000000000)) + s.SubBalance(evmAddr1, big.NewInt(10000000000000), tracing.BalanceChangeUnspecified) + s.AddBalance(evmAddr2, big.NewInt(8000000000000), tracing.BalanceChangeUnspecified) feeCollectorAddr, err := k.GetFeeCollectorAddress(ctx) require.Nil(t, err) - s.AddBalance(feeCollectorAddr, big.NewInt(2000000000000)) + s.AddBalance(feeCollectorAddr, big.NewInt(2000000000000), tracing.BalanceChangeUnspecified) surplus, err := s.Finalize() require.Nil(t, err) require.Equal(t, sdk.ZeroInt(), surplus) k.AppendToEvmTxDeferredInfo(ctx.WithTxIndex(1), ethtypes.Bloom{}, common.Hash{}, surplus) // 3rd tx s = state.NewDBImpl(ctx.WithTxIndex(3), k, false) - s.SubBalance(evmAddr2, big.NewInt(5000000000000)) - s.AddBalance(evmAddr1, big.NewInt(5000000000000)) + s.SubBalance(evmAddr2, big.NewInt(5000000000000), tracing.BalanceChangeUnspecified) + s.AddBalance(evmAddr1, big.NewInt(5000000000000), tracing.BalanceChangeUnspecified) surplus, err = s.Finalize() require.Nil(t, err) require.Equal(t, sdk.ZeroInt(), surplus) @@ -54,8 +55,8 @@ func TestABCI(t *testing.T) { m.BeginBlock(ctx, abci.RequestBeginBlock{}) // 2nd tx s = state.NewDBImpl(ctx.WithTxIndex(2), k, false) - s.SubBalance(evmAddr2, big.NewInt(3000000000000)) - s.AddBalance(evmAddr1, big.NewInt(2000000000000)) + s.SubBalance(evmAddr2, big.NewInt(3000000000000), tracing.BalanceChangeUnspecified) + s.AddBalance(evmAddr1, big.NewInt(2000000000000), tracing.BalanceChangeUnspecified) surplus, err = s.Finalize() require.Nil(t, err) require.Equal(t, sdk.NewInt(1000000000000), surplus) diff --git a/x/evm/state/balance.go b/x/evm/state/balance.go index 0de4863f6..0ed16f6b1 100644 --- a/x/evm/state/balance.go +++ b/x/evm/state/balance.go @@ -5,16 +5,17 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" "github.com/sei-protocol/sei-chain/x/evm/types" ) -func (s *DBImpl) SubBalance(evmAddr common.Address, amt *big.Int) { +func (s *DBImpl) SubBalance(evmAddr common.Address, amt *big.Int, reason tracing.BalanceChangeReason) { s.k.PrepareReplayedAddr(s.ctx, evmAddr) if amt.Sign() == 0 { return } if amt.Sign() < 0 { - s.AddBalance(evmAddr, new(big.Int).Neg(amt)) + s.AddBalance(evmAddr, new(big.Int).Neg(amt), reason) return } @@ -28,16 +29,25 @@ func (s *DBImpl) SubBalance(evmAddr common.Address, amt *big.Int) { if s.err != nil { return } + + if s.logger != nil && s.logger.OnBalanceChange != nil { + // We could modify AddWei instead so it returns us the old/new balance directly. + newBalance := s.GetBalance(evmAddr) + oldBalance := new(big.Int).Add(newBalance, amt) + + s.logger.OnBalanceChange(evmAddr, oldBalance, newBalance, reason) + } + s.tempStateCurrent.surplus = s.tempStateCurrent.surplus.Add(sdk.NewIntFromBigInt(amt)) } -func (s *DBImpl) AddBalance(evmAddr common.Address, amt *big.Int) { +func (s *DBImpl) AddBalance(evmAddr common.Address, amt *big.Int, reason tracing.BalanceChangeReason) { s.k.PrepareReplayedAddr(s.ctx, evmAddr) if amt.Sign() == 0 { return } if amt.Sign() < 0 { - s.SubBalance(evmAddr, new(big.Int).Neg(amt)) + s.SubBalance(evmAddr, new(big.Int).Neg(amt), reason) return } @@ -51,6 +61,15 @@ func (s *DBImpl) AddBalance(evmAddr common.Address, amt *big.Int) { if s.err != nil { return } + + if s.logger != nil && s.logger.OnBalanceChange != nil { + // We could modify AddWei instead so it returns us the old/new balance directly. + newBalance := s.GetBalance(evmAddr) + oldBalance := new(big.Int).Sub(newBalance, amt) + + s.logger.OnBalanceChange(evmAddr, oldBalance, newBalance, reason) + } + s.tempStateCurrent.surplus = s.tempStateCurrent.surplus.Sub(sdk.NewIntFromBigInt(amt)) } @@ -62,7 +81,7 @@ func (s *DBImpl) GetBalance(evmAddr common.Address) *big.Int { } // should only be called during simulation -func (s *DBImpl) SetBalance(evmAddr common.Address, amt *big.Int) { +func (s *DBImpl) SetBalance(evmAddr common.Address, amt *big.Int, reason tracing.BalanceChangeReason) { if !s.simulation { panic("should never call SetBalance in a non-simulation setting") } diff --git a/x/evm/state/balance_test.go b/x/evm/state/balance_test.go index 639c82f6e..1201d3005 100644 --- a/x/evm/state/balance_test.go +++ b/x/evm/state/balance_test.go @@ -5,6 +5,7 @@ import ( "testing" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/core/tracing" testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" "github.com/sei-protocol/sei-chain/x/evm/state" "github.com/sei-protocol/sei-chain/x/evm/types" @@ -16,17 +17,17 @@ func TestAddBalance(t *testing.T) { db := state.NewDBImpl(ctx, k, false) seiAddr, evmAddr := testkeeper.MockAddressPair() require.Equal(t, big.NewInt(0), db.GetBalance(evmAddr)) - db.AddBalance(evmAddr, big.NewInt(0)) + db.AddBalance(evmAddr, big.NewInt(0), tracing.BalanceChangeUnspecified) // set association k.SetAddressMapping(db.Ctx(), seiAddr, evmAddr) require.Equal(t, big.NewInt(0), db.GetBalance(evmAddr)) - db.AddBalance(evmAddr, big.NewInt(10000000000000)) + db.AddBalance(evmAddr, big.NewInt(10000000000000), tracing.BalanceChangeUnspecified) require.Nil(t, db.Err()) require.Equal(t, db.GetBalance(evmAddr), big.NewInt(10000000000000)) _, evmAddr2 := testkeeper.MockAddressPair() - db.SubBalance(evmAddr2, big.NewInt(-5000000000000)) // should redirect to AddBalance + db.SubBalance(evmAddr2, big.NewInt(-5000000000000), tracing.BalanceChangeUnspecified) // should redirect to AddBalance require.Nil(t, db.Err()) require.Equal(t, db.GetBalance(evmAddr), big.NewInt(10000000000000)) require.Equal(t, db.GetBalance(evmAddr2), big.NewInt(5000000000000)) @@ -37,7 +38,7 @@ func TestSubBalance(t *testing.T) { db := state.NewDBImpl(ctx, k, false) seiAddr, evmAddr := testkeeper.MockAddressPair() require.Equal(t, big.NewInt(0), db.GetBalance(evmAddr)) - db.SubBalance(evmAddr, big.NewInt(0)) + db.SubBalance(evmAddr, big.NewInt(0), tracing.BalanceChangeUnspecified) // set association k.SetAddressMapping(db.Ctx(), seiAddr, evmAddr) @@ -45,7 +46,7 @@ func TestSubBalance(t *testing.T) { amt := sdk.NewCoins(sdk.NewCoin(k.GetBaseDenom(ctx), sdk.NewInt(20))) k.BankKeeper().MintCoins(db.Ctx(), types.ModuleName, amt) k.BankKeeper().SendCoinsFromModuleToAccount(db.Ctx(), types.ModuleName, seiAddr, amt) - db.SubBalance(evmAddr, big.NewInt(10000000000000)) + db.SubBalance(evmAddr, big.NewInt(10000000000000), tracing.BalanceChangeUnspecified) require.Nil(t, db.Err()) require.Equal(t, db.GetBalance(evmAddr), big.NewInt(10000000000000)) @@ -53,13 +54,13 @@ func TestSubBalance(t *testing.T) { amt = sdk.NewCoins(sdk.NewCoin(k.GetBaseDenom(ctx), sdk.NewInt(10))) k.BankKeeper().MintCoins(db.Ctx(), types.ModuleName, amt) k.BankKeeper().SendCoinsFromModuleToAccount(db.Ctx(), types.ModuleName, sdk.AccAddress(evmAddr2[:]), amt) - db.AddBalance(evmAddr2, big.NewInt(-5000000000000)) // should redirect to SubBalance + db.AddBalance(evmAddr2, big.NewInt(-5000000000000), tracing.BalanceChangeUnspecified) // should redirect to SubBalance require.Nil(t, db.Err()) require.Equal(t, db.GetBalance(evmAddr), big.NewInt(10000000000000)) require.Equal(t, db.GetBalance(evmAddr2), big.NewInt(5000000000000)) // insufficient balance - db.SubBalance(evmAddr2, big.NewInt(10000000000000)) + db.SubBalance(evmAddr2, big.NewInt(10000000000000), tracing.BalanceChangeUnspecified) require.NotNil(t, db.Err()) } @@ -67,12 +68,12 @@ func TestSetBalance(t *testing.T) { k, ctx := testkeeper.MockEVMKeeper() db := state.NewDBImpl(ctx, k, true) _, evmAddr := testkeeper.MockAddressPair() - db.SetBalance(evmAddr, big.NewInt(10000000000000)) + db.SetBalance(evmAddr, big.NewInt(10000000000000), tracing.BalanceChangeUnspecified) require.Equal(t, big.NewInt(10000000000000), db.GetBalance(evmAddr)) seiAddr2, evmAddr2 := testkeeper.MockAddressPair() k.SetAddressMapping(db.Ctx(), seiAddr2, evmAddr2) - db.SetBalance(evmAddr2, big.NewInt(10000000000000)) + db.SetBalance(evmAddr2, big.NewInt(10000000000000), tracing.BalanceChangeUnspecified) require.Equal(t, big.NewInt(10000000000000), db.GetBalance(evmAddr2)) } @@ -82,55 +83,55 @@ func TestSurplus(t *testing.T) { // test negative usei surplus negative wei surplus db := state.NewDBImpl(ctx, k, false) - db.AddBalance(evmAddr, big.NewInt(1_000_000_000_001)) + db.AddBalance(evmAddr, big.NewInt(1_000_000_000_001), tracing.BalanceChangeUnspecified) _, err := db.Finalize() require.NotNil(t, err) require.Contains(t, err.Error(), "negative surplus value") // test negative usei surplus positive wei surplus (negative total) db = state.NewDBImpl(ctx, k, false) - db.AddBalance(evmAddr, big.NewInt(1_000_000_000_000)) - db.SubBalance(evmAddr, big.NewInt(1)) + db.AddBalance(evmAddr, big.NewInt(1_000_000_000_000), tracing.BalanceChangeUnspecified) + db.SubBalance(evmAddr, big.NewInt(1), tracing.BalanceChangeUnspecified) _, err = db.Finalize() require.NotNil(t, err) require.Contains(t, err.Error(), "negative surplus value") // test negative usei surplus positive wei surplus (positive total) db = state.NewDBImpl(ctx, k, false) - db.AddBalance(evmAddr, big.NewInt(1_000_000_000_000)) - db.SubBalance(evmAddr, big.NewInt(2)) - db.SubBalance(evmAddr, big.NewInt(999_999_999_999)) + db.AddBalance(evmAddr, big.NewInt(1_000_000_000_000), tracing.BalanceChangeUnspecified) + db.SubBalance(evmAddr, big.NewInt(2), tracing.BalanceChangeUnspecified) + db.SubBalance(evmAddr, big.NewInt(999_999_999_999), tracing.BalanceChangeUnspecified) surplus, err := db.Finalize() require.Nil(t, err) require.Equal(t, sdk.OneInt(), surplus) // test positive usei surplus negative wei surplus (negative total) db = state.NewDBImpl(ctx, k, false) - db.SubBalance(evmAddr, big.NewInt(1_000_000_000_000)) - db.AddBalance(evmAddr, big.NewInt(2)) - db.AddBalance(evmAddr, big.NewInt(999_999_999_999)) + db.SubBalance(evmAddr, big.NewInt(1_000_000_000_000), tracing.BalanceChangeUnspecified) + db.AddBalance(evmAddr, big.NewInt(2), tracing.BalanceChangeUnspecified) + db.AddBalance(evmAddr, big.NewInt(999_999_999_999), tracing.BalanceChangeUnspecified) _, err = db.Finalize() require.NotNil(t, err) require.Contains(t, err.Error(), "negative surplus value") // test positive usei surplus negative wei surplus (positive total) db = state.NewDBImpl(ctx, k, false) - db.SubBalance(evmAddr, big.NewInt(1_000_000_000_000)) - db.AddBalance(evmAddr, big.NewInt(999_999_999_999)) + db.SubBalance(evmAddr, big.NewInt(1_000_000_000_000), tracing.BalanceChangeUnspecified) + db.AddBalance(evmAddr, big.NewInt(999_999_999_999), tracing.BalanceChangeUnspecified) surplus, err = db.Finalize() require.Nil(t, err) require.Equal(t, sdk.OneInt(), surplus) // test snapshots db = state.NewDBImpl(ctx, k, false) - db.SubBalance(evmAddr, big.NewInt(1_000_000_000_000)) - db.AddBalance(evmAddr, big.NewInt(999_999_999_999)) + db.SubBalance(evmAddr, big.NewInt(1_000_000_000_000), tracing.BalanceChangeUnspecified) + db.AddBalance(evmAddr, big.NewInt(999_999_999_999), tracing.BalanceChangeUnspecified) db.Snapshot() - db.SubBalance(evmAddr, big.NewInt(1_000_000_000_000)) - db.AddBalance(evmAddr, big.NewInt(999_999_999_999)) + db.SubBalance(evmAddr, big.NewInt(1_000_000_000_000), tracing.BalanceChangeUnspecified) + db.AddBalance(evmAddr, big.NewInt(999_999_999_999), tracing.BalanceChangeUnspecified) db.Snapshot() - db.SubBalance(evmAddr, big.NewInt(1_000_000_000_000)) - db.AddBalance(evmAddr, big.NewInt(999_999_999_999)) + db.SubBalance(evmAddr, big.NewInt(1_000_000_000_000), tracing.BalanceChangeUnspecified) + db.AddBalance(evmAddr, big.NewInt(999_999_999_999), tracing.BalanceChangeUnspecified) surplus, err = db.Finalize() require.Nil(t, err) require.Equal(t, sdk.NewInt(3), surplus) diff --git a/x/evm/state/check_test.go b/x/evm/state/check_test.go index d0a4bb67c..8d3d32133 100644 --- a/x/evm/state/check_test.go +++ b/x/evm/state/check_test.go @@ -4,6 +4,7 @@ import ( "math/big" "testing" + "github.com/ethereum/go-ethereum/core/tracing" testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" "github.com/sei-protocol/sei-chain/x/evm/state" "github.com/stretchr/testify/require" @@ -35,11 +36,11 @@ func TestEmpty(t *testing.T) { require.True(t, statedb.Empty(addr)) // has balance - statedb.AddBalance(addr, big.NewInt(1000000000000)) + statedb.AddBalance(addr, big.NewInt(1000000000000), tracing.BalanceChangeUnspecified) require.False(t, statedb.Empty(addr)) // has non-zero nonce - statedb.SubBalance(addr, big.NewInt(1000000000000)) + statedb.SubBalance(addr, big.NewInt(1000000000000), tracing.BalanceChangeUnspecified) statedb.SetNonce(addr, 1) require.False(t, statedb.Empty(addr)) diff --git a/x/evm/state/code.go b/x/evm/state/code.go index a9a49fc97..f7596db07 100644 --- a/x/evm/state/code.go +++ b/x/evm/state/code.go @@ -2,6 +2,7 @@ package state import ( "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" ) func (s *DBImpl) GetCodeHash(addr common.Address) common.Hash { @@ -16,6 +17,15 @@ func (s *DBImpl) GetCode(addr common.Address) []byte { func (s *DBImpl) SetCode(addr common.Address, code []byte) { s.k.PrepareReplayedAddr(s.ctx, addr) + + if s.logger != nil && s.logger.OnCodeChange != nil { + // The SetCode method could be modified to return the old code/hash directly. + oldCode := s.GetCode(addr) + oldHash := s.GetCodeHash(addr) + + s.logger.OnCodeChange(addr, oldHash, oldCode, common.Hash(crypto.Keccak256(code)), code) + } + s.k.SetCode(s.ctx, addr, code) } diff --git a/x/evm/state/log.go b/x/evm/state/log.go index 8df7ee256..682bca0db 100644 --- a/x/evm/state/log.go +++ b/x/evm/state/log.go @@ -12,6 +12,10 @@ type Logs struct { func (s *DBImpl) AddLog(l *ethtypes.Log) { l.Index = uint(len(s.GetAllLogs())) s.tempStateCurrent.logs = append(s.tempStateCurrent.logs, l) + + if s.logger != nil && s.logger.OnLog != nil { + s.logger.OnLog(l) + } } func (s *DBImpl) GetAllLogs() []*ethtypes.Log { diff --git a/x/evm/state/nonce.go b/x/evm/state/nonce.go index d251b6c81..d6eaef234 100644 --- a/x/evm/state/nonce.go +++ b/x/evm/state/nonce.go @@ -11,5 +11,11 @@ func (s *DBImpl) GetNonce(addr common.Address) uint64 { func (s *DBImpl) SetNonce(addr common.Address, nonce uint64) { s.k.PrepareReplayedAddr(s.ctx, addr) + + if s.logger != nil && s.logger.OnNonceChange != nil { + // The SetCode method could be modified to return the old code/hash directly. + s.logger.OnNonceChange(addr, s.GetNonce(addr), nonce) + } + s.k.SetNonce(s.ctx, addr, nonce) } diff --git a/x/evm/state/state.go b/x/evm/state/state.go index d4b8cc780..5856c8593 100644 --- a/x/evm/state/state.go +++ b/x/evm/state/state.go @@ -6,6 +6,7 @@ import ( storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" "github.com/sei-protocol/sei-chain/x/evm/types" ) @@ -31,6 +32,11 @@ func (s *DBImpl) getState(ctx sdk.Context, addr common.Address, hash common.Hash func (s *DBImpl) SetState(addr common.Address, key common.Hash, val common.Hash) { s.k.PrepareReplayedAddr(s.ctx, addr) + + if s.logger != nil && s.logger.OnStorageChange != nil { + s.logger.OnStorageChange(addr, key, s.GetState(addr, key), val) + } + s.k.SetState(s.ctx, addr, key, val) } @@ -62,7 +68,7 @@ func (s *DBImpl) SelfDestruct(acc common.Address) { s.k.DeleteAddressMapping(s.ctx, seiAddr, acc) } - s.SubBalance(acc, s.GetBalance(acc)) + s.SubBalance(acc, s.GetBalance(acc), tracing.BalanceDecreaseSelfdestruct) // clear account state s.clearAccountState(acc) diff --git a/x/evm/state/state_test.go b/x/evm/state/state_test.go index a49c8d018..84f1a9c36 100644 --- a/x/evm/state/state_test.go +++ b/x/evm/state/state_test.go @@ -6,6 +6,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" "github.com/sei-protocol/sei-chain/x/evm/state" "github.com/sei-protocol/sei-chain/x/evm/types" @@ -19,7 +20,7 @@ func TestState(t *testing.T) { statedb.CreateAccount(evmAddr) require.True(t, statedb.Created(evmAddr)) require.False(t, statedb.HasSelfDestructed(evmAddr)) - statedb.AddBalance(evmAddr, big.NewInt(10)) + statedb.AddBalance(evmAddr, big.NewInt(10), tracing.BalanceChangeUnspecified) k.BankKeeper().MintCoins(statedb.Ctx(), types.ModuleName, sdk.NewCoins(sdk.NewCoin(k.GetBaseDenom(ctx), sdk.NewInt(10)))) key := common.BytesToHash([]byte("abc")) val := common.BytesToHash([]byte("def")) @@ -60,7 +61,7 @@ func TestCreate(t *testing.T) { tval := common.BytesToHash([]byte("mno")) statedb.SetState(evmAddr, key, val) statedb.SetTransientState(evmAddr, tkey, tval) - statedb.AddBalance(evmAddr, big.NewInt(10000000000000)) + statedb.AddBalance(evmAddr, big.NewInt(10000000000000), tracing.BalanceChangeUnspecified) // recreate an account should clear its state, but keep its balance and transient state statedb.CreateAccount(evmAddr) require.Equal(t, tval, statedb.GetTransientState(evmAddr, tkey)) diff --git a/x/evm/state/statedb.go b/x/evm/state/statedb.go index b8db5c770..dcf818781 100644 --- a/x/evm/state/statedb.go +++ b/x/evm/state/statedb.go @@ -5,6 +5,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/sei-protocol/sei-chain/utils" @@ -29,6 +30,8 @@ type DBImpl struct { k EVMKeeper simulation bool + + logger *tracing.Hooks } func NewDBImpl(ctx sdk.Context, k EVMKeeper, simulation bool) *DBImpl { @@ -44,6 +47,10 @@ func NewDBImpl(ctx sdk.Context, k EVMKeeper, simulation bool) *DBImpl { return s } +func (s *DBImpl) SetLogger(logger *tracing.Hooks) { + s.logger = logger +} + func (s *DBImpl) SetEVM(evm *vm.EVM) { s.ctx = types.SetCtxEVM(s.ctx, evm) s.snapshottedCtxs = utils.Map(s.snapshottedCtxs, func(ctx sdk.Context) sdk.Context { return types.SetCtxEVM(ctx, evm) }) @@ -105,6 +112,7 @@ func (s *DBImpl) Copy() vm.StateDB { coinbaseAddress: s.coinbaseAddress, simulation: s.simulation, err: s.err, + logger: s.logger, } } From 4c225f4787fbac0daa716f370354b120ec18b9e6 Mon Sep 17 00:00:00 2001 From: codchen Date: Mon, 1 Apr 2024 15:31:32 +0800 Subject: [PATCH 03/34] Add gov proposal types for registering pointers (#1496) --- app/app.go | 3 +- go.mod | 2 + go.sum | 3 + proto/evm/gov.proto | 42 ++ x/evm/client/cli/gov_tx.go | 175 +++++ x/evm/client/cli/tx.go | 3 + x/evm/gov.go | 32 + x/evm/gov_test.go | 106 +++ x/evm/handler.go | 16 + x/evm/keeper/pointer.go | 31 + x/evm/types/gov.go | 139 ++++ x/evm/types/gov.pb.go | 1124 ++++++++++++++++++++++++++++++ x/tokenfactory/types/query.pb.go | 2 +- 13 files changed, 1676 insertions(+), 2 deletions(-) create mode 100644 proto/evm/gov.proto create mode 100644 x/evm/client/cli/gov_tx.go create mode 100644 x/evm/gov.go create mode 100644 x/evm/gov_test.go create mode 100644 x/evm/types/gov.go create mode 100644 x/evm/types/gov.pb.go diff --git a/app/app.go b/app/app.go index 8af4f63cd..f8c21e79f 100644 --- a/app/app.go +++ b/app/app.go @@ -637,7 +637,8 @@ func New( AddRoute(dexmoduletypes.RouterKey, dexmodule.NewProposalHandler(app.DexKeeper)). AddRoute(minttypes.RouterKey, mint.NewProposalHandler(app.MintKeeper)). AddRoute(tokenfactorytypes.RouterKey, tokenfactorymodule.NewProposalHandler(app.TokenFactoryKeeper)). - AddRoute(acltypes.ModuleName, aclmodule.NewProposalHandler(app.AccessControlKeeper)) + AddRoute(acltypes.ModuleName, aclmodule.NewProposalHandler(app.AccessControlKeeper)). + AddRoute(evmtypes.RouterKey, evm.NewProposalHandler(app.EvmKeeper)) if len(enabledProposals) != 0 { govRouter.AddRoute(wasm.RouterKey, wasm.NewWasmProposalHandler(app.WasmKeeper, enabledProposals)) } diff --git a/go.mod b/go.mod index b5bd8d100..d96c53669 100644 --- a/go.mod +++ b/go.mod @@ -125,6 +125,7 @@ require ( github.com/fzipp/gocyclo v0.5.1 // indirect github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff // indirect github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 // indirect + github.com/ghodss/yaml v1.0.0 // indirect github.com/go-critic/go-critic v0.6.3 // indirect github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.1 // indirect @@ -172,6 +173,7 @@ require ( github.com/gostaticanalysis/nilerr v0.1.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-bexpr v0.1.10 // indirect diff --git a/go.sum b/go.sum index 655919613..1cfcac8dd 100644 --- a/go.sum +++ b/go.sum @@ -457,6 +457,7 @@ github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2Gihuqh github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/ghemawat/stream v0.0.0-20171120220530-696b145b53b9/go.mod h1:106OIgooyS7OzLDOpUGgm9fA3bQENb/cFSyyBmMoJDs= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -765,6 +766,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/guptarohit/asciigraph v0.5.5/go.mod h1:dYl5wwK4gNsnFf9Zp+l06rFiDZ5YtXM6x7SRWZ3KGag= diff --git a/proto/evm/gov.proto b/proto/evm/gov.proto new file mode 100644 index 000000000..0425df2de --- /dev/null +++ b/proto/evm/gov.proto @@ -0,0 +1,42 @@ +syntax = "proto3"; +package seiprotocol.seichain.evm; + +import "gogoproto/gogo.proto"; + +option go_package = "github.com/sei-protocol/sei-chain/x/evm/types"; + +message AddERCNativePointerProposal { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = false; + + string title = 1 [ (gogoproto.moretags) = "yaml:\"title\"" ]; + string description = 2 [ (gogoproto.moretags) = "yaml:\"description\"" ]; + string token = 3 [(gogoproto.moretags) = "yaml:\"token\""]; + string pointer = 4 [(gogoproto.moretags) = "yaml:\"pointer\""]; + uint32 version = 5 [(gogoproto.moretags) = "yaml:\"version\""]; +} + +message AddERCCW20PointerProposal { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = false; + + string title = 1 [ (gogoproto.moretags) = "yaml:\"title\"" ]; + string description = 2 [ (gogoproto.moretags) = "yaml:\"description\"" ]; + string pointee = 3 [(gogoproto.moretags) = "yaml:\"pointee\""]; + string pointer = 4 [(gogoproto.moretags) = "yaml:\"pointer\""]; + uint32 version = 5 [(gogoproto.moretags) = "yaml:\"version\""]; +} + +message AddERCCW721PointerProposal { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = false; + + string title = 1 [ (gogoproto.moretags) = "yaml:\"title\"" ]; + string description = 2 [ (gogoproto.moretags) = "yaml:\"description\"" ]; + string pointee = 3 [(gogoproto.moretags) = "yaml:\"pointee\""]; + string pointer = 4 [(gogoproto.moretags) = "yaml:\"pointer\""]; + uint32 version = 5 [(gogoproto.moretags) = "yaml:\"version\""]; +} diff --git a/x/evm/client/cli/gov_tx.go b/x/evm/client/cli/gov_tx.go new file mode 100644 index 000000000..40b475768 --- /dev/null +++ b/x/evm/client/cli/gov_tx.go @@ -0,0 +1,175 @@ +package cli + +import ( + "strconv" + "strings" + + "github.com/sei-protocol/sei-chain/x/evm/types" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/tx" + sdk "github.com/cosmos/cosmos-sdk/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + "github.com/spf13/cobra" +) + +func NewAddERCNativePointerProposalTxCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "add-erc-native-pointer title description token version deposit [pointer address]", + Args: cobra.RangeArgs(5, 6), + Short: "Submit an add ERC-native pointer proposal", + Long: strings.TrimSpace(` + Submit a proposal to register an ERC pointer contract address for a native token. + Not specifying the pointer address means a proposal that deletes the existing pointer. + `), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + version, err := strconv.ParseUint(args[3], 10, 16) + if err != nil { + return err + } + deposit, err := sdk.ParseCoinsNormalized(args[4]) + if err != nil { + return err + } + var pointer string + if len(args) == 6 { + pointer = args[5] + } + + // Convert proposal to RegisterPairsProposal Type + from := clientCtx.GetFromAddress() + + content := types.AddERCNativePointerProposal{ + Title: args[0], + Description: args[1], + Token: args[2], + Version: uint32(version), + Pointer: pointer, + } + + msg, err := govtypes.NewMsgSubmitProposal(&content, deposit, from) + if err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} + +func NewAddERCCW20PointerProposalTxCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "add-erc-cw20-pointer title description cw20address version deposit [pointer address]", + Args: cobra.RangeArgs(5, 6), + Short: "Submit an add ERC-CW20 pointer proposal", + Long: strings.TrimSpace(` + Submit a proposal to register an ERC pointer contract address for a CW20 token. + Not specifying the pointer address means a proposal that deletes the existing pointer. + `), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + version, err := strconv.ParseUint(args[3], 10, 16) + if err != nil { + return err + } + deposit, err := sdk.ParseCoinsNormalized(args[4]) + if err != nil { + return err + } + var pointer string + if len(args) == 6 { + pointer = args[5] + } + + // Convert proposal to RegisterPairsProposal Type + from := clientCtx.GetFromAddress() + + content := types.AddERCCW20PointerProposal{ + Title: args[0], + Description: args[1], + Pointee: args[2], + Version: uint32(version), + Pointer: pointer, + } + + msg, err := govtypes.NewMsgSubmitProposal(&content, deposit, from) + if err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} + +func NewAddERCCW721PointerProposalTxCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "add-erc-cw721-pointer title description cw721address version deposit [pointer address]", + Args: cobra.RangeArgs(5, 6), + Short: "Submit an add ERC-CW721 pointer proposal", + Long: strings.TrimSpace(` + Submit a proposal to register an ERC pointer contract address for a CW721 token. + Not specifying the pointer address means a proposal that deletes the existing pointer. + `), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + version, err := strconv.ParseUint(args[3], 10, 16) + if err != nil { + return err + } + deposit, err := sdk.ParseCoinsNormalized(args[4]) + if err != nil { + return err + } + var pointer string + if len(args) == 6 { + pointer = args[5] + } + + // Convert proposal to RegisterPairsProposal Type + from := clientCtx.GetFromAddress() + + content := types.AddERCCW721PointerProposal{ + Title: args[0], + Description: args[1], + Pointee: args[2], + Version: uint32(version), + Pointer: pointer, + } + + msg, err := govtypes.NewMsgSubmitProposal(&content, deposit, from) + if err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} diff --git a/x/evm/client/cli/tx.go b/x/evm/client/cli/tx.go index 5e31c220c..c79f398df 100644 --- a/x/evm/client/cli/tx.go +++ b/x/evm/client/cli/tx.go @@ -64,6 +64,9 @@ func GetTxCmd() *cobra.Command { cmd.AddCommand(CmdERC20Send()) cmd.AddCommand(CmdDelegate()) cmd.AddCommand(NativeSendTxCmd()) + cmd.AddCommand(NewAddERCNativePointerProposalTxCmd()) + cmd.AddCommand(NewAddERCCW20PointerProposalTxCmd()) + cmd.AddCommand(NewAddERCCW721PointerProposalTxCmd()) return cmd } diff --git a/x/evm/gov.go b/x/evm/gov.go new file mode 100644 index 000000000..1673810a2 --- /dev/null +++ b/x/evm/gov.go @@ -0,0 +1,32 @@ +package evm + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/sei-protocol/sei-chain/x/evm/keeper" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +func HandleAddERCNativePointerProposal(ctx sdk.Context, k *keeper.Keeper, p *types.AddERCNativePointerProposal) error { + if p.Pointer == "" { + k.DeleteERC20NativePointer(ctx, p.Token, uint16(p.Version)) + return nil + } + return k.SetERC20NativePointerWithVersion(ctx, p.Token, common.HexToAddress(p.Pointer), uint16(p.Version)) +} + +func HandleAddERCCW20PointerProposal(ctx sdk.Context, k *keeper.Keeper, p *types.AddERCCW20PointerProposal) error { + if p.Pointer == "" { + k.DeleteERC20CW20Pointer(ctx, p.Pointee, uint16(p.Version)) + return nil + } + return k.SetERC20CW20PointerWithVersion(ctx, p.Pointee, common.HexToAddress(p.Pointer), uint16(p.Version)) +} + +func HandleAddERCCW721PointerProposal(ctx sdk.Context, k *keeper.Keeper, p *types.AddERCCW721PointerProposal) error { + if p.Pointer == "" { + k.DeleteERC721CW721Pointer(ctx, p.Pointee, uint16(p.Version)) + return nil + } + return k.SetERC721CW721PointerWithVersion(ctx, p.Pointee, common.HexToAddress(p.Pointer), uint16(p.Version)) +} diff --git a/x/evm/gov_test.go b/x/evm/gov_test.go new file mode 100644 index 000000000..355403742 --- /dev/null +++ b/x/evm/gov_test.go @@ -0,0 +1,106 @@ +package evm_test + +import ( + "testing" + + testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" + "github.com/sei-protocol/sei-chain/x/evm" + "github.com/sei-protocol/sei-chain/x/evm/types" + "github.com/stretchr/testify/require" +) + +func TestAddERCNativePointerProposals(t *testing.T) { + k, ctx := testkeeper.MockEVMKeeper() + _, pointer1 := testkeeper.MockAddressPair() + _, pointer2 := testkeeper.MockAddressPair() + require.Nil(t, evm.HandleAddERCNativePointerProposal(ctx, k, &types.AddERCNativePointerProposal{ + Token: "test", + Version: 1, + Pointer: pointer1.Hex(), + })) + addr, ver, exists := k.GetERC20NativePointer(ctx, "test") + require.True(t, exists) + require.Equal(t, uint16(1), ver) + require.Equal(t, addr, pointer1) + require.Nil(t, evm.HandleAddERCNativePointerProposal(ctx, k, &types.AddERCNativePointerProposal{ + Token: "test", + Version: 2, + Pointer: pointer2.Hex(), + })) + addr, ver, exists = k.GetERC20NativePointer(ctx, "test") + require.True(t, exists) + require.Equal(t, uint16(2), ver) + require.Equal(t, addr, pointer2) + require.Nil(t, evm.HandleAddERCNativePointerProposal(ctx, k, &types.AddERCNativePointerProposal{ + Token: "test", + Version: 2, + })) + addr, ver, exists = k.GetERC20NativePointer(ctx, "test") + require.True(t, exists) + require.Equal(t, uint16(1), ver) + require.Equal(t, addr, pointer1) +} + +func TestAddERCCW20PointerProposals(t *testing.T) { + k, ctx := testkeeper.MockEVMKeeper() + _, pointer1 := testkeeper.MockAddressPair() + _, pointer2 := testkeeper.MockAddressPair() + require.Nil(t, evm.HandleAddERCCW20PointerProposal(ctx, k, &types.AddERCCW20PointerProposal{ + Pointee: "test", + Version: 1, + Pointer: pointer1.Hex(), + })) + addr, ver, exists := k.GetERC20CW20Pointer(ctx, "test") + require.True(t, exists) + require.Equal(t, uint16(1), ver) + require.Equal(t, addr, pointer1) + require.Nil(t, evm.HandleAddERCCW20PointerProposal(ctx, k, &types.AddERCCW20PointerProposal{ + Pointee: "test", + Version: 2, + Pointer: pointer2.Hex(), + })) + addr, ver, exists = k.GetERC20CW20Pointer(ctx, "test") + require.True(t, exists) + require.Equal(t, uint16(2), ver) + require.Equal(t, addr, pointer2) + require.Nil(t, evm.HandleAddERCCW20PointerProposal(ctx, k, &types.AddERCCW20PointerProposal{ + Pointee: "test", + Version: 2, + })) + addr, ver, exists = k.GetERC20CW20Pointer(ctx, "test") + require.True(t, exists) + require.Equal(t, uint16(1), ver) + require.Equal(t, addr, pointer1) +} + +func TestAddERCCW721PointerProposals(t *testing.T) { + k, ctx := testkeeper.MockEVMKeeper() + _, pointer1 := testkeeper.MockAddressPair() + _, pointer2 := testkeeper.MockAddressPair() + require.Nil(t, evm.HandleAddERCCW721PointerProposal(ctx, k, &types.AddERCCW721PointerProposal{ + Pointee: "test", + Version: 1, + Pointer: pointer1.Hex(), + })) + addr, ver, exists := k.GetERC721CW721Pointer(ctx, "test") + require.True(t, exists) + require.Equal(t, uint16(1), ver) + require.Equal(t, addr, pointer1) + require.Nil(t, evm.HandleAddERCCW721PointerProposal(ctx, k, &types.AddERCCW721PointerProposal{ + Pointee: "test", + Version: 2, + Pointer: pointer2.Hex(), + })) + addr, ver, exists = k.GetERC721CW721Pointer(ctx, "test") + require.True(t, exists) + require.Equal(t, uint16(2), ver) + require.Equal(t, addr, pointer2) + require.Nil(t, evm.HandleAddERCCW721PointerProposal(ctx, k, &types.AddERCCW721PointerProposal{ + Pointee: "test", + Version: 2, + })) + addr, ver, exists = k.GetERC721CW721Pointer(ctx, "test") + require.True(t, exists) + require.Equal(t, uint16(1), ver) + require.Equal(t, addr, pointer1) +} diff --git a/x/evm/handler.go b/x/evm/handler.go index eeaf2ab45..f610540ec 100644 --- a/x/evm/handler.go +++ b/x/evm/handler.go @@ -5,6 +5,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" "github.com/sei-protocol/sei-chain/x/evm/keeper" "github.com/sei-protocol/sei-chain/x/evm/types" @@ -29,3 +30,18 @@ func NewHandler(k *keeper.Keeper) sdk.Handler { } } } + +func NewProposalHandler(k keeper.Keeper) govtypes.Handler { + return func(ctx sdk.Context, content govtypes.Content) error { + switch c := content.(type) { + case *types.AddERCNativePointerProposal: + return HandleAddERCNativePointerProposal(ctx, &k, c) + case *types.AddERCCW20PointerProposal: + return HandleAddERCCW20PointerProposal(ctx, &k, c) + case *types.AddERCCW721PointerProposal: + return HandleAddERCCW721PointerProposal(ctx, &k, c) + default: + return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized evm proposal content type: %T", c) + } + } +} diff --git a/x/evm/keeper/pointer.go b/x/evm/keeper/pointer.go index 0d5231830..e26526e85 100644 --- a/x/evm/keeper/pointer.go +++ b/x/evm/keeper/pointer.go @@ -16,26 +16,50 @@ func (k *Keeper) SetERC20NativePointer(ctx sdk.Context, token string, addr commo return k.SetPointerInfo(ctx, types.PointerERC20NativeKey(token), addr, native.CurrentVersion) } +func (k *Keeper) SetERC20NativePointerWithVersion(ctx sdk.Context, token string, addr common.Address, version uint16) error { + return k.SetPointerInfo(ctx, types.PointerERC20NativeKey(token), addr, version) +} + func (k *Keeper) GetERC20NativePointer(ctx sdk.Context, token string) (addr common.Address, version uint16, exists bool) { return k.GetPointerInfo(ctx, types.PointerERC20NativeKey(token)) } +func (k *Keeper) DeleteERC20NativePointer(ctx sdk.Context, token string, version uint16) { + k.DeletePointerInfo(ctx, types.PointerERC20NativeKey(token), version) +} + func (k *Keeper) SetERC20CW20Pointer(ctx sdk.Context, cw20Address string, addr common.Address) error { return k.SetPointerInfo(ctx, types.PointerERC20CW20Key(cw20Address), addr, cw20.CurrentVersion) } +func (k *Keeper) SetERC20CW20PointerWithVersion(ctx sdk.Context, cw20Address string, addr common.Address, version uint16) error { + return k.SetPointerInfo(ctx, types.PointerERC20CW20Key(cw20Address), addr, version) +} + func (k *Keeper) GetERC20CW20Pointer(ctx sdk.Context, cw20Address string) (addr common.Address, version uint16, exists bool) { return k.GetPointerInfo(ctx, types.PointerERC20CW20Key(cw20Address)) } +func (k *Keeper) DeleteERC20CW20Pointer(ctx sdk.Context, cw20Address string, version uint16) { + k.DeletePointerInfo(ctx, types.PointerERC20CW20Key(cw20Address), version) +} + func (k *Keeper) SetERC721CW721Pointer(ctx sdk.Context, cw721Address string, addr common.Address) error { return k.SetPointerInfo(ctx, types.PointerERC721CW721Key(cw721Address), addr, cw20.CurrentVersion) } +func (k *Keeper) SetERC721CW721PointerWithVersion(ctx sdk.Context, cw721Address string, addr common.Address, version uint16) error { + return k.SetPointerInfo(ctx, types.PointerERC721CW721Key(cw721Address), addr, version) +} + func (k *Keeper) GetERC721CW721Pointer(ctx sdk.Context, cw721Address string) (addr common.Address, version uint16, exists bool) { return k.GetPointerInfo(ctx, types.PointerERC721CW721Key(cw721Address)) } +func (k *Keeper) DeleteERC721CW721Pointer(ctx sdk.Context, cw721Address string, version uint16) { + k.DeletePointerInfo(ctx, types.PointerERC721CW721Key(cw721Address), version) +} + func (k *Keeper) GetPointerInfo(ctx sdk.Context, pref []byte) (addr common.Address, version uint16, exists bool) { store := prefix.NewStore(ctx.KVStore(k.GetStoreKey()), pref) iter := store.ReverseIterator(nil, nil) @@ -60,3 +84,10 @@ func (k *Keeper) SetPointerInfo(ctx sdk.Context, pref []byte, addr common.Addres store.Set(versionBz, addr[:]) return nil } + +func (k *Keeper) DeletePointerInfo(ctx sdk.Context, pref []byte, version uint16) { + store := prefix.NewStore(ctx.KVStore(k.GetStoreKey()), pref) + versionBz := make([]byte, 2) + binary.BigEndian.PutUint16(versionBz, version) + store.Delete(versionBz) +} diff --git a/x/evm/types/gov.go b/x/evm/types/gov.go new file mode 100644 index 000000000..1ef47dddd --- /dev/null +++ b/x/evm/types/gov.go @@ -0,0 +1,139 @@ +package types + +import ( + "errors" + "fmt" + "math" + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/ethereum/go-ethereum/common" +) + +const ( + ProposalTypeAddERCNativePointer = "AddERCNativePointer" + ProposalTypeAddERCCW20Pointer = "AddERCCW20Pointer" + ProposalTypeAddERCCW721Pointer = "AddERCCW721Pointer" +) + +func init() { + // for routing + govtypes.RegisterProposalType(ProposalTypeAddERCNativePointer) + govtypes.RegisterProposalType(ProposalTypeAddERCCW20Pointer) + govtypes.RegisterProposalType(ProposalTypeAddERCCW721Pointer) + // for marshal and unmarshal + govtypes.RegisterProposalTypeCodec(&AddERCNativePointerProposal{}, "evm/AddERCNativePointerProposal") + govtypes.RegisterProposalTypeCodec(&AddERCCW20PointerProposal{}, "evm/AddERCCW20PointerProposal") + govtypes.RegisterProposalTypeCodec(&AddERCCW721PointerProposal{}, "evm/AddERCCW721PointerProposal") +} + +func (p *AddERCNativePointerProposal) GetTitle() string { return p.Title } + +func (p *AddERCNativePointerProposal) GetDescription() string { return p.Description } + +func (p *AddERCNativePointerProposal) ProposalRoute() string { return RouterKey } + +func (p *AddERCNativePointerProposal) ProposalType() string { + return ProposalTypeAddERCNativePointer +} + +func (p *AddERCNativePointerProposal) ValidateBasic() error { + if p.Pointer != "" && !common.IsHexAddress(p.Pointer) { + return errors.New("pointer address must be either empty or a valid hex-encoded string") + } + + if p.Version > math.MaxUint16 { + return errors.New("pointer version must be <= 65535") + } + + return govtypes.ValidateAbstract(p) +} + +func (p AddERCNativePointerProposal) String() string { + var b strings.Builder + b.WriteString(fmt.Sprintf(`Add ERC native pointer Proposal: + Title: %s + Description: %s + Token: %s + Pointer: %s + Version: %d +`, p.Title, p.Description, p.Token, p.Pointer, p.Version)) + return b.String() +} + +func (p *AddERCCW20PointerProposal) GetTitle() string { return p.Title } + +func (p *AddERCCW20PointerProposal) GetDescription() string { return p.Description } + +func (p *AddERCCW20PointerProposal) ProposalRoute() string { return RouterKey } + +func (p *AddERCCW20PointerProposal) ProposalType() string { + return ProposalTypeAddERCCW20Pointer +} + +func (p *AddERCCW20PointerProposal) ValidateBasic() error { + if _, err := sdk.AccAddressFromBech32(p.Pointee); err != nil { + return err + } + + if p.Pointer != "" && !common.IsHexAddress(p.Pointer) { + return errors.New("pointer address must be either empty or a valid hex-encoded string") + } + + if p.Version > math.MaxUint16 { + return errors.New("pointer version must be <= 65535") + } + + return govtypes.ValidateAbstract(p) +} + +func (p AddERCCW20PointerProposal) String() string { + var b strings.Builder + b.WriteString(fmt.Sprintf(`Add ERC CW20 pointer Proposal: + Title: %s + Description: %s + Pointee: %s + Pointer: %s + Version: %d +`, p.Title, p.Description, p.Pointee, p.Pointer, p.Version)) + return b.String() +} + +func (p *AddERCCW721PointerProposal) GetTitle() string { return p.Title } + +func (p *AddERCCW721PointerProposal) GetDescription() string { return p.Description } + +func (p *AddERCCW721PointerProposal) ProposalRoute() string { return RouterKey } + +func (p *AddERCCW721PointerProposal) ProposalType() string { + return ProposalTypeAddERCCW721Pointer +} + +func (p *AddERCCW721PointerProposal) ValidateBasic() error { + if _, err := sdk.AccAddressFromBech32(p.Pointee); err != nil { + return err + } + + if p.Pointer != "" && !common.IsHexAddress(p.Pointer) { + return errors.New("pointer address must be either empty or a valid hex-encoded string") + } + + if p.Version > math.MaxUint16 { + return errors.New("pointer version must be <= 65535") + } + + return govtypes.ValidateAbstract(p) +} + +func (p AddERCCW721PointerProposal) String() string { + var b strings.Builder + b.WriteString(fmt.Sprintf(`Add ERC CW721 pointer Proposal: + Title: %s + Description: %s + Pointee: %s + Pointer: %s + Version: %d +`, p.Title, p.Description, p.Pointee, p.Pointer, p.Version)) + return b.String() +} diff --git a/x/evm/types/gov.pb.go b/x/evm/types/gov.pb.go new file mode 100644 index 000000000..6e7ef58d2 --- /dev/null +++ b/x/evm/types/gov.pb.go @@ -0,0 +1,1124 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: evm/gov.proto + +package types + +import ( + fmt "fmt" + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +type AddERCNativePointerProposal struct { + Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty" yaml:"title"` + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty" yaml:"description"` + Token string `protobuf:"bytes,3,opt,name=token,proto3" json:"token,omitempty" yaml:"token"` + Pointer string `protobuf:"bytes,4,opt,name=pointer,proto3" json:"pointer,omitempty" yaml:"pointer"` + Version uint32 `protobuf:"varint,5,opt,name=version,proto3" json:"version,omitempty" yaml:"version"` +} + +func (m *AddERCNativePointerProposal) Reset() { *m = AddERCNativePointerProposal{} } +func (*AddERCNativePointerProposal) ProtoMessage() {} +func (*AddERCNativePointerProposal) Descriptor() ([]byte, []int) { + return fileDescriptor_fb66eb1aab5c39af, []int{0} +} +func (m *AddERCNativePointerProposal) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *AddERCNativePointerProposal) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_AddERCNativePointerProposal.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *AddERCNativePointerProposal) XXX_Merge(src proto.Message) { + xxx_messageInfo_AddERCNativePointerProposal.Merge(m, src) +} +func (m *AddERCNativePointerProposal) XXX_Size() int { + return m.Size() +} +func (m *AddERCNativePointerProposal) XXX_DiscardUnknown() { + xxx_messageInfo_AddERCNativePointerProposal.DiscardUnknown(m) +} + +var xxx_messageInfo_AddERCNativePointerProposal proto.InternalMessageInfo + +type AddERCCW20PointerProposal struct { + Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty" yaml:"title"` + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty" yaml:"description"` + Pointee string `protobuf:"bytes,3,opt,name=pointee,proto3" json:"pointee,omitempty" yaml:"pointee"` + Pointer string `protobuf:"bytes,4,opt,name=pointer,proto3" json:"pointer,omitempty" yaml:"pointer"` + Version uint32 `protobuf:"varint,5,opt,name=version,proto3" json:"version,omitempty" yaml:"version"` +} + +func (m *AddERCCW20PointerProposal) Reset() { *m = AddERCCW20PointerProposal{} } +func (*AddERCCW20PointerProposal) ProtoMessage() {} +func (*AddERCCW20PointerProposal) Descriptor() ([]byte, []int) { + return fileDescriptor_fb66eb1aab5c39af, []int{1} +} +func (m *AddERCCW20PointerProposal) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *AddERCCW20PointerProposal) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_AddERCCW20PointerProposal.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *AddERCCW20PointerProposal) XXX_Merge(src proto.Message) { + xxx_messageInfo_AddERCCW20PointerProposal.Merge(m, src) +} +func (m *AddERCCW20PointerProposal) XXX_Size() int { + return m.Size() +} +func (m *AddERCCW20PointerProposal) XXX_DiscardUnknown() { + xxx_messageInfo_AddERCCW20PointerProposal.DiscardUnknown(m) +} + +var xxx_messageInfo_AddERCCW20PointerProposal proto.InternalMessageInfo + +type AddERCCW721PointerProposal struct { + Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty" yaml:"title"` + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty" yaml:"description"` + Pointee string `protobuf:"bytes,3,opt,name=pointee,proto3" json:"pointee,omitempty" yaml:"pointee"` + Pointer string `protobuf:"bytes,4,opt,name=pointer,proto3" json:"pointer,omitempty" yaml:"pointer"` + Version uint32 `protobuf:"varint,5,opt,name=version,proto3" json:"version,omitempty" yaml:"version"` +} + +func (m *AddERCCW721PointerProposal) Reset() { *m = AddERCCW721PointerProposal{} } +func (*AddERCCW721PointerProposal) ProtoMessage() {} +func (*AddERCCW721PointerProposal) Descriptor() ([]byte, []int) { + return fileDescriptor_fb66eb1aab5c39af, []int{2} +} +func (m *AddERCCW721PointerProposal) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *AddERCCW721PointerProposal) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_AddERCCW721PointerProposal.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *AddERCCW721PointerProposal) XXX_Merge(src proto.Message) { + xxx_messageInfo_AddERCCW721PointerProposal.Merge(m, src) +} +func (m *AddERCCW721PointerProposal) XXX_Size() int { + return m.Size() +} +func (m *AddERCCW721PointerProposal) XXX_DiscardUnknown() { + xxx_messageInfo_AddERCCW721PointerProposal.DiscardUnknown(m) +} + +var xxx_messageInfo_AddERCCW721PointerProposal proto.InternalMessageInfo + +func init() { + proto.RegisterType((*AddERCNativePointerProposal)(nil), "seiprotocol.seichain.evm.AddERCNativePointerProposal") + proto.RegisterType((*AddERCCW20PointerProposal)(nil), "seiprotocol.seichain.evm.AddERCCW20PointerProposal") + proto.RegisterType((*AddERCCW721PointerProposal)(nil), "seiprotocol.seichain.evm.AddERCCW721PointerProposal") +} + +func init() { proto.RegisterFile("evm/gov.proto", fileDescriptor_fb66eb1aab5c39af) } + +var fileDescriptor_fb66eb1aab5c39af = []byte{ + // 359 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4d, 0x2d, 0xcb, 0xd5, + 0x4f, 0xcf, 0x2f, 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x92, 0x28, 0x4e, 0xcd, 0x04, 0xb3, + 0x92, 0xf3, 0x73, 0xf4, 0x8a, 0x53, 0x33, 0x93, 0x33, 0x12, 0x33, 0xf3, 0xf4, 0x52, 0xcb, 0x72, + 0xa5, 0x44, 0xd2, 0xf3, 0xd3, 0xf3, 0xc1, 0x52, 0xfa, 0x20, 0x16, 0x44, 0xbd, 0xd2, 0x44, 0x26, + 0x2e, 0x69, 0xc7, 0x94, 0x14, 0xd7, 0x20, 0x67, 0xbf, 0xc4, 0x92, 0xcc, 0xb2, 0xd4, 0x80, 0xfc, + 0xcc, 0xbc, 0x92, 0xd4, 0xa2, 0x80, 0xa2, 0xfc, 0x82, 0xfc, 0xe2, 0xc4, 0x1c, 0x21, 0x35, 0x2e, + 0xd6, 0x92, 0xcc, 0x92, 0x9c, 0x54, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x4e, 0x27, 0x81, 0x4f, 0xf7, + 0xe4, 0x79, 0x2a, 0x13, 0x73, 0x73, 0xac, 0x94, 0xc0, 0xc2, 0x4a, 0x41, 0x10, 0x69, 0x21, 0x0b, + 0x2e, 0xee, 0x94, 0xd4, 0xe2, 0xe4, 0xa2, 0xcc, 0x82, 0x92, 0xcc, 0xfc, 0x3c, 0x09, 0x26, 0xb0, + 0x6a, 0xb1, 0x4f, 0xf7, 0xe4, 0x85, 0x20, 0xaa, 0x91, 0x24, 0x95, 0x82, 0x90, 0x95, 0x82, 0x6d, + 0xc8, 0xcf, 0x4e, 0xcd, 0x93, 0x60, 0xc6, 0xb0, 0x01, 0x24, 0x0c, 0xb2, 0x01, 0x44, 0x0b, 0xe9, + 0x70, 0xb1, 0x17, 0x40, 0x1c, 0x27, 0xc1, 0x02, 0x56, 0x29, 0xf4, 0xe9, 0x9e, 0x3c, 0x1f, 0x44, + 0x25, 0x54, 0x42, 0x29, 0x08, 0xa6, 0x04, 0xa4, 0xba, 0x2c, 0xb5, 0xa8, 0x18, 0xe4, 0x16, 0x56, + 0x05, 0x46, 0x0d, 0x5e, 0x64, 0xd5, 0x50, 0x09, 0xa5, 0x20, 0x98, 0x12, 0x2b, 0x9e, 0x8e, 0x05, + 0xf2, 0x0c, 0x33, 0x16, 0xc8, 0x33, 0xbc, 0x58, 0x20, 0xcf, 0xa0, 0x34, 0x95, 0x89, 0x4b, 0x12, + 0x12, 0x26, 0xce, 0xe1, 0x46, 0x06, 0xf4, 0x0f, 0x11, 0xb8, 0x4f, 0x53, 0xa1, 0x61, 0x82, 0xe1, + 0xd3, 0x54, 0xb8, 0x4f, 0x53, 0xe9, 0x18, 0x2e, 0xd3, 0x98, 0xb8, 0xa4, 0x60, 0xe1, 0x62, 0x6e, + 0x64, 0x38, 0x1a, 0x30, 0xd0, 0x80, 0x71, 0x72, 0x3f, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, + 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, + 0x39, 0x86, 0x28, 0xdd, 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0xfd, 0xe2, + 0xd4, 0x4c, 0x5d, 0x58, 0xd6, 0x04, 0x73, 0xc0, 0x79, 0x53, 0xbf, 0x42, 0x1f, 0x94, 0x83, 0x4b, + 0x2a, 0x0b, 0x52, 0x8b, 0x93, 0xd8, 0xc0, 0xf2, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x08, + 0x8c, 0x8b, 0x94, 0xd5, 0x03, 0x00, 0x00, +} + +func (m *AddERCNativePointerProposal) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AddERCNativePointerProposal) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AddERCNativePointerProposal) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Version != 0 { + i = encodeVarintGov(dAtA, i, uint64(m.Version)) + i-- + dAtA[i] = 0x28 + } + if len(m.Pointer) > 0 { + i -= len(m.Pointer) + copy(dAtA[i:], m.Pointer) + i = encodeVarintGov(dAtA, i, uint64(len(m.Pointer))) + i-- + dAtA[i] = 0x22 + } + if len(m.Token) > 0 { + i -= len(m.Token) + copy(dAtA[i:], m.Token) + i = encodeVarintGov(dAtA, i, uint64(len(m.Token))) + i-- + dAtA[i] = 0x1a + } + if len(m.Description) > 0 { + i -= len(m.Description) + copy(dAtA[i:], m.Description) + i = encodeVarintGov(dAtA, i, uint64(len(m.Description))) + i-- + dAtA[i] = 0x12 + } + if len(m.Title) > 0 { + i -= len(m.Title) + copy(dAtA[i:], m.Title) + i = encodeVarintGov(dAtA, i, uint64(len(m.Title))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *AddERCCW20PointerProposal) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AddERCCW20PointerProposal) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AddERCCW20PointerProposal) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Version != 0 { + i = encodeVarintGov(dAtA, i, uint64(m.Version)) + i-- + dAtA[i] = 0x28 + } + if len(m.Pointer) > 0 { + i -= len(m.Pointer) + copy(dAtA[i:], m.Pointer) + i = encodeVarintGov(dAtA, i, uint64(len(m.Pointer))) + i-- + dAtA[i] = 0x22 + } + if len(m.Pointee) > 0 { + i -= len(m.Pointee) + copy(dAtA[i:], m.Pointee) + i = encodeVarintGov(dAtA, i, uint64(len(m.Pointee))) + i-- + dAtA[i] = 0x1a + } + if len(m.Description) > 0 { + i -= len(m.Description) + copy(dAtA[i:], m.Description) + i = encodeVarintGov(dAtA, i, uint64(len(m.Description))) + i-- + dAtA[i] = 0x12 + } + if len(m.Title) > 0 { + i -= len(m.Title) + copy(dAtA[i:], m.Title) + i = encodeVarintGov(dAtA, i, uint64(len(m.Title))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *AddERCCW721PointerProposal) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AddERCCW721PointerProposal) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AddERCCW721PointerProposal) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Version != 0 { + i = encodeVarintGov(dAtA, i, uint64(m.Version)) + i-- + dAtA[i] = 0x28 + } + if len(m.Pointer) > 0 { + i -= len(m.Pointer) + copy(dAtA[i:], m.Pointer) + i = encodeVarintGov(dAtA, i, uint64(len(m.Pointer))) + i-- + dAtA[i] = 0x22 + } + if len(m.Pointee) > 0 { + i -= len(m.Pointee) + copy(dAtA[i:], m.Pointee) + i = encodeVarintGov(dAtA, i, uint64(len(m.Pointee))) + i-- + dAtA[i] = 0x1a + } + if len(m.Description) > 0 { + i -= len(m.Description) + copy(dAtA[i:], m.Description) + i = encodeVarintGov(dAtA, i, uint64(len(m.Description))) + i-- + dAtA[i] = 0x12 + } + if len(m.Title) > 0 { + i -= len(m.Title) + copy(dAtA[i:], m.Title) + i = encodeVarintGov(dAtA, i, uint64(len(m.Title))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintGov(dAtA []byte, offset int, v uint64) int { + offset -= sovGov(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *AddERCNativePointerProposal) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Title) + if l > 0 { + n += 1 + l + sovGov(uint64(l)) + } + l = len(m.Description) + if l > 0 { + n += 1 + l + sovGov(uint64(l)) + } + l = len(m.Token) + if l > 0 { + n += 1 + l + sovGov(uint64(l)) + } + l = len(m.Pointer) + if l > 0 { + n += 1 + l + sovGov(uint64(l)) + } + if m.Version != 0 { + n += 1 + sovGov(uint64(m.Version)) + } + return n +} + +func (m *AddERCCW20PointerProposal) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Title) + if l > 0 { + n += 1 + l + sovGov(uint64(l)) + } + l = len(m.Description) + if l > 0 { + n += 1 + l + sovGov(uint64(l)) + } + l = len(m.Pointee) + if l > 0 { + n += 1 + l + sovGov(uint64(l)) + } + l = len(m.Pointer) + if l > 0 { + n += 1 + l + sovGov(uint64(l)) + } + if m.Version != 0 { + n += 1 + sovGov(uint64(m.Version)) + } + return n +} + +func (m *AddERCCW721PointerProposal) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Title) + if l > 0 { + n += 1 + l + sovGov(uint64(l)) + } + l = len(m.Description) + if l > 0 { + n += 1 + l + sovGov(uint64(l)) + } + l = len(m.Pointee) + if l > 0 { + n += 1 + l + sovGov(uint64(l)) + } + l = len(m.Pointer) + if l > 0 { + n += 1 + l + sovGov(uint64(l)) + } + if m.Version != 0 { + n += 1 + sovGov(uint64(m.Version)) + } + return n +} + +func sovGov(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGov(x uint64) (n int) { + return sovGov(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *AddERCNativePointerProposal) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AddERCNativePointerProposal: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AddERCNativePointerProposal: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Title", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGov + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGov + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Title = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGov + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGov + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Description = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGov + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGov + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Token = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pointer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGov + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGov + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Pointer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + m.Version = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Version |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGov(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGov + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *AddERCCW20PointerProposal) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AddERCCW20PointerProposal: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AddERCCW20PointerProposal: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Title", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGov + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGov + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Title = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGov + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGov + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Description = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pointee", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGov + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGov + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Pointee = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pointer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGov + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGov + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Pointer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + m.Version = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Version |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGov(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGov + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *AddERCCW721PointerProposal) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AddERCCW721PointerProposal: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AddERCCW721PointerProposal: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Title", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGov + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGov + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Title = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGov + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGov + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Description = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pointee", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGov + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGov + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Pointee = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pointer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGov + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGov + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Pointer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + m.Version = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGov + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Version |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGov(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGov + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGov(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGov + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGov + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGov + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGov + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGov + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGov + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGov = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGov = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGov = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/tokenfactory/types/query.pb.go b/x/tokenfactory/types/query.pb.go index 9970bd075..b1c6dade3 100644 --- a/x/tokenfactory/types/query.pb.go +++ b/x/tokenfactory/types/query.pb.go @@ -344,7 +344,7 @@ func (m *QueryDenomMetadataRequest) GetDenom() string { return "" } -// QueryDenomMetadataResponse is the response type for the Query/DenomMetadata RPC +// QueryDenomMetadataResponse is the response type for the Query/DenomMetadata gRPC // method. type QueryDenomMetadataResponse struct { // metadata describes and provides all the client information for the requested token. From 1127dbd8fe481a44df9f1ff3ef466a0791d0cfd2 Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Mon, 1 Apr 2024 11:07:17 -0400 Subject: [PATCH 04/34] [EVM] cancun valid block tests (#1487) * not following eip-4788 * add run_blocktests script * add initialize_local_chain_no_run script * add prints * add more prints * made min fee per gas 0 * Revert "add more prints" This reverts commit 3c940e154bcbb4628a53c862e83447597beb0c1d. * Revert "add prints" This reverts commit fd28389c6b6b6980131b8a40496a8e0217ab2b66. * 2 new failing tests * use initialize local chain with NO_RUN flag * Revert "Revert "add more prints"" This reverts commit 4c7603ac584f351c9135c91ba2a57e6467e036de. * Revert "Revert "add prints"" This reverts commit 98ea147eb8e6107f2ebdb90f8028d5f48c73de8e. * increment height in getHash and ignore withdraw addrs * update failed tests * add eth blocktests * adjust git clone * Revert "Revert "Revert "add more prints""" This reverts commit 1e0aede5f9273cf1d785d3a47a7fac279b47a0d9. * Revert "Revert "Revert "add prints""" This reverts commit 37641228ba03f67a641f6663bc16081c37d4c899. * Eth blocktests ci (#1495) Refactor EVM blocktests to run partitioned for faster CI performance --------- Co-authored-by: Uday Patil --- .github/workflows/eth_blocktests.yml | 53 +++++++++++++++++++++++ app/eth_replay.go | 26 +++++++++++ run_blocktests.sh | 64 ++++++++++++++++++++++++++++ scripts/initialize_local_chain.sh | 5 +++ x/evm/keeper/keeper.go | 12 +++++- 5 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/eth_blocktests.yml create mode 100755 run_blocktests.sh diff --git a/.github/workflows/eth_blocktests.yml b/.github/workflows/eth_blocktests.yml new file mode 100644 index 000000000..4c54deaba --- /dev/null +++ b/.github/workflows/eth_blocktests.yml @@ -0,0 +1,53 @@ +name: ETH Blocktests + +on: + push: + branches: + - seiv2 + pull_request: + branches: + - seiv2 + +defaults: + run: + shell: bash + +env: + TOTAL_RUNNERS: 5 + +jobs: + runner-indexes: + runs-on: ubuntu-latest + name: Generate runner indexes + outputs: + json: ${{ steps.generate-index-list.outputs.json }} + steps: + - id: generate-index-list + run: | + MAX_INDEX=$((${{ env.TOTAL_RUNNERS }}-1)) + INDEX_LIST=$(seq 0 ${MAX_INDEX}) + INDEX_JSON=$(jq --null-input --compact-output '. |= [inputs]' <<< ${INDEX_LIST}) + echo "json=${INDEX_JSON}" >> $GITHUB_OUTPUT + + eth-blocktests: + name: "Run ETH Blocktests ${{ matrix.runner-index }}" + runs-on: ubuntu-latest + needs: runner-indexes + strategy: + fail-fast: false + matrix: + # generate runner index array from 0 to total-runners + runner-index: ${{fromJson(needs.runner-indexes.outputs.json)}} + steps: + - uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.21 + + - name: Clone ETH Blocktests + run: git clone https://github.com/ethereum/tests.git ethtests + + - name: "Run ETH Blocktest" + run: ./run_blocktests.sh ./ethtests/BlockchainTests/ValidBlocks/ ${{ matrix.runner-index }} ${{ env.TOTAL_RUNNERS }} diff --git a/app/eth_replay.go b/app/eth_replay.go index a16bae7ed..5b3cf6197 100644 --- a/app/eth_replay.go +++ b/app/eth_replay.go @@ -10,6 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/tracing" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" @@ -133,18 +134,23 @@ func BlockTest(a *App, bt *ethtests.BlockTest) { for key, value := range genesisAccount.Storage { a.EvmKeeper.SetState(a.GetContextForDeliverTx([]byte{}), addr, key, value) } + params := a.EvmKeeper.GetParams(a.GetContextForDeliverTx([]byte{})) + params.MinimumFeePerGas = sdk.NewDecFromInt(sdk.NewInt(0)) + a.EvmKeeper.SetParams(a.GetContextForDeliverTx([]byte{}), params) } if len(bt.Json.Blocks) == 0 { panic("no blocks found") } + ethblocks := make([]*ethtypes.Block, 0) for i, btBlock := range bt.Json.Blocks { h := int64(i + 1) b, err := btBlock.Decode() if err != nil { panic(err) } + ethblocks = append(ethblocks, b) hash := make([]byte, 8) binary.BigEndian.PutUint64(hash, uint64(h)) _, err = a.FinalizeBlock(context.Background(), &abci.RequestFinalizeBlock{ @@ -167,6 +173,15 @@ func BlockTest(a *App, bt *ethtests.BlockTest) { // Check post-state after all blocks are run ctx := a.GetCheckCtx() for addr, accountData := range bt.Json.Post { + if IsWithdrawalAddress(addr, ethblocks) { + fmt.Println("Skipping withdrawal address: ", addr) + continue + } + // Not checking compliance with EIP-4788 + if addr == params.BeaconRootsStorageAddress { + fmt.Println("Skipping beacon roots storage address: ", addr) + continue + } a.EvmKeeper.VerifyAccount(ctx, addr, accountData) fmt.Println("Successfully verified account: ", addr) } @@ -202,3 +217,14 @@ func encodeTx(tx *ethtypes.Transaction, txConfig client.TxConfig) []byte { } return txbz } + +func IsWithdrawalAddress(addr common.Address, blocks []*ethtypes.Block) bool { + for _, block := range blocks { + for _, w := range block.Withdrawals() { + if w.Address == addr { + return true + } + } + } + return false +} diff --git a/run_blocktests.sh b/run_blocktests.sh new file mode 100755 index 000000000..7400d2d9d --- /dev/null +++ b/run_blocktests.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +set -e + +# mode options are list, run, or all +block_tests_path=$1 +runner_index=$2 +runner_total=$3 + +if [ -z "$runner_index" ]; then + runner_index=0 + runner_total=1 +fi + +echo $mode +echo $block_tests_path + +# Define an array of tests to skip +declare -a skip_list=( + "DelegateCallSpam" # passes, but takes super long + "blockhashTests" # failing + "blockhashNonConstArg" # failing + "BLOCKHASH_Bounds" # newly failing + "logRevert" # failing after increment height +) + +# list out all paths to json files starting from the block_tests_dir +block_tests=$(find "$block_tests_path" -name "*.json") + +test_files="" + +i=0 + +# for each json file, run the block test +for test_file in $block_tests; do + test_name=$(basename "$test_file" .json) + + # Check if the test name is in the skip list + if printf '%s\n' "${skip_list[@]}" | grep -qx "$test_name"; then + echo "Skipping test: $test_file" + continue + fi + + # Check if "${test_name}_Cancun" is not in the test file + if ! grep -q "${test_name}_Cancun" "$test_file"; then + echo "Skipping test due to missing Cancun tag: $test_file" + continue + fi + + if [ $((i % runner_total)) -ne $runner_index ]; then + i=$((i+1)) + continue + fi + + i=$((i+1)) + + echo -e "\n*********************************************************\n" + echo "Running block test: $test_file" + echo "test name: ${test_name}_Cancun" + echo -e "\n*********************************************************\n" + rm -r ~/.sei || true + NO_RUN=1 ./scripts/initialize_local_chain.sh + seid blocktest --block-test $test_file --test-name "${test_name}_Cancun" +done diff --git a/scripts/initialize_local_chain.sh b/scripts/initialize_local_chain.sh index bc29bd024..c1dafc722 100755 --- a/scripts/initialize_local_chain.sh +++ b/scripts/initialize_local_chain.sh @@ -111,5 +111,10 @@ fi ~/go/bin/seid config keyring-backend test +if [ $NO_RUN = 1 ]; then + echo "No run flag set, exiting without starting the chain" + exit 0 +fi + # start the chain with log tracing GORACE="log_path=/tmp/race/seid_race" ~/go/bin/seid start --trace --chain-id sei-chain diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index 8d729e703..5ab52335a 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -470,7 +470,15 @@ func (k *Keeper) getBlockTestBlockCtx(ctx sdk.Context) (*vm.BlockContext, error) Nonce: btHeader.Nonce, BaseFee: btHeader.BaseFeePerGas, } - getHash := core.GetHashFn(header, &ReplayChainContext{ethClient: k.EthClient}) + getHash := func(height uint64) common.Hash { + height = height + 1 + for i := 0; i < len(k.BlockTest.Json.Blocks); i++ { + if k.BlockTest.Json.Blocks[i].BlockHeader.Number.Uint64() == height { + return k.BlockTest.Json.Blocks[i].BlockHeader.Hash + } + } + panic(fmt.Sprintf("block hash not found for height %d", height)) + } var ( baseFee *big.Int blobBaseFee *big.Int @@ -481,6 +489,8 @@ func (k *Keeper) getBlockTestBlockCtx(ctx sdk.Context) (*vm.BlockContext, error) } if header.ExcessBlobGas != nil { blobBaseFee = eip4844.CalcBlobFee(*header.ExcessBlobGas) + } else { + blobBaseFee = eip4844.CalcBlobFee(0) } if header.Difficulty.Cmp(common.Big0) == 0 { random = &header.MixDigest From 548078b29c446084981a7d5df3b144051e0aff25 Mon Sep 17 00:00:00 2001 From: Steven Landers Date: Tue, 2 Apr 2024 08:30:46 -0400 Subject: [PATCH 05/34] [EVM] Reject blob txs (#1498) --- contracts/package-lock.json | 30 +++++++++--------- contracts/package.json | 2 +- contracts/test/EVMCompatabilityTester.js | 24 +++++++++++++++ go.mod | 4 +-- go.sum | 7 ++--- x/evm/ante/basic.go | 39 +++++++++++++----------- x/evm/ante/basic_test.go | 18 +---------- 7 files changed, 66 insertions(+), 58 deletions(-) diff --git a/contracts/package-lock.json b/contracts/package-lock.json index cfd238f19..a4d1415c1 100644 --- a/contracts/package-lock.json +++ b/contracts/package-lock.json @@ -20,14 +20,14 @@ "@openzeppelin/hardhat-upgrades": "^3.0.2", "@openzeppelin/test-helpers": "^0.5.16", "dotenv": "^16.3.1", - "ethers": "^6.9.0", + "ethers": "^6.11.1", "hardhat": "^2.20.1" } }, "node_modules/@adraffy/ens-normalize": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz", - "integrity": "sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", + "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", "dev": true }, "node_modules/@aws-crypto/sha256-js": { @@ -6942,9 +6942,9 @@ } }, "node_modules/ethers": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.10.0.tgz", - "integrity": "sha512-nMNwYHzs6V1FR3Y4cdfxSQmNgZsRj1RiTU25JwvnJLmyzw9z3SKxNc2XKDuiXXo/v9ds5Mp9m6HBabgYQQ26tA==", + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.11.1.tgz", + "integrity": "sha512-mxTAE6wqJQAbp5QAe/+o+rXOID7Nw91OZXvgpjDa1r4fAbq2Nu314oEZSbjoRLacuCzs7kUC3clEvkCQowffGg==", "dev": true, "funding": [ { @@ -6957,7 +6957,7 @@ } ], "dependencies": { - "@adraffy/ens-normalize": "1.10.0", + "@adraffy/ens-normalize": "1.10.1", "@noble/curves": "1.2.0", "@noble/hashes": "1.3.2", "@types/node": "18.15.13", @@ -14146,9 +14146,9 @@ }, "dependencies": { "@adraffy/ens-normalize": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz", - "integrity": "sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", + "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", "dev": true }, "@aws-crypto/sha256-js": { @@ -19539,12 +19539,12 @@ } }, "ethers": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.10.0.tgz", - "integrity": "sha512-nMNwYHzs6V1FR3Y4cdfxSQmNgZsRj1RiTU25JwvnJLmyzw9z3SKxNc2XKDuiXXo/v9ds5Mp9m6HBabgYQQ26tA==", + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.11.1.tgz", + "integrity": "sha512-mxTAE6wqJQAbp5QAe/+o+rXOID7Nw91OZXvgpjDa1r4fAbq2Nu314oEZSbjoRLacuCzs7kUC3clEvkCQowffGg==", "dev": true, "requires": { - "@adraffy/ens-normalize": "1.10.0", + "@adraffy/ens-normalize": "1.10.1", "@noble/curves": "1.2.0", "@noble/hashes": "1.3.2", "@types/node": "18.15.13", diff --git a/contracts/package.json b/contracts/package.json index 98d2f5bc8..38332f44e 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -18,7 +18,7 @@ "@openzeppelin/hardhat-upgrades": "^3.0.2", "@openzeppelin/test-helpers": "^0.5.16", "dotenv": "^16.3.1", - "ethers": "^6.9.0", + "ethers": "^6.11.1", "hardhat": "^2.20.1" }, "dependencies": { diff --git a/contracts/test/EVMCompatabilityTester.js b/contracts/test/EVMCompatabilityTester.js index 30a3f1599..467c02422 100644 --- a/contracts/test/EVMCompatabilityTester.js +++ b/contracts/test/EVMCompatabilityTester.js @@ -52,6 +52,7 @@ async function sendTx(sender, txn, responses) { responses.push({nonce: txn.nonce, response: txResponse}) } + describe("EVM Test", function () { describe("EVMCompatibilityTester", function () { @@ -234,6 +235,29 @@ describe("EVM Test", function () { expect(await evmTester.uint256Var()).to.equal(12345); }); + // this uses a newer version of ethers to attempt a blob transaction (different signer wallet) + it("should return an error for blobs", async function(){ + const key = "0x57acb95d82739866a5c29e40b0aa2590742ae50425b7dd5b5d279a986370189e" + const signer = new ethers.Wallet(key, ethers.provider); + const blobData = "BLOB"; + const blobDataBytes = ethers.toUtf8Bytes(blobData); + const blobHash = ethers.keccak256(blobDataBytes); + + const tx = { + type: 3, + to: owner.address, + value: ethers.parseEther("0.1"), + data: '0x', + maxFeePerGas: ethers.parseUnits('100', 'gwei'), + maxPriorityFeePerGas: ethers.parseUnits('1', 'gwei'), + gasLimit: 100000, + maxFeePerBlobGas: ethers.parseUnits('10', 'gwei'), + blobVersionedHashes: [blobHash], + } + + await expect(signer.sendTransaction(tx)).to.be.rejectedWith("unsupported transaction type"); + }) + it("Should trace a call with timestamp", async function () { await delay() const txResponse = await evmTester.setTimestamp(); diff --git a/go.mod b/go.mod index d96c53669..4da61b9a4 100644 --- a/go.mod +++ b/go.mod @@ -125,7 +125,6 @@ require ( github.com/fzipp/gocyclo v0.5.1 // indirect github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff // indirect github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 // indirect - github.com/ghodss/yaml v1.0.0 // indirect github.com/go-critic/go-critic v0.6.3 // indirect github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.1 // indirect @@ -173,7 +172,6 @@ require ( github.com/gostaticanalysis/nilerr v0.1.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-bexpr v0.1.10 // indirect @@ -348,7 +346,7 @@ require ( replace ( github.com/CosmWasm/wasmd => github.com/sei-protocol/sei-wasmd v0.1.0 github.com/confio/ics23/go => github.com/cosmos/cosmos-sdk/ics23/go v0.8.0 - github.com/cosmos/cosmos-sdk => github.com/sei-protocol/sei-cosmos v0.2.79-seiv2-hotfix + github.com/cosmos/cosmos-sdk => github.com/sei-protocol/sei-cosmos v0.2.80-seiv2 github.com/cosmos/iavl => github.com/sei-protocol/sei-iavl v0.1.9 github.com/cosmos/ibc-go/v3 => github.com/sei-protocol/sei-ibc-go/v3 v3.3.0 github.com/ethereum/go-ethereum => github.com/sei-protocol/go-ethereum v1.13.5-sei-9.0.20240327165640-6ab0d196bac6 diff --git a/go.sum b/go.sum index 1cfcac8dd..e41951ffa 100644 --- a/go.sum +++ b/go.sum @@ -457,7 +457,6 @@ github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2Gihuqh github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/ghemawat/stream v0.0.0-20171120220530-696b145b53b9/go.mod h1:106OIgooyS7OzLDOpUGgm9fA3bQENb/cFSyyBmMoJDs= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -766,8 +765,6 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/guptarohit/asciigraph v0.5.5/go.mod h1:dYl5wwK4gNsnFf9Zp+l06rFiDZ5YtXM6x7SRWZ3KGag= @@ -1350,8 +1347,8 @@ github.com/sei-protocol/go-ethereum v1.13.5-sei-9.0.20240327165640-6ab0d196bac6 github.com/sei-protocol/go-ethereum v1.13.5-sei-9.0.20240327165640-6ab0d196bac6/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.2.79-seiv2-hotfix h1:Yx2W7/xrGk13rpZfs+bkjx229b9LCbcCpcwTrzbz26Y= -github.com/sei-protocol/sei-cosmos v0.2.79-seiv2-hotfix/go.mod h1:ib/gp0gCxN7FXUZ40j5+x8BeyoI7AcX+rTvf53JoDsY= +github.com/sei-protocol/sei-cosmos v0.2.80-seiv2 h1:stu0hTZmQWvewzr/y40iZrKsKHFuCXFKNEJGsgivvL4= +github.com/sei-protocol/sei-cosmos v0.2.80-seiv2/go.mod h1:ib/gp0gCxN7FXUZ40j5+x8BeyoI7AcX+rTvf53JoDsY= github.com/sei-protocol/sei-db v0.0.33 h1:TspNkw/lW7fubR0TsOpmJdzWiLecMDOS5DAxFNQXQ6M= github.com/sei-protocol/sei-db v0.0.33/go.mod h1:F/ZKZA8HJPcUzSZPA8yt6pfwlGriJ4RDR4eHKSGLStI= github.com/sei-protocol/sei-iavl v0.1.9 h1:y4mVYftxLNRs6533zl7N0/Ch+CzRQc04JDfHolIxgBE= diff --git a/x/evm/ante/basic.go b/x/evm/ante/basic.go index ab0b1fe3c..9871b289f 100644 --- a/x/evm/ante/basic.go +++ b/x/evm/ante/basic.go @@ -45,26 +45,31 @@ func (gl BasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, n return ctx, sdkerrors.ErrOutOfGas } - // Ensure blob transactions have valid commitments if etx.Type() == ethtypes.BlobTxType { - sidecar := etx.BlobTxSidecar() - if sidecar == nil { - return ctx, fmt.Errorf("missing sidecar in blob transaction") - } - // Ensure the number of items in the blob transaction and various side - // data match up before doing any expensive validations - hashes := etx.BlobHashes() - if len(hashes) == 0 { - return ctx, fmt.Errorf("blobless blob transaction") - } - if len(hashes) > params.MaxBlobGasPerBlock/params.BlobTxBlobGasPerBlob { - return ctx, fmt.Errorf("too many blobs in transaction: have %d, permitted %d", len(hashes), params.MaxBlobGasPerBlock/params.BlobTxBlobGasPerBlob) - } - if err := validateBlobSidecar(hashes, sidecar); err != nil { - return ctx, err - } + return ctx, sdkerrors.ErrUnsupportedTxType } + //TODO: support blobs (leaving this commented out) + // Ensure blob transactions have valid commitments + //if etx.Type() == ethtypes.BlobTxType { + // sidecar := etx.BlobTxSidecar() + // if sidecar == nil { + // return ctx, fmt.Errorf("missing sidecar in blob transaction") + // } + // // Ensure the number of items in the blob transaction and various side + // // data match up before doing any expensive validations + // hashes := etx.BlobHashes() + // if len(hashes) == 0 { + // return ctx, fmt.Errorf("blobless blob transaction") + // } + // if len(hashes) > params.MaxBlobGasPerBlock/params.BlobTxBlobGasPerBlob { + // return ctx, fmt.Errorf("too many blobs in transaction: have %d, permitted %d", len(hashes), params.MaxBlobGasPerBlock/params.BlobTxBlobGasPerBlob) + // } + // if err := validateBlobSidecar(hashes, sidecar); err != nil { + // return ctx, err + // } + //} + return next(ctx, tx, simulate) } diff --git a/x/evm/ante/basic_test.go b/x/evm/ante/basic_test.go index 80ec510e1..860cbfea4 100644 --- a/x/evm/ante/basic_test.go +++ b/x/evm/ante/basic_test.go @@ -53,21 +53,5 @@ func TestBasicDecorator(t *testing.T) { return ctx, nil }) require.NotNil(t, err) - require.Contains(t, err.Error(), "missing sidecar in blob transaction") - - msg, _ = types.NewMsgEVMTransaction(ðtx.BlobTx{GasLimit: 21000, Sidecar: ðtx.BlobTxSidecar{}}) - ctx, err = a.AnteHandle(ctx, &mockTx{msgs: []sdk.Msg{msg}}, false, func(ctx sdk.Context, _ sdk.Tx, _ bool) (sdk.Context, error) { - return ctx, nil - }) - require.NotNil(t, err) - require.Contains(t, err.Error(), "blobless blob transaction") - - msg, _ = types.NewMsgEVMTransaction(ðtx.BlobTx{GasLimit: 21000, Sidecar: ðtx.BlobTxSidecar{}, BlobHashes: [][]byte{ - {}, {}, {}, {}, {}, {}, {}, - }}) - ctx, err = a.AnteHandle(ctx, &mockTx{msgs: []sdk.Msg{msg}}, false, func(ctx sdk.Context, _ sdk.Tx, _ bool) (sdk.Context, error) { - return ctx, nil - }) - require.NotNil(t, err) - require.Contains(t, err.Error(), "too many blobs in transaction") + require.Error(t, err, sdkerrors.ErrUnsupportedTxType) } From 109c324c76afe9b2ec9e82b60301bc7d579a8308 Mon Sep 17 00:00:00 2001 From: codchen Date: Tue, 2 Apr 2024 23:00:25 +0800 Subject: [PATCH 06/34] Emit tendermint event upon address association (#1501) --- x/evm/keeper/address.go | 5 +++++ x/evm/types/events.go | 8 ++++++++ 2 files changed, 13 insertions(+) create mode 100644 x/evm/types/events.go diff --git a/x/evm/keeper/address.go b/x/evm/keeper/address.go index e91d70267..685d16d45 100644 --- a/x/evm/keeper/address.go +++ b/x/evm/keeper/address.go @@ -13,6 +13,11 @@ func (k *Keeper) SetAddressMapping(ctx sdk.Context, seiAddress sdk.AccAddress, e if !k.accountKeeper.HasAccount(ctx, seiAddress) { k.accountKeeper.SetAccount(ctx, k.accountKeeper.NewAccountWithAddress(ctx, seiAddress)) } + ctx.EventManager().EmitEvent(sdk.NewEvent( + types.EventTypeAddressAssociated, + sdk.NewAttribute(types.AttributeKeySeiAddress, seiAddress.String()), + sdk.NewAttribute(types.AttributeKeyEvmAddress, evmAddress.Hex()), + )) } func (k *Keeper) DeleteAddressMapping(ctx sdk.Context, seiAddress sdk.AccAddress, evmAddress common.Address) { diff --git a/x/evm/types/events.go b/x/evm/types/events.go new file mode 100644 index 000000000..6cb0b4541 --- /dev/null +++ b/x/evm/types/events.go @@ -0,0 +1,8 @@ +package types + +const ( + EventTypeAddressAssociated = "address_associated" + + AttributeKeySeiAddress = "sei_addr" + AttributeKeyEvmAddress = "evm_addr" +) From d4d1f7079eed637862bb41c8eac0377498b70e2a Mon Sep 17 00:00:00 2001 From: codchen Date: Tue, 2 Apr 2024 23:16:00 +0800 Subject: [PATCH 07/34] mock beacon root (#1502) --- x/evm/module.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/x/evm/module.go b/x/evm/module.go index 19327cd46..b0e6f0113 100644 --- a/x/evm/module.go +++ b/x/evm/module.go @@ -3,12 +3,15 @@ package evm import ( "encoding/json" "fmt" + "math" "math/big" // this line is used by starport scaffolding # 1 "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/tests" "github.com/gorilla/mux" "github.com/grpc-ecosystem/grpc-gateway/runtime" @@ -166,11 +169,27 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw func (AppModule) ConsensusVersion() uint64 { return 4 } // BeginBlock executes all ABCI BeginBlock logic respective to the capability module. -func (am AppModule) BeginBlock(sdk.Context, abci.RequestBeginBlock) { +func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) { // clear tx responses from last block am.keeper.SetTxResults([]*abci.ExecTxResult{}) // clear the TxDeferredInfo am.keeper.ClearEVMTxDeferredInfo() + // mock beacon root if replaying + if am.keeper.EthReplayConfig.Enabled { + if beaconRoot := am.keeper.ReplayBlock.BeaconRoot(); beaconRoot != nil { + blockCtx, err := am.keeper.GetVMBlockContext(ctx, core.GasPool(math.MaxUint64)) + if err != nil { + panic(err) + } + statedb := state.NewDBImpl(ctx, am.keeper, false) + vmenv := vm.NewEVM(*blockCtx, vm.TxContext{}, statedb, types.DefaultChainConfig().EthereumConfig(am.keeper.ChainID(ctx)), vm.Config{}) + core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) + _, err = statedb.Finalize() + if err != nil { + panic(err) + } + } + } } // EndBlock executes all ABCI EndBlock logic respective to the evm module. It From 1f3798c9a93bb93c51b0d75d4b5ca7d59f5b4578 Mon Sep 17 00:00:00 2001 From: codchen Date: Wed, 3 Apr 2024 11:04:32 +0800 Subject: [PATCH 08/34] Add CW->ERC pointer registry (#1499) * Add CW->ERC pointer registry * fix tests --- app/app.go | 4 +- integration_test/seidb/state_store_test.yaml | 26 +- precompiles/wasmd/wasmd_test.go | 2 +- proto/evm/tx.proto | 16 + testutil/keeper/evm.go | 3 +- .../msg_server_register_contract_test.go | 2 +- x/evm/artifacts/erc20/artifacts.go | 22 + x/evm/artifacts/erc20/cwerc20.wasm | Bin 0 -> 252665 bytes x/evm/artifacts/erc721/artifacts.go | 22 + x/evm/artifacts/erc721/cwerc721.wasm | Bin 0 -> 255724 bytes x/evm/artifacts/utils/utils.go | 15 + x/evm/keeper/genesis.go | 22 + x/evm/keeper/keeper.go | 9 +- x/evm/keeper/msg_server.go | 58 ++ x/evm/keeper/msg_server_test.go | 45 ++ x/evm/keeper/pointer.go | 66 +- x/evm/migrations/store_cw_pointer_code.go | 32 + x/evm/module.go | 6 +- x/evm/types/codec.go | 1 + x/evm/types/keys.go | 17 + x/evm/types/message_register_pointer.go | 54 ++ x/evm/types/tx.pb.go | 578 ++++++++++++++++-- 22 files changed, 925 insertions(+), 75 deletions(-) create mode 100644 x/evm/artifacts/erc20/artifacts.go create mode 100644 x/evm/artifacts/erc20/cwerc20.wasm create mode 100644 x/evm/artifacts/erc721/artifacts.go create mode 100644 x/evm/artifacts/erc721/cwerc721.wasm create mode 100644 x/evm/artifacts/utils/utils.go create mode 100644 x/evm/migrations/store_cw_pointer_code.go create mode 100644 x/evm/types/message_register_pointer.go diff --git a/app/app.go b/app/app.go index f8c21e79f..ca3099fb7 100644 --- a/app/app.go +++ b/app/app.go @@ -588,7 +588,7 @@ func New( app.EvmKeeper = *evmkeeper.NewKeeper(keys[evmtypes.StoreKey], memKeys[evmtypes.MemStoreKey], app.GetSubspace(evmtypes.ModuleName), app.BankKeeper, &app.AccountKeeper, &app.StakingKeeper, - app.TransferKeeper) + app.TransferKeeper, wasmkeeper.NewDefaultPermissionKeeper(app.WasmKeeper)) app.evmRPCConfig, err = evmrpc.ReadConfig(appOpts) if err != nil { panic(fmt.Sprintf("error reading EVM config due to %s", err)) @@ -806,8 +806,8 @@ func New( oracletypes.ModuleName, tokenfactorytypes.ModuleName, epochmoduletypes.ModuleName, - evmtypes.ModuleName, wasm.ModuleName, + evmtypes.ModuleName, acltypes.ModuleName, // this line is used by starport scaffolding # stargate/app/initGenesis ) diff --git a/integration_test/seidb/state_store_test.yaml b/integration_test/seidb/state_store_test.yaml index 3c96f1064..829d79587 100644 --- a/integration_test/seidb/state_store_test.yaml +++ b/integration_test/seidb/state_store_test.yaml @@ -39,19 +39,19 @@ verifiers: # Verify number of wasm codes at each height - type: eval - expr: BEGINNING_LIST_CODE_LENGTH == 0 + expr: BEGINNING_LIST_CODE_LENGTH == 2 - type: eval - expr: FIRST_SET_LIST_CODE_LENGTH == 10 + expr: FIRST_SET_LIST_CODE_LENGTH == 12 - type: eval - expr: FIRST_SET_LIST_CODE_LENGTH_REVERSE == 10 + expr: FIRST_SET_LIST_CODE_LENGTH_REVERSE == 12 - type: eval - expr: SECOND_SET_LIST_CODE_LENGTH == 20 + expr: SECOND_SET_LIST_CODE_LENGTH == 22 - type: eval - expr: SECOND_SET_LIST_CODE_LENGTH_REVERSE == 20 + expr: SECOND_SET_LIST_CODE_LENGTH_REVERSE == 22 - type: eval - expr: THIRD_SET_LIST_CODE_LENGTH == 30 + expr: THIRD_SET_LIST_CODE_LENGTH == 32 - type: eval - expr: THIRD_SET_LIST_CODE_LENGTH_REVERSE == 30 + expr: THIRD_SET_LIST_CODE_LENGTH_REVERSE == 32 - name: Test state store historical data checking specific wasm codes inputs: @@ -59,19 +59,19 @@ - cmd: tail -1 integration_test/contracts/wasm_first_set_block_height.txt env: FIRST_SET_BLOCK_HEIGHT # Get code id from first contract returned at first set height in forward order (0) - - cmd: seid q wasm list-code --count-total --limit 400 --height $FIRST_SET_BLOCK_HEIGHT --output json | jq -r ".code_infos[0].code_id" + - cmd: seid q wasm list-code --count-total --limit 400 --height $FIRST_SET_BLOCK_HEIGHT --output json | jq -r ".code_infos[3].code_id" env: FIRST_ID_FIRST_SET # Get creator from first contract returned at first set height in forward order - - cmd: seid q wasm list-code --count-total --limit 400 --height $FIRST_SET_BLOCK_HEIGHT --output json | jq -r ".code_infos[0].creator" + - cmd: seid q wasm list-code --count-total --limit 400 --height $FIRST_SET_BLOCK_HEIGHT --output json | jq -r ".code_infos[3].creator" env: FIRST_CREATOR_FIRST_SET # Get height from second set - cmd: tail -1 integration_test/contracts/wasm_second_set_block_height.txt env: SECOND_SET_BLOCK_HEIGHT # Get code id from first contract returned at second set height in reverse order (200) - - cmd: seid q wasm list-code --reverse --count-total --limit 400 --height $SECOND_SET_BLOCK_HEIGHT --output json | jq -r ".code_infos[0].code_id" + - cmd: seid q wasm list-code --reverse --count-total --limit 400 --height $SECOND_SET_BLOCK_HEIGHT --output json | jq -r ".code_infos[3].code_id" env: SECOND_ID_FIRST_SET # Get creator from second contract returned at second set height in reverse order - - cmd: seid q wasm list-code --reverse --count-total --limit 400 --height $SECOND_SET_BLOCK_HEIGHT --output json | jq -r ".code_infos[0].creator" + - cmd: seid q wasm list-code --reverse --count-total --limit 400 --height $SECOND_SET_BLOCK_HEIGHT --output json | jq -r ".code_infos[3].creator" env: FIRST_CREATOR_SECOND_SET # Get creator id - cmd: tail -1 integration_test/contracts/wasm_creator_id.txt @@ -85,9 +85,9 @@ # Verify correct code ids # NOTE: Since chain is continually running / stateful, may have remove - type: eval - expr: FIRST_ID_FIRST_SET == 1 + expr: FIRST_ID_FIRST_SET == 4 - type: eval - expr: SECOND_ID_FIRST_SET == 20 + expr: SECOND_ID_FIRST_SET == 19 - name: Test state store iteration through tokenfactory denoms inputs: diff --git a/precompiles/wasmd/wasmd_test.go b/precompiles/wasmd/wasmd_test.go index caa097587..eb8959974 100644 --- a/precompiles/wasmd/wasmd_test.go +++ b/precompiles/wasmd/wasmd_test.go @@ -68,7 +68,7 @@ func TestInstantiate(t *testing.T) { outputs, err := instantiateMethod.Outputs.Unpack(res) require.Nil(t, err) require.Equal(t, 2, len(outputs)) - require.Equal(t, "sei14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sh9m79m", outputs[0].(string)) + require.Equal(t, "sei1hrpna9v7vs3stzyd4z3xf00676kf78zpe2u5ksvljswn2vnjp3yslucc3n", outputs[0].(string)) require.Empty(t, outputs[1].([]byte)) require.Equal(t, uint64(902898), g) diff --git a/proto/evm/tx.proto b/proto/evm/tx.proto index 35e6852dc..c23e22602 100644 --- a/proto/evm/tx.proto +++ b/proto/evm/tx.proto @@ -10,6 +10,7 @@ option go_package = "github.com/sei-protocol/sei-chain/x/evm/types"; service Msg { rpc EVMTransaction(MsgEVMTransaction) returns (MsgEVMTransactionResponse); rpc Send(MsgSend) returns (MsgSendResponse); + rpc RegisterPointer(MsgRegisterPointer) returns (MsgRegisterPointerResponse); } message MsgEVMTransaction { @@ -53,3 +54,18 @@ message MsgSend { } message MsgSendResponse {} + +enum PointerType { + ERC20 = 0; + ERC721 = 1; +} + +message MsgRegisterPointer { + string sender = 1; + PointerType pointer_type = 2; + string erc_address = 3; +} + +message MsgRegisterPointerResponse { + string pointer_address = 1; +} diff --git a/testutil/keeper/evm.go b/testutil/keeper/evm.go index a71647038..d5449ad59 100644 --- a/testutil/keeper/evm.go +++ b/testutil/keeper/evm.go @@ -3,6 +3,7 @@ package keeper import ( "encoding/hex" "sync" + "time" "github.com/cosmos/cosmos-sdk/crypto/hd" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" @@ -51,7 +52,7 @@ func MockEVMKeeperWithPrecompiles() (*evmkeeper.Keeper, sdk.Context) { func MockEVMKeeper() (*evmkeeper.Keeper, sdk.Context) { testApp := app.Setup(false, false) - ctx := testApp.GetContextForDeliverTx([]byte{}).WithBlockHeight(8) + ctx := testApp.GetContextForDeliverTx([]byte{}).WithBlockHeight(8).WithBlockTime(time.Now()) k := testApp.EvmKeeper k.InitGenesis(ctx, *evmtypes.DefaultGenesis()) diff --git a/x/dex/keeper/msgserver/msg_server_register_contract_test.go b/x/dex/keeper/msgserver/msg_server_register_contract_test.go index 415087eeb..9b989ed79 100644 --- a/x/dex/keeper/msgserver/msg_server_register_contract_test.go +++ b/x/dex/keeper/msgserver/msg_server_register_contract_test.go @@ -22,7 +22,7 @@ import ( ) const ( - TestContractA = "sei14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sh9m79m" + TestContractA = "sei1hrpna9v7vs3stzyd4z3xf00676kf78zpe2u5ksvljswn2vnjp3yslucc3n" TestContractB = "sei1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrqms7u8a" TestContractC = "sei1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3shh3qfl" TestContractD = "sei1up07dctjqud4fns75cnpejr4frmjtddzsmwgcktlyxd4zekhwecqghxqcp" diff --git a/x/evm/artifacts/erc20/artifacts.go b/x/evm/artifacts/erc20/artifacts.go new file mode 100644 index 000000000..9e964cbec --- /dev/null +++ b/x/evm/artifacts/erc20/artifacts.go @@ -0,0 +1,22 @@ +package erc20 + +import "embed" + +const CurrentVersion uint16 = 1 + +//go:embed cwerc20.wasm +var f embed.FS + +var cachedBin []byte + +func GetBin() []byte { + if cachedBin != nil { + return cachedBin + } + bz, err := f.ReadFile("cwerc20.wasm") + if err != nil { + panic("failed to read ERC20 wrapper contract wasm") + } + cachedBin = bz + return bz +} diff --git a/x/evm/artifacts/erc20/cwerc20.wasm b/x/evm/artifacts/erc20/cwerc20.wasm new file mode 100644 index 0000000000000000000000000000000000000000..aac641633a3f97d80fff41094817e5a464ca3b05 GIT binary patch literal 252665 zcmeFa4YXxfRp+}u&gVV%-a3_3LIvuL-sg12=v(h4ZQ+Qi0`t0Smq`eN*p!UCqMk4P zQh_Q-ZiSE(1%@{;sX%~$K>`Fi3awFsi3l27RCw~*0)s}48Z}B3#Of9`t%4EKO%Uk3 z-+#`v_TJ~*b8mf6seoF6d-h&?eakwL7OUotEW|@#=kb=2yP-%4?H67QJ@g4F|pG!j*gX9=PJwS6*}V-YY3@ z)vdQQ3cTXlt6$2O|7Ty4s-)+=>Qyhl^4cnG_{x1Rz3$t;4zT(yU4qW|xukl7U z*2xF zn!)w2+IQeJSG@G9E3dwm{{BPuM3!VpC+jfx$NVp={&i9w(-h3fI-O3M57%crB>XQ) zdc97{=TL0msm1|`r@Z0ZaS)R9As=@ws(-x(&^t5)1E~U8&@c-~{u1#(H%QDqu z|FSfrK$@tDUaOT2dV@jMYLim^Q$AICI`xvV8YP2HI~!U_D@$6b3h=)L8b~_)w9{lx zHFVRq|I-WQ{Y5p-(Xf8$cg}}yn$K$(^J$m2JZqo9NBW+n^Ess!&wNIbcXM?lUEIhg zzU28?t!{c<`iuP6=F|GlS@}(AdFasLWN=eb{>2?1x8KL~+x?|*JR1y z>T6$l@XBisUM+-sOWNIc!@ifkipL*J+cveyo$2XUUwd%hf$zKWrTc^iue|yd*IuzL z`GvH1wq1ikDpZ%6)m(|H&*(%5?Ax=||GvNH2Zy z_x_)+-gPM7wex#kboJivTfS!B{+I81&G+AMN4D!tss4MT{r5xZ?`ALfXu9k7(udQR zUG@9vAEbYn{!#j=^pW(B(?3Z+oqi_$)0CQa{fAWlz4#~7_ow%yyX<$@t`DU5razVb zZTfTR2h)$Gzn1=bdSCjp%KrIu*RH=we>wftbk~1MKau`?`ZMWIryojp{lDqQ(@&-^ z+I4?=IDPT2XS>wNT@R+eo$mVG^ta%K4_RAu^9$*V{zv*y_9xkk-k!Z^*X`M^cV_R% zej?lTnjeP6y!c(&?b%Oe_hffwcW3X+ zPk)wuF8hn@b@^Xrf0aF&{eE_HKKGM9viL;y%b?Ru>5cy~DU1E%?Chk-%HDF34sXt~ z?eV`NJ2&Z->GiGcdg_fY9H-?O&s*TR+_b!uB_n;`XivGm zZy2{OYEfyC@>8ax)_giB(lWVZpVcv1)LsmRaIS|pso+dTP`qE#nM7a?yl-zHN@rewaQr$s&GgCg6UZ5XvFPd7K6 zpUv~s-m0ptq4{hu`~+lBYSbU+fpHbfXD5R{Tc6F}lnHoS_K!ONuR|}#3k3roekJ8? zvn1F4B@$BCsKR4Otkmu<+Co5yoZ*E!E)PSf)1s&;;HP_&tt;V7G5 z0QTNUvCPxWR#ib23u)Qn`#f2B?DIGnsJhW;ZcxmXPo~$8+f;Y)LXQ^5)Na-BEiZ3R zRn@nvi?^qaDxK`6x<_}U1z^SBN!NRkCg>c`18uI$BiXo#}{dOW7SM zlUC)g>S?PUu&O*{o%vVFdMKsKBm=>v?6y>pT*_`uNAvoD8l}15E^zrI2$W$WKr^1_pq}oychSOpV=^GNI2l9L z26#0aJ_YNkN)WedE?$^3$m`QA$af>YY4RNMNVYojf150qe}W(> zJHt1p^QX;7) z36Rx84QJjT)!BhPN@iSxOj_s^z2T2#k|PcW35QUq0fQc?=TU1e+& zdgvRPZO*Py*}jCZ4KAQUzZB{feKiZSTgon^h{mRo(fl}F%6j9BL>>}K2X@pFE`?Of zog(I-h^$0@We|kQKa?ofMFQ@?$RR&WfwT#fj~+jBrk)6Q5@Cy}GM&xYCiP1bL6v7mf=+xEgHAI?-9A#A zHvum}hM>%ZNlTa&g9Mr!-sAWT4 z@i0`pY1vg&+V-I@8^+MG*oZZh4l*!+lrh{(2xK+uQIj1$qLuqcYoq$8a+`z~3Cj9l ztXeEvz@ow3XJm`6+CG**geb3V>9%tP>_ED08&!7HLAO1WMcsD${$lQ!bz6y9i~!jb zJe5JSopYrVEqN|zwilRKLe+)aY>S#L%<$Y~i|JGAgy`3gg0-UbXOH`?*Y<;6yB;hs zMKqNJ4|*-SauoVfkxj2P#U(SnHl3#wm2DnL2QWn~Gre}6_JUrU&JV>t4x1{uMIAhx zjx$tA#*~dxP^LyV)pdV5Ubl$doiU%WnCn@g+yW{r#9MVt3U}1;)D&uV`GrK)$KwCG z(dvR}#rLP9g)$ZDgn@eZ{-?>0svT5X>%?Q$ckjew>9{97SZb>mdM-~NLBEmIdLUii zoF|d1=7&)&?z1`{qS!^K`+0dUm1WIL_mSx?GHuQuNk{9|)rZqjzjQ$v+Nc0v`k_>F z@6&fGJ6*D5lkr~*D*h8e#y_5py3}R5k@vT%OOs&Z@VZPjr!GtGmDxbb!b;T~_(?U- za$^w8v()p5}8?5kJt*fLX3hrJ35f;T-i&Y3D<S^^gnRE=|DH zFG23Gy4YK-ixEj@;ciEWJPUU#%#D@1O8a0y^9NE;j-qr4ij2JFV$f|Z?NpitQdCNJ znU01IEn6wFmd!JMm}9SAEi;(C_A4dQLiFOgE$DNfs(Z{Y`J^c;SG`w~Cv zx8?;u{qwU;`~cF8{9p`iFvZqcIeID@PzTLIurYI^BRzG1o?wlXKt*ktX4{8k+|EGf z=$pPO#fff%)pPmrqqTc3LzaF*$=r=Lj}n;A0{fD+WL& zXB{lfu94Nyb1Dm#>X#dq>blBOeJoh2GSVP>7(4c+lp)u*`xn9Si{XI-kWytuO&A*h zQs`{P29VzE@LMk&6zDYr1||*oQ0B%)+te9ngl&ZWxPwY{KgiGx$j~m@o3jhH<{JyL zUWy&U&rV2{pUcpR+MDy;$W-3;j4-H{vb`85BwhuYNH;TeO~whIwxJOP<3z4c^lUP2 zGu&#s|iDL#PgfPQQn2r2^AlNl#HaLM*7L0XN^$Ih(0W%WE49rm3JZ7|* zW{XCT;N(#=)0Pv{#x0}G;OOHX14nwI)(nn`!_g~MIZy=qcdl`m0BQ#oT^ z_^d^iR5F%PBtFq{F(PH<%uj07tElVrP+2Rb-*KB-Hn`@zVV36n!&#c&%2K76f%o;~@F@e0$CdFq5$rgU4Q#%w2xY|O6Y~b?Vcha$J__|c|QuQpMXADH9pjpQT>ij6# zGoOVcPlHkaZZk>nWq^h625%^Gi<5x%}L!q-oa(3hMvIA3yVjBECiQ#F(ydb&xboA&iDF}k_s4Ity7NjGm( ziYRGKx(UnLi4jkul0ZClB`2A9KB(RuJMpZQJ)qVbpYHj{lYZb~w*<&nJnZvI=^kw^ z@}!+Q6RRr-^H5iClIP)ZHFfOsuvYdk5BERi^MKNVj{w;}N`3V@M*$3Gd8_OoOwt63 zPRqj*CIb6KEK#QAug6zpV#Ti|5w*(0T5A+EI87uUB{&~akz739g$?4S(EQMDU4A0Q9-2G8K2Xd{OXy*EFKwT1NSl#ebB|p z*cE5@=!2pgqYeNE&r&qxd1EwRWq}#snxK$DW!|i=t3&^5R3fB_T1bN20+iptYQiGX z4(fT){kG(c?@ao1aDd8MK4baZ1d&QQ`LM90^%ZF1D9J(EwDi@bjr1ks<1HU465T=> zJWbyujPy+!mj?%UWQA#hp@YJyYSjR3XA}Cn5RO9lv8s_AD_^}}6vZLBsdeZQj7++y5ATlcuP5!gZiq$~#@*HtP zp^v!*{^jb=2!?X_+&(} zU|upjT*(DN9hsB$%~?srkkH*t9LNEus$B9EGRwxWTa|LYv>2Ny!$1-Iv6cwtsK1H;_2?z*W2pYc~; zl%RScY@^FNU}J)ww;AJ@pi*X*5vf)SU@~@KTw?Ru{h)=LWpi35j2;%MPI$!6j85Q& z@=l0_ui6PksF-B2NzH7j(5#bMk;ji>NI1CbDDh<{+0aA4c7zcVQqVZQjH5dwN3mg3 zj{~!?u~szitX!)pJ*}KFc)ayazMj(_5N|` zCjF**Uui3=tM^tzyY0GtqlTy2Fup~TdF(pnTG_=Ia8I0iT+BP&7C8!a~!TjihPCCH1REr1U(N_KRq0{%OY z>fM$`t#(vnFg$m(JcG=<5hjh*r)p^abUu-!1*?&%$mr@?RiE3DjLzi$XYl_Gye}#w zO5u_KOPaB@;fCbef29a-G2>ZJ&km-u^;H%M^#D!2oMV0hOg5~LrEy0iAhievZ#K#m zoSzHGEpJetN8@}v=Wv-b*82q)Gjd|BGUhEZ%JZ;P-!ik?UkOE^ z>a&29Y6#Gumq}*9=h(2A+safv!g_9J_-u7~_c*uUoERFVDlQ*)L*ReR>K>5YZd7%s zK`X+YA;Dft*>#@U)$?nKs4Im&B>SNop^>a?#5A;E0~E$ud!y4?hjTPt7Xyp( zVx3J1VOyBHb3}Wz#<&s}8&d+R#>%6Qq($XE0sZkhb8BcSt*VQC&p;eS6YRZ+gB93Q zUp8^zObobRfwDPZS@#8q!^dsQ<@>9EB7)&#gRvH~h-))KpnHa2n5a%${WZe)kNS86JE2QM>B*|7J62&ZqFSojHF7CfvaGb ztURKnc`ZgAls#^4%B=jF#X>8O7W5Q~FsA;^+2zV({;jP1T+E|>fHL_3H*{BJ^F3QLb;;^$4RDavS(lRUSD3$LyKZZO7o_MuH>q+P<$*;JC5X+S zt-Vg4dq8S$8BT#qM{K~bBNOKs6ldN8qqf;C!^Ek07&7o^bWyNXqpE{ZD_jFmTVw%2 z;jN2`MS{ZdsfPfAOmQTFH!&~ZtVR}LE+A~*Ut!QT7`(7Y1$IgoV62<-ouf0!woU98 z2N{hLgX*yqy<&rWO}*kw#c1@@zy^M308A>*z>y+K>1m=bkaMmlm$Hp1OkhMZca2#* zp?S~gM65gDrPZeH9#W)wahms;7>+F?1m;q1YBsTU98`% z2&2LrhM*{AHcDG;mm8KxU5{00$qi>hl&~%hzii2a-a>Aq2NkL$=)K@sl+Mf6cRy>K zmFykOo?qq%NAr|cn0l+USfUpD!P*BU+N~l;%mCcdpf+j|9j->?A`csuhd<1e z5u8DV{PZ*$FhAtFGKCb495-#z3lPc;#C)|mB?N^E%5>TEl1fVu^FTES%p)o!3yRa(pc!891(F3# zbZ`Qz>&)i@^+I9`#WS|D4M3HT#X{9RsYkg9g&4?>tJVYga|R=T;D;Z#EV6e(&5)o%o?jBm8&l-+Ash?y)d^U zi0!*2i+&iWt&XS#%Og)IBxn=QU{VIdCP7UMN!nmyJxw(j6jKc*x50q%3{G^)OqrK8 z?1_k>{b~;BY@-XCGSBd!C3Q}&ar4^zO`5fQkHRpnPJmg#`-u-_N?RYYM{iw}oGifz8U@CC8JB(Mc%xpe=K3!5| zQ5-M|e6nM1_(lo9ycb5DOW)=_A-s(;`>pQR0))=@`oQxza1EM>4%SdaxQtgwmH7-m z9ZrE4)=q&Jo;1tw_NUd=o9|!jB#Pzm_<=Xwc36wfbqv&ISle4jR`!5eUp&h4L`^_qAuu%1zb~^U zHFAw4a>x{tW8VN@=I*SyyPg9P$UXHdQUfA@lYX!ov%&=?pxI^Vf*e*?tDLB-wW2Qh zI_kP?kJ9X!u{kmxY_z-P#(0rx?2W@$#omS=$rN2WO@g7$^y5J%O_l`TFl90g-xR67pAnFR`P{9di4&~ft^9m>OYy6&n zN%=1u5fI>pL?|l!M8T(W4qZoOBj>a_1qi$# z=zLFy`tw~izx=L~#xFm4H1+4Ez$(LUM8)G3yYlv}9=S&tc$Y2ZDF-)!$7%Vg_ZpQ* z51JSELqU7-8 zSmwdw0ow@MqboAQV5b`UYB`0FW^ldpXwztLaB*-FwWfd@+X25t@@G&?UjD;vhsEPG zdtsz(6f#~KGAUMb>u5D&kIbt1 z>6(;NzX%)xN{KStGoM2+WC|Fi!Uf5#;al#!oXBlN>ef?1U~sEw z3mnEo%_1@;ZKhE4Wf?duFc;^a9|_WNUzUXb(+ZBZgX5i%aMlKg<<85A+)o)ePCflB zxLy9$o578RC^XykHNE5lAGKBf& z&@fy37u(q&k&4Ou|DAM2S3p+imtW!A4E=H`Ny`7uCw^sauhcND1sR7I66pjg{P@jU?-m3 zh>9kaEE6pIWAi-8`LKiy9z9Pu0$7fVhe<9H(U04y1utwV~(n>4%DH#k;HM1GfSS91zMBi1&2#lArVb&UGB-Ks&WwmVYJmCRE+b|)5oGHsStqywc53Mq$QI+nP^#X9b0}ja7BN9#OJ+{&c*;5b(nWix zR|u8aoe3~uROW6Aomf~<#0qOQ1KV3M?LCuYXwq%QwhuwS4+aoa4?h|fi#r$A>j06p z{AO`3r~GE?Y#KGqv!-z&3#McIZOmWZGBxRTo2Lq5=FGBln-)EWVCDDcX-w18!c1Bm zfK)*2^wlxJTZg>FF}mn}Iw+lNx~tLX%se9o?ek@5?Qxvt&T~XUv~f z!=f5hobb@=2|Tb!3XUnA!UMK4p>Ucjv3x)cSnvzyWO~x?D5<6MYamS9qcYp&r{h=> z4?748H5|!PC73pAb^5WFXtj-FFSF*cmw-#?s`E^E^y4n~@`$@^kH{Rb#ljX-*eD!n zkdBJ(1}qzA#b9fG6+cv{T38+2BcL9=7Th(BTIN^QqOch|dUK)&v3ELp)0}mlsp*i^ z0iIP~Fzj>jNXo@yH-#`d2ULE^H!%cpB0G6fuI_K6{i!;EB6|7=scnVZA25Be7fy87J5|;rTs=5qFv+9}wb|7N7 z9H)fl_RuQZxo(hi%Nqzdt_JLIUfjxd)~InU;9yo64RUglJ6f4hVG2PSnr5)10syk0 zeNoZ5h^1zpg7>3Bh|oI4Z_ts%YG1KzF}iQe02s7@yJt+^ogg#*Zc9xBDFrzw`pfy~ z10KN@LIdlfTeTX#Ilp0~`n6Os;oz0#KqH&;?N*;^*MTXFg#%MG77l_`tDMK9bAHTw zqe;YZKFc2*$j{>{Iu`E{me1}|{LkR0`e|>~Wv5#9WfQzq_;+l=pQ%|x_-j`q!oQsX z5DETX^RzuX_Bn`hXL-i%d7P~P$R=+g*2zF+qY=)co~_mRF3+y5HwDozPp5&tHQXv> z3X4jiBYVd>dnjgbMjCUFymHy8;}gEhvXdtp-wm}G0$(%gsp+d6efRo0!wL~fo}-A+ zIXM41t*opqK^-hp5|jv@0I?a+==ij(0S&2kE1_WrEu*jTk#(cLICNN+9Rdo!#-cA0 z%>Y!Zf_D>8q%LJM0k!HnQ)7*Zkk+uq zEg;#+)-E{%Fr1T3b%#Kf1|~j}rLD-@S6h7Qv-m_u6pe`1dl3L8T9GFw@Fha|q|w+! zPzB;f7^lN?^=PeKIO>>8@I#?!o&@#c63IzBrD>iYf@x}epTWC4UtGy2 z{7V~&ooT_oj>@r|Re>zD7+94rUH$A_)^_(^ZMg{h;v)GZkBV=ipPg`-mT@q>eGg#y zbh3Z(U$#=ek|SN^UpwufCg$aM|KcD^;=i`3j^JMmW@;=;G**c7@|=q)OThmD({(RC zKp2d#%vU8RBZ!5qAXOq73Hp-o*5jspZQbLhpbBkQX7#)sYBXgsUY&p>^mF4ilNc zh(VLrD@MA+=gI#qZcLReS!IbHH{0^Rv;Lfk0YZunnAmMW;3SSvV%XZkwvIPgc<{Qa zRXv5OG`{b9vZJ}D+I+8tazaP86WVp|j+-z*Nm0Y14#el0dRFfC6}&kPOKlSlpdZBv zXnC7<=%+Q;^$pE({X#E66>9@+1?@&1&B7$ZgvicN9HrvbG-*DAQp8l;)M9G#nBVF& zc)rQqt2qlqDHbpCrY=X9OMLn=#&~Y>{ICloOx^bVVd~%$YE@Y-B>!`h%bmIHrZNbR zzvw{2f1`fcJ_Q(x6qs|9gYr{5O!-C-HfK$U>cR+Q&=~LGSb4J5{T94!Owr(6VJH!I zz)&iX+gaq%^w_lenoViztJo@*14?wj2?T)(ooO3z*ZaPd)Cwu! zj)hxz6M~bPP^J*cNDCja|&yPg@DVw>%v04}HntQW%JKK2WCy+o)ioq&}Gx59AtKnaC9K z2rd={@z$pkE=vZs0GA!_VqZjK&=B3J2ghG>7ny5%;fx?~q&J3#u!ZE})=hV85O-p{YApNv5r@IIpCShQ;{TRBJ0Q{7l(MdvwX2WGT2FTg@@$Wq1c zI8X)*vy;bHDIrI}WC`J>mXj%Ftnoux64m79FIdSr9Q~}_O_Oe?l_Sp5@>dlFpsnDX z8dlMa_yunCwES&-)%rIoY4ug-l-bum(pNhVQ*t!$8mUM2^)vdqNLd9BrRA67E80bs znK(Po{MY)b#Z+U)p{jQL+6Xz2Hb!l4cq94em0x5W^0U`WwP_Nv@($(T;H6Y4F^3l7 zo9`Anb6+|EF3PfW*3Vi=D{Zw}xe&2DqS|bzjowEvZBCLMpZm?X{?;FT=+0mK5`kb@ z-C%?jN=o^KPyE79cNCOqBlv=9oE|{{o{!*b%F*H^niV~MbF;_v*Mi9)F77-OI{JQf z51kR2)JG?#D|=SnBGCJJjmFyycuLb@bIqp1a4TRM@E(|p$F)M!><@GLqt%L5@|sp) zuudzHrZIFH^il{ebb79z3WxQE+%(UD zWsxk^KDdxU=kL>!Hhh-9tRZSmptU^Y0w9DN4F)D*x8jW@XPrrG72GAvdjg%N`*UDREQhSPwWGQ16V#@gXPo!=C{-! zi$+su^XTe;*|TCCE?goaJ`!sLa~g>F#OgI(ca$0fCKeGBWf+wOd6?Qrif!DT9q5ts~eVtCX0O z+A1MICC3>hOltr1`jgB_Uk6vvdfrLlq=)ppl_-R!rXWFLaA-=;{;qmb` zYMdf~*akX^8W@~1KK(awJV>!u*5lF2+A6fZlC4g%qsLTZ7YJ=z9=@Wk@f%@qs&VBc zegh0{m03JK4mUaMFGoNwMK0_|_~Or94U)I7!8K9q0|9=`;CadrVigj+clBZ1wQ?Af zpn1Inl$+B{jPT1|W|H-NN2_urUEAgcT^+L;v=6VWGfEw~i~_WONPV4ux}p8Z>ceJ)!GT24jpHj3j?xTG@+ z^tIJFq0VpL|E;Udf1sE3DqrVu`$c+1I_(#Eqp30{l|UjvyiHZN0(+KLf9!84$~;d$5WydSahBEuh*7Mak(L=4_fAmJRon37f?d39nNB8))6x}xDFtTW4cL_bq zFqzxL_*yPos<0Vf$L(WSx+<><3fms}TFh4WBi(rcv>k@Kjflf*0R zK(19Sf9rSC@{j#);pUrZ`5W-3OUUxK;F)0g|M=h}3)3ehMe>uxD-@N}kfz2TSjSCv zWIr(O+yhOPpxTb?vS+Pus{Ya4bmLSxRX?l7<{%CX>ZUX6xC9a?WlDm%X&;R>a*9E- z##K-hx6mYtuJqG>ks9eCc3#6zdwdRnW)ESzToV}`3CCx62g+`U-dJ;5KBZ+SlhKs=NeO!#h2-0Y{S-f{r8Ye>gu@JCLiCFW=@s*ek&&?c(D%t+kX z>o(B|x*3k!-m&17Z&iEe3n$y&`E9Z2ljWS%G077wT8heiZSlN6DtO1P*!`4>z)l0I zpc>SSQBM9+ddPhAWU{6^rO4wKIZ^u2xMyv@wguj; z;?LYZp~JXOKP(g37>-Nyn7(veqD8inz_N<uKD!qX@a70Q>{7IPX%|YCOVUe zweGyt?sR*7G-)jQWkr5NRLdb&d|9U-*}KJck&c3I@63*3f225XS9N~$81Y%*#@E@O zn4#55F%_zKFji#>z)~L-xMknO>_*9CK~4)p@B{d>J!wA(>mAxpY{|(dEN$ zI-Ha!BG%UMHWuhOGPGRaygC^id3m-Rp#*O>N@q80%{IA$uGmqg)D)!Ji&9(ADsc>voYMgbF{g<$Kf;InD0i=LEep6yT;Sfdz(V)33aw@fow zy(n6N4C`CMy6p}SzaxmF7#6gIxqUx>EGRNSG!Af^#~{kYhZNs%P?&LwsKvz+qfKM_w0YcZ?#tz1@FKJAIUY7np&^9`yls111qauL zS_{P?Til{$pt6y6t#vKlQKz4=i9unf9P@8z-5#E&ZtWgte!TmQYX9nuuv>5)$yjW0 zAPN)JKmvH^)ma8cN{+fY7y+p4w&SrMkk3w6HnZ7I*M(x;JrE9CA_IJIc*kn%6XG&O zHB;?+DhL~us|x(IDDQO>K6BRWS`gAXZcL0iP(f+}9B`aTaCEt_kmKxYP;d`Did=+c z4Q7QYfheiA`m_#|pVOHYeeL#P9xenQ7_V2|*C}SzlrfWbDO?)PH@9ou(gw>yYfzL7 zVMU!{D7v&9P-sH1T&8q8BrZZwa0UW+e!^LEnRtA-P>xLx;f6Z(REL&_&?1V z4!hmrkk;q0s|i?)VS^Y}YI~gwP5caTt?5{P{?-SrGfEp22d&5Qob%o&Umx_2cu{UY zWt}IhMN#VCZwMbZc*1N$+u_`Fua}LTbV}Xh2=(WSj~S%n9Nel3kp*6DIQ6{h5z>Id z(AdaoXPiFYVIn9OO4u|B?s6zGCEDUZ5ql9#;Q#WV!?Vs_&TbS7v#a;y8u1!iy4h}> z@ibF|bdS7p#nbEvmDmKLsXR@3_%O6#O(w-U%{}&yI5o|-oSP~?40j|}xujwPojTv5 zkev{YAYjjA`JVTJjAxT&Kk zLi3kyOHMaOjtUXBTTEyd>`{}##JDf9Va@ApA&k%hdIn!TEn9G3x-=$}w5Bl)q%pOO z!?D-5(wIOYX-r(1YZ_ClLBtlpV;U2gq%ra18dGaRV^TiXn1p4O#$;_tW1?8CF$u+K zt=5>Vm8dbbOoRz1gFeK~@PH@~k_gb74kTa+YTY!ZR#|CGW>~zI%u!>qgiE20R;4lN z8OXcFWC4n;rp83Ay%B*(>VD9e2nla$OxE*FW8=(Znl}PQPzuG0;XPx~`%I0=0OGeJ zy2jL!#-u?yhFdoSi3N?Rjq+o>xh17(K}wVE8WOLvCP z5qazh+yA^Hd&h3y9|4y-x(&FIgL0y6?T90Fr7 z=^irxg~bv_*+-@^w4lfc5a8_<^XXRR!K#TTKXXTO&nQGgek^WZ5&&w4`s4^)nFILC7q{Rg)_!hy1 zCPH_v@saQK?p(zMI3xyKZHMj72&vGEaMrGRklR_ic`>i;&ctZhcIW;@Oo458?z;$B zV!LzLo@P}r1T`p@r$=k;!jbL8rrk@uGbE@NY1RO>1p?n>)=!?o1hu~^XCGLXq6(pag*u)`!QA8$G5R>ROev~g$4xAin5hA(R zP`*r-PAXqyaOKN{ctiP``T%mo(leDWYo08##N#ETSIUEQwfxt} zrF^-P(p0_}d98d+C@MkuqW7+RElT-XXO!3SXzIbO=84PP z=zu?Pui6+A_r>@X=6+XjFwGuRe(g-5ohpCWB!k&O{SPY#`y3~>p4e8CU&+$^CR1TY zWF3AXQ}{qS{5$(q%AaKG1}F-;T2G2Xa&D505^ZWO^5ybL7}u6^E3K~6j`X?bKdWdh z%hbwPw}j>r5?imPW^N49jVq+1(T!Ez3us5b<&nn^C&S;=wvn{_IL{?<396fnB^B)- zKAcg3skdtXsJ;1=fN;vXGq~D!X%2nYFdj*cu8;84t^z>#CC1a*ZZN|z!4;%rIr_^F zzKyXT(JZISHb_C)cVv@i_+K>g3=gUX-t>C5s1>j0r>h%ys!M6G22*@Q%n$15xH{9q z!aB7*70p{L9?_s3H(JhvlliF-^}bQxHEQR{@eSp|IG@GU8|QN#7_TYUMlsvwX~I|g zmgQ=7mdUv7SN`1EC?x$rlkFQciL-7Ggj6I4K^F z;xcY(W0nvAG^zGofsGPf&3%|2H%uaxOM-70w-k@kjM8Xf7P*m(tr1s$rb=vCQ9_ub zOwx7~k21)UbZ{Cya2d&uuL*oOg%FEm_>$=_l;0qnA7|n?vcFPDapp}~cB>h=Qs1Cr zEL%R!k$5PinHgznNrU=AZI&q^(>NWzl(q~Isv~p^Y^Bk#n>=yhB!OVN$p;T*P4>au z-$Z^nie5++nGU3y*X!{4=8ZgbCNr{|d zaxB$1>`I`kzRB9@<6*Bl8vtIb1~tlB*l)4{JW3S^rZhVyn(C1YKm}DJ9V&_pD3XGH zBBmiON%+-wrKzIcD7B(Giz}P1j;RfGN$OA+b^P9fAM_>9NEhDQH9rP3iaMZP@JC)? z+r?C`a8#7b?R|T2|J%{TT{$Kw!*r6o*RZ9kj{eil;s32qR)Xas8pBBNd*l{)@!}}1 zD$Zhy6{hyq0T}BiwHP4%gg6?qJjVp~HN=if674-c#QE_K!;~cxH)WHT=1qRAOz?(v@8Cf` zaE+s;fsxnWH)nhNx=Crr%qK9b#?44@%u@9wC2S%4I>wpn=C6eDm+USirImHV$XkZN9`@pjtUjjhzggHJ40969> zNseuGg5qqtaMPGWt)FAmdJgG~+s0q5KH35YuCvBWa}ce@$+E2O)oU%6{~b-{+Y$@W zY?Xh{i$n6Yz^OQC|-ooY-BWDZpY;EN|}HyS&_Q0lRHrPv;#UG@?+RaO}H4h z8hWsm{RU6Jjtb7vESfA51^2CCn?*5T3T+fnB(1dWVGxp>Di*63sAjHuS%OhiFG%~o zzxaAT#i($}X$$P5VRKpf$qR|oUyX8_qfI)A;S!U+<{01!#?4_2>K&BdBSBEyPeD=g~SR_UH(_*m;%LYg^JZ7%e}g6Y=$c6tT@aAOvy z!SWTI^EywN=OSnlpbwX(&LEMgDKeElEY$#kpnBzbkvCg?=a|@#-5+IYPmBiIR?Cv9 zxcqvRP`!a?i2b@k&W@Fm1_i_<&F&~`!Lt@QD_I+)DcyRl`gRNID1OS*&H2_q@2eavH#n2)ORU)&e2_*+W5SuhC6T%rFVv5?oz!>rcK5~OVC5Ht7 zvDE9TF~^ZIsU7KIG?a>SZx&8V-<41&CYH00}K~86*UE zCLU6gXy{3S5TGpqzHfjC2?7BLH3M$oK^(U5z`zlQ-N1#AXANAaa%g2fU#dY-YZzz> zYiSq`o=(w~hS94uj0S{`4^fT_JU|B3VH#6dZWEv?fQa~^w`pu4p5lb?rI_;x2fYqT z!*Hf+Dh5e6g!z_+;3@`6g;WeQ4XGHMCB9O{029#`q=BwbF?tB2Ko@h?a;0KW=sFYU zD^!euh7LlR01t`;FR)N9PY+)}wV-@xwZ$|OD%|F@WO!(dw4q@%VX9d=N2f-p5%`z+ z5lj^zWRx^5NmLrf!bO}Xx`=?Plo?v$%x^1P2z_M>1XLu#Mcyf6ig3|N#aLOdA8m(< zadK*l+Rw?UO~EHKG?r=A1sN@EMDp4M_&2dC(<|??W4M3*-^N{l+m~gYHx-m@dxxi>F&gswjM{a0A&OEDw#<+DG;mbZ+vVh|q^kO~G`yxtK{=44 z09wk*kKBG31#WUz&@t6%Co46pqfZJsPn!eMyl6?f;*)09HLA5lYL7r%PJh}2AD2i!4N%A6eUnB|QKv0Uz*(|$*!pR|_^ z+Q~<6%rsFdmKcV&lrM1<84(`%IlE-BqNFWfDVV}WgnlyBR@lvCsos`cqEkRuN^p8w zSQ`4eG_#9<+cDvmpPJW1E&>7V#&@lbS)ZxSIV(gGHZ&H6@lWTZ1_tYzOvYK~@2t8D z&sGOQyjXiMMQXP25J=73hZ>N%4|!;i+T2P~`PUr;jDc3Y_x5OIZSU=5Sye=P`^&cW-qH>$fp|?-y}YLU%4=HQ^E%PGc{(kRH(V9A zy|?;cKGNyJVehSdH_pfUc=;~Xs4cdDVn}r}_skleH}}ly{6#;ZDWAP(R`6d)9nS3u zII}0W31?mdoVmeWOk3aSBJq|roN0O%oR3!4;5=DY6&al81ZQ0uKPNb=dcm1rgYyRk zl{tg6Wh=`wvbr`ty-M~FvM*z2d7q|h&8Ye}WT@F`enS|@lbXt^7U6;dvNZ$Xf z?wlEqWpD%ud{=CrY^+x(AcfA0nJY95-K(+EV?hcn#VsFoA?jf@rnvM~kKi-j64r-o z->-GTQrpgPEI^B{+tSsas=^Kxva;o?SB$_y)n+XpQQ;O7X08m@6@nDZlCO7h2w!ZX zQ~tT?f7Zmo5X$Rv>GG)o>TCV0kz6_AZX1}6F_Q6b87&qAYLu~8Gs&H zy~dqKsj&ii5kb!VzBGoBxLPOotf*2^HtMs7Q5hn>{`HLgj8g{cJJnow1*m1#uLA1p zR*b^eDkG>Xz*M;C2TIA8zDkw&k*24wf($%Akm!%8tLsh~WPV1?4Oc*>xnXsRO7V9Txe^*SF~S!V?r6#RgQ53Azo&C^dMKU@VBA6mQmc_^h}bM(09?-s$euh72~`ar;}dzp67el7Pts`f>Ag&CoZ0BV9v}9r|mBCN$2BqMNYQ(oF zw}~IZU^ntZtO|<10=hUzpnT(d592Dg;n;4_)ZIFBnwLzC17#FR?I*RZ#!CQ`Jy_@Q zqoU_$TXz-n*rsq2Ee9@?;t3Bqc6qJZHw!65`BpWtznd`ZkV z7`z8}(!`8tZc3|2WKWn5aS964sn#xi@(eb?ni6nEsRwVFS5y<@Q(3`^@35PFS0Oi^0(D>kXhYeW$W#Ix~YA6L|?ApF=tyo0Gny3feA z67AsX$-16e8(~uN)ao~0YM{1m zhp8YeLIC!I@6-u<1$W?kfX(*4@m;wGZ^>dPmq%5EsV84=tR2W@&o6=HoUkXK{1-0} z11#GI$8eTQc(!>&ZDkuP}z|gbU1i;m*-tVML&~7?)gbtP~cW?-1ccsMxDID%p5!=yJ^tG6x+$Zj^VhwqxA#h z-aQH&|K4XUb`uK$=$L+{>{r}&x!+KSRPo0Sf}4sB^F0Dgr)2% zAE<9I^dX7hN@Qy+uzi1(Upm3eGZX~C;T8B46`BMsaJ}_lF!=E_ZQCIMy|M5H3`KRSUfj0ZrL(*kqP!L zUd6UgR}B5KWXM+C(H~;P=GP|)bRdVHg6I<4c;wnDyV2Qik&s3aNpwm{*&y#mO+CLG zLVKdx!5dQFv0Gh{y=qI4(nMR-G%wa25e!vCg~Tm?&G!Bs=gPPHP^Cl0J_Nl&(I4U*acWJD)nt z#*&`zYJ!oeZEA3x_HJBf&c$ruP7INbJhg2ep4sOAraivG!ZECh2^MZ7>pOs?!jaxH zU5^^P+HW*RzJM6ufLN5WGZV@ku`|>3lLQzIO|-Sq0x0L<>%s+!Rj45DEag!I7P#P` zOhyn~W~{wP2RA`66+X^xwgm-+W?NOWW@4y4j1RR3A@HLZ;ILaL z?GP;>CIdB$QkNtJ$J-?NcDko%B6+IR4_Gh)NEpY{JpJHD!{^5-LCt$_ph4}5*BQ5r zT_S$Eq6P^_ztGVDHMuG9fQ}i48@YjU-iXnrQK_ml!uua$LVy&)JE0ME!iYAG?0h5n z+BH7L(lJolh&gQRn1?n&-waN*K~gfV_~RF#L*Ewe7U-MN*YuscI5K8qa))kAj@u~x z;VfQc;X0DdFE)&yn<1Jtfm#n>N|ZgDvJ1O0hNZYI6G>{9<8iF zcCxIhr@j4USNKYktT}1wzz2aRE-b9p{X?h{bjHZV~Zoxn;l~emB$UH=u358`W zf!orMq_YdqP-|;KVmGeOuYzSYz)HJcL0d`P-AShb0zGK2pD^`vWBsf zWmPNf?JxTxdQtM;*biu;`$qVdCtz$WY=o~5hOyIy1IF6-fU)uu2xD<( z2aMHmARs|Dd?!j0^Y3$@gK*&|&T3J!;X=M0$L7GM-%@@0Ya!MOYNqA@bx^`9thV`i zPD*i%?3f^|S0Ko_0VTx`6t6Kd93gHRE$T~LJ+#J|WJUTJMp|{2+Nm&dVtH_?#1>it zks2j*zPN;nbZ3k7JGw+SU^FHABtp77W$=|sqN5@H)0OCWcwm!%rzITljguzr@G1SA z!`3PNE`gZA$E??$iTpy#J#MwvB7a^YU#ElD%G9*THx>2c99~$7{C}#TcjhPWgNR$^ zo5a46I;Un0Z_6aff>aW_L>8bBk;00g%rPCDM_Cz6BImJNYvPnB*5Z^(r8@I9McMAD zsPsrmS)a5B?V~5FCD=1gl#8iVBqA>-Uuo+^54^Y>mv!+i+HitCHsJ*dV-;gCgP;M?oQDND%JH2IAyZcut+>c|%Rc zJ%Xiuvb5H}ueb-p_@|1(Afa&A4O?u3AiHEGxN!#ukr5BqI0=TrpZ+t*k=ka00Z&1` zCk}nG!24kS&rDewX=Q}rXJVCI_b+IJJO?&v-G@V1A7uG@_}|2yrLG+@uPyA)ZrTy% zLgT`8_yHI-6KN>qxqLh+3$#lE`{l7726{G=6UQ7gu20ncX*v)ssW<*diR z<$GhZr-N2(ZeOLE+n0x#T5%Xv-G2f z=NsfX4KHLK^kQa1sXe^sCy#U7QE_?P7~gCVx8tV;&DJa~|KG$pS{0XXeZvrk+GGy9 z`!oVw4vtI?4o?=t=5%fSn1h2~;zOhnX?@oCxjY=gXZ|!i9LCS*b%fZ)qUqt7=I7?& z_|>L|L&v{}6MVBQ!r6%=ViR^Ei3ypBML0i>#4>zCi*U7v16oVW>x;hu*(IiV3OyWR zr0;->Pq;Yb&Ndf^GxIz$b5r0?%*7GgsF*n}{OIG5m*prv4rAu-e}y&UmoaAU-cR{B z@DK%N?rFhQ>QO_JK8};K=J|8EOn@2xAp%?b7uO}dUayn)y1lH|Yxh#ExN$a2`CtF~ zH@?*Im}uU<^qU`jU*~xWp!I8fk}_j8$>KqE5d_KAO(nPAaX2YY<6aWEw921|Z|oqh z%C@G;5;&~7((=Q~y+B#tqWh`%O5|5nxF0irTwir1iZ%3`@vWJ+JJnDpl+^$~|N9?( zU-x;O5?0mBb&=kU&ruiLX7VbQx$t#!lI(c=b8q<4FaFLS|EDh|-?NZwR@3tFs*Io! zfHDZ}7t%fsy^H?NPhH2-4mn~8yPoBfsoNVEv62NWw8+>1r+sD2yoGHr#%~p5FYM?VFCKCVrU z1|aOth))$jAAJX3bzM*e(61|hXDxs}rZO`D`ZLX`&9*bS`ToUD-pbNeB44$L70dshYD*RJ5=@{XbSe6^z!wZz(gOlak;(HaGoJ#ePT~K{zg-;95)%zB`SBD(~KWk|lkYvM! z1w5za5q7$Bi&NbR?Re9HAyuz*gph73tBUA|WHFv9L1!gN5vvk74Vr{TPoJPtv5+c< zPAM0Ii3#ES>z(w?j*`u9wg*H*BPi5EW%kAPUa--4SfSu$-1`K^gY&42|Mg*`wPJn9r?&P3FO9>9?+3T*x_ z7jARL{^n9uJD<Of`8+Cj*j>V*}% z>ImlmU%S*PQ=~cFgI>uCdT2s8qMbhwnvzcQB+h6zs43}mOHwtv$1L`pLpO?L&7wL4 z6SE?AfraHv3NrNqi3-DDAMSvMC0fj~wg|OU#|+ZgNTTr~Vb+PJHIBw6|1Obh9Rs}F`(^2*6hW5T8w%((9v z!~;cwIgvQ073-b;G{v{3f&9b+v+{$~DYbvh9zlUpj4&(jCUuS;0Xm~UU`r}{_r~)| z-QcO<2zNM@cT{KXiyLt3N%#zUtSdqVOUq+|eVly2iQ707+jz)*>F9K-03Y@gnY7E@ zylI@`Xt4hBF^qjyO<2F`4L_W6&uZiwM~ot>8o5tlJKuBzkob6ok+6a-MzZ-fehkXl zrDUYj#RAA7B$%)6Dw!+Rg*2|Xuj&AGH8Gk_){nVDI%8Pbef*Y>Q%!(4vqZv{<>B3Y zSjukC#;C=zXTU=Sq=@``v#MjcnufdO{)XX)!FX>>|(OU0d* z0JFHAwF1ZES{wT&Z-8*v_VO^(+MSfELmSxu$_A<-KgNN~W%d9FwaL9_AEBWom=)@q z_KK26dqpY8&e9#ssXB->#YFxH5am>{<;A^~CJN2oa>6Bp_@fT%|ba+|@{N z61%sN0xKcseT@|GSl(7=o6Hx3+nVpLF@lEZbeAgEjF9^-Z0E>;w?ML`v*Q*-i>~7u!dAe8`J&U1Er<$11tX0j z6^P&uIifU-4Ss0|kHD{zU<)F2E|RD~4F{PE+>kyCAl&(G$iJJ(C&`c0mq_uw(3-Y5 zQ@R(gsHF%99VNNpxv81-(`r#Dh}LQ#V&iU_rdp%`E52wlYndW2Wu>NkLa2bT2#m{} zSdLAq2u`SN=1#P&U99f1*WNe8ARn~@Nn+BRi-bYwUC|K!-q`j;;=BY#0{4?)Odhgo z5zzgT0DUBEMm58XWpzN?X*`qesIg4oVuw zKGjt~#*0PUht&hfMe;-F%}$LEhsQhNR}bnlk`E}D4rbf}x94}WcDF|Z*Jb2drhN2m4PBn-?IXNzP74QFV9P{J(xooPt~okR>^WaCOnWb@}4_fld9 ze0R#fu3t>(fGc39Xss4Ec_nV7Kwx;_yf)qRj=#T=WsuZ9@3XqB^>ccfAxvt>@kTF^1^kkKY8c;H4AQPy8 z+94cj+)kJ`ZznW`+SG9F4$7*TmQthHzf+TPzu&Lq=i57+;82i^i{<-G<#9v`kkXt9 zEW$f8(JN>7=%cHTgDLweMPtw;Z);NOaVfa5-x?5S-2!Z24Z!V&46h_ ziCL(K=rlUpUM{~@(@9=_xqfj-Z75FHElb&sw}J}~F~kW-A(|bdq^unSMjaoVfDpqu zh4@5kh>=OFC0c_O@s(KxLG1W=WtT(CHSxa}yj};qiYaiN0>m;}nL;g`1z9S6wu9m zSMEI6gPLV^8P5(Ku$Iij72FtRr(wlOX!zkI_%x{GFf^z}Y&?yb*o<(de)G5(Ly@p) z*nwh6JrX3~SJoFf3Cu}U7!1bgHbiqDHp6qbHx%+!#^yvLZE3%!nfA*=H$M0!Qk3`c zT;lR?_+;2|qVSgmA>XQCfKy`H-+eS-*kAlcTIZR4MCs z(Ns{@krKzFtl!m8){#&?a3FnmRMtb)O=aE7xUz1dO>QdrHB2wk?A{G>OIJ00fPHhO zmP>vGW&KwA82)XBD^0YH{J$EWW`IXCSXk{)l+8ydPoP^5{D`uNgy4rGom zJ(vF)7A2Q9N``CbxohF(zPJ$g1-;SvSPw8S+Uj=!{^p-DuNh6|#bEg7I^#yqI3_Iz zKGgAC1r59p7i`T7J_FvV`lNEz_3^@N%u^x6Vmky?%Xm(JL_4M)ISjXO)hA0?+)%7_ z9mElSbaA?9GxX&>3-^I~QK=#rpzQ81AED>OIOpZx;rzS(1HAk#e>}*!8PkB>^8>;F zLBH5gtp5SoLAyRgnp_sblfe!M+FUKN)GuR9c?zR~lT(N(sn*0FtSLhtOrnT9!WdzXVN8uIl}TjC;wBNRW-`Gd zHtM!f^J~Tgb)J1Kg`?u7@@JcL3}%o<93Ev6nGu9mU>4E6^gtHTT{A7Bb(OYZc-UOD zSlc4HgR}j<%`fG){j9g~zByV%x708F${PA09g(?yT-MM-PZ&!6`W9D+-wAq8rG1&BF1DQ z<4&JXOE7B)IB9}OFbMHixD+j&8kb^9jY~15#-)Ze1Qt)=lB^*h7AqFH8ZK4TRo2$f zdOEwNH6%!#QEZsBh9)3YVN?yNH8D!Kee7^L<@f~fkkqrRp?b-6HDgG?GBbbJG=?Ui zbqesxNM#DM%ou_aBT`JbU>ewE#t_sP^UdN_(gA1TmywDaLzlWSghEgmLoCOdpXCxT zZWUvwSk)M+Vk?3%R0UtGG=?q_eVrs@=u$U^_P8;$vo?lmMs&=^kQ@?~G1Qm|X_5_N z=!3Qg;~0z~uP-tZc#{;NrZv)NLnL>%8AF%MvPKG6R>R^tHe(1^l=zrvC29u~%{Lqv zYW`9gLpRK7eTFSGA8euCH@z+Nkl8}Sf9W1j08yJl9!FxPl(f?rxU%F=oRh?I@aDV_ zYK7(c7{X#|_e2N_8d*yrD2ijbT6X~Jqdx7D^fh9#V*foHoB z!y;|T6mrF|xO3IT*EqzmAls@KmK;N#^>)KCQ?@S?3ZP*th6S5OF)ZCy1XLe!v)9BjiSo&dNQ%83NDIIu0CCATGyJSbmKDV56P z;*V=+9?+7@3^1rMPae=>b9N#DExEV`<`x2>Wq3Y8H%yEHC72tLa4x9`M@s}I0Gur^ z9q&@0MVIMn>=Oxaf%C77aA^grN-8Dv35BA*-uf*a=VH!Nn+Ycr;hMP~;QI{kpW|GR zdyw}($GM0_5kd2hDbA%Kc-D+_!ONsYNJJHJ6!87>r;#=1hNG{__tk>OBVS}6nSKpl z4Wh@~$OM+f$QL`?L?U{Q48+bz2=#Wn8SlyMK$rkFNt}63Kz<`hN065swDV zW(Y#45;jwjFACyBO$g#dI*0XTMdV8r$tuxPN4|6FyGT7uE937x~hEdLv&@JCGOz)*m`7KPmpH4Ao38?|obF zlagt{;hB2D!CSjo*SCo)Mmxo?Y*Flnr~xD6c7RHK3gP~|yL}qM{Vg^`if0J-w{-@8 zM@^OrRpmz`m%UiMqe0II|KDE3@PD0?E}zRp_`lSViSU2(@k8x^QiR8WF*btTJo&2W zbyP#de48J%g?NvLw0iAuuvmN#rfiv~U6-s8wH_}98O_DZLPj#Q&qHe*z6r@IoxVd2 zxPUBPR`jlORHC}EUCv|lBD3@#G!9eA>|bfb`y_N?2|AJ0zExJ(#l9+Z{f zf6j{{!T-$Kg}n{=30`QN_6bht+zwz0%!xe(%%v&Q^)7SXiRM$thi2HmGm0E`R=peS zAX?04Haa@RiqAXQ_&Xa_t9Koo+wzf(J2qoSjh$oBVgI;e6fDG~GL^Fiw6V?KwYelV z)2&;& zt=&`|cu%}n9(O^NBF4g+StA8*LCWDeMN;!4=@??$S!}$A9WiWcq$=XPvD(N6R>}gG zP53d~OGh+NDk*>Ey-=^>3XWvUT&=?cGr1E-3j?r4Pce;^|3PzMjOMtLiCp#8JPOo3 zPGXWew^MEmpmWt|Q9su}#H3y~qV1*Zh7o@FrR=pM)(xdOjLy>Q*(3EQK)M)XHw#IJ z87Jfl$_C>5ZG-}i(;Zt@EJdIRDH`)f(1|{*F|&v1zN}0I9!=v6(Cj3cAk*@R9|1B2 zFR%@R46&SI8Ut)ewe81!uh+#3%#8((TYu>h4$&Gx$PF+NGj9Cs^+j_x(zMz^D@zS`%n9k%Ol{yxAD~jJZLv+*YR7x0ttr=jW7P zHRnZt_zx*%5q16H6Fi-poX76)1OinM?iRLd+{~;haQWP1iSie*gMs+$>qjlGXY-gd z(93g#+U@e|$;AupvQO?7XPqw$%Aq}SFPFDcXi1y;T3%ma_06m5(Fp$6E1yiS=Q1j) zA=EQPk|kVg(hJp;^TH?ni8A$wUJFtNvEw5ARPAj7= z&k9zd3<0T#Q+_V9fg_UCM|EnW`bX^yU%gR;3#n)Ml@8X|IUBi+c+N&opfVZR>J~=K zLXM4?RFxfu8UcVZk+Oa~U|=*NPz|W#c?;gq%5myOX~4`$UHi)iZa%DS-)~Om35lLL zYzAn)k;!4BhR+^03rum?nm>BR)~@T38XCRs?dg<@2+Vl|uHwEuMF;5GIMmtLCziix z;~EF;RSw#{0&SR)i;0^}ioyBpV!=ew%~p6fS-1Dgf#d@t@8)c;1g7o2V+B2`BaGDs z;DyO`*;Wb?_f7KVWS{sPPnKV?oG;cm_A((vr=PCPW{EEJpoZkd=O<*O5G*LUh$r@{ zU&s&U(RK|G*HRBy`{f1ne7j?f^kLVHG(0IlqF=+F3w1!Y&qROt^%?=?A>Q8L?H?n^1C^^aP&--Udk?& z9RB~=dmm`auDibP+;i`J_wRe}zR{C3qZ!-xUXRfmYgCI?NQ3Rvb5zSnmAnwCSC`6i zsam`iUNdegJBm>oVUHWx$VD!q5etC@2;0Ph7$RCh#EP;QsIoA`1ku<!`~B^G?z!*X8O@B06|+XO^zJ$P?7zSL+rR&N@3rYw((9s(^1)1?4^R$R z0Ycd~QHg1sx=xI$7=|8ANuO@vvPk6BH15l0Wl_@7n__@xz8TQfLtYdI!=-F(jYiNlUwf$SNEcT~D1nPiZWFErQxFRCsJ}d+q8++e~X@{u0xd=8c~hs8|SCok4iy@dZz=*?#qa zi#|qE&;7t@(7xCjkOk5%Lkg(ktO!+kVAXR@X<{RS6gd~zC{QdgQMDkez8As+AJe1Oh}JTHwHSV{6eH&4oU2y1 zy5$C-2g;utc;2X02ij0F*$J?O^c^>G^|;9tj5E4lNK1ZD>6@XH_A6998r%v}`o(&OL(HeCylp+N&Oq7%rD$KP5 zj*1ixHycGZ*?E-B*Nw11P)B`05h$Zm0D0xKIraeY0HOFY2f@G;(ImW!PxoO+AC+nK zPoYShNiSoEj8bt}mP`^aR_E)fa;Z8xb_RP(VSyXruG_dvK|;5%^uiN31h z#mdh2mQ_W*@xupG%DCWzDOJ4$8h(+&xK8&YKrjxbbe?o%SuUIo{!cf4>@Hj%{LgpI zNkBo@A869C_{1pJq$-}3BR@|X5%R-oXBq=4}@A{#ha?BIbPL-86~lu zHOOQeS^+YfTHGRgzb6m*z-AqGh&5hEfUnEBNS_8onecdWHek72Tr)AQW-Dc*dRa0D zv89oI#2HzZcROK~nWJ+9dzlBfrNS3P@gWyFwFfU|+StWJ9;vOU2I-8fNQ=CwCh|yW zvk6D>CNO@P?R=iS)E%njRj5Q8afJ=-;j&hHPA%PDRbLL&OO?gRVvb=ELRbF;G(n9U zpP0PQD*+j*{wR0EiOFA&^!8Hv;S-bZiu4Zam4(Bq(Y?rA(tvEQl(?ip#}HExqgCOZ zT3TWl#C*_8y~qj*0HjXb1u8wZ2~SpIg6EWS8J;bVPbA%H^jS(=x3`34H(5K*QUrRl zyAfuq@W#Tk6yeC0T}5>ElO|dP5mHz;7evJVM1*mPilHxWNJ`N4j&4oU4|f*mxWl3& zbep1$3ff8?1$gMWjp*v%iV~AOB}SB#)<`j#@`R`kcNS3x9bf2%M0mz9^tG>SNN7|VlNrNr@LJ+5jcrAvJ=9f3RDAu2+w zi$#(MMO24jvsNy@kZfj1a^%|cJ#-bMiRz0;qSZgApmY+Mys7(4DY2eG%di=Z0JggN zFCY%eUP|!)Z=rV4quPeW1WD)iObd)s<7v5pg)iCiDHeDFt1WJ<`QD2DK1GPn*)s0=?f?hhU<1f&>J1R>%P#Z<-1EY5Nb_VelnV= z#uC?ws_~<#k0r=Wz9f~fMzy?^U1Li5-CZXYIZ0KVO2?$Z6byLA62Vck_R<)yy6RKwDm^m#z^+f|~3)I*57OR4%DjUgG_?1sn% z7I&AHXT7^rE$Qx3j9NNKhZFs@uO57TS|TF9PsPR-ZbXl$z?A$#mdRM}KmvEDyiG7e0j3_z?Ps3;7UyQSd+k}^;MhtZ0+^phcl;!EEcExjEZeTMb{?p?_B*vx2e6!K%?|1J{?_aj zO6PEuqk4a+HG8FgA8yTFrSwNyvupK!2%=ZRc|SpQq)qKX;Nb}&d%yYu^gaL+{(^q> zd8Y_4DMceDh_?K!rWW(dvt{-mtOU$E$@MIZB6$${GCs(IK%1152VrUtLR_*5krE(5 zOpx3CrAK<4kqW=j<5fJ;D?NT4kI%*9Ts3~SoV{AVJF~-l0j2OH@OpUu8sQka+HCEn zVs2E!NDI^ekz@YjQ7ap?YLb!gTA}qQDm1IbI&ua|dpNdi}xBJY=i-Z4hy*`hixy3vNrn zm(#7hV|>et*~KN@Y)!XbSA2i#4aI-WBP{mq;|+BO;p6T2A@UO%K}v(*r6|A-2k+J(;fCczE{`pH@Ykg^0%vJF9q(|N1boCt*~XP% z(Ydzxl+3R=3to)HcZm_5>i@w|qzr$j1`GPZ>x!Rhy`gp2G|CTD=!w>h?AoA2#0UD- z>soicp%|cijF6w0PJv9efQbEBm#k&kXk7+rj*I21px7Z3#y-hj%agcxLmboW4F~<+ zTDQ|KTjC`i!E79Q-Ug%IP`#6cQqzraF37dgndvZC5R0_Id;mrXvj*W38S)XpZqU71 zL1mLte0A(LOf+t}` za1(WvQ)qdb4ya3?C45tEl4Mv5WSvozSUvLDhsiAA%h2kH>NO{75EDh(V7`_nYAsLHKoccJa`!|X z1rlGOiNe;SVJ%G*hXKFXM7dXKk_slt3h87*$LJce!$htf(IUXOJrk0!E+!Kuss5-* z0-xlEEvhT?BvIAtip$^78b?Wc7x#1O48;!3hVsa~t~J&qt!a|5?|71|06{Q?P>jYF zdi#MkH57Yo3HIX* zd+y27Us=3xrKW9M8F-C$~!+2iA_g92gA zu{P+c7>$^94^q)nTtudZ3!b-l5-wh6*+@@cFRHknu(BuH%0w|0;PTFl7zkxKO6ED- z1@8#qXm*VVSuk6E0HfTs#gy+6axhT10V%V27jmn}ijgVST6I*!YD7C@VZU#o&c$=Y z0+E68)Ki>j1;Ku_&Oz5CDl$Q~;-~EqAD{y&)ka-+TlcKt+Z22;Ju5)?O8#9 zLx%MhJi0wwYag%k6oyZc!#qU)1*&yTP69tmG=pCu6Cu^8#tbCb3@{ul#Aemda`S66 z575UP<$o@KGvDM?2??_S^Rf-hH3Zdh8Flbw9~pbHU!xyaKlFjqIuHKCEhCbrGfj>` zn<|`+L~T7;3G{Oy&;-2CYu;?u{9~$lq?!?wS53)Tr@f_VmrJVd<)>8FfVwn$=QqXf zaYiL>SfCH}4730fz!tT_;!saxhLfh6K5yLgU~mCqI9dGhdPW;8V}U#f>c3X(v}Y~a ze55CV`2K{2%5Y?n!DKIGfP^rcsX^JGdzEruy~yo&`<}e*_qWKs&!Q7^P<)OaoEBl| z$=I<|VB`K}2c@OjRM$h))!QLm0W8ryLRWaC0t(+)d{`CGq5DCP*M9Qm>18()oAI9a z$jli$ZD)4%6pB2WUc=YTo2R4nHK^{@B<{Nz9vDSJ8|~Rp;DJCwWebP~X=F~!@7)kx zh`)!pG2Ic(B_T1Vo=@F{p;jD4A#zLiUaO7w8YLQCulhtyn0waodOFiLk?NjedB_vn zo})C)KcGR5sBx1TapAawNN4Wi9FFP0#-4W>A^X@X-IKI3-A94@Rr;D62|yCmc-0Vk z-p7`YIWjo!*0Cf#X}8Dtk@aMv$R3E&sak2<5ZsA=kd6bh=rv*rHpBsX)tNewK&**` zMPz}PFbNo#lC@>S>{M|@bpqt2R|js08(No&ZUao0K$x&d5Ky0PgznbEZZg>?q&cJL zd&p2$C&yB*_oYJ4Hwv9qD-5j1YEU zeaZ%;M^I?RmWeej@8OZ}@8E~7qrBQBB3>&!_D&H}Xe5vSg$OR)wWzl)82j+)1fO-m z*k^Or3BcF~3)Kn0xYIBvS?}#MVw0@&`UuZlgif~a5;cIPuA5fGZt0FDbF$AtaI2sf zGKZD~L$y|I5Uj_VNV+rxC+a+HB+YfgnL}-s^IHPQ00Crhb^$~RMTj0uEYA`?YB&wD z@BvlbPzd}Ea@|1LEP!yy3j&A_ZX|SKAhc`)c{m|WE`*TQY@FDbQfG)Ej`Iv@1n=W| z3z-5^OEw18Lvm|NA{hv)Ri8>NB$7HKsRxhOg)Ud*Zrtw**AjXu(Efh%&L6Vl}-BI2ClOVz>cBxK4*n&col3but z@`%on3y=p2v5f)CLv(HsbVW85hRJ`Ba#({k~>h?gb11f29_Bo&ke?@DFF?}AR6)!!oa}H zbC8*5V8K|-QQ=Aa$4kHAydsayX=7Yv0W+F&t0)Fs8Mwa;CDAs%oRc(Jw z&|s$6`i= znSS-tw4JH4o|Y8CcBaYtauhnyHr<6b&#ga@y3qboST%9>YtfJj&E^HFnrU!EinnCt zUUTqxp&pB0Su4@m03j1oOe7e(0_kxc;JKD4w~7Eq@*H4+=qp4>3gL!Hk`zKo7$}6Y zG~)Z9&R2$q5UPbD*p{!a}n*5@I<7Cb|DQ+ zD{pq>CJ1*}t8(tBg~(&AbWze&yEla-PJoY?DnBnK$1lX+e$3CdMvTPvk__3T`-#Iz z#HY{J_0)~oW?LpU4SQeK^JGd=C_cwJVv`yHg|#`RiE#tK5WHOZLhZ9ULFEIg%3RD< zy@jZLL3$MsJ;Mz!UVR6WPdgL6pF9Q8TNBZnc&-;;{*As!b<&qy^D@)~g_x5is5yLl zFVuo5=MpbIi@IuJc?Ri64V9KqyjH~wd08|X{8UGZP zuHWQUc#XaYuc0CyVIjF9F$+}`%?OLWp)CGmwG`ZgZU)cE9ic)gY*F407PU;PR@ibW z3^ks|DoriypQfKl=*=>r26qL)Np1j#^m}=fTD+*8pG1y`DiYqPiquPl`rOM55AFI= zr(Kgp3SYG}Ft-~?4V-r2f31k@M`DY*keM_V=>cZwX;@AbQ1W4oa-`bZX1K2<9tZL1!H9@ol98&$oHlIR}I zSwxbeTpOEBNPTosR>Kj*LV*@wvU=#phr!9)DM7$ReNK%&w5=&(t(7$9l^dEOU_}HI z6@)t4wG#CKJzJ*a7!lB7$^J2R~A4y7!&4|sgbsGj* z%#9q#V9kjC=us`T3GQDLQVMcsEv4MDkWy}aIi(bc`zk5LTHVD;DbS}?QVMFwB`M`a zq!jOz%=?`#NhxAx7cQkN0dY-o=tW2=e<&%Xdr3<93n-P1(4IGO(y-)w(dSjJL=d#uX{$d^<4fg<++aEDW;s;)0-y zG7)gp^w2he&Bgq;aE9*&Spj^*PSr=xYsAs%zo(6i5WCuNGTB$-i7YA@Dzu2AYs<*X<;A;@cVl{4SehD$ zD-^-=>ZfQzuRnWz!z zOIS6d4m(>Ob+WT1m^AEcLzzlsW?HjhRmL{?d29sB5KK0=sEcb;1wAk*+1yeMRM3-S z&p1~tBwD(H-OcO6cDLwSn-&hLo6txvBrynCHDNEK`KECi!dsX>VxL-mqcX^CW6K ziV!5MV<;fvdmjpd59^ne6Y(8>3MP=Ze9lq zOKx5$A}1yf^!O=4R3Q=O#ln>|Il?O+7!qY1cs!gQa9$SDwkc8e*SdFjkZqpa4u9=- zm?wv|zn0_lN(iAhxB+Sh9mRUU!KC&_H-qtrPWZkR(Mez}4Q(jIGsP7t`iPC(5o;6y zNZp$W0-YX=q%cGqivR{As7=YknAvc<(!u0*-40W7^jH8q^R*EAa%^GGJho63zVYQ$ z6>8h#vZ`R)(A65{ z?1-KDMyLoqm2RpCxKo!@1U}E^?Nus*}G`}&~ukL0Qf!a)DIPrt~}G%LAY>v&H+=6&kGv+h@}$H{C*c0HG+dWf;Net^$P zK8;)^`jj;mol6^Iyi(b55-T>j)>t?f7(SJp#B%QH2D%!!O@Tf@DuLd`XIHedK!4>z zpuf@r{RRR(=j@SH(ZINOH>I_VDHV$3zwGiT5WCMzI5JR8KbkX*O1a2X?p?rGy9up@-ztM7ou=dLYJ zEN8Lon%L3wijd|TlNyMTB>9D;eL0=1r$Lf75Nt}?gfeV4ByEJ^cnwJ#rKoEencwVB zc2rV#Sw;qYSsWjV33i*}c-NwtF95%O{FMv{+HCmZrHd!@|S2lhkPqH!Kg5{;u8Z+24$vS^$OLzq2z4Y8OWAQpRR zTP%(qiF|Fxx?K{B#~26J<{OB`QP0Ay9rc*hP>)FuBje~E)3;%W#nD^KEM1Gb4%v%N z+ck_r=TV|iMqUdwk=K$-8eoXUNg(!TWY(1TaVVH&)~=*bWK*ar(qXsZ7VcW^>^9_1 zu+s8v*YYh}hLU^>X1OdwrDlsU<)n_{NjhRnJte%NiutefVC4ViUQ9E@dM zi>RBTy3o}|g4atevw|_3UP4)pbcyWd-V8aCjdF5xcSDXu?F-~sh`AY^7h-P4_ax>r z{4C}Ypa**CXe!f?g%GOOwVOdLM>M1@MUH4lA$=i&wk>XMH^og)DxGSIn`u%)++-X{ z+}sRtGX|!Cd`*a(AVLnbzHT9Ie*McSZq`=hWpUG1_$CI}be<~WI)FVsPP z$z-1y_PKxM3q20_q~2UxRL#9#zAMRlzxeV-_?F`bi3U05^K_NpfJb61FUUFTcD2lg&2T@&6`wQf(88WXO^WWw6txKVgEck7{g6_~b7WPxA2GUg)!}~Z zxT~InF!m+~6&v(i(t&ZLFS_`7nLuh$(-D2-hx=3D)44!hk7NIyYaz@M#R7FCa_kyK zx14FsUQ6uX5e+X4$hqaHiKdh7F1O%FHX2CM3aw#$cKqM77@<{fH2>V=CROH=Q|9 zk|tGI6jsjY6{IAh`rKy`nH=pXd4ocWn9LYe@#`rjQ{@*incPKG{Gf&rrSd#xOht&+3Q)P18MsUangvBfa4ZqNXiTQYlVT+? z&Z^^ivb;0V@vaXW*B;OWCeb$bFKFt3jwG8?Y$>iqn{kLXBqZtC#TG%ClAEuIo>G); zM7~9kzfae)F9!f4vo->A8C7ikbHL~v&XnZl_D${S)mpFLa?n>#pEIm8N!Feq~jS6gX>JenG%PDXoS%z@9EO6QyDg7-UP?ZkiI4^+{$#RvziT&i?1Bbetby?DBS~-hk zLge#aXSxGuUy36gl}JwXaZI%|aKg`I*wiCx*^)nfn*2>R{bYaoxHfXy^S10y5AQ2B zBODOfcddd?F@qaK+uAQ7AM{y#*DJm0wq)-0raL)%)3aATdDHFeNUcV1o@#~bO`i1V z#`x z@j}0N#pPWGPwg2ucD6LG6hr+|e5XN-Wd3US*lj#q_|1Xc0+i**9)W>EN~7ULNNMyv zM+VrFPv0Fx1Zm<3KXy}0H}J=f6)>Bf@TpmB3#13D~>Jh#_@Ba5*O;QXJs~A>V!>E2TpjRs2t>pL!DCthRx&S{=z%d zG2CWx(_Xz0H@(+`mGZKjTvpp6m&HxnA}?0lM6O&VZouaB$w1PA)6zC{4Z`<a7Ln)hP)keeR4M~chT@y9%2Z=s&-gzZ>PR0>CvA-uS}&Q?ZV>{CQceKdK6 zIs!E_74DHNL2HzbicWEqOn8x^&N!|sc4j#0tY}1?VSUbZUKw?&n5q4md~;ka8b0!? zr(97AFW4ao_pU*pGu`QEcJTrcMUauQ#TDxscgX={I#Zbg$m%iUIdEYHs;ss%tq%fl z1DE5B3gl#!W6X>GEsa^w+aptq#Rb&|pU7s%WOyRgD?zT|_bTjdDHPw9{9c8L5}=9* zb}%Q0_Gnv1rPSg$kV2dWFJ9rpyb6W+ z%;kp)#f$b8yA**weu?-ix=dR>u0$1Z)LNnAh>!6DKv?5(9?4aS)W!Ip9%6c6L1-$KGpfy!ALb!a)AEyP@9-@I4OT%U4 zv-o@waPmXBBfhz|P5?D@r*OP$8%~}Q5Kq3WJZGR}p0gD82tg2t@&P)^%Eayzv9Sj< zLjutG{=%y~m&HyBK8#kpFGrQ+;6tp61`ujSwxTUx>|7uNvYRv9U5P`FsG0ee`*HJIVx8a+v~iE~^!YYp0a>8!O& zXRTS_7-LYv^zRe%E}gYz$-yCEIbuff7i@G}#Pvv(ZD;;+>8!N{tNaUi)*5qv=@gSc ze@_a+VHQ$CXRs$_@Nx<%wHmst+FA|0SRn;zWQC9-5yhgUE-ky0$gnX7G;^t!j>+|X zWVPC`#kG?peGy4K_I+$s*DHYlxH{pDO1Qt)Rko~AY)QUN`)s$koh4KZt}{DQzFiv0 zExOw5NcpZC%fmdOe{9DEXA|loGK{}mw)SN@6&>`(?aL|Ao=9}RTpGhxE!^`?DApDV zM-u)om%gw?rurS-c#BN+J3{Ifnd*1&VG9L1-W91!2e@QSzbl{;!$9As9sQp&ORAXo0GCQtC^t`-m*CHN9Tz`#? zUsr!pUD1VrpDuNQ;3L-JH4|JW=oDDg+`D+3rfX$=QzJ)v=Q|m>KbEvpT z`dl{;)+pjz5ZS`tUtm#x`sy3XQ#3NY_Qv*^7Qe5$fv{bEUwK3MoPLk`K-K>A6*mwo z%=@7m*b=}mw=iDk0MCB)$JhlP=0eSA_KUnd|0DVBcX@lJe)|$!y#C(RDGAt5_(~98)kuvGG$#Cv8)qVO30=`h9;Pv;sE)gVl_m^GJPhP- zA~^Ly%>egi&OK%A6V+!POm>Q#J{O`JP3+X5~3Z|1409(M|pTBkhb6ecyNXgZ(e@>pMj zx_drB(G~Lvm}5RW=RH=<%x$CTD;T1#As$U%#nUVOG+pOa_kNP1`#znhdsP`5?}s!- ze)reIWPlBv7sh6s_l2T0-h1hu15@ezgH1feJ6>|Bsk)&VoEJBFHzXp&4S}3beq4*) z`1!!c^!SueRnYK#rIX()A)`Y7b7FVQRW#r|42uSycdc#!(sn;9ipoy4Pee;RKcMGR zk1b!k90wtHg3}Ss<07)c*LCuW*-pd6V5(v2dv}V1kOzi& zR9*5t+8W^;e;;O2ZW4NZ!JXqTQg*(#Y$|iI>fZ`bY@?n0a5O5Xq}rSgPE^0~5g^!~ z+~2+`@q;m_JEwEhnrYY%89@I2((2C(G zYb~&2e>ExFGXtC3n?SA0G>Zbx{4)0=Kz7*zWb9~K1u_H7EpATB$@g-d2u(o*xGar} zO7Q)`p6HVfG33#Jf~5e;mOr=z*DljTn}d}aoye$%$Yq0XDb4|QJlP0X>5FQ>Nvnd z&s)sl*-A|Ilu*Em!x>uWQ-_VHqVFc^paTq`;&c(VmvUQ|SNnzz!0M#i(v;6#qhnPn zOn*mddAMgBs8mm4@vc)vlCRUqrISsNN|m(yu;YE{m=TBm@VG zjcAyU%2&hp5L5Sj4>1)MU47t(P8Zc5QP8sGgO({CX;Sh&= zVhEC1JhVd#Wv-Ee`bHiE$(%p`-BgcuUztAhpKUw%q4DU^@-hg)R5#gXO;6g_GRp?@8`s1#*m;R^Rf! zibceFzp3+G^U)`{cdq}f2X&A_uT{2to$eZ}51EB^);}@%PZd+4rjRnzkT zM1Zif03mV!pBowi5xFjqGz6{`sea?bjFT&cBzkIg#1+n~PHPCw>S{7WA7ORv>X+`P z>aEHDY|&Mt)}52aEN*^GIs#7%%yKymu=>X;3Zb2FHDqAb;@ju+O&T)7R{D$tt$ck2 z_KznYEF}RcC4?tCJUU{YAv~$=nQk?P0@bY}y-itz9Z3frNe3on^@q2ppY_`< zf}M|VYghkDh}ciqq6pUsn8gCqD!m)qt2AMUM);+mF9}MVbM5r`Y#S7=DUj@QNh5t?!iYIQLn7rE+%EY3hjrikx z?swOC9sLKHmO*UPFlSaBM3Dzh3s7Q!C`MB5q$V>eS5Qwk+OdRK&~$>@lWQin2sB;j zFlFd4CfDVHoWmsU3mVQD0b0SL@sM#$l#~Am`~ zoWABCFeLMPA;_B6nF|1HD{nBG8rZJRsTf0&lCmaXo~6Wfw8W5`CBU^colteQBFFa@ z0U_6{q@1OdJN6WjauTa~EtHheN7l==1*1jq0w;jy9!4wDCVKm#@Jgv%BogaQDUtu#bB+nA#kjv+nmZU zxL~c$gOfpp1tZ9Et|M7i+QDIW*HK$m_`cw<*q4^MtKL|Wl11hmer7g1_$0w~d)e%o zh>d80*lh4Joja=ddFA=Px8)!e0w$kC#*jw+k(sSNd_XCik#@C}yNgaIrM=o6r75^ch$ zgJ9}OG&`!_%#QpP?5O@=c2vKa9r>!rau+*7A`&~|xS>Sr44d|X){y}l9is&9vMnWc zMAZT{&@sO@JK`y4&;eK$l@{#CD0q)VFg4f~g>*9+rj!;Bw{GqrkXW)mMLfWNW@}9!H)Xk2x3Pf@vfkre^YbM|HR^2AXDGSv|vYT z!H!JSxbR#37eO;Sa-ziFIx(>$e-Jz3A&GrqGgC@~9qktT5<6;$eG7K9B=(t&B(cxH zZZem=8kWSqT_u*;kxKxyj@eO`6n7O_W=Adp+~YR%vlLloM=G*lN5m`0W;Xh}W!VaL zgc1@_mAkl+(an;-p3a4(AC$2HTO|^cFvW^~TEO3VTQRhf$%+Ky6 zl;)>iA+@+*gljAYgDx$GjtES1eq>0C#elewk$qQ*<@r&G1>^#?M?e1+1l*$nTzfU( zepKJYrL58Xg#g{Dh2beM&c>s57>_i&(pedA?aju(H2JdZQpT$pcpzDRryS3XL)&Pm zQ`!E)x)u|(SsTCFd#yh|-Y(k9L-MA0Et(SLO^*PS-PGGqp1X+B*HMvov@Kjc{@s&0zXqOf%pkba=#<;X3F-x zB`Zfnh(9}Kn8r@mG@Y-B`-+o>Fw3qxu#*Jo38DuC*Vv|9Jx8sW{zM+Dhklah-{p@j ze`UZi+^#?no}oI}+$@IawUX9F8U!c66(=-3WERPPGe3{mZAwiVDyL`hK$wxwE#R@w z)1CZYuR&@;CeO&}(GlhT2YP~zP#FhZ#-v_v6PCa(k9E5W;`C43W#|2+j8dA1dYZrv z51#Z1-Xa7sH8}5JGB=U{>DL!H2Uz7XS^7I|!FH=_xDcr*TM&#=9{o}lTXLS5`4mXf zh})tNU$?%wb6h?y<-i_wQ*qO9@*&z)Etpr)=Tzyhff_(>#6U1Urt@VQOwFV@8?W>eZf)ibZggZ%=x1cX(oQnQb19 zpS+>G&QAyPEZ?Bk5OX}mJ15RI#nWV_j2HwTO9tclr=7C<{!TVH)^nSeAae_pwe*A( zbKT78xwTLCrOxe)xdnPVCuY4o#g-xld6u;-iLhBc=a}U7Gv~e+ggNGQa^HM7?Z53k zd(ePy(>c-+XwaXI-o~MLTu0&8!GDf7W%R8FfsK^+8a3z^HavA}7lvTeLNy5-G{U-l z+Xp$oF_z!hrk%o9@`<1REj_%oVqm<<+hDP9oufEMVxJF{nKc)UzONr z*#~9>10v2zmXEwB{bD2k#oq_NMX4{kDZv63{f8Q^;Np9s9v>3M&LZZ9KpLPA)*@O zH-{Oy-RkH+5RiN3PB`$ATp|SP)63;?WYX6KIB;rRXsJLIH5VLF03^TubSB>hEr2B+ zpEJLT=CRp9!a2lcjt|Y%nuWbt;5VP*APi>IcJuhGBg!diq`nclqh&Y~sfHW3Y5O6O zZ0X$+^yhs7Wq1q-9X~nW!X*4(YS7d{pYEg=rN@rlK->LNCn%!`QX);sbe}Tql=qtb zlwv#bE4nwz*S_V(_MMd8f1>*Acb{gvrrM^#39d|v?YAe>R?F(J%xwfoG)kBE-dG+I z*)8P+;ctx%rF?ZH)aVjTm-XV3tpG9;trH3y%(oQty-(FWph&2Dc_QIwxjnf}wf1MPp3)~IbV)p;>8r*3xsfa}d$n91n!j4JPo^=ULpCO| zm|J`TlB6lTWcj$S`HBG0QN*Ds?SUk7LXx^nJ)=4oN+B zVm_I?(3%dBYhai|)y|*Ju5Cx@f zrK5PKD*Yvv;kCtKwy|naYbaBKY^bxu%*2=2o(*B8rr9PKMcRRRAHIK7ceKa8-}aQ7 zyx2qA*Ue>{?8oYA->}to)ul3d^KB`ae=fZkhLW2!b3i z!pCkpD*G-c)3!P2yeqlw9b(`XK-;={AaGBfpj@H%1>K@TJLTSoc@s+9|$6 zfju_QtSM|orQ3JiSbU>KxTVbOA^_B=@|e6OZG zCPe2TuAixit5rf#EeR#3AZR=?i?(Jbfg+LCAfF6XJVES)zcehS(i}PbPMpLeZ?Hm0k5mALb&H3!PjB04 zgb#Ul#@6I6i4W6x3I?_ez;=+yl&8!$_wy(>0K7oqrXdNyFxnvqXr;*R-2v0)Z2??O z9v4nCaXaOkwGm8H$UvY}uP@%nGlj%{s;56F!h;0y*IYkbUcDVr7)eT!3scP3K=r&?a2S{}geTI?#X8Y4P^R{A z5iXxfU>j!=nH`~O;jvDDciC_n37Q4kI}CxLl4Fn?es-W@sbnpJ4T3-o08M*8O@i=r z%2sHN(S3k=gMh3b{v>4oFQKfma?4lDt88LfAd^@m%5mKsbCl#Cu?q6& z3FhkcC%&t(lB_>)GNidE!~|(Kz}X7X5wKD++P5|G%V)=gwDC60bt~jN0%OIAuh(Lc zhdRZ|g4+;y$1w?}as0Z}28u-jQzDB4i|cxR;5H~e^j#>#CV|^2g4QDCfT!z--<`Y_ z1KnNf%_P$i*yMh2Ah}Ai>gr0{(`g%Zl^Iq&wS*bE4OFERRS`bhH%lgjG1(yuR-v>P zilAn~B080q>l>q-JOI_NiLeaJoS`i$x88z)O+)xqmN}uAIK_ZIde*4voxRY2cMbr1 zsR8f39Pr*-6|?@dl%QT6@ZL)S?}8GNqp)E*I9XkA+&fmh(L#frj)4azj17X`Scwv2 zW<5ZLzM$xM0cs9!qOowgju%c>ZkQk)eww#En1@jyIbCHN`3Dbr3=V1%ikd&1Po46$ zB0}w{5?kHmZ#rQ9{Oeljv>c0g4Mc7fpJAjhwNVs%N2_Pbf)R67szoL{GE5}TQev{F zgf5`-$zD1nCS<6uG=!fPga;8e_7#CJAzV=R$pdV}xR43^_Xo(JvAFeT1eh*iOp3l1 z9Uk&f4CH$E#5%G&1OSC+V@DQ&M55|vpYc$W-yvBp0r4r-Ktjr5->Zah)`@Q`m+!Tf z-)oHWow&xRWknh(mg^I24kJWVRRYLLtK zgaPU$$4>c1=^3iSg|YuxtGlSe3K17<7lp&L6M%`Rlf8X0FhyLK!w^!3RHp?> zsh`YxQa@R8(qZDV!1f+FPG?w6*N?RCRITg6uyvhDR->3B-`6Ejq&Zn9cX_*du`{** z&5H_io4`^?HZ8E@I!us$$QGTu(h3>WN@?zrkYL2F5N@H#>2q_!311h&P`f6OyUIZRfbT8mF&0I`vaNSFERF^0;>#$M7pX zc7FG?OPfXnt6s&?J&jU1Ox60n&mq7^Yv7WKyvxFZ4{ z2+TW@1J?|{o(_4KzY1@3wlfpnayK)wW;?Tv0puKq)CUK++#~3a>Fvsd-fy9ME%c{G zKG~V?rgX#3>=vaxQEXvnUK7Q2C}E>?d1taSulb0)wZ*ir2{p-IPkS`IwwQMHQg&vw zUfTm4*YkE#!p?jrPqs6YSu-qyfF1#xRuuyj32|hkECMBFW-9Ho#3#fTX#iN73tk{o zV6>hn)}KzWKD1N#5TTui=C-AgeLR~|U8vH<^zbL=GhV~yEU}La+2(BE*yhZWcP)D= z$X**g)YF9J>M0WxMh}na9#X>SApqFuq5Lv>-0LxVxHxic42U^lh|dJm-lWiFMZt_2wKi6-Fz~6rdfu>tVS`6D4@(a96Ne^R#Oa zn62tTu+WzV(Xd`IjJp@sD?=n%ue8Dg&F@KQ4)WP5vho9zI+q?v`?2*(b%=)fBdXWx zc|7LvaT+2E+I47qGbIt2`030#Hx;wPEM!FuY(RlFq%CWYt{n;+pu=nvBGpN2T@eHh z$Gi%oy@=EK|5G$=98#*Md_d_{ajiRo{8e%^@Z~67>(Z_wBGUn-t=Ujtrm~o`hZm((|9CwtN6pjN?ZmySPeEI zr&he%(%0Ipq78QEqJPH7>$37eW9p;3mn)U)({h#977w>(R}ig%(6GCx49#vfiu&kv ziaHtS?UawUW*5$R>Tb9>*49&}U<`BM*aHBP2cSF>NgRKp-u&8&kc3Kr%vF__$K z1>AfIW;lwI{O{$qQlEqkJ48|<_~TS-_UfBjv)5F`Xx7%B17tvc%5#+6LTT24;g&Ik zV?ns<;5cZY%Lxv7;&CvQ(?gzoip>sAaX3voie1?nbHGhP0O<+xIZ+jpVU$9F@EPiI z-yC9E18H2{oU5?3QiGLM(CL>|A~KDMZu`zM3(n$PAA>>4xn( z$;&h6WS{xX%1{&jf%Qg zVFiYIk40eU?0}H#HFvm&k#!j@IAnhrEjFg}Jtz?f3SX&yPy-xmfWCG>N#CcWzLIpn ze?6luA;+~vp_N@=ac5$=tQ8t|1N$g9DaKa_YLMVLL(t8QqVE>=ePuc1 zQkifFaTNQJ@;-gNOnkhnqo?AlDnM@K@1rDzDyAUt;`_P!@~!lbK0Qn*$?ZH!5D-e8 zv$j}kivMsddAhM&^6f1meivMJtP|qZq3^n}{Sfn-IVPP@#Nq&i_yve!nqvWPVS6UKS6~{tl-O29`q)m<9KO zY7gr1Ug$bxsTIwP;RfybNV>65s7lCR*J50z7E`_ZW;CW6E~_q=`?5Q|GGIDX>A^8?7X`2l1sG~voI9k8|; zx-WikbnCQ|KmcXH|@s4@qqo9o#(a+&n!P2k6HhV6OhKP8m$-mf5S-rx;$M74A3yM9GnH zXCqVtMD-0jdr{2rPh2-gz~CDa_(pPksMi~0!g+&EOP=F+SonD(A_G6i=q*2Z>?whm zBZ*7yV$!B_?j;eR_o?lIAC~)+A0TmvAHcq!9|M|D69IakdNUEA_o?^&={^Bk{YkK9 zDJzdWQXM`5>y3r4YF`X>UmVi00CijLQNc+Vlsl92su^4~f*)X+;15TVJk_Gxn8Z6O&PzPha=0(eyOs;JC;KoKh`- zFtu?^+Mrrf|L#?9=%JD(G7dZ=*zT!0RH@kfUw+>gdpGJa$rko_mA8-XY`q^P8ebGXPF9cX6SBk) zN2A(g$5)8QiRxc!FmY~?^F+gSVf%0N!TZL>)R@c4f^#$jeRB{pyImX~Bxgx6J-Tts%$=CRWBBkr`ulU0l z@fi*2k@$l54dHdvT;0K+Ujwc`AGeSBb`g?+pI-JQz+!c}AT^=&ao;LFgMf@>C zwhhXb>UlJo)-id>PHw-wiFbI@7k2VKi(Zw1YD9!DQ0Dgq*EfWSjL$ zS#;v54kutDUi6ZEAZj0y<84_cpggd;v>5&4-BH014&*C8h58MDgpHnz%)Y(m?dMqq2SaM z)5wg-SkF(>SYL{)g|QxP2Zgy?=6ahR`Ma>+*#LRZjpe7jH2~7d)UEj5J|hd_;~e#p zXta%lGThn1nQZo3&^T%myV?q){g&peVek|iP_Fjlwt4F8R6d`sS&PmyC4pl%gKc(TJs>9R_COsfjos9C3co+rx-ls-&#}#PFU{u03;cR#W(Jz zS*-xA*)1&`plm^Flt&8yWIvAaDiwS6EA;CK7rwqCe}e0kTFyje>^O>mZrYfCER6ZA zp>_67kUcOk+45_5icR@P)vNu}*h-^b)uw#)QSdGT3of5*OX*$L(#+L*7<;O5YOW?U zJ}_p5bK%^+geZypq?Y-)xC(US)j7QpxGTaNp%xGi0H{xbIb4kXkN1{WvGK!4551s> z^uxvE<6;RQPBHnv#RVW-Ek7wz5RPU0KXUf|IbV?|CzoXguU{pdZdIj160=|Jg}f?A zc>yGDYx$;y!?RP)G*To3u}7hiA_=H1M?+moivdU4B1N9D)2;a-+4!}@LQ*;O$*R=p z&uz&L-V{~WfW$+U=qCqqlB&S=PnZjC!`bEfdB^MurM`29_YdNPeT$!aP$NmaclIj1 z?aZ#CB4`6%3tkV;56%pjqcip?%?=@#oM~D!PPNNlK%NA4Og<06>Io%4o zQAYsfSy%nYV#Hb`c$(p~Zu*~2ojUcHtO&wiukK=UZF3^xStm?S=Il9=%uh|*G7a|1 zHV4-9`k*XFP`|FY{0;cLTh&M>IA59hkuuNlV`(jO%JY7iBz%fpPu!|Cg|{j8mNbU} zsi^+)Y=#~P#KTQ?WO+MI4`z{o?JsU(<;L5E_8(4Hv&y4JXcM{#X1XKz5UykU5F z`5PE)-|~dY-J1H|+~<}iVPL4Qbvx~{RR~fJp%GeyXA49?NSn3zKTFjS`2>e07-@we zTt56{>gFk%x=Wd#9U@oC)QxPmGegi+=A&4v<_H?^;uWR(on)IPBPCxKcJD$SOHyX@ z!Mv4>(k17Qb;Bxwhf|nTfP*%5I;g;=PRcB<d?-c)fibQYuG)#>JzY z0gg3KP(d2VP+1s&1pr}$>KHH}FX=)+#_$9P473UYy?XfMBAP&u%k>BpY#9?paF|7P z`U6P0U0E#I1JO625a_C(Rq}{rjZL4<2@z#lz)-ESkeemW4RP{%>=|&a*}4Xt%%zHl z7@B%<+ZZbyP{-?O9W`v^v)$>C=uK=)QgjVZ=g8>c0$dO|KBgJYk8p8;>m$Mh&DKlwg zF{#pkIlH|0mXvwK=x)ZerRHSJ3a)8jDB>U5?@l_#Q(%`8HXV{6rM;Hcx;M3y)-J5j~!^br$3rGRUmOy5TS* zttK_V3i6_BkrSe4Lx)}KibgGX2fp^#=6C9p7dthtv*AmOTArDn@zDsVum{}(KdHt5MQ~v?ao?jZEbDQHk-_&ZCL5q+oqv&wcV1~kj`j|Ns;74#{`!*&tD-a z$j7I79rgmndkf}_WkV`MIz&?%1CI{FNlB_5IgXgd)%;Edeew4v>Rp89b%PbWF5sfS z3gk~*k0)_G1w{{p0NPl=6I>4sa|Z(``t*Y9p#b_ovA424LA7SFq%U^&b2i89-bg-IRM9O@I6Jsitjy$A480DGh#82joVHQflGkO|n5eF5X5dO5u#*X>wM!OjuwAk)W0$Pzv%$342W$BB!3SQ`2V&3`eQ3-1O31Dj zr+{Y`!AR|Z)$qkzbyqp6VCJpcUgl#tmlEVz$rDmlXBWG&^%sQ&O&X0P()r@2~&qJsZdK#^GI)lk%YjOzxPjCvdq2ttUnOfQTY zU}o@!cmO6@_lgcLv~Kay z)S{Xpe>hr+HBnPSeG-cwM|i+N$+N4F^zZ{Fh??mZggdX1JmDf68jN<|DpHOG%P-jN z^XwmM&DRkOphVFNA157S67MHfow4>%PT6md^c#LYz5p21=*TAKdP?#w|El(CSiq{8fl1RJ}}d02vpYWt}mC4%?Fjg?FjIo$-91{C9*f=rUCHdsm`8 z7K$)wS{ENk_+fbO%xWW0J5nOj;0=>m!29SD8}$}LTQTbd@ouEBco&CLG_*SrHL&zs zNhlbF%meR`%pGzkaQF-bULbuh{EkZ{c4 zzTy%N)c0kRaF*NtT9o>IlBsMi3`XtsnJnH6zsCvfiIZ~)$%=L44EH+(*tYxwbz0uteGf?5=Ud99wKM1-Spj3{u2MF1l~THjy(g(M(@^UwezLA&iaQ}2#LX*N z9v|_AtgFB;ldi&3)>RfOS@LSC-nvSOSX#fEhBkB+=8oC5sb;iJ_(69G4zRXatcYi7l)S@UKfN`K!m1 zKX_>+Us}o74?{ruir5eHN=Dwq3xqH&f0^RDbf;a-)#v)F zfaGIgOy=nulPz&glCig+tfkE&*CW)$T>}cmOdcV^)8kw#bWD`_Le?ocIA%o>==pqP zzTOnYwOM4HT#ozaQWOfwa1!mI9oSt&F_|Qhx;wMTgEa+b8$x`%VtOIZY{niDYQR{f zoe8B~NEqtuQ9@wvITYJwhtcFJ1=!w=Q4{}}V&ZX)%(gjGkb3S(XQs+NG1AFa&N)_2 z`AcMO7-0=0^)8~uNOj!ryD)>0^iHgk{JdjEOqdM7-PB&L#tu6QELY=Hy0KHf3mc)U zp(&~vF&v+Ao&jO2>bfKT56sH;afX+Sf*PqDZfv~9L*2nUgOl{;^-%+$Xal((D@i~d zh|0mnu(yDieQyCcq72ig6%gxbLKmZS@p^g-PO-FSJwG<#$ zV7~hf&(KW4Vd$uM9CCl+tgh$}dn42t!p1qQ>sFpPlK#!IP3Pqa+fbvB?VK?%g(Hwd8neJ$h&J@5plWBj4%N{J)!dz$zdP{` zC^~<-mO}57!k$hyAcxg*`zPHHf|a^g`zLLybG(?l23{(j&$Ek&wyw>UN|3x}cj111 z7$E*op@F=n3_@O02GHk+&dZ}H^Qe#<(;d#wUc}K$%A@o2iC#w(X~TuM%NyzKHMNFM z80m3hM6BWWrQ!D*8h&3IJ~zkJ-bml2XAR#^*6=wbPyO{K9kCUjtl{^whTpF>e4es~ zZwBH+K>7;}UvbP7uQhz%p{WShr11|F+L|@|!NRm2%mi;_?<^WVqqK&v0YM~CUsY=; z@q7ynUk&pSG*Gts<3htHXVUPa?PYJ|a@z|HKiclPQmThdpS^8n@my_N!`JQQqaB2e7FV-yhN?peIJaKV7pX{EbU`!6m)m&qptSS)64h zODAaA>V93*>K@dz`B$dyh$kqI>p92KB^&%siYVaAQFwR@Iw}+Q&U_tqfb7Z5-Frd^ z#?k`DQJrE53Ja|$2a57S60$6B(I2m`Z0izQ*TWuP z*jlhs@}^Sn($+g^20XuukO6+)sliIJ z#}^>Zl{*#g29J7cRv?iV?#?jF<&qLK%ywf;2#UG*va?=e8UUU~5S;eC8y>me&C&Gj zhi_T*{uj0AjgYe;wJMib8YyF1F;c*?a-?-XTY#0$<$yID)J=Z}v!T!zF_NZd0?opG zs8Zh$1Zn2J0O-8|(0c^XG(Eb<+dc^eeCbhwpryY!9o_C?SeissdVpA|ur=Hrw<8Lz zaIBzrtcgA9vjebWX>_u=lNLffPYt1-S-6LQ#ZsHBB^}EDq*f(Ax6(Ph z0WLQG(haH!***X#VRB3uoy1B@lC&&I(pN$m;z|B8`jG%cn@*ZHWBUC2H;e?_O9}twGK(_8==y~7Z!)q zbr)kVkk!6Pcw@Pw5@J%TBkb>h!?F+^9NFo%y4|i+P{xZo!XD&MN8CVGNB9=%h3sC?qGgo1_#rfKUmsGy-GWhibJet|C_nA1ry5H26Aq6akO3;2%;?AEbv5bI+vsL%X`4x95K( zzx^>A`JSoY*z7|NT>5cghbM@Hdb3x%dcY0+K?zTa>Q0dmkJDHA#Id%H68K%_M28hT zuisbR*gm7*D{d%1uV1cRdQQIp_qrR~r}an;#FZKh)z5z7wEFoa_XQ~P%oerkGbDq` zTRlE1Ag|icHVKZ=b&u*mHP`DPw$EtnRQzdg`14GYu-H)Oy-K8j2aE9<3BiM$qE502 zsC`S+wHpISU7CMF*z?)L^lM(+WTMm7aboVylOGr0KdqiU@G(6;n^1{_PnRbqzgM!h zHTvgEty_awQ+bA93|Bx!c|6g8qxBXIJnvfF!2E{tG__ON)8&b1iRWkZ{B)!vD%??tC?7c4e9>`l7c>To(3-5nlCZQ!^^zy!?rG1#h> zPX^bPgpwn zVO{u++~P)>#o-s6C5i|$`K=ZT8r0uJNd4J?`Xp3;!P|u`U3dp6`IRRTiYyv&qsvh}a`gjdrSwGb?dU+BQ_N9H*7arsFM!vypsz-l}moM?h-$p;(YPH^P z`L9Lq%Br|!y`?+6fNbY@Dfsz3oOuUe8eR;O?Kmyj`lVM&AHtngTvdJG`+l)N5!GRN zt;vtHxL1M-8M_p_h=CRjX1@6$P@Q$K^hH$99~-&VV5I4n{Ke>Vtybs#xKgLS#UVPb z^Dht+bO?vmJT`*XJsju*%1_(39-Q-Ab>4b#&OYIH&BPl2&!7C>K*{2;#^4?uX{dv?>{b;H5F0 zZ6rYA&toJnX@Yw9c!Gdh|JJJAoFJm`V}g43yet#+t1XsA;2qn}1Mf7UVBT$DUYa6+ zeve2y|2hftG)0|iF-1Bb3BYR*mNk&Fn^R;t3uNj!nC_ahK#<*Xbo=?3~DD zx+kaY+YY8W>Dn2^ZDTJGOU3e2tZ z)_2nw{JWSVP|$P9CS~<1T`ScQPym5^{8X-{G>hGn$S@pnV`|7OGL#CBolu3N9!}~N zBE!B|y6itxPO-IgJVO%{ObqWk!K^%%(R|!z(9i7F4y^tc3UHIQOnW%xEW5$^f<{W!qG^qC{ z*@vJliJj_w^sqDXEZZ*W#*#>~-osS4tEHg8s<7Lvkj|>!kHUXi+@sPSRi~FaeOuU1 zdjM7>lmBl$=(B+w)~8bLtjwh~STwY5TZfY%@N#m@A7kKOIo|*BvKGej6=W@sZfH6v(_L)9%P`#LYwL`Bp=5c(_` z(FLh|P>jjP{EEs4?vdV5`G!G3vdSm&|F{0uX~##EERZglr8=Bfo!0kEkWzKX2---~ zp;H}B`#}@>2$fPToAR$$wK}eb7G?y4q4~|FT@u^G987e41ilW0Xi1C=Wm;-5T4fAI z8KI#pWe>*ZzeTF#UzUa_xk%Yg@t1TjNV*^6_{p7upd)&tvlbz>T1csxh%wUQBiT|b zHN05EK0I!@;Z){imDvP`?|wY8=@6j@Uty* zg^uy)S%EFH-d}y18c;CH>P?71pXB-T{IPy%tjD(WvP?jkgqEtk0)2?L`o0Cgi5ogm z{Y~CkeBqtrBEy%8jRan@+Bx6-ip<4%7suvf85q36or7Lk%y0@Z4x}U}U zjuJ$8>GW6yGB!;{cbPoPVY}k`9T~3UZfJhqmP|N!LC8x9npJ%^#bc4Vi&PGZ%bZI0 z1%!pNmX6Bfd zeUiPRy8m&n@|ITl(|Ug7C-N!>B0I8Sx2AxAyMN?W^6_>Jj|cjXU{$+BEYY>K3(_tU ziYE_K>6Z8<;Be(5v`J`1PH5!pGvQf_KxuldzS|C zG~<(&QC9zu->JScxEp+`hoW=bSH8ZHr|9M}o({Xi*YiXa8DDY!rEbdML^r>E2^=Op zR4*(&R6_)=kJMZJ@lUD;g3V|6jW%|uSw>|6Ku}T}oZ-C;ifcr5e&1Hc*5nWTtCTO7 z;lr5xS$_D_a_)^FKm0 zY`F=OuFX8P$N|u0z>&=fzMjoA6<5_X=+k)}>c9tw!$mId}B&vj4A5&6VfB#U^L<<_o`?%KWo_a}pMy${<_(y6ZZwYTPR0)X~9kG`5%KaC741 zU3vU(t5wja`ZdE!DBS^#KsMQ_gyZrP8Gbx*9sTc>3N_UVY~X;Z`gC2@KRW-ad~(sA zs?O9^37uGQ=hG{~L09zZ)Ti9gX@^b^Xd5 zT@@ML13;P4wJ?`KX~|>H_@AvSy?{=$V`G_w|62fE?JS=))!u>y;A^mKP2`{>oB-`#?N{`l6{l@uM zh1%1MaK*2 z@LWZO>J-f*`Gsh;Ijx^2Nz-aAZISc2Xr!6*3n`}%wNd96m2=+`0z81AMV*15(#t^5 ztKi_%)za|)E;-L=>FY~D;Q}_k3x#F+LvlE|u@+p>T=hJ0G3cUiAR`nZty-cqRov?hpZtIotX<=oUeMujN)&KaEex{Nbs|R`Z z_!tp0%J^ENpKmV>0^#`l1|eZJ#1BR%dRkPce^!)e%+o{vo1U=1xLxsX5N<_@=gv_A zP^>8N#dDNUblTotJ^XWL@0HZ^y(OMHM+soQqQsZZQ9{$AHY~Z;uzHBu30VCLes}7b zJIYf5Wft`Gl+_H)A8?e-p$OX98vVDmWJXYh>;$S1>w`osnvd@iuHXI@%szr4_)t21AubH5r|&oA;^F&I@( z#)`U5PChH8y{&h;fJSv5o%&6+{?+Ui`jcU4xQ%|O%*bShvzu1etR_wM3PSbt-^*m@ zQFy^JiC#?%KYxxAtBK*GAWD$7y=|-}hELWdE}j@J0QKc^^!eA6``+JwA1Fqoqc4?U z-rDcw52oSbn^?2l6v11mq_&f)HnEFbAUNHWMIGjPT>selBd17bz8;2wiRj3>4FhZ< ze5^H}$QnQ{&I^Dz%{Fwqgzl8o-4s_Ba^C#%(7j<~=ptsodt*^LW)wf7bdIB5Hb=)^~l{uTQL5j2N8! zF7V2mxGU3vI#XF^YVXc;u+G%3Gj(=ny1dRrOnhptyF1erb*BHHz4w9Bw7Tj%pFi)v zs<*28?e6MMLzD2l?=X{QOq*6QV2jzhH37m91YLG@_v3z`LwBINn*LK=G|4W~mC#{Y zB_q4ym>Dr)6onYFqGlBpO~>E{g+x?Ln26X(NF+)k6Ul@`2>bn=bD!t^Q&mkv!Wx}) z!TbEV_ql)0Irp4%&pG$rdX=d$n(34(Q=`h%9L@AHGW{UnPD7JSa?LFZcCE1FoI{-a z#fI54ZYWNk-Fm~}-iG3&*>C51Qn7J%(+!7CqJU*Ii*d)1a$?ef(!z0NPmOL?< zY#1bY(vl}flMQzyPg(NRXtH6AW>i$Cl(Kg(bgYG`Soor&{ugI-v|X_Vj?BT!e)iKTWc| zsN^DU;Tn?F_#unuIVF=%xtZ%tTr`;=5H)iuM`9|tm(@-YvMDwE5{flSVCnPi0~nU-a{%)g|dpq4bGmW;Q< zn-QnC2vvqjK6UGa6W}peU0iYMXNsHnU)v(c~mK zmgsEGC8x;2I)zo_Sfa2wqttHujSDANlVgdt=FD&nIhq$vt|iA3RZT?QSJft_F|I*b z4~UR#7HRrfjw)8_XXsyOXXsnf%{E9kLzO}^L%BjP>#bWs-C9?vTkA~SdTpXvC}tNa zc2ecm_Z2wZmI~Z#ape2RD&C`*eq%euIMg#HZGQ}+VFi76f=vmM^K1*r9L2pgn{?N4 zo${&B8o3#v2xz3Ynou1c4l#|$iq&jVqP1reyI}W}v8&sLw}~BfGx8h1O2ha0LU3MU zUZ&=(D?+srLH&N&Wb!WVKM`S#X#y3)g}YPPj2I<_Bga)@!A^37WJP+48Y_s(oC}iKQ3U?N$+k&u;Kr9@}(rw&*|dN?~8K zn}%QUTE_0#ElXdMG$DFx7V9qN5H-7K;XD8an2pUL>wNOq912qsc{~aK+2vy*V6kjc5rE{V=vLfMacONfsODs@x)S|7? zpb|mGaJEe`Tdr*RirI>oP4iSH_A8aPA!gG|l`U{q%GQb5kT)hJT39jO|0vkGU-}Q? zKnSZdcr>2qJOeN}k6sG-3OJZpOE(NuI;%(`>z+!)0#<*Ld_~J5f>~)QIk1c%JW)Mm z0j;ZiqC7#04QvBqA>8;_*_sp~!TTmb?)=>{@ZRR^=yH&HxO3pWP1CWJ!2iQN1MlO^ zJT&=xpnAX3Bpf`*2)>=hi@V@2Qc z1QDc&T-KJ^N(1aqY=mN26f{B>QJ0xu*jSU#EZlOH!M-0rGHG!mU#@xZrNnJ)OQgi@;RG+g(WCVo9kOT8)M=v=6KVk8=HQ+bF zTv5m@%76yT%%=hr0knnwDV!-r0I@%ef?_b-Z^{Z?wE>EF?>DGDcf7dvV1Kvv)Vw{g z*s?wHTGRWq3NTWyA1G@n!BKTq-S*I{fo_W@s60Vt+43H%%2U_|;C-I*U>?@0zH{Ah zVK!NC5!}K$1bP?0?h#$K4YyWbD`eq$+n^T2h_NrE$q2P!P@S8a%-9r2I|JoOy9miVGgG5Bzl@&Pc!RVTWc;E7OkMm+-}-24If+2=CPO2A=Ar2{Bre41swNB;gO zxrqF?Vc26YhKXKnb)L{lt@c27urRzBFL?H@aJ}&c5kqzB12H`LMU711F}Hz8vWBk@ELp!nxWhS?eDA=P3e1v*8tRVq1pBQVT!E<^H7AnM`2L1G5a%^ozRt6d5<=! zhhWDXwl-LRaMkm=THBCdpqsb{Y$!}Of)qpbz{&Vc6$JU_3bD8=vl;!NlF9zVjm8?V2ER}zTjz*;xf6bU zTN)mOUoMW{O!#H-392DIh^u?~-hg1*fF{b)y4t^&W~pNwM^*|GDdSGFnUb1Cs%&&1 zDA|SaA=0LqRIhMV(jD+78NgFd+)P6WHqXXKaPS`~ns2JXooE~TEM3L&Ev8z_=gAT1$(=~GGv$vm{ zvf1!pcT7HcuV@6assd#tv%|xGz=+m#rRsBa^V+3Nu~^{>IElC3F4FtddPY4WjK;J- zp5nRHXfBCo86Y$@7@CeryI|EdOoEVQvPVcTF2u!bco;yypX@meZP?nW!2^vqs1ZdI zQn1yBeXyco_rsU1Hh}UtaPZ-#UcmdxUJl;WOc(u$T9#ETX_!F0tK}~gy(lwOiE@Ss zt8JH|Td1$mt0Vh!3<#WT%P0v&6?%;VF?%aUk;R(U@RiVZFQzH7_x!&{lhcy~9~Psz zvn#xR@L86@!ROnm2Q;?|C^Xd~y`f5!8R!u{eK?mifo(dmnYM0ZS2e-5_SeYKFcUje z6gPCNVaBEzB1y2K6YNsK8M>|1P5LPzja8HONKU8W^IT6OX9^X&Zp}74+!efG_L@r3 zwh$c+b&=cYIlIcc>Orb$6-q%UXs;(pU?Tzl_4R3kh@~FGXJ9nh4TYc$o8DP9uVj;D zd^vijSZl>5KTs>V74m6iVpxV0WGY$8!YYh}qYPcqDV(d;e;egFa0W@U+LRO4=Lq7a z)9gV)l10Yx7)yg3PfWXREA8}w0M|z#6nEMX#oY$M9bJ~rXLKNi`*F+VuOG1G9`izcAo z$yeYe!lEg~0I0NERh$I+K0ft)qE9{Pa@2z`oc+WA^<*kA>#zn-!m}hGXzck3Xb_7u z>V;tOHt6Oh#bPDcqefeC^)!591dA_b^nWJvc*GG+r9=~K2)N=#kifD_jf>dVawvhV zN~V#)MH?#layj7#C|gn-DNz?-UPwpGsBHL8mBHJ_dbO#x*+v=(|C)%)#PtX!fqbZ4 zc$i>v$HlyV7P=Z=Xj&*E#xknVV}?G4X{A%x;+rRU?Tc>`lTtDf5J#?>kjPBabtmmt zGJwX|h&QFIWn({TF?|_)G%uO#2%x>YCAdx2JFth8iRFNuE(`1t@WDT$QZe3Z;f%Pw zmrpg*1S?Y_8*w66#o`uOPb%RkNVUkP(`}5kA@sqM1_c*&FVXBo>XLy0Ys`rQ_L)9l zO-va$tr2#-ia=^l1x-TDR&COKs9{8}t(2POD)-sM60G)T6U#LC`IO?H%>OA2l=e$C zP#(Nbd~OLyALjLntJH2=ewEstyeGaJJk%ut?#KX9Vhr#<)2K$DyaqDOq zr^x|`ffYAjY=})d@x!VNyJ!|Do;5s(^`z#aV^0ukxP%Y#y)>a0!UvZqiH_b)G`^fm zo051>g{ox~OGodLZyu<=IUdLK*(z3DWVP?D;#w2aZQi&Q$A&*3lP)a2Dn{c{u+Lau z>=bXZuqob6Z_G|URJ#s)-%T>#49(R`ER^Wd?%F3ibGA#3CsX84?o4Ugkh;>bF`9~M zoMd1M(94~VrP@EdJ55!`lDXa_X2?lMwzsC(SiHRR72FK;t-Fe3B*Z#W*Gbu36VFR5 z)|H1@K?~fE5hEIp$omJj{!5EhcI>t`o3@jr)@i=6rt_(^SO?jXnIl9peK;-U$TJ@+ zGY>u@=+xK^6j-8o zU{U`nll`W~sbPH5-4FpiMIW>|zn%9EXxH<+z7N`B{QzLOUjo?1>fU}z^^tUo>5AW3Y;ePBB0&Uz<5 zdLvBHPl@JuCl|E8qfzmW0TS<2r={v@jDa-a6>El!+rVKha9C@d>5X^1JH=XcqVDGw zto4>zmT{d>QzZ(!YlYp6ONHIF#oCD7@=M1qp9#ASk{!DSf-GX!&V@JZ8VICN&|xiG4%;w`dSdo zA=F@AD&TR5Qm%@dm~v~t56BUfb9360ep%)Qr2}~pJ%L3Pl=4Y?zZlD~y(yP`!bypW zM=w8FrIPr_AKK$JSN0}LPelIOr@zkT`;YROci9>KhzOM{7cwpJ-)$j`%t;3%1 zHYl*U77lA2jkd|UIeJ1FK{(;d4Zf9yEr7fN4Y8VJk@6pw3_$B(Gx6;@l(rUbRNPJf zQ_^Cwhc#|SzVlM`4O4t8Sn_eZh&;#irf-v+>DiS^)v;QH+nu9WWmFFFUfbx4UyjVEdP(+6bgqN;n2n4^T@oiyVaO4q z-IO%=N`q4@@Z&VpNS*vd0~LN}t!LNX)AQF?CqG$bUX4)e(g!0MSfWu)Zxn;U6F3Rd z6ROq{{0*xw(G8?ugB)?NG$m4m63}sGvXfgu2qHd*qU{tW^;jPRPuznBEa*hmn{kJX zB#cX3=gD*enM~&**B*xkRKTrTo&^;|8LZ`Exnzx4a?$YBwvzC6Vp*Rp^JeujY}tG@ z#+I3n+Rl;sRwub_P0mIyrWB&?pR##M6`iNq$yk69I|*Uu|vC z)Ym^XVjOBl{LB}5mYa;lKd%ME9m0s|WJP06XEE0fgYeNBZ}9vgeP#ux;^ zdMKC6$B@fT1OQM(fFQ0#aY@T(=)*a-{DWCk6wQor8#~%QjXNFiBAG1wZBYwAUz38o zkj&6)@)&AjH)=8Gj8ZMcpAWyI$JWX&kn1KH;GrgvZLcwTYVbGEtJ!IaQkiORB>-q45AUfrlo=N&X75texY zK{#sfoz9NNGE5RfFw1g{q+=mTyAC~)A6Nv37MtI`=Txj2_(`Qwu1P04a#cw0*I*)_sgSlMeW z(V@jr3>(c^0pNiGkj7Zd9_mYDMt>zFBZ??+Q+6+5k`&AdM4W&G8}&1v;Aq-$o+l3J z;CJBLvfO*2@OVJf>69WlkjGQ0_$Epa1|W-pY+l57$)A($C|!yriWhyDN5NgESH|x* z@lvDPODF1vTandKq{e&9-)-W@>gW~4k&>vlT|A?!q>jw#9%EVZ!Iwd-a+nCN^~Y?|PkeXK5$*@{Xyu(Es7V9U!LnpEvV zFPqAe6lj(uFpabCoBCcmyl9R;!2#O5y$nknZvl{wm42BnS7JipvRwjovP|wMd~WyKGn^Yrz*z=(oo3Hl1$sV4Tz_K_w>a1Zry%w|)}a9DRL(% zV}0;&?YbETfT|s?T@Uv`l;qwnUA?aJh+bz6Wi8u_EJbswx>>&l>ZNFJ5~iJzI=wym zAnKK@7}62nvL``Q`p}R07TrD*O#@dL4VaZ&lL-|ndq+K<@9kA+^AY<#zwFH9eC{we zn90OYP~sk01~jhqLHK4!n5Ts2rvo@vpThMR~pMBJ8N(}GRen|=5#up@+h zIU%#4K2Y1t2vIj%j;-YJHfsBKS~fw;icHaG7Q+mW^N%=imSp?LJXjxf}0iHZB7Vi(3_Y&0o5bIgorZ|+P-#BFz1ej`^Q#8*hYV-4QD7pvN;0R@@r-Gwy zL9FOrx5hnRC)Bn)=!dngM$vZsnEIY3$LV0UJ;oRX!ZLpIc4g)MQ%N)9%p z!ADa}RR&S+O5dmgQi=_l1Wn;^@iTFFW*Rd^v#Mvxn<7*BOx3KKAj}C}X+vMB(Tt_Y zr|Ly}gQEVJk-hd+@}-bLb?0M6<@a4Z;Vbssnu2(`Kb zjyj`{vuRb}K~Y?&cr_Sw+^MM+Al1MIK^)LHCWvvelyN~GJ`-_h;HdoC09c-F$RvhX zc<5O+`&N&TVyvJT+5ui`I-fSfqD%@lpXNggJ@d@RzB1Rd5piix`|_U)s9V4PA07IEgdiNcP~QOz~MQ z6mQLEv=m9YweEnS%_?kNsQE;gfJ?*9T*IbXg(=1sT~e?lLZx@Sy_QLNfh*>I&=X77 zj=w(0+=^gsBU=+>RqEhGM_aJ--i16>Vdp>sr6$O;6X@4;J|jI)N6(i5T=8gm+cM9O zUt|`;XrSBLBhbdEKB-0~cS9r7&K$Gun>KFI3N4RaXxW-%h1plzGoMxYM+UtxH(T z=o6rE@YBV(QgCD2evA)bV!XlxcSD#kw^WRcgAn@;D$S7a8yO)ojTWrINu%FEO>}8! zL`-xZX>O-i;(MHuhw3sx}Q3 zS*$A7Mc`Y|=ej=d6|2gW(YV*T2z+CA78y7c_t_>^Rt*BKlP<=Z5@VARW0M17Y*J!u zQev#vh=Odgn2dtV?o5!eos|Tc;&J)m1fG8&I9KK3$`|vQ<{Ohh#kAP-WS>1x3v83L zCCVz!3DQ}t%7Ss|sAfa##@nw0D^$N(3YT@DYIUDK89ns(XcEmSj(B^tH_Xw7sOYKThvWlzQGm^HYuzb?qq{+UTGz# z1|r~L7ZcyMLCf}tp4R9LN8iJeg3sxg>@}S)r)YXuSKr~z1TOl!-A_dP5uWf}6IYOmqRWjc=yBG$JJs3j;bAOa z94%m4o4d9R@;@ScUR)%_%e93>Qow!L`l4W9vR>cDKTpPsZKbT3ix4sTCme5?n&@=W ztP>9kVqm9%6F*EIzU=P#Ic~pp9Clh z#LT&JBKD+}6N#bd*MSvGNZ<#H5~y%O7an*;NPu3Ut3k1%nN+q%y(;XL7VBQw>zj=8FVV^6qCE;Nm$lpaTy|s&FU4Y|a^DM(``j`1Ts0oUV>pLg&-0^dHY!z93%9Kxi+OX^5I3c61~RJ{JVC&zO-`Z}k*;GrcqIyp2hU2N*FVxFhg)FiQGdzWvMO+<7iI zbmp}mq~=os8h8Hj7~GaG6^ml=h;c=Z?U^I-u5l|qYCTm>H>9C<;e|rWhlFFHj2Lbg zZQit)vX8mB)R;^i=0PT4-=@b4By2|nlgF3XirFJlkQZynYzS(CNG0x!Y=~^|Kms^B zciQ`*VX3&kOv9CMaw;qEzmSo2-UQq;e?frwM1d|eC^Fz1Gen@~dsiR;In-l^ zOu?RJa@d%TW=9kglle-bw$u^{CqWB5+3X9KfItW|p8=Zo-tdPztRALJL$_1f&g#>_ zSwNU&pH9+pKbvfKylg4)f`2_t8ae{1pv9vE-0r#|!oezE*_UaKlEQ%fiV0w(u% z`8&K#q`ltaa1A~EMvLU-g5vkaDwK<=EMz@f5I8dPTv|9T-|e#+H2qU9xv?(RUFF95 zAyvG<4t2Hx&9cq2A!1+eD8H7gOkr~N^>)4nP>EL7Bz~}+gZ0?~u;N-3HG4bZhfeKGA}tP=eXYN9#MY86;XZH&si<#!>*w z{67hc>;T0T6nQl2ZNGO^E@`$IYsMaOvE}1q9^e~`&^~F*@P>-0yRD;NM8)l55yFKp z6bFY9n_voTPQ%O$nKlu|={SH(!ysZR9Hy-yV}mo1dbJ0d?QkZ3%B)rVRH%Ws96x*J z;_jOZ7k57}jEmpq`32&lDk9>dU&J>U7f02$6UW8zvPWF}Ayc{IjSDd=$pDx9U*~9W z66ySrZCtA*ASw4EB7)#WL?a{JxSROsaVb?}+_b2FjIMlfpetYe#&zXCo*=jShGM5= zfX1=v;#g?TfIklvZ$T2b;Q{60Cw?c@g8l4ZVO)Pef)8$m+0tiAa53$Qu~Ol+=PR6r zTgw&>2V>@_31V%kZ^F%YkR2JZmOCHL{gP4XNvOy&e&`3TU^YeMFlPs@ zA4*cvQ)avQo-W6vhJ@Sa`1gNYptA?iiWb-t zg>-K7-Ru{Mo%G~F27!)P|8eqo!o6S*gg(U`iH3<#<*nr>UP za8o13)%daPf8Nj;ZdiGBhTAy2w5p47Sa1GVP$D++Mh`JCV)gq!C7T z@-7Zfz+rWnX8x?jMSbZdI)g(--bX%*wKg6#GR{7D%&jMJA|eHQ+3Z<2^zzxQM{W=I z0}vgSf&3H8XJ$8x>(dYG>eXg@OR{M7mNNM?zAt z&76*4zFo(K*4T-GV!Q-J1y%#HAC9&%ShQov@?c^9y&PsHy`KK*YR8Ay3LT#UDl36< z)k>gbIxFAmaiO*P#Gu7FPDWpSB0M2ztz8jX)5}9^{WpTPUbYfI)*X+wFzvNCuj;ta zI_boq#gT_E23i|egckn94pVHI90a3v1--hTE~)rg zy3I)nC4&o>1^q3Vuza7=jqzFwO%vx@qK;*wauQXMc;57+8^ta`{5 zWzpmeN%*`Sc##Ce0?KgF(1EVn&i0djtmd!UTg@zV*kF=^e6zWj2_|=^9}7Qw?e7Xt!z$Vat9b4ihIJ2JB8I?4c2E zQC0V=DnaVT!6zMfGE{17v{XAR2MKo((6@5gV&@{ZS-QCH}!4%(^dn;QGTXUeA2gz!l9L?glz)ss5;O!)(4Npc5*}rld)Z*&M{eJ$5A34&I&Y)3Hj;9PA6{0WclKgr(Q3azF7Q# z-WQxVy@3m%TGM{%aABK^iE#IC9mN7_#f5uy{}4aEwY&~^2%EzldhrMd?)*MK0l@hl ztI{{#`Ddj4kzU`yUHI4GA-zy$Lh7ik<(C>4ZK^>vW^9qNXlk1K+T>3owS9`cjxi19 ziqtliG4YYib;Bo<7*-`09ZQIOw`*Hw3{tbseW?(j5S%x-OGn-dmL})yULjBP>yfa; zZIQ!5DC8z#Wl!mP&)u!_lLVaLigstXIoL;>;pSWMa%ps&_^T-s^o;itHs2MF^2%v1 z)lpCU_*w!%#9B9_eN{M!MgX%_)oLMW@@rqr<(5A=O3UBC-SF}0EY_B%LOq*d-#0Eq zh4|jgXmx4l4>H>32dQt$UI#M+Dl<&n)sEJ#&IP*S9>$4yB>FiY>_b;RVxX(Nt_^Uf zIp9v=$u{y=&{ufs0r34fR4V!FYB(WbCO3WrQaF=We~j1mUOn83!s$T-2AosWIm2(x zar2H}vg72XB@gClQ-@p-7gkp=jA2tMeGgR}h79JiP=f~4IKJ&QvI1&Sm+fFs4`9+fw&lkT^j+QDZazN^8yX`%Y zN^S4&0IeacZJ}{OV=odRqPZ%>gNcJ8G_g=dYH+_YAsKQk*jf>DxXV2u^hj$UDx_$h zuP7r}f`$n0oqFY5Mqa3>dN^uyprvZJ*_{rX$GLpxp90=LWB>s+KbV_}Y;*u_ujN(c z(H)5$;c*%nB<(~y+_AT}LtMTMAx0-WE3$&)kko8WH2*tMAcK}RXn?fj3NUyZ*q`YzAT3n;K5iEV++&M7K zwG*!!>{{Q&KlpA=1NaCju4!3Q$DZK=73W3xFBIC!g7mU9gn&Nb=uGWchoiiq{E^Tw zpCLo=$2#I|dn~B`^>Bmo_yfK*#8UwdS0-&x7_i|$6#<)_zefU{ zgYyJ7ii*`|t?_U5nB&b!?_hU)GzA#i&shj6Gg_rNe% z^+vNbHr}3?oLbdcy=Lt+!Y2z)Ac&yu(9>y(S@`!4`Qu4iP|3+O@$r<2HG2F|eB7u- zomqG^KAt+!(c`o6@n5;q^%wr!i`wK-0{6$qGd(KcZ6EX0SGs5QJ@N4@KPK>#)g%7W zEd0lhd**M~mNZ%TaD3b#<98PRDn72#%El~wAwEuMOO-5qH9l^X2|Eiv{je9cQOZpg z?uw6pY#z`oJQttOmccp;zw=)__xIQ^6gXj4umZ=Y^e4rdxfII6TVX$^L028n2?pF}u3supgOUGODpW*j-G!uTvQt zb0)PK|Be0|d_@etiZ`%mFBr9f;jYE$IjpKq5Qh1Q1zh!*rpnfb5D*{-zVLH@2kR%6 z+&bH=It%9-{Y(|XvDNimEr2aCRLRF+V;F4}St};$y&iun^3;uvH)r|l)e z`1$PgIbm+N4-$I!ug2_OXT^UpU982vQ=wEqB;F@pNLjwK1Rx%@ZnnQn{tto=|Qb>8Uvwkcnp*>#S;n zDboVYC&(c5N>SCR#fS_ToZ^Fs2j=AEl5XJI0h+PL#`#8PpDZ6Qnv@pzaZD4onFWP} zV}HTWa+^&!llK}ID0&BxoRn%?c8D4oN3g-FT#1BQ7$_GJYvncspPEXNzDy{X71xQy zwaP}GXmJW;^x||}X<6KcSX}x-i?i(wtaY;dDNv1I_yt)!7Up|0^U20E@4cRP^Nsa; z@kyqVi+ANFE$|lE(+3V62#Yt}b4a6~p(?G829d^krNvZ`=h_ilMb5~ox0udvO;+=i zV&@A-_0+-KW;Uq7EGz-=EQ8-LpFKvk69?H4%|6We5Xzl03|o_)`%MuHBMb_W3ygz} ziPUC-YbvF-7bo)5Y@V^9VexLU{m>#ys~uJuzgff}VcmeQFDlsD${q(SbZ3#d0Kw*q z_a^631`q)RJj^*D6tX+mk+nS4)z`8k-ZT18zHn%)IT)Jz!;{vpI9%eS#Tg2dmR~4Y z${=Te`a4Wg#8cF3Mo&$n2R!Gxiaq2`lAfM}LNrUwcE`=M zV+((54rHs9#Eh`A41}EPWY(J=(F8zeEk2EyHRT<1dw3_W{ZI>AgJj>C`oaLWElO2-qw5X1 z*b(Rx2kvX`GBIGy*_xzWw3w^?T{cS@s*QnWN@7x&I{&7tq@f5RO5#LD=~70WpIf?82k@6e!u*dAU?({E3Q z=l`1WX;B8C-Y`h7QoinnFlCO&!X0)uZ5ZJs(a&3$6^f*(Z2Hhlr(jF-&@3FSK71Q* zHi2@D23vr|-K)6M%r=9#Lnb4E@n!g{!+sc!Iq!5qTZR;EgC<;2&45>v2F;Ey_FA*!*Zt`2VGFDu z3Ys0i1TK=}M1=k1K1tCw(luM*)eeWkbhz^`qhN=OF<;sw$aMWyuV#!9k&XQxt|$W2 z1~{TG6l#Vf^)}3uRFN6g%sH9SJTu(iKgmQ9H5*f&`lLc6LLmVsfLfn+W7)exPp$U~ zHH6@aGGGdundWAUM@EC$$e7`0uR^=dO+}}VXvNYe`Ai(1VA2#JVm$xU{aRrVF7?Sp zW1(owTh6S^nUNC-+*p{m{QD%5Ssf~&Zy9sT*bHkH5N1y%`&D{gedBp*yV}6lrbJ@X z{Xc4M%}s5@_SD4qw9LQM)H+msYRyero;TT)>LE}S#{t$O9+Y8_t)O*+AAt-(3ch=S zAH1+y9FMUQ#bdn`UHO#vKh}b%cu8t`YN!^c)NedmzA#jaCh#u>#6CMzi%X1ed|$pe zREtfd^H69;0C}f4>7TE2eiRB2FdBWSn)xMfrW&JqoY_1d*taahpkBl`VT(DvR^jwz z@)Rvl$HN4mdy;8??O^2aBRukXnjmhr%UT{`3R2jtf!RmI%A$NK*jvQad>S%kPvvX^ zhZ1Q6iuzuD8omg+KY3A%x|DjG zdemIu`Ri<0M=f=LLJ_6tU{g6BC{4Dcy!v!cN!V?e6>Hi>dI=RQf>R9OggTLy62N+QTL$#sR+@qe*yiz z@U}f%75D<$w){kM@lz|dZAixk?#b+9kAC>=H$C|FM}O1~*q;61m%sA(13&d^e-hA@ zmjoC?a0)PfxeYc^8(3hwg3qctIoeGNzZB)ohO6{A8(~nVd1k>@H}5O;*nnK8mn73X z+vr)dtT_2A$+N&Lt zLGd;dstBoUj2gyN)U=x!=IXjud^6)5-`tZe}LP+VGgrtk_TJ_($X zbLtKH6Wp=TOb)weo$@?SDsIY4EN6+uGs!Rym%5kwne$UAvZ!+hlp$&mdDv7l*N{kN zfB!>2`_cE^bo9|*I^^TvPv7>dPk;5!zy95W8V5E|-uvu-z2!aE|G{B4;ic}`r=R`s z*RH?eH;#OdGPIL$?B)-uU+1*DaIcaU%|s;ND@9fiS&Q0#p7{m`bj*T3NbDvMuo+GQ zsvSB+X0tWf{yJ`3#w6$_PR+;2!~}H;SP~KvEtP_y89d8m=@#lIZ_8I~elwh>Dx%CG zUr#FSqcjw9;;4)<0w3`|Tx_`#_Lt7j0&R(F>fkZTdBLD^CKw~^*Y0JPCRtNUQlU(O zMMfE84-#1I=amnL;H!U3Avn1_wc^7%rQ(l|4)Vw z2MsY3Qj^XtzVJ4RMjx0|)T?XOimggg2@4(_1L)!X6lOnU3D;3fe(;+83^TXA5_8I? zIPUuB{NLiR)NEDqZ8j6`^ClLV%Fe15!)>$9`!bM;vo+0Ts@7#{okhdckT53&&rYFP>C9<)qSesH1Z z%V;6ZvcIAO8U~|doHJBtjU3usU`x^=bP?Zd|4t_VK26ymPc&er!}#h%Fa)55j(+K` zkYC&AQA+0SHP;C_rNrq2K?G-#%2q2>nM~=mjeh77I$?&VfTA>RNx-g6z!RxFs+z1U z95!YqAVqGQ4XA9>DT?5|<^Kcxr z;WOxy%PB^m0<8o(n+9oHITQY+g2l%hpb%QDhB{Z|!7vDnEV=y)nGH;dL;z)rhlD)b zV<(4G=@`q1h#uE^p@6*&FWMEoZ0;cLUPv}wmf5Xiy{ktctluy?o=wPbGgtWB3teEHjREe zzXi90altV9T$1O1ay&AF2aa$__y0l=-%Omc*5{Nkx0M+s1OZ+|@579epb-(QIHVDe zbIk#+Sbk_+aXFqUk@}Di>I-2>Y~k2>Pu3OFvYRY){3Z`@BD-af5x0xY^;*$0)AU_) z;Ti-YiW`sV1srV6PG(-DZdWXl`GUA*DG(4rj6jA%Yi<`>-Gzu{L;^1N=a7BWwx1!4 zUNqNbzZfr_;1nALNj8(u@Ug~*OWxmQG_Zhzv82(L$i0|!HMBavlexH`IC+AAxp!rO z!8pg*FIzDP48bKF0`yD7i4P-wcBs+ zWvkrtflk@(2jgTj0PjmuC`*_(N;KjTaWn$+K{RHDMtthTLR4r>g@+3^7N9^YK5D!C z(^|<&&ml+~+z`WMyc(>OyqZ%1OI}@rS1UyPieBQp8Zbp(-Kf~Kc$r0jgB%c4m&B3# zZ8e@p z-w>5ug)hUg*%3=KB$OPCu>5V5qzu-NQEHj^l`;QJ<3YI7H{6&-~c)uF>c zb=gcsck)Uq4U`k00*=xBT;12>&)K{yUtZrAm^HVG$VCYrM6QDweP5ho7`cG5Lav+O z9l4BE10Vn>2c%yId*urb;?$=nDHVA%Wke=j@*N4g8bTO0(m6OIJr#490)#|X@U{dh zkvJ6FIk%Uf!Abk4y`i&E=rJkUXo9_#qcN9Plou1-iSPhCO!uwcxZN{QeG(QPl&I5> zja?8254b>OsVk+tEw#=h2hkm5Y)ebz`NT!6G?9nPBF>?N)&=vE7A94S-OfFPe&Sm& zNev5#a=e#b$jsbI!cm@Hz|5sR$&M*1tY0Eg!W8Lic(nL%K>82Je@$p&i=-O$MZVRo z5aY5|0+9x^hcRnYr9F%-r#+B|UK7aX%299CTzDC(kg$)|0?JTVG_4heT!s{5yUyR( zo7DK5BG71+SSVRtrsPYcvqOZAwp1pnlgdQ9kTTwbx%lLn&?&-&G?rMOLwzi)64K?a zH}$YApY=^%QOU2nh(dy7Yt0X726&{y@ z5FzdMkdPTm3b}nm>kT6sDo$1&(F5%Icq5u^%0)$7Sdr;7c#0Rh)~0&UzDYlA?n^G7 z*`C|XyoL_|Cfyjo_p#HZzk=l)!K2QrkO|J1OpfwiYHVW{_TZB63R3|pW5>DDvZ7Uq zS!go@V&b3#2KMbpFr2t{#8e4aDpCqv)s&!nqZeWnMt3g3`iQGKWbC43H=tG+tj26lB-hQo zXOJneH)YQ}B9M_bJhU+XJ27B|@f)TQ23EBZR=nKMqH*3AgSYxuO1(jNOPEDq$NX*u zivlu2#@+#}L>giQEG7z;u{E?Nj@#0Qz^ zWo$NL;1PB1xl;wj*0x*qHC3HQB?X=OwqNfy$iqHC4yRz2JtQ*^De-JyqDVv4R+w!8FjTTIcl%68{qN>nA55%td6qbf1X>P%m7 zZfB~5X#=Pd395wo=D#ugPBs^3s;*SlFzI}~(nIT_91KpYOvD0Zvj#K3~Ss>os%7kFA!!!mI1V5WBffiX6%kd&~u$z zu~JyXC8kvKw;(_9z!uql^D9LMj+A~%o!hTm{k9?9n2t;H(d;9gD{2>Rl>R^o8Ttds z?w&&Uc7D~AxbpI|=F5?Pp^N;I??z%CyrR9B3qYPNbwr9qSs^{P&Y4PR-{rA9Ok%xw zkLOQjG>L8|wv=)*%RAbK(<*8s!5z1VKVfr{NcFZTj7_Rs;l9-9A;AA43Bi3B_ znGsA8ADX?~^*2W~plERtPNU75=dk+S6w=HBMR@TrnLKu$Rrrc8x*W(SY3%Po$+NsVW@UAp*q)hkSY7fLRjzM<`HPZecvkqo^vDnS>_|H8wu1 zQBF%gLzqz&PA)pN?qjYP>^=jE9$|;JMp^gqx$&|?_Z6|hh8>mfS>2~%g=)E@)4I>O zOEj6O8c9apa2o6WV8d)aC!j*)7ZE;Ch0$1Z+|?uAS_Rd>{D7Os$grgQt-S|5*PxF<%O z%+0sQ{iaCvx7Q@$BqbhHUM!pEZ6wv_Z*Sgjaf+LhTFCa3qe@8kx7*}VLcaf;_A9yf zFR&S>^FanhLyuVwG!wLGQ~MRlmj=0@mrI@)NVecD^7?#BoiXAfpho1WBCjKqz)D$? zr)lNvYWsE0u25&Z&b3ccXe8{EE!DNy1`N1)y&e0<3a z{!11<=_Mo*(ogt1#4ieIjJJi7j>#t5c^ZXuQ+65xm`j1}+zzNpJ72N)r-)+VfHtOG8cDyf^^j;J zV%F5DSf-Hr+SE+EHR}tKtXyLS52S1)#Or7Zoj98dUE*uJZ4CH4oGoS50=8+&>kz|S z&c+dhB52zUl=0&{YQ-AmY|`g*A(NS2--Bn1KezAEI#fW9aVSxG^WWJ9|GoP6Ce*@S zbKyVm2JzBk?VrHIw`I8k)fs1IucuIlt_}|QszC(c=Bn&?;3Z(jz#DDk#R4-m7}&ly{cE9emCQvkfC5BO49bXcQlL_nWh>`vuBnMl)kM}K3m+Mqg}&6wKZ z6B|+lX!L=UjT6d;E7(Jr-c4WQP2 z{Xr8bV!mYU+3*HanSWs$OyR+0WvRO;J?19z^`46Jz|l*k9QPx!2#{idS;D99SUU;Wg0 zW^4l8B#KU5qy*5Jft{pMqm!Mk0`|jH5d%@83CSoFoxi6@8)lSfHfOyS3lGQO#hqik zR;UqlDWu?(Z%ahu0sUmgOYk6JnY1r-MjUI>M*=2cZ{fBl0oY_XqKov5_v_{z{$%ys z%zfv-4wW`VkxY_an|Wt)*Ip{m#F4mQH9SWq9kEF`={zDAO*qQ<1&( zs3$pSv20RD4NcrRi9VGoYePC1!cL?RdGv zF{Y&9+roo?3ot)Qb>)j=<%>(J*?msWXpGA?qn?}zeOzjh$F1%VVRT-Lf4S4@2+eMg zy~n;xJwCQM7UfUC1w82wsc^J>FF01SZ27TptEMXd5pa4!<-f@GFFw6O`Cn?VK~gqj zgx4E4ovD}4TPz5a0xcjY*eWJ^Y&0f_F^Q2Z>{X<&6PT8A!t(H;GSG9a%We3&T-zT+ za+WWRMm8t5E?2vhur8Ni9bcErE)Z<9;p=aGU2eWPv30p)wk}usZCx%~sN1?+73u48 z>-}}Pd}Hf!?Hg^=VH;84iV;%V#8SZZ=41)*lya*pYdIZIyRip+KUG|I{ye|?&Q|~d zH`;QiTK!wr5?iR2b8F$^tQel3kF~lFy)Zik@)ZSw(PE-DhPvsAu!3Dw8)jhggfB>4 zH3Qy8jUNq6VprH;LSvS}5_g*nwrmf~bpA$OC^DFOM0s|*hivd6uO4W)Fb9`tMDgaN(qOwpE45x0sGK4iw&<;-8`K8G zwJ3n?801W(J>o5*!p%uRJ~0?cENiE&5~N)RDND`M9nl@Rie)_eM2iZ5Pe+D5)Szyn zR;WUWzJk3MWV-3BPKo+}N2IITa_|Ffk^rL;HBXa@XwbT7LORaP}zj8#A* z&sT`HYPF(bd%VCE6WXt@GSSFVzF- zn3#a!Q%~IF(OSUe;dtBmNY>T_`@1u;cAm}Ji)mH47UoMiHoz8{le=-?4xm9sh!N8O zJ{LQ5K47XjXmxWNH>JrCOiQZN!~4H@l;%nUatRgp9)0G&!KKACk!-o9AB$k9)X!{w zNg#F);cBzkEELjps&<=s5|PrSeWr~`X${Y@%y0`k3(;X#)8<-D%WO=FX9oTmMNDkZ zH#(^u?9vGl07;9AZQ$3UzQPB6u51O868Lzo^N{H!Aa>ar&wME=RICo}PMu0xC{j}` z4994k?VCLE_+#~SJgz5m8ak~s!v%~CuYytvqa4Xoi8C$*FS!sQugipQLnfKs;cyAY zV-)uaj$&A7`V9QhS~a?WrWv8(geC+e>AOijLyB)!TB86&exb2IH*|lR4+8>VO#7o+ zErjv>3`Q8U44PJntv6E~N3RZsoR1BJ+M`4*bOjJ^1*?9_MzoMlOWKkSwFNfXZ;KQc zxR*}6K#QZPKD@MwI_}m)#5UrLP%TgGYHcGbTG7%1bd}lUe5b76)ea(uK;vb9S){3(Om~_CO$`MZ> z24osu8e>KI(6zS+ZVC{rH4+TST24_^6B9sJV@LZEUVEIc5uR zCC4fmHfDJ=C-YZq^E(c`X)$qgSFc@)n1a?+l8J`_nbk%`D6>Jsd86_M`)j3>_*|xYM7>X4E6CV@}dU-6O6tDi&DQ z4+0Ds*mbAF1*|d9oS#&Q6u*p*bq0y|0pmGIO%CE48GZKh0!UwS8^P?~=J>%fcg3vXyf$ zHcZpNMfQ6~`G&o0|I9uYW3;j?1k=PNQ^wH=1N=w*iemqSCnWD*Nuj^H?WR5Eq=*H_ z-SBUbPHn2U;=eR+wX*VI@Y>KrQ!%x+qi3$YVvt9*?@d7>%1SDR^2p=q_olXe*V3fN zED2Irn)HYz5eB|A>26C>^zM?njwPxK{++>S;3cBkC7JHEOtKk-MLgBaOP1#OsO9O$ zxRO^3=47J??FCqU7s~Ex;Z;5(&;^_|khN@NI))=WgH_&`e|w?F5IGN$YF@CBtvph# zl4BQG@hc9ELR3Cyg23pKKA~XDF-*8zU>qV+EZc$}vPHC*OJrxnbja$iel_b3qQ{V? zwH+U?)3#H^U235ljvm2ha6cLq4P5;3*ebF!Fg5W&cSU2KgC`C&8b>nDpLl9CjUnQH zm$O0|*Q5g4mvP}FDO+$wiy3J(MjAd!MaeQj35!2LWynkB4)h&Ns33HzO!EPOKnO_p z3lI&;3J;bIWttP$3l{`U0$mL`7RDt!X;~bYW!-Q#W4A4n58@wuSTPH&fXF?{m+h4VFcAoZ(n z!*8=G{L%GX-kpWdy`Af?Wcpk(vTfAqqZ$H~LtC}VnjlXQBR)Wcn#5|mwpC&P=&#`z zU~)BtO;CBoelJ6B*{_ecYzja0R;%%BJ@wFADF~=IBYDT|?exf7Ijqw8D*^|(WCTNo zwsz^sIo}HRTaO4@2%e*WW!BDQ)_xbyAYc8UC#R>4Gj&Jb_-1O?t5eI#?#El91t7UK z8P~A=@3-?tFyrAyz~-8K8U~+oGbgr>)id@Wye?y)X*{x{2P_;)#1e4PMwwmEW;p8r zw{Ew^h|F_iP6db($-po+EQAeLmzEdJt>vSIpJ1ioc%7UvMb@6Y_+jy_Z#(?v{V3qt z^?&FEjsy_dV?7LNI1SGw$hDT#_L*EnAF-QG7xj!knmhUjFS-}$q((ta(iSozo>dOa zg7sC|Y(gQZ(}dvab|gtBRWJJ*0aThxFi%W2Z1*y^pOoAz9FyK7CLAW=#S>W_!h;BF z8vM5&I+qxJSy;lm`QDbYV1;5138S76t7(XL>|r@PS+L017DsmHChEbu)M;vY5_PDa zCP^Y5+z6|@u?;Q9s-?VRJa$Q)yk(kA1= zT9I+s(Lm}OChLxoW0HvxIYJYFjiu@nY{FH6Z{Yku&v+EWY=uXSQIPPy!^PNP-;+}_ zel#UnTcS;=$ce9t6eK;_oDyn1NM-*3Cl#JWH8$qWbRdo(13kP!YEJvcyxiz;22o0Q zMrw{1cnKSONWrYrb~H20hdzmLe>M-(;oX{}-@;EZwlQRZSB1dg?^12UK%b?pEp+lJg~`_HOPXMQZH;1uonia_6*aLgk<(XdEX^%3>g z_9jr^OYc#46=%fV=A_t~oXs64+$)eop&vc6ZPA>L z>BL{5P1?)Y34bBOnhs-=9?SHO9GkK&;Wo9~3|eQng4;EoDwc-xn%RJ&>tu_msS-*u9hsE^&Sm9d!r)G&VZNYz3^tFUeg{dWP0TX}&G4(~ zhEa}wYBG6E(>o~1Uq>jl-(5@Fhb)UvMUpPWDm%wSHUo9z!LV`7_($v;xf3|XlnRn! zkpO!^!zGl1H+FU!D|vx}&g0@@*2W|xU46;3F~D)HiJ)5=DD37Amk?E9=4i1-S^QNG z&d;^z46ORhyElej#_a9KZ@%NfV$7Yj?HTj-xc7z_l92~V&h z0m%-t3n*cN6VivdZp~|^7P7FcUuM}R?+M87ag;zNQ^5r7qzOa~ioRLO66b)hP#FU> z59d@SH_|X;!S# z&_FtvLLpnQEK%q*(~`@{>~qgu|FvIw^pmT9*q=W5@mK%PfT?bz41vOauq2BmN!o$` z1UYxuzF6$_@2~sOUWoQ^ziqsls>NX#ozz=;IFIUPjWjSuW`4`{u zaW3{KF~~MUhX25u$;gs3kx7(Zr_3Zj{n!ff;jN zgveL5kcA6pG2?C2*8ASF;lhkCREswDRn4AtIFoA3ErDpBPg|uqBxYQOnSZRbW)vtdc=|i0xB#Q{n z((6C{W zc=mUH{%ybT@84TnY^!`adhD*>eD`nO@+I%V=^R5?yOtV0Lr;h$Pj_`bg#v6|sDO1( zvi`=jmkQ{IFV@69315)|i9FMDo$J-OX$K{RQ)i|+Ip(^om0k$CBb6fEmQ-5SRDFAi zSNGXiUFy{px$oJ%1Cs&mvX<`(uVjvg|Ea4NH<>pmZU1fRmP#0%V!AW);K1qRvx$xz ztS7by8`u8kYnlQG2IWR+)y`>lOvHqxjcn{{G9R8!IIJYr;MYsGLNoLX6#wFOYHOp@ zcGZ^{SM%+|eU%zYOX+EN4E}}2Ivgq)17{dQ(w5s_90nDrKxz4B4zc5IBDXBiUZbIl zr?B8sn72VBbqXYfDn89i)BWwDoGgFPRIdFg4;aa2fHvRDBEe8hP-sz^d53C?fY3c2 zF#_vC(oM+>{Lhsugm>LXP4%@G#)g(PX$x>6$2DZfxo; zg{u~A3nJyyT*MH>K;}&9C_`V$s)ux;4!VMB5iE)expqHlYy1XnPBvZ2g^9f?Nv&=6 zGOJ!@@ud{`49^D;;Lu2HGtz5yuw)jFN<(gNxo9I|1(={DVv+5(fJH4lgQKh|oe-X` z!6f%v#yOBH`sMoc!8-%)q9n{SX-Pxm9iU5=d{v~V;;IMrAGko#W)8=YB72^SGHjyp zlm(JgMH(R^%$<<)_V*;2yb+k$7w=7286SX+6M_7AnDv>=G2KWk@`P4m7xD>F`y}cq zjUdD7wamu#r{w~*uFchm~c}{#5}q?R@` z-DF^=wy0rDv{=*tIW<|n04I=-B{@$|Vp!pBs6)Uv>3yA8 znJTgJ-H?;{O2g;fiZi@MMp6NEz-fzY;M~v8HnOQ|F`G}wCm9?wqL}I4MCdU}jG%j~ znC14S%oawnkMnNENLIK3r83Oq*cMM^*vsBSHujcbEcV7dT9K#)MF)<{Sc`d@&srYX zXD^9Y#Ax90iaIT%sOd!B7F@vR2;{YhMfMBh;1K8rmS7)R;3AGf3tY5f8~tcPrsN6A z8Pv;H^Sv<}^XFojCtiZj8R=Xx1i@ZAAJ44oIa6v>J@}yl4{;uCPNw`kH+#o)nC&O% zoVgLw!qzTuQP`oT>+m7*9}L_&kI-0k0fW^Pi%XTO9{JfDEE^do>{GF}xdLFj6Q1U= zj8IMk6DFF1GcD7e>8rJ8`f6sUSl4MRTx^jo91ZG(U)qKrH2mZqJ=~mZyvWRC3gy@e zJ7|aTizZbl>m1s$qnT_3dz!s5OsNF)gUcLGbk51U(qn~ti5>73uxG1mc{0awWO^Mm zQ@SvRXImgiR$!c?2=bs1U>k)E6>WS3UN2~+HtJxjBwA1v-^TA?UQ1BLDQ+yo%0Q1a zZf-%A9&loA#MFbfAyX3u0htluDIIwqb&M-547bW1bibzJ5GPgF!!1M^cfO=IF`!c# z0gxZYAv96q@B@cr(^vXj7ix_dN(a!R5hB2JF>mEe{9q-MNI$PJ4L6oGe!kUT5u~U< zv-!%DjnaprqDF+bY99`|zQ`DQRkCQRk2?Kas^{lSKhpEp?D=ce^RsDNAu<&`L1i*c z$)XG{JE4x2_y=D-s#p?(m+7A|O#y@+yh@V;8Wqb~3_|E;(pIgc4QinO6)7$z5RC=P zqZvUkL4LG9J4b03T10ONKM%Xj9ZmGK01c@hb&89D$v8Dq0E$~lC0-Wh3ynTx4)aVo z)v)0r`J^n2McK}1LV5IK4poNoTRc&zky-}u=8?Gyo>m4-1w$F~(G0X|D1+28aVp!w zNWVzM}9v(F3|jAGjJ9O?J)N1X(`9oK=Q?s3JTx zaDP4T-T($<>VHl4!*(Y(z{PgAiM!X6wncW}o1Qr;e@Z=IG++VMpUBCs8p6FK_{pvH z@Yq1vqjjn-)jwS4WmTPCyNsD$yW9p;0Ug{9lCq+pRk1nF2lE*-9}S%;m3ZvB)*9(dDbw?_z6c=u zz;RM&@*cKsV*#S1P(G}94Yz$>qrBoZs6RGhK^M5x4&%6%!V?1k9R=HGc)u4v^B0EI(ce-D@&US zWOd6%FrT2XA^~5MEyM-9#CzSuyFNuTfghPfEHcGpo@3tOS!Z;~)-6qj^ncVn(_GVn#k16EkQTwJzY7n1LXHX98b+Gp z+=G%sF1#YewW!vW4=p{sqee)kV8|i4B4gP=YWYkikf7vforZ>ZO}!>s(?bWCbACSz zJR{rEKNONtxBdj^g>O?5DhdMZqK(4DQ4{#*nZRK!$M}*=3@=lUIxXy!s<@+Rd(s`Tg^iP3T)fxFP39*~w) z)fa6P>In*g3=lLOSAfm8E&48@DRs4%>4JsH)Li9{k*E{=0OJl zw-&}AsOWnFk>*_LHFc3IhUG`4SAziCVV@P`IHxWnJC}DxfLAmG80PGAa+(2|CTuDP zz<^fHl}~HGA#zFau$HhcnD7#_XH(c!dR<&F!X!|R1K1fPk^nxs!o2?yTRwk+!fiJ?7GkzTii-XH8I3`&n z&Mf}_ZrdpgY#CDSW2S*%t^}z?KL1gzBu@k{Kn0aH-|XMT6mjE}kB-CAmXckVVP7UP4pOn!HLY<_nd4ruVa542PL2AEYW z5BJggPW*xnPZK{A%u^5KVWNv+O`w9g8R9Uo8IFn+e#k_*)lESzP=ZZrUMw4rn6CB< z(VYN#r=X#B4Z#p7DDk%C3^oiO4_ssr9L-qJg-Rh%cau0!YoaOss+C=2_jts(va1X} zk~WT&i7#`SiRkK&<`q|U^&lEs(8VGWjbx2sO*BqqN!Kz}9cMY$i>yg1d9{|ydPv(= zT+G$JC6#S73#-J4l90e@Fi_(ShFAGYbr1!|Bu)La03}#Bj0~X<2g3_PUX7*X-)iuh z5+fJEZg)nQE13pqq!kLb=3}O~%xJi(O6j6muA9MX2-hTM`)+9r8OT8)UnOy#L}$fd zLz#ehN?~>$S|+=R+N^aU=y92vZW@hhmpz20x72i$0*OXOgQTGLqDO5@-Y{v3&;pfi zz#d26$cl3b4T5@Wb9#%Ryxpf z1;M`YA+;noT84QF7^qTR06C2lks4fGc~Kvu_7eM;JA@O?+-6|#0Ygr}1t7p*3Qw=D z`HHaF9sC$vTAYwj%S>J~`1X}){knq25(V?zbE~db0p)G|I=TwrXNx1f=5_Q?Y23H2 zySB&84cGQ++L;At6^%>YCuQn7po}M;G|PGTS{(32e%-a44%Tiza8sHyh8B+|?R($y z#%;T|?YVr%o^4m{IPjLM-ne)7_8ph+ylUI-0}Fc>w(Y+3z}5Tq?S9Ml9eeg(we70C zSMOPP2C%dgzFbI+waw;$NC zXZw!%H(kAF`+;rSx6ki5a9}4d-n{pkJv-*-sbt^I9eWlI?6X`u-n?%oIri+h=F+Qn z?pfF|f7O92_Ra6zwd3-I?K=)!KEHFH>bh#%{M?R(oqMi0u&`}@;nHnyBIE9zSM6N5 zdiU-f^Ot|;mP=zrJ80JS9osM6x9u&v_io$1ZTIfI*Ql50@8Qcm3p=+h?6~m26+@r= zL()kmlO*}Uy;m>n*m3Zxa}Hkh16S{uf6EWeZ`-$z2J7{Hy|{GywuNnFsuyA4c+-yg zvc9T!4ziIU+mEM!5s(o?cH-=#|V(<`&ZLWvWhEtzjI3{>n!tEP)z=H)=9>c z@8DH{ar=(lJFWntm#Xqh0o}r0fheNle<5#^yw=m$sVjL`=iOB?Z4{kl`eD-B9aoj9Npc41>q-9~`Fz{%o&P%{wHT~+ zYmqP~>Le$V=XE;|EX?nGK!}Z zc)1{T#omMG?fw6XyAt@Qs&jw0S+h-85<&u8FhByyWVXyqCV&BwKokK9i!7Q`YHO=j`&zYHTbK8=TH7l6(`Vm%{V8p&&!1X(|L@#; zCX;|@Mcy0yJo`D{Ip;gycFs(;qlfK@w1#sylmJbDhb=*e47+$Voyg&(Gr~+Vz5z{9 ztnJhyU=Mt`SiQkiA7g#7QI->qf-H$Kq2qyYk2WwE>q`P5;(A7QW(X6~&;ivP(85VQ z+NsY5L-E4~u4){+q{d^ZWTY#%Cec)SG_fADTE$rCFqmv2+=I1<#p4OKq(QJ+dPtdQ5Ha8;bV{LbZ`U*9gPuE}iWoLYzOYZP${hzX0WoKOm&D zzXBnh_cr44VcPMf36cdlp@On=j5_Bbq_aRv%V;MBuAd&iw4jo0+HfwHSRW^T$gxzo zPfLZl-05sqOM6ge~y9#&(6Ls$zmVVJ5(EsdSlqG~v< zQ@Wao%LlX2CX^2Gp{&3Xi4>mP62Hb&gPm{;*azz3GS2P@q zxcx47*w@?Z^)?K|dXnMfa04-;NCUR8fzCy|&zVdKt1S-URP^~BbWHq)coE@EKzvb` zz&f`#4vX+}v>~1Z`blcx6ljTa4TJgWGKpv|R|1hgu5M1Ek+32`-bIo8vwXx+@bT@uXp8sj%@N(YosH!9gKSrNK8^_R?gkKwY z{5wLTomUYOz4D#z0g=;2A4xZ#mpZ5iMkXYD4MwKRVV>aPI5U)!!ysyXU9AXduL_OO zfspohmJ#|85?wDwNO+%Ogq;Yf-3FsPijZg^jgaO!7$d#2PwkCq15vfR`yld(hTcU; zXEqbx7RTP?%Ij39vPn%11L6rin{)#D)m3U&f{^H{yH4d?K;>mYlo4-D74hj?ZH}+UhpZ+P@CzKp%Ch^*rLb|k1MBmEanV|7k^4W z|J?9Bu{hXfD71uyV@DjPH;uvOGn6Gv=1X3TBXuo^@kNWqE&}N!*n!h@*+shC7^IhC zJsHD9({ZZX5o*2O5G)J(hh}ZMZgND!v8K>bx%3eu9mOa(>NMKo+mG8!WR?sLEQp5D zVkW)=mRL<~x0;BPDDG}cCc9Op!wJ;}ZIc|B4QR-uk_`~2;emz-biPDGQtKl{aG1$g zW`#NcJivSaF1W_jsYlugXaKkXaFGcffEVBcGJgsU0!+-lFai1xNe0niwi-(@#t3h>p(pJ z%havz1kR_TPp(_V;!Jf`d!RG>0czYapp?^EZ_RqIvND03ks*^^(#?Fp(x z1Fa|{e$VSrfAncvv()2KqX{hq@sd`_zXGZvp#x3xY%_G1z%{Z{hWVgZO+fLEcIE0A#shTnlo3rNNAq&# z1Q^7qIYBb1i6(;&(L6k3^kV?R0svGy&?46=Mzy%{<`dZq@ToA}awr^uUx$>&$Up)r ztI-Nw5#^zl&P_NKj^-~RXH-(#9!mj#-4tmfD%E)))I2Q)i-ZJ|9vfwb#2SrUpsTy8 zbj=ut=*a3uSD4>I0H^Uqh@T8-0en&WVKLg$ht~1Srty6f$I|!`jKOhVia60w3*d|5 z-iEgHIT5(8m^2pmc8o)3jbIsIIiLfu2EgakiTGN;I=~m5(|WX}&xx3mI(h7zzJfG; zHURkC&Om%U{3pGnK!U2dcjYS7CA$W+2`^z%9m@283-eMzxCiB=mo9@9o{U9u1O*QN>RrnLi$u{EUbPCl}>>TiUuHP!^-RanGcxP_^Y4~%hI zW7}guwr5y9M@uF`YGJu91E2?^Hu0F;vI^BGBl{$OuYhL?o|K4&{QN!Lh_tb9tJ$`p zVt)B1lpFho5-=cRVfDQ-60O*nPV5Wc6|~t5pnV|N0?@Z zCerP++;EdcS7T{43{4&~IO1r<^3T;-=!5(8sG2@WR*f&?nE7_0zP^@AhBsj)^XJ-) zveEU-H6PPHj5>>>QBrF%1FI7|I{TBI{R0L?spJn-p#nzL+o$My$Gr&6X!8InY zgw!T~9Y*nqN3Q{{*8y$-><1hG+yWSHukJ%Xbbh~!knld5ZFvvMNY~(XxJ~GOw_%w||<=cwDyLq9x&Uq+dUZ!u`OF z?EEo&59ue7PyPaf;?@3es$cC*BnQK3;ULN;=9Q&leep27e6ZJ9g^y8AYr)%s*TSAB zt(W%zJG;`sd`Y|xG=v10#4xcb+@;}W%Bgnh${iFsMlq?7>SA>3hVtKn_M|VbGyD&l z?qV0R$fx~ga|FSR@&#k0<(No2L(|CU{p&cfHH$fyJfUTk*bF zTx%FiM9CN0QRNVb4}Aqde3!2~NtUfhlguI9w^5lPZ) zCAs+_LgF3c>Kd*MRd7txQXvo!i9+8>QZtEZ3ADLyAfekS<| z16YT#^vCQEa2945#dH;h7!7kX;r@cQ0Hmv5L64@Rn!y4yqpQk$!5H5$@V^5gX$C~PhXBW;)1Hm?^db52W!o6<0rz}6I)AJr#!DHnc13gRnvlAl zF5T2Y+>tPauSZigGT6w*?ZizUE^csFpryfB(v*j5y)+n33VP#kB7<05&EO2@swTBT zFelW6*&L2K)fEXaCEWkPGpH`p@Eie2eE^*Ut8|UQDqtivrrY29HE-%RiW5n9PoAcQuIh~7=9GOi7C1<;)G~Hj|}nXZjMr;B;eA38 zlOUwH&{YzfHp9xPuA#RRsJf$$Pm{V#Y6HXM?IPbmkJcaF9wQupN?zNPQ(c=-;rPe( zW12N)Gfd;XLeGGvf=v_uj@PL>G@AC1O3rPXR1zEp(vEdbx029s<9Lt}(PpX}G_@ZH zVQ{QWgr-kx53QXYG`h3ZD76%~u5?RRrw+2aR&<309ZW!srV_ZB#ju(Ruajzwk|4*r zvtPye0I?sZxZtYDI3FGE?hGH@&RM#Tj?NG10cil|Eg8hO19kv*0_d!A?*<5xB(W9+ z<5o`6d44QD1aeF#66yfa7cJYECM*FuNW@{Z;odvHk5=?ii7`H{kNjq=pZ?5XI6k(I ziw!&s_wd?8g3)>I6!p^+l{hGYHUR&HaeVqfqil26pXR(1?SnK|V@)z(1SF?;`U)cr zWdKuS2Ac=BoH%Jwh<4CKZm(6B)7|*#1EJ5tY6l*+jrM(q(f1MB%P150EcAtb^x2%( z=bhv5e|lW{*G75`+`c$JbTgU8s^Em-p^!R+yJwsV^!Eq!OLrwFh873Go07w%yC}0A z0@+05DGZ|5Ms3vfL0jdVi# z0JSe&zP*5X<;%BoF$U)&Uq$>pKo)?p7cW`b+Sa~odB-WIt~hPws@12j>0G;R{f09( zZVLBAqFQfXe{Acvfx&oU=^&-s%OucTT?r4eqFt@ zfl&CI!=Bh`hJKfG!3%q94F8KRV(~@+{0MT64)uT*?@Pl-IeoG-Sd~X`uMwl^mS~nNBh)If_IktPUar1j$s`uXh1$t)#otxi1CZ$`TqnM zLOxpdKPHo$tJuaeb=A<3;S<)MB{82N#)c+@Vfao!zwD2ZHB6c;+fV%!Enzf6W>14MthZG-@)_r6T0(beW$+c0n+91Q1G z7zhIeR*Mh;QsHD$*F*=yL(b#m6>u&`wfe0oX!i7W5I&1Wr<=JfqB(bk@LrvR`5Q-Y z?#PDbX_6mxfD?%W9;h)gAV#vY-XV}*XEtCmU?s37fBoXlr5zoktE+PYlpjKU(usJx zZzE2=izT|J2jtMpbd?&X?$e-P#Ei~2C|AiLpO1CI_^9e8B~gDa$KwTU>4Cm3uR9DX46Ac&AG2z~(q zvy9BzemE&`aLApFmquLr=}bJ;+TdV!SvixFDOR|YA>9>vc_t2Vh{Hiv9-Um6E5eOv zLvIc^eqTeJ_Kv4%Un>B|xmU-OTkin}l3N!7XpcCTp}DKzaJ{``-aCbEm+qe(L`e0v zBP9K;jdKWXXO3~Ol?Pigc+rC&jOMqc{hxb%OHOaFCT`lE4a1sj#$U(vX9nUVg#T2sSCENt;O1oEAI3qUqM zPhW$0OJ4eF#PiGP9OtKR7?=L;xb!!WCVtBMzW{NfL!NFyoc4>SmmsbJs2=$S$$q#H z(2cu1Tr7a7hhXwYj&r$mvE+8bL~qhP=y~Eu*z48Lq!FpzqiL&8 zzfEZtq~4%ZNSblkBxdOpf-Hza!J1zlTC&-;t@^kcXP_j!ZY$RrVH6zkA1o z4W``-b%dVA3k8i1CP!6-)IWc}jkjV971kQZy9MQRCf5QF;%|ly1X7Ll!nY5X9@}%Z zKatp`dnLnq4t;F_zI&goMvE2x)#^ zY(CL3(@k`j&X_&B31#hYn$wekHsN-ynh8?^v~V@YSSuAGFcT9AOEv zlxjIX#I!Kq*-bjF@jW^UA96ZTZv*OKONG{;t|NywkH!a3M*F}+{Y>lE4u``nFgzdPU#x|=*MkK5z%cs)K(qsQ+Fc!HiL zugmN9dc0n*&)ewrdjsB}x5?-7xqTj=*XQ#!`ux6tFX(G(bTzsgJ&oQ*Ut?pVzcJ7l zY;5wo{BFO;@AdoqjefsB;1Bwn0xoi#yy+L2FG3XBl zg27-@6Hsiz@J;Bt3C)_26;46kGEbB)8ba#42&`dsk?L;ZbN(-E9`TO+HM^JEO9xU7JbUM#uMvAerI6-s?2j2p`W%`N37Y>^76qk-0ZXD^Um|COT zbhUIUySwqVl!f|plywN{S=%mzBwo))NKce5L`X-v2UinuV&1e9H594UUsurjlLpWY zmW{f_sQYt-bmX2uNL$7GAUwt}1ct;Y4j-UCT_^DRN1RhITdp?MEYe9)26A<1b3|Rk zOVlugFQ=u+>SU)|Sc`U~3KO}Kn}espDVRzum_<<*@gpP_3;qD1O|qK`q+-P(mP!*$ z6H6zFlcjR0qNvI=%{oJ@7Pl(fq`T#NrH7?wrRSv=3SYGSR(eT#MLcMFU3y#jlk|>y zNclkeQ2t0PoZGx;<*LhWxZ%6I_Fnm|o9=t?>-U;1Hvhs!>;LxL^U9djr+fM;Fepz|KP)qnF|UhPYa#g zcKXe?{Q5UHS}LZ_n!V`cxBvX^;m4m))RShY^+z8*q7+(I zcLcmeUW?OeADOe})cMwVWmD%=O)9so!~m^D6&AaBxpl5BQ?O*wJaeCnjEqz zySHcha;rUi^I5an3hd^hi6OJyU#FC3f3hIjS-9M0Z*QwyZtX1Uu-LQjx7(-5%Q^yb zvDI#Fve-xb6_#eXYQ0$EDLVhU-b_LEv5QtjiuSl1WtZK)d)fY<>~6BmQ?{7r*xT(j zrir`n*{q$aG+9bph$OE5z`EzR^K3W0J>o49r&V3yD%(s&vd!YSqI;Pwy&(I( zJ!Ku5+Ji73uua))fo0KVM)Lm35{{pEOcjr$h_a+p~8Crxn#HHj7kj&R%=|%Sx$S zB=1nV@C#yMNud(N&%XH<{?e8~?#)E+XGOI=mtS}NQ_nnG zR9YKqZtK{vdCOV&3cw|o--afSKlR$1ZyhQsZR?0?***6?{K%s(zI5m>=YMVQftw$B z^zmOl`^xW^U-Q%7Jn_u49V=ID*mzdgg_mA-?~fjM^k1L&|39A`&JLda z>TAU_;)$whUFUxFuDj2B=;5;R=`-4wtz1R)|JCz;`1lJi{^8JH{+>)-md<=@j-g!5k2rN8#N!z~5JNq(EKK0!5 zFaPfCPYw%e*R0*IDZ5*(m5RA^Z)&&K;cqL6vb&&8nQAVOvv;DX!F`&XJz!lR zm&gk&LF;_e?!%>1tPQ30^30N%CE1IW-B(u>OupbMQ-i4)ls3hdePmX;F#Fr7g{JIb zQ}(sOzg{l~Y$IDHWq)kVK5ep3X_oEgptT*pewQwoE^ksc+Op?Qsj`>ZRw&tv%y%3p zELYt7m62EG;5Y9~*_$0Bf47M0JTpo!QL>N7m2yeZr}A)vm$P5802`mu>R?}4S`D6E z1K{Z`h?D#ny`$9cF^}kX)Y~i!p)ZmJWVRLnwq$nY@_LXy2|y+{y{gGqi|$80GcR&> z2E6yNXjf*aE1h6wNf#8Fv8vP$(MFIVTNPEf+|(_cIq^neLb*DlQ0<=aZrzRZ=eg9n z#LWlmq+7e|r+?J#6h2Y?HyrNv|3iG;FWP4Ys*7F^+)*5EYM8RW$yF6z{3@jY&;~M zw@Da+N2DG)kc5BY@&eanhp2(RBuP|G5~o*gE(qCd;uJ-+K@^(i%L}aYrif|)4HPTr z$YPhKi6LsPSW(3;O~sFmH9=-766jW(F3Dm6rA??HPLj$XQBfOxTSbd(m!{)y>5BM8 zOPg4O?$IlLK2cOGQUU9gP{N=RrK_YS;KxUqCN38hTo{X@RXkmkEQQt{QL+_SI;2YU zCyIe$5hI!k#A=(^tB7VGB~?g@>`?Ic(QS9733fa(NGRqCFvSjED|j=pe#Mn zB8b13Dae$cME3F4gH5r>~5^SRMckDbULfnV(6cK;7%*=MmEXhtRxM0D8h-=D# z3VJ!mi~+Fbpf(yx6tKHKlZj%YxkLbq5yX>~(~&PYrE=)!cou-a0BxDBTqO$u#bXtV z#WIsvf?i8mkEW=2BibxfumYAri_o1tq<;;Ot_(JgdFQ+Ty?@Sm&pCMmL8GMOe&1nkPi|ovdub9aZL@O;gj?rIgY~uhz5v z@{;kxTde1phlbwyZP~A$n*J2+h@u_skXmJ=RK)+w+&o&nS9!hudKHabrJ6crX@2>% zP13J`F)yprqq)O|)a@R{Q{ed6cxck}XU%qS4ML+p!Z)EKtdC#tw(vv;s zU;3(-?B4b2$cD18Yv&LA;153gIYVXEl!-67VDC#W9QxGQx$7mbdYO-=bJ?qQ?RoY2 zFTL=Bi!PzR%jrKS&1RG&%#;lNSF}SG!Gue~sffQ`P#` zYGr)af0_NAvA;>$%J{Fzf1YZ2(m3um;y6m;G;2|L`QL1Vs`)QTT4hoat4*>}dppaL zPP@}dvW8j>|0yS3pfYkbN%Su9-dvqUSrTP2<@LXLYLA-yG~#GhrCV{s|I-WQ{YCZ7 z(yD&xbjk-hIj1qt#Vy{_q;Z^J5-0IoN}+}0*MJAD)K=2M3F=Usr0LTb+okbu@~^?C z)y-$5SI5Qj@`0#xbyR%uXYaD#NA%nJ&3LZ)@?9@~#hzCuQRkvdUb**zOZHwQw0le3 z+I7XQm%fU}_r(pH;OMsa_=_&tyKB!+UhvXgLWWmf^omQ)-yHpyxP8IJ7r)}AD)wLF z*`2$pw*zr^&#uc}b#{)M|Pxb*y&T=2?WX)^OmNgNe%=QrZJ;+zo?4FkH@Sor7U%OJ+p$(BU_ANp5hd9;{BMY6je=W)?_{)r2-pitJBnQ1m# zQAc0rw#~QmsF+)tYw=!WOLI;AwwEZFZBdS_`%8(Ex=VA7kR_X*CDo_7LFu-aUQE_( zn@{<)W@*l9qoLSqts0suv##AXACqx6Ma34t?(Y4y$Kmp!RVA+QP`18r7kU zV!{B|40_!SV_vs3x6)WAR0O1k#+ZeHI6Na8$44OnSI1ZV%c#hA_mh*NJOSo;+`TRt z%we9K619u?GRpBZQ}oXSE^&7`A#+!mujOem>GtE|gfr)rx=usr#XPOGr&Qn9^s{Xl zh>^$q6mc&T2Gd4#{8xy9$I%rRM zx-kK_iW7JFcUr953sHup2?FA|{7mkpHO zHouAv$CvfOlq&t%{h8wAGpTo`_=YN-Tg{-Dm2NMdJ08G$6N^hcT}R!gM2l1~9~T+l zk0UFOGdxm?Z|i$A9V+@}d|5xCx-;hIXs=K0R$Z4o#erB=JzE_-5R>Z!wM`%AW;%mq zq`K~o`>PgUC<(Jt7JD5%sLIVRke&}ZmTQDGmGzQ2HFZkVEB-c8^=0u>>ZModEnkZ@9U_FeEJ=u_1V09*O&H;d-lTk|j6v^7Xg}m==@A;vU5plSkuTi@I2EV|8gRY#ctm9}aa{a=S=6y*bLP z9l(2Sz$gIyj9!wmo>|dJn0%p>5Q7Av)$uvQU7fc?M>|aRd_9)Fw&J#e^n9n=G2Q4ltU}Y5X@H+xj@ra9enRnN83|6S|aKs{B2Id-wJz+ zQ1^?J^WK2z_rxN2wzOH4CAByMj7y8hqNQAvp2tUdG%muF^4PM}JlU9B3`6SXGXhn_ zT8VlyqJxqe^e&p08ca*V8jIw@u>kLU)R|k=$(vhWvn=_Sb+7FH`XI|XGBQ~_3U$x| zhx%-yFR^ARE$)rQAn8WgQTqP%S`W$psOq43I(zZlg7Q&_zh5tl8ktcxswN#W#NRp* zYHx(<)`^iS5hyIPFj2q}@qrA{NL$~jp1)Q9yb<;NQ6nP)-=K2_ga%jkL?&50@XFU3UB9qDTbv;LZ?d=|TVN*&3&6Ye>2@*wW`5kJbOS$3 z!^!*r_eFkIZ%T81W;Q14_yMGA`9a=TlQ$)s7&S+lm}Tm1YS+^l)hiLH<8#alP*Gd6 zl2c~rh0CS!w)vU71+u{p16I%K7p$JwFHo8*VMNgqlNrArH5GIAXqXHsD~^ZS8s~U0 zUMfi$>m^M3!_J^oSfd6>lZ!jxdE3{R)=Rp`1Z|AU>Q_BikIMW=yeYbjM}TuNYaY_x z$vYB_)qTW(ZP5I@yJrt8DGV+x9#n;Do0;naN3SZmxDTMubMUP;SOIxWfI&&)T}}|Q z;4cl6Yn&D~;Qsw4ven(-LMz}xBX6uvPTQ2ORo3VA+WI*gionmgtklN(^gOsKZ#!U! z{Om-eB=JJ9L_(U$ZQX0gsD@@7K_bm=lq1xwPuIFMXox}Yg&0xZ5n@D+7?D7~jyK@4 zmLKqAO>RPK)7O`#!$eo>sOlABv;tzF9C=%yS0+geNmJlVjdcn!0CPhko{hVLqmNr~ zq$ff{gJa}y^h#9@3_)D|?CNCB#nlbF`%T1AA#Q&@hh0lrZNlbzGf1oZ<5F4$GV_Nl zttMNvpf^Ji={`@-3G(W^3zPJHKFF)5B5S7w(j}<9p_EsRo=VKrT4?awYYZ6s`u;3J zfk~_q3M8>Y2^%J2CN;StA8STlk1@m`rU`ThrDV8dZaEBmIQXM%j7)j zvgBqFb$XIz$K*U@9Lafc3PKvVd=ilH5|hr(oJT9-d6u=-l5jg$n+))~6hj6=UO5FL zLf$Mwo`0GI`IcjXW0*r8%h_O>D#+V#Bw$Y%<;*}x`3f@LuS-H{BB6-4heoSMmlrzYA-&SrKb;YkFM4z?8uL?Q@$2dOPiQW#UUv>#WwMs&`&c=6B3}4bJTZGGN$XYI-T-DcGLSIK@I}ud$eORF_ z(ZJNMt1-29#nhGqQ=`nq(gX}2N(?bMv$kT(F0|p8CQ_73JC>*j3Ycof@+(oGtiYBk zW3Z*l7;H=w&fqszMtuatFc@Rh&eIK+uo_aukS@l2GLQTro>0k6lcigDkk*I^j%C*p z@p;!OB{N*XXX@U|4|O1E8DE#$CIH`Sd>dqjOti(kg*Ao+=ki1K;5wR&(oZM5#H{&B z%+gY1G0fD3p&9W!8(ctynL@H}KBH!l)E1M|DdIAbHQ$dHlXlL78i*I0=p$OH=6b6r2t3qUn5Ia;k=f7HrY2Gm!p$eJyB^Gtc~p&<0u$Z5S_s9c z^(awIDk4}0+A!D(a9W%i&cTh0l~M6n4CFry2J#<{+CYBy7|h5om{rfG zuV5uVs>qc<){3@NRM=uv-14&rB7T2dik6{War-+CV0Y;LJGW+=@jEj!SXnL`F9(he z%BdzZnnP2*10Zxg@EC^B{zHP$a5+J!D)$6H=!W#*1gaEqnf{}@-(vXG*uBt-XY^kr z6GL(TZQxL+`v&HJ#_c_Su9OBTSc4rq_=%qzWwOz48B}81$Prs`S-p~mC`eWeD$$mW zP_wMW_v}OpZG+EZQXv8F@o^`Z9#EDrPW72b~RLunCb^3o+!0BuS0dSCe`(m)IalnUB<-3?6#Y*rj6Xn|8gikYPXby>dkBs)NF* zO1OrEuR{hG!jS{JLiJI6JSm>0&48qMR@qH5e1G&kKNPPK@wbu^;u|V_W-7&CrZ64|HeeB6gPk9%XYf9l$>&Y)lV*knFN-&$0J6Dke_AO# zpA1%`%AVAl7nf|RwoFeNJQL)*9l0A9WZVS=$}ar3J&Ck9xzyimA0t!Oo9dpehvwE~ zQz{e3EO?J`PHhOmWS-Z&3+b%Vc~+~wg?$MrW6ZG7UC>D|^I`yJcx;g5>`EuTsBuxi z14PJfra^|p<7(MrDP+DShTEzfxisXd4rRqXIRJU4$#`~gR(4h($ z7%I)op2FJv5%twpoH{rk`YyPgiOOEN!{A_G-8Gb+hsyY?`%gedgs!zNZ-s~ndfukM z6=)DM%ZO|?=%VR`P9qi%-Va&;W}y?d8U~$kllX*AuutZlFv)0ICuG7kwqlc-m_BWC zmXTxI(PXE`kg#*@6V9c;mlD#6f3j0&I;BzhJru>otJPSy8LO zWu>7Sj)Eb$6_cyUmMELAHAV`>d7~G6#JwZ z17til?u%Xvv{KrGnlup3r>d=9f!6#`1wla?RB)7+{Ead~9P*Ms zNba!Yk1gqS^~h`oHRZ)8VTf=ZbQ+@ANN=r%C!X{^c37nMAi2Xl97|*1L+=|kb=mS? z(HJ;fP6m~#oas5T)R-mZsF<*i1x0^%jiSH%=uz}PJU%(@>MZz2kLa_~CI&jKX|%;> z<(uHrV2dA=BWbaSzw-28HwJYyLzU@u(N6BGoQ9EB*X50^?M*n1m=03<2unc;d*@r@ z-Wo||q^y?riu_;X(;^?^%<^zqLgbNP6Z$Y)X;Z zQH?=S+nRX>sYN|={KKhN)nr??9?vIjh#?;0n2Kb}|0P^@wnn|D@&6O}{~F#G7}>IR z;ZRv5OVV1Yc)9vD?XNU|4W?}B>DfWmw7yEFcW-CDoNRhEN)B@SV!x>okeUYtG3ll9 z_ecfg%$w5ZUO(-394?*UK!Ue$W)37Nz0S;qPeQz@1!b*Le$9!*^WbxmnVRaaco$Oj zGk}z82+*H1hZ6ZSp!H$jP^RJmwnm%XQ`P13`l(clR^?1W6)&!QNy@BlF*wiFkR~-~ zN4?oytr{-%)RvyFWH*_gD|)oDnCz8}qRi`6T0R+R=dG zL7V{EvexgF{@RxQniOBR4R+;`C0{-Zed=GIyjXe68k7_dm3h<;(B{gG_zLi3HJ!#2 zZERLAtUCTy1w0pZ?bS!9oo5NUeZ2~{tT)SUq_pVY%VU%$t4MAnKV?&m6A9Fd4!l@| zt*nm~@PkS8BXn0~(;b@Pd5fkv%^f~=ZU6^vPa8UWh%(mUJoY+Js7 zp8@7)8Jl1giCjDcZFAMIag`7k2)695&}TEmy)1CH^ae({K0UklG_q|D&a*VSS(HZ> znwfl!EHg9t)8unGqXyRS1K_};`~=JnS|uzXd1YeyFrNk+Q<%OI#oUl&+x}*0rW0k| z+TSm0Q+ILcf~6eiea0tqLl0)Tm~63QMfnN6r#pLE6r!#MK|@Q8e6<`0(%j5zASh+l zY77kyfdRa|mM)}_O%Jqf>=hb=mR;vw(}IcY6^}hO zK(rjgu{7~kRv)7ZKyJ8MR5r~V1S4Gw#0*1Uj+4y&V7jGgA6gxZhZw+1mq8Gtn6DoR z({9WuyBD%Fiek>XE#Ov^70=!+iL#h^K4x$;ac1m&D5;kS3rV{kvf@~7yG7tV^=vqc zvd{UkJ!Prn16yaW>NmIM*Po41SnZmz`L=$8d|sja`y0<^s_iSVP;<~nK}EjR5T5E8 z`~f6b!M>`=v;%lp`fEEfW+_b>EX>XiENQSL=${gwF*;Z+oQZ?|z}9cZukAG$ZK7$g z0aJlnt)av^uEg29v*=QNz)17a*4ge2;)iKFj5^h*d5`d4r_64v`$~Y&e31`46%uIR z8Z?v4280L`i`AG+r zT&(G;W>JHlhLyU$-{?18Pi>kegDUG9ryWaheN~UP1uKfc)363C#fS{F)AKD-9nAGz zR<`cwP(xZNBB_LWNnn&VG}1TV`-AB;=4jgA_qIQ;N7z+)&XlBgZ9au`Oh8K;yJfWt6Qu*Gc5w$UCajcrpJF{z*_qCW(H zK%>i=N;Px<5RvM>NK$IBZ3`R*s|lb8nxZ-|6&!5Es*f#bIQTQ>6H{Bp0IGU% zC6S0;d}0~8$~gjo>~!}C*dm~x!AWS=>UP(3;Q{5arBKspF#&NE!BK80zSJ>|gr#mO z*ZuPCRy8{%aoU;0M5h;mpz9MxN0jBfP04ni*aJS@^}WxTN>AB9EZ*wnN-ODJM>=uX0VOG3&z&wNMf*W)=rP0^HC;PHDa!jB47}sUrqr zxc5Di4}Ds^Miz6q0@~@mxWLybvcnqU&T`-@>rJ-QYtB}xIh$NFGaY-)Yz_oGB`x47 zX<*L5!ZS$Qa0x7uE<)Y=9=XHXx zJj{ydOq1|}cU6w#j(i))EhD|Y6@X~CAkU&8N^E?n7Vr~4$zYnL-6 zF;Sr)*KzSjTDiV3=ft;|ZP_Z!2k|fX1|VNBKl@CUuq|1LJ>H8dBTruouwxFrRJTR{ z&<3mpf?yHBiY1rUTWGm|IW5T1h$*UCXgrgW)`HvSrFlAunIC*1 z2Wr$Q$e|KL90F8|$wFLBGA7DKt8Fswcn z6fbpf{PF7mN4g?7EO%NA<$l7z@vXs?qr|xKHw7SH+mpHSzH7eQI!SP9CB_kCF2Z!ZOi=o_4 z7&zYjq#u!65HQl>f64B+0B)GFg5eO-xcnN@rZ~k|v23iE#O%7zrgy_!I2Ei@w}N{NQG7RJQM zs)b>3a;Sx;1WKkUtok>VTKOTZ`k~xU*s8y-o6NX-l!C^LnNOWlkNtI>snnq0=F*-H z&73OpW8IvJc?)A=FsWh@Yhv~4H3t&Hdi;LeW{A^*2aX{!M$_!57S?OB9o2k(T5^48 zn)S7%*n%|ElX{wBp_P-#Jgv!^h!wFVUzH?85qXZxM#Ob6i>hKFvU5#Hx!w%6-35@ODjm6$IJg)NymStMcz-fjHSMSG`LP|?Yq2{6Gf zV>aZ_iTQcC60vXAv11~mCSjR7hi$=}U=j3ZzyR5E&z$e(3un)-RzR178H3BY95Dv3 zvT4*bPnyPgN2$Tz+We)NZNF9lcN}77qP1w53+~@B{{2jiX>?kcN%IkLsGKq1aMbe2 z8-mwrsloHN$;df6U50uSkVj}TKp?yWWkqOc`egwxJ|J5&QTxF9r_ViO0E!AJ%lQtS z9zp{%>W`xVN3t->dHwHI{ffCmOdL=7Wu;`Kuqy4W&15UKqCH{Rvp^MY;UWNHm0;!t ztI{Lfz>;ebZjz!3Q$rh$tBRw-<9Zg)rer6Nn1FX^jhoAg7q)O9wZi2Bq!KN<4yMv1 z?`%phvNO~Jb-9ePV+Lr?RJXC0BeK3=JWWL) zDOX0=5w_GK>=+rc$ZuHXtx*j6^o{5{mC;?{aduG&4o?~-F+q0toGQpom{W}J>lL8K z$cmG%Gvq~N(Ru`_31igBL?JNQguq2WhO#OH(WI&-03BLmXk1PRt?i&u4y9coGnUsM z(A)M22KPCu=lEqi>!Fe9vS~f{eY-%LE7C0d~qS=$(Q#c{*{tsY#OKnP9uC%kSsk~Yp3^`#tuC4 zCe4$eUThbOwD3~ea)L#oFNy$V{4i{HaWjM&+G};n<@w5>19%Y1vCv5t#A`)*<%9}c zp~XO`d?)rpvq{4@{RSF~aIf_E7^YC=n^w=+(3q?;J^nag`E0a%;a_Gk7jGQozecRf z0Meqrd!ds=<$nzm89~3&p-kKL0*0Lt31C29KP;*kdy88GAU|>ifY}Nn>QE zL=hF3rzB=}0m#tSwqS&|?z`rWBJR7!qR@$JTtOP4MiU~-X#Vg)j}57rL{U-De!#Dd zN1d-oikmH(A51iC&R{6PrXW^mo&~{)MvaKX6Xyb#!ZfiFKF5y9fD(ARQPYsWOr6rp zZM|S8l>bxG%Pv}Fk?LJf<0auw^?Aba7o2l3;NVBt5N&OGgSnq3P_?QjSCvNf{aAUa z=85Y2;TPp(9ff1#66Bk%x&S44`D@}}o(C1NQrD}X%;m7uHoh(Tk<*KuHE4%^T5~Nw zldTP5)(ebWQW$p>$X~b;&&(A!Oo+M_cVAPv@%ao&2vczr8>z{P4nDbZQsSWcL_ExXCaAt!KTi$r<4D=5&kM#JGhGRUzGJLKp9olG*0!~g5g`I1TDM&ccFceM8>u{*up+^bS z*q1g&FH_U|Vvk8brH8hdto*CW;yUn972iu~fE9Ryfz7AD{YFagw*c&l;75?az^%1q zn+&$f5XQr9zU`)(lH<-0$f{PW0?B&NzSdwrKkSXFt`UC#s}-22KOA+aA=Aqw>VpZ6 zYa$;)g8g#1@9e7#yH(T^BU`19ZDP|X@+FsI*0?}BBf3I`)p`?IQBK9MfUY<{u>cf3 zum{;r+=QZpJ8^&hL=-EfhHES~uDxdVD$l=xPX`l3EDX8cYpN9Af)FagX=(BVZgv*c zg<6;Pk~O8bOK_r)pRG{HJ#Gbt0gDnCEWF_B(>RlSTfu;JEN}IG$|@;olUDJ7L*Qdv zk8vU*LEM%Mb)CQknjV{0?}WRrFuRt6mFR#I2*N6KqHVxk?I(p|_jCcOZ1LgZ7rSel zUz!lpNLP&@T9{t#{nXspR2jK*6OGRn2@4LG&^AOe4emzel=%dn1TSE2!=&laV^s$} z03VVMi!fC|N^DLSeHKm%BrV*q;sSx6C2%Ta2z$J`ZJs<9!c{Y4Dt|Q>KcB?OkOMSe zvbj*89q*&fw4Xj~Wx^!155ylV#>-r`CpWmbOT16%3UWQkIfZy_KO%A&IjO*BIS(*@|pWBCh=H4AQV2Z{C8DK|zK@ zc9ZVgjHVe~Q$Q!le)yQ##1UPaTx&gWm9A5vfX?~_Hv^VXcd7f+%G{lg{g^`2DsC_J zIf)eYGeY`uxP-G&x(_Pn6;bqz?CPAK?yL0PMhHX?CG%2ovLab!mdX*UJ|qDS*)Y<# zQqlf+(NJV}(L75{vt*jx%dK)9E>VklvQ+v*ygtRxNm;yE8!C*~JG{Gy|4pbOnNY=h z9*us;I-V2>2gBL`0thz%1bvqcra^?DWRADBcJc+Wta$YL15x*HVP2SIFs56~Wu8>_ zMs`!Dq z=nKE{nZLR1cdz?e_sVEViIZt?HKEoGs{DiV^{gLZ?M9@v*ivIDwO$ycl_or{N^ox?;}{BcJtXQssD?NB$iy)eHa0txbyE0 zU^`2$7DKop=1dV~_nkd&H;erv=(K@6Yj$^kMzDbj36xY$n{Bn(pQ&2y&yqCODBA2; zqh(7~*>kVxzR3mIp~-5e5rs??(TjjVLilBzfOvhEqS74!BXKe zE?qIA$2pyj(lNd_laa59>9h?DBPU4}E^hc4hP?@F`q8E0ZawdRJ-GA*$#E?OeU4aO zRUl|y`x5%8oDj=de`XWFY)LV&)K)%&G+D$Ts)ZeI8e8!NjqIWaXuv7aT=!?(5s#GU z85^S+SQ~Zdq@)SSmJ?x%WO#5zGT?wFVTr}3GU{1@vP-&Sgve@iDfjBEOW+S(5`Ngd zXH!aS7%L&sK^3T+K-Kwh!r7?;Q*U+ST(YHL&(Q9i({e7p^799@MZu+WO{PNU6ww~H zvk6#TQI~pl>6K6-gw0gBQKC6dD$pXOLIV3RKo4dnpk&=W!br|kvPhUt8d`g?IaZ1m z;M6F4EHf?1C@Zjia0XsK5I$K;y7%UpsM83a7-eypENv>&vF_A3!Un&`;t`6oqiWVVnY?UM~3 zYc6Z}-fItN-{{Sf_rz7SWDcdu=ZYuPYokYs!vS#}3`!(^jtVu8wDn=RNwm5*(Z|3G zeKs^pJPmlE<=1Q!|JjN#?LTd0bmC5R*|)p&O$Q(6#3xlH8Ze{QvI3HG-8Wcdf2H`c z(xrkFt(FxzbvV}ocvP*!SnOD?1pd(P-%&$z^cxtd8Af!Zl`$%1%YFB^nM9Q7xZ zt%OHDOL&wNP`ptUrroz7(ulp-{SISn-OxU;b&!h)2GtVdh7V9>jI9gyrJSA(!5Fcj z4r5*!h+xd&Wniq&24jgAK~AS-QYEJk!FYwD2IJwPs)yEg7qfz~PD{@MCsi&79DWfl zmWo>il39bXWeG4AeAtIBt1wXOyFfi!MF6#Z4s!Z(Y6s^B>|pYfb3IsGD^7IduP!n+ zWvE&FhWhn%F$`%z41l2oS}v+jwu+WZr|KzSG<>`!A|XS-{Oz_s)q$isRqgtHH71!O zE7dLBHDRK8!FkWW` zZMLZbwC@nKpLwjI{lN5L+&4aqLxT3h)2n=Nr7GRke~i*THoeM6R;uz31!ocO#gcgY zl?B%)Qzvj)kzvqU6mlZbfe{8GBD$NWK=-(6%fiHZR< zx*xQA$l5(DU)*?cxAVd5|#Y_PNp%gM6?^OqezEpDHV zGW*9-rowz?BVhjWK2Y(vCk^wbFp4{;595yUVXQ>D{KCL{rq_93yv__nCarVr%4@qw%a9Fw0Po?hjHD^)p(iz-mVW7F$=WW3Hgyhmhdd<{lY*qk0o zVMli)1%u59o^z`7UiFcsW8|oG4@tO2^xJR&_K6V4mOLsEc={EfCj?j3@RA{MsNifX5P0h`)tW5M#gA(B+(edXH4Xh=WDqc|l&@HOgY4wv~ULGF>WcBcEzSeoO z!@sWtiz(d>7THnE_iZv*yIsL@8b-oY#p<|A0{ zw(G0qV7nbcr^?A}z_ed;ohU$^AsH$kjwn;@ked|nW>uK_+IV%j7!OmdD7n~xJq<|K zTdn>ljbu%M&tWZ0=`aI`zkLiqP6V~xarh#@fC}DhGiiW%a;V@qXyiv8+a}ADf$W$r z%N#1vsvHV91%ak}IKC?1It~ROgoT1nia*n0Zm#a#CVZ=MMXDIYw*ERKAf zQ$&}thV>j#0FB5T9a@SkZwsEMGQ*s=rmJI%FB(Z>noM~K4Ys7aSTm(gPU%V~A9arq zK3n&#!oMyngc-hCt)mUad`D-WQsb~+S#Y>;osOC<;5-rYeOFQ~nWx!5#qOkz(*xV; zrMhXpeFgq!>Qge}Se6v#hlXRB%e#2_;aDaEy?Kmg?lCG837%Fml2jg}h*;el5}hpI zjgGxYOY^2!!&`f6@m3jrhn1uY-kQ@Wq<5GHNcS$-Mgm$}L$=BU!;7Z@<%h zql}JX3*jWsI^e>#}fi#inGP)?8+%(YSO# zUdcKcpBeqnAShLWT9!evOi<%%y>a48Xcej$6zocV?#5|b=lm_y~Fa zD5D_*6bUpqbw7y?(Ith80eU!dsrww&nd6B=5*hPQo_jIytm}6`q0n_cIq@1JqvS=sFW}vSe^>1mxassqJ+n@7j>3;1NpDkXG9YSq!rm4$S6KopW z!OxVw+u=(U%~hS9g0c*x^m=Sg&e~C`mWKzYtq|WQ(Mi8l)&(l~KSUmPgyXT^k)3_{ zi1!DU`ZxDM9A#dM8Gp z^Jm-UxrRsQN@E-><8Ux6#L1EFsyI0~zs+|=YXVr%O3Nw+G1E)1tR6mCri;z|(*$UQ zX*mcqG`r)_u=95_20@Pce_j#YQjYq!wh@uQ+o6U1*d~E{vbrv6mwxSqquEy7;W6{~^~ zs6i1tJz8t0_bgBWd7tN;PPn_=`mMIg ze14p&EekBwxI3ug7Fb!FN<#1w%TNcuHy~`{9D$EST)?F=k+-}Y6<5;RNOZ2;XeE8- z`$;{hpooVkV)q(Lvobo<$#4CJV z@Cn99iJ>OxM%<5KlHtWROyl4J;p7j-^kKu4E~T5;BpS_*eH1;K%p1kFkydIf|HY&% z+t4M8FqMO$?-($Djhr#_mxf12jwqsuw~^PR6c&sSlwobRVJ+xwRT!Zi4SGuOVhKGZ zczyQ@UK_+nj4hlw-~6r@yz0<6I)c|-61)`t?@onH4?o3)l-7k z!_m@Hf|m(-Pg%U4vUs_@v-~S7%y){#YtvJLmz_LuGgKWKM@R6wR)UxA_N>$^|zx_>_lrT>)R^_1ZC)K=qDTaBftHT?UXBzXP6Q-W7b@Tx=O=m=iFCc$gf zQ-ar_Z8ZjHJtcU3_X%EqE{j+1DZ%TzMew>aN#ZKzoBR3b?7|TR27Q42Qbnk#qJrWw zKxG#BnK7l_+C$h-2W0(aRm_Ue)>BDbZ^a)WZ+ns1P6_YCgEV6(OB_?nbId#$h_SNMdjV?@_#nBhE-Mg(xiAZw?GZYFP&px z3>@llSKRs6aV)puZn$bNXQbRW1q6yah<8e!KTUdscVwz@N{u>c6nBoZ^74yOHFBru zd@6V+hia7GliwuHy3F}<{Ct}$tDD#ES@=)#yPPS&hFs`=^fEFL$(3u`c$>Stcu;S2 zgtzA9)#v%buY9PLST&lbs-mou+u6u}+~q~e|w#dwf2*?uijlNnKKb({aEW(?I zVABx50>!qp;VJ|h{x>RE@32=+7Y}&NNvFrqtWvA1pYHxK0ZN`|1Oid@3b&@(3Abi| z=7WNW0BcfcLbE7n6D#)Fqmx6igU`>Il%RvE-~5h8JE3SKqn!W+Ll1C~Qx9nQ4~8c6 zshJe2Jf4}{TFncNN``n1nv-;R&~ypHPiG zZKm%VS`E`;bC|j=gwim?7q;8ClzKl+;D_2yJfWC(Sg}r)ot+#@h}P3hy~ez zXXyB{A5}ORN(mA9A2Ec2fAY9HxgSIK-2HuhvO(tp40{x0*BmtECy3XgKFPNaTwToV zJWkHr$%-;0$H`*(gGAjXg1b5U$KAIlu#OPY#N0`peOpa=zp40~9^2ROEN_Z^H|+K% zc|78l=)t{jrdEP`>-cVQpPt|UA)5IDGAF&4%d$p$K8}f=FO)yY9|N z80(D*&c4NO+ssgtmcff{#o@K6GYeXV!rJrrJynC0h^yt7nphBbe@~CBMC-<8KuB*+ zHJ3%}%oeR0^LaHN;UIq58ixIww=b*r(X70KVB!?|@dQns3icbscJ~13ikL+pDpspc zq)n!UqW-?!3uy#T5OdPC#C~8gUh?}*NYm9obUG<|;`Q?V?6q*0w~PWaA>pH~vkH~3 zX(bSRc(dA}Y0YBh$8;??=R&qdFW~5z1;gjHdu5-v@#pTDN8N!3!(P<`yomXh_i?x< z?!Cg}_nV47Wv??ofrE4BMJM9!DYBFJpwKIXMAogLR;fx3(cZhzhT2yP5s~=41%=ti z@7jlN7a0)&N4P7>LooF;D%|T>m$f($tF}85BXmMWG8E8eAKRkyYD#qurp=U6NeX0W z(yNl%q>QVd8+{*N5E`9inxlImA8k^h^{4UkIm5TjJb`cL8j^wPhD4-&pW_=(!o0Kj zwdEzH%rWmYmT!f4)WSItN4%2vE~Eo$xgor`OF)*YQj(zUdmoMVjKV5>3M>(utY7JCPR#HOV{jQOw_dU3Kv z|3o1fJP%r)U`ecgumszEyAzhDwLLoqvIZ#drhfSSi}Vu=tGbmKVYU-{06{mkjn!CR zb__(dfq;HEY_RNfdL;?VjwkN_VpeT#2Fs3K$2Pl??0gZaJzIeH#npUEdN}NH+qkT6 z#E@qAjF2B+dIG!8!pg&~t#8emMm1R9;(Sbqkq}$oW-C)8!y~el{-uSa?N$`=Q@?Ts zdR@2O*<5ns#OOM*X*hx{DYc5ZV4OlCQE}Uw4n&3YNX!G>|5v%Ih~Z5@raXPd9O=r7 zHb?^>Fp-;AL><4~Awyl&XAUki-((IE!wq+(t}0?b+GOf+FjpvQaWIFAs#03pU8Jt2 zXPh9r+?v##8)`8p%;Ya?D7vrXnUFcMc(cY8#V$h?77kP2rR|W&paxZhaG3TP4%J3J zLGM65@g-9KYvwt{YKKp5&HiJ*a~rqav|n^0xNlLzTZ9dV>2p~saIbK_d9iiVcz9^?&YuNs3>-l_7@+{!s5oh;}GdP70&OKnJ$q;!Cfi z=G7qL`XVIr+V*&uoZ8P-7Zn=A+xV--Dux;2r|au$3bT|AW;IFTh>lK5PF(i-&D z_0!61(fB2~Su-V6D6QxbHvOYev39Bo|40?W+H?@`)mEWhMyco#Q)=8W3C@Nds#$}_ zB$AcUBRtLk*JTj`ckFhDFuQhX6MgLx$+w(A?$H8Oi_uG)j8=1AQT;ddmg|ONr{Fr{ zSf$ztz3ajkj6TYOvJe7jD*6B^wGbaNiY)qS6Z7cBx$)eJeZYey^x1v7GKbKXuQn|U zSwkvt*KNhN+Q6lTz&WMF23lg!R!`)J&eCXD=&311wu~16@(l}AFr~=ZV?`u5_?m7M z4@xudylFmZ4*0Cw769lNpB2`L=&p#YBYaln=403kqNw<+5ZZ6h6Zu9QEpN~hXYiXj z;3E~E1&(|e=d;M@@L4YRQoQLEijMGE6%9oyKAR5sY-ka)>@c4tOSqXs3lqxht%e)) z#QQ2fTdShtvwR-#S=djV!}@c{XB&af&fJEAJekjG5jdZ9zFl2K1*@zWY^wqy?AU== zUPVPVrIZX-M{I|AtoKOkXG6tlSEzf8(=t1%md#eL2kF)9M2+IKYSoD*YMq>7L9?}_ z7Nh6r5;ha(P7Rzc+#^J>lGNGSJJs)etCKprU`=aJO`cj-%@uEYM~Sc%V+*;)f2=aQ zKrSDma(;na&w?BXS{y__D0CWL2BjKz#eg3nMg)Gu(|{ihWShW`tS7!{OM^q=h|%~Uj@T`D zW0S;83@Qx#$PT8zTCH0&qoFPAks62jk!A`w*Z2{2RB~1&z*w01gYqN0M9_}jTYH$O zCh;SqX%d|wl%i*7W$+_Qm94ExE%}js5kKM~a7jD@j31RkgRz5Ap&{_2N+ziDBbobX ztHzH4`=5O2AZtP)QKpp`=0~=Q;78@b_lb=R^CK(5ocb}MDg7GeM?PsKKgt3>dMJzA zG9m28!r)T6_+)Vt!5_8yUh8g?v`_=6T@^AcdL&<1M>yZ zUo0wIqr2LKeN^RcKl5-9A$}S*2<^Mm9O^&c*I*0?k>m*9bqzXXD5Jm$Jg-v$*$fEL zpxjU|nJZdSY^4Q@)7vJDOBa$I5MiNhd@2JcOsa-h04leRAB6R_{19n^7_*Ap+okMB z-+O@Ds;z4ZoFLx;#yTrePB&{xFasTZCkm#&$b!S7ekwnAyhAoEj@~NBN3l*lGMl=L zn|iEFn%YUiZcfq$o@B?og}v=mwpF`prdad>Y=LjxzZ~vIPK)b#nJcKVIlRJFI2o6f@(?9veEy=hE29MDgZkRRXp;aLvSP z@CEcKaA7X_B0{j=u7kfWj-16A4kSf`J06oXN@LbZTzu?p0JhbAL&_`;-3Pu(Uq7O+ zGkj%|1>?B5TPY3YXJ+fyC6gcIgSFnP9BKF8E1}FF(WUcW6Rz@f%N#koU$sqvxcjL6 zD&@gsRR=^~4$DE;OZ1`?#9lQ9qfu$?9HbIgD0&! zor~N6j`3FpaM?E9#0iubEt{=z_c6wkZAk?)ut2UBB%8m**Riv9zobcKmqUXTlx?Fy zdAff!$wh93cgo*cJr-}KOo^@iMU+zOWv5y>ZaZgM3Pk!Z7|NB`*vbWb@fO1Fy zQX)=bhifkuwE~>B+}5ZE0EMsmrCU%_9?&^2lOQ!meG;oAV3tEQjYy-xW)J7y?rfscy?G!8cJ3ShGJ5SmiIaqmklMY4EHosznxwq@uSa*90p#uiWb-%dNK5UL|!3D>x#o z^r~P5H>Jz0;7BcK=do+&D-<>De7Gnx=7r2*hh{JcTy9PFLW_A5l1Qpd;MlQv?W@hQ znBQtoLDR|A*IL0M=;ZcUI=K)^L$<9NF39NKXnftS$}o|g(DP#CvVQ`AA#Q%+SQ530f#m11(g7xa}(dy@rq##?sf!l(*>;!q)a}n9^WR z;s+2)_X1T=Q3fD1W0rx??MW@i*cFNz2#1TRQkq%eHr+1>gh%N%U63}xP;@pk24V!1 z6_@R`fNt3TGz~kepsRT~w4IHAR!`Y{V53ZB;KBoC{n}gjhE0RE!e?=C+b9hxx4XL! z83Z6t@Obm>k<})5Q;?-LOvx70R~@yG>!UUcxfLNrR3-_TU`6&!w30d;yoCJP?1MEX z%b`}T6aw2n#Ti9}Oyo@pMS06LSfNJjmg|9Oz*0v|0}Wf2Qso0a;fldNAYXjBhfgXft7lkmW&r%+ylxC85Af*kmM2Fn)S-hiUZ$ zS=#|wQQHHuYI97oYIhkqk(qS(%h&-GshQ`@u3_f6;Am#Duw%_&t3WPJ&bxH@!W44U z@xk3$N^#sWQ`G3~9l;(}5t@i9Xva3%hOWx6#+hK5{tb~QS+<5E?~jz2Zn+Z2w2d|z zcVILH3^l;3d)8S zIK@dQHT;|mSJ&?%#LJzTjKjB$!{aX3LeQ$fZd;UdeK`EuybktJ4sXQdPb;v0mkLw} zM82Ml=?czAOL)5fDbNbE(9-UkjviF+t3y?rbVes2$ZZ)w>+)1XbOJtH$y02ni*N@G zU?3D>K^$lRCLB#KIwDW0sLNB0-9=L@N{qh=i}D_Mi+MvfoTl+7`;LLPUeLT>^g;6I zNWoGAu+Nf5rC~rO{!j39CCb|x74lm{LVnYQ{EuQy{I1riDjna@14S~?wun&$+<>}o zO(eetX;e~@%$jD=XBR%%!bhyDs2!3lLB@6;)d&$u*oP7f_9>CtV3`B#gDaEjPD8Q| z(I8x(Sye+O!s0T`3^PTvg_V0Ro6@}$!=TEsKfq6DLI|!zWWT9Rcnfr_Y(d7NP&I_# z?TG9gbYE}V0FuoqQrko}{vNN-m zKZeoYZRw4{*QEUVO?_?3a*pw$G>o$=;ALNT^MMHYkgwQ8%CA}Z^~UNel(78TD8D|W zn&;|P`gd0Oqe1?r=ZEAm!2RT)uj5wwH^mi1$d>M2XvPWuMKV+h2!Hwc-+J$DAAI0H z0R$nek{^w1iH?0 zus&6uY#3QNfTSZ4oKIpov0hRzs(64A=_rR5%{8f3lhip3N5EB&bA4oe)NJu+a}hT8 z+SJ5QPBcCufDkB5cca|?b3E_k;H90}_)MB)S0`7o%ylHiHtI}XhK>qvNyj2D{al8t zepViL$fRJqr~UxF#A2f2(2qN;=D35b`PyV7nD~@q4Y*No;^75jXBgyO(TC!5hJmH_ zBJGM`v?$Ljh#06h2+Vdo0^Vl$O?R4q5ma1f6j)1y)~Yo*SJA#7?cw9o!3N9h^JhNLg{a9evea9_;RP zsnkrC?d9=wqVJSx-+D*YIMgj_E zW04$8wqtLhveTVzW>y6>3b{83l4dhBtHPX)PYr7nIzBbf zYCZUUXGrY*)|!!aX)|n3nvvU?k#;pBq69}gBP?}UeBUG51ysjuH3O(-ekiT3r;S2Y zwt~)(koK)5_Rf}K@9enr3jLX_#NOG*BlZr2G}cIjtZybx)jnRC9WW(LFOenmbQJ&pAyLG9!I5+=4qaHqL6lUG>iic z7;r2GjI3Fa1XFZi!F8Q2b)D@{ZQx-W!F0CiMMrctna^Ek!v=0D|(ND&W2g> zZjl5xD_WM)r_v0dfk^8^rM3l&y-bo}8^Jz@Z3GG0NExElC>d_=)?2%uxRL=byUDv! zGDs^e|TuO#DDD;6WsU@_GLTVX;G^iP#2D>un8BkFv_`6;G=-R zp@oV9Q}wqq{=Tmglj{^%yz`iE+J)x|o#3fRjI1-j}+YveydwIp(YN^?M zOQNGSO8BG_8cOipzTyv+&{Bd^pyE%;9MXYTqB9nIU_~+SD|PE;#VNlf=E*pvR1$GY z@6lkx{rv)kb4ttdc!X!EGu3W!rOTi_lG?_UeDt9Gm)J#IEaJsG1F_NftIcn9Vu@iV5|z;O*j{C%8$RtYRs_USyH*mtLz zO9yI-_DAiIu4Fx?%6uJy0jhKi@3l*3ab4M`h##45bd{L|bB!JUm8%E<1u+e;9J98_ zIL2_W1DDqVnw3O83#Tha84V5v2eD`%Mx)An>7s@&7#BBa@t$CxACGN%QDF zVDlPnmC;&wY`D$D6SOVjMaB^~ajW)-N8Z=iWZOHVkmIf9)!uE&F+rg|n2I2WQcaHa zF}8bWHP4!wGL?rp(+u61(ohLN(2D>)<{v>wLnyPOAIY^BnHDw4WaR)bl?%;amrz$j zNoHkG_~bGbyN(0Sg8pBeEE=~Cmn_<5*et+6iTLYT)dKjKJ{1@L zpBj3GoyoS|X|9g2QgD>T5)uDBI_~{=QBls6hmVTjeh@DX6`#~jh1+lVr308pc}zkY527O*OS{fVSt?^S~Y#vD*E0}Me?r;TIB*f@p(b4OVtm{U9BDQRsAs4rci z#sKQP#6+D_v!i0uDqKbu9GSd2tVfU%DQP5Sh>BW}R+B9%milc&M&H{mW8oZukbx9F zDr9_KWHdcGWVG6XHd^^tsyQ=y;DptX-=vO}DBqV=6zO-^<=D1cacZ7*B|y70wxc|Q zY~+!8a0Ct3ZbVo-dSvZJx<%@e8Q2^+B-@Tf>XEk)w}aOr2uv?(*6!-aygM3sYrBgO zP`6=Ql4`Pt2&5MAP$~jEJW>x?!fs7jHiYPLC#BJ{Rp4z+(6WS?j(|X?S@sdi<6bY0Gi(=>efAu}@RHr6QrIER~`&!LBo= zl?0+7Th!L%KlU59q52f8lR7Eh`aysY)N+98A>c)5CRS};x#r9RtqcxqX_t2+v!%>*cawLW*zoWsRw_=hZf|7t_0@ve%k!hSV*;Q~Co*r49 zFrfhVPpYf)m5hGG3(4feLkBsdFu4)zkuH6BqfqB<37M*qOXduV&*ki2 z&B%+96x-76tBL|>0=|s?gWs7A*pWCd9NB9SBMVFE~9oTJ^Bt+$bN}5X%A6z_?q-kg&$Uha=BTz z)cub_I#|mjppvzL@h|*KG5f^u=0nythM+N=w<#;fn{nTJQzV0WEXIzyY zRzdGkjWRb9-NVlzK5nH}HI7f>W7A>2l2SWm772hdZ5Efuwc6of|6vJ8__=G|+wiQm za4Xa5ym7qFQmXY>oVEb(H+Vj22r*>|-aZ{P`^Sed44Ths1aj@ih)~0bJEvE9$4XU> zuj_5N3?m&*uk*loofWiANHA!BNqwC=*3f=n`Y`SrAI31Ws})_DfSi>-JiX2b$Lp*> zV-ksh=I?A84Kz;&vBK2b#Pu-EhrZ|iXO!LAYhq9q3MZ1OFLZ5zL`a&kIeyPSco z>xzY8+<-_ZC4#Eb2Zit2&!@ zu|L^y1xve6#BMR!ANR4LE+)6f-Q%44-WL~%LfNH?SIA!|QSj+V6)aup@gU`4OL?T0 zg0?~)xI$IRg}w)CDe_K12^^%z3*x?bQ@Y)G&V7l#e~#~R`JjVxySSh4_h@94E>W_asZ*Gx^1^x81b0TVpzJE(3!_?jzgYe_A;-v+&Tbs*i?FRNRlsBz0-KEQCO3 zM{kvWcJ@#z)~6Tto~5@-dtLqP?O|S8pI*_+^|r1jfCu2N?b|M4-aWnqt-;E~*_+K39c-y%)}h)F=%PBODyyi5D!^uo+l3IRxK3jJqbVWOX2 z%%5qyEO!#+Yb|OSo)Bq4Z}fN^k9Uy%486?rLZAB2)Qip_A*S`$>E(D0^47R#7hI9} zJ$y8;2<`LQG@Z1GnIx4Sm3Df$*_pXpDipBkpFmapf06UkRtQENiLwBVk6^2G|0U+5 z)4dL5Wzs)+v~Mor%QC*|`8mZuMesjB&QM@==*q;>Nh|ZXA6f@**a;3w3r|8<_9AQwbm7JgesxR~U za;tAnRrh+RUG3tV@nxJEqZ;lLiF*rphc)Ngp_-z9CT+)s7E7zUS$5MMG<^b1TRxqq z3?E--MhE|D$JexYTHVzubGDVi9>?>Gad6`2JdZnht__@puRf|%Yt=t$XZY&P+zc(8 z5h+#A~Vp3JM8)^gq%0$ZQ{tN@75rJxkI?kM* zwT8yWInN#gV3rG4aqBhU?Lzm(a|(esY1E9*9NLCQ&CE_7HHMvX)S5mz#xdZ_@b=N* znF1*Pf_yV@m*R$x*i+Q-BS#bq1PPtT3F<;|J^ec76yv zb-HRKlp@rG@9UGD@@}$#?{V=JGaAztIjsL)m|W*>q9C#W$?KzCG9U9~`QoU<9aFuqa(7I&f|i_3e-AVeDN1S_wTW;DNrj!P#$gn!U(k=h7gV2 zAB$Rx-5r1#iVzFTMqzJ-5#FgUAK{B97kBV@oaHFq&!Ym#lHqn9PxQxIc|2Z^qS%`U zj1M+M0vAfT-)EX5_U)(gJl~W;=hA%j#^meJ^Srw;c}&00+LWRVrTN^(A+%ZSx3@c~azCGe(drqkg{XVBJkQbsM#o=dlyY=paip-ss}xupDK%L@ zBRP9hx?c^@2gM83QR+vmFX}1jkI>{pj8LbkOHu2t*fvIxT1a{<5h5A@#LFMyvc>yD zg(Y|FZg(G%2YnC&VW@b(Qs`>cNf(IdYMG)=?k@$Rv0e&dW4%OD0y6XxQCMQcA@owh zRS?P4%e7_e*28C1Z|dE;@@>VQ2@J#VQ_pl4u4xg6(X;$`7LH#_6!A1KF(QVaDZn$( zCkaYYdXM7x31-OgGcw1|vuF-zs}r%~RK&5SDbKo|d9w0`YT_rAvgQAn)r` z0l#Z4-SE@|9=tN-n9!8>%^Q(m7_RWO?LBI$lQRhGWRs|)Y!+UT8H1Ww#0g>};IP0O z4huRwLCGaV9QR>gfRgA_a1)jEbg#VP6ZhcIh_zp#EGt!M8?yD<=$5vNBl>0c;WesY zaW00*k=C3;`5qKRyg#W^_sXzA;`mv4WuOCUn_$c<1M&+i zy%M`#YnYz+vNi}!;getXkC zlJzwieC5O1rS7%iLpt=~q^0g_!UyCU6>aB4?VjTj3K#aGVEECU_mpI7B(e*iw7w*~ zO7^H$n3x-*u`6gI#W)O(>h8D6vJ246DOV0LwjqkuMDYD-B{qzgKr)NOe5Vx=ogHZe zdKuZ??v#EZ(~cA|>CdK6q(?T-!y#aP2B%KJ(4H1trUW`_4193eLUJ_`twDg??-(-f zJmb)z<8}gON{MhQQLO~NV!4zEiYdyQ%od{TM$W+pNdM1}G)trtT&ZlGb0WhIMr4S9 zfGx!FqYC1MSZO-k3|oEHkhxmf;>f*-rOx`|pjMOWAq!$Z;Udtcb;c+NjA^^*M_Swu zcja%Xc$lb`Ja1ESuiptE-G0({7l2^R#cekph>9=3>k4#wKfiKMVzvaySPa_Q0MLSm zNvQWGI`1P3X!oyTY1hDk4&8@Ef(erQh&ZAaL%&5Ic|i3@x2WTTe+BY0%&oO|sgH+H zxBo0%H<#a*=eh=Gp%pc?q1-@K(aD)!45NjNoz(XzkCKfYZ z!?uBt9vsdYO-?)fctDnL=ctB6Cg%Jod5EP}_CJ4?e7K$!e7JZy_r!7_%#YS&qWdN2w^fP2 zT@7I|xzGo?(?lsBl7X8`9?Ak+c31hOxR%3+t6^9H2=P03;DC>gZnY5AFe-q0_sbY& zC>W%*hs^BNS7%86Ts&QHv;$RS3$($?i&*q%dLT}sR9KB=eW96$SuyXF#QT-n*M~jA zB<(&*ID2UjkH$-yFcwl=jSO;Lju1N4e;+kK+>4Ls=(K4<$E5Sng-42#U5i0cs+`V{E zM&`?)(%)+ZMR6ei>nSUWW0$NYx=FdbJPitDZ_@iRe|#-ET$2ojP%@sVf^_qy=$}+M z9qOy1Z}QtBbk`DD#${hedmg8Kc zrv%jCUJ*F3<$a939tRYLLItpS^z-o#8N%OW*v8RCtJ*@U3maMI4uG5D9y|va+@KZ= z3R_mKiAkBakt}5K;g}l>!h*xqXnL zUfiU(Y#aIE6L#zM6gsq0UUi9GVieg_U9`6FsdXB2_q9w`m%7c zh^jQeV%YugAZ-$+G|K{-p@1oKw`pvP$-dZ!wQR$BrIM~MhqbI>Ng#{2;<(t&VelH?_v$*$PNqycsXW*w&r7M zE}C=JxbBl}+kI`JayIIyJk(U1A#o+tdaDE|-aA&2T~ZO*H9q6@*~*}|pqKD26gGg? z+E8!rw%#%~le=4QrGch^o4_Z;>RJ2cL%8gI6k6J8s=!4x)gUK=YrD4}@}c4caM5#& zI6h2a)0}J@{I*R*V*Vwg3k_Uk@^^_7_Kv{Gf;1U6Bm^(olu~@0lMEgq!sGrRK#^LB zO_~za0QRxi7dJ^z4x64yMXdp_Oujaqe{7Y0A|XGua1uF@cTec6-1{W8t^WHjnDJ}`|CqELnGSA=;uFNb>4CnjAZ35e5u zrBZf;(GbGINvHOZa<~x-MVI-peFohep{I;)?WW`;iVV}!lQpZkP*|%G(DGDKPYRvq zh@Q2@Hr3DKb18i6xi4|)Ng0y17Z zgf7rG(GsT`PD(QIP?r9Gnk^NZJIbY>19zEiHQQqpAYqEpSqC)WQKta}y>m39ZKnYj z8(mP`3t#rXV$1#{o4}ZnX>98@#qaTD|2~#|9Bm38;{EX^Wmi`&qGo~_U&s$Oys-L3 zQ0l9H^I-ME=duFj*}|g;Ue?lS;%S-P$7rZfqXb3*U?Bq9(EgIx>U;fVv3+8mjIVM<|E!S2*&>5_L zdK1=!%>Y%`mboOEp#?1nkfJ@mk`zdO&O|uM6wUDSY-v4@4^T{TT1Q7s8f!w30mRxH z1io@66M47;5a~==RyUa$d zN@#==FqR8aphM0yRI1VdUVoOfAhfBhfOw?=@k%~0+fH8m6OL4y(#?4%P|nkQI6`kH zoLj4${SHlK{_ z8|gGRg&OI+qDDGvGqoP~713AfWG?oORy#yGMIMJlEwEYik{#6#9i~@KxaPe*j!>x=xNT5dqhX0?v zcLBESI_vz--sgGmx%c+D{g7JfmVM5#4Ba3N?Uax@&_M5w^@{DXp`0p}f@7{pjGn$R9ZP>37^2ykXh(lZg@%oxTH4=96b z$nXFE*4lfYd(UmPT9#xIsZ00l$J*=ht#5tr^{t&o4U5X7W~dsqP&|*aKY1Nn@bwgf zv@&1Gj@OI0!PzQTF+K}@*UJ&w7>Zlt1NWI-@ucI*T_pjJ;IYo+J;pKa#7))#6iHS^ zu~5?#d^3*V8^TpMFzN&bP%UeO2Zt@mxtA2Y_8H8S5T+h3kJnVd70brpGuxqU4tLRV z_42MGYWV1Su$Thm09O!G2kb=o_Y&r58k)TiF?SY#uAsh@T4y^d_=r@|2Llv!3z{D4 zmdwZaR~5mD0*oH+0YxPAxFbR!HKko;1dOrj(m%XTU zcY$~My~uBG@*`fB7Yfsz^$nhj@b=`1K;W5Q7xjtCBD%%1SNNIM2c`!csw_*pP~;Pb z(OegchBfwzU|x3Tqryx0iFL5%Lo6anZ9)jaa$i$Wh+F9ttVs#i);0vn&(oJBuzVY@ zUJ6)B(ibdEsS$#pXkAv&tecplPNLTQZfA}eqcprD&Io86moU*J_Y>w?WN)}Lo+Fj6suAC z9~*ZViIBBJ=Y>3X-O~Amf=`ew`1`4vL6%ecx;8% zgL=m;SQcG-c>y6B85&}f$ENWoi}zv&q@`_%SvBZ9Am1sDQ~G5o~6aspu6(}7UPU$T!|rIk&; zx`3R2C_**ZT+2-|*vKp24-&IQ+oVltpK99gSL2?axL=7yaO-;bXN2xpFo{Y%+?|TT z`uIOdK55m17JFycsQ^w}ahicv9=!~PwgeI?iG&C6qs?&2if%X7oM|Z@d75bnTaISP z&XS?>W@>_F+A_=R%ybfciemGe4eMwzp}K<}3SD_45m(^pjY{z+cYx;^L?^)5Wqcx& z3gbUa;W79Z&=i+qntZ6Jz7Wu8 zD(wb}cn(2mVu!fUnDN%~pD}l@9SW3rs0c)RFkF{HM+z@uz76ck?|I~8^_xF&p;Kw$ zvWfCGHF5-u7P4uU37=IwU{q1_Oox?}(q}_ga3HxH`Ejp6SATt6IFi@rF)h7d!rLnJ z2yPM_PScveDmc{0Be5cQs8&J3R?@zq1FYFzOe4oM%7+)$cggxLZ;HM^uDn`I-`zw* z&m5?5Zvxx70Ndi4Sx`ZCSR_DQWUpB|$O7z{SP727Tg8FJDh#eJ4s{{%IHE5kZpmyf zxus@p0-2TMB-4EsBG=N-;B~<44tN|ei*m8x5=&!4M;Hp0@Ft8YFAZ;!lq}vP9`!}m z?8*XDI?CiTYW?YD^LBZ>{ZBZ0H2b23woH7evO*f85>_QBB(DIL=uzfPf~$}(c#~XC z=1uSw9>bdww-F0#m=k1M%n2T)?(#P@ms0i*%!TG=|BF{7QUG1A`P18O>HMllm8co{ zuUh=+yq`d@R9v0523NK;I1Qr%rI!Y029?>;JFUxjl%c_SfhPua&iY$sP;ePQwZCCe zHu9MxP1Q2QiuVV_sZuOfP>welOZy_q((VZ_ET!Vy%`KoSg6Pr|_X0sN`4}bSk_)RE zqz0Q%S%ts0o|AjQ?F%k=Fq(-V5WdZRHa7XCbt?Ope8n5=pxISB!Q+=RIT5Xb^=mdyS zKd?xYNg&&F8kRl9xEqdsf+dx16q}GJ`(A#q7s*Fyov%FcKG^x>mhuc>QO_<1!s2u< zJcPvno)0**)j_j^xk(%#RzR{hc?z~cIM%Jm8B-suRZ|tbkRMcMtGQn5RA~&(C{yB@ zIEKFxgXJ_c1X^2KEP7TR6F=yN#_8r-M8pi?Y%x#%yt|qFonx`^=c&VuKTn}DiDBFw z^gcNlStH?KBsZYME~N-`ffQrOBN3>M;?{UB+&EIUx(aEWp5J}I9siKezn z!{UZAF`>xV%*b+?ZNL>T3aL=loOWwRt|)=%=$z=++n6d7Dh{@&S&M^T%?}B^V@a%u zhVK>E>dLpkJ$!F>+>5bD&7p@eN(BCQ~aU-%=h7EkV;D znLn|bIHN$yL;XM#r%0CkfKDo7zZwUy;r=P4J>1QcN7;&Vb9tI89%x&rw^rU^%t)&hrmjRH0FqY2_VtduLTmIPWngE&ycr=u{6(&>rLXV!D@0mPZAX)D{ zIVTKWO^GmR+x80I%tn;gw(YQTAW%-)+qUc2=9qjV07t{b=Itq0dF%E>Ta0ZbnP}@a zZ*F22dPx^G7>pyabsJ45Z`~egM@wwo=Go!-c)lK6x7WRS8BJups8+BLvNw6)J;}c8 zM?+Jr)7lx$r_*{LvkUNKHbdYUyBg$LWaqK=YfEwyF7ziit50vktFiC)^&z3{eDdw{ zb-JB>Dto?{ej%|#UW7{)#~cla6XPH+)ii;YWgmfSJ48 zhQdhV?RPiR;It)@&b6}Tiy<= zzJaBAlnUWdRwXk@RVyTH;3w?~R|iJ1ADkB0xuL~|DrYGjF9O1V_iy`vPg~4_j;I8t zCjQC*ABG@i{cz_$(JpKw)|=VY>?u(Z3Sw_a*HHSLxUdUQXB52}Sq|cQbh3q7J5A=1VGM)HQx9l$vGc1yQ_fED(AX zOtdrH#rm4063}@|II19<^7*QSuh2sOXH11>_Mrf_L(57*s|o$Kki=#>eXHiPF9vIh=BG9@K}x?VEY z6>O=USBubcg;9ZUPxWk9RK<>NzVGZ3OD2~18#>qX1C^foJK~%>ucpqwI1eh{=vKAQ zdr!!r{A6>VHx8LaT^19SDbIHBX@1|*?=<`0C(3|_1>Mfp>%@Lyftxd6m=UKsbu>LTk7Z! zUyrp{)UiAK3P!=8wYHbDDsj=LI&BX8R-T;k(8ZmZk!I*`^T^RDW8^SiO3?7uks}$@ zBS%Dxk!!rKm*CB>ZOYTDba!=p?)i!PwZ_`KH-Q`O zf$QRM=Q^!PwjW6Le02^)T&?!${>J8>s3sk#lxQDcGFUBHcUp3E*86=rll6W!YenY5 zkZ+}#pOY|o0U6Okqvc9T%SeswUD7tW46eYwywb8SvwpVNm-Qs_ZYe_-2(f5i7Oh*f zFO#%lnSEI_O88aVmtCEomX_i~o1}_st!UXBOJs?679=b<-H_Z=kA?7qkxD_EVghR} zq&-8TVGP4^uu67i##ACC>^Jv`;=F=uc0u`=Da<#w;V5TrQWW2+IaOtNeCg<;m>Vc$Vx}sxUj8C_y1TjE_kn_MoumEPORE=iq*QYCtHJ3C+g+qD&!M7 z7OjUq9%(mXFJw#W0;dj@AGQ^Mi~AFtSPojEJPA$xGS>sZmeSU&63ha-$2`k=_U`-& zjn2#dg*Pn2k9ykh0Eo6Zkk7wlx^&yJd_JvK#R7#Quq+lRr2j+6b!l&tS6B8^2}dNA zC?1F_(dIB8_oWM$_WHK%#?XxHyTtgJZNuTagr91Oz@Am3f_v<{9BOYuc9Cp%VK(@u zuR1t1jq?SasY^@&OrN!UN+fuGfN)c@B-n>p_H} zJLQ4oN3%w1iC7*RU|m$BNN~XkDE?i_N-W#u7ykRuG{ZZW48;%!`{wA@Pe!-ePAvB+ z*H@CL>w?Q{b7IlOrA{pM78(6dWF~$U*W_Kj&4PM3v229I4JFzywNz7>s7f;F!prLi zkeQZ^*ZEpBS2)20C4V0 zylq4AY`#{rNjiLU4fij%tbQOr2DjxWxa}p4jyO8AF0sU?hNP?>vMi7U7Hz1}Pd5ne zv_qQ_(RgyE&;~dbLGpE2g0al9L;qAj8wOlvP}98C{0L3!MWT7=nLY3GTSD`&KfOfu zLxsFT_3?l^G;%h;`0?eVd0Hea5BXBE4^2eS>|7>9y_0taftrVhY&i_w7i46X72I(L zKlsm>enhiOA4Mu14p>&thd$b=EM&iQgolt72e9a6rkzVdJ655c+U**cI%!yfJ?+>Q zMFYS)qG`b72<=$1ob4slL7j=N7&>$19;hvN)>r3}JZl?Xa2El#{tVJIkCgHCBb0@? zG9lJcR51quT~;dWDaXGv)Yz;EZYBcEFHF$XSus`&;&g;qsf6kDy%9|nlNS^ZgQ9}2 z9AIVW-L3d42%@$)f_5^)iPE5Viu5?XDpFXWdZOwb3-3dh{T|pld`APIwp1K4_OL#^+LABru05@TsuJiI|tvE#6(!Y6Q54eCR+G zB+eA5!WBS?KoyqvGLZfH-)Cr9iKTWNjs(d89jOPOq8mV%{%B#gGdzA0{1pGOUq<4{M zw*J<~OzB0r?9uL6TI6F9d{3>26?0@t} zxgb|Y6O_HAB})Niy+Tun!qRd^Hg}wZ+cqH(Z@FX~R@Rb5M;+!Mxe~Fx`dO*TUu7B* z^6TkPlT8-Ak0Oyp>^g}VDD;vK&b1y`em#>P2(tcuM?o^x*~rA|SD6G!Q*%RNf#7;g zORm$>wosN}$s^5B^VFiUH(RDiS%gZ$W|swN-5otG@$>ayBM#DX`&mp}CQxcBUQ4U| z9Ct@@6ifEozg`r#K=9Z(`Ci*^pR9^e|u>AdFus*gY>t*L6itP6n;A-EnTV%EKRpT2T$~QbzBtYxq+BZD3Y&mRX z%b_7TY^0@VdDzI6Zrr}%VdEQi9=p3~C;Nu|;PvXMVw`N1*EN(uMD-3Xp)H1%jMD|a zVTL5%@I>9WZ+H@7*`)RjtK~BJhAFf*J3tLE7NZWn;e*_UZ&-NL@(mjx_6>7IN=t&L zjB^@h1C=~Pb*?tKdmL}&NjACLgCNJXMsRcawq}UPb4I(E21XS<*NB8%5Tc1hhK0GX z$sHsJle=69mpLOaip!pp>?j#lc9i6pWVM^pK@I3LCg`*j1_uYIiDo9yI2qsH;Ll)7Vp3mmH z(Hfntebo-`Lc2;Zmn;3a+{I1@Z$VYn=@a?N@w+f&xp?J*AwjtEfb;Ge*?D(66MA8X zx56Lf|8{lQVEd*s@e4aT6l@bhM2!`3R+)vOZ4EL7pANZ_hsUsSQ%Rm|2Sz2Mer+&qIt+EwnAXdKKng(cE zu1&6YbSKY40Dpz59BUAu9~lF#&nm?V z5wOCptP;gp^c@J%YL%L!Lj*!Bwiumh2{h=`^g-4OVVJ&)B>7ym;vuy9W-HLcNRpq& z6iM>k!U|cG{kZ}pDz5MnACvTM{K?4h76IZNu^QhFHgoswvqBKiWeWY#Js!<K$$A@EL5?o%ucEChm@q>}O-5s<%?rp2brYkz@O%5%k2a3V4nMSjt zZ@g{O-$-+yfK+Zx6S2AKDM^I8pk5)jd|xfx!4K8HmLIA)&JR@_)9xsZ;HZc(K^=5g z5svh;Afd0&#PP5lZV)ovbg(%dSma{=p1jm6pnW*sNUhy-YHh()DYn%eF{xb|@?L)E z%!D60v&N4%2oYPMT6Wofr3JcrE5{d1%XhyUqja2JppGC0?UJbAeqc!we(nlsD7qs+ zeKCcB;Pk^6Prtah0iw#!AppaV;z9VC5}?n|L8e&D&iqf&Aw1hZ*Ie=KrVLPf`C*h3 zegN_sKbWIN{J7H`#=#7z)0+Yq7OsQp*QNlr(Rg+@174fjliY@vbCLQq1Yo3(8RVu0 zsc+L5H)D)Z1*PmpW)3A_&`AkW}W4f2MNJnJ=A!gT8~-mJ8rYl>r5_A|U!YAI1LAV8yU zcUQ8OLZt~$N7WF*Y`srA5wj&Nh?xOti|XD9mOgcCd7gPIuc=} zZ9+r{yR5x#)$jZRF&c3BZ%_4&UAP0SjQJ zVrcji#v5DgXumezYzNGjjR1o6}Y16fkt@S43 zjZ%d1K$sufxz@BuM~!<@&c~6!lkqg;WeZc2Sz0}pNgixOKY^C4)fV|Rtyo*+*CIzV zS=(iiCkDxwybM(xrC=~9N*LXnXgc9}{ye zsBmzAEsK!nH(P$@se1=whk(hqP$ z`T;#VLVcWa6MN<&%YjnBA(MO*tuZ!$f>*ujybAG4HiJZEQf06dr^wei8arX9{^bn~eDYLe)!| zp=0N%JMxrKO&>r|AR!%U2~-P8LIXEN2akAx)X9ZpPvGS-=-sTe54fih@>JF_mn~W2 zP%ShDj`?s(M1hpIGPCPjN=c4!xA@CpLalKFkz2~AJag@?H>+f!>)Tg}T6iJ44wR$o z>iC0B=XhVY=q~I&m?|w~eG0h}zj)LvU~C|a+8HVQ5O}3$WJ`g(Q!XA&3_{)=Gze`^ z3;VYqe%Z!v)Q)JIxDGcAcB;WJe%C!i)`<3AxGo4YQX9#E0BVE-;gaBx?TK=N$Fr0J zcca{R{JX>pFU*Ma_=<{d>=0{Hjg))LC(%`dHy)53#sS8?euoSW`ezuWl=1@7tPs)F zB1sbzQ(D7}_Yl@gzK3{rm^#_hq%bAWx97iH?;#yT2b#arVq-|~Gp7#>;jT+QTZT69 zV7i2k{+!C=lUQ0_2q&F->C5dn8;u)Ymxbd|-tfD;@cuUJyd4cru2oyvOB747RCahl4NTV*&=Fr0c7N<l+GXSgD=J(DT@9*ftf-aP?XbGK#x9z)~PiEq3A$J2TIe+|sAvDRR_l66<-qSDy|wHQN`F zk^W2n`_FvhkN)KMA9{*)aM>OD&`urttHb5)d-}>Ds*LMlT$4MhgbgKQVQf(=5d-Q6 z*m7DSnIe4LJ8)KNp*R}%bzrBhUR@q0(~q{_@V*Xsw2iA3Kg#NHOUGG93b^%NYy16m zR#e@Ii*!>t{pttzW8#uOvu;KvP5CqH2gDBXpw%&%Ugcz~OJHW@0hNZ83KiAWvy}}K zyq@Vq1p*r7Y(p;E%?}Q0ADpd3g@fxmZR(k?DTO)Q34f1%2HK;gC)~B}2uZgFh^;6E2ji}OV zdSvp?-z$o<*t0ibkfQ2g=!gp0Vo=H?>pJ+RuQv zqj>;Ut5WNIWxuTr0|V+UA0QCeHDFIZ%e4aL`3o$nKWD=R96{a}4qCD8VeXh#C6@aF zR)IX&F85Exy}PYyINL|>V@&Golg0c%bXVu9ffGD?x|oq*5m_-}WpxOj`qbeW&VXy; z3Y)^UzM3Ae`D)5mvqMl?85*Qe3}j+iKntzOC3ta^kp^De!AHvHv49xLV%b_E88ZNA zIn#)b679qfelQ;Zmw?#vi{xhe9I^5X{C?GKt?r88LxD0&jMyxi`Mj&KsmYu$jU=w5Q$hZ#x`|^yD7pAXt8n}4k zTrGU23c0KvQXBJL{><{ zp-u`us?SvbI})+LhF*U~ywSrnDiDzG>k(bDRxhd_KC7D3eX5DeXv(4y3 zI?9&yC8NF%;blMmVK4R1sYj&8nEXRXM0n%`ymsa4Shwd8%{N+F1>y}REDY>m}>nV zqC=65TELBWBeoETpz$X(PV;_3whotT;`oqM;2seUL#+$+T4)4*^A4YQ$$G-NU=|nP zV)Bv79m12?)E%eqNS%)WccBAh-^rM3gur$z0-I7+u*_1jPt6X~tQE6YU7GS8UGh_j z5D^luM4UT)A&114FU*w``msYJ*zdP|B8#>R>EcOH9x(4Yt(`a_x4n3X#qbHaK1WEc zfqyOMLodd2B<%9`tEo;$nutD-RtG=6Z^$O13 z7QoKmLn?DrW$+Z(T;R>=nq<>tl~Wo*Re}tx9g)oz9|%It&&BMjS5-dIZA5*e>JNnb zuLAdBwx#$DJVES6JVRc8e%4M_Pkq9?29~}uxwnKZfKDLgEif66zYK&`p9Q??RdEG1 zi7O=?#EVl2p&<;CHs&@Ma^+w78Z?>mk;Bc|2LGx!hU*zx=IG6``W8;!{3O@+(P5v; zDPWA`=ElXV@sGr5?Fw#bHWoEnnvaE$Ml+Hfsw*+D3oB~@Q(N^2v{Str<)W8YA4FMw z9O$k2E9sjkh??@{KoN7(cBCghmM@J2v@&-BE40f_BqZz zAN6<9kzcmkBJ;-ajEV$noPLJgk{MwZ1iw5dxCH~2JOhgK=0d>&HxR_c(vh8<>*7~4 zaFGkdpVH>=Qo!5KYFzbR#}gK!`8-aKJO~ly^79B{h;=D{xXQefJ3c(dhe%L2amQ?E zo;4vQG{#5}C?Lhp@&Hyrc$csOEs?($TcYa&eu7S!1gb0x1JU}){Vs#*SUW4V-$=S&2y)NZyc=X!VlvOagqa9ks;G_p32B2p{# zKsWYQsex8P@4`bs>t|{u(B%8MDS=ubq7=xWH7vT)!3{Cxbtj&dvGdI8WM7GAc_w)9 z%!;K&TIL`1@?KVyMwgZngGx#$LRO=MYQ1^RB=yLWFChoQfeu$ahUgDbG%Tw#g5u}+ z6I7ABH4?f~`kL(-mbRw6*_C7sEkNFhVD7vG(aD5F^gg+%`a z4sESj^c3UCInxy0OOGigAF@MiU>$2o>HBkOu&nB+w4G)Kjt}gRnrO{KD;QK4Gm*~% zHQDXkjUx2Us<~2p^EnH3AYOL{!yd~ij57lV_HpE3T_-#_)d>#RD8XN7vgX!vXu|~0 z?vH1lO&ce8h6SL;0oy>Js^Gwr&j|;Fd>DIJIBoEFC(aTR#9j3`-=4;$C5H)#fW;3= zVhIe9#AHUq#TurPbnv3Rb&^S+1q(=k#`cF4EMY8=127!tyK86BZq{sWXLs$fB3;ZO zH$8~B1gvrA70opMfwF@dbB3A=WIAWV1l?LD#*h+QCPvnbCJGpfM7%hL&!+)rj}^?o zgjF^r@}O2ZBVA_8?h}bRTXr7=C{xP2m>^j%`GcMiskeT#-~z-29RJf8FKQuM@NtZC zK*ZY*%9dg$j$yS9`jXT-uIS2*ai)MjkomL9BL!i zCrt4aTUJVzj7`f0r7*Q%QFeXkY-&rJ+&N&O1`TwN$1SCwVlHnMOXfcC^`GcG`C%yi z0}4y&R~MhHO6mMnAsgLdjS#EJ8Jm%ts(4oSPFwI*e_vbhqj#YKwd+9z8U~?3-BZXd z9gFx%+kt!Y6{&j;aCHn5*#f2RQKN0-JA5H46k=oR3&i}84rMmCwEyJ>??VFb%9jtk zxW%9^lfMg#XiwJP7zm5zgLRJzEd9xcAoUCbl+#6L<>pQ(c`Oy?JZu7D5i<#*P$iKq zQxow><`LD;y0?wVW+u7FZULgU`Ra93x1 z#g>#XJXt{_Ox*99FOJYs9z=YBi#Ay&v=0S3{tNh8YdT)1+*qLFtjbHI++g++qf2h( zX#o+}HwcFHJJu0qx$iYz2GF$V+i4C&xmrNmp60+nlT49jyaAfrgEZ9w0mMUAAmq8} z0tk8uXgj7k;EP3|>5B$vGF||*i$w++M0W|$JbY75U3s?*J<452bg$g$? zK}x(*n23}gvcR&FliRH+Tbh;Kn`2Irqb;xO!4z$}Wp9;! z8iK%!x6Ylc49BMs^ z2~WP*dLmV)FQ0e)RagyVmk3EecCexWH&0{_QQprx1QOcps*Z3GyB*m9p5*=08X0E0 z>ev51YesIir%SeS@r!NX4jW-UCM%V-qw@ITKaK8=`8*E~(cM~j-K(Q&l*;Fw{7Bup z!|pQnNRoko3cekWG*90?`x@Q7lZ|csyoa;vczAmDwYpP=Tw4`%vp>v6)5)F;Ub@$> z98l48^M_K5*vDne!llk0jnJ7O`vcwTn}%Z{e(b>z$6&vsaqHAVQk~ z-|BSl*LC)NBXZMfHO2#4U7gvb$}(|I`m^_NY3qP92N9X2_z3jHo-8A)+8)zWTxhz& zLzAvpp6LqSl&%n~(v@2GxB8WhRr1U(@&*8}2Z5KAlQ4UvVL7&h^%-Ls+}Jr=(7{}U zZQ+QtXX3pAc?Z=$1$R~814SMb;{VrmzPI?s;`lVa_ebXnlr28Kw_uy-Dn*LpHAIRe zJ=oJ~Czc_C6F^Q|ri<9D430dGV!SvDurdL?-|Ln|2SB;Y#tY>Z>@d!byi76ii=W1w zLg?d21tyg1e5it;#kWWFEm;nVvdp3D> zc;3Zo??elwGb0c*)1*#VNf{DwA~@lK4SwF~&!^K=t1>Q7fG0CY4Li-kN~BrPUVqx- zgaxohduXWVYIX1eHe_Wx7}k%O#CT4!`P&_=`Jjt$>hXic+>Zykc|_Ur_<^ z4aI+Bz9>v2w7s&UQI0UaYL&%fk_hnF`xTg!+{L%3t;Z8|A4|}E)X>c{cAH}p?_*}{ zZ|PL`a_P0vkr`ATGEiFH#j|M(0|lV_skBcGNK_py1LGyAtB=m|Td{K$xJ^5RYeDQ^ z%M(Bdet(ZqNaHo?TF3IrOygY(u8VELV9KPaCt(+Z7fN&v@> zxI}!SV;yYBO8rnPd?JZ1>|}Nd5gj2V5}Vek$s{&wz&sc!Up-3ifq_Uwr`d_7i5K8J zp9Yg5M)`*{Nf{2_RY5JCU*jn$CwbvIq|$zh+Z#K~$b_j>4i(s7WJAbRQ{?83KG9@M zM4$L~n0kBwUYZz?0c3o)C}sHhIu>v|nM}_7P;|%RpC69cL?p;V8dWhpjPM0W1O*KV zzIT16gs3J8Jd_6zyD_p4k$yD~$GYq%_#V>yV}tgS;I0D5z^85ugj2bULf<|FINyw{ zt1v#}1xrPkqBt{CP7!p`Nck7I{13IZ>4Zt7qibw6P6)9o46zxyegK9HS{Pz_Pjp{K zh#>Y3XUm_^7bBo>CAcvVW)2l#7wBLEJohn{0BIURfYiLiAYBcRCQc~6>ZR*7nrnQS zb%@sO*W&sA80%OV(-zk8hhO=uBX#kmW*r3E9yh6bnRUF(I;=*T*zU|aEI5Av9UxMe zCD!qp7m;-!qb*?_kpC-Y9a6-$vksK9&^jE0jn*MgSqY;u?<|jB`k$ijj>B8wr(9Jj z$D3KJ`1f@4KL=P4*MCTRYK}fg{&dccmxQNN~nvL0?E) z2;BB*gO)RjGHPH=ZfD1}P_ZKlCFUCen{Oj#N_0w@yy^RsclJ2!0Uh zENsq7eMSz>bu>y+hW;iMSV_{yx2+>arHNSv^u7?G1@=QHdf$rpRyvpht?8eO^$p3~ zMzTLeg48c+p4h#0eYd){97@Jegd~`cR&Xs1B3j%Q)s-BP1djw5{Zw{l( zNoKb@grvT%i}%-;4Vnw#tV(t~+ONlA=@lUDQe$)Thm{p7nzqJ)?&M}sZT2YVdF~_)NvDaD z)@jrudUH$XhtwnN-S<+wck8r^QiHw8ku&JJ#*C_#Q0Nie;IVhrczw_vubUtRQf{MB z2P6CB?a*AKZO01cUb=%HGN7Y0QhfJ3{js7`D#$7+F+S9f=O{Y$6C%QRg2tG%%JM{a zdT6ReK*$%NLZwt+ELiAZ4hgXa%_fR8HJ%ejtz=#T24^JQQofb1NLXn%uN6>1>GUn- z>v>Ms@8CyTq4jBCaIPRT1NdD`Ap|z=qLGWzmI}#9n$C)-UfEKxQ>yDm!A|M3v253$ zuv#3@91mo5@NoDGyckeuL+XWMRWwD5Zx*6Cn~WhEiIqkPAwwRY(Q>T!B(5!)`-! zvQo;b56NPUsEZD^TiG|K!;X3#YDR-xN+7P6{UH3i~E+Zh{nrt5WvO zUPu_Mi^jg$f#2A8(@G}G>%;T)IR`z8JIgFd?amMzwoQ6&L82!?qbqDz&8k&A-hxKy zGDsLHsjP3Yol$1S0iC+Vo+t)7GUFQ(50He8QvK=L@Y$Qp<5B_W0r~@ z4F_a#O>TavctDTRw)aA!? z$8~L_x)9e?*Vr&ea>zi*mQ}iZ&1{6^Kt4YJYn!S@kD<5Hj>Q#tRx!uuIF^)_fQ$5y z>uIEiQf5f1+XQ-+9s-E0bIeie|$hUwA6j}!GE1Ep@2!fXZwSsLM=t$~FhTXMhA+FK?xDDnH z?W5z_0O?%gY{bY+Mm5`VtC<$t_fd>09|p*-a#x-$3^7E%E3e79c|Zb%R%A1#f*RD5 zWX3egBSB^RW$szBUq;W^+D<2_B-=0H*)}Xgc#IZ#BHtoxg_nC;FkJJMrM!{GqH9)N z@H!qaQ)7has2~~Va|K>XRN$p#F)=6t}(@B#AJsL2g!sRB2OP4L}hg6~cl8`1}D-;CMSu%}|C-2lw`KHC`)3-NByFH+OuA_YO=I+@g_T=Lm#d_-3de~(6$Thp zGuO4;Vo02|-15#y%V;a^TF5Oi6ixa|X&46{Sq8DH8ZyZ2MU+8mTf~A4Vq3%&${=zn zH>5AiAYjR&3~~_}L|a;Wmuuk=E6M++y&F5nA+;a?R}0pTUU(ajMR;=zzUTymgcu$r zsjRIX$mYq~0Yik&SjwQXSrOIi^=sy1_?%^tV3%R^fR4kU#Pa;1wvkF6@jgD)u1_UQ zw&W4?zP3CfHCYCbamXX%#t7oh*wfh!v7|F2sEkD=_1>!a%H)6|AVUcfH>X4-y+o0e zgwpgMBMEwF4k*r_!#m^quEZ5WuB>{mAg=IOThjHIECD9UY_=0Yp3%BRj;_o;h%BfH z+v*e1ZuduakEYtzTa0N@`$npL6t!XoaVw%A+iG`ycAfjja>-Eka-qqdvg-qewK3Kq zAe-$wxsrpb_c2YHN+BGQG(!5XZgq{RMYs*}LE}QhhGFQ06 z*Nql>wFE8JEprJI)SVg+3h^%qSG{bxsf8;t;^&HL1Thz8&-fMu@ndGf2vSdBOdSge z)W9a#G*P?M$Lm)EzjE!GB~4usE^5-y!j*lh9#CzHBI*9N0eKSj9GNWxTEbeGh}ONx z2;oU4qG!j4wQM5FEhR+5fSNEYuuEnX@{A|iAc=)pVQE@QMC5KuiHN^#DUo5YQ92uk zEG72Z#-X0AGoA~{EF7Bz`cgiOL-1$WTMTx{)|J=f@B?HWf+%bA64!0>k`WCl5w_7w z<|QvJ%u93+fp1-Ya2Al?+s5Q^890rNDMnx$6S?bcV?Vd0HB|lWx$gF_ug%0L$H45p;A7Ni}K+zL9dKzJ zAT^Zvux&_7PzghtGk7l(NNN2gG>XC0vOpS4&5;_{yods6ZNgd*NNvKpLV*-~SR#-@ zR6-!#t!0&rVnYPdVGdWx!sg}tY6n{~j&ZeM9MkD#VI1SlP0$;i5T&F9N9G$6NO1w- zf}`DRhun=}6lrvnq&c1+ZpZ#%7ZVNO|B*dDh&B^wFI;>>Qp`|Nk}6h(l9DWoc4S)_ zaf!iaO^ii0F+dOjcaD~`h-6LNUb-xUx*@lq&urw`nCw;mF9gwSeTNxnXlvOTvS^k@ zX$q^E<15RKwzuU<5H_L~g$%kDo8g8w=xiXA@-_t&rnjo!vglR%#gyjzhlqTuESGMp zc_r5z<8uEVgpoA0Zz+L*7%g0#T99;U5a|9Pi#Xd_QG{ebK^!pmf1kdf|E;$uTTwf? zRowrHW-)3WtC=y8l0GvoPhc#q9L?B5XU3-exSMwTBN9x(MOiAo1-z0eRT!N%mvI8JplX+>tD) z{pj^5{-s9IWfWI0X~N;K>{v}rv}YNOYRmMdQ7{IJZ+SLWVlzxO7IG@o?nYzQB*bkP z3t4uY=a-Fz=0C%>vCzl9M8gsThjt!OK8%HUENzOD1@igS;@D5q92U`)*Cchd3}bU4 znn#zihg3#aJHd^AmM5yYtsI?*uV z8$#axxRLxelwUL1@lXaVzxi09ISVY=8HY)Z8#*l1In|6&k_hqa*^W3&w#Z@FLYYFq zf?SCi+O|kdX|hH7GD3TpTIv`lTcloeZHpv%mTZw;dLjEU)H9a-G}PTHXk+4TGtHK$ zwp)B|*b>#W`JV@kF>$v{mp(}|L^VzpMEUbUl1X+0FJiV5oHVnR!(+DA487@B+f;0k zJLh_cnzD`54Jr4U%I^*v>F%(R4#P$Y)|1RA*^X(oB%~dYW^^kE*8vT}idmA-wJSXd zPB6n#l2>*zwkH&lvE2YfK2UROl% z^qP1i1w^e#t}-sww$n6;5dpiq_WAA(+i46@U%r&KQzkcys^)H>3$EPF7g1ELEu9OZ zsx6&YD5^3YOGH(sbyjtQQ9&>JZ|h?|VceJDzg>Xd^Yq{Ti3F(? z1SzDC>>oxPY&rT~_TNIkuhbL#;`wjWL}vHv%l=!1IK6cKTNwV!{@d8t^wRookteeZ zVrN3gAl_7oRQF=Ukk!Vm1sMc6;)`N75QLtDE!yqjdd%Y|fiiB;k|Xyh*YT{tz}Xci4j-O*aqgsO0P8iUSaJ z1T4tU5wP0!#oyjZ9Tl6J_6F)beAJ!D+^e${DRMLiJnoUku!%J;sRKHPk^4i3aX0)5W;4`fj3x3sOCxx7)gl%qzc zf8qylD)BFo_(1vYzMupM0QYTL@IIa&Qob;^63Im&M5-;`=y1fqkQb_ zb*}IURm>-oJ7i|NoRkY^K8pN74um@80yN`}@$}VT=vP_y0l0T$*pf()l1)f0nHKz{ z4TUHC2)`kPwV$C2$uSSrXMZ@~alAwIP<==0LCXIGsjE|Aojeei z`cX;o4xKpjNj)C>5jm_y$^OqCCQ;(}s?H&;5-0!(<5lwz{dc2$}ff;_tF zoS%S$^^?fA@j&xLBRNk9l=E%Si3Rj?XQ*=#{XohcxiAwm8`-7^0-#Aj(*f5*mWT}%YLAZaQglhy=tb+v9t|Ur6ONC+u zw6CjN9AqLLK|7R~RN3!Vn6mHC-KamsHL5(NP0D+WEPj=;-w`rjJ$?=vP)2&Tg2J-8 zT~3wGc;Bv#-d`k7f8>IIQ?w7Dwm}a}c2cd5cE3796j!j18=V}H&&i84W>nhdG3Ru1 zQhqB7(P+AnvLA9wl703XEw6rOpBee6v+O*{cKjc)20mTGc!XK=W01BcQfgLk(qj z8Pp6jH^>ennpHz^RswTwqtdXla4J11kzv25W*aP+D{3SL)oO!%24d(8cSAMLH@wd1 zCufLyRRQ_$gs8+SgCca>%p@k*&9Tn`gYDY^sSeytjmg z(Vl|f1p*SA@VOlbINxanuUj`hykZ}+2M;L+rL0caw>|Q8aclG6XweK zEq40kP1`;pU@K+zbEXVd%yS`Nk#n<6!LTYj=cco92na#|v+Y8m#zp|xhJh`Z^#VF{ z+?j57_(DnfH9gH23RsbL#%IpCNd+lAUXM_0$?V_9qltD@q;*a_w^B8O*b~*&jlNL@ zeqDdfbmnY;ZpgvCUL4c-#7`%&IH*-FLw;nhXQ@QYw>x7B0%N*E^rK&$wO~P93ZPJr zB@mUbzomS}vCLV>-S$3&T!ew@=YH^lvT{(BxbAJ@c~6(hSVgx}I$YXl*ZvHN+Q}`+ zdY}ApF)QfMJ^Jbi1?QO?rrh;U`hi}Uw~}|<{+qeDIpsfB?99${nrx+tq-lRIPK3v; z#pEqT@;cX-n)!UrUH=8Q7jHkGI&rA{5>95xcV8+_h8*-{aWbEW>6IDH0gtZK7kkXD zIPeXET`j;NGV=t%u6`no&Qg7TB9ZQCpmj%e?uUT#*YXxYu6jr}A43>*3i9e1B^p(F za^%tKp$~C$-;bns;|G7XD6h@A<}SnmZOSz_K!`y69Z2VNp1FYo9hGo(4jTY}oLi2I zhf{7jPD-PDJ{c`_&&f)7f+$`;K{Mb9(t7wkRK4=~ANoN3&>8$0(wfuP?}1k-%; zUIeUH{hsr)^{Nln`PqnK`#SdXeq-@wMY8feVdRI{aT0-)_uGX|k>btD&t}LKNN{#q zN+w8-^UQz^HjYJ=OH*Lu3y))KezrV^;)tg`oG!SBdy7m_JOZX%FsPCp55^r>bzyq)d9)<51K z;yoONK(4++92B!9ID0cGPrH~b)P-rMy5TmFR3xaBoMD=5h}6;qwf7Zu%ad6|RrlBN zqfoWPY6<)w_I|AXG&Ql#(T>c6mIUGZC3CMjuTcdAN6}RWBJ~wMKw<}nfkz?lqMlSL zKs435e)*u={$MB?)Gm!gtn)QUDjB6a?b2P1wJH>G;Y0MNp#~;ull9z&;HBZMvgiVB z$gQ9NG2$FA1dv81cLWHr=0S?MB@-35im;KY!J(1}G^I?eA)LXrZ!S-}GfZLU7hxdh z88$`OK4QuGdR~A|Xx-R}REgM_H`gcgZK>K7bRZ+4_o>Y}!%`*1%%Ww5aNkY+ zUFreGw^LQx17Y#t0yd^+;-9+)u$5UzqO@_II1}}S6=f7d>nwRnP;Kv=D(IH?1 zT{Og~fUlcj90|q&x9bEqXEZmsN7L&hKXXPE9Ty59PuMP>NxF}Ga`ZD4Q--sM=ONyh zJ~;+GKT-5HT`S){b;$C4T;9bcpn&Gv@GhDv^8Y50J=PAF-KOC{PX{$R zkUTXq6fh_9@IQX+aAlUJ$|*aT0^S0c`$j`w9_dER%{CVDLH1-6lyOneQGAl<3D zm0Lj=iR#U^#MREbbtls~UqJlfW<)n0DnD6>OLGQU5v=ZG$Rw`Mx?{T!m5RZ-Z2V#b znM}J6kg2g)peTueM-|Ys%(fV;*w$v(&Ml;8__3T;Vq0qx>=NX`>J0gYZ3)PWh_WRI zkC<0SQ4)FdOJTeQn-pxzuGxP_;otq5N4-CDE@#$=sagag!(IkNLtz;ZH6819{Vg82 z9{GHB=7ACcEKLyqEMe1p7d*1sv4znJDUDf+!pPD&LL4@8Y|qaMhXgY>nK`nA$IZ$X zk%-&im8|=6841U#$z+>XYRe4^5^8rk8jWegs&fMPzWg?-dO-J9W`X0jq}qCx@oZfb@)Degs?D&IKZYW zW5Q*L16Xc$6bArU5(max#Q~c*+v0#uk!IYDI53V`YQzChE1o5Bz+Yr>U~I06ofM$e z;sA4!acP0SuS%U+;P1kf;P1p=Dj*a7t}RMT>aA=Ya(rgr_s_=fZQS)djDw#=&(xC( zFA%?%K$rRb9Sy&~^M&L0itJy)?^(N7%`KGs}iPo-|+iT^cHoI%L0u5}7YF9NRqVrIp)yR%_zF@mO z4BbQvUS#G0LpL#lp^qxYFD!>S=aE{UOggs|WAW#HS4LC`FuNM%a+kTZ#3jBx1FS_1 zaxD#)?!(44wW?{Ua%twFAuolCnA|9o%T$kGv{H2&Ddq_C?D3~US3qG33pjcY1=7cW%yjdo^V?nUL7>rtUCjP44vImWEf0TUy*eT45WoJb^*j zlVZ1pTZXKl?GL9zB@9NH3X@aGlYZbEI_8ohpE;n(yi+Q%x=$Mr0l1sk7#gV1VVl}3 zGp+LsMHxxw;BID1-C#@I1-67_8dGLPm{MNbhK{pv>4V4$GP2bl0S-M;KdxV+15Cg?E3ZDQ!Uj>^+<@-X5l-RfJv3jJ znj|%fH<|C0a2rM|5;7Y_bu+82PNrh*6)GQFQJv8ZLI~&a<+`Y|Pl!;>sx{Mi&W97x z6=4zJjp74V(He5W>Q>R=XaQ*cU`G%`qS{#*c(#kd(6 z`!~a&89>tE6*1^#W5R>T1j&_BleV|A-6x-15l~nhF5_A@T%fblaO=(%vqG@%(C3uh ztTayuCcwn>u>x6ZSY#E3dqa9@2;OeE!rKj3csp>-b0yN-G54^R73Uq8-gZnIt&DU; zdfzE-?)P)@UrCkeD3MU6w3zO0F5fflNPT%HuTbi4;*J;qww)d20img&HwH%9euw5= z_|Dy79mX#DFB}({Oa?4V9-Q!y&4-i#7dj1@qW!BQ9oE!hO(k=s<2Ey`U6<+kpJ zWsT@q3|bx$83Py*{Rr(a#W;Mj(Ix{vp0gOXm>vG@&Xw zgiriEPbF~I-db3^65wDGhg|2HDdH##gM3A7Ty;^;EuHV+QqY&;G72Ieii;x@r~w-~ z;}1)mzAE1-L)=}AJ`?#ehYaux9uNw-id(z!_1LB~wi_geB7{>m-d4eOT)u*sAi z{tZ1}VZE4|hv%h_DGZSY0?-r9(k0;;CguFVAvk#`QxD}Wp(iYQXhtq z#0R6oy!wxO^TCoSUV$tBEui<)8uhbxhBa)siL{N@d_VL3pnnGt+rFRzT} zx-vfS^2)$OC+F6oW@Q{|R>t8MPBEwn{Nll?hwn}89%e8i0 zfp-f5lIpj3x3KuEXN$~bd$@GYz%~yT$wf$-|{mvhHk5UZF~`dfB8NE)0B4 z*~0}ecF7OZEjrz7jOSAlluZEB&Q7kOMufm6M;8pK?exgTnU>Fl23@u>_|vi4B$T~h zRt=QR2CD|@jvw2Uft>B%1*ceUe@Dld>)GxY(|4V*9Dd84V+Q&)v%M%md$ZY1M`ens zuPY|w($2B+IJT*QkPFvtfaqBJH z;Hg<$ue!3)ge9-G@712XMcZ3zdT%4w18aY^{*D%0>!QDK@XPbIE+XQcwl>RhzH*10@ug*Av~3$|va9Wo3uEFA4msRt+Z}R@ zvxBC}hi8#)oA?{lEIkL5^0rlMs1S1Ah8^-ly5APT<47H1M)VN_mRXW%QiM*oVt)nh zxpRDHJ&~c4ZW*<8XvR;ip^uo9Cy)V3FN)w#A{*5SBB0 z;Y62O0$&teEP-F4=z@&0Omu-Rh3LX=r&*E0a=O?xeH3|CdCR2gZv9o(3mE_)wOr-g zeAaS&aD|m%HVYh?$#PDSR$vZUHY3xzBtswK%6m(Oc04&ShbM>UeT;Vzm}6#%RV6!t zn&2FtVB3{im}|;LQw}N8$mIQ@wpn=KQ-F?NKv-FoIq(xBk8O=%DAW-Q(tt65>hvy& zFyqPfst8W*Y#R_Ag}BgVH3Hjl8v5C0mS`Xkb<9q#0M-ah__EHBS17cwYYwTyZ+Uxu z;jQXT(K!4fc&zGPh9(O>7G$>7o(!~GtMy}_;$T50>g%hXs8impsHqHu^UoQ?9 zqXe>xlImj~$$-yxWnz59(tuU_fURvp%$kIlG<}#&axFFi+lIR(!iV~niZITDvhxJ3 zBOq*x@QDyr#iq6+e3k=J^DhaenI8kv6VV1_ni!jG73J#u`PCmP%2g*IudX{WVWe$| z?k=DMLB?C#ajrE|nJh;DO;?hXqN!R^62=>4cb>;}mcN}+8SIL@0>VioK}z-sUmK0i zqlm${tqs&&izVHbm$GYd!*Yan6D2Pt<|#3Ma2X8GAw8Q?sN>rd(vzywXvL1BF3N|| zwyRuUr&(;H=UtJ!j9#?@Lh7c%cDb&{5@EajSqGL(zDaE825H6@XKf)s$jiy{2(#28 z4IFBC<}Gxlh0&QV5<3Tw=Y|sEcpH6d9@Gm8BGH z7;REK(MH?+zY*5Sjd0v{BfJ>KNn)MkxZMz&=(Zx9H$=X=Eyt}Z^cf#%OwnB9kpN!8 z905%-boWq-Qc6=Q=|>|<(oZ11z8EP*+-ey2l_E}$Ql!#!w0P=v zZT=f!^N(SxLl<5kn}4!^OY$*^4cW0l}s*^T|8#VXlN z8{2In-Dhn=b+bL^|D@{%otyxdi}Bq zdKSGt2LSs_^<=-P^=&Kg&rh#^TzWlcbib_EKW}yXD?+bN_>$G_7f!EFh?5)o|C3%% zNa4$Ry*CL*EZZ>>1 zQtsypE7x8MvLnNJ#FPg-RqVYSyY&%yF?Ax3YrH83V*8+=2^q?h-^9oFOsX7s9mfPs z2=I>g3Iy_bKh#Ilst=P)-h#I~JUQvOpGmb>LC8R6qL_6Lqjrr%=d~T;D0O5z((Z7L zL_PYB9X$^ECR-XuGADc+_*71(mP-Nd1Nnx?vGzor$z>qt5Koj^E?_w@bRxA}dP0si zs^u7Xb@I}w<*uoI<0B}J2Pk%q*v8w((`#{RMJ`~vrERT)O8KhYxw>2D{e;e`6J4^t zRrmV|ovWWHU$i@ypQu}}C83?tq@E?MV$MT7sGec(X3j%BP}adZ54Fcd`8uJyht7jK zPc|u6-O#Tn`?$P|B*oA6(ZTeUw{$>X&n^p42b-y2+2N zq&`(vju(gngqBBzZzf-u>ej_?}%<)rWpeS9fz2 zRp9QN{5i)}@qp#4{TYmOZdZ6m8z7EKX&_~w2V=m?U#iimObklx#g?Vq-otK1?O|+w z$CR2x7u+tFtc#n=xBKacEA8r9=uE+bOLazLo%MI>6A?9Fw3;L@=N$q%zGV}!>fZ0W zpaheh>IXr-M|E@l?*P7kL7F8+fDRmSK^{o(JF?}2@x}#cSp9_BD9MJ7{6y$PR(M>@ z#sRX!4+M?}wE`Y?MiEY20F9pdU`&-4Az7h5P5w>C55MT`GToj~`d26EcPh#U*;JF~ zmLeh+cTBGWsrEUi#eQ+OtL4Bq$pODFuLcO<^j4;X--E8mfSGAuEBV3ugntdlB|FpE z0XdsEMV;VVnOB`i{5V{Mf+{f=0?cu9lN3|gqVax{Em{xzQ>Kv%LTvsDTP*mmJ&Lh7 z3Q%q2)dd1<$t6Bw|E5blTdmF|zNJFW!x&EfvPe~nB-}(b{2U`!xqgpQc_azp=CYfL ztiSEH@Dw}Wlale$+Fr52poTPlxLk{++Rovtr79R`zPyWRhp+a->X}Vbu$~t<%s)0` z7LbGx0a6lNGXD;p_P305w@&X(;Au+x>+OMkYNK;dh{`mgub{iW(mv-NJ3a4S^>~WR zo7;BKD>?iD6ENeao%XlSu!HalksdBIXPGZPns-&2y6fQdZG18MQ@@Y(di+%N&;KrT z;7OsN(ET>meLSW89Vk-6O|(CTyG?k?xgdXB5Wh5l&hT2DN&V%~g_5pL9VlnU&Z9OB zfwA+XMMD%u!4pypw5bZ`C6cu^rOfr3NhxzhNkp}B%G^N(W89iD_dtgU;+)_&(ltox zO0?6b>XPo_Fs2k+I_=1$q8_|^-kt*`lqJd*BwZ8(z#zL zReJIZ<-9+6q@4AFzs6sl|D#f~4VJxOovd)xV?X+7t@7IlU+Yzm=9})cH+iTebh4*< z2GhQd)GY7h0Y9hAG0t#{*KP9mV|OCuX3Afp$0K@Qo~qt=W^^Y{!1CT3-bG_9+0LBI zsPtSp-^=QiCmCu_Kmi2v@>V{iU)t&m;vySVlT&kSjsiuOq3ARjU6aB2lx|rRz3Oqg z>_1e_XM4#d;rhUtL(}^y^~VJA3H) z3i3Ve-g&^MB`AHPw});AiyADe;@z53)#k+hM$X(nTI;glgeJUI-Jo1`y1LsqeLz^# zv^6VO2tNr>)ts}n3h}R{=>N!){i>qiAZ|)f9g>5p`jwLRx~jm3$@jtgit*o~7IdLd2sK(yr-tqSeFi znckgx&gHkfi$d@IhK}}%A?&`lx~Mx^RUTHe@ooZXGn;u?>ic19+yArkp zyZ%$cS-uBkKijH3)oGqp1cLBgSHgv?Hv%hB%8wOAS77CQ45k5i3Yy9Xz{FKfm(ZKT zncpKpL7(4wK;*9)z2U&TTix?tSFe2Utl%5v{AK~(%uZ+U>}IrOaEKnDFl+sknwb6i z#i}k$Dy6mlJm`O@Tw^@^gt#GA&bSmkVn`H_xie=@OYsX-;!CHRpZq%1VDcD$cp@c# z{2N6e2qWp{kw2fw?i`zYJxPDZhI`saQcuYkqt)v73Pzpc1v)t&NH*sg z**;EIH?UqIWs0V-EC_>)F9-FP1Z%qV;AG_!aik6AYB*j=Gd1u6<^|+|1?&c!&zRJ% zCkd5dI%qne?<${yqbfbUTfVH)F*ts2odUf@nbYCq3{^5eDQ~$&GjXz9Wv=Bmlxm;H z+?%gRhSZ@BUXpyUVo9CT(R{+iF!1$>1>!7p5rF?DP~}j{5>;-rvdEa+F@+5x{UX!l z|EONiSH6`0^CeUSv2P7r5R?4SKe~WTt@?<5R+!`cC#z5D`Vsw+CFipbaq(!}iH%JD za)Bd%%aZD&=JTK8!_DgF^`l@}c>1G1ccC-+w+r9z(7j3O-s5Wc>gr4Cgcko(y8leR zWA*vLm-G}-ZhQBxW38KRLzIyGtB2{0(7WnR{;VATm(h8&dPyH*RTeh(Cj$|i8Fl6( z`N#Px1e4$7_mD{J&E*aJlvr3%A` zDaLe^cQ9XcgzA}JR7a$4{1(4cy?65t#-!hd2XBvY6`(lE)%EV?Oh;<1@>g~H;Ck2YdbA>m>w zHCkAsHYg1pmjOpYaaVlN#-qZ-k-bV;u+TuD{lCk|bElGe4X=pZIZuIPts#iAYHlmPgJx_%Xou8PQQ1EBQtlI6V! zN=xh8#(&?>XHY(e@uLne8~>G5g@DqoDh>6?E2)a$U$clzq1vDnqP0MI<{xe!^>YBF z-PTOnWwi9b^J@w9t__}q|Bu#{{%(y4&$^VP4{l#dpR23-z00pk61b|e&!t|KPuEoi z2bwnPcx>ser4n(ty1ggzi@%fwABylN9HQ#<$kUa5==c>B_B|c$wJA8 zQ%MnrR>{Xya<%$x{Yc4d1P>q6(kl78Dzd(`q?R@%vmEj-RdQGLXZjiO94h^cuJ4s7 zk~;n&t|x!t^cwB%d`v}_cSlQ+$~5}|#?M-=S=t?~v{uPqQpr*EY5j;rw}z;NM9JLo zUyG8p$b@Ck65TfF&~dGd)~BD>r)!Pub*WE<>U^3<^4HO7ds@#SPiR_g*=S1M&tn>C zm;6{NDMW46`H)KPUMTrklnew-CZcP_fuw_O7x#WuB9ghDojIVeT%)xx5u8zG2qcG7 z8-w7A=E9%-$Fw~{WK_PBqPnQtRow#ZqWV|5-PA1*EULfK?Nz#!iL3gdN4VXiTZUd# zAJgq#-7-EgjSRDg^WTV`f5n%F>Y2NNs}AH;oLpB~3rKK`sWa^?#{X3>(UxO z@CEHk&Z+=M7OApsbfvs=X;rP0|yyUl#Wu`o! zUxf6@Gi6&PL1Bhq|JT)c&oGG2#S+nt|)T6E+Ro!<; zkJ>feB_A}*^|=0#icRLr-h4d_0~65^lE`7fCaLs4DCQGc11PokK``Er^LB>(H$`1yIvF*v#+RUQcKml8l zOiFwvgKGN5h^4XJ08bJNW$u$MSrXqQKSx>p6&=tSjnMM1Q--5jfsvzX?_dxoBU9)nim$OFHn^=}1NZ#@Ssb=R+D|>AKC= zaCz;Czb?KNSKSb^=ayId^@=r%5#uB_f51z=z^#R@sSA~Lq3+f~2kJuIx=?Rxp?!5B zLT*xX{jG(rstfh&LW8Y^4%USRb)n(bLRVAh+W>b_4Jic6SPpjW=Ne5XC+Fko{u4L7 ze|qhSZ+QRN`TprOC;k?{*GvzbIQ0Ir(|srA-0hoQb>bkuS4|I|xQeG&pO|oW^@%h8 zzWY5C{&h=o#UMzP}P22Q|>H{9O9a0Ab*Sl<+44kUA{&u)8<+YDIrE3r%GOezQ`}>#e!aT&wP%ypW4|76eeHSV^_pL= zZGG(-v!BZGTbJCR+Vtzqt*>K>2GwrA-o5p8Owpj)%c zI;Ln)UFFwTZG9b6G^noj>#MiE))aj!p)Wo{>MLWlRQnaO!z}FI+Sx?-s%t!!Yqq}5 z3*}nBzIN;DyigAK^?|Lg^Flf3*9W)0&I{!_zrJqk>%352;n%O&`Z_O^SNioUx4w>9 z=r9Yrzfx8L=T!7g!FqLu>TEPc6`B`!uFDHMR-DY+Dq?S+LMHs2WI`EoeD~YB3l>(L z`9og&fz<->MWQEqaADI!V%_e9iQzG=MyKX$Z(Q%d5T!X~{s<0qtJ4ZTD#RLz5~{v4 zI3(;fI3!=gAIBoAgRP5I&GT`2ftt5D{T-D*%53jWwC6!aD)s~S>hDcME;(nKJZ z3?!DUj;qtK)B7}4o=HF*cQKQ|9Anz~`nAu-U5^K;1Yzc18FYibB0G#gTojwtDWR$$ z&Kfv2mK@YYU=ZFt+dn=p6_7NVt?Dy$tW!jjxz3d?UsGqP$Tl~ocvz1w9b=uO9`q}b3F_k!HX8JQa|Sv5v=h7g~k>Wvzz9UQcrUPX#<+S^-w!#k>P ze%H^uf4cANY~OT^)l7S8x9W^%1Az%p?qRkz%AzCn3Z)I3S72Dz6 z;)5LsWWa#3(8!T!B;DKucR%|t67HTD&92g1@c$nEm!(6)PYN1?m=I-d(+)CkOex*H89OcXO=}fDylssR!~rpP0~tVzvk1pV%Ll z+>ROIiD`4oBLzyHIJEeP$1--GII{gQuhxWb!|A>^y?fo!Tf-CgOs_gO-S<6jdbi?4 zp>w8n2SjJ5>u*G1T}9JkksdvFtiyZZKr!6|<`fW6gBbm{g=Pt=z!xsQsmUr%3FjyZ zhu3DCA}TzRDwK{o9cwkHMkZsi*r8Oct76?9i%nB8>8Xx!TVC7#R7{%cbZy7lCaDz!(@v@PgjeOFdUUHOjs1SxikSFnZZ%pc|66fg;%5647G^s4(>&#ki~%Yo|E z`POr*>DWr3|J8%7=SefKhWxuwKi^3S2alG$!ulvo_wv#yQo=Xi^th4j#~V8&1Ip^N zHx7sHXb&?{R?~^TwHD9esLiYRD=BEl}4VAh4U69;?jO2HnENd=bc zjvM*QUIe8^XwKpiwzW_sMph&*-jb>{g8!Gjw}G>}JnOvwFXznLnVFoxKm(!jKS#7- zB@kLlXo~IpQvyUvsTHlyKJF(BnE@t~Op=+&P*#L#8!Bt@kbz3w+T+wf$STOkY2+Xl5D1Y3P3O_pIB2GyC-k*vc}zLNnM;;Iwg4D+;MPg_$Oq2tkyK+is-RenBC%TzLeqGZQdFZA3Oj*yBKD>e<= zM9dDiYwPnw+(f~{(ogYGg;2W-SxIcPLC8?u`a~?Bq9`B7+;EeiJA^c$AXr-7>zJ%r ztI4YwDwkg!mYK<6^~c!=gQ`l0+ZwHUdF$WPGz2)cMj0+}k6>8hoZ$_$;^VWn`el{w zrcfjVfFcd}NSTlr1^Ht^r~LH^q9)e$?H~HbfBN*p@B8`RFK?lETkpI5sV9E(mfIh> z<%#dQpg2+H!HS5KJ;02hDO!g_%VoI5vb-u* zFV)D4CfWsJV2f)QVZg6=I}74A63p-sX^3~|X~xr*&%SX_dCx=KFZ~7s{7Esn7|^$? zS}b789u)45;HxecNt3nMkRFz9C{mESVSw%HB6--r1D7$Y5V{y2m=`LV36R%rJbs^Ha?!g(>cbZ^pL3tEm?=4WB2zdWY{K}^6Ebq}K^@!0i zhpkN(AYAoa$88pUBypC=VljEW!B`lSFqRo*%eT1radm~NMa*h(})560`O|-v%wtjs@afEwE)DA*v*?25vR`=JsREkte=5e zW&#)Ot$GprV0b`VTnObN2~-4cMM{seb)-mj(cGoi^nfv-)oQwq@}$q+er{s36AzT- zsLtOK$yDG~43rhmF7NzpX0+6ms?XKU8&)>OVudT?mBfNVRyoptu}yiFL3Deq6-BoaBx#IS@gX%7DC%n7)Vktf_hiuUod(}W~eyi zOcU0PcyD{~7VPV5*Wvv+1_Tb+l=HKwLa*zv+Pg7|EY)mdhyZQ32$&%I9Bn@{N$_DY z@0)B9(f!OAKi&QORP})5Rsn^kTBJ8vi7*3N+F}`T6Ue3`n@Q`2b~f$|xbL5$nWJeY z%Bd)B@L05%)wq1nsBE>-G8{=Oag%;Z+)T^IX^;4HT7I7E8D!8b6eHK2r1*j>+G{F7 z+d_0GDiOM!nKx|;tBKlVg9hjeOhJpu#eoe4{I4%-?;cp{@!)i3lig4V(y+;$Rr4a5 zjPd0(OQiLlNG5;17OB-Skzo~7kg3EFzXuhn5h;%_b%m!e&syhg;OD@ZLE5gfOCfze zK-@I@+n|tm5i-aQjGXYc4#gAEu5Zl+J1XuSKrrr%9*nyOggde-#uaRk^06byri2rT zg>k4-M|keWzz4dY!NB{wpYN?6gf_4ULmQ+WZ>t0Z#vbb25mi>A&D|98l|maRrdCaM zC_`GF@uCsvSMoKeiAK?cVgMBFRuv^#g8_E%-!nh(O@j^CM9AD1yr`WE7uzxks^L>_MW9ysJ#hPb?e77c=|+BJ+4eczrh`h;W7s z0ax4zVpw*maS{1iZAh3);%Ovs)`m#FT1w_M^S3~7q(ohSxygu;?R2iH4B9r>u8lU# zHqw{yuOD`qxEz5JNEKS%kAf-M3D%$mdgy9+A+=COj8#OTdmh##WSUkog)P3hkJrBV zCNVB069IALstJyanyx#hK9z9?3DAr;p=`kBevo216YNMHiR>~!dsid4jn_M{dxVMA zfSs-i><;L3Cs`y26+`U-oDuJD=cD~;f|V(ejX063Vrh%4ClzxPqy|VQ;%lT0rq4KO zQgBiC5@{zAmkbP8V~!uN4?bK0*4UJR(;5LIbdcIp!4qP(YLo0k4a?-(igc}3`OC4S z>ihD44HKpDa!r)me`nQ+awo6XT&4EZ>Uo~7@_boS>Tt~m#HAL{0B@XV#sBVb3rSG& z=tz)i4a7-?7|+Zwk;ABq=no3Fh=+-3h=0oqAyh<#G&|;qK*N{;qG4P_!=QaC<21b9 zpzX4ToFNAw23FjBK?E|T6F;oVu#2=n(X8G{Y!jL0B4ZB|Yq)|Al3$Y03!#H6ghU@+ z$*@uXHX-r3YN%EbEPZ%a%8z(IQXpA=!yu?xEM>$ZP4l>F`#;aD!nlcaAnn}3@YCxNykKIsw! zCNhpmIP%17VC#vyA7OM&HUOm=EGohCj3+6SWX(7slqRR|36#d<3JGq>it3}RBPWy! zPT*p(U^rdmV>~B0>GkQb=j>B@O)M@<3W9h~I42!`eKLr1rh&$`Ie~BEaZ&xo$Q9LV zEQfc1Mu8;?5f=8ZGv04voEk=!)`kG+QTi|{(DpfKPv-gL4rq&$y8z4m3c&Vle@_Ki zfOE2g<8M-dqw`;Jau;AI2H!H?99vNfEZELbJ*R7Z>e|U2m?$YQw5{z;1MF~;GJMuj^jNVrp-mP1z~41CkLV$IOlf?_8EhZC(c?V+Z3 zr#Mla;23X>?up(qOERRhzpIZL-4ivsyzBJj#Nxy-x-qAZF4;7?eSG%OH4tQBbhRg$ zkFJ400tGGZ^xrHbp$ol%HPZJ1aV^hM-179RPQUJE64&ThjR7x);@qiiPR6y*Ep`k{ zE5Z`qluR5Z5Fw}bW=Ol<)?_vxYJLfm%;w}m#GRV#=2*cmXb(~t$2{3&w3$GPEw*_s z(h%1c3tap^bO-!^j|YNxGuirr!>FwVU=7sz6ycb+weVyg5@b zR=J(IIuHx9$!~>#2)J7bmKbb)9;8%jZ%5_Ugzw@bD(8;0E%`F0W=OlzYKt-!Sy0Mf z)B8=?QYgbVy;_ll!xR;d91b3>~9xg6%SI?sA^9!4pOD(Mi-U$;t`!r>z^tR zsWlNmb;0X-07Exc1&nPJ(=d2G$=a9NT=~NU>&w8F76Us*Ms*D6>TnaDIjDQ!27lkg z;g74sjad>qyJ?~;)$Dvq z(jOKNKG(cYSNJ>)OD3S-DH z@G1F~Tk}hK3#6UUXi!86R2Y23a5v=}bVb|x;uh&sBL}$9K!x8~>)8%ad$Hm}>674S zm3cK9TIW6($rvRX)$fgBGWZAf)AkQlYYF~_)W_il(z0+lSo+0MG$ahi)?}JnL5R5c z9TYt$s;(X@Gw{ScXuv{dk@aTCAtMgs9M@?wZH6b)xj?mtfQ|~d+qSQ*Hz3IpcC=h9 z7o``ZiauX$D-Pc|!`O&0Z`Q9umd#gVWEp*Q0KCG?yqTTV(u3O)&Ph>&E}(pklFsCA zzA;v?DSNBN7j`D{vN^d@qFG2NqFxhUqfARBY06GC6XpIoj4&@prlWU zqa1f}19zbuyUv-^AWT!siPO}b7>UZ>5u3yY=A9;8)L@+TU0Y3C-J6ng&QDI)p!0IW zRuf?KGvvB) z256`;WZOkdo*F#P8j*g@*Bk>mcMjkVL$(AIb|zCy^-_bF3^5Z(u^D{Fu?sHR-~5c> z$RjBWs>CW|Gs#2&kEZRqVcR2?){!u#1q9_0d+&I5B$Q#C7=&3R3w4DoAljMvN7L5n z+f$}5P>Q(?*@{lD3V}SpO2Lw1!Zl_|&6sc&nXvjLCOlAl&HG0h;t&BqaN!7`4i~P8 zWKq;9OpTzO2tWvV09T(v>wAII=fHU$z@X$v2%s72?P^fy0B#K^Ach%dS_Ql+oe7hR za*3DUAzt1uUJeZJNohXvg^{q>Yc0{CtAPz0&RGEr9R^BFy2xlw?1yi6=k0lzr=GmA+=sw$PfSG*86U{Y`zK$q#g<(TVmOse-ke?yS;RxZnza%9f523=6weL2&4Z=31?=HfphAQW3e<+ z9w>H=&Z#m^mG>$GS5>pG43WQUNOu3u$`p=MX| zAqWEkX%Gdtiw8xyC}7@5+gX_a9Pv#-kE0zB2y}N*B?%ZW3Vk3DpSnb!(4Jg)Fs%+8 z=#miBzbB3J;urm@LNwU?>{k_{=TnG2Yx4HXqY&LLBKlQ@=tU|-0eX;El#>556e6d* z|8^B3(Qo_^EaX5j(mon>6r;Kuor0aWzB#!dh-Nj^V+cHNURNna_Qn{AjuwFbVXGp+ zc?wpM&`Jo)mbMNnltAKgRrg<|HF*~4nw2~Us|7B+KeN5Q?Zy|_h-ALh%#A@bMu)J2 zq3SZ>*lCDlkr0U#jVv$CqOT)R^R3ldkC4p5CL)?B*92t>mRJ~N?Yfx{hMUibOw;lM ztS*w-imp6`sr;1gmd72M)Psd~Hku_V(2OM@L0kTQ<<|}`((#9z>-F|3EOERAK$|cC zyh4}jFrjdn6hmEJ#Gn#WTqxr2fUnB1*OqH<%OZuYYgy@&twBy5&@hG0#HLtRI!!^A zw{q-tpVk)X=n$mn2uqWqk+bRFDXl&vaf<~5B4-I=gGifwHYzNAk>rVB)!**hflST@ zi(zDtU1Dh+#o8oS3SCc8CX=few~YCxY7o@GFa-S(^cA>MDfX-OktPRN_4IMjv?;Wl zI!d!d>gRqBZcs<09$vq)q!&1FJ~IZpO21XSWE0kKDH_Yp)P`T zQ1i@XyI^}eMX_QF668)%LSn>Cv@|7)Et$e)RVH=)C*4Yy~rBMTDBKi zigc>FS--mKrD$&wrk!PVdV7>1$d#-Zq7~As`amjWOb$s77T&5O(h#mS9Kei9@HWbX z3YDd$zA3p@T7p-h%||SMe%01wCwG_|%w%Fi5XT-~1~hK;N%#&>n5Tsnvq6;$`3wzG zo@vn_(S)8mjBU(K3pQo%@aea}ju4h}SY|8R z^d8T|30&$877^C~ykioxNg*GFMvLW5YV4p4N$a7IA|R^n+W}qv?Wo+sc2`Si*0^;@ zSjwSu*jBh)>Rr7E3WOipn!vh0Eh8M1xe+qTvM#f02{$cULydIDMq9WbYZ`LW!R*i$pTQOykOfF*I8Vo#Dz7Koaq(N$@;j-nZv?uHUik@=#6A9&A zB(4eIoX`P|1w(0Ve*lIBLTTp)e^zIdSw|1YpeV|q;u9D_A3LFd1xWQ_gCGuQh^mK3 z661nAd?t*cfur(k17LZ!L6Z<-;h|^Q_IG-O6mtc^u%-cQHlH@TRg=V~Pm|F?&pa`q zXP&tGk?{1!2whHD`NCs|Hg zm%5=vN(Cnk7WfcCa8joDtN{dX=`&i2B-vVbz|gb`TNi3R5fpF6D;Ti#(wZFu7N=gYB8tsh0Vi}bI9!Lo^Pxb0m8&L+ zNEA9e6M!{+2&~`gKF!5%0mcP@X%l0o8*Uf@xMyaz%*mjb3tNqflxb|h8c_Q?iMIdh z!PX5j_{p(B20V@DFe+=pEuft6T;rli@6$+Yscb+AZ3*#)hR?OmM9?(E@?;}H42#7Z z+7McB2vv4aS~J|rYEws8iiT=KZ=<*tdTW}sX4AUjqyYSNA|@wwfUoddAZ7&lqyT)Q;1=xJ z6UwRxUNs1~jyPxT7iS(3XCCR|%p;5qFW}5=4ly_0JW`AV-fVZqo7tgDyjdZ}{NX&F zcV=EL(FvlbW8^WW%BDzgoRJR48502;p)G;qaHguByE)DXNsP~vSs%@bneaePEi~7F zj``x8G4rL0oI6QpEehM_*7JlRR-ss#7(#k1*3HFPx2iY$(;8nQQ%}3u@rc;5K?b-u zJ9c!_P}u}fPA*-v5p<_4`s%EQ+rA?m{_NQjd)Ea?+7pYK7t8~J%;1!X? zz$Nq&xajX>k*dGn!4vLy9Q>@IC4@5Uy+MyN4jR`vwsMp67iR#?b#J{v{yQ|DZ!MDI zmD&{}DPUUKf`Ez176dl`c`_Dei+@F2G!V=G(-?0V9d0($tQpQevaWHmdn*~nEUQr! zW+w{f>^xv`?>rFY)p;ODr1Lp2gXkZgxvRuKzL?dn6 z5jR@Ms?~+!wVkEcHi@?z)voeH*e%P`*$IRBu9;5bSdu3(&36o8$m#0Ghq3`**{}61 zw2~?`G0md99RItY3nKEz&DyNDdMev_`6tQD=-1ZLzVulC>S-U-CNJAFqm3+y|Fpc& z1n9JRE83PvN-+_*BZ9d*-1}nk{%LaY92Y!Oa|F4jQVazu?mY7#G?ttSsyQ3gA-8K8EHSL>oNJgKXKp-;ftb{E^Tk z4=*u#v)QHKErO8Q#FM5=CGJ)26SCk+1#lO0mS(6@lTnV^Op_EzpUMgxNhD;!Ix7cI z7hoH%@z8}niY#&R*+8JLcvl#9c?SD)hKB*FRu=#{#33yyNLmAM&$1ui7iw6a2QrEW ztBGXp_6f>>uYrKl^b7c8SD+Yd*upFOLX7Jc3tt2%3II!9yEQK?c9-|s=7CvuDfJ-@@D~?G^NniCRcZ-kfWV}ll;g(R#J5W=pxB0|(`cc*d};i>t%^yb zDvQ0(76gvWT(A~bOuhqFgY;OXl0WXPx~u$gZ&$?&Y_(@Q>8#p3nhiSvZGmOmBm>4s$ys5*EsWwS|1}wPV*U8YnbBb}Gq4!p=}CvSnx?&{fX-twKWY z+NR{~@@LW%VWd-8jg9k=-$GEW_VjVOYUC+KAZt*B>rUYYMRL+O|Cxi4;xwJ$XgtFP zgxtAO1tKBvvJ;VL5YsGe{(dMW7wPeIM+KQ4k3X{mUG>Fy z#P{UK?uroD|9eS0W%D8}vzgIaGB}umBiLk;^2kSyBwWEZj}U(9!$%Y*z}9d5VX(N^ zlKw#tx(-EX?>bb}o+9dQf9e!b5$;fg(8?F@gN^|)LAqlDP6l8bgz_QU$^pwC4VylN zed%o1IgJi#w?;=;rtv#w@_&(I@f0B%-DB~W96OwAG__v^GNJ(?ly; zC76&JdgiI~-qa%JVYA0Vn?uTSiQw^k6widNisG?Zlc={Se?{@$`xO9(pBxX*E$;*( zT#p3}ON`*tmH1@?#t$AR`G3iYf8Z%;Sr-dHmx5*GWksx!(lq|@gde|?*qM||K#=P< zODKZ;W1DyV8MdIUUn0B8#`)S+c?c22YIfrO67yxA(!DM+i zVJD>d~o)sOYls^t&5w5OeGU)U-KC zS}EFYKHK8F*D~SuDF43mG999TD6zoiE_~-U-^<3ASVvDSWDqE`Qld+G@y3FKQ%K_) zY+ajo)GybuvxFeZPK3Gy)9E5cgEZYX?BJ$Gur6h3Z+YfQpAMtLwyVSFhG23HHK>*F zt3$;4%Z)PA_Lry94jRhsXz))^!d?)^+SzR-MB^XE^q( zoxQ)^C{JEo*^+)7wq~HZ$PaHnZMCY=>2%`CF`+ehe4rSLps2tq_yxeM4Hj*E zvN~AMzgHWX5wE9nOx&^IwMNHBfy!E-T(=e|QEcUiJ|?u*A0Mb2({B_`p{`Mu$`j1z|snb1RcUa@Uo$1=7}5 zeokA2OSg$;hf`o%MXln1n!JXHq}cr&<{ANGkHElQ7-JC0xBi5qxccB7tQW#WPS*gt zAJ0fTmv^hSP;NO0$HCD9#DLwY#EA5jx2q~Ul0=YN>VDG6Ej^_UEHBkg{ei<>0rah0 z_6oX!{bF;4#Hc+)u`B@e*r?TD0Yn)ZQT&$kRM|8@oz;S6dnvgj^Os!0OLPZY?v;}Cp-+W&)>%4KBzuINgR;mDAw)cp z6-XGv^3DwsY6G8#moGhY`n&B~e6!vc9Gty@3o&P7era+c>VCL<_E(Q!0kz`F!yM32 z-oYQ=BVQ*~l$*;>lcM=4CRsevq|Y~tmArB5AM#CSZIyR&SN^;5A-zyyVgspdz%Ml} z7UMtYMo0;ZCZ@TEOnY-Sj z-dR{KXM}n-&K`AK=#uXv&6bC0^JB==;n4R@*+tMRpfZkvsdfzPXv@ z++5qkBoK02%&AtbM%?u%*sR^<0UJBwCDKtGCmA`f<*azPM`DM3oIwJy5sesk z?EdW#ml;m_5*`3vHt8ezWeR)ra~gMM1D~~TxOSsOJF<|d%v#Kgr%7b(h*QvGH2yJ+ zDB2Z)n;D=5ss?vRj~!jYfI{B&N*hzk%~jrr&oMVN5%vwtFw$I`;kv=D^*#K9pyf1x zOt44kJN-Hs4fRDN4X(OSXtN8x$I_s#%EIBA+S3jXbf3}(M8jkVamLZ<18*B>F&;KZfK!+S|M#5 zc;D&$G!l|PyZ)S}O<{N3<)`fyW^e-|FV z-hG+x`?%-blxMpZ?gto~FzcEN10r!}Epm z`())$|B0u5ofALC22uqf@bQ#bqQEVu{8{-fh|d|I7XUqr4@Qcz@=*h+_NL8XR60ID zcOnf?4elW7pU1(_~FDP^X3SZ14Sc&{PGQz=5z*p=pp zE{HG8lYusgXl~oiCH0AukC!Kq!;b$f2ca?LD=sTBUoMpITUx3eWY;i6z*LnZ5{K#x zpUacv5^fw#Id`eVKaep=RfRKwc8z0r^@78IWYx&1#?oNUFrm3lWo+D;)N1^{{9pGg zV)RwKfkk`8s0~bZEnd$p4i6YJBPV zYH%RzLrxlR?e&b7j}v@Jr~v&f>X&VVGFn3kKEcmv?Uz%BZbFb1mx}W{L1P0m1XIYH zNOk`n2rvYFMg*AmEyyUoAC{|<8Y!3Y;k0?7$$w2#Ma%Rc$m&o6M)htO2~S>~BzeyN z%m1zNDkRD1@-gA%AL&MUQWtohTc3?oF#!F>ZmrD$Sh-Aa1}8AERTp0p=>;F-aEA5w zkE{1^H_0LC9?=bu7;as?YNK$Bq?%dUFi^O`ien0AFvNOnQ2}*`ht*~z{wqix0ZZbA zMe|FpOtzn-aBv77*~*VTj{IQyURNPlN>xm2h?;YLYLr?rz;Dn69`O(Aw>GG@wqm@Q zax!DmEKVnV3(xU*2A++7*J)+uoSSo9g$@8g(vH1Uge%ly3T&41y$ z$cg1sp;SOn+m-3Mu75obUGG=D^~4$9^6414bMIqAFmjZh?sU%6rL{(XF%~0GxY~=A4rfh(id8s zZFyj=lMziZUqq8H$at_Y*OtXh=9)S0^_*Kmtlvw|pgbUEm_Y~ipi`zqNgU7D>LVGCpjS|>CS@& z`y|y4FS1FRWz5M4=8oD3Ta%9dq7a4|28PI+#9_yTI-}B>7^yAC38FOfVQ6T%^nl2I zIY5$uM!C-D%@T^gbpyJ-q-bR;dkC=5oh9UdqP3SEN-m}hAOZ+@z&Ig5DEhU#BPa4y zS6^dCyl3l;Vk>7%@3G}RB1YvId%9w_bz1Av z6B7D${^F+}*ImIKXG-15Yd;CW7O`2VF}&KCw1@w-RWnMZR^YYQdC)yAv$t$+@8%_o znnh6&8-I-QosDy1DtV=%^b`n}U8O*K8w<4JJrZY7lE-|@1(AjeZZMk5Ow0Ee>$v={ zu@B);q=*4nDaK3M>Bu=~3JA}amr&axiq9KeZpcNB7(P+pj;E`Ny+Yaz?`&GdCH_ay#-*S!hayOw@`X2M&;N;lsTypmKZ5=*jJi^ zl+t(;dh_v>@uzE+vG{^oQC08Ipn(WUFQ)15PkVKKsd-w}0H`-~A8 z#%x45UG(!7m16oRBAYUriIUNh?wOVMR~g^Un@tS4W`nI@;_fW&q|IhvxE+nhofYb8 zZ%|fWLpv*hv0Q=*_*sTfQRpFCI5FjQFi|*TNOPg_Xqwe4$s5xpGlBVK<5!3LFdcK= z>4LNjD%z$^cy;feNzvBQHCrOp zC~q#u%Dew8Fm})wgQY1!ChHHhYepDh*_hvv4~7FvwQWI?hZC23cevI?fC)tQH@~P>BMYUXHGO%KIN` zfmOULwS1wc7RS_=epo)!Q;QV%mjhyd*;9*ijF-MIpYN%~6zRMyq!EDLDgN%~>zp4C z0|X35U#@2Uy*E>hQ9VvRv}O?B2T%6m8W^R4*x02Q?NiCYa~eg3~GOEF;Mtr zH1c_xAZoV9S{`7^ps?Ek+D90bC3#V>w}`5_8Zv56QJcVp#J4U%eWWx|r#r_wOM|Fx za{7uKRe`YvR%(kRUvt(0n~EpptMMdXTxC3oWhBVx#yeO#<_?JG z0gyAEikXpGDB;{k{)xuUAUXvGnFaMDEzlmS=76bH5z;D@pQ2z|d&g6zNc7*AHQsid;?$oQvXa<*fi4@!9wxhZ7sr&8cja+QdH?(P3;l^Ko`Q;ZwHDbq zF%=a%uXotFlVU_stDsDAbXE8&m;l7kf4DkMDHG%b^B{SzEg){|>Fx0emCJg$8q84R z!bw0xF$L$8D=*+3j;nT`uOjX!FN(vLP;aV7%>|yn%6etgC<7D<&O`YhF^f@sB3y9i1>$A9|t!|(g~-!GAsSH$XqaEjG^B{RjbO)_}-B%wSuolIrKTjZXi zL~@+XFtF2nv5ZzL?+EqSfLx{*CzGCS_$+N}d70}PsBOd(nAHD!HQWFz%+5j*!v~JG zWKSk(2O71u#C_Cud4DqLCmOqZ{w9u{H@$1@2$->G)7c8uF^1UmU~3QGPNIAMg-Lff zcF>Z<0tH`#wM{^uiVn%{!ZQU_p!g(E%A|bUpg+tVD=ctYmY;LX^E9cbDKD{{CE9?4 z6n0AQ1JsYs@BYl@&wv@i2BC+knz^P#vi-v!`QhLF!0kt#_^F$H9{jI&{oJ$veBWRF z>Z0a>O_ZPh;^*%AZ}0w%!)(k;-P@o2;%|QG-S7Dyx4upZ8er^y`H<<-jTY1^;-Z;| z1bj^wIF=TLHyVGC`3?be%)&kR*h)ZPGn|x&cE}K!X=}3mHQlz1NzhH4(#J@If;!5O z5Cjq|m4cxeJmY7{7V0N&<12QL=}l7=5mU(5la^hELQWx-F-G7c;)e^G6f+IBd&pi2 zlspACFA!9|1fzqU%Dw1lku|9#)re7KDBeU9@&l*EPT`?QSqa>NNPW-?k!g*hVQ_%Fy=w?QAwr_Enh$2$eop@tN%OyF5-l_)bhr6G6e?rYwNldN&0oz3?R&@v z+gvd;pD3C#my78^-bcF}c-;Jz3=aW{`4-2sZj7Z5O=6M5(=BDErnomjbcx2|FLUY1~QiI$Z`Th_^u##HiQpm{&3 zaY@T}94zm<<3sG@f>0JA-3FEUlrJu{eVsOlANwmjpkWC{!WM8;y>e%Bfhb9W&_z_U z@hh1;`gCi9{LtAr?|B%use{d^N!P~ct_Huh(W8>g*=vpyQc8qV20;W@lFC*qRGAFv zwT*rZ6B1#DpMat?Yx#gxn*b-i@~CRESU5z?3_uEjHVaT}(`gEYz3Y8Ip{>krJ!zI> ziVvij7ZVL`qr&Dwq65?;0HmB}^?(4bKV$;+}h zg_-gV?pinv+>U*V^E(@lEynLgD;L^zTZ~U@5Lfc%&u7efO^_m0L>Y9zfyhz=yStQzmsAXgb zupnk2(_vs{4>H~5uw_^R4)>$*K59G26hNUP<;545@ltyW0* zwe7@dHDC&~y00SBqGcBG&EPOVb#WXyPkx?tm;D+sz0AkySSp%?BSuRgxgbdwxrGZF zOUz+UlHXDUB$=86zX6J3V7MeIYHc-MLf>GOErlyXvDsQm`Vm5o_%z)OcwV0=lFK&6 z&PzPA+*0or+sR@ZI|Iyw#$_cT3$P=2DpK1*8!=8|hEmJtq^t1SWRJG7TpQmouwCMY z#6IJxZr{rnnh)SVZT_1064oK778G~+NS;w~{LkXMvK!#a0=Q6g)d*c4 z3*O+WcN%|SHj9i#On0N<}L#V4Y*8YsVkA+MyWH&8R#ZF zc0g;*$>J$QKZ)`SRvEnLD6hQkV()2 zdrag3rIt+9Q!9TdGtgjGYx&;&rg@l^P5algZ+BAO3yV_}Z&Kc^yCK~Po_6NCr7yHG zickf^FZ~9mwBIkt+7_2B2%?1e3>G8WQ;thPfRMILNJxw&h15Qy^@bS@7I%@*k*x?i^=S*(}>TOg|%4{efTO%mI3%!)!VM9C#uZG7P>N1t#w2TYcR zGNA0j7b_w5jJ6s71)>6AwCm_ClsfYwh=>gKv{ppSP@L9UdkIVuF$6O{bhH~4DO%rK z9=#BuFuZdP))}~}Lq;xw+Q5h;8o_GLwuN%7+02U#GHEXKRf8S;T9dKsDx8+cruJ9@gHcx*cH02IUIJtaM& z2Z_#l(6##VQ9V2uzUW#ddqfWpg)h2R$sW_g!{LjrRkA1b@M!p=YnANNdU!m1(X~qU zj2@l}Uv#aKJ*$VO!xvqvWMAn1vh@9Y(HnK{Iem!_x9W@D=*!{mhuig_H~Mm0_rslf z&>MZZqx<1*J?M?T+|~VXuO9S9U+(FCxL*%?qc8V$KRloZz0sE=diY>?(6##VQ9V2u zzUW#ddqfWpg)h2R$sW_g!{LjrRkA1b@M!p=YnANNdU!m1(X~qUj2@l}Uv#aKJ*$VO z!xvqvWJ|)*FN80;R>^MFm*>J4U8`ib>&s#O2q3yv$?nv{ZQ+ZqRkFMFa7Xx}YnAL? zJ=_((=vpPaUk~?$FS=IA?(6;%M2S^cy_5DJO7xOClNX%YnJ8h>0HQ>KC}E!YmxkWS z=Hf)vg~}QxoiCSrXjPOvKteii}44Cznw;Lp`RyW&x=| zO7o5dMA0*>a5HgEb|`Yd%z7*XNXJI_+j7j<9gCi4nm>h=!s0D4rJBD5{)zv!$PSuc zDL8N>^i%50L8a=n4eo|IF3CsQM>?{c$h>UInSzlMHhW6^UwfFrWM(E{I9%|_5cf6qmK5Y!Z&|Jn>sYg zTm3zngYL| zf4D45gwTAD*S=u?ML*q(1G5LfoSxMNp+hVsgNe`VkuZ=LKl25knxumDK~TzoN&yMu1=ffB;IAHjAXHsnG+WAQPQ#zresEI~mH8X{k; z%(e&Se9dvbQU{R{I^Pukh(?!$XJ&;*1z{Tui!G3j(I;ewX6ZWy94_QAY!D9E9nnNu zh?1(pt{ou|Y;ecN?fT7<*g1iK%5nvRfH|rvlnM2>p|Sa4jdEQ20fLOGU~=K9bsuwu zfF?1U{LB&c2s?DA`}o{=*`)i5*ElV7U%qE`pNbWz<&IA4KBqm=WK=ai8G1u$toz*! zb7&`vw{fE8)6C9_|eBEFK~TMR*PjG&E!bqVs*9(PYrx89o^ltWpU61G@w$xgaGaM^0K$Yx#R@(W#j>Ho zG)K$Nt@Wja@YR@73I8<#33@bXAiU|!L0b`zWw@Ofm7Z%1`Av}=Y@CpkrvSioQE9Pk zUb2y|uD`wGphYQePHJU#kQCL2^kAbw8hywQ?r6M9)tSbJ%{ZMaWl%J9G|PcBK~uRJ zU!!zsDHrr=pNG3XTj&;PT~||Q47muW5qhe~n+PSaQdXqtw{rG0{5oS-urpj|8mA~Q zlIF5Yxt7{60&ZS!g#NL9>tN@0(O>}1TMzd%o)T*59zzyPAO`m^z6$?=_A^f9@4OfK z#l9R&E)rs^TA!OHu3;Jh2rT}AsH#h%sL;_rQU;NX2e@heSyq11OGw0|pZ0gKUn<_5 z3=6L=(cK8t@6?j{Va{Zmmz$H*@Z*LFK34wlKO9L)qnI1K73PFj6MslwQZ+YxQAhy& zkt~un{xGY0nRqXKiS)m+5T6gwC78p@6yw8^n5uLk2y2kPoFJbEp(=<(y+J;wf_LE2 zVQ17#;Qxn8H$n)K#<+_tE-Wx~D|q(dXClvj=(A3+4A*YswejpH*j!$bh}AY!Vugry zLSqnF%ml&l%zV?<>^ulOYdraT-k#=Co{fwDycIT*>u@9fveA>wY2pOh1&TeYuHtQ> zPfyAw+x!v&>89)q7%-Ot+qs?Zls1pr`_qK6P(Yj0&W+SoTMdas;)0O7g;HOcnu)e% z9Y&JH6;{wd%7#8)L{sR*g=F!-(zW+x6Ec)WW81<@fLg_R?nMpTI-j zSgt^I-qwtLyaIG}0>=^%G~ni{>{#F>V8*~3fXfMW%E{>pvBk2|dwaY3YXHB{Hi)Ce zW>L|eQ#4Fw8}G1S??wCg46C3Q0EMFJz1BPS^jf3isMOV2bIFnH%>n!$wZu?0*J9wmt}C@c@-3v^sd zouTd`m3`g2N$I7=#O68{F~9ciC`I%DJ4yj!a39;NmaPHQy01Ry2a1?4S$WpGp;YEM zY(ptLxU4I67o`Wjn8T3DPOjQ)sg%UNJKi(Iayi@Z^9x zO^GHKMGV12(|NV(laQO^(;l2+n5r~=B(Eu;pL;y;^trSTA@bZr3MA3d_yc6 z59nWIyks22ER)9NPKZM|tTG9hguRt_ejb30l+Qr`O34}b>iM1egYc$$ZsorD4?U%g zQY1h+H6JISBWZj@oS1$DZPQw+HQ2ZPy_0lA2Fmc(qcI`03n*jW^HgYWHMPa5ES8O% zVR=W>0u-`ls`H8i1Q%+aFA4gBNBF6wao7+5R3eBtnk6ar*Bl%H@k|odjLC(Wq;4}! zYMKb#YxxfF^06{q0JYk_JClM4PSlht8(+riLJ^Vv{kuEZ>w_Ii`-V_sz_gzTkovOC6BGjwLIFS z!?vNo6{A6I5K95on~^2Jzr@Y0tmSk-?ZzJP7OJ>v=Nx|z>{I{&H`;KgTKgMo2`yC1 zxwU9<)(g+gg<9Q*UYMN%{)zy>Y%x|FLf!O4Siv5uEyp49gfB>3H3Qy8%^yunVprH+ zLUWc83cE{|wrG!$X?|9YED|WxTcz9m3VtU3V7n38)`;}5WpDCvs{&D{rY8LQ&EKS@ zPpP#hJY?MudG$cUHFB`GzQEobRr=T-(Mqky0+muo!(O))cLUpixRwO4Cy< zShzVUNGAd#j%Dq%HG;Hjk+Rf0-4Wf9t61iWV> zPZhQc$6VKtgCA(~2{7uT^fZZx2A%9?@`Ed^z1B8Wv8vu;tO6RjQz6=_)rt!3@dDTU z(73dc;EgOnX5H#WRib`*AngE1G@W>0QIri_Bs)6^bNcgj>VafTOu*2or~k7@YXO(X z!fo>pvW67w*JdPbzB_L$rB&rxmM`Jh09#~^?nZ&J@GE^;Q0N*Oje6 zzLaF1X@0`w5(akF8n?b26)IK-cc%{ZEEK7!7M73FINN}D+2enzr$YiFHcrQ(Gol%; z#K>?Km{J(#h@Xm`aVdDog$Q|FhJ6z<@$4q2G%z0nyH{`&(?aSq@JDLZ>;jr*goY9t z6A-6wCHXjCd~?zo1;FwP%>}xl`!jqR5CCJ+AJu9hjHhQZLYQUHv`TEXnc_IwbwwXsS;yZ63=kGQiO)v)%}9 zGTy7F@H5-&rxqxc8qRy#1F_P1GVH~*P#`M|L#E50vKV}B4Sk4KOO0eH42$E9eL+fq zgP(($jT?kI#bIBw;w1Qo;<&Zq?2IWLFQ;-u6NmvBgO-L^Q8Lmu(gtu-fMBheU_jP# zilS0X03ACSnzpF16C@$kD#C`5KRqLfzM+LXWh$GIj?x5E)5b5=5h4)7#BMeGr?m&L zq_z7v)B{9-EA-JnEWDc6z)?GE@q&pizdo2IK4!~rdv^C7v^3Jm939x!cL;n#U1u$O zyAfGTL2D|`#6uU)Y9J!SWZ>`+J%eY3O@0TRXR(W8zm?kGl@ewpL5J=5*f!0Ce}xsd z`e-(d5@gg9pe`HQjjrm@<@;vT=z2D;-l*Y(Bc^m=i^Al(d7qWX&Xo*}g6UvtAmciN@j8UxPx;g4_*^~?lJ z*z>YRQh713N+T7KRDl%CvNn^@+MKABHgbI|PAWw&W2L>t88nE|QUZQRj`W=7sFcQm ziWkbv$zHD1k~Sa9WLLGFY|ZbIxic&)9|@c->RxP@rilyh_l}Z>-E05YvWqZUSr&q6 z>=Ko6xxxVbQNIG)KW>HOgKG)&4>a7ghfWGx@UiRtTco3#>I30lnhy+Q@uBVke}5E^KSuy#eFhb$4;Ng_Pe=p`%D{H~?x#JG}H3+7~_C>slq`WBeo(!#5J zT%ap))_~Tsk?A~(^11iAkVWD)Q{)m*s{gW$ZHR_e$<^JU!Rbq|%A>{z3@_;u0>%u} zgv(_{Ap*v-Dd^3%gcfs&?2MQWS>4sC=A=dB7`|z3$H(im?Q~I>TG=X(d=EYY+nN#Q z|81dFBxhnu@j!M(VxEB}b~PGDGR~iH{4$Lp;(w2mLK@dk1+*{Y!jVk2;EEPA(`w8# ze3XikML{W-zL(12m$DkOqAi6g14L|y(gy?r4M2KOgu9Qj%12^DQFFp-;ewz^psOjz z!nlMdEsJAhSv8!E+wFkyhw}d49G)p3`Q>le$;*%Z6!&$sv$XutFEnP#Pc`)RlON~y z$2RPo)BBqamOuK;(8b*S#Em+2p;|N3%Wd%mJhrW zKg@^o=DQR-4*1gYmp?N!Q!2F6+oe4WpLzGfPU#MO{pZ7^-&FqgEnI#qEC2etx&CaX z?BbCP!_Hu&LW)BmRjslnxWg_lK|(39+N^Dz2mtbH`6OU+F@!BpdBtuoJ#X2qkGE_B z-}6?h@oY79&szxyh&apA4%yqWWpCxMO6RUEIT0lz7_w|=kDi=^qHw>pu>^wf7`A2B z&Scj9QJxumN!ie@Gt<7Ux}$I88QpVEYAMzhaFJ|a(wCx*e% zFnJc0sw~)qs{-H5L42O@2!`2e95qK7gby4p1`qp=oYMG_lw@rQHl-pbzA9jl&V|Nk_k(KgHn2GAq$~HUjW2FFO=V3F$9xsW@KidP_sLOc6OChcYDgujqsO{bbk zjzzsA#indad8gWK2Ceg4z-^gqMr%=3ihetEm{WGp+88qaoSQ(OWfTf>u#XFk00q<$ zh7B{0Y(MG@V<}S(Uex!SQK(+t%k@0IXqQUwn3f5u?RK^qAu0~{I;vtcoY%|-6kI3K zdf^qCl>*9T*6F7!S1xm3Y8_Z5V;KP?R)OH z_mO12gj^5Iuz8A;?UeG|=tAV*JP&K6a!OuMm1ZRsOFqrm%DEQn5{(|x+q`9>wOi_a zN?S^a+uDNC^WltL(LJUeEvUMu;^`Lhrv?lHg2jXo zSk^DIY(MV_$Zw-~WHJ>D(@vVuC~p&fvy>(1fRIob1Ehy@3X>aY&=`5R#3HQlwx%=Q zN6rXiJ6$wNI4KjDMu$R>)U4JKc292nW)?#SnF;7mv_`|w!k zd|c+H3%F34(HibHJFNl2qBY7ahvgQan2VZV{xNF8V;42yK2j4i3(($}Qakt40VgkY zk(b=aOXJn()ifVWg}8@cD*=t!HdcPnd);FzZ#4c(AKbwN3fY2Xi9)6s6JJiYA3gf+ zFa6XLpIra#{`8@bpYv^iscxtYfYiLGZ+#}@X?>W`|H+9F={F9zT>}s?<+}S+pn)u5+{++L^cHm@5=iy zzL%$!ALU}6QXpmK)(9@ON1)*C>Ss`i}Ufz5-@)=JKuC+ukZc zUb)4Qwlp3{ua9QGw!$=FQA@501-PA58e@&9?Uz*UqL}68;eXsx7pt z9kc9s09$9bd_zy8_RMAiAw{dYbFbI~OJM0v@ZxspY9P(D)Rz!ZadvNCC4kUUdRjgP z<|3=~2FZnx34kX#sqy(?cti)#%4i(2^{E|Pvp1GqCandErMIPq^#FA9K1QA{xL zLEDz2f02oxlXTySFnF+Xq^AYn7331GNaPMnY04_B9YOf6WMHN>)sQ_k)isI$a&*+3CaZ< z2$XWAu_n3%HjV`H;9$^2j7QMqq&wjY0-f-~92uujPgH+<1uNHtG_OsWH5%GbvlKDj z)RxlKQ*p4YFE$=XDxy?TiT2U6W|Dfx;O?GBO7*FhZ=^6O*lK~)KB0#lt9+? zpDx+~!UJE*z zm#qAx>+ij*Bg7RL4(V`N*BOuMI;tD97Q|Un{9PTr-sbNudNM8lFZ;65YORy&Ya^zf zqdttahs!oo$*{h?4kf5PYNnEm5(C|3y2~3))yzftJw?PrB=#(DLrlTZ^nht0kfK<2$Nm>hNV>>BMl>oV*8f!*9kU5V@r_WKyh--wFiwb;Ep4^*0ezmxMC-O1~>mSv##f8T*!kEO~s|*TiBe8 z`e9%84sD6u3Fw>$K^GRR4tVE=+f9kM0qvr)wSQ0pwDpfrYy*FDj5)NBd z(Z87jV7rf&=CR65Hf7^D5-5<`wW0QUZK%B-<5`mI(AkS(zX_O*yA@t|u)l?|yIe?SY(BT`%tf z?VJBwmgW%eC$Ry@^VLVk#t@u{WBBVg%jB!?GcB++j8NJ&JenZ_ObhEr?k?&PV=byN zEpLl8{@p-l!H9y!%p@oSA(A1|j2cnCQ`;gN9zC4RL@6Eh|=?y?DGA}A_+0?TB%kin6$Y?gII$=`VHh(ZkvUM8f5Z;CJT*hYXims`MRgr7%c<~~gHv=C4@>zk>71-i>S!(z|O zu*AzkyVdN2G40_G)_Y8Rut!N0gFyhG2qa8D1CLU3rMGaCD7IPx#?1pa6rqF?7(0}`DoYuVcxx&5s)SL{OsH9PFCW#+TAAZF6G-6nF(+I5(HfqZ5D>G zzUJeZIZJ8nLwxY_!0PDRoqCqnsk&5uZ=F|Fb$acxb$RWwDp$2X_C>LiPRam4XE)v zluvg7bUASUTi1a8zpDcGx(c`sW(}x=Sr`8BXcZ7?>2&27jFPHCgqj8_1y;05%D_==oZ1}3$ynh8d5Nnv^c4gq}8Mh!#GyJ4lZcLRnl z!ehe?7N9fXa0UK_CR~}igCD*K4=z$Ys%DWEL9QQDuk z1Y{x;q>#Q&{1j)xLa3o-^i@8QK*!4G#IKB$0Ww9>n4=uVJ}*ap5mp28d{C~Yx~neu zKx?>~UZsdLJQvuqucR{fNdw$kIS4`p*8~G;zK3?dF0$0KIwcytiNEZ1X@HJ1>N31@ zbz3##ii7~Y+kR9&6(G|OnaTk$pq1_Y&$S&5sU&z5OBeUdp`go4 zfX4K+695XqI#npL7-tkBYDnXUj=OPNi~qmdb{Z2~+WSY}OY9mh|Hw$qp7h(bh@SWzERxi? zYFpgYbaLgqUpb*N-8pgRG9OYwL4#_<2WF<^A6zB5Y!`f%0Va2Y1{X*d!-)$v1&AVx z7PTZD$yBS_=41dyB*!LedAr=OC9_TyS+K~D;M*(e3KoGYZ=@|fu^hJSxxAk=@ILsc zaL|3TUc`^eM=1qySuRy42^w;w5RkOnElcgeEt~Se-Lm%1+_HQ#I_7R!YpJ88kle%7!or^zn<8mkmY`OW(@a=bXEv|7=%)?R*m@t<7-%MI4olHEo)tf< zRCSEiJ}(n@h^qhd1#sJCTg>m4Isy5YPv}@s$KRFnm(YWBNT`= zG8@DNwN*J{Tk_@+6NDD1bOZJ{`bJVbP)HEeTbt2a96Q3fK3WbNECv^av4ocu9OCYi zM9G;mbR3yU-4P+M(t(x>2$pA!)JPfTPGF)+bOGcvPDpBSb>T&Q4AM*JW9|@6IFOl% zK?c~3fD1r?zXYCkUHXbfv(=rMv9vG}4K13yH2B7KY5k^x#u6lP^ys>GtAKc0zlp8_ z`0d55?f#qSp}uiHaMO)#Zr*cayM{JEX5e(?2P9101eEJnet>V5^6-r}3WaXEk)xv; zjfekpnlp!%jwFqTzH@SFcK^PKeftm1E?m8L|L&QExry2NiQNm+`)7CWpXF)y!gW*A z*Iu`KzBB z`L=KV+WiNn<|Y>Q&&^Hko<1-=HM=msFfq5VYvP)Pskyz=`=%H6Uq3rFH$A(1@1e=5 zU7l}p?$Ff4-ubCRd-qPw?cVz8b9aTRrfAsY)a0%M6F2PLKQTFR;K1De#i_~Z`CajL zc4FVu{0;lA-oN+I-1N>_Dw|!Ho>-W=eE!0dxxQ`xp@pfb#eF*#_kHuB zsks~8K1XR&b8~uqP%m~(PAp8sub~(N)-_Xeu`Sok?cZ1Ru>wPAy|*(a4X_FYS^sxW zSXTqotM2w>2kZumuHNptX8+tSvRWAt(jnRW6i~K`C-y2$tbJjBgjGl=knM7qg*Sk1 z$K39>I*{KsHGg3L?EKU+*aNq-vu3i6D`^>{D>RNVaq`<~GpSeK$}mvX=j2(w4^-cG z&c_rN@1HC8?v3fXx=PxgpuPsz&KP^m-#RxPGtlxR`2*7BDtmDs!#+8+cj{U&V3%OL zi(%GazG=U(wnG0TIZ7V2x0W}PKCZDquxWnc+Nt@8g@w84t3inQsYM~N3Nd7Pn^0ta z;p&Omncaux7xwQ1MLk*}r@L{61yeHNP;qZJVLvwr$-tU%2~vAGU4VC}G>Sh^GOI-=MV9Q*%Wu zwGd{{Z7Y)GFX`(B`WjQr-!Q*0wXgGipw>)I=KHr$z{K9^|46Tj?wVD`CeoZr8s@!< z-67yC+;Cv31FtedZgFDop(%~|FuxkVL4HI0G^P{9Mbq;K_DWTTO^UvE>oYmK~ z{>S&%6mi#X&TEtB1&ZPM~bzl_z8a9-(C^Esc)z76VAr5+{nF+|F&&D_}jL9 ziy^_c9J(3;GCj$>x^`lIm$9q4sRMg&*tLK5ZMt4DJF)lri5un(_va7o-VL>wg+R>p z&f2-D`9pgb=C^H&b8wfx*|zOXf#O4MNO56rwyURSLCtO3w%hd$y&qp>nN>D!jI3|l zM%{L|3L&BQZ+O}3eSVAj-S%$pXUD8^LrJ)P5jQ}_wV?913wI(k`;T>p3QTt?>ob{`1}vyd17Ig z%VgY0EHiB5+Xn}mMo)j0Yro3jDt<;}Y(;OkaF0s%WzpO2zP;4+_M)L)db>kHq;Y}m za91ZC6MH9ScTWXzu%aMW`|J5HTZ~;H!JWv4oZ?fbaTU%%3X)IJPTj|M=kZPQMwIJZ z_Dzzv@?K*R)4e@BFMl5ew_8IUFW*18IoT<7V4_R)Y)qm zB)ae78sxvZsYSSUc#wqXFLZY`EH_D>q73nxXF|K44P_qXsygb!E{`?2ijSYlRW$Y0 z{FG1kui4@9-h|v+ zOzxkWhmS85*G()=iM1oM9F$Cm(1M<1`V_o#0nui*07rIDUn9;9yQ7S8$|||;ym~jp zHjWm`*6i%mUKFW?{ZSckMNM-ay~Kv{z{KvEsRap_yZ7#&N5J)56m_Mi^69`m!sxyO zo%h?N?J?TZ^EATA$@%IK{{&;!f+iR6dmTTOWVyM2J-;}v7jplN{JxxVeFORR@8uZR z+edrH)z#VvU(JFz8(esfQ0`mjI$@X=U9%%eo~BIw(^!WOlK|BSllTS0wHMwK$6-E< zM`tj1Uk|=iLVV=?JCX7u5AIE2D%BO&tp2eKy50P-Kd=7vE!Qk`S9{^t!!qBntn#3^ zuUi zfm@Xoph3Bk%B-dg833-IM*BpojOZO0XZ5<0w~^cbe{)btWeFVLHMlq`5b7_0-;d(1*Do!rZ_jV8WrYWLLiqG+O9o*j^;lZQ`p9kPk|uEavFKwQI{IjOn!tt<80=|_ak zw{!o0@%#7uUMy_>Z}RHj%K@9KUeODi2YJ@N@8B25{XcO3ZP@Cfco{8ukCIk)5g>rI ze6Bcf=xVfu_!aq{@3NO%0@<9K-d%lYZgASPGuVE%o9(9%`JcTOWn&ZHWmk#cC5aaE z)Bh2DB-*XMu|V)TG_m&^FD311q@CodIU4g^H}#IHz#I9dcD@PW7ZGcc=1xu(-+JZ6 z7Zg|DfCi8Jys*DYog{aX{_9C^!uK_}G5){et^_=)>fYaT@141`WM#>|T#zLR$z=8| zY_bF*Gz7vTi!hVSganhBG|33avrG_c5fxEeefqRwsp3*mYt_2pYb$DNMc=2@+C}uk z>TCNcUG=M0-tT|zy~!jX)>8TgkLR3ww*NWjfBx&aH!;}2^mTa-Wpw6YI!I#1(LQdW z;~fyQQb|`jhB{;q`UJ5!R2RmaA!{eED&!Sw^EB!$!6J6R=1zSCQAXp;>$(VD55TEj z9Pdkp)x~&DI|L$u+H~HBmQ{$MjBHbxdj&ZAlc~P;IG$(j>3Zb#eQV*{hKiZx8&IzA z8%n?-^@Y{;O3%#0#u^3tIQr#`d&8?k%t)1OEMyVwCs4ywT)Zdn9{PhoriAQ{hnE0vDD?m*`W+C4d z_D&EsqKwWpg-!%sl!&?X*nKiwyax^&tudX^E(9(i83?Z7jDa-5)>2LPxEwLH(Qryz zhhcaUvDX3XFA#o>@D{=`gm)1}+o4}VKO|eciJ0I%mCg7#%E(S@)G=(;|A@_aT$$E( zGGaPMnf7X-#%Zc-nfR1A>Y&vNIcH{aqUqQFtg|hvyZhqny^l zCDot~^SMh`e+s6#erqYijf5=3GvktwJ)aEvJR4lrSN1jOh=nPL8)OhajZU zp7iw8bh>5_#hI!}@S`Fw&JkwA(h^5g8P$PX2d{vU z)Gp2ByE)$7jl*ov>gWKs|Mn!we+?pzSWR}Q5bQ7x=sMhmTB0KgS0TZ~1>G*9glIt* zRn)flz-C;wP@|Y-qAtD#<`xuj4nl$pxxx9gIaCgH8C}pp)y=iWG^xwJc=uNFo{=@W zEe^wU7r_8fGTQb#)a6MPmYD%K(|Kb)!vWlTY3q)wAk##@6SeB*I8D1(C8sq_st*(f z+>Uon))z>)3Eb>yk55on#nny#gu$^g?KFK_dr0m4pz#Y7lq2gB8G35f9)4K@U133c zl3=6#NpdqM6LA%eRaGA)Ne*+z0pN=T$bN?G!VpD9`TTJ2K==7QI!W{S5pN)z--=++ zmWz?zhCsaJ5(KhK8Ac%>Byr++41{~$WR2!`C3=C51IeV?P54F2Hlhj701uK0_ylmr zBD0U3=%W;4d{!Ts&3HeZsh((JWFIf+aOk$f<;f(+^PyAJ2?I_%0ZgC`z<*I3pN+s# zzPT&E2LXezCMggC;!{TcT0IY808`@%n;SPYaMEI6?ZAnly;hwLm+!L&LZ6GL zXuc4m?|byVPvRBBMBwwl7Y?A$h75S`9R>eSM&)17^UL5y#`z%w=Kxj(Ck&U-)LvX0 z!l^)iWmEIGFZ%4@Vh?Cj-&T@c=3yMt@yPB&yhDde2ZWO$00#dr>J^~es87Bds;}S6 z5ssmp_{47z)ALNeWbh%vA)Vh4n-RW(dIsKaM>>r#h=3(Jch1~-^BWf|Y&vhz;x9EX zX<53gb@_^wtIl7&Cfe2>i+617?Aml;cTXbO`{lm=fz(Btw`{%G>2iC#K7Sw>npRI2 z9sj}KOw^mDwF@^)pP{!Bm?+66vtqH@>{;14xq10x#*Q0bP*_x~RVXPfE3c?jCrmtN z(&Q;qtEy|J)z&%c350)e*t1(rmq&)K(+JzDb$fxv68gZB)PB%qLc;ENV&edm&g)T! z?ya01oYP06OAi?1QH&Ku96%u3>lu!DDiNJfjSp5^lgDM07L4OTNRbWcbeaPkv&Nbj zZOP_9c9>Vt7yagvV3+Jx;e4Zetl(taKtz;HvtK#pFKL8Q(gjXO!aZ^Os7mg;QxzoC zWZNd}#;M9q*WY=qOPVzW&VkDe{LY%j>W0ArH-sb;RU=cwQzOH*qMVOHv;bYDYlLdm zyr9$hcKW(`a~d0~hwE|1Il8wv%p908by_g+&`_qRu3$?GQxAB@If zJcsk44bp;&rpW)tX{cif-&ihQ=`tN`nB2nY;5IId4bF{WjGcfy)Y(Pa z8%bg$oe@rP@JR5~MLH5rwY$nB?m*G33p#im=wMu~Lm#kc!h??+1AWo1Zo(QJCYhvC|+bCe~>4|1k=#ByeUW)($P|?0VO>*jq_Bzh++`CEyCG^0hd8J8W17rbmVb6-DHeOcL19KHQATX zZJpcHG`zYRB|!O6)GtSDwEIt_N!OmEStfuE9h|oo)oVI9#q{GSeAXs~CD#oXGcaB| z#`_h*S)#$*OtO*e{_xHAruyKdjn`|2cPPjm9bH?nu=fKF(#3Al;b?-Sj2o{|62h7! zmP|n4=eJ*+gziBgB1yf5(H^P0TtI;% zf#fa7lBon(A`S>?CUklsVhh)!O%2)@@Lh&9?VpjSy(K%_8E(~SdDnY@f%NMu5NMAK zN<(v3LFGC+NF{X$%UzmHF^ZV#Z9)t=N|;>i;dfb|;)q^BDB8w6*Q;q(= zhBV=!k)Mq;?U#|CgS3jU3xVueq(jj8h`|2@V>pny7iwzz87`N`np_U3l_B3~mLzQV zYUlDD06_X`FY?%?p_rs1H8Pz41m)xhGU^e_hq**3^J(_HS{1wuY0{x~BN+3#66vcD z_8=I#(lto$MYtBhAan4J^~cbjY;*tYI|}WvE%1qU^b8ob?JekY5&HCDaXe}l;UtY?8TCT zB04WOBhZg<=)Vw%CK>xp=ZDVBZ3vmq_aXl;-0MtK5)KA5T#6yi+<#;3_`dKRqT62= z#oMuV?NP{^bPv^V$^gFOXrGVx6~wfz1z0x|;wYJ_;3(o|P?As~=6=qLIl}E|M?YF4 z!o`ze0pbFVNKnI-HjJgB-Uo-CtUkFeVAhemTJ7&lrtoSFr0VH2NcB^zAe=>_0}6L{m0KRkrK311H&E=0T=F~M^sVw#@^n@_ND*@Vj&BlqlEC~JhFm)zXkriJ`| z1Otb@jr12dWBM`Blp(_>#?7pnAy(5gjGc{jZNy|oDXN+}qnQxN$4(Mf2cNU5`P{j; zC!-eLEQ6`)bhS1ct4VhiXrsAG!ZV~TRU7aS(ZYO-!y2v`dvppqJt7r@tTR#?esXkPM_273^;?%kjv?Ex!f*~%j@#F z{H}m2=nA==ZkOBb_PD)npWE*axP$JH$LVo-+#Zj|>+yN~o`5Il33;7fm)Gs}c)ebq z*Y6E@gWizO>2vwqK9A4q^ZER~fG_9^`JH~3-|hGKy?&qH?+^Hc{!qY)SK-_NPrw`S z1^j_PAQ%V*ok3U79rOgfL0`}x3{ju1NAj) zfVSoEs&M4cN(hPjAzW~-t?3l%z0);3cna}wA9x8uN1Ee!PMB(xYwfk(hYi0R)6j@p zfrY#onw@nDVxlAL|M@4J`_)gL7VIAJv_E`UX~a*apTNJ#XMe0g>*`zO?40~D)h=&%eq+;l%}ZkObq_yM zTBcZR_Hl*&Q24-Ium9W{xMuGG#nv#hqifH#`N<83-v3}t+n+x<(YpMmn;rF2t5)1{ z+rDq@zw^L%9(&?PW_#B7^6;$rOYgYzg`eK06qih#Jag8&zyILGGlymMoXJzFJi+k7 z^A@+Xu2^~gnsw_V?eUHa`?p-O{fhl}AH4tRm+m{5NIrGl`ia|2l3XWsNUYwG-cc^O za?9jN)(X=!(>yt+CVjVgk~~SSvUu&ya|Z*~0-L3%VSY$zw^*G8_@m{eCN?`LFEZ83 zHpQyUR;S8YR=*TBl_+wS($W<0WP22c#Wpx)*`jHdnu3xkW#bC1%@|-_cClhJFSJax zrtEWO)|eYiHuF+5Gv!I9^zOEbg%(@-j`b7g+im9Tv0<~#Un>`;zdt?JnzhhsYn)%Y z(9)XSq}bAbZM2n33z`B_j>TpUDYij>vC<%wtz^0G>`QO%NZHdrx?*vA_D*MB!JfN! zEZFz`9U-MgUT2KF zt!!1q9CP}nOJA4srEF=lya8WOW4T#!5RdN5{FWS4wJPbKOt$Sb3rsSZ z%w|zBTNG=)t;}AWRg#^Tla(vyNn^&0wHB~Kxrh}@B}yqP6Dtc;X_{1LcQB{q65Z@h z@h+Y*=cdj~r_m$U_{VFGS(c+Ikan#RRcfp3YcV2b% z-fQoA;IYS_IsE*Oe|_wo6N1bUIuLG{(RAJgJFiCJLytXv_{T3EIra|z5S5PZ4TSIO zWAUAPZ@%UE7msA;SBD$sH?3N`Zav;OzH0AXX!6YSZytN+cy|8$rdT|^^T8*cI`rym z$NzBYWxMy^@zkMbo;~u$FBabT&6X~8W|N70G%0#lPe8VMQx$ofi$Db%Dtf*{U(7c53|0~BDUSL(4?c3}*(0yLdDHA0ZglRc{GV4|Jkh*l&058hn>V%o{SOk!z>HaQ=3le7 zbz|ze=U;mH^`F1{=?OvIFmcD5@{W0yQrVn8cyCVn9#f@tuv99xFu7j#%8JAkvyyLX z$sMDtP$ap`W|b_GB8edUSu#GWXJ$F$P0dQFvPu!ng;_1~T&WJ^Dc_u%6_(2t#gHF8!z_{S%X|s6n!sgO)~o{cpftA+3>DTho^omDvici{CJonVIsc*u$avBGJpO8K z3utx~f{|Z`H1UsNpQYy4JgMJd>$A{{zK9o)3TsDLm)bV;tPS~d5J>%|TOpYu()aM3 zs~ST(2dw#Bu??wSyb{4xlnoGQMv7Grqm3Yex5}!p*Ax*h7<-#ArckZSQX`cg)ZR9& z#;MjO?>Jg39*ERcd>nBIpQ`>_PelBGXK(qLZ9-sD_FI8_a-yO7qJ1G}S#;r_D)ud& z?Txk^-?VS(lH`PCw>+_LnQ$c98h?3TtMJBz<-*aoR^0b?bk*;VO<4WXd;3|UI`=&DpI~o3;{l4lybI^$#5VuW?@U2sAO5%m}s>tO=2nfV=R!v zFrvxMCRtgB%*+5vEEe%;5E*}7Of<7xhP9W<#R~kJjV~uzm}s{$oFP(Z3+JNR4)vyp8#Dt1(ZT|f}5+kr#@=`p<_h||tpW~VU{y?%)Azv>E0oiR~Ijq3Ma?xu(@6i-v ox1r4p87rXlC_*HCTzhSw+y^Ab8lfF(?Jk5n5MDs|V<_+c04VU1FaQ7m literal 0 HcmV?d00001 diff --git a/x/evm/artifacts/utils/utils.go b/x/evm/artifacts/utils/utils.go new file mode 100644 index 000000000..c75eeb861 --- /dev/null +++ b/x/evm/artifacts/utils/utils.go @@ -0,0 +1,15 @@ +package utils + +import "encoding/binary" + +func GetVersionBz(version uint16) []byte { + res := make([]byte, 2) + binary.BigEndian.PutUint16(res, version) + return res +} + +func GetCodeIDBz(codeID uint64) []byte { + res := make([]byte, 8) + binary.BigEndian.PutUint64(res, codeID) + return res +} diff --git a/x/evm/keeper/genesis.go b/x/evm/keeper/genesis.go index 99e554496..dc46d5ac7 100644 --- a/x/evm/keeper/genesis.go +++ b/x/evm/keeper/genesis.go @@ -3,6 +3,7 @@ package keeper import ( "fmt" + "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/ethereum/go-ethereum/common" @@ -13,6 +14,9 @@ import ( "github.com/ethereum/go-ethereum/trie/triedb/hashdb" "github.com/ethereum/go-ethereum/trie/triedb/pathdb" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/erc20" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/erc721" + artifactsutils "github.com/sei-protocol/sei-chain/x/evm/artifacts/utils" "github.com/sei-protocol/sei-chain/x/evm/types" ) @@ -31,6 +35,24 @@ func (k *Keeper) InitGenesis(ctx sdk.Context, genState types.GenesisState) { k.SetAddressMapping(ctx, sdk.MustAccAddressFromBech32(addr.SeiAddress), common.HexToAddress(addr.EthAddress)) } + erc20CodeID, err := k.wasmKeeper.Create(ctx, k.accountKeeper.GetModuleAddress(types.ModuleName), erc20.GetBin(), nil) + if err != nil { + panic(err) + } + prefix.NewStore(k.PrefixStore(ctx, types.PointerCWCodePrefix), types.PointerCW20ERC20Prefix).Set( + artifactsutils.GetVersionBz(erc20.CurrentVersion), + artifactsutils.GetCodeIDBz(erc20CodeID), + ) + + erc721CodeID, err := k.wasmKeeper.Create(ctx, k.accountKeeper.GetModuleAddress(types.ModuleName), erc721.GetBin(), nil) + if err != nil { + panic(err) + } + prefix.NewStore(k.PrefixStore(ctx, types.PointerCWCodePrefix), types.PointerCW721ERC721Prefix).Set( + artifactsutils.GetVersionBz(erc721.CurrentVersion), + artifactsutils.GetCodeIDBz(erc721CodeID), + ) + if k.EthReplayConfig.Enabled && !ethReplayInitialied { header := k.OpenEthDatabase() params := k.GetParams(ctx) diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index 5ab52335a..2b43e3d00 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -11,6 +11,7 @@ import ( "sort" "sync" + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" @@ -49,6 +50,7 @@ type Keeper struct { accountKeeper *authkeeper.AccountKeeper stakingKeeper *stakingkeeper.Keeper transferKeeper ibctransferkeeper.Keeper + wasmKeeper *wasmkeeper.PermissionedKeeper cachedFeeCollectorAddressMtx *sync.RWMutex cachedFeeCollectorAddress *common.Address @@ -109,7 +111,7 @@ func (ctx *ReplayChainContext) GetHeader(hash common.Hash, number uint64) *ethty func NewKeeper( storeKey sdk.StoreKey, memStoreKey sdk.StoreKey, paramstore paramtypes.Subspace, bankKeeper bankkeeper.Keeper, accountKeeper *authkeeper.AccountKeeper, stakingKeeper *stakingkeeper.Keeper, - transferKeeper ibctransferkeeper.Keeper) *Keeper { + transferKeeper ibctransferkeeper.Keeper, wasmKeeper *wasmkeeper.PermissionedKeeper) *Keeper { if !paramstore.HasKeyTable() { paramstore = paramstore.WithKeyTable(types.ParamKeyTable()) } @@ -121,6 +123,7 @@ func NewKeeper( accountKeeper: accountKeeper, stakingKeeper: stakingKeeper, transferKeeper: transferKeeper, + wasmKeeper: wasmKeeper, pendingTxs: make(map[string][]*PendingTx), nonceMx: &sync.RWMutex{}, cachedFeeCollectorAddressMtx: &sync.RWMutex{}, @@ -138,6 +141,10 @@ func (k *Keeper) BankKeeper() bankkeeper.Keeper { return k.bankKeeper } +func (k *Keeper) WasmKeeper() *wasmkeeper.PermissionedKeeper { + return k.wasmKeeper +} + func (k *Keeper) GetStoreKey() sdk.StoreKey { return k.storeKey } diff --git a/x/evm/keeper/msg_server.go b/x/evm/keeper/msg_server.go index 7edf01a7e..04b3d5064 100644 --- a/x/evm/keeper/msg_server.go +++ b/x/evm/keeper/msg_server.go @@ -2,12 +2,15 @@ package keeper import ( "context" + "encoding/binary" + "encoding/json" "fmt" "math" "math/big" "runtime/debug" "github.com/armon/go-metrics" + "github.com/cosmos/cosmos-sdk/store/prefix" "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" @@ -19,6 +22,9 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/sei-protocol/sei-chain/utils" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/erc20" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/erc721" + artifactsutils "github.com/sei-protocol/sei-chain/x/evm/artifacts/utils" "github.com/sei-protocol/sei-chain/x/evm/state" "github.com/sei-protocol/sei-chain/x/evm/types" ) @@ -249,3 +255,55 @@ func (server msgServer) Send(goCtx context.Context, msg *types.MsgSend) (*types. } return &types.MsgSendResponse{}, nil } + +func (server msgServer) RegisterPointer(goCtx context.Context, msg *types.MsgRegisterPointer) (*types.MsgRegisterPointerResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + var existingPointer sdk.AccAddress + var existingVersion uint16 + var currentVersion uint16 + var exists bool + switch msg.PointerType { + case types.PointerType_ERC20: + currentVersion = erc20.CurrentVersion + existingPointer, existingVersion, exists = server.GetCW20ERC20Pointer(ctx, common.HexToAddress(msg.ErcAddress)) + case types.PointerType_ERC721: + currentVersion = erc721.CurrentVersion + existingPointer, existingVersion, exists = server.GetCW721ERC721Pointer(ctx, common.HexToAddress(msg.ErcAddress)) + default: + panic("unknown pointer type") + } + if exists && existingVersion >= currentVersion { + return nil, fmt.Errorf("pointer %s already registered at version %d", existingPointer.String(), existingVersion) + } + store := server.PrefixStore(ctx, types.PointerCWCodePrefix) + payload := map[string]interface{}{} + switch msg.PointerType { + case types.PointerType_ERC20: + store = prefix.NewStore(store, types.PointerCW20ERC20Prefix) + payload["erc20_address"] = msg.ErcAddress + case types.PointerType_ERC721: + store = prefix.NewStore(store, types.PointerCW721ERC721Prefix) + payload["erc721_address"] = msg.ErcAddress + default: + panic("unknown pointer type") + } + codeID := binary.BigEndian.Uint64(store.Get(artifactsutils.GetVersionBz(currentVersion))) + bz, err := json.Marshal(payload) + if err != nil { + return nil, err + } + moduleAcct := server.accountKeeper.GetModuleAddress(types.ModuleName) + pointerAddr, _, err := server.wasmKeeper.Instantiate(ctx, codeID, moduleAcct, moduleAcct, bz, fmt.Sprintf("Pointer of %s", msg.ErcAddress), sdk.NewCoins()) + if err != nil { + return nil, err + } + switch msg.PointerType { + case types.PointerType_ERC20: + err = server.SetCW20ERC20Pointer(ctx, common.HexToAddress(msg.ErcAddress), pointerAddr.String()) + case types.PointerType_ERC721: + err = server.SetCW721ERC721Pointer(ctx, common.HexToAddress(msg.ErcAddress), pointerAddr.String()) + default: + panic("unknown pointer type") + } + return &types.MsgRegisterPointerResponse{PointerAddress: pointerAddr.String()}, err +} diff --git a/x/evm/keeper/msg_server_test.go b/x/evm/keeper/msg_server_test.go index a2007f9cf..194476cde 100644 --- a/x/evm/keeper/msg_server_test.go +++ b/x/evm/keeper/msg_server_test.go @@ -16,6 +16,8 @@ import ( "github.com/sei-protocol/sei-chain/example/contracts/simplestorage" testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" "github.com/sei-protocol/sei-chain/x/evm/ante" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/erc20" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/erc721" "github.com/sei-protocol/sei-chain/x/evm/keeper" "github.com/sei-protocol/sei-chain/x/evm/state" "github.com/sei-protocol/sei-chain/x/evm/types" @@ -456,3 +458,46 @@ func TestSend(t *testing.T) { require.Equal(t, sdk.NewInt(500000), k.BankKeeper().GetBalance(ctx, seiFrom, "usei").Amount) require.Equal(t, sdk.NewInt(500000), k.BankKeeper().GetBalance(ctx, seiTo, "usei").Amount) } + +func TestRegisterPointer(t *testing.T) { + k, ctx := testkeeper.MockEVMKeeper() + sender, _ := testkeeper.MockAddressPair() + _, pointee := testkeeper.MockAddressPair() + res, err := keeper.NewMsgServerImpl(k).RegisterPointer(sdk.WrapSDKContext(ctx), &types.MsgRegisterPointer{ + Sender: sender.String(), + PointerType: types.PointerType_ERC20, + ErcAddress: pointee.Hex(), + }) + require.Nil(t, err) + pointer, version, exists := k.GetCW20ERC20Pointer(ctx, pointee) + require.True(t, exists) + require.Equal(t, erc20.CurrentVersion, version) + require.Equal(t, pointer.String(), res.PointerAddress) + + // already exists + _, err = keeper.NewMsgServerImpl(k).RegisterPointer(sdk.WrapSDKContext(ctx), &types.MsgRegisterPointer{ + Sender: sender.String(), + PointerType: types.PointerType_ERC20, + ErcAddress: pointee.Hex(), + }) + require.NotNil(t, err) + + res, err = keeper.NewMsgServerImpl(k).RegisterPointer(sdk.WrapSDKContext(ctx), &types.MsgRegisterPointer{ + Sender: sender.String(), + PointerType: types.PointerType_ERC721, + ErcAddress: pointee.Hex(), + }) + require.Nil(t, err) + pointer, version, exists = k.GetCW721ERC721Pointer(ctx, pointee) + require.True(t, exists) + require.Equal(t, erc721.CurrentVersion, version) + require.Equal(t, pointer.String(), res.PointerAddress) + + // already exists + _, err = keeper.NewMsgServerImpl(k).RegisterPointer(sdk.WrapSDKContext(ctx), &types.MsgRegisterPointer{ + Sender: sender.String(), + PointerType: types.PointerType_ERC721, + ErcAddress: pointee.Hex(), + }) + require.NotNil(t, err) +} diff --git a/x/evm/keeper/pointer.go b/x/evm/keeper/pointer.go index e26526e85..2708f372c 100644 --- a/x/evm/keeper/pointer.go +++ b/x/evm/keeper/pointer.go @@ -8,20 +8,26 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" "github.com/sei-protocol/sei-chain/x/evm/artifacts/cw20" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/erc20" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/erc721" "github.com/sei-protocol/sei-chain/x/evm/artifacts/native" "github.com/sei-protocol/sei-chain/x/evm/types" ) func (k *Keeper) SetERC20NativePointer(ctx sdk.Context, token string, addr common.Address) error { - return k.SetPointerInfo(ctx, types.PointerERC20NativeKey(token), addr, native.CurrentVersion) + return k.SetPointerInfo(ctx, types.PointerERC20NativeKey(token), addr[:], native.CurrentVersion) } func (k *Keeper) SetERC20NativePointerWithVersion(ctx sdk.Context, token string, addr common.Address, version uint16) error { - return k.SetPointerInfo(ctx, types.PointerERC20NativeKey(token), addr, version) + return k.SetPointerInfo(ctx, types.PointerERC20NativeKey(token), addr[:], version) } func (k *Keeper) GetERC20NativePointer(ctx sdk.Context, token string) (addr common.Address, version uint16, exists bool) { - return k.GetPointerInfo(ctx, types.PointerERC20NativeKey(token)) + addrBz, version, exists := k.GetPointerInfo(ctx, types.PointerERC20NativeKey(token)) + if exists { + addr = common.BytesToAddress(addrBz) + } + return } func (k *Keeper) DeleteERC20NativePointer(ctx sdk.Context, token string, version uint16) { @@ -29,15 +35,19 @@ func (k *Keeper) DeleteERC20NativePointer(ctx sdk.Context, token string, version } func (k *Keeper) SetERC20CW20Pointer(ctx sdk.Context, cw20Address string, addr common.Address) error { - return k.SetPointerInfo(ctx, types.PointerERC20CW20Key(cw20Address), addr, cw20.CurrentVersion) + return k.SetPointerInfo(ctx, types.PointerERC20CW20Key(cw20Address), addr[:], cw20.CurrentVersion) } func (k *Keeper) SetERC20CW20PointerWithVersion(ctx sdk.Context, cw20Address string, addr common.Address, version uint16) error { - return k.SetPointerInfo(ctx, types.PointerERC20CW20Key(cw20Address), addr, version) + return k.SetPointerInfo(ctx, types.PointerERC20CW20Key(cw20Address), addr[:], version) } func (k *Keeper) GetERC20CW20Pointer(ctx sdk.Context, cw20Address string) (addr common.Address, version uint16, exists bool) { - return k.GetPointerInfo(ctx, types.PointerERC20CW20Key(cw20Address)) + addrBz, version, exists := k.GetPointerInfo(ctx, types.PointerERC20CW20Key(cw20Address)) + if exists { + addr = common.BytesToAddress(addrBz) + } + return } func (k *Keeper) DeleteERC20CW20Pointer(ctx sdk.Context, cw20Address string, version uint16) { @@ -45,22 +55,50 @@ func (k *Keeper) DeleteERC20CW20Pointer(ctx sdk.Context, cw20Address string, ver } func (k *Keeper) SetERC721CW721Pointer(ctx sdk.Context, cw721Address string, addr common.Address) error { - return k.SetPointerInfo(ctx, types.PointerERC721CW721Key(cw721Address), addr, cw20.CurrentVersion) + return k.SetPointerInfo(ctx, types.PointerERC721CW721Key(cw721Address), addr[:], cw20.CurrentVersion) } func (k *Keeper) SetERC721CW721PointerWithVersion(ctx sdk.Context, cw721Address string, addr common.Address, version uint16) error { - return k.SetPointerInfo(ctx, types.PointerERC721CW721Key(cw721Address), addr, version) + return k.SetPointerInfo(ctx, types.PointerERC721CW721Key(cw721Address), addr[:], version) } func (k *Keeper) GetERC721CW721Pointer(ctx sdk.Context, cw721Address string) (addr common.Address, version uint16, exists bool) { - return k.GetPointerInfo(ctx, types.PointerERC721CW721Key(cw721Address)) + addrBz, version, exists := k.GetPointerInfo(ctx, types.PointerERC721CW721Key(cw721Address)) + if exists { + addr = common.BytesToAddress(addrBz) + } + return } func (k *Keeper) DeleteERC721CW721Pointer(ctx sdk.Context, cw721Address string, version uint16) { k.DeletePointerInfo(ctx, types.PointerERC721CW721Key(cw721Address), version) } -func (k *Keeper) GetPointerInfo(ctx sdk.Context, pref []byte) (addr common.Address, version uint16, exists bool) { +func (k *Keeper) SetCW20ERC20Pointer(ctx sdk.Context, erc20Address common.Address, addr string) error { + return k.SetPointerInfo(ctx, types.PointerCW20ERC20Key(erc20Address), []byte(addr), erc20.CurrentVersion) +} + +func (k *Keeper) GetCW20ERC20Pointer(ctx sdk.Context, erc20Address common.Address) (addr sdk.AccAddress, version uint16, exists bool) { + addrBz, version, exists := k.GetPointerInfo(ctx, types.PointerCW20ERC20Key(erc20Address)) + if exists { + addr = sdk.MustAccAddressFromBech32(string(addrBz)) + } + return +} + +func (k *Keeper) SetCW721ERC721Pointer(ctx sdk.Context, erc721Address common.Address, addr string) error { + return k.SetPointerInfo(ctx, types.PointerCW721ERC721Key(erc721Address), []byte(addr), erc721.CurrentVersion) +} + +func (k *Keeper) GetCW721ERC721Pointer(ctx sdk.Context, erc721Address common.Address) (addr sdk.AccAddress, version uint16, exists bool) { + addrBz, version, exists := k.GetPointerInfo(ctx, types.PointerCW721ERC721Key(erc721Address)) + if exists { + addr = sdk.MustAccAddressFromBech32(string(addrBz)) + } + return +} + +func (k *Keeper) GetPointerInfo(ctx sdk.Context, pref []byte) (addr []byte, version uint16, exists bool) { store := prefix.NewStore(ctx.KVStore(k.GetStoreKey()), pref) iter := store.ReverseIterator(nil, nil) defer iter.Close() @@ -69,19 +107,19 @@ func (k *Keeper) GetPointerInfo(ctx sdk.Context, pref []byte) (addr common.Addre return } version = binary.BigEndian.Uint16(iter.Key()) - addr = common.BytesToAddress(iter.Value()) + addr = iter.Value() return } -func (k *Keeper) SetPointerInfo(ctx sdk.Context, pref []byte, addr common.Address, version uint16) error { +func (k *Keeper) SetPointerInfo(ctx sdk.Context, pref []byte, addr []byte, version uint16) error { existingAddr, existingVersion, exists := k.GetPointerInfo(ctx, pref) if exists && existingVersion >= version { - return fmt.Errorf("pointer at %s with version %d exists when trying to set pointer for version %d", existingAddr.Hex(), existingVersion, version) + return fmt.Errorf("pointer at %s with version %d exists when trying to set pointer for version %d", string(existingAddr), existingVersion, version) } store := prefix.NewStore(ctx.KVStore(k.GetStoreKey()), pref) versionBz := make([]byte, 2) binary.BigEndian.PutUint16(versionBz, version) - store.Set(versionBz, addr[:]) + store.Set(versionBz, addr) return nil } diff --git a/x/evm/migrations/store_cw_pointer_code.go b/x/evm/migrations/store_cw_pointer_code.go new file mode 100644 index 000000000..2e188ec24 --- /dev/null +++ b/x/evm/migrations/store_cw_pointer_code.go @@ -0,0 +1,32 @@ +package migrations + +import ( + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/erc20" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/erc721" + artifactsutils "github.com/sei-protocol/sei-chain/x/evm/artifacts/utils" + "github.com/sei-protocol/sei-chain/x/evm/keeper" + "github.com/sei-protocol/sei-chain/x/evm/types" +) + +func StoreCWPointerCode(ctx sdk.Context, k *keeper.Keeper) error { + erc20CodeID, err := k.WasmKeeper().Create(ctx, k.AccountKeeper().GetModuleAddress(types.ModuleName), erc20.GetBin(), nil) + if err != nil { + panic(err) + } + prefix.NewStore(k.PrefixStore(ctx, types.PointerCWCodePrefix), types.PointerCW20ERC20Prefix).Set( + artifactsutils.GetVersionBz(erc20.CurrentVersion), + artifactsutils.GetCodeIDBz(erc20CodeID), + ) + + erc721CodeID, err := k.WasmKeeper().Create(ctx, k.AccountKeeper().GetModuleAddress(types.ModuleName), erc721.GetBin(), nil) + if err != nil { + panic(err) + } + prefix.NewStore(k.PrefixStore(ctx, types.PointerCWCodePrefix), types.PointerCW721ERC721Prefix).Set( + artifactsutils.GetVersionBz(erc721.CurrentVersion), + artifactsutils.GetCodeIDBz(erc721CodeID), + ) + return nil +} diff --git a/x/evm/module.go b/x/evm/module.go index b0e6f0113..5fd7810e8 100644 --- a/x/evm/module.go +++ b/x/evm/module.go @@ -142,6 +142,10 @@ func (am AppModule) RegisterServices(cfg module.Configurator) { _ = cfg.RegisterMigration(types.ModuleName, 3, func(ctx sdk.Context) error { return migrations.AddNewParamsAndSetAllToDefaults(ctx, am.keeper) }) + + _ = cfg.RegisterMigration(types.ModuleName, 4, func(ctx sdk.Context) error { + return migrations.StoreCWPointerCode(ctx, am.keeper) + }) } // RegisterInvariants registers the capability module's invariants. @@ -166,7 +170,7 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw } // ConsensusVersion implements ConsensusVersion. -func (AppModule) ConsensusVersion() uint64 { return 4 } +func (AppModule) ConsensusVersion() uint64 { return 5 } // BeginBlock executes all ABCI BeginBlock logic respective to the capability module. func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) { diff --git a/x/evm/types/codec.go b/x/evm/types/codec.go index e4adec0a2..a092de6e0 100644 --- a/x/evm/types/codec.go +++ b/x/evm/types/codec.go @@ -35,6 +35,7 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) { (*sdk.Msg)(nil), &MsgEVMTransaction{}, &MsgSend{}, + &MsgRegisterPointer{}, ) registry.RegisterInterface( "seiprotocol.seichain.evm.TxData", diff --git a/x/evm/types/keys.go b/x/evm/types/keys.go index 3a64d211d..b376c2b59 100644 --- a/x/evm/types/keys.go +++ b/x/evm/types/keys.go @@ -47,12 +47,15 @@ var ( ReplayInitialHeight = []byte{0x14} PointerRegistryPrefix = []byte{0x15} + PointerCWCodePrefix = []byte{0x16} ) var ( PointerERC20NativePrefix = []byte{0x0} PointerERC20CW20Prefix = []byte{0x1} PointerERC721CW721Prefix = []byte{0x2} + PointerCW20ERC20Prefix = []byte{0x3} + PointerCW721ERC721Prefix = []byte{0x4} ) func EVMAddressToSeiAddressKey(evmAddress common.Address) []byte { @@ -103,3 +106,17 @@ func PointerERC721CW721Key(cw721Address string) []byte { []byte(cw721Address)..., ) } + +func PointerCW20ERC20Key(erc20Addr common.Address) []byte { + return append( + append(PointerRegistryPrefix, PointerCW20ERC20Prefix...), + erc20Addr[:]..., + ) +} + +func PointerCW721ERC721Key(erc721Addr common.Address) []byte { + return append( + append(PointerRegistryPrefix, PointerCW721ERC721Prefix...), + erc721Addr[:]..., + ) +} diff --git a/x/evm/types/message_register_pointer.go b/x/evm/types/message_register_pointer.go new file mode 100644 index 000000000..1f2c124c4 --- /dev/null +++ b/x/evm/types/message_register_pointer.go @@ -0,0 +1,54 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/ethereum/go-ethereum/common" +) + +const TypeMsgRegisterPointer = "evm_register_pointer" + +var ( + _ sdk.Msg = &MsgSend{} +) + +func NewMsgRegisterERC20Pointer(sender sdk.AccAddress, ercAddress common.Address) *MsgRegisterPointer { + return &MsgRegisterPointer{Sender: sender.String(), ErcAddress: ercAddress.Hex(), PointerType: PointerType_ERC20} +} + +func NewMsgRegisterERC721Pointer(sender sdk.AccAddress, ercAddress common.Address) *MsgRegisterPointer { + return &MsgRegisterPointer{Sender: sender.String(), ErcAddress: ercAddress.Hex(), PointerType: PointerType_ERC721} +} + +func (msg *MsgRegisterPointer) Route() string { + return RouterKey +} + +func (msg *MsgRegisterPointer) Type() string { + return TypeMsgRegisterPointer +} + +func (msg *MsgRegisterPointer) GetSigners() []sdk.AccAddress { + from, err := sdk.AccAddressFromBech32(msg.Sender) + if err != nil { + panic(err) + } + return []sdk.AccAddress{from} +} + +func (msg *MsgRegisterPointer) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) +} + +func (msg *MsgRegisterPointer) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(msg.Sender) + if err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid sender address (%s)", err) + } + + if !common.IsHexAddress(msg.ErcAddress) { + return sdkerrors.ErrInvalidAddress + } + + return nil +} diff --git a/x/evm/types/tx.pb.go b/x/evm/types/tx.pb.go index 531e6c244..3debf5765 100644 --- a/x/evm/types/tx.pb.go +++ b/x/evm/types/tx.pb.go @@ -32,6 +32,31 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package +type PointerType int32 + +const ( + PointerType_ERC20 PointerType = 0 + PointerType_ERC721 PointerType = 1 +) + +var PointerType_name = map[int32]string{ + 0: "ERC20", + 1: "ERC721", +} + +var PointerType_value = map[string]int32{ + "ERC20": 0, + "ERC721": 1, +} + +func (x PointerType) String() string { + return proto.EnumName(PointerType_name, int32(x)) +} + +func (PointerType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_d72e73a3d1d93781, []int{0} +} + type MsgEVMTransaction struct { Data *types.Any `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` Derived *github_com_sei_protocol_sei_chain_x_evm_derived.Derived `protobuf:"bytes,2,opt,name=derived,proto3,customtype=github.com/sei-protocol/sei-chain/x/evm/derived.Derived" json:"derived,omitempty"` @@ -450,7 +475,112 @@ func (m *MsgSendResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgSendResponse proto.InternalMessageInfo +type MsgRegisterPointer struct { + Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty"` + PointerType PointerType `protobuf:"varint,2,opt,name=pointer_type,json=pointerType,proto3,enum=seiprotocol.seichain.evm.PointerType" json:"pointer_type,omitempty"` + ErcAddress string `protobuf:"bytes,3,opt,name=erc_address,json=ercAddress,proto3" json:"erc_address,omitempty"` +} + +func (m *MsgRegisterPointer) Reset() { *m = MsgRegisterPointer{} } +func (m *MsgRegisterPointer) String() string { return proto.CompactTextString(m) } +func (*MsgRegisterPointer) ProtoMessage() {} +func (*MsgRegisterPointer) Descriptor() ([]byte, []int) { + return fileDescriptor_d72e73a3d1d93781, []int{8} +} +func (m *MsgRegisterPointer) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgRegisterPointer) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgRegisterPointer.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgRegisterPointer) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgRegisterPointer.Merge(m, src) +} +func (m *MsgRegisterPointer) XXX_Size() int { + return m.Size() +} +func (m *MsgRegisterPointer) XXX_DiscardUnknown() { + xxx_messageInfo_MsgRegisterPointer.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgRegisterPointer proto.InternalMessageInfo + +func (m *MsgRegisterPointer) GetSender() string { + if m != nil { + return m.Sender + } + return "" +} + +func (m *MsgRegisterPointer) GetPointerType() PointerType { + if m != nil { + return m.PointerType + } + return PointerType_ERC20 +} + +func (m *MsgRegisterPointer) GetErcAddress() string { + if m != nil { + return m.ErcAddress + } + return "" +} + +type MsgRegisterPointerResponse struct { + PointerAddress string `protobuf:"bytes,1,opt,name=pointer_address,json=pointerAddress,proto3" json:"pointer_address,omitempty"` +} + +func (m *MsgRegisterPointerResponse) Reset() { *m = MsgRegisterPointerResponse{} } +func (m *MsgRegisterPointerResponse) String() string { return proto.CompactTextString(m) } +func (*MsgRegisterPointerResponse) ProtoMessage() {} +func (*MsgRegisterPointerResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_d72e73a3d1d93781, []int{9} +} +func (m *MsgRegisterPointerResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgRegisterPointerResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgRegisterPointerResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgRegisterPointerResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgRegisterPointerResponse.Merge(m, src) +} +func (m *MsgRegisterPointerResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgRegisterPointerResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgRegisterPointerResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgRegisterPointerResponse proto.InternalMessageInfo + +func (m *MsgRegisterPointerResponse) GetPointerAddress() string { + if m != nil { + return m.PointerAddress + } + return "" +} + func init() { + proto.RegisterEnum("seiprotocol.seichain.evm.PointerType", PointerType_name, PointerType_value) proto.RegisterType((*MsgEVMTransaction)(nil), "seiprotocol.seichain.evm.MsgEVMTransaction") proto.RegisterType((*MsgEVMTransactionResponse)(nil), "seiprotocol.seichain.evm.MsgEVMTransactionResponse") proto.RegisterType((*MsgInternalEVMCall)(nil), "seiprotocol.seichain.evm.MsgInternalEVMCall") @@ -459,52 +589,62 @@ func init() { proto.RegisterType((*MsgInternalEVMDelegateCallResponse)(nil), "seiprotocol.seichain.evm.MsgInternalEVMDelegateCallResponse") proto.RegisterType((*MsgSend)(nil), "seiprotocol.seichain.evm.MsgSend") proto.RegisterType((*MsgSendResponse)(nil), "seiprotocol.seichain.evm.MsgSendResponse") + proto.RegisterType((*MsgRegisterPointer)(nil), "seiprotocol.seichain.evm.MsgRegisterPointer") + proto.RegisterType((*MsgRegisterPointerResponse)(nil), "seiprotocol.seichain.evm.MsgRegisterPointerResponse") } func init() { proto.RegisterFile("evm/tx.proto", fileDescriptor_d72e73a3d1d93781) } var fileDescriptor_d72e73a3d1d93781 = []byte{ - // 640 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x93, 0xcf, 0x4e, 0xdb, 0x4c, - 0x10, 0xc0, 0x63, 0x12, 0x08, 0xd9, 0x20, 0x3e, 0xb1, 0x42, 0x9f, 0x92, 0xa8, 0x75, 0xa8, 0x55, - 0x55, 0x69, 0x2b, 0xd6, 0x05, 0x0e, 0x3d, 0xf4, 0x52, 0x02, 0xa8, 0xe5, 0x90, 0x8b, 0x0b, 0x1c, - 0x7a, 0x89, 0x36, 0xf6, 0xe0, 0x58, 0xb5, 0x77, 0xd1, 0xee, 0xda, 0x82, 0x17, 0xe8, 0xb9, 0xaa, - 0x7a, 0xe8, 0x33, 0xf4, 0xd4, 0xc7, 0xe0, 0x56, 0x8e, 0x15, 0x07, 0x5a, 0xc1, 0x8b, 0x54, 0xde, - 0xb5, 0xa3, 0x16, 0x04, 0x4d, 0x4f, 0x9e, 0x9d, 0xbf, 0xbf, 0x99, 0xf1, 0xa0, 0x05, 0xc8, 0x12, - 0x57, 0x1d, 0x93, 0x23, 0xc1, 0x15, 0xc7, 0x2d, 0x09, 0x91, 0x96, 0x7c, 0x1e, 0x13, 0x09, 0x91, - 0x3f, 0xa6, 0x11, 0x23, 0x90, 0x25, 0x9d, 0x76, 0xc8, 0x79, 0x18, 0x83, 0xab, 0xad, 0xa3, 0xf4, - 0xd0, 0xa5, 0xec, 0xc4, 0x04, 0x75, 0x96, 0x43, 0x1e, 0x72, 0x2d, 0xba, 0xb9, 0x54, 0x68, 0x6d, - 0x9f, 0xcb, 0x84, 0x4b, 0x77, 0x44, 0x25, 0xb8, 0xd9, 0xda, 0x08, 0x14, 0x5d, 0x73, 0x7d, 0x1e, - 0x31, 0x63, 0x77, 0x3e, 0x59, 0x68, 0x69, 0x20, 0xc3, 0x9d, 0x83, 0xc1, 0x9e, 0xa0, 0x4c, 0x52, - 0x5f, 0x45, 0x9c, 0xe1, 0x1e, 0xaa, 0x05, 0x54, 0xd1, 0x96, 0xb5, 0x62, 0xf5, 0x9a, 0xeb, 0xcb, - 0xc4, 0x54, 0x25, 0x65, 0x55, 0xb2, 0xc9, 0x4e, 0x3c, 0xed, 0x81, 0xf7, 0x51, 0x3d, 0x00, 0x11, - 0x65, 0x10, 0xb4, 0x66, 0x56, 0xac, 0xde, 0x42, 0xff, 0xc5, 0xf9, 0x45, 0xf7, 0x79, 0x18, 0xa9, - 0x71, 0x3a, 0x22, 0x3e, 0x4f, 0x5c, 0x09, 0xd1, 0x6a, 0xd9, 0x8b, 0x7e, 0xe8, 0x66, 0xdc, 0x63, - 0x37, 0xef, 0xb8, 0x08, 0x25, 0xdb, 0xe6, 0xeb, 0x95, 0xb9, 0x9c, 0xf7, 0x16, 0x6a, 0xdf, 0xc0, - 0xf2, 0x40, 0x1e, 0x71, 0x26, 0x01, 0xb7, 0xd1, 0x7c, 0x48, 0xe5, 0x30, 0x95, 0x10, 0x68, 0xc4, - 0x9a, 0x57, 0x0f, 0xa9, 0xdc, 0x97, 0x10, 0xe4, 0xa6, 0x2c, 0x19, 0x82, 0x10, 0x5c, 0x68, 0xa0, - 0x86, 0x57, 0xcf, 0x92, 0x9d, 0xfc, 0x89, 0xbb, 0xa8, 0x29, 0x40, 0xa5, 0x82, 0x0d, 0x75, 0x6f, - 0xd5, 0x1c, 0xd7, 0x43, 0x46, 0xb5, 0x9d, 0xf7, 0x82, 0x51, 0x6d, 0x4c, 0xe5, 0xb8, 0x55, 0xd3, - 0x71, 0x5a, 0x76, 0x3e, 0x5a, 0x08, 0x0f, 0x64, 0xb8, 0xcb, 0x14, 0x08, 0x46, 0xe3, 0x9d, 0x83, - 0xc1, 0x16, 0x8d, 0x63, 0xfc, 0x3f, 0x9a, 0x93, 0xc0, 0x02, 0x10, 0xba, 0x7e, 0xc3, 0x2b, 0x5e, - 0xf8, 0x25, 0x9a, 0xcd, 0x68, 0x9c, 0x82, 0xa9, 0xdd, 0x7f, 0x72, 0x7e, 0xd1, 0x7d, 0xf4, 0xdb, - 0x30, 0x8a, 0x65, 0x98, 0xcf, 0xaa, 0x0c, 0xde, 0xb9, 0xea, 0xe4, 0x08, 0x24, 0xd9, 0x65, 0xca, - 0x33, 0x81, 0x78, 0x11, 0xcd, 0x28, 0xae, 0xe1, 0x1a, 0xde, 0x8c, 0xe2, 0x39, 0x94, 0xc6, 0xad, - 0x69, 0x5c, 0x2d, 0x3b, 0xf7, 0x50, 0xe7, 0x26, 0x53, 0x39, 0x1d, 0xe7, 0xb3, 0x75, 0xdd, 0xbc, - 0x0d, 0x31, 0x84, 0x54, 0xc1, 0x9d, 0xe8, 0x1d, 0x34, 0xef, 0xf3, 0x00, 0x5e, 0xe7, 0x13, 0xd0, - 0xab, 0xf4, 0x26, 0xef, 0x69, 0xa0, 0xb0, 0x83, 0x16, 0x0e, 0x05, 0x4f, 0xb6, 0x38, 0x53, 0x82, - 0xfa, 0xaa, 0x35, 0xab, 0xbd, 0xff, 0xd0, 0x39, 0x0f, 0x91, 0x73, 0x3b, 0xd9, 0xa4, 0x81, 0xaf, - 0x16, 0xaa, 0x0f, 0x64, 0xf8, 0x06, 0x58, 0x80, 0x1f, 0x98, 0xac, 0x43, 0x1a, 0x04, 0x02, 0xa4, - 0x2c, 0x98, 0x9b, 0xb9, 0x6e, 0xd3, 0xa8, 0xf0, 0x7d, 0x84, 0x14, 0x9f, 0x38, 0x98, 0xa5, 0x37, - 0x14, 0x2f, 0xcd, 0x3e, 0x9a, 0xa3, 0x09, 0x4f, 0x99, 0x6a, 0x55, 0x57, 0xaa, 0xbd, 0xe6, 0x7a, - 0x9b, 0x98, 0xf1, 0x93, 0xfc, 0x24, 0x48, 0x71, 0x12, 0x64, 0x8b, 0x47, 0xac, 0xff, 0xec, 0xf4, - 0xa2, 0x5b, 0xf9, 0xf2, 0xa3, 0xdb, 0x9b, 0x62, 0x65, 0x79, 0x80, 0xf4, 0x8a, 0xd4, 0xce, 0x12, - 0xfa, 0xaf, 0x20, 0x2e, 0xbb, 0x58, 0xff, 0x66, 0xa1, 0xea, 0x40, 0x86, 0x58, 0xa0, 0xc5, 0x6b, - 0xd7, 0xf5, 0x94, 0xdc, 0x76, 0xdf, 0xe4, 0xc6, 0x3f, 0xdf, 0xd9, 0xf8, 0x07, 0xe7, 0xc9, 0x81, - 0xec, 0xa1, 0x9a, 0x99, 0xde, 0x9d, 0xc1, 0xb9, 0x4b, 0xe7, 0xf1, 0x5f, 0x5d, 0xca, 0xac, 0xfd, - 0x57, 0xa7, 0x97, 0xb6, 0x75, 0x76, 0x69, 0x5b, 0x3f, 0x2f, 0x6d, 0xeb, 0xc3, 0x95, 0x5d, 0x39, - 0xbb, 0xb2, 0x2b, 0xdf, 0xaf, 0xec, 0xca, 0xdb, 0xd5, 0x69, 0x0f, 0x5e, 0xcf, 0x6e, 0x34, 0xa7, - 0xed, 0x1b, 0xbf, 0x02, 0x00, 0x00, 0xff, 0xff, 0xec, 0x21, 0x3d, 0x00, 0xf6, 0x04, 0x00, 0x00, + // 763 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x54, 0x4f, 0x6f, 0x1b, 0x45, + 0x14, 0xf7, 0xda, 0x8e, 0x5d, 0x3f, 0x5b, 0x4e, 0x3b, 0xaa, 0x90, 0x6d, 0x81, 0x1d, 0x56, 0x05, + 0x4c, 0x21, 0xbb, 0x8d, 0x8b, 0xd4, 0x03, 0x17, 0x6a, 0xc7, 0xa2, 0x3d, 0x58, 0x42, 0x4b, 0xda, + 0x03, 0x17, 0x6b, 0xbc, 0xfb, 0xba, 0x5e, 0xe1, 0x9d, 0xb1, 0x66, 0xc6, 0x56, 0xfc, 0x05, 0x38, + 0x23, 0x84, 0x04, 0x9f, 0x81, 0x13, 0x9f, 0x02, 0xe5, 0x98, 0x23, 0xca, 0x21, 0xa0, 0xe4, 0x8b, + 0xa0, 0x9d, 0x59, 0x2f, 0x89, 0x23, 0xa7, 0xc9, 0x69, 0xdf, 0xff, 0xf7, 0xfb, 0xbd, 0xf7, 0x66, + 0xa1, 0x86, 0xcb, 0xd8, 0x55, 0xc7, 0xce, 0x5c, 0x70, 0xc5, 0x49, 0x43, 0x62, 0xa4, 0x25, 0x9f, + 0xcf, 0x1c, 0x89, 0x91, 0x3f, 0xa5, 0x11, 0x73, 0x70, 0x19, 0xb7, 0x9a, 0x21, 0xe7, 0xe1, 0x0c, + 0x5d, 0xed, 0x9d, 0x2c, 0xde, 0xb9, 0x94, 0xad, 0x4c, 0x52, 0xeb, 0x71, 0xc8, 0x43, 0xae, 0x45, + 0x37, 0x91, 0x52, 0x6b, 0xdb, 0xe7, 0x32, 0xe6, 0xd2, 0x9d, 0x50, 0x89, 0xee, 0xf2, 0x60, 0x82, + 0x8a, 0x1e, 0xb8, 0x3e, 0x8f, 0x98, 0xf1, 0xdb, 0xbf, 0x5a, 0xf0, 0x68, 0x24, 0xc3, 0xe1, 0xdb, + 0xd1, 0x91, 0xa0, 0x4c, 0x52, 0x5f, 0x45, 0x9c, 0x91, 0x2e, 0x14, 0x03, 0xaa, 0x68, 0xc3, 0xda, + 0xb3, 0xba, 0xd5, 0xde, 0x63, 0xc7, 0x74, 0x75, 0xd6, 0x5d, 0x9d, 0x97, 0x6c, 0xe5, 0xe9, 0x08, + 0xf2, 0x06, 0xca, 0x01, 0x8a, 0x68, 0x89, 0x41, 0x23, 0xbf, 0x67, 0x75, 0x6b, 0xfd, 0xaf, 0xcf, + 0xce, 0x3b, 0x2f, 0xc2, 0x48, 0x4d, 0x17, 0x13, 0xc7, 0xe7, 0xb1, 0x2b, 0x31, 0xda, 0x5f, 0x73, + 0xd1, 0x8a, 0x26, 0xe3, 0x1e, 0xbb, 0x09, 0xe3, 0x34, 0xd5, 0x39, 0x34, 0x5f, 0x6f, 0x5d, 0xcb, + 0xfe, 0xc9, 0x82, 0xe6, 0x0d, 0x58, 0x1e, 0xca, 0x39, 0x67, 0x12, 0x49, 0x13, 0x1e, 0x84, 0x54, + 0x8e, 0x17, 0x12, 0x03, 0x0d, 0xb1, 0xe8, 0x95, 0x43, 0x2a, 0xdf, 0x48, 0x0c, 0x12, 0xd7, 0x32, + 0x1e, 0xa3, 0x10, 0x5c, 0x68, 0x40, 0x15, 0xaf, 0xbc, 0x8c, 0x87, 0x89, 0x4a, 0x3a, 0x50, 0x15, + 0xa8, 0x16, 0x82, 0x8d, 0x35, 0xb7, 0x42, 0x02, 0xd7, 0x03, 0x63, 0x3a, 0x4c, 0xb8, 0x10, 0x28, + 0x4e, 0xa9, 0x9c, 0x36, 0x8a, 0x3a, 0x4f, 0xcb, 0xf6, 0x2f, 0x16, 0x90, 0x91, 0x0c, 0x5f, 0x33, + 0x85, 0x82, 0xd1, 0xd9, 0xf0, 0xed, 0x68, 0x40, 0x67, 0x33, 0xf2, 0x01, 0x94, 0x24, 0xb2, 0x00, + 0x85, 0xee, 0x5f, 0xf1, 0x52, 0x8d, 0x7c, 0x03, 0x3b, 0x4b, 0x3a, 0x5b, 0xa0, 0xe9, 0xdd, 0x7f, + 0x7a, 0x76, 0xde, 0xf9, 0xf4, 0xca, 0x30, 0xd2, 0x65, 0x98, 0xcf, 0xbe, 0x0c, 0x7e, 0x74, 0xd5, + 0x6a, 0x8e, 0xd2, 0x79, 0xcd, 0x94, 0x67, 0x12, 0x49, 0x1d, 0xf2, 0x8a, 0x6b, 0x70, 0x15, 0x2f, + 0xaf, 0x78, 0x02, 0x4a, 0xc3, 0x2d, 0x6a, 0xb8, 0x5a, 0xb6, 0x3f, 0x84, 0xd6, 0x4d, 0x4c, 0xeb, + 0xe9, 0xd8, 0xbf, 0x5b, 0x9b, 0xee, 0x43, 0x9c, 0x61, 0x48, 0x15, 0xde, 0x0a, 0xbd, 0x05, 0x0f, + 0x7c, 0x1e, 0xe0, 0xab, 0x64, 0x02, 0x7a, 0x95, 0x5e, 0xa6, 0xdf, 0x05, 0x14, 0xb1, 0xa1, 0xf6, + 0x4e, 0xf0, 0x78, 0xc0, 0x99, 0x12, 0xd4, 0x57, 0x8d, 0x1d, 0x1d, 0x7d, 0xcd, 0x66, 0x3f, 0x01, + 0x7b, 0x3b, 0xb2, 0x8c, 0xc0, 0x9f, 0x16, 0x94, 0x47, 0x32, 0xfc, 0x1e, 0x59, 0x40, 0x3e, 0x36, + 0x55, 0xc7, 0x34, 0x08, 0x04, 0x4a, 0x99, 0x62, 0xae, 0x26, 0xb6, 0x97, 0xc6, 0x44, 0x3e, 0x02, + 0x50, 0x3c, 0x0b, 0x30, 0x4b, 0xaf, 0x28, 0xbe, 0x76, 0xfb, 0x50, 0xa2, 0x31, 0x5f, 0x30, 0xd5, + 0x28, 0xec, 0x15, 0xba, 0xd5, 0x5e, 0xd3, 0x31, 0xe3, 0x77, 0x92, 0x27, 0xe1, 0xa4, 0x4f, 0xc2, + 0x19, 0xf0, 0x88, 0xf5, 0x9f, 0x9d, 0x9c, 0x77, 0x72, 0x7f, 0xfc, 0xd3, 0xe9, 0xde, 0x61, 0x65, + 0x49, 0x82, 0xf4, 0xd2, 0xd2, 0xf6, 0x23, 0xd8, 0x4d, 0x11, 0x67, 0x2c, 0x7e, 0x33, 0x97, 0xe3, + 0x61, 0x18, 0x49, 0x85, 0xe2, 0x3b, 0x1e, 0x25, 0xb4, 0xb7, 0x8e, 0xff, 0x15, 0xd4, 0xe6, 0x26, + 0x64, 0x9c, 0x34, 0xd0, 0x3c, 0xea, 0xbd, 0x4f, 0x9c, 0x6d, 0xbf, 0x02, 0x27, 0x2d, 0x78, 0xb4, + 0x9a, 0xa3, 0x57, 0x9d, 0xff, 0xaf, 0x24, 0x77, 0x8e, 0xc2, 0xcf, 0x06, 0x62, 0xb6, 0x06, 0x28, + 0xfc, 0x74, 0x22, 0xf6, 0x50, 0xdf, 0xc7, 0x06, 0xb0, 0xec, 0x71, 0x7d, 0x06, 0xbb, 0x6b, 0x20, + 0xd7, 0x87, 0x5e, 0x4f, 0xcd, 0x69, 0x99, 0xa7, 0x4f, 0xa0, 0x7a, 0x05, 0x03, 0xa9, 0xc0, 0xce, + 0xd0, 0x1b, 0xf4, 0x9e, 0x3d, 0xcc, 0x11, 0x80, 0xd2, 0xd0, 0x1b, 0xbc, 0xe8, 0x1d, 0x3c, 0xb4, + 0x7a, 0x7f, 0xe5, 0xa1, 0x30, 0x92, 0x21, 0x11, 0x50, 0xdf, 0xf8, 0xc9, 0x7c, 0xb1, 0x9d, 0xdb, + 0x8d, 0xa7, 0xdf, 0x7a, 0x7e, 0x8f, 0xe0, 0x8c, 0xca, 0x11, 0x14, 0xcd, 0x11, 0xdd, 0x9a, 0x9c, + 0x84, 0xb4, 0x3e, 0x7f, 0x6f, 0x48, 0x56, 0x75, 0x01, 0xbb, 0x9b, 0x4b, 0xfd, 0xf2, 0xd6, 0xec, + 0x8d, 0xe8, 0xd6, 0x57, 0xf7, 0x89, 0x5e, 0xb7, 0xed, 0x7f, 0x7b, 0x72, 0xd1, 0xb6, 0x4e, 0x2f, + 0xda, 0xd6, 0xbf, 0x17, 0x6d, 0xeb, 0xe7, 0xcb, 0x76, 0xee, 0xf4, 0xb2, 0x9d, 0xfb, 0xfb, 0xb2, + 0x9d, 0xfb, 0x61, 0xff, 0xae, 0xbf, 0x5b, 0x7d, 0xb9, 0x93, 0x92, 0xf6, 0x3f, 0xff, 0x2f, 0x00, + 0x00, 0xff, 0xff, 0x8b, 0x3b, 0x7e, 0x87, 0x74, 0x06, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -521,6 +661,7 @@ const _ = grpc.SupportPackageIsVersion4 type MsgClient interface { EVMTransaction(ctx context.Context, in *MsgEVMTransaction, opts ...grpc.CallOption) (*MsgEVMTransactionResponse, error) Send(ctx context.Context, in *MsgSend, opts ...grpc.CallOption) (*MsgSendResponse, error) + RegisterPointer(ctx context.Context, in *MsgRegisterPointer, opts ...grpc.CallOption) (*MsgRegisterPointerResponse, error) } type msgClient struct { @@ -549,10 +690,20 @@ func (c *msgClient) Send(ctx context.Context, in *MsgSend, opts ...grpc.CallOpti return out, nil } +func (c *msgClient) RegisterPointer(ctx context.Context, in *MsgRegisterPointer, opts ...grpc.CallOption) (*MsgRegisterPointerResponse, error) { + out := new(MsgRegisterPointerResponse) + err := c.cc.Invoke(ctx, "/seiprotocol.seichain.evm.Msg/RegisterPointer", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. type MsgServer interface { EVMTransaction(context.Context, *MsgEVMTransaction) (*MsgEVMTransactionResponse, error) Send(context.Context, *MsgSend) (*MsgSendResponse, error) + RegisterPointer(context.Context, *MsgRegisterPointer) (*MsgRegisterPointerResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -565,6 +716,9 @@ func (*UnimplementedMsgServer) EVMTransaction(ctx context.Context, req *MsgEVMTr func (*UnimplementedMsgServer) Send(ctx context.Context, req *MsgSend) (*MsgSendResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Send not implemented") } +func (*UnimplementedMsgServer) RegisterPointer(ctx context.Context, req *MsgRegisterPointer) (*MsgRegisterPointerResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RegisterPointer not implemented") +} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -606,6 +760,24 @@ func _Msg_Send_Handler(srv interface{}, ctx context.Context, dec func(interface{ return interceptor(ctx, in, info, handler) } +func _Msg_RegisterPointer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgRegisterPointer) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).RegisterPointer(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/seiprotocol.seichain.evm.Msg/RegisterPointer", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).RegisterPointer(ctx, req.(*MsgRegisterPointer)) + } + return interceptor(ctx, in, info, handler) +} + var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "seiprotocol.seichain.evm.Msg", HandlerType: (*MsgServer)(nil), @@ -618,6 +790,10 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "Send", Handler: _Msg_Send_Handler, }, + { + MethodName: "RegisterPointer", + Handler: _Msg_RegisterPointer_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "evm/tx.proto", @@ -953,6 +1129,78 @@ func (m *MsgSendResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *MsgRegisterPointer) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgRegisterPointer) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgRegisterPointer) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ErcAddress) > 0 { + i -= len(m.ErcAddress) + copy(dAtA[i:], m.ErcAddress) + i = encodeVarintTx(dAtA, i, uint64(len(m.ErcAddress))) + i-- + dAtA[i] = 0x1a + } + if m.PointerType != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.PointerType)) + i-- + dAtA[i] = 0x10 + } + if len(m.Sender) > 0 { + i -= len(m.Sender) + copy(dAtA[i:], m.Sender) + i = encodeVarintTx(dAtA, i, uint64(len(m.Sender))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgRegisterPointerResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgRegisterPointerResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgRegisterPointerResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.PointerAddress) > 0 { + i -= len(m.PointerAddress) + copy(dAtA[i:], m.PointerAddress) + i = encodeVarintTx(dAtA, i, uint64(len(m.PointerAddress))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintTx(dAtA []byte, offset int, v uint64) int { offset -= sovTx(v) base := offset @@ -1109,6 +1357,39 @@ func (m *MsgSendResponse) Size() (n int) { return n } +func (m *MsgRegisterPointer) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Sender) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.PointerType != 0 { + n += 1 + sovTx(uint64(m.PointerType)) + } + l = len(m.ErcAddress) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgRegisterPointerResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PointerAddress) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + func sovTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -2099,6 +2380,221 @@ func (m *MsgSendResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgRegisterPointer) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgRegisterPointer: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgRegisterPointer: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Sender = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PointerType", wireType) + } + m.PointerType = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PointerType |= PointerType(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ErcAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ErcAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgRegisterPointerResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgRegisterPointerResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgRegisterPointerResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PointerAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PointerAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 From 8be58467684d3529305ff57a05efdd10547404a3 Mon Sep 17 00:00:00 2001 From: codchen Date: Wed, 3 Apr 2024 11:05:27 +0800 Subject: [PATCH 09/34] Add WSEI contract and cmd for deployment (#1500) --- contracts/src/WSEI.sol | 66 ++++++++++++++++++++++++++++++ x/evm/artifacts/wsei/WSEI.abi | 1 + x/evm/artifacts/wsei/WSEI.bin | 1 + x/evm/artifacts/wsei/artifacts.go | 54 ++++++++++++++++++++++++ x/evm/client/cli/tx.go | 68 +++++++++++++++++++++++++++++++ 5 files changed, 190 insertions(+) create mode 100644 contracts/src/WSEI.sol create mode 100644 x/evm/artifacts/wsei/WSEI.abi create mode 100644 x/evm/artifacts/wsei/WSEI.bin create mode 100644 x/evm/artifacts/wsei/artifacts.go diff --git a/contracts/src/WSEI.sol b/contracts/src/WSEI.sol new file mode 100644 index 000000000..02d9ec833 --- /dev/null +++ b/contracts/src/WSEI.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract WSEI { + string public name = "Wrapped Sei"; + string public symbol = "WSEI"; + uint8 public decimals = 18; + + event Approval(address indexed src, address indexed guy, uint wad); + event Transfer(address indexed src, address indexed dst, uint wad); + event Deposit(address indexed dst, uint wad); + event Withdrawal(address indexed src, uint wad); + + mapping (address => uint) public balanceOf; + mapping (address => mapping (address => uint)) public allowance; + + fallback() external payable { + deposit(); + } + receive() external payable { + deposit(); + } + function deposit() public payable { + balanceOf[msg.sender] += msg.value; + emit Deposit(msg.sender, msg.value); + } + function withdraw(uint wad) public { + require(balanceOf[msg.sender] >= wad); + balanceOf[msg.sender] -= wad; + payable(msg.sender).transfer(wad); + emit Withdrawal(msg.sender, wad); + } + + function totalSupply() public view returns (uint) { + return address(this).balance; + } + + function approve(address guy, uint wad) public returns (bool) { + allowance[msg.sender][guy] = wad; + emit Approval(msg.sender, guy, wad); + return true; + } + + function transfer(address dst, uint wad) public returns (bool) { + return transferFrom(msg.sender, dst, wad); + } + + function transferFrom(address src, address dst, uint wad) + public + returns (bool) + { + require(balanceOf[src] >= wad); + + if (src != msg.sender && allowance[src][msg.sender] != type(uint).max) { + require(allowance[src][msg.sender] >= wad); + allowance[src][msg.sender] -= wad; + } + + balanceOf[src] -= wad; + balanceOf[dst] += wad; + + emit Transfer(src, dst, wad); + + return true; + } +} diff --git a/x/evm/artifacts/wsei/WSEI.abi b/x/evm/artifacts/wsei/WSEI.abi new file mode 100644 index 000000000..e8a5861f2 --- /dev/null +++ b/x/evm/artifacts/wsei/WSEI.abi @@ -0,0 +1 @@ +[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"src","type":"address"},{"indexed":true,"internalType":"address","name":"guy","type":"address"},{"indexed":false,"internalType":"uint256","name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"dst","type":"address"},{"indexed":false,"internalType":"uint256","name":"wad","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"src","type":"address"},{"indexed":true,"internalType":"address","name":"dst","type":"address"},{"indexed":false,"internalType":"uint256","name":"wad","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"src","type":"address"},{"indexed":false,"internalType":"uint256","name":"wad","type":"uint256"}],"name":"Withdrawal","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"guy","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"src","type":"address"},{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/x/evm/artifacts/wsei/WSEI.bin b/x/evm/artifacts/wsei/WSEI.bin new file mode 100644 index 000000000..471d4c80c --- /dev/null +++ b/x/evm/artifacts/wsei/WSEI.bin @@ -0,0 +1 @@ +60806040526040518060400160405280600b81526020017f57726170706564205365690000000000000000000000000000000000000000008152505f908162000049919062000323565b506040518060400160405280600481526020017f57534549000000000000000000000000000000000000000000000000000000008152506001908162000090919062000323565b50601260025f6101000a81548160ff021916908360ff160217905550348015620000b8575f80fd5b5062000407565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200013b57607f821691505b602082108103620001515762000150620000f6565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f60088302620001b57fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000178565b620001c1868362000178565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f6200020b62000205620001ff84620001d9565b620001e2565b620001d9565b9050919050565b5f819050919050565b6200022683620001eb565b6200023e620002358262000212565b84845462000184565b825550505050565b5f90565b6200025462000246565b620002618184846200021b565b505050565b5b8181101562000288576200027c5f826200024a565b60018101905062000267565b5050565b601f821115620002d757620002a18162000157565b620002ac8462000169565b81016020851015620002bc578190505b620002d4620002cb8562000169565b83018262000266565b50505b505050565b5f82821c905092915050565b5f620002f95f1984600802620002dc565b1980831691505092915050565b5f620003138383620002e8565b9150826002028217905092915050565b6200032e82620000bf565b67ffffffffffffffff8111156200034a5762000349620000c9565b5b62000356825462000123565b620003638282856200028c565b5f60209050601f83116001811462000399575f841562000384578287015190505b62000390858262000306565b865550620003ff565b601f198416620003a98662000157565b5f5b82811015620003d257848901518255600182019150602085019450602081019050620003ab565b86831015620003f25784890151620003ee601f891682620002e8565b8355505b6001600288020188555050505b505050505050565b610e5680620004155f395ff3fe60806040526004361061009f575f3560e01c8063313ce56711610063578063313ce567146101ac57806370a08231146101d657806395d89b4114610212578063a9059cbb1461023c578063d0e30db014610278578063dd62ed3e14610282576100ae565b806306fdde03146100b8578063095ea7b3146100e257806318160ddd1461011e57806323b872dd146101485780632e1a7d4d14610184576100ae565b366100ae576100ac6102be565b005b6100b66102be565b005b3480156100c3575f80fd5b506100cc610361565b6040516100d99190610ace565b60405180910390f35b3480156100ed575f80fd5b5061010860048036038101906101039190610b7f565b6103ec565b6040516101159190610bd7565b60405180910390f35b348015610129575f80fd5b506101326104d9565b60405161013f9190610bff565b60405180910390f35b348015610153575f80fd5b5061016e60048036038101906101699190610c18565b6104e0565b60405161017b9190610bd7565b60405180910390f35b34801561018f575f80fd5b506101aa60048036038101906101a59190610c68565b61082c565b005b3480156101b7575f80fd5b506101c061095d565b6040516101cd9190610cae565b60405180910390f35b3480156101e1575f80fd5b506101fc60048036038101906101f79190610cc7565b61096f565b6040516102099190610bff565b60405180910390f35b34801561021d575f80fd5b50610226610984565b6040516102339190610ace565b60405180910390f35b348015610247575f80fd5b50610262600480360381019061025d9190610b7f565b610a10565b60405161026f9190610bd7565b60405180910390f35b6102806102be565b005b34801561028d575f80fd5b506102a860048036038101906102a39190610cf2565b610a24565b6040516102b59190610bff565b60405180910390f35b3460035f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825461030a9190610d5d565b925050819055503373ffffffffffffffffffffffffffffffffffffffff167fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c346040516103579190610bff565b60405180910390a2565b5f805461036d90610dbd565b80601f016020809104026020016040519081016040528092919081815260200182805461039990610dbd565b80156103e45780601f106103bb576101008083540402835291602001916103e4565b820191905f5260205f20905b8154815290600101906020018083116103c757829003601f168201915b505050505081565b5f8160045f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040516104c79190610bff565b60405180910390a36001905092915050565b5f47905090565b5f8160035f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054101561052a575f80fd5b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16141580156105fe57507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60045f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205414155b15610716578160045f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20541015610687575f80fd5b8160045f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825461070e9190610ded565b925050819055505b8160035f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546107629190610ded565b925050819055508160035f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546107b59190610d5d565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516108199190610bff565b60405180910390a3600190509392505050565b8060035f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20541015610875575f80fd5b8060035f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8282546108c19190610ded565b925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f1935050505015801561090b573d5f803e3d5ffd5b503373ffffffffffffffffffffffffffffffffffffffff167f7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65826040516109529190610bff565b60405180910390a250565b60025f9054906101000a900460ff1681565b6003602052805f5260405f205f915090505481565b6001805461099190610dbd565b80601f01602080910402602001604051908101604052809291908181526020018280546109bd90610dbd565b8015610a085780601f106109df57610100808354040283529160200191610a08565b820191905f5260205f20905b8154815290600101906020018083116109eb57829003601f168201915b505050505081565b5f610a1c3384846104e0565b905092915050565b6004602052815f5260405f20602052805f5260405f205f91509150505481565b5f81519050919050565b5f82825260208201905092915050565b5f5b83811015610a7b578082015181840152602081019050610a60565b5f8484015250505050565b5f601f19601f8301169050919050565b5f610aa082610a44565b610aaa8185610a4e565b9350610aba818560208601610a5e565b610ac381610a86565b840191505092915050565b5f6020820190508181035f830152610ae68184610a96565b905092915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610b1b82610af2565b9050919050565b610b2b81610b11565b8114610b35575f80fd5b50565b5f81359050610b4681610b22565b92915050565b5f819050919050565b610b5e81610b4c565b8114610b68575f80fd5b50565b5f81359050610b7981610b55565b92915050565b5f8060408385031215610b9557610b94610aee565b5b5f610ba285828601610b38565b9250506020610bb385828601610b6b565b9150509250929050565b5f8115159050919050565b610bd181610bbd565b82525050565b5f602082019050610bea5f830184610bc8565b92915050565b610bf981610b4c565b82525050565b5f602082019050610c125f830184610bf0565b92915050565b5f805f60608486031215610c2f57610c2e610aee565b5b5f610c3c86828701610b38565b9350506020610c4d86828701610b38565b9250506040610c5e86828701610b6b565b9150509250925092565b5f60208284031215610c7d57610c7c610aee565b5b5f610c8a84828501610b6b565b91505092915050565b5f60ff82169050919050565b610ca881610c93565b82525050565b5f602082019050610cc15f830184610c9f565b92915050565b5f60208284031215610cdc57610cdb610aee565b5b5f610ce984828501610b38565b91505092915050565b5f8060408385031215610d0857610d07610aee565b5b5f610d1585828601610b38565b9250506020610d2685828601610b38565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610d6782610b4c565b9150610d7283610b4c565b9250828201905080821115610d8a57610d89610d30565b5b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610dd457607f821691505b602082108103610de757610de6610d90565b5b50919050565b5f610df782610b4c565b9150610e0283610b4c565b9250828203905081811115610e1a57610e19610d30565b5b9291505056fea26469706673582212209760e9f7136a7937f92559eee60462de4e4979de76e9753a09c73876151b049864736f6c63430008150033 \ No newline at end of file diff --git a/x/evm/artifacts/wsei/artifacts.go b/x/evm/artifacts/wsei/artifacts.go new file mode 100644 index 000000000..fda3b85c4 --- /dev/null +++ b/x/evm/artifacts/wsei/artifacts.go @@ -0,0 +1,54 @@ +package wsei + +import ( + "embed" + "encoding/hex" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" +) + +const CurrentVersion uint16 = 1 + +//go:embed WSEI.abi +//go:embed WSEI.bin +var f embed.FS + +var cachedBin []byte +var cachedABI *abi.ABI + +func GetABI() []byte { + bz, err := f.ReadFile("WSEI.abi") + if err != nil { + panic("failed to read WSEI contract ABI") + } + return bz +} + +func GetParsedABI() *abi.ABI { + if cachedABI != nil { + return cachedABI + } + parsedABI, err := abi.JSON(strings.NewReader(string(GetABI()))) + if err != nil { + panic(err) + } + cachedABI = &parsedABI + return cachedABI +} + +func GetBin() []byte { + if cachedBin != nil { + return cachedBin + } + code, err := f.ReadFile("WSEI.bin") + if err != nil { + panic("failed to read WSEI contract binary") + } + bz, err := hex.DecodeString(string(code)) + if err != nil { + panic("failed to decode WSEI contract binary") + } + cachedBin = bz + return bz +} diff --git a/x/evm/client/cli/tx.go b/x/evm/client/cli/tx.go index c79f398df..e4b10bf57 100644 --- a/x/evm/client/cli/tx.go +++ b/x/evm/client/cli/tx.go @@ -34,6 +34,7 @@ import ( "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/artifacts/wsei" "github.com/sei-protocol/sei-chain/x/evm/types" "github.com/sei-protocol/sei-chain/x/evm/types/ethtx" ) @@ -61,6 +62,7 @@ func GetTxCmd() *cobra.Command { cmd.AddCommand(CmdDeployErcCw20()) cmd.AddCommand(CmdCallContract()) cmd.AddCommand(CmdDeployErcCw721()) + cmd.AddCommand(CmdDeployWSEI()) cmd.AddCommand(CmdERC20Send()) cmd.AddCommand(CmdDelegate()) cmd.AddCommand(NativeSendTxCmd()) @@ -662,6 +664,72 @@ func CmdDelegate() *cobra.Command { return cmd } +func CmdDeployWSEI() *cobra.Command { + cmd := &cobra.Command{ + Use: "deploy-wsei --from= --gas-fee-cap= --gas-limt= --evm-rpc=", + Short: "Deploy ERC20 contract for a native Sei token", + Long: "", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) (err error) { + contractData := wsei.GetBin() + + key, err := getPrivateKey(cmd) + if err != nil { + return err + } + + rpc, err := cmd.Flags().GetString(FlagRPC) + if err != nil { + return err + } + var nonce uint64 + if n, err := cmd.Flags().GetInt64(FlagNonce); err == nil && n >= 0 { + nonce = uint64(n) + } else { + nonce, err = getNonce(rpc, key.PublicKey) + if err != nil { + return err + } + } + + txData, err := getTxData(cmd) + if err != nil { + return err + } + txData.Nonce = nonce + txData.Value = utils.Big0 + txData.Data = contractData + + resp, err := sendTx(txData, rpc, key) + if err != nil { + return err + } + + senderAddr := crypto.PubkeyToAddress(key.PublicKey) + data, err := rlp.EncodeToBytes([]interface{}{senderAddr, nonce}) + if err != nil { + return err + } + hash := crypto.Keccak256Hash(data) + contractAddress := hash.Bytes()[12:] + contractAddressHex := hex.EncodeToString(contractAddress) + + fmt.Println("Deployer:", senderAddr) + fmt.Println("Deployed to:", fmt.Sprintf("0x%s", contractAddressHex)) + fmt.Println("Transaction hash:", resp.Hex()) + return nil + }, + } + + cmd.Flags().Uint64(FlagGasFeeCap, 1000000000000, "Gas fee cap for the transaction") + cmd.Flags().Uint64(FlagGas, 5000000, "Gas limit for the transaction") + cmd.Flags().String(FlagRPC, fmt.Sprintf("http://%s:8545", evmrpc.LocalAddress), "RPC endpoint to send request to") + cmd.Flags().Int64(FlagNonce, -1, "Nonce override for the transaction. Negative value means no override") + flags.AddTxFlagsToCmd(cmd) + + return cmd +} + func getPrivateKey(cmd *cobra.Command) (*ecdsa.PrivateKey, error) { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { From 465eaf3a543c9c25d32473b160fdfa987e92d5e9 Mon Sep 17 00:00:00 2001 From: Steven Landers Date: Wed, 3 Apr 2024 10:22:10 -0400 Subject: [PATCH 10/34] [SeiV2] Add EVM txs to OCC test library (#1503) * refactor to wrap sdk message * ensure evm info is set for occ txs * fix imports so my eyes don't bleed * goimports * make name specific per tx scenario --- app/app.go | 1 + occ_tests/messages/test_msgs.go | 96 +++++++++++++++++++++++++++------ occ_tests/occ_test.go | 32 ++++++++--- occ_tests/utils/utils.go | 69 ++++++++++++++++++++---- 4 files changed, 165 insertions(+), 33 deletions(-) diff --git a/app/app.go b/app/app.go index ca3099fb7..6321c92d8 100644 --- a/app/app.go +++ b/app/app.go @@ -1443,6 +1443,7 @@ func (app *App) ProcessTXsWithOCC(ctx sdk.Context, txs [][]byte, typedTxs []sdk. GasUsed: r.Response.GasUsed, Events: r.Response.Events, Codespace: r.Response.Codespace, + EvmTxInfo: r.Response.EvmTxInfo, }) } diff --git a/occ_tests/messages/test_msgs.go b/occ_tests/messages/test_msgs.go index 0a7f88536..a9f3c2d2a 100644 --- a/occ_tests/messages/test_msgs.go +++ b/occ_tests/messages/test_msgs.go @@ -2,13 +2,17 @@ package messages import ( "fmt" + "math/big" "github.com/CosmWasm/wasmd/x/wasm" - 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" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/sei-protocol/sei-chain/occ_tests/utils" + "github.com/sei-protocol/sei-chain/x/evm/types" + "github.com/sei-protocol/sei-chain/x/evm/types/ethtx" ) const instantiateMsg = `{"whitelist": ["sei1h9yjz89tl0dl6zu65dpxcqnxfhq60wxx8s5kag"], @@ -34,38 +38,98 @@ const instantiateMsg = `{"whitelist": ["sei1h9yjz89tl0dl6zu65dpxcqnxfhq60wxx8s5k "maintenance":"0.06" }}` -func WasmInstantiate(tCtx *utils.TestContext, count int) []sdk.Msg { - var msgs []sdk.Msg +func WasmInstantiate(tCtx *utils.TestContext, count int) []*utils.TestMessage { + var msgs []*utils.TestMessage for i := 0; i < count; i++ { - msgs = append(msgs, &wasm.MsgInstantiateContract{ - Sender: tCtx.TestAccounts[0].AccountAddress.String(), - Admin: tCtx.TestAccounts[1].AccountAddress.String(), - CodeID: tCtx.CodeID, - Label: fmt.Sprintf("test-%d", i), - Msg: []byte(instantiateMsg), - Funds: utils.Funds(100000), + msgs = append(msgs, &utils.TestMessage{ + Msg: &wasm.MsgInstantiateContract{ + Sender: tCtx.TestAccounts[0].AccountAddress.String(), + Admin: tCtx.TestAccounts[1].AccountAddress.String(), + CodeID: tCtx.CodeID, + Label: fmt.Sprintf("test-%d", i), + Msg: []byte(instantiateMsg), + Funds: utils.Funds(100000), + }, + Type: "WasmInstantitate", }) } return msgs } -func BankTransfer(tCtx *utils.TestContext, count int) []sdk.Msg { - var msgs []sdk.Msg +// EVMTransferNonConflicting generates a list of EVM transfer messages that do not conflict with each other +// each message will have a brand new address +func EVMTransferNonConflicting(tCtx *utils.TestContext, count int) []*utils.TestMessage { + var msgs []*utils.TestMessage for i := 0; i < count; i++ { - msgs = append(msgs, banktypes.NewMsgSend(tCtx.TestAccounts[0].AccountAddress, tCtx.TestAccounts[1].AccountAddress, utils.Funds(int64(i+1)))) + testAcct := utils.NewSigner() + msgs = append(msgs, evmTransfer(testAcct, testAcct.EvmAddress, "EVMTransferNonConflicting")) } return msgs } -func GovernanceSubmitProposal(tCtx *utils.TestContext, count int) []sdk.Msg { - var msgs []sdk.Msg +// EVMTransferConflicting generates a list of EVM transfer messages to the same address +func EVMTransferConflicting(tCtx *utils.TestContext, count int) []*utils.TestMessage { + var msgs []*utils.TestMessage + for i := 0; i < count; i++ { + testAcct := utils.NewSigner() + msgs = append(msgs, evmTransfer(testAcct, tCtx.TestAccounts[0].EvmAddress, "EVMTransferConflicting")) + } + return msgs +} + +// EVMTransferNonConflicting generates a list of EVM transfer messages that do not conflict with each other +// each message will have a brand new address +func evmTransfer(testAcct utils.TestAcct, to common.Address, scenario string) *utils.TestMessage { + signedTx, err := ethtypes.SignTx(ethtypes.NewTx(ðtypes.DynamicFeeTx{ + GasFeeCap: new(big.Int).SetUint64(1000000000000), + GasTipCap: new(big.Int).SetUint64(1000000000000), + Gas: 21000, + ChainID: big.NewInt(1), + To: &to, + Value: big.NewInt(1), + Nonce: 0, + }), testAcct.EvmSigner, testAcct.EvmPrivateKey) + + if err != nil { + panic(err) + } + + txData, err := ethtx.NewTxDataFromTx(signedTx) + if err != nil { + panic(err) + } + + msg, err := types.NewMsgEVMTransaction(txData) + if err != nil { + panic(err) + } + + return &utils.TestMessage{ + Msg: msg, + IsEVM: true, + EVMSigner: testAcct, + Type: scenario, + } +} + +func BankTransfer(tCtx *utils.TestContext, count int) []*utils.TestMessage { + var msgs []*utils.TestMessage + for i := 0; i < count; i++ { + msg := banktypes.NewMsgSend(tCtx.TestAccounts[0].AccountAddress, tCtx.TestAccounts[1].AccountAddress, utils.Funds(int64(i+1))) + msgs = append(msgs, &utils.TestMessage{Msg: msg, Type: "BankTransfer"}) + } + return msgs +} + +func GovernanceSubmitProposal(tCtx *utils.TestContext, count int) []*utils.TestMessage { + var msgs []*utils.TestMessage for i := 0; i < count; i++ { content := govtypes.NewTextProposal(fmt.Sprintf("Proposal %d", i), "test", true) mp, err := govtypes.NewMsgSubmitProposalWithExpedite(content, utils.Funds(10000), tCtx.TestAccounts[0].AccountAddress, true) if err != nil { panic(err) } - msgs = append(msgs, mp) + msgs = append(msgs, &utils.TestMessage{Msg: mp, Type: "GovernanceSubmitProposal"}) } return msgs } diff --git a/occ_tests/occ_test.go b/occ_tests/occ_test.go index 91b034ea8..ff5cb3c0c 100644 --- a/occ_tests/occ_test.go +++ b/occ_tests/occ_test.go @@ -106,12 +106,12 @@ func TestParallelTransactions(t *testing.T) { runs int shuffle bool before func(tCtx *utils.TestContext) - txs func(tCtx *utils.TestContext) []sdk.Msg + txs func(tCtx *utils.TestContext) []*utils.TestMessage }{ { name: "Test wasm instantiations", runs: runs, - txs: func(tCtx *utils.TestContext) []sdk.Msg { + txs: func(tCtx *utils.TestContext) []*utils.TestMessage { return utils.JoinMsgs( messages.WasmInstantiate(tCtx, 10), ) @@ -120,7 +120,7 @@ func TestParallelTransactions(t *testing.T) { { name: "Test bank transfer", runs: runs, - txs: func(tCtx *utils.TestContext) []sdk.Msg { + txs: func(tCtx *utils.TestContext) []*utils.TestMessage { return utils.JoinMsgs( messages.BankTransfer(tCtx, 2), ) @@ -129,21 +129,41 @@ func TestParallelTransactions(t *testing.T) { { name: "Test governance proposal", runs: runs, - txs: func(tCtx *utils.TestContext) []sdk.Msg { + txs: func(tCtx *utils.TestContext) []*utils.TestMessage { return utils.JoinMsgs( messages.GovernanceSubmitProposal(tCtx, 10), ) }, }, + { + name: "Test evm transfers non-conflicting", + runs: runs, + txs: func(tCtx *utils.TestContext) []*utils.TestMessage { + return utils.JoinMsgs( + messages.EVMTransferNonConflicting(tCtx, 10), + ) + }, + }, + { + name: "Test evm transfers conflicting", + runs: runs, + txs: func(tCtx *utils.TestContext) []*utils.TestMessage { + return utils.JoinMsgs( + messages.EVMTransferConflicting(tCtx, 10), + ) + }, + }, { name: "Test combinations", runs: runs, shuffle: true, - txs: func(tCtx *utils.TestContext) []sdk.Msg { + txs: func(tCtx *utils.TestContext) []*utils.TestMessage { return utils.JoinMsgs( messages.WasmInstantiate(tCtx, 10), messages.BankTransfer(tCtx, 10), messages.GovernanceSubmitProposal(tCtx, 10), + messages.EVMTransferConflicting(tCtx, 10), + messages.EVMTransferNonConflicting(tCtx, 10), ) }, }, @@ -178,8 +198,8 @@ func TestParallelTransactions(t *testing.T) { require.NoError(t, pErr, tt.name) require.Len(t, pResults, len(txs)) - assertEqualEvents(t, sEvts, pEvts, tt.name) assertExecTxResultCode(t, sResults, pResults, 0, tt.name) + assertEqualEvents(t, sEvts, pEvts, tt.name) assertEqualExecTxResults(t, sResults, pResults, tt.name) assertEqualState(t, sCtx.Ctx, pCtx.Ctx, tt.name) } diff --git a/occ_tests/utils/utils.go b/occ_tests/utils/utils.go index 7b671ed80..4b20702ef 100644 --- a/occ_tests/utils/utils.go +++ b/occ_tests/utils/utils.go @@ -2,6 +2,9 @@ package utils import ( "context" + "crypto/ecdsa" + "encoding/hex" + "math/big" "math/rand" "os" "testing" @@ -20,14 +23,19 @@ import ( "github.com/cosmos/cosmos-sdk/types/tx/signing" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" "github.com/cosmos/cosmos-sdk/x/auth/tx" + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/abci/types" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/sei-protocol/sei-chain/app" + utils2 "github.com/sei-protocol/sei-chain/utils" dexcache "github.com/sei-protocol/sei-chain/x/dex/cache" dextypes "github.com/sei-protocol/sei-chain/x/dex/types" dexutils "github.com/sei-protocol/sei-chain/x/dex/utils" + types2 "github.com/sei-protocol/sei-chain/x/evm/types" minttypes "github.com/sei-protocol/sei-chain/x/mint/types" ) @@ -38,6 +46,13 @@ var ignoredStoreKeys = map[string]struct{}{ "deferredcache": {}, } +type TestMessage struct { + Msg sdk.Msg + Type string + EVMSigner TestAcct + IsEVM bool +} + type TestContext struct { Ctx sdk.Context CodeID uint64 @@ -52,6 +67,9 @@ type TestAcct struct { AccountAddress sdk.AccAddress PrivateKey cryptotypes.PrivKey PublicKey cryptotypes.PubKey + EvmAddress common.Address + EvmSigner ethtypes.Signer + EvmPrivateKey *ecdsa.PrivateKey } func NewTestAccounts(count int) []TestAcct { @@ -66,11 +84,20 @@ func NewSigner() TestAcct { priv1, pubKey, acct := testdata.KeyTestPubAddr() val := addressToValAddress(acct) + pvKeyHex := hex.EncodeToString(priv1.Bytes()) + key, _ := crypto.HexToECDSA(pvKeyHex) + ethCfg := types2.DefaultChainConfig().EthereumConfig(big.NewInt(1)) + signer := ethtypes.MakeSigner(ethCfg, utils2.Big1, 1) + address := crypto.PubkeyToAddress(key.PublicKey) + return TestAcct{ ValidatorAddress: val, AccountAddress: acct, PrivateKey: priv1, PublicKey: pubKey, + EvmAddress: address, + EvmSigner: signer, + EvmPrivateKey: key, } } @@ -130,14 +157,15 @@ func NewTestContext(t *testing.T, testAccts []TestAcct, blockTime time.Time, wor } } -func toTxBytes(testCtx *TestContext, msgs []sdk.Msg) [][]byte { +func toTxBytes(testCtx *TestContext, msgs []*TestMessage) [][]byte { txs := make([][]byte, 0, len(msgs)) tc := app.MakeEncodingConfig().TxConfig priv := testCtx.TestAccounts[0].PrivateKey acct := testCtx.TestApp.AccountKeeper.GetAccount(testCtx.Ctx, testCtx.TestAccounts[0].AccountAddress) - for _, m := range msgs { + for _, tm := range msgs { + m := tm.Msg a, err := codectypes.NewAnyWithValue(m) if err != nil { panic(err) @@ -157,6 +185,25 @@ func toTxBytes(testCtx *TestContext, msgs []sdk.Msg) [][]byte { }, }) + if tm.IsEVM { + amounts := sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1000000000000000000)), sdk.NewCoin("uusdc", sdk.NewInt(1000000000000000))) + + // fund account so it has funds + if err := testCtx.TestApp.BankKeeper.MintCoins(testCtx.Ctx, minttypes.ModuleName, amounts); err != nil { + panic(err) + } + if err := testCtx.TestApp.BankKeeper.SendCoinsFromModuleToAccount(testCtx.Ctx, minttypes.ModuleName, tm.EVMSigner.AccountAddress, amounts); err != nil { + panic(err) + } + + b, err := tc.TxEncoder()(tBuilder.GetTx()) + if err != nil { + panic(err) + } + txs = append(txs, b) + continue + } + err = tBuilder.SetSignatures(signing.SignatureV2{ PubKey: priv.PubKey(), Data: &signing.SingleSignatureData{ @@ -202,16 +249,16 @@ func toTxBytes(testCtx *TestContext, msgs []sdk.Msg) [][]byte { } // RunWithOCC runs the given messages with OCC enabled, number of workers is configured via context -func RunWithOCC(testCtx *TestContext, msgs []sdk.Msg) ([]types.Event, []*types.ExecTxResult, types.ResponseEndBlock, error) { +func RunWithOCC(testCtx *TestContext, msgs []*TestMessage) ([]types.Event, []*types.ExecTxResult, types.ResponseEndBlock, error) { return runTxs(testCtx, msgs, true) } // RunWithoutOCC runs the given messages without OCC enabled -func RunWithoutOCC(testCtx *TestContext, msgs []sdk.Msg) ([]types.Event, []*types.ExecTxResult, types.ResponseEndBlock, error) { +func RunWithoutOCC(testCtx *TestContext, msgs []*TestMessage) ([]types.Event, []*types.ExecTxResult, types.ResponseEndBlock, error) { return runTxs(testCtx, msgs, false) } -func runTxs(testCtx *TestContext, msgs []sdk.Msg, occ bool) ([]types.Event, []*types.ExecTxResult, types.ResponseEndBlock, error) { +func runTxs(testCtx *TestContext, msgs []*TestMessage, occ bool) ([]types.Event, []*types.ExecTxResult, types.ResponseEndBlock, error) { app.EnableOCC = occ txs := toTxBytes(testCtx, msgs) req := &types.RequestFinalizeBlock{ @@ -222,16 +269,16 @@ func runTxs(testCtx *TestContext, msgs []sdk.Msg, occ bool) ([]types.Event, []*t return testCtx.TestApp.ProcessBlock(testCtx.Ctx, txs, req, req.DecidedLastCommit) } -func JoinMsgs(msgsList ...[]sdk.Msg) []sdk.Msg { - var result []sdk.Msg - for _, msgs := range msgsList { - result = append(result, msgs...) +func JoinMsgs(msgsList ...[]*TestMessage) []*TestMessage { + var result []*TestMessage + for _, testMsg := range msgsList { + result = append(result, testMsg...) } return result } -func Shuffle(msgs []sdk.Msg) []sdk.Msg { - result := make([]sdk.Msg, 0, len(msgs)) +func Shuffle(msgs []*TestMessage) []*TestMessage { + result := make([]*TestMessage, 0, len(msgs)) for _, i := range rand.Perm(len(msgs)) { result = append(result, msgs[i]) } From 5442e030d8fac7501b483b30eddad0c3380a7cbd Mon Sep 17 00:00:00 2001 From: codchen Date: Thu, 4 Apr 2024 11:17:41 +0800 Subject: [PATCH 11/34] Optimize replay (#1505) * Optimize replay * check state --- app/eth_replay.go | 1 + x/evm/keeper/replay.go | 17 +++++++++++++++++ x/evm/module.go | 7 +------ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/app/eth_replay.go b/app/eth_replay.go index 5b3cf6197..21746b6de 100644 --- a/app/eth_replay.go +++ b/app/eth_replay.go @@ -90,6 +90,7 @@ func Replay(a *App) { a.Logger().Info(fmt.Sprintf("Verifying tx %s", tx.Hash().Hex())) if tx.To() != nil { a.EvmKeeper.VerifyBalance(ctx, *tx.To()) + a.EvmKeeper.VerifyState(ctx, *tx.To()) } a.EvmKeeper.VerifyTxResult(ctx, tx.Hash()) } diff --git a/x/evm/keeper/replay.go b/x/evm/keeper/replay.go index 1b2720e1a..c202b38ed 100644 --- a/x/evm/keeper/replay.go +++ b/x/evm/keeper/replay.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" + "github.com/sei-protocol/sei-chain/x/evm/types" ) func (k *Keeper) VerifyBalance(ctx sdk.Context, addr common.Address) { @@ -87,3 +88,19 @@ func (k *Keeper) VerifyAccount(ctx sdk.Context, addr common.Address, accountData panic(fmt.Sprintf("nonce mismatch for address %s: expected %d, got %d", addr.Hex(), nonce, k.GetNonce(ctx, addr))) } } + +func (k *Keeper) VerifyState(ctx sdk.Context, addr common.Address) { + store := k.PrefixStore(ctx, types.StateKey(addr)) + iter := store.Iterator(nil, nil) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + key := common.BytesToHash(iter.Key()) + ethVal, err := k.EthClient.StorageAt(ctx.Context(), addr, key, k.ReplayBlock.Number()) + if err != nil { + panic(err) + } + if !bytes.Equal(iter.Value(), ethVal) { + panic(fmt.Sprintf("state mismatch for address %s hash %s: expected %X but got %X", addr.Hex(), key.Hex(), ethVal, iter.Value())) + } + } +} diff --git a/x/evm/module.go b/x/evm/module.go index 5fd7810e8..16490ef5e 100644 --- a/x/evm/module.go +++ b/x/evm/module.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "math" - "math/big" // this line is used by starport scaffolding # 1 @@ -213,11 +212,7 @@ func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.Val } coinbase = am.keeper.GetSeiAddressOrDefault(ctx, block.BlockHeader.Coinbase) } else if am.keeper.EthReplayConfig.Enabled { - block, err := am.keeper.EthClient.BlockByNumber(ctx.Context(), big.NewInt(ctx.BlockHeight()+am.keeper.GetReplayInitialHeight(ctx))) - if err != nil { - panic(fmt.Sprintf("error getting block at height %d", ctx.BlockHeight()+am.keeper.GetReplayInitialHeight(ctx))) - } - coinbase = am.keeper.GetSeiAddressOrDefault(ctx, block.Header_.Coinbase) + coinbase = am.keeper.GetSeiAddressOrDefault(ctx, am.keeper.ReplayBlock.Header_.Coinbase) am.keeper.SetReplayedHeight(ctx) } else { coinbase = am.keeper.AccountKeeper().GetModuleAddress(authtypes.FeeCollectorName) From 51b45873b540c6e6c03cd07541433167cfe07d05 Mon Sep 17 00:00:00 2001 From: Kartik Bhat Date: Thu, 4 Apr 2024 09:15:39 -0400 Subject: [PATCH 12/34] Update EVM Basic Ante (#1491) evm Basic Ante during delivery --- x/evm/ante/basic.go | 3 --- x/evm/ante/basic_test.go | 3 +-- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/x/evm/ante/basic.go b/x/evm/ante/basic.go index 9871b289f..a0e407f37 100644 --- a/x/evm/ante/basic.go +++ b/x/evm/ante/basic.go @@ -23,9 +23,6 @@ func NewBasicDecorator() *BasicDecorator { // cherrypicked from go-ethereum:txpool:ValidateTransaction func (gl BasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { - if !ctx.IsCheckTx() { - return next(ctx, tx, simulate) - } msg := evmtypes.MustGetEVMTransactionMessage(tx) etx, _ := msg.AsTransaction() diff --git a/x/evm/ante/basic_test.go b/x/evm/ante/basic_test.go index 860cbfea4..c7d5255f0 100644 --- a/x/evm/ante/basic_test.go +++ b/x/evm/ante/basic_test.go @@ -20,8 +20,7 @@ func TestBasicDecorator(t *testing.T) { ctx, err := a.AnteHandle(ctx, &mockTx{msgs: []sdk.Msg{msg}}, false, func(ctx sdk.Context, _ sdk.Tx, _ bool) (sdk.Context, error) { return ctx, nil }) - require.Nil(t, err) // is not checktx - ctx = ctx.WithIsCheckTx(true) + require.NotNil(t, err) // expect out of gas err dataTooLarge := make([]byte, params.MaxInitCodeSize+1) for i := 0; i <= params.MaxInitCodeSize; i++ { dataTooLarge[i] = 1 From 41bb27bf7b136f4ba50a1a31cb854805ab543bcc Mon Sep 17 00:00:00 2001 From: Kartik Bhat Date: Thu, 4 Apr 2024 09:16:22 -0400 Subject: [PATCH 13/34] Update Precompiles Input Method Parse + Evm Exists (#1494) Update Precompile Input Method + Evm Balance --- precompiles/addr/addr.go | 5 ++++- precompiles/bank/bank.go | 8 +++++--- precompiles/common/precompiles.go | 13 ++++++++++++- precompiles/distribution/distribution.go | 5 ++++- precompiles/gov/gov.go | 5 ++++- precompiles/ibc/ibc.go | 5 ++++- precompiles/json/json.go | 9 +++++++++ precompiles/oracle/oracle.go | 5 ++++- precompiles/staking/staking.go | 5 ++++- precompiles/wasmd/wasmd.go | 5 ++++- x/evm/state/check.go | 5 +++++ x/evm/state/check_test.go | 9 +++++++-- 12 files changed, 66 insertions(+), 13 deletions(-) diff --git a/precompiles/addr/addr.go b/precompiles/addr/addr.go index ac1c80513..9d10fb1b7 100644 --- a/precompiles/addr/addr.go +++ b/precompiles/addr/addr.go @@ -70,7 +70,10 @@ func NewPrecompile(evmKeeper pcommon.EVMKeeper) (*Precompile, error) { // RequiredGas returns the required bare minimum gas to execute the precompile. func (p Precompile) RequiredGas(input []byte) uint64 { - methodID := input[:4] + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return 0 + } method, err := p.ABI.MethodById(methodID) if err != nil { diff --git a/precompiles/bank/bank.go b/precompiles/bank/bank.go index d5ff728a9..99da36812 100644 --- a/precompiles/bank/bank.go +++ b/precompiles/bank/bank.go @@ -99,7 +99,10 @@ func NewPrecompile(bankKeeper pcommon.BankKeeper, evmKeeper pcommon.EVMKeeper) ( // RequiredGas returns the required bare minimum gas to execute the precompile. func (p Precompile) RequiredGas(input []byte) uint64 { - methodID := input[:4] + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return 0 + } method, err := p.ABI.MethodById(methodID) if err != nil { @@ -127,7 +130,6 @@ func (p Precompile) Run(evm *vm.EVM, caller common.Address, input []byte, value } return p.send(ctx, method, args, value) case SendNativeMethod: - // TODO: Add validation on caller separate from validation above return p.sendNative(ctx, method, args, caller, value) case BalanceMethod: return p.balance(ctx, method, args, value) @@ -255,7 +257,7 @@ func (p Precompile) symbol(ctx sdk.Context, method *abi.Method, args []interface func (p Precompile) decimals(_ sdk.Context, method *abi.Method, _ []interface{}, value *big.Int) ([]byte, error) { pcommon.AssertNonPayable(value) - // all native tokens are integer-based + // all native tokens are integer-based, returns decimals for microdenom (usei) return method.Outputs.Pack(uint8(0)) } diff --git a/precompiles/common/precompiles.go b/precompiles/common/precompiles.go index 6cdc89216..7579c4073 100644 --- a/precompiles/common/precompiles.go +++ b/precompiles/common/precompiles.go @@ -36,7 +36,10 @@ func (p Precompile) Prepare(evm *vm.EVM, input []byte) (sdk.Context, *abi.Method if !ok { return sdk.Context{}, nil, nil, errors.New("cannot get context from EVM") } - methodID := input[:4] + 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 @@ -102,3 +105,11 @@ func ValidateCaller(ctx sdk.Context, evmKeeper EVMKeeper, caller common.Address, } return fmt.Errorf("calling contract %s with code hash %s is not whitelisted for delegate calls", callingContract.Hex(), codeHash.Hex()) } + +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/distribution/distribution.go b/precompiles/distribution/distribution.go index 1465b1e7e..933529170 100644 --- a/precompiles/distribution/distribution.go +++ b/precompiles/distribution/distribution.go @@ -76,7 +76,10 @@ func NewPrecompile(distrKeeper pcommon.DistributionKeeper, evmKeeper pcommon.EVM // RequiredGas returns the required bare minimum gas to execute the precompile. func (p Precompile) RequiredGas(input []byte) uint64 { - methodID := input[:4] + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return 0 + } if bytes.Equal(methodID, p.SetWithdrawAddrID) { return 30000 diff --git a/precompiles/gov/gov.go b/precompiles/gov/gov.go index ea372d21f..b140cf608 100644 --- a/precompiles/gov/gov.go +++ b/precompiles/gov/gov.go @@ -79,7 +79,10 @@ func NewPrecompile(govKeeper pcommon.GovKeeper, evmKeeper pcommon.EVMKeeper, ban // RequiredGas returns the required bare minimum gas to execute the precompile. func (p Precompile) RequiredGas(input []byte) uint64 { - methodID := input[:4] + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return 0 + } if bytes.Equal(methodID, p.VoteID) { return 30000 diff --git a/precompiles/ibc/ibc.go b/precompiles/ibc/ibc.go index b83b0792b..c23d15dec 100644 --- a/precompiles/ibc/ibc.go +++ b/precompiles/ibc/ibc.go @@ -78,7 +78,10 @@ func NewPrecompile(transferKeeper pcommon.TransferKeeper, evmKeeper pcommon.EVMK // RequiredGas returns the required bare minimum gas to execute the precompile. func (p Precompile) RequiredGas(input []byte) uint64 { - methodID := input[:4] + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return 0 + } method, err := p.ABI.MethodById(methodID) if err != nil { diff --git a/precompiles/json/json.go b/precompiles/json/json.go index 1a3d5eb4c..5a0434751 100644 --- a/precompiles/json/json.go +++ b/precompiles/json/json.go @@ -4,6 +4,7 @@ import ( "bytes" "embed" gjson "encoding/json" + "errors" "fmt" "math/big" "strings" @@ -73,6 +74,9 @@ func NewPrecompile() (*Precompile, error) { // RequiredGas returns the required bare minimum gas to execute the precompile. func (p Precompile) RequiredGas(input []byte) uint64 { + if len(input) < 4 { + return 0 + } return uint64(GasCostPerByte * (len(input) - 4)) } @@ -101,6 +105,11 @@ func (p Precompile) Run(evm *vm.EVM, _ common.Address, input []byte, value *big. 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 } diff --git a/precompiles/oracle/oracle.go b/precompiles/oracle/oracle.go index fbe18402e..110809e93 100644 --- a/precompiles/oracle/oracle.go +++ b/precompiles/oracle/oracle.go @@ -95,7 +95,10 @@ func NewPrecompile(oracleKeeper pcommon.OracleKeeper, evmKeeper pcommon.EVMKeepe // RequiredGas returns the required bare minimum gas to execute the precompile. func (p Precompile) RequiredGas(input []byte) uint64 { - methodID := input[:4] + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return 0 + } method, err := p.ABI.MethodById(methodID) if err != nil { diff --git a/precompiles/staking/staking.go b/precompiles/staking/staking.go index 7115fec03..6b27362c3 100644 --- a/precompiles/staking/staking.go +++ b/precompiles/staking/staking.go @@ -84,7 +84,10 @@ func NewPrecompile(stakingKeeper pcommon.StakingKeeper, evmKeeper pcommon.EVMKee // RequiredGas returns the required bare minimum gas to execute the precompile. func (p Precompile) RequiredGas(input []byte) uint64 { - methodID := input[:4] + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return 0 + } if bytes.Equal(methodID, p.DelegateID) { return 50000 diff --git a/precompiles/wasmd/wasmd.go b/precompiles/wasmd/wasmd.go index 213f1b5c1..94688ec8c 100644 --- a/precompiles/wasmd/wasmd.go +++ b/precompiles/wasmd/wasmd.go @@ -80,7 +80,10 @@ func NewPrecompile(evmKeeper pcommon.EVMKeeper, wasmdKeeper pcommon.WasmdKeeper, // RequiredGas returns the required bare minimum gas to execute the precompile. func (p Precompile) RequiredGas(input []byte) uint64 { - methodID := input[:4] + methodID, err := pcommon.ExtractMethodID(input) + if err != nil { + return 0 + } method, err := p.ABI.MethodById(methodID) if err != nil { diff --git a/x/evm/state/check.go b/x/evm/state/check.go index 7053a4b86..3b3a6b606 100644 --- a/x/evm/state/check.go +++ b/x/evm/state/check.go @@ -23,6 +23,11 @@ func (s *DBImpl) Exist(addr common.Address) bool { return true } + // check if account has a balance + if s.GetBalance(addr).Cmp(utils.Big0) > 0 { + return true + } + // go-ethereum impl considers just-deleted accounts as "exist" as well return s.HasSelfDestructed(addr) } diff --git a/x/evm/state/check_test.go b/x/evm/state/check_test.go index 8d3d32133..13bd435f3 100644 --- a/x/evm/state/check_test.go +++ b/x/evm/state/check_test.go @@ -22,10 +22,15 @@ func TestExist(t *testing.T) { statedb.SetCode(addr2, []byte{3}) require.True(t, statedb.Exist(addr2)) - // destructed + // has balance _, addr3 := testkeeper.MockAddressPair() - statedb.SelfDestruct(addr3) + statedb.AddBalance(addr3, big.NewInt(1000000000000)) require.True(t, statedb.Exist(addr3)) + + // destructed + _, addr4 := testkeeper.MockAddressPair() + statedb.SelfDestruct(addr4) + require.True(t, statedb.Exist(addr4)) } func TestEmpty(t *testing.T) { From ead66f723cca876c5ba47021d285bd801b1fd17b Mon Sep 17 00:00:00 2001 From: Kartik Bhat Date: Thu, 4 Apr 2024 09:24:53 -0400 Subject: [PATCH 14/34] Precompile Remove Panics + Add More Validation (#1504) Add More Precompile Validation --- precompiles/distribution/distribution.go | 4 ++- precompiles/gov/gov.go | 4 ++- precompiles/staking/staking.go | 4 ++- precompiles/wasmd/wasmd.go | 38 ++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/precompiles/distribution/distribution.go b/precompiles/distribution/distribution.go index 933529170..3f26ef6d3 100644 --- a/precompiles/distribution/distribution.go +++ b/precompiles/distribution/distribution.go @@ -86,7 +86,9 @@ func (p Precompile) RequiredGas(input []byte) uint64 { } else if bytes.Equal(methodID, p.WithdrawDelegationRewardsID) { return 50000 } - panic("unknown method") + + // This should never happen since this is going to fail during Run + return 0 } func (p Precompile) Address() common.Address { diff --git a/precompiles/gov/gov.go b/precompiles/gov/gov.go index b140cf608..34f84f88f 100644 --- a/precompiles/gov/gov.go +++ b/precompiles/gov/gov.go @@ -89,7 +89,9 @@ func (p Precompile) RequiredGas(input []byte) uint64 { } else if bytes.Equal(methodID, p.DepositID) { return 30000 } - panic("unknown method") + + // This should never happen since this is going to fail during Run + return 0 } func (p Precompile) Address() common.Address { diff --git a/precompiles/staking/staking.go b/precompiles/staking/staking.go index 6b27362c3..cae41df24 100644 --- a/precompiles/staking/staking.go +++ b/precompiles/staking/staking.go @@ -96,7 +96,9 @@ func (p Precompile) RequiredGas(input []byte) uint64 { } else if bytes.Equal(methodID, p.UndelegateID) { return 50000 } - panic("unknown method") + + // This should never happen since this is going to fail during Run + return 0 } func (p Precompile) Address() common.Address { diff --git a/precompiles/wasmd/wasmd.go b/precompiles/wasmd/wasmd.go index 94688ec8c..66677ab2c 100644 --- a/precompiles/wasmd/wasmd.go +++ b/precompiles/wasmd/wasmd.go @@ -8,6 +8,7 @@ import ( "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" @@ -167,6 +168,7 @@ func (p Precompile) instantiate(ctx sdk.Context, method *abi.Method, caller comm label := args[3].(string) coins := sdk.NewCoins() coinsBz := args[4].([]byte) + if err := json.Unmarshal(coinsBz, &coins); err != nil { rerr = err return @@ -175,6 +177,22 @@ func (p Precompile) instantiate(ctx sdk.Context, method *abi.Method, caller comm rerr = errors.New("deposit of usei must be done through the `value` field") 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 + } + if value != nil { coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), creatorAddr, value, p.bankKeeper) if err != nil { @@ -225,6 +243,19 @@ func (p Precompile) execute(ctx sdk.Context, method *abi.Method, caller common.A rerr = errors.New("deposit of usei must be done through the `value` field") 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 + } + if value != nil { coin, err := pcommon.HandlePaymentUsei(ctx, p.evmKeeper.GetSeiAddressOrDefault(ctx, p.address), senderAddr, value, p.bankKeeper) if err != nil { @@ -263,6 +294,13 @@ func (p Precompile) query(ctx sdk.Context, method *abi.Method, args []interface{ 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 From 507ab0b415e7b15d6dcfb515306dcd1203a9bb00 Mon Sep 17 00:00:00 2001 From: codchen Date: Thu, 4 Apr 2024 22:28:02 +0800 Subject: [PATCH 15/34] Add gas limit for EVM static call query (#1509) --- app/app.go | 6 ++++++ cmd/seid/cmd/root.go | 7 +++++++ x/evm/keeper/grpc_query.go | 3 +++ x/evm/keeper/keeper.go | 3 +++ x/evm/querier/config.go | 29 +++++++++++++++++++++++++++++ 5 files changed, 48 insertions(+) create mode 100644 x/evm/querier/config.go diff --git a/app/app.go b/app/app.go index 6321c92d8..2a3b57b2d 100644 --- a/app/app.go +++ b/app/app.go @@ -123,6 +123,7 @@ import ( evmante "github.com/sei-protocol/sei-chain/x/evm/ante" "github.com/sei-protocol/sei-chain/x/evm/blocktest" evmkeeper "github.com/sei-protocol/sei-chain/x/evm/keeper" + "github.com/sei-protocol/sei-chain/x/evm/querier" "github.com/sei-protocol/sei-chain/x/evm/replay" evmtypes "github.com/sei-protocol/sei-chain/x/evm/types" "github.com/spf13/cast" @@ -593,6 +594,11 @@ func New( if err != nil { panic(fmt.Sprintf("error reading EVM config due to %s", err)) } + evmQueryConfig, err := querier.ReadConfig(appOpts) + if err != nil { + panic(fmt.Sprintf("error reading evm query config due to %s", err)) + } + app.EvmKeeper.QueryConfig = &evmQueryConfig ethReplayConfig, err := replay.ReadConfig(appOpts) if err != nil { panic(fmt.Sprintf("error reading eth replay config due to %s", err)) diff --git a/cmd/seid/cmd/root.go b/cmd/seid/cmd/root.go index d63b62c1d..1d86266ad 100644 --- a/cmd/seid/cmd/root.go +++ b/cmd/seid/cmd/root.go @@ -39,6 +39,7 @@ import ( "github.com/sei-protocol/sei-chain/evmrpc" "github.com/sei-protocol/sei-chain/tools" "github.com/sei-protocol/sei-chain/x/evm/blocktest" + "github.com/sei-protocol/sei-chain/x/evm/querier" "github.com/sei-protocol/sei-chain/x/evm/replay" "github.com/spf13/cast" "github.com/spf13/cobra" @@ -378,6 +379,8 @@ func initAppConfig() (string, interface{}) { ETHReplay replay.Config `mapstructure:"eth_replay"` ETHBlockTest blocktest.Config `mapstructure:"eth_block_test"` + + EvmQuery querier.Config `mapstructure:"evm_query"` } // Optionally allow the chain developer to overwrite the SDK's default @@ -420,6 +423,7 @@ func initAppConfig() (string, interface{}) { EVM: evmrpc.DefaultConfig, ETHReplay: replay.DefaultConfig, ETHBlockTest: blocktest.DefaultConfig, + EvmQuery: querier.DefaultConfig, } customAppTemplate := serverconfig.DefaultConfigTemplate + ` @@ -496,6 +500,9 @@ eth_data_dir = "{{ .ETHReplay.EthDataDir }}" [eth_blocktest] eth_blocktest_enabled = {{ .ETHBlockTest.Enabled }} eth_blocktest_test_data_path = "{{ .ETHBlockTest.TestDataPath }}" + +[evm_query] +evm_query_gas_limit = {{ .EvmQuery.GasLimit }} ` return customAppTemplate, customAppConfig diff --git a/x/evm/keeper/grpc_query.go b/x/evm/keeper/grpc_query.go index c4369002f..4dd93ca65 100644 --- a/x/evm/keeper/grpc_query.go +++ b/x/evm/keeper/grpc_query.go @@ -54,6 +54,9 @@ func (q Querier) StaticCall(c context.Context, req *types.QueryStaticCallRequest if req.To == "" { return nil, errors.New("cannot use static call to create contracts") } + if ctx.GasMeter().Limit() == 0 { + ctx = ctx.WithGasMeter(sdk.NewGasMeter(q.QueryConfig.GasLimit)) + } to := common.HexToAddress(req.To) res, err := q.Keeper.StaticCallEVM(ctx, q.Keeper.AccountKeeper().GetModuleAddress(types.ModuleName), &to, req.Data) if err != nil { diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index 2b43e3d00..667747d51 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -33,6 +33,7 @@ import ( "github.com/sei-protocol/sei-chain/utils" "github.com/sei-protocol/sei-chain/x/evm/blocktest" + "github.com/sei-protocol/sei-chain/x/evm/querier" "github.com/sei-protocol/sei-chain/x/evm/replay" "github.com/sei-protocol/sei-chain/x/evm/state" "github.com/sei-protocol/sei-chain/x/evm/types" @@ -58,6 +59,8 @@ type Keeper struct { pendingTxs map[string][]*PendingTx keyToNonce map[tmtypes.TxKey]*AddressNoncePair + QueryConfig *querier.Config + // only used during ETH replay. Not used in chain critical path. EthClient *ethclient.Client EthReplayConfig replay.Config diff --git a/x/evm/querier/config.go b/x/evm/querier/config.go new file mode 100644 index 000000000..8997594e2 --- /dev/null +++ b/x/evm/querier/config.go @@ -0,0 +1,29 @@ +package querier + +import ( + servertypes "github.com/cosmos/cosmos-sdk/server/types" + "github.com/spf13/cast" +) + +type Config struct { + GasLimit uint64 `mapstructure:"evm_query_gas_limit"` +} + +var DefaultConfig = Config{ + GasLimit: 300000, +} + +const ( + flagGasLimit = "evm_query.evm_query_gas_limit" +) + +func ReadConfig(opts servertypes.AppOptions) (Config, error) { + cfg := DefaultConfig // copy + var err error + if v := opts.Get(flagGasLimit); v != nil { + if cfg.GasLimit, err = cast.ToUint64E(v); err != nil { + return cfg, err + } + } + return cfg, nil +} From 0c0827010a5893257b1003f914d503c9a6000add Mon Sep 17 00:00:00 2001 From: Kartik Bhat Date: Thu, 4 Apr 2024 11:03:02 -0400 Subject: [PATCH 16/34] Precompiles Raise Error over Panic (#1507) * Precompiles Raise Error over Panic * Update test * update test --- precompiles/addr/addr.go | 19 +++++++-- precompiles/bank/bank.go | 54 ++++++++++++++++++------ precompiles/common/precompiles.go | 12 ++++-- precompiles/common/precompiles_test.go | 22 ++++++---- precompiles/distribution/distribution.go | 18 ++++++-- precompiles/gov/gov.go | 13 ++++-- precompiles/ibc/ibc.go | 6 ++- precompiles/json/json.go | 27 +++++++++--- precompiles/oracle/oracle.go | 18 ++++++-- precompiles/pointer/pointer.go | 12 ++++-- precompiles/pointerview/pointerview.go | 12 ++++-- precompiles/staking/staking.go | 22 +++++++--- precompiles/wasmd/wasmd.go | 21 +++++++-- x/evm/state/check_test.go | 2 +- 14 files changed, 196 insertions(+), 62 deletions(-) diff --git a/precompiles/addr/addr.go b/precompiles/addr/addr.go index 9d10fb1b7..4ce312aac 100644 --- a/precompiles/addr/addr.go +++ b/precompiles/addr/addr.go @@ -104,15 +104,26 @@ func (p Precompile) Run(evm *vm.EVM, _ common.Address, input []byte, value *big. } func (p Precompile) getSeiAddr(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { - pcommon.AssertNonPayable(value) - pcommon.AssertArgsLength(args, 1) + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } + seiAddrStr := p.evmKeeper.GetSeiAddressOrDefault(ctx, args[0].(common.Address)).String() return method.Outputs.Pack(seiAddrStr) } func (p Precompile) getEvmAddr(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { - pcommon.AssertNonPayable(value) - pcommon.AssertArgsLength(args, 1) + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } evmAddr := p.evmKeeper.GetEVMAddressFromBech32OrDefault(ctx, args[0].(string)) return method.Outputs.Pack(evmAddr) } diff --git a/precompiles/bank/bank.go b/precompiles/bank/bank.go index 99da36812..2def02947 100644 --- a/precompiles/bank/bank.go +++ b/precompiles/bank/bank.go @@ -154,8 +154,13 @@ func (p Precompile) validateCaller(ctx sdk.Context, caller common.Address) error } func (p Precompile) send(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { - pcommon.AssertNonPayable(value) - pcommon.AssertArgsLength(args, 4) + 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") @@ -183,7 +188,9 @@ func (p Precompile) send(ctx sdk.Context, method *abi.Method, args []interface{} } func (p Precompile) sendNative(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address, value *big.Int) ([]byte, error) { - pcommon.AssertArgsLength(args, 1) + 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") } @@ -216,8 +223,13 @@ func (p Precompile) sendNative(ctx sdk.Context, method *abi.Method, args []inter } func (p Precompile) balance(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { - pcommon.AssertNonPayable(value) - pcommon.AssertArgsLength(args, 2) + 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 { @@ -232,8 +244,13 @@ func (p Precompile) balance(ctx sdk.Context, method *abi.Method, args []interfac } func (p Precompile) name(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { - pcommon.AssertNonPayable(value) - pcommon.AssertArgsLength(args, 1) + 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) @@ -244,8 +261,13 @@ func (p Precompile) name(ctx sdk.Context, method *abi.Method, args []interface{} } func (p Precompile) symbol(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { - pcommon.AssertNonPayable(value) - pcommon.AssertArgsLength(args, 1) + 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) @@ -256,14 +278,22 @@ func (p Precompile) symbol(ctx sdk.Context, method *abi.Method, args []interface } func (p Precompile) decimals(_ sdk.Context, method *abi.Method, _ []interface{}, value *big.Int) ([]byte, error) { - pcommon.AssertNonPayable(value) + 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) { - pcommon.AssertNonPayable(value) - pcommon.AssertArgsLength(args, 1) + 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) diff --git a/precompiles/common/precompiles.go b/precompiles/common/precompiles.go index 7579c4073..f305bb656 100644 --- a/precompiles/common/precompiles.go +++ b/precompiles/common/precompiles.go @@ -54,16 +54,20 @@ func (p Precompile) Prepare(evm *vm.EVM, input []byte) (sdk.Context, *abi.Method return ctxer.Ctx(), method, args, nil } -func AssertArgsLength(args []interface{}, length int) { +func ValidateArgsLength(args []interface{}, length int) error { if len(args) != length { - panic(fmt.Sprintf("expected %d arguments but got %d", length, len(args))) + return fmt.Errorf("expected %d arguments but got %d", length, len(args)) } + + return nil } -func AssertNonPayable(value *big.Int) { +func ValidateNonPayable(value *big.Int) error { if value != nil && value.Sign() != 0 { - panic("sending funds to a non-payable function") + 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) { diff --git a/precompiles/common/precompiles_test.go b/precompiles/common/precompiles_test.go index f02caf1db..588039714 100644 --- a/precompiles/common/precompiles_test.go +++ b/precompiles/common/precompiles_test.go @@ -8,14 +8,20 @@ import ( "github.com/stretchr/testify/require" ) -func TestAssertArgsLength(t *testing.T) { - require.NotPanics(t, func() { common.AssertArgsLength(nil, 0) }) - require.NotPanics(t, func() { common.AssertArgsLength([]interface{}{1, ""}, 2) }) - require.Panics(t, func() { common.AssertArgsLength([]interface{}{""}, 2) }) +func TestValidateArgsLength(t *testing.T) { + err := common.ValidateArgsLength(nil, 0) + require.Nil(t, err) + err = common.ValidateArgsLength([]interface{}{1, ""}, 2) + require.Nil(t, err) + err = common.ValidateArgsLength([]interface{}{""}, 2) + require.NotNil(t, err) } -func TestAssertNonPayable(t *testing.T) { - require.NotPanics(t, func() { common.AssertNonPayable(nil) }) - require.NotPanics(t, func() { common.AssertNonPayable(big.NewInt(0)) }) - require.Panics(t, func() { common.AssertNonPayable(big.NewInt(1)) }) +func TestValidteNonPayable(t *testing.T) { + err := common.ValidateNonPayable(nil) + require.Nil(t, err) + err = common.ValidateNonPayable(big.NewInt(0)) + require.Nil(t, err) + err = common.ValidateNonPayable(big.NewInt(1)) + require.NotNil(t, err) } diff --git a/precompiles/distribution/distribution.go b/precompiles/distribution/distribution.go index 3f26ef6d3..59d456799 100644 --- a/precompiles/distribution/distribution.go +++ b/precompiles/distribution/distribution.go @@ -111,8 +111,13 @@ func (p Precompile) Run(evm *vm.EVM, caller common.Address, input []byte, value } func (p Precompile) setWithdrawAddress(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { - pcommon.AssertNonPayable(value) - pcommon.AssertArgsLength(args, 1) + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } delegator := p.evmKeeper.GetSeiAddressOrDefault(ctx, caller) withdrawAddr, err := p.accAddressFromArg(ctx, args[0]) if err != nil { @@ -126,8 +131,13 @@ func (p Precompile) setWithdrawAddress(ctx sdk.Context, method *abi.Method, call } func (p Precompile) withdrawDelegationRewards(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { - pcommon.AssertNonPayable(value) - pcommon.AssertArgsLength(args, 1) + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } delegator := p.evmKeeper.GetSeiAddressOrDefault(ctx, caller) validator, err := sdk.ValAddressFromBech32(args[0].(string)) if err != nil { diff --git a/precompiles/gov/gov.go b/precompiles/gov/gov.go index 34f84f88f..52bab5b69 100644 --- a/precompiles/gov/gov.go +++ b/precompiles/gov/gov.go @@ -114,8 +114,13 @@ func (p Precompile) Run(evm *vm.EVM, caller common.Address, input []byte, value } func (p Precompile) vote(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { - pcommon.AssertNonPayable(value) - pcommon.AssertArgsLength(args, 2) + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, err + } + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + return nil, err + } voter := p.evmKeeper.GetSeiAddressOrDefault(ctx, caller) proposalID := args[0].(uint64) voteOption := args[1].(int32) @@ -127,7 +132,9 @@ func (p Precompile) vote(ctx sdk.Context, method *abi.Method, caller common.Addr } func (p Precompile) deposit(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { - pcommon.AssertArgsLength(args, 1) + if err := pcommon.ValidateArgsLength(args, 1); err != nil { + return nil, err + } depositor := p.evmKeeper.GetSeiAddressOrDefault(ctx, caller) proposalID := args[0].(uint64) if value == nil || value.Sign() == 0 { diff --git a/precompiles/ibc/ibc.go b/precompiles/ibc/ibc.go index c23d15dec..a446ceac4 100644 --- a/precompiles/ibc/ibc.go +++ b/precompiles/ibc/ibc.go @@ -128,7 +128,11 @@ func (p Precompile) transfer(ctx sdk.Context, method *abi.Method, args []interfa return } }() - pcommon.AssertArgsLength(args, 8) + + 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") diff --git a/precompiles/json/json.go b/precompiles/json/json.go index 5a0434751..098be447e 100644 --- a/precompiles/json/json.go +++ b/precompiles/json/json.go @@ -117,8 +117,13 @@ func (p Precompile) Run(evm *vm.EVM, _ common.Address, input []byte, value *big. } func (p Precompile) extractAsBytes(_ sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { - pcommon.AssertNonPayable(value) - pcommon.AssertArgsLength(args, 2) + 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) @@ -140,8 +145,13 @@ func (p Precompile) extractAsBytes(_ sdk.Context, method *abi.Method, args []int } func (p Precompile) extractAsBytesList(_ sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { - pcommon.AssertNonPayable(value) - pcommon.AssertArgsLength(args, 2) + 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) @@ -159,8 +169,13 @@ func (p Precompile) extractAsBytesList(_ sdk.Context, method *abi.Method, args [ } func (p Precompile) ExtractAsUint256(_ sdk.Context, _ *abi.Method, args []interface{}, value *big.Int) (*big.Int, error) { - pcommon.AssertNonPayable(value) - pcommon.AssertArgsLength(args, 2) + 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) diff --git a/precompiles/oracle/oracle.go b/precompiles/oracle/oracle.go index 110809e93..7b6d1be9d 100644 --- a/precompiles/oracle/oracle.go +++ b/precompiles/oracle/oracle.go @@ -129,8 +129,13 @@ func (p Precompile) Run(evm *vm.EVM, _ common.Address, input []byte, value *big. } func (p Precompile) getExchangeRates(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { - pcommon.AssertNonPayable(value) - pcommon.AssertArgsLength(args, 0) + 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}}) @@ -141,8 +146,13 @@ func (p Precompile) getExchangeRates(ctx sdk.Context, method *abi.Method, args [ } func (p Precompile) getOracleTwaps(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { - pcommon.AssertNonPayable(value) - pcommon.AssertArgsLength(args, 1) + 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 { diff --git a/precompiles/pointer/pointer.go b/precompiles/pointer/pointer.go index 9bacf5d12..be607b5fc 100644 --- a/precompiles/pointer/pointer.go +++ b/precompiles/pointer/pointer.go @@ -113,7 +113,9 @@ func (p Precompile) Run(*vm.EVM, common.Address, []byte, *big.Int) ([]byte, erro } func (p Precompile) AddNative(ctx sdk.Context, method *ethabi.Method, caller common.Address, args []interface{}, value *big.Int, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { - pcommon.AssertArgsLength(args, 1) + 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 { @@ -161,7 +163,9 @@ func (p Precompile) AddNative(ctx sdk.Context, method *ethabi.Method, caller com } func (p Precompile) AddCW20(ctx sdk.Context, method *ethabi.Method, caller common.Address, args []interface{}, value *big.Int, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { - pcommon.AssertArgsLength(args, 1) + 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 { @@ -202,7 +206,9 @@ func (p Precompile) AddCW20(ctx sdk.Context, method *ethabi.Method, caller commo } func (p Precompile) AddCW721(ctx sdk.Context, method *ethabi.Method, caller common.Address, args []interface{}, value *big.Int, evm *vm.EVM, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { - pcommon.AssertArgsLength(args, 1) + 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 { diff --git a/precompiles/pointerview/pointerview.go b/precompiles/pointerview/pointerview.go index 937d9e968..a47f7e085 100644 --- a/precompiles/pointerview/pointerview.go +++ b/precompiles/pointerview/pointerview.go @@ -98,21 +98,27 @@ func (p Precompile) Run(evm *vm.EVM, _ common.Address, input []byte, _ *big.Int) } func (p Precompile) GetNative(ctx sdk.Context, method *abi.Method, args []interface{}) (ret []byte, err error) { - pcommon.AssertArgsLength(args, 1) + 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) { - pcommon.AssertArgsLength(args, 1) + 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) { - pcommon.AssertArgsLength(args, 1) + 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/staking/staking.go b/precompiles/staking/staking.go index cae41df24..5a4804056 100644 --- a/precompiles/staking/staking.go +++ b/precompiles/staking/staking.go @@ -123,7 +123,9 @@ func (p Precompile) Run(evm *vm.EVM, caller common.Address, input []byte, value } func (p Precompile) delegate(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { - pcommon.AssertArgsLength(args, 1) + 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) @@ -151,8 +153,13 @@ func (p Precompile) delegate(ctx sdk.Context, method *abi.Method, caller common. } func (p Precompile) redelegate(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { - pcommon.AssertNonPayable(value) - pcommon.AssertArgsLength(args, 3) + 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()) @@ -173,8 +180,13 @@ func (p Precompile) redelegate(ctx sdk.Context, method *abi.Method, caller commo } func (p Precompile) undelegate(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) ([]byte, error) { - pcommon.AssertNonPayable(value) - pcommon.AssertArgsLength(args, 2) + 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()) diff --git a/precompiles/wasmd/wasmd.go b/precompiles/wasmd/wasmd.go index 66677ab2c..572ae618a 100644 --- a/precompiles/wasmd/wasmd.go +++ b/precompiles/wasmd/wasmd.go @@ -149,7 +149,10 @@ func (p Precompile) instantiate(ctx sdk.Context, method *abi.Method, caller comm return } }() - pcommon.AssertArgsLength(args, 5) + if err := pcommon.ValidateArgsLength(args, 5); err != nil { + rerr = err + return + } // type assertion will always succeed because it's already validated in p.Prepare call in Run() codeID := args[0].(uint64) @@ -221,7 +224,10 @@ func (p Precompile) execute(ctx sdk.Context, method *abi.Method, caller common.A return } }() - pcommon.AssertArgsLength(args, 3) + 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) @@ -283,8 +289,15 @@ func (p Precompile) query(ctx sdk.Context, method *abi.Method, args []interface{ return } }() - pcommon.AssertNonPayable(value) - pcommon.AssertArgsLength(args, 2) + 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 diff --git a/x/evm/state/check_test.go b/x/evm/state/check_test.go index 13bd435f3..6c631bd1b 100644 --- a/x/evm/state/check_test.go +++ b/x/evm/state/check_test.go @@ -24,7 +24,7 @@ func TestExist(t *testing.T) { // has balance _, addr3 := testkeeper.MockAddressPair() - statedb.AddBalance(addr3, big.NewInt(1000000000000)) + statedb.AddBalance(addr3, big.NewInt(1000000000000), tracing.BalanceChangeUnspecified) require.True(t, statedb.Exist(addr3)) // destructed From f87a819d83ced5c8a5cf6564fb6d3d11f2632518 Mon Sep 17 00:00:00 2001 From: codchen Date: Fri, 5 Apr 2024 11:58:21 +0800 Subject: [PATCH 17/34] Add query for pointer in registry (#1508) --- go.mod | 2 + go.sum | 3 + proto/evm/enums.proto | 12 + proto/evm/query.proto | 16 + proto/evm/tx.proto | 6 +- x/evm/ante/basic.go | 1 + x/evm/client/cli/query.go | 27 ++ x/evm/keeper/grpc_query.go | 43 +++ x/evm/keeper/grpc_query_test.go | 47 +++ x/evm/types/enums.pb.go | 78 +++++ x/evm/types/query.pb.go | 551 ++++++++++++++++++++++++++++++-- x/evm/types/query.pb.gw.go | 83 +++++ x/evm/types/tx.pb.go | 123 +++---- 13 files changed, 884 insertions(+), 108 deletions(-) create mode 100644 proto/evm/enums.proto create mode 100644 x/evm/keeper/grpc_query_test.go create mode 100644 x/evm/types/enums.pb.go diff --git a/go.mod b/go.mod index 4da61b9a4..bc6ad8a02 100644 --- a/go.mod +++ b/go.mod @@ -125,6 +125,7 @@ require ( github.com/fzipp/gocyclo v0.5.1 // indirect github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff // indirect github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 // indirect + github.com/ghodss/yaml v1.0.0 // indirect github.com/go-critic/go-critic v0.6.3 // indirect github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.1 // indirect @@ -172,6 +173,7 @@ require ( github.com/gostaticanalysis/nilerr v0.1.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-bexpr v0.1.10 // indirect diff --git a/go.sum b/go.sum index e41951ffa..0795a73d1 100644 --- a/go.sum +++ b/go.sum @@ -457,6 +457,7 @@ github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2Gihuqh github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/ghemawat/stream v0.0.0-20171120220530-696b145b53b9/go.mod h1:106OIgooyS7OzLDOpUGgm9fA3bQENb/cFSyyBmMoJDs= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -765,6 +766,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/guptarohit/asciigraph v0.5.5/go.mod h1:dYl5wwK4gNsnFf9Zp+l06rFiDZ5YtXM6x7SRWZ3KGag= diff --git a/proto/evm/enums.proto b/proto/evm/enums.proto new file mode 100644 index 000000000..fefdee9ab --- /dev/null +++ b/proto/evm/enums.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; +package seiprotocol.seichain.evm; + +option go_package = "github.com/sei-protocol/sei-chain/x/evm/types"; + +enum PointerType { + ERC20 = 0; + ERC721 = 1; + NATIVE = 2; + CW20 = 3; + CW721 = 4; + } \ No newline at end of file diff --git a/proto/evm/query.proto b/proto/evm/query.proto index 6f8117097..6f90daa9b 100644 --- a/proto/evm/query.proto +++ b/proto/evm/query.proto @@ -2,6 +2,7 @@ syntax = "proto3"; package seiprotocol.seichain.evm; import "google/api/annotations.proto"; +import "evm/enums.proto"; option go_package = "github.com/sei-protocol/sei-chain/x/evm/types"; @@ -18,6 +19,10 @@ service Query { rpc StaticCall(QueryStaticCallRequest) returns (QueryStaticCallResponse) { option (google.api.http).get = "/sei-protocol/seichain/evm/static_call"; } + + rpc Pointer(QueryPointerRequest) returns (QueryPointerResponse) { + option (google.api.http).get = "/sei-protocol/seichain/evm/pointer"; + } } message QuerySeiAddressByEVMAddressRequest { @@ -46,3 +51,14 @@ message QueryStaticCallRequest { message QueryStaticCallResponse { bytes data = 1; } + +message QueryPointerRequest { + PointerType pointer_type = 1; + string pointee = 2; +} + +message QueryPointerResponse { + string pointer = 1; + uint32 version = 2; + bool exists = 3; +} diff --git a/proto/evm/tx.proto b/proto/evm/tx.proto index c23e22602..03eaa8ce9 100644 --- a/proto/evm/tx.proto +++ b/proto/evm/tx.proto @@ -4,6 +4,7 @@ package seiprotocol.seichain.evm; import "google/protobuf/any.proto"; import "gogoproto/gogo.proto"; import "cosmos/base/v1beta1/coin.proto"; +import "evm/enums.proto"; option go_package = "github.com/sei-protocol/sei-chain/x/evm/types"; @@ -55,11 +56,6 @@ message MsgSend { message MsgSendResponse {} -enum PointerType { - ERC20 = 0; - ERC721 = 1; -} - message MsgRegisterPointer { string sender = 1; PointerType pointer_type = 2; diff --git a/x/evm/ante/basic.go b/x/evm/ante/basic.go index a0e407f37..e9db85dca 100644 --- a/x/evm/ante/basic.go +++ b/x/evm/ante/basic.go @@ -70,6 +70,7 @@ func (gl BasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, n return next(ctx, tx, simulate) } +//nolint:deadcode func validateBlobSidecar(hashes []common.Hash, sidecar *ethtypes.BlobTxSidecar) error { if len(sidecar.Blobs) != len(hashes) { return fmt.Errorf("invalid number of %d blobs compared to %d blob hashes", len(sidecar.Blobs), len(hashes)) diff --git a/x/evm/client/cli/query.go b/x/evm/client/cli/query.go index 7d76198d2..084f4a725 100644 --- a/x/evm/client/cli/query.go +++ b/x/evm/client/cli/query.go @@ -43,6 +43,7 @@ func GetQueryCmd(_ string) *cobra.Command { cmd.AddCommand(CmdQueryERC721Payload()) cmd.AddCommand(CmdQueryERC20()) cmd.AddCommand(CmdQueryPayload()) + cmd.AddCommand(CmdQueryPointer()) return cmd } @@ -339,3 +340,29 @@ func CmdQueryERC721Payload() *cobra.Command { return cmd } + +func CmdQueryPointer() *cobra.Command { + cmd := &cobra.Command{ + Use: "pointer [type] [pointee]", + Short: "get pointer address of the specified type (one of [NATIVE, CW20, CW721, ERC20, ERC721]) and pointee", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx := client.GetClientContextFromCmd(cmd) + + queryClient := types.NewQueryClient(clientCtx) + + res, err := queryClient.Pointer(context.Background(), &types.QueryPointerRequest{ + PointerType: types.PointerType(types.PointerType_value[args[0]]), Pointee: args[1], + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/x/evm/keeper/grpc_query.go b/x/evm/keeper/grpc_query.go index 4dd93ca65..a94393cd2 100644 --- a/x/evm/keeper/grpc_query.go +++ b/x/evm/keeper/grpc_query.go @@ -64,3 +64,46 @@ func (q Querier) StaticCall(c context.Context, req *types.QueryStaticCallRequest } return &types.QueryStaticCallResponse{Data: res}, nil } + +func (q Querier) Pointer(c context.Context, req *types.QueryPointerRequest) (*types.QueryPointerResponse, error) { + ctx := sdk.UnwrapSDKContext(c) + switch req.PointerType { + case types.PointerType_NATIVE: + p, v, e := q.Keeper.GetERC20NativePointer(ctx, req.Pointee) + return &types.QueryPointerResponse{ + Pointer: p.Hex(), + Version: uint32(v), + Exists: e, + }, nil + case types.PointerType_CW20: + p, v, e := q.Keeper.GetERC20CW20Pointer(ctx, req.Pointee) + return &types.QueryPointerResponse{ + Pointer: p.Hex(), + Version: uint32(v), + Exists: e, + }, nil + case types.PointerType_CW721: + p, v, e := q.Keeper.GetERC721CW721Pointer(ctx, req.Pointee) + return &types.QueryPointerResponse{ + Pointer: p.Hex(), + Version: uint32(v), + Exists: e, + }, nil + case types.PointerType_ERC20: + p, v, e := q.Keeper.GetCW20ERC20Pointer(ctx, common.HexToAddress(req.Pointee)) + return &types.QueryPointerResponse{ + Pointer: p.String(), + Version: uint32(v), + Exists: e, + }, nil + case types.PointerType_ERC721: + p, v, e := q.Keeper.GetCW721ERC721Pointer(ctx, common.HexToAddress(req.Pointee)) + return &types.QueryPointerResponse{ + Pointer: p.String(), + Version: uint32(v), + Exists: e, + }, nil + default: + return nil, errors.ErrUnsupported + } +} diff --git a/x/evm/keeper/grpc_query_test.go b/x/evm/keeper/grpc_query_test.go new file mode 100644 index 000000000..5df048043 --- /dev/null +++ b/x/evm/keeper/grpc_query_test.go @@ -0,0 +1,47 @@ +package keeper_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/cw20" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/cw721" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/erc20" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/erc721" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/native" + "github.com/sei-protocol/sei-chain/x/evm/keeper" + "github.com/sei-protocol/sei-chain/x/evm/types" + "github.com/stretchr/testify/require" +) + +func TestQueryPointer(t *testing.T) { + k, ctx := testkeeper.MockEVMKeeper() + seiAddr1, evmAddr1 := testkeeper.MockAddressPair() + seiAddr2, evmAddr2 := testkeeper.MockAddressPair() + seiAddr3, evmAddr3 := testkeeper.MockAddressPair() + seiAddr4, evmAddr4 := testkeeper.MockAddressPair() + seiAddr5, evmAddr5 := testkeeper.MockAddressPair() + goCtx := sdk.WrapSDKContext(ctx) + k.SetERC20NativePointer(ctx, seiAddr1.String(), evmAddr1) + k.SetERC20CW20Pointer(ctx, seiAddr2.String(), evmAddr2) + k.SetERC721CW721Pointer(ctx, seiAddr3.String(), evmAddr3) + k.SetCW20ERC20Pointer(ctx, evmAddr4, seiAddr4.String()) + k.SetCW721ERC721Pointer(ctx, evmAddr5, seiAddr5.String()) + q := keeper.Querier{k} + res, err := q.Pointer(goCtx, &types.QueryPointerRequest{PointerType: types.PointerType_NATIVE, Pointee: seiAddr1.String()}) + require.Nil(t, err) + require.Equal(t, types.QueryPointerResponse{Pointer: evmAddr1.Hex(), Version: uint32(native.CurrentVersion), Exists: true}, *res) + res, err = q.Pointer(goCtx, &types.QueryPointerRequest{PointerType: types.PointerType_CW20, Pointee: seiAddr2.String()}) + require.Nil(t, err) + require.Equal(t, types.QueryPointerResponse{Pointer: evmAddr2.Hex(), Version: uint32(cw20.CurrentVersion), Exists: true}, *res) + res, err = q.Pointer(goCtx, &types.QueryPointerRequest{PointerType: types.PointerType_CW721, Pointee: seiAddr3.String()}) + require.Nil(t, err) + require.Equal(t, types.QueryPointerResponse{Pointer: evmAddr3.Hex(), Version: uint32(cw721.CurrentVersion), Exists: true}, *res) + res, err = q.Pointer(goCtx, &types.QueryPointerRequest{PointerType: types.PointerType_ERC20, Pointee: evmAddr4.Hex()}) + require.Nil(t, err) + require.Equal(t, types.QueryPointerResponse{Pointer: seiAddr4.String(), Version: uint32(erc20.CurrentVersion), Exists: true}, *res) + res, err = q.Pointer(goCtx, &types.QueryPointerRequest{PointerType: types.PointerType_ERC721, Pointee: evmAddr5.Hex()}) + require.Nil(t, err) + require.Equal(t, types.QueryPointerResponse{Pointer: seiAddr5.String(), Version: uint32(erc721.CurrentVersion), Exists: true}, *res) +} diff --git a/x/evm/types/enums.pb.go b/x/evm/types/enums.pb.go new file mode 100644 index 000000000..d5f2e5642 --- /dev/null +++ b/x/evm/types/enums.pb.go @@ -0,0 +1,78 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: evm/enums.proto + +package types + +import ( + fmt "fmt" + proto "github.com/gogo/protobuf/proto" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +type PointerType int32 + +const ( + PointerType_ERC20 PointerType = 0 + PointerType_ERC721 PointerType = 1 + PointerType_NATIVE PointerType = 2 + PointerType_CW20 PointerType = 3 + PointerType_CW721 PointerType = 4 +) + +var PointerType_name = map[int32]string{ + 0: "ERC20", + 1: "ERC721", + 2: "NATIVE", + 3: "CW20", + 4: "CW721", +} + +var PointerType_value = map[string]int32{ + "ERC20": 0, + "ERC721": 1, + "NATIVE": 2, + "CW20": 3, + "CW721": 4, +} + +func (x PointerType) String() string { + return proto.EnumName(PointerType_name, int32(x)) +} + +func (PointerType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_9ba0923a26222f98, []int{0} +} + +func init() { + proto.RegisterEnum("seiprotocol.seichain.evm.PointerType", PointerType_name, PointerType_value) +} + +func init() { proto.RegisterFile("evm/enums.proto", fileDescriptor_9ba0923a26222f98) } + +var fileDescriptor_9ba0923a26222f98 = []byte{ + // 194 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4f, 0x2d, 0xcb, 0xd5, + 0x4f, 0xcd, 0x2b, 0xcd, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x92, 0x28, 0x4e, 0xcd, + 0x04, 0xb3, 0x92, 0xf3, 0x73, 0xf4, 0x8a, 0x53, 0x33, 0x93, 0x33, 0x12, 0x33, 0xf3, 0xf4, 0x52, + 0xcb, 0x72, 0xb5, 0x5c, 0xb9, 0xb8, 0x03, 0xf2, 0x33, 0xf3, 0x4a, 0x52, 0x8b, 0x42, 0x2a, 0x0b, + 0x52, 0x85, 0x38, 0xb9, 0x58, 0x5d, 0x83, 0x9c, 0x8d, 0x0c, 0x04, 0x18, 0x84, 0xb8, 0xb8, 0xd8, + 0x5c, 0x83, 0x9c, 0xcd, 0x8d, 0x0c, 0x05, 0x18, 0x41, 0x6c, 0x3f, 0xc7, 0x10, 0xcf, 0x30, 0x57, + 0x01, 0x26, 0x21, 0x0e, 0x2e, 0x16, 0xe7, 0x70, 0x23, 0x03, 0x01, 0x66, 0x90, 0x62, 0xe7, 0x70, + 0x90, 0x02, 0x16, 0x27, 0xf7, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, 0xf0, 0x48, + 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, 0x88, 0xd2, + 0x4d, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x2f, 0x4e, 0xcd, 0xd4, 0x85, + 0x39, 0x03, 0xcc, 0x01, 0xbb, 0x43, 0xbf, 0x42, 0x1f, 0xe4, 0xde, 0x92, 0xca, 0x82, 0xd4, 0xe2, + 0x24, 0x36, 0xb0, 0xbc, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x3c, 0xe6, 0x84, 0x48, 0xc3, 0x00, + 0x00, 0x00, +} diff --git a/x/evm/types/query.pb.go b/x/evm/types/query.pb.go index ef8c5b7e6..5c80febcc 100644 --- a/x/evm/types/query.pb.go +++ b/x/evm/types/query.pb.go @@ -316,6 +316,118 @@ func (m *QueryStaticCallResponse) GetData() []byte { return nil } +type QueryPointerRequest struct { + PointerType PointerType `protobuf:"varint,1,opt,name=pointer_type,json=pointerType,proto3,enum=seiprotocol.seichain.evm.PointerType" json:"pointer_type,omitempty"` + Pointee string `protobuf:"bytes,2,opt,name=pointee,proto3" json:"pointee,omitempty"` +} + +func (m *QueryPointerRequest) Reset() { *m = QueryPointerRequest{} } +func (m *QueryPointerRequest) String() string { return proto.CompactTextString(m) } +func (*QueryPointerRequest) ProtoMessage() {} +func (*QueryPointerRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_11c0d37eed5339f7, []int{6} +} +func (m *QueryPointerRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPointerRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPointerRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPointerRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPointerRequest.Merge(m, src) +} +func (m *QueryPointerRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryPointerRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPointerRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPointerRequest proto.InternalMessageInfo + +func (m *QueryPointerRequest) GetPointerType() PointerType { + if m != nil { + return m.PointerType + } + return PointerType_ERC20 +} + +func (m *QueryPointerRequest) GetPointee() string { + if m != nil { + return m.Pointee + } + return "" +} + +type QueryPointerResponse struct { + Pointer string `protobuf:"bytes,1,opt,name=pointer,proto3" json:"pointer,omitempty"` + Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` + Exists bool `protobuf:"varint,3,opt,name=exists,proto3" json:"exists,omitempty"` +} + +func (m *QueryPointerResponse) Reset() { *m = QueryPointerResponse{} } +func (m *QueryPointerResponse) String() string { return proto.CompactTextString(m) } +func (*QueryPointerResponse) ProtoMessage() {} +func (*QueryPointerResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_11c0d37eed5339f7, []int{7} +} +func (m *QueryPointerResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPointerResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPointerResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPointerResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPointerResponse.Merge(m, src) +} +func (m *QueryPointerResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryPointerResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPointerResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPointerResponse proto.InternalMessageInfo + +func (m *QueryPointerResponse) GetPointer() string { + if m != nil { + return m.Pointer + } + return "" +} + +func (m *QueryPointerResponse) GetVersion() uint32 { + if m != nil { + return m.Version + } + return 0 +} + +func (m *QueryPointerResponse) GetExists() bool { + if m != nil { + return m.Exists + } + return false +} + func init() { proto.RegisterType((*QuerySeiAddressByEVMAddressRequest)(nil), "seiprotocol.seichain.evm.QuerySeiAddressByEVMAddressRequest") proto.RegisterType((*QuerySeiAddressByEVMAddressResponse)(nil), "seiprotocol.seichain.evm.QuerySeiAddressByEVMAddressResponse") @@ -323,39 +435,49 @@ func init() { proto.RegisterType((*QueryEVMAddressBySeiAddressResponse)(nil), "seiprotocol.seichain.evm.QueryEVMAddressBySeiAddressResponse") proto.RegisterType((*QueryStaticCallRequest)(nil), "seiprotocol.seichain.evm.QueryStaticCallRequest") proto.RegisterType((*QueryStaticCallResponse)(nil), "seiprotocol.seichain.evm.QueryStaticCallResponse") + proto.RegisterType((*QueryPointerRequest)(nil), "seiprotocol.seichain.evm.QueryPointerRequest") + proto.RegisterType((*QueryPointerResponse)(nil), "seiprotocol.seichain.evm.QueryPointerResponse") } func init() { proto.RegisterFile("evm/query.proto", fileDescriptor_11c0d37eed5339f7) } var fileDescriptor_11c0d37eed5339f7 = []byte{ - // 427 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x92, 0xcb, 0x6a, 0xdb, 0x40, - 0x14, 0x86, 0x3d, 0xc2, 0x2d, 0xf5, 0xb4, 0xb4, 0x30, 0x0b, 0xd7, 0x98, 0xa2, 0x1a, 0x15, 0x8a, - 0x37, 0x92, 0x7a, 0xd9, 0xba, 0x8b, 0xba, 0x98, 0xae, 0xba, 0xa8, 0x02, 0x59, 0x64, 0x63, 0xc6, - 0xd2, 0x89, 0x3d, 0x20, 0x69, 0x64, 0xcf, 0x58, 0xc4, 0xdb, 0x3c, 0x41, 0x20, 0x4f, 0x90, 0xe7, - 0xc8, 0x3a, 0x90, 0xa5, 0x21, 0x9b, 0x2c, 0x83, 0x9d, 0x07, 0x09, 0x1a, 0xf9, 0x22, 0x7c, 0x91, - 0xe2, 0xec, 0x0e, 0x33, 0xf3, 0x7f, 0xf3, 0x9f, 0x73, 0x7e, 0xfc, 0x01, 0xe2, 0xc0, 0x1e, 0x8e, - 0x61, 0x34, 0xb1, 0xa2, 0x11, 0x97, 0x9c, 0xd4, 0x04, 0x30, 0x55, 0xb9, 0xdc, 0xb7, 0x04, 0x30, - 0x77, 0x40, 0x59, 0x68, 0x41, 0x1c, 0xd4, 0x3f, 0xf5, 0x39, 0xef, 0xfb, 0x60, 0xd3, 0x88, 0xd9, - 0x34, 0x0c, 0xb9, 0xa4, 0x92, 0xf1, 0x50, 0xa4, 0x3a, 0xa3, 0x83, 0x8d, 0xff, 0x09, 0xe6, 0x08, - 0xd8, 0x6f, 0xcf, 0x1b, 0x81, 0x10, 0xed, 0x49, 0xe7, 0xf8, 0xdf, 0xa2, 0x76, 0x60, 0x38, 0x06, - 0x21, 0xc9, 0x67, 0xfc, 0x16, 0xe2, 0xa0, 0x4b, 0xd3, 0xd3, 0x1a, 0x6a, 0xa0, 0x66, 0xc5, 0xc1, - 0x10, 0x07, 0x8b, 0x77, 0xc6, 0x29, 0xfe, 0x92, 0x8b, 0x11, 0x11, 0x0f, 0x05, 0x24, 0x1c, 0x01, - 0x6c, 0x93, 0x23, 0x56, 0x22, 0xa2, 0x63, 0x4c, 0x85, 0xe0, 0x2e, 0xa3, 0x12, 0xbc, 0x9a, 0xd6, - 0x40, 0xcd, 0x37, 0x4e, 0xe6, 0x64, 0x65, 0x77, 0xcd, 0x6e, 0x67, 0xfe, 0xcc, 0xd8, 0xcd, 0xfd, - 0x66, 0x65, 0x77, 0x1f, 0x66, 0x6d, 0x37, 0xb7, 0xed, 0x42, 0xbb, 0x2d, 0x5c, 0x4d, 0xc7, 0x92, - 0x0c, 0xdd, 0xfd, 0x43, 0x7d, 0x7f, 0x69, 0x91, 0xe0, 0xb2, 0x47, 0x25, 0x55, 0xcc, 0x77, 0x8e, - 0xaa, 0xc9, 0x7b, 0xac, 0x49, 0xae, 0x28, 0x15, 0x47, 0x93, 0xdc, 0x30, 0xf1, 0xc7, 0x2d, 0xf5, - 0xc2, 0xd9, 0x0e, 0xf9, 0x8f, 0xeb, 0x32, 0x7e, 0xa5, 0xde, 0x93, 0x1b, 0x84, 0xab, 0xbb, 0x37, - 0x41, 0x5a, 0xd6, 0xbe, 0xa0, 0x58, 0xc5, 0x39, 0xa8, 0xff, 0x7a, 0xa1, 0x3a, 0x75, 0x6d, 0x58, - 0xe7, 0x77, 0x8f, 0x97, 0x5a, 0x93, 0x7c, 0xb5, 0x05, 0x30, 0x73, 0xc9, 0xb1, 0x97, 0x1c, 0x3b, - 0x09, 0x75, 0x66, 0x71, 0xaa, 0x8f, 0xdd, 0x2b, 0x2a, 0xec, 0x23, 0x37, 0x20, 0x85, 0x7d, 0xe4, - 0xe7, 0xe2, 0x59, 0x7d, 0x64, 0x82, 0x43, 0xae, 0x10, 0xc6, 0xeb, 0x25, 0x92, 0x6f, 0x45, 0x53, - 0xdc, 0x4c, 0x4b, 0xfd, 0xfb, 0x01, 0x8a, 0x43, 0x66, 0xad, 0x64, 0x5d, 0x97, 0xfa, 0x7e, 0xfb, - 0xef, 0xed, 0x4c, 0x47, 0xd3, 0x99, 0x8e, 0x1e, 0x66, 0x3a, 0xba, 0x98, 0xeb, 0xa5, 0xe9, 0x5c, - 0x2f, 0xdd, 0xcf, 0xf5, 0xd2, 0x89, 0xd9, 0x67, 0x72, 0x30, 0xee, 0x59, 0x2e, 0x0f, 0xb6, 0x58, - 0x66, 0x0a, 0x3b, 0x53, 0x38, 0x39, 0x89, 0x40, 0xf4, 0x5e, 0xab, 0xfb, 0x9f, 0x4f, 0x01, 0x00, - 0x00, 0xff, 0xff, 0xa3, 0xfc, 0x90, 0x59, 0xa3, 0x04, 0x00, 0x00, + // 547 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x53, 0xcd, 0x6e, 0xd3, 0x4c, + 0x14, 0x8d, 0xf3, 0x7d, 0x6d, 0xe9, 0x6d, 0x29, 0xd2, 0x80, 0x42, 0x14, 0x21, 0x53, 0x99, 0x1f, + 0x45, 0x48, 0xb6, 0xa1, 0x6c, 0xcb, 0x82, 0xa0, 0x0a, 0x36, 0x48, 0x60, 0x10, 0x0b, 0x36, 0xd1, + 0xc4, 0xb9, 0xa4, 0x23, 0xc5, 0x1e, 0xd7, 0x33, 0xb1, 0xea, 0x2d, 0x4f, 0x80, 0x04, 0x2f, 0xc0, + 0xc3, 0x20, 0xb1, 0xac, 0x60, 0xc3, 0x12, 0x25, 0x3c, 0x08, 0xf2, 0x78, 0x9c, 0xb8, 0x6d, 0x62, + 0xb7, 0xec, 0xe6, 0x8e, 0xe7, 0x9c, 0x7b, 0xce, 0xf5, 0xb9, 0x70, 0x0d, 0x93, 0xc0, 0x3d, 0x9a, + 0x60, 0x9c, 0x3a, 0x51, 0xcc, 0x25, 0x27, 0x6d, 0x81, 0x4c, 0x9d, 0x7c, 0x3e, 0x76, 0x04, 0x32, + 0xff, 0x90, 0xb2, 0xd0, 0xc1, 0x24, 0xe8, 0xdc, 0x1a, 0x71, 0x3e, 0x1a, 0xa3, 0x4b, 0x23, 0xe6, + 0xd2, 0x30, 0xe4, 0x92, 0x4a, 0xc6, 0x43, 0x91, 0xe3, 0x3a, 0x8a, 0x08, 0xc3, 0x49, 0xa0, 0x2f, + 0xac, 0x03, 0xb0, 0x5e, 0x67, 0xbc, 0x6f, 0x90, 0x3d, 0x1d, 0x0e, 0x63, 0x14, 0xa2, 0x97, 0x1e, + 0xbc, 0x7b, 0xa9, 0xcf, 0x1e, 0x1e, 0x4d, 0x50, 0x48, 0x72, 0x1b, 0xb6, 0x30, 0x09, 0xfa, 0x34, + 0xbf, 0x6d, 0x1b, 0xbb, 0x46, 0x77, 0xd3, 0x03, 0x4c, 0x02, 0xfd, 0xce, 0xfa, 0x00, 0x77, 0x2a, + 0x69, 0x44, 0xc4, 0x43, 0x81, 0x19, 0x8f, 0x40, 0x76, 0x96, 0x47, 0xcc, 0x41, 0xc4, 0x04, 0xa0, + 0x42, 0x70, 0x9f, 0x51, 0x89, 0xc3, 0x76, 0x73, 0xd7, 0xe8, 0x5e, 0xf1, 0x4a, 0x37, 0x73, 0xb9, + 0x0b, 0xee, 0x5e, 0xa9, 0x67, 0x49, 0x6e, 0x65, 0x9b, 0xb9, 0xdc, 0x55, 0x34, 0x0b, 0xb9, 0x95, + 0xb6, 0x6b, 0xe5, 0xee, 0x43, 0x2b, 0x1f, 0x4b, 0xf6, 0x17, 0xfc, 0x67, 0x74, 0x3c, 0x2e, 0x24, + 0x12, 0xf8, 0x7f, 0x48, 0x25, 0x55, 0x9c, 0xdb, 0x9e, 0x3a, 0x93, 0x1d, 0x68, 0x4a, 0xae, 0x58, + 0x36, 0xbd, 0xa6, 0xe4, 0x96, 0x0d, 0x37, 0xcf, 0xa1, 0xb5, 0xb2, 0x25, 0x70, 0x2b, 0x85, 0xeb, + 0xea, 0xf9, 0x2b, 0xce, 0x42, 0x89, 0x71, 0xd1, 0xe9, 0x05, 0x6c, 0x47, 0xf9, 0x4d, 0x5f, 0xa6, + 0x11, 0x2a, 0xc8, 0xce, 0xde, 0x3d, 0x67, 0x55, 0x82, 0x1c, 0x8d, 0x7f, 0x9b, 0x46, 0xe8, 0x6d, + 0x45, 0x8b, 0x82, 0xb4, 0x61, 0x23, 0x2f, 0x51, 0x8b, 0x2c, 0x4a, 0x6b, 0x00, 0x37, 0x4e, 0xb7, + 0xd6, 0x32, 0xe7, 0x88, 0x58, 0x0f, 0xaf, 0x28, 0xb3, 0x2f, 0x09, 0xc6, 0x82, 0xf1, 0x50, 0x71, + 0x5d, 0xf5, 0x8a, 0x92, 0xb4, 0x60, 0x1d, 0x8f, 0x99, 0x90, 0xa2, 0xfd, 0x9f, 0x9a, 0xa7, 0xae, + 0xf6, 0x7e, 0xac, 0xc1, 0x9a, 0x6a, 0x42, 0xbe, 0x19, 0xd0, 0x5a, 0x1e, 0x34, 0xb2, 0xbf, 0xda, + 0x56, 0x7d, 0xcc, 0x3b, 0x4f, 0xfe, 0x11, 0x9d, 0xbb, 0xb5, 0x9c, 0x8f, 0x3f, 0xff, 0x7c, 0x6e, + 0x76, 0xc9, 0x7d, 0x57, 0x20, 0xb3, 0x0b, 0x1e, 0xb7, 0xe0, 0x71, 0xb3, 0xdd, 0x2b, 0xe5, 0x52, + 0xf9, 0x58, 0x9e, 0xc0, 0x5a, 0x1f, 0x95, 0xf9, 0xaf, 0xf5, 0x51, 0x1d, 0xfb, 0x0b, 0xf9, 0x28, + 0xed, 0x05, 0xf9, 0x6a, 0x00, 0x2c, 0x32, 0x4a, 0x1e, 0xd6, 0x4d, 0xf1, 0xec, 0x32, 0x74, 0x1e, + 0x5d, 0x02, 0x71, 0x99, 0x59, 0x2b, 0x58, 0xdf, 0xcf, 0x44, 0x7d, 0x31, 0x60, 0x43, 0xa7, 0x93, + 0xd8, 0x35, 0xed, 0x4e, 0x2f, 0x50, 0xc7, 0xb9, 0xe8, 0x73, 0x2d, 0xed, 0x81, 0x92, 0x76, 0x97, + 0x58, 0x15, 0xd2, 0xf4, 0x1a, 0xf4, 0x9e, 0x7f, 0x9f, 0x9a, 0xc6, 0xc9, 0xd4, 0x34, 0x7e, 0x4f, + 0x4d, 0xe3, 0xd3, 0xcc, 0x6c, 0x9c, 0xcc, 0xcc, 0xc6, 0xaf, 0x99, 0xd9, 0x78, 0x6f, 0x8f, 0x98, + 0x3c, 0x9c, 0x0c, 0x1c, 0x9f, 0x07, 0xe7, 0x78, 0xec, 0x9c, 0xe8, 0x58, 0x51, 0x65, 0x4b, 0x2d, + 0x06, 0xeb, 0xea, 0xfb, 0xe3, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x7d, 0xec, 0x65, 0x60, 0x2a, + 0x06, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -373,6 +495,7 @@ type QueryClient interface { SeiAddressByEVMAddress(ctx context.Context, in *QuerySeiAddressByEVMAddressRequest, opts ...grpc.CallOption) (*QuerySeiAddressByEVMAddressResponse, error) EVMAddressBySeiAddress(ctx context.Context, in *QueryEVMAddressBySeiAddressRequest, opts ...grpc.CallOption) (*QueryEVMAddressBySeiAddressResponse, error) StaticCall(ctx context.Context, in *QueryStaticCallRequest, opts ...grpc.CallOption) (*QueryStaticCallResponse, error) + Pointer(ctx context.Context, in *QueryPointerRequest, opts ...grpc.CallOption) (*QueryPointerResponse, error) } type queryClient struct { @@ -410,11 +533,21 @@ func (c *queryClient) StaticCall(ctx context.Context, in *QueryStaticCallRequest return out, nil } +func (c *queryClient) Pointer(ctx context.Context, in *QueryPointerRequest, opts ...grpc.CallOption) (*QueryPointerResponse, error) { + out := new(QueryPointerResponse) + err := c.cc.Invoke(ctx, "/seiprotocol.seichain.evm.Query/Pointer", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // QueryServer is the server API for Query service. type QueryServer interface { SeiAddressByEVMAddress(context.Context, *QuerySeiAddressByEVMAddressRequest) (*QuerySeiAddressByEVMAddressResponse, error) EVMAddressBySeiAddress(context.Context, *QueryEVMAddressBySeiAddressRequest) (*QueryEVMAddressBySeiAddressResponse, error) StaticCall(context.Context, *QueryStaticCallRequest) (*QueryStaticCallResponse, error) + Pointer(context.Context, *QueryPointerRequest) (*QueryPointerResponse, error) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. @@ -430,6 +563,9 @@ func (*UnimplementedQueryServer) EVMAddressBySeiAddress(ctx context.Context, req func (*UnimplementedQueryServer) StaticCall(ctx context.Context, req *QueryStaticCallRequest) (*QueryStaticCallResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method StaticCall not implemented") } +func (*UnimplementedQueryServer) Pointer(ctx context.Context, req *QueryPointerRequest) (*QueryPointerResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Pointer not implemented") +} func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) @@ -489,6 +625,24 @@ func _Query_StaticCall_Handler(srv interface{}, ctx context.Context, dec func(in return interceptor(ctx, in, info, handler) } +func _Query_Pointer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryPointerRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Pointer(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/seiprotocol.seichain.evm.Query/Pointer", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Pointer(ctx, req.(*QueryPointerRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "seiprotocol.seichain.evm.Query", HandlerType: (*QueryServer)(nil), @@ -505,6 +659,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "StaticCall", Handler: _Query_StaticCall_Handler, }, + { + MethodName: "Pointer", + Handler: _Query_Pointer_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "evm/query.proto", @@ -717,6 +875,86 @@ func (m *QueryStaticCallResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) return len(dAtA) - i, nil } +func (m *QueryPointerRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPointerRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPointerRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Pointee) > 0 { + i -= len(m.Pointee) + copy(dAtA[i:], m.Pointee) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Pointee))) + i-- + dAtA[i] = 0x12 + } + if m.PointerType != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.PointerType)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *QueryPointerResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPointerResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPointerResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Exists { + i-- + if m.Exists { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x18 + } + if m.Version != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.Version)) + i-- + dAtA[i] = 0x10 + } + if len(m.Pointer) > 0 { + i -= len(m.Pointer) + copy(dAtA[i:], m.Pointer) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Pointer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { offset -= sovQuery(v) base := offset @@ -816,6 +1054,41 @@ func (m *QueryStaticCallResponse) Size() (n int) { return n } +func (m *QueryPointerRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.PointerType != 0 { + n += 1 + sovQuery(uint64(m.PointerType)) + } + l = len(m.Pointee) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryPointerResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Pointer) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.Version != 0 { + n += 1 + sovQuery(uint64(m.Version)) + } + if m.Exists { + n += 2 + } + return n +} + func sovQuery(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -1390,6 +1663,228 @@ func (m *QueryStaticCallResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *QueryPointerRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPointerRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPointerRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PointerType", wireType) + } + m.PointerType = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PointerType |= PointerType(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pointee", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Pointee = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPointerResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPointerResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPointerResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pointer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Pointer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + m.Version = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Version |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Exists", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Exists = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipQuery(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/evm/types/query.pb.gw.go b/x/evm/types/query.pb.gw.go index 27555d43d..8d4d10c21 100644 --- a/x/evm/types/query.pb.gw.go +++ b/x/evm/types/query.pb.gw.go @@ -141,6 +141,42 @@ func local_request_Query_StaticCall_0(ctx context.Context, marshaler runtime.Mar } +var ( + filter_Query_Pointer_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Query_Pointer_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPointerRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_Pointer_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Pointer(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Pointer_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPointerRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_Pointer_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.Pointer(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -216,6 +252,29 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_Pointer_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Pointer_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Pointer_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -317,6 +376,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_Pointer_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Pointer_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Pointer_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -326,6 +405,8 @@ var ( pattern_Query_EVMAddressBySeiAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"sei-protocol", "seichain", "evm", "evm_address"}, "", runtime.AssumeColonVerbOpt(true))) pattern_Query_StaticCall_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"sei-protocol", "seichain", "evm", "static_call"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_Query_Pointer_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"sei-protocol", "seichain", "evm", "pointer"}, "", runtime.AssumeColonVerbOpt(true))) ) var ( @@ -334,4 +415,6 @@ var ( forward_Query_EVMAddressBySeiAddress_0 = runtime.ForwardResponseMessage forward_Query_StaticCall_0 = runtime.ForwardResponseMessage + + forward_Query_Pointer_0 = runtime.ForwardResponseMessage ) diff --git a/x/evm/types/tx.pb.go b/x/evm/types/tx.pb.go index 3debf5765..d9989aee0 100644 --- a/x/evm/types/tx.pb.go +++ b/x/evm/types/tx.pb.go @@ -32,31 +32,6 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -type PointerType int32 - -const ( - PointerType_ERC20 PointerType = 0 - PointerType_ERC721 PointerType = 1 -) - -var PointerType_name = map[int32]string{ - 0: "ERC20", - 1: "ERC721", -} - -var PointerType_value = map[string]int32{ - "ERC20": 0, - "ERC721": 1, -} - -func (x PointerType) String() string { - return proto.EnumName(PointerType_name, int32(x)) -} - -func (PointerType) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_d72e73a3d1d93781, []int{0} -} - type MsgEVMTransaction struct { Data *types.Any `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` Derived *github_com_sei_protocol_sei_chain_x_evm_derived.Derived `protobuf:"bytes,2,opt,name=derived,proto3,customtype=github.com/sei-protocol/sei-chain/x/evm/derived.Derived" json:"derived,omitempty"` @@ -580,7 +555,6 @@ func (m *MsgRegisterPointerResponse) GetPointerAddress() string { } func init() { - proto.RegisterEnum("seiprotocol.seichain.evm.PointerType", PointerType_name, PointerType_value) proto.RegisterType((*MsgEVMTransaction)(nil), "seiprotocol.seichain.evm.MsgEVMTransaction") proto.RegisterType((*MsgEVMTransactionResponse)(nil), "seiprotocol.seichain.evm.MsgEVMTransactionResponse") proto.RegisterType((*MsgInternalEVMCall)(nil), "seiprotocol.seichain.evm.MsgInternalEVMCall") @@ -596,55 +570,54 @@ func init() { func init() { proto.RegisterFile("evm/tx.proto", fileDescriptor_d72e73a3d1d93781) } var fileDescriptor_d72e73a3d1d93781 = []byte{ - // 763 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x54, 0x4f, 0x6f, 0x1b, 0x45, - 0x14, 0xf7, 0xda, 0x8e, 0x5d, 0x3f, 0x5b, 0x4e, 0x3b, 0xaa, 0x90, 0x6d, 0x81, 0x1d, 0x56, 0x05, - 0x4c, 0x21, 0xbb, 0x8d, 0x8b, 0xd4, 0x03, 0x17, 0x6a, 0xc7, 0xa2, 0x3d, 0x58, 0x42, 0x4b, 0xda, - 0x03, 0x17, 0x6b, 0xbc, 0xfb, 0xba, 0x5e, 0xe1, 0x9d, 0xb1, 0x66, 0xc6, 0x56, 0xfc, 0x05, 0x38, - 0x23, 0x84, 0x04, 0x9f, 0x81, 0x13, 0x9f, 0x02, 0xe5, 0x98, 0x23, 0xca, 0x21, 0xa0, 0xe4, 0x8b, - 0xa0, 0x9d, 0x59, 0x2f, 0x89, 0x23, 0xa7, 0xc9, 0x69, 0xdf, 0xff, 0xf7, 0xfb, 0xbd, 0xf7, 0x66, - 0xa1, 0x86, 0xcb, 0xd8, 0x55, 0xc7, 0xce, 0x5c, 0x70, 0xc5, 0x49, 0x43, 0x62, 0xa4, 0x25, 0x9f, - 0xcf, 0x1c, 0x89, 0x91, 0x3f, 0xa5, 0x11, 0x73, 0x70, 0x19, 0xb7, 0x9a, 0x21, 0xe7, 0xe1, 0x0c, - 0x5d, 0xed, 0x9d, 0x2c, 0xde, 0xb9, 0x94, 0xad, 0x4c, 0x52, 0xeb, 0x71, 0xc8, 0x43, 0xae, 0x45, - 0x37, 0x91, 0x52, 0x6b, 0xdb, 0xe7, 0x32, 0xe6, 0xd2, 0x9d, 0x50, 0x89, 0xee, 0xf2, 0x60, 0x82, - 0x8a, 0x1e, 0xb8, 0x3e, 0x8f, 0x98, 0xf1, 0xdb, 0xbf, 0x5a, 0xf0, 0x68, 0x24, 0xc3, 0xe1, 0xdb, - 0xd1, 0x91, 0xa0, 0x4c, 0x52, 0x5f, 0x45, 0x9c, 0x91, 0x2e, 0x14, 0x03, 0xaa, 0x68, 0xc3, 0xda, - 0xb3, 0xba, 0xd5, 0xde, 0x63, 0xc7, 0x74, 0x75, 0xd6, 0x5d, 0x9d, 0x97, 0x6c, 0xe5, 0xe9, 0x08, - 0xf2, 0x06, 0xca, 0x01, 0x8a, 0x68, 0x89, 0x41, 0x23, 0xbf, 0x67, 0x75, 0x6b, 0xfd, 0xaf, 0xcf, - 0xce, 0x3b, 0x2f, 0xc2, 0x48, 0x4d, 0x17, 0x13, 0xc7, 0xe7, 0xb1, 0x2b, 0x31, 0xda, 0x5f, 0x73, - 0xd1, 0x8a, 0x26, 0xe3, 0x1e, 0xbb, 0x09, 0xe3, 0x34, 0xd5, 0x39, 0x34, 0x5f, 0x6f, 0x5d, 0xcb, - 0xfe, 0xc9, 0x82, 0xe6, 0x0d, 0x58, 0x1e, 0xca, 0x39, 0x67, 0x12, 0x49, 0x13, 0x1e, 0x84, 0x54, - 0x8e, 0x17, 0x12, 0x03, 0x0d, 0xb1, 0xe8, 0x95, 0x43, 0x2a, 0xdf, 0x48, 0x0c, 0x12, 0xd7, 0x32, - 0x1e, 0xa3, 0x10, 0x5c, 0x68, 0x40, 0x15, 0xaf, 0xbc, 0x8c, 0x87, 0x89, 0x4a, 0x3a, 0x50, 0x15, - 0xa8, 0x16, 0x82, 0x8d, 0x35, 0xb7, 0x42, 0x02, 0xd7, 0x03, 0x63, 0x3a, 0x4c, 0xb8, 0x10, 0x28, - 0x4e, 0xa9, 0x9c, 0x36, 0x8a, 0x3a, 0x4f, 0xcb, 0xf6, 0x2f, 0x16, 0x90, 0x91, 0x0c, 0x5f, 0x33, - 0x85, 0x82, 0xd1, 0xd9, 0xf0, 0xed, 0x68, 0x40, 0x67, 0x33, 0xf2, 0x01, 0x94, 0x24, 0xb2, 0x00, - 0x85, 0xee, 0x5f, 0xf1, 0x52, 0x8d, 0x7c, 0x03, 0x3b, 0x4b, 0x3a, 0x5b, 0xa0, 0xe9, 0xdd, 0x7f, - 0x7a, 0x76, 0xde, 0xf9, 0xf4, 0xca, 0x30, 0xd2, 0x65, 0x98, 0xcf, 0xbe, 0x0c, 0x7e, 0x74, 0xd5, - 0x6a, 0x8e, 0xd2, 0x79, 0xcd, 0x94, 0x67, 0x12, 0x49, 0x1d, 0xf2, 0x8a, 0x6b, 0x70, 0x15, 0x2f, - 0xaf, 0x78, 0x02, 0x4a, 0xc3, 0x2d, 0x6a, 0xb8, 0x5a, 0xb6, 0x3f, 0x84, 0xd6, 0x4d, 0x4c, 0xeb, - 0xe9, 0xd8, 0xbf, 0x5b, 0x9b, 0xee, 0x43, 0x9c, 0x61, 0x48, 0x15, 0xde, 0x0a, 0xbd, 0x05, 0x0f, - 0x7c, 0x1e, 0xe0, 0xab, 0x64, 0x02, 0x7a, 0x95, 0x5e, 0xa6, 0xdf, 0x05, 0x14, 0xb1, 0xa1, 0xf6, - 0x4e, 0xf0, 0x78, 0xc0, 0x99, 0x12, 0xd4, 0x57, 0x8d, 0x1d, 0x1d, 0x7d, 0xcd, 0x66, 0x3f, 0x01, - 0x7b, 0x3b, 0xb2, 0x8c, 0xc0, 0x9f, 0x16, 0x94, 0x47, 0x32, 0xfc, 0x1e, 0x59, 0x40, 0x3e, 0x36, - 0x55, 0xc7, 0x34, 0x08, 0x04, 0x4a, 0x99, 0x62, 0xae, 0x26, 0xb6, 0x97, 0xc6, 0x44, 0x3e, 0x02, - 0x50, 0x3c, 0x0b, 0x30, 0x4b, 0xaf, 0x28, 0xbe, 0x76, 0xfb, 0x50, 0xa2, 0x31, 0x5f, 0x30, 0xd5, - 0x28, 0xec, 0x15, 0xba, 0xd5, 0x5e, 0xd3, 0x31, 0xe3, 0x77, 0x92, 0x27, 0xe1, 0xa4, 0x4f, 0xc2, - 0x19, 0xf0, 0x88, 0xf5, 0x9f, 0x9d, 0x9c, 0x77, 0x72, 0x7f, 0xfc, 0xd3, 0xe9, 0xde, 0x61, 0x65, - 0x49, 0x82, 0xf4, 0xd2, 0xd2, 0xf6, 0x23, 0xd8, 0x4d, 0x11, 0x67, 0x2c, 0x7e, 0x33, 0x97, 0xe3, - 0x61, 0x18, 0x49, 0x85, 0xe2, 0x3b, 0x1e, 0x25, 0xb4, 0xb7, 0x8e, 0xff, 0x15, 0xd4, 0xe6, 0x26, - 0x64, 0x9c, 0x34, 0xd0, 0x3c, 0xea, 0xbd, 0x4f, 0x9c, 0x6d, 0xbf, 0x02, 0x27, 0x2d, 0x78, 0xb4, - 0x9a, 0xa3, 0x57, 0x9d, 0xff, 0xaf, 0x24, 0x77, 0x8e, 0xc2, 0xcf, 0x06, 0x62, 0xb6, 0x06, 0x28, - 0xfc, 0x74, 0x22, 0xf6, 0x50, 0xdf, 0xc7, 0x06, 0xb0, 0xec, 0x71, 0x7d, 0x06, 0xbb, 0x6b, 0x20, - 0xd7, 0x87, 0x5e, 0x4f, 0xcd, 0x69, 0x99, 0xa7, 0x4f, 0xa0, 0x7a, 0x05, 0x03, 0xa9, 0xc0, 0xce, - 0xd0, 0x1b, 0xf4, 0x9e, 0x3d, 0xcc, 0x11, 0x80, 0xd2, 0xd0, 0x1b, 0xbc, 0xe8, 0x1d, 0x3c, 0xb4, - 0x7a, 0x7f, 0xe5, 0xa1, 0x30, 0x92, 0x21, 0x11, 0x50, 0xdf, 0xf8, 0xc9, 0x7c, 0xb1, 0x9d, 0xdb, - 0x8d, 0xa7, 0xdf, 0x7a, 0x7e, 0x8f, 0xe0, 0x8c, 0xca, 0x11, 0x14, 0xcd, 0x11, 0xdd, 0x9a, 0x9c, - 0x84, 0xb4, 0x3e, 0x7f, 0x6f, 0x48, 0x56, 0x75, 0x01, 0xbb, 0x9b, 0x4b, 0xfd, 0xf2, 0xd6, 0xec, - 0x8d, 0xe8, 0xd6, 0x57, 0xf7, 0x89, 0x5e, 0xb7, 0xed, 0x7f, 0x7b, 0x72, 0xd1, 0xb6, 0x4e, 0x2f, - 0xda, 0xd6, 0xbf, 0x17, 0x6d, 0xeb, 0xe7, 0xcb, 0x76, 0xee, 0xf4, 0xb2, 0x9d, 0xfb, 0xfb, 0xb2, - 0x9d, 0xfb, 0x61, 0xff, 0xae, 0xbf, 0x5b, 0x7d, 0xb9, 0x93, 0x92, 0xf6, 0x3f, 0xff, 0x2f, 0x00, - 0x00, 0xff, 0xff, 0x8b, 0x3b, 0x7e, 0x87, 0x74, 0x06, 0x00, 0x00, + // 744 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x94, 0xcd, 0x4e, 0xdb, 0x48, + 0x1c, 0xc0, 0xe3, 0x24, 0x10, 0x32, 0x89, 0x12, 0x61, 0xa1, 0x55, 0x12, 0xed, 0x26, 0xac, 0xb5, + 0x1f, 0xd9, 0x0f, 0xec, 0x25, 0xac, 0xb4, 0x87, 0xbd, 0x2c, 0x81, 0x68, 0xe1, 0x10, 0x69, 0xe5, + 0x05, 0x0e, 0xbd, 0x44, 0x13, 0xfb, 0x8f, 0x63, 0x35, 0x9e, 0x89, 0x66, 0xc6, 0x11, 0x79, 0x81, + 0x9e, 0xab, 0xaa, 0x52, 0xfb, 0x0c, 0x3d, 0xf5, 0x29, 0x2a, 0x8e, 0x1c, 0x2b, 0x0e, 0xb4, 0x82, + 0x17, 0xa9, 0x66, 0xc6, 0x71, 0x4b, 0x50, 0x28, 0x9c, 0x3c, 0xf3, 0xff, 0xfc, 0xfd, 0x3f, 0xc6, + 0xa8, 0x0c, 0xd3, 0xc8, 0x11, 0x67, 0xf6, 0x84, 0x51, 0x41, 0xcd, 0x1a, 0x87, 0x50, 0x9d, 0x3c, + 0x3a, 0xb6, 0x39, 0x84, 0xde, 0x08, 0x87, 0xc4, 0x86, 0x69, 0xd4, 0xa8, 0x07, 0x94, 0x06, 0x63, + 0x70, 0x94, 0x76, 0x18, 0x9f, 0x3a, 0x98, 0xcc, 0xb4, 0x53, 0x63, 0x23, 0xa0, 0x01, 0x55, 0x47, + 0x47, 0x9e, 0x12, 0x69, 0xd3, 0xa3, 0x3c, 0xa2, 0xdc, 0x19, 0x62, 0x0e, 0xce, 0x74, 0x7b, 0x08, + 0x02, 0x6f, 0x3b, 0x1e, 0x0d, 0x49, 0xa2, 0xaf, 0xca, 0xc4, 0x40, 0xe2, 0x88, 0x6b, 0x81, 0xf5, + 0xd2, 0x40, 0xeb, 0x7d, 0x1e, 0xf4, 0x4e, 0xfa, 0x47, 0x0c, 0x13, 0x8e, 0x3d, 0x11, 0x52, 0x62, + 0xb6, 0x51, 0xde, 0xc7, 0x02, 0xd7, 0x8c, 0x4d, 0xa3, 0x5d, 0xea, 0x6c, 0xd8, 0x1a, 0xc3, 0x9e, + 0x63, 0xd8, 0xbb, 0x64, 0xe6, 0x2a, 0x0b, 0xf3, 0x18, 0x15, 0x7c, 0x60, 0xe1, 0x14, 0xfc, 0x5a, + 0x76, 0xd3, 0x68, 0x97, 0xbb, 0x7f, 0x5f, 0x5e, 0xb5, 0xfe, 0x0a, 0x42, 0x31, 0x8a, 0x87, 0xb6, + 0x47, 0x23, 0x87, 0x43, 0xb8, 0x35, 0x2f, 0x4e, 0x5d, 0x54, 0x75, 0xce, 0x99, 0x23, 0x49, 0x12, + 0x57, 0x7b, 0x5f, 0x7f, 0xdd, 0x79, 0x2c, 0xeb, 0x99, 0x81, 0xea, 0x77, 0xb0, 0x5c, 0xe0, 0x13, + 0x4a, 0x38, 0x98, 0x75, 0xb4, 0x16, 0x60, 0x3e, 0x88, 0x39, 0xf8, 0x0a, 0x31, 0xef, 0x16, 0x02, + 0xcc, 0x8f, 0x39, 0xf8, 0x52, 0x35, 0x8d, 0x06, 0xc0, 0x18, 0x65, 0x0a, 0xa8, 0xe8, 0x16, 0xa6, + 0x51, 0x4f, 0x5e, 0xcd, 0x16, 0x2a, 0x31, 0x10, 0x31, 0x23, 0x03, 0x55, 0x5b, 0x4e, 0xe2, 0xba, + 0x48, 0x8b, 0xf6, 0x65, 0x2d, 0x26, 0xca, 0x8f, 0x30, 0x1f, 0xd5, 0xf2, 0xca, 0x4f, 0x9d, 0xad, + 0x17, 0x06, 0x32, 0xfb, 0x3c, 0x38, 0x24, 0x02, 0x18, 0xc1, 0xe3, 0xde, 0x49, 0x7f, 0x0f, 0x8f, + 0xc7, 0xe6, 0x37, 0x68, 0x95, 0x03, 0xf1, 0x81, 0xa9, 0xfc, 0x45, 0x37, 0xb9, 0x99, 0xff, 0xa0, + 0x95, 0x29, 0x1e, 0xc7, 0xa0, 0x73, 0x77, 0x7f, 0xbd, 0xbc, 0x6a, 0xfd, 0xf4, 0x45, 0x33, 0x92, + 0xe9, 0xe8, 0xcf, 0x16, 0xf7, 0x9f, 0x3a, 0x62, 0x36, 0x01, 0x6e, 0x1f, 0x12, 0xe1, 0x6a, 0x47, + 0xb3, 0x82, 0xb2, 0x82, 0x2a, 0xb8, 0xa2, 0x9b, 0x15, 0x54, 0x42, 0x29, 0xdc, 0xbc, 0xc2, 0x55, + 0x67, 0xeb, 0x5b, 0xd4, 0xb8, 0xcb, 0x34, 0xef, 0x8e, 0xf5, 0xda, 0x58, 0x54, 0xef, 0xc3, 0x18, + 0x02, 0x2c, 0xe0, 0x5e, 0xf4, 0x06, 0x5a, 0xf3, 0xa8, 0x0f, 0x07, 0xb2, 0x03, 0x6a, 0x94, 0x6e, + 0x7a, 0x7f, 0x08, 0x94, 0x69, 0xa1, 0xf2, 0x29, 0xa3, 0xd1, 0x1e, 0x25, 0x82, 0x61, 0x4f, 0xd4, + 0x56, 0x94, 0xf5, 0x2d, 0x99, 0xf5, 0x03, 0xb2, 0x96, 0x93, 0xa5, 0x05, 0xbc, 0x35, 0x50, 0xa1, + 0xcf, 0x83, 0xff, 0x81, 0xf8, 0xe6, 0xf7, 0x3a, 0xea, 0x00, 0xfb, 0x3e, 0x03, 0xce, 0x13, 0xe6, + 0x92, 0x94, 0xed, 0x6a, 0x91, 0xf9, 0x1d, 0x42, 0x82, 0xa6, 0x06, 0x7a, 0xe8, 0x45, 0x41, 0xe7, + 0x6a, 0x0f, 0xad, 0xe2, 0x88, 0xc6, 0x44, 0xd4, 0x72, 0x9b, 0xb9, 0x76, 0xa9, 0x53, 0xb7, 0x75, + 0xfb, 0x6d, 0xf9, 0x46, 0xec, 0xe4, 0x8d, 0xd8, 0x7b, 0x34, 0x24, 0xdd, 0x3f, 0xce, 0xaf, 0x5a, + 0x99, 0x37, 0x1f, 0x5a, 0xed, 0x07, 0x8c, 0x4c, 0x3a, 0x70, 0x37, 0x09, 0x6d, 0xad, 0xa3, 0x6a, + 0x42, 0x9c, 0x56, 0xf1, 0x4a, 0x6f, 0x8e, 0x0b, 0x41, 0xc8, 0x05, 0xb0, 0xff, 0x68, 0x28, 0xcb, + 0x5e, 0xda, 0xfe, 0x03, 0x54, 0x9e, 0x68, 0x93, 0x81, 0x4c, 0xa0, 0xea, 0xa8, 0x74, 0x7e, 0xb4, + 0x97, 0xfd, 0x1b, 0xec, 0x24, 0xe0, 0xd1, 0x6c, 0x02, 0x6e, 0x69, 0xf2, 0xf9, 0x22, 0xf7, 0x1c, + 0x98, 0x97, 0x36, 0x44, 0x4f, 0x0d, 0x01, 0xf3, 0x92, 0x8e, 0x58, 0x3d, 0xb5, 0x1f, 0x0b, 0x60, + 0xe9, 0xe3, 0xfa, 0x19, 0x55, 0xe7, 0x20, 0xb7, 0x9b, 0x5e, 0x49, 0xc4, 0x49, 0x98, 0xce, 0xbb, + 0x2c, 0xca, 0xf5, 0x79, 0x60, 0x32, 0x54, 0x59, 0xf8, 0x7d, 0xfc, 0xb6, 0x9c, 0xfa, 0xce, 0xa3, + 0x6e, 0xec, 0x3c, 0xc2, 0x38, 0x85, 0x3c, 0x42, 0x79, 0xbd, 0x1e, 0xf7, 0x3a, 0x4b, 0x93, 0xc6, + 0x2f, 0x5f, 0x35, 0x49, 0xa3, 0xc6, 0xa8, 0xba, 0x38, 0xae, 0xdf, 0xef, 0xf5, 0x5e, 0xb0, 0x6e, + 0xfc, 0xf9, 0x18, 0xeb, 0x79, 0xda, 0xee, 0xbf, 0xe7, 0xd7, 0x4d, 0xe3, 0xe2, 0xba, 0x69, 0x7c, + 0xbc, 0x6e, 0x1a, 0xcf, 0x6f, 0x9a, 0x99, 0x8b, 0x9b, 0x66, 0xe6, 0xfd, 0x4d, 0x33, 0xf3, 0x64, + 0xeb, 0xa1, 0x3f, 0x52, 0xb5, 0x93, 0xc3, 0x55, 0xa5, 0xdf, 0xf9, 0x14, 0x00, 0x00, 0xff, 0xff, + 0x51, 0x5e, 0xb8, 0x08, 0x5f, 0x06, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. From a1e7bebdd1d8918a2a512f7ac1847519c2e0102f Mon Sep 17 00:00:00 2001 From: Jeremy Wei Date: Fri, 5 Apr 2024 10:07:25 -0400 Subject: [PATCH 18/34] EVM Invalid block tests (#1497) * not following eip-4788 * add run_blocktests script * add initialize_local_chain_no_run script * add prints * add more prints * made min fee per gas 0 * Revert "add more prints" This reverts commit 3c940e154bcbb4628a53c862e83447597beb0c1d. * Revert "add prints" This reverts commit fd28389c6b6b6980131b8a40496a8e0217ab2b66. * 2 new failing tests * use initialize local chain with NO_RUN flag * Revert "Revert "add more prints"" This reverts commit 4c7603ac584f351c9135c91ba2a57e6467e036de. * Revert "Revert "add prints"" This reverts commit 98ea147eb8e6107f2ebdb90f8028d5f48c73de8e. * increment height in getHash and ignore withdraw addrs * update failed tests * add eth blocktests * adjust git clone * Revert "Revert "Revert "add more prints""" This reverts commit 1e0aede5f9273cf1d785d3a47a7fac279b47a0d9. * Revert "Revert "Revert "add prints""" This reverts commit 37641228ba03f67a641f6663bc16081c37d4c899. * Split eth blocktests in CI * Modify ethtests path * refactor output * minor fix * add debug statement * Add more stuff * wrap filesnames with quotes * github workflow split tests * fix ethtests script * fix ethtests script * fix ethtests script * fix ethtests script * adjust for invalid block tests * a bunch of invalid block tests seem to work * run invalid block tests in ci * add logging * sort block tests * reduce prints * reduce prints * now getting real errors * cleanup * cleanup * not run blockWithAllTransactionTypes * Revert "not run blockWithAllTransactionTypes" - this should work This reverts commit 51aa2b16ad9872fa3f0893f0cc5c6999f3904399. * not run blockWithAllTransactionTypes --------- Co-authored-by: Uday Patil --- .github/workflows/eth_blocktests.yml | 2 +- app/eth_replay.go | 5 +++ run_blocktests.sh | 65 ++++++++++++++++++++++------ x/evm/keeper/keeper.go | 32 +++++--------- x/evm/module.go | 14 ++---- 5 files changed, 72 insertions(+), 46 deletions(-) diff --git a/.github/workflows/eth_blocktests.yml b/.github/workflows/eth_blocktests.yml index 4c54deaba..f85962665 100644 --- a/.github/workflows/eth_blocktests.yml +++ b/.github/workflows/eth_blocktests.yml @@ -50,4 +50,4 @@ jobs: run: git clone https://github.com/ethereum/tests.git ethtests - name: "Run ETH Blocktest" - run: ./run_blocktests.sh ./ethtests/BlockchainTests/ValidBlocks/ ${{ matrix.runner-index }} ${{ env.TOTAL_RUNNERS }} + run: ./run_blocktests.sh ./ethtests/BlockchainTests/ ${{ matrix.runner-index }} ${{ env.TOTAL_RUNNERS }} diff --git a/app/eth_replay.go b/app/eth_replay.go index 21746b6de..80b045068 100644 --- a/app/eth_replay.go +++ b/app/eth_replay.go @@ -6,11 +6,13 @@ import ( "fmt" "math/big" "path/filepath" + "strings" "time" "github.com/cosmos/cosmos-sdk/client" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" + ethcore "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/tracing" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" @@ -202,6 +204,9 @@ func encodeTx(tx *ethtypes.Transaction, txConfig client.TxConfig) []byte { txData, err = ethtx.NewBlobTx(tx) } if err != nil { + if strings.Contains(err.Error(), ethcore.ErrTipAboveFeeCap.Error()) { + return nil + } panic(err) } msg, err := evmtypes.NewMsgEVMTransaction(txData) diff --git a/run_blocktests.sh b/run_blocktests.sh index 7400d2d9d..05367f6d5 100755 --- a/run_blocktests.sh +++ b/run_blocktests.sh @@ -15,50 +15,87 @@ fi echo $mode echo $block_tests_path +# Define an array of test directories to run +declare -a test_path_run_list=( + # run all valid block tests + "ValidBlocks/" + + # run only certain invalid block tests + "InvalidBlocks/bcEIP1559/" + "InvalidBlocks/bcStateTests/" +) + # Define an array of tests to skip -declare -a skip_list=( +declare -a test_name_skip_list=( + # valid block tests "DelegateCallSpam" # passes, but takes super long "blockhashTests" # failing "blockhashNonConstArg" # failing - "BLOCKHASH_Bounds" # newly failing - "logRevert" # failing after increment height + "BLOCKHASH_Bounds" # failing + "logRevert" # uses an invalid opcode (0xBA) + "blockWithAllTransactionTypes" # recently started failing + + # invalid block tests - state tests + "gasLimitTooHigh" # block header gas limit doesn't apply to us + "transactionFromSelfDestructedContract" # failing + + # InvaldBlockTests/bcEIP1559 + "badUncles" # reorgs don't apply to us + "checkGasLimit" # not sure what issue is ) # list out all paths to json files starting from the block_tests_dir -block_tests=$(find "$block_tests_path" -name "*.json") - -test_files="" +block_tests=$(find "$block_tests_path" -name "*.json" | sort) i=0 # for each json file, run the block test -for test_file in $block_tests; do - test_name=$(basename "$test_file" .json) +for test_path in $block_tests; do + test_name=$(basename "$test_path" .json) + match_found=false + + # Iterate through the test_path_run_list to check for a match + for run_path in "${test_path_run_list[@]}"; do + if [[ "$test_path" == *"$run_path"* ]]; then + match_found=true + break + fi + done + + # Skip the test if no match is found + if [ "$match_found" = false ]; then + continue + fi + + echo "test file: $test_path" + echo "test dir: $test_path" # Check if the test name is in the skip list - if printf '%s\n' "${skip_list[@]}" | grep -qx "$test_name"; then - echo "Skipping test: $test_file" + if printf '%s\n' "${test_name_skip_list[@]}" | grep -qx "$test_name"; then + echo "Skipping test in skip list: $test_path" continue fi # Check if "${test_name}_Cancun" is not in the test file - if ! grep -q "${test_name}_Cancun" "$test_file"; then - echo "Skipping test due to missing Cancun tag: $test_file" + if ! grep -q "${test_name}_Cancun" "$test_path"; then + echo "Skipping test due to missing Cancun tag: $test_path" continue fi if [ $((i % runner_total)) -ne $runner_index ]; then i=$((i+1)) + runner_id=$((i % runner_total)) + echo "Skipping test not in runner index: $test_path, runner index: $runner_id" continue fi i=$((i+1)) echo -e "\n*********************************************************\n" - echo "Running block test: $test_file" + echo "Running block test: $test_path" echo "test name: ${test_name}_Cancun" echo -e "\n*********************************************************\n" rm -r ~/.sei || true NO_RUN=1 ./scripts/initialize_local_chain.sh - seid blocktest --block-test $test_file --test-name "${test_name}_Cancun" + seid blocktest --block-test $test_path --test-name "${test_name}_Cancun" done diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index 667747d51..f17c69d04 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -421,8 +421,12 @@ func (k *Keeper) GetBaseFee(ctx sdk.Context) *big.Int { return k.ReplayBlock.Header_.BaseFee } if k.EthBlockTestConfig.Enabled { - block := k.BlockTest.Json.Blocks[ctx.BlockHeight()-1] - return block.BlockHeader.BaseFeePerGas + bb := k.BlockTest.Json.Blocks[ctx.BlockHeight()-1] + b, err := bb.Decode() + if err != nil { + panic(err) + } + return b.Header_.BaseFee } return nil } @@ -460,26 +464,12 @@ func (k *Keeper) getInt64State(ctx sdk.Context, key []byte) int64 { } func (k *Keeper) getBlockTestBlockCtx(ctx sdk.Context) (*vm.BlockContext, error) { - btBlock := k.BlockTest.Json.Blocks[ctx.BlockHeight()-1] - btHeader := btBlock.BlockHeader - header := ðtypes.Header{ - ParentHash: btHeader.ParentHash, - UncleHash: btHeader.UncleHash, - Coinbase: btHeader.Coinbase, - Root: btHeader.StateRoot, - TxHash: btHeader.TransactionsTrie, - ReceiptHash: btHeader.ReceiptTrie, - Bloom: btHeader.Bloom, - Difficulty: btHeader.Difficulty, - Number: new(big.Int).Set(btHeader.Number), - GasLimit: btHeader.GasLimit, - GasUsed: btHeader.GasUsed, - Time: btHeader.Timestamp, - Extra: btHeader.ExtraData, - MixDigest: btHeader.MixHash, - Nonce: btHeader.Nonce, - BaseFee: btHeader.BaseFeePerGas, + bb := k.BlockTest.Json.Blocks[ctx.BlockHeight()-1] + b, err := bb.Decode() + if err != nil { + return nil, err } + header := b.Header_ getHash := func(height uint64) common.Hash { height = height + 1 for i := 0; i < len(k.BlockTest.Json.Blocks); i++ { diff --git a/x/evm/module.go b/x/evm/module.go index 16490ef5e..f3585b60c 100644 --- a/x/evm/module.go +++ b/x/evm/module.go @@ -11,7 +11,6 @@ import ( "github.com/ethereum/go-ethereum/core" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/tests" "github.com/gorilla/mux" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" @@ -201,16 +200,11 @@ func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.Val var coinbase sdk.AccAddress if am.keeper.EthBlockTestConfig.Enabled { blocks := am.keeper.BlockTest.Json.Blocks - var block *tests.BtBlock - for i, b := range blocks { - if b.BlockHeader.Number.Uint64() == uint64(ctx.BlockHeight()) { - block = &blocks[i] - } - } - if block == nil { - panic(fmt.Sprintf("block not found at height %d", ctx.BlockHeight())) + block, err := blocks[ctx.BlockHeight()-1].Decode() + if err != nil { + panic(err) } - coinbase = am.keeper.GetSeiAddressOrDefault(ctx, block.BlockHeader.Coinbase) + coinbase = am.keeper.GetSeiAddressOrDefault(ctx, block.Header_.Coinbase) } else if am.keeper.EthReplayConfig.Enabled { coinbase = am.keeper.GetSeiAddressOrDefault(ctx, am.keeper.ReplayBlock.Header_.Coinbase) am.keeper.SetReplayedHeight(ctx) From 05ec7c17fcaaf01ff036cea6130aeda78e71fe20 Mon Sep 17 00:00:00 2001 From: Kartik Bhat Date: Fri, 5 Apr 2024 12:43:35 -0400 Subject: [PATCH 19/34] Lower Evm max priority (#1511) --- app/antedecorators/priority.go | 6 ++++-- x/evm/ante/fee.go | 5 +++-- x/evm/ante/fee_test.go | 4 +++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/app/antedecorators/priority.go b/app/antedecorators/priority.go index cb17f62bc..0fe7b287a 100644 --- a/app/antedecorators/priority.go +++ b/app/antedecorators/priority.go @@ -10,6 +10,8 @@ import ( const ( OraclePriority = math.MaxInt64 - 100 EVMAssociatePriority = math.MaxInt64 - 101 + // This is the max priority a non oracle or associate tx can take + MaxPriority = math.MaxInt64 - 1000 ) type PriorityDecorator struct{} @@ -27,9 +29,9 @@ func intMin(a, b int64) int64 { // Assigns higher priority to certain types of transactions including oracle func (pd PriorityDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { - // Cap priority to MAXINT64 - 1000 + // Cap priority // Use higher priorities for tiers including oracle tx's - priority := intMin(ctx.Priority(), math.MaxInt64-1000) + priority := intMin(ctx.Priority(), MaxPriority) if isOracleTx(tx) { priority = OraclePriority diff --git a/x/evm/ante/fee.go b/x/evm/ante/fee.go index 48328b82d..af408219b 100644 --- a/x/evm/ante/fee.go +++ b/x/evm/ante/fee.go @@ -6,6 +6,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/ethereum/go-ethereum/consensus/misc/eip4844" + "github.com/sei-protocol/sei-chain/app/antedecorators" "github.com/sei-protocol/sei-chain/utils" "github.com/sei-protocol/sei-chain/x/evm/derived" evmkeeper "github.com/sei-protocol/sei-chain/x/evm/keeper" @@ -84,8 +85,8 @@ func (fc EVMFeeCheckDecorator) getMinimumFee(ctx sdk.Context) *big.Int { func (fc EVMFeeCheckDecorator) CalculatePriority(ctx sdk.Context, txData ethtx.TxData) *big.Int { gp := txData.EffectiveGasPrice(utils.Big0) priority := new(big.Int).Quo(gp, fc.evmKeeper.GetPriorityNormalizer(ctx).RoundInt().BigInt()) - if priority.Cmp(utils.BigMaxI64) > 0 { - priority = utils.BigMaxI64 + if priority.Cmp(big.NewInt(antedecorators.MaxPriority)) > 0 { + priority = big.NewInt(antedecorators.MaxPriority) } return priority } diff --git a/x/evm/ante/fee_test.go b/x/evm/ante/fee_test.go index a7de56bfe..733858f42 100644 --- a/x/evm/ante/fee_test.go +++ b/x/evm/ante/fee_test.go @@ -11,6 +11,7 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/holiman/uint256" + "github.com/sei-protocol/sei-chain/app/antedecorators" testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" "github.com/sei-protocol/sei-chain/x/evm/ante" "github.com/sei-protocol/sei-chain/x/evm/state" @@ -178,6 +179,7 @@ func TestCalculatePriorityScenarios(t *testing.T) { _1_1gwei := big.NewInt(1100000000000) _2gwei := big.NewInt(200000000000) maxInt := big.NewInt(math.MaxInt64) + maxPriority := big.NewInt(antedecorators.MaxPriority) scenarios := []struct { name string @@ -236,7 +238,7 @@ func TestCalculatePriorityScenarios(t *testing.T) { GasTipCap: new(big.Int).Add(maxInt, big.NewInt(1)), Value: big.NewInt(1000000000), }, - expectedPriority: maxInt, + expectedPriority: maxPriority, }, { name: "LegacyTx has priority with gas price", From fe9ca6868858f66a287f9d31993cb917138ec1a9 Mon Sep 17 00:00:00 2001 From: Kartik Bhat Date: Fri, 5 Apr 2024 13:05:00 -0400 Subject: [PATCH 20/34] Replace MustGetEVMAddressFromBech32OrDefault (#1510) --- evmrpc/association.go | 6 +- precompiles/addr/addr.go | 6 +- precompiles/common/expected_keepers.go | 2 +- precompiles/pointer/pointer.go | 12 +++- x/evm/client/wasm/query.go | 66 +++++++++++++++---- x/evm/keeper/address.go | 8 ++- x/evm/keeper/evm.go | 12 +++- x/evm/keeper/grpc_query.go | 6 +- x/evm/types/message_internal_evm_call.go | 6 +- .../message_internal_evm_delegate_call.go | 6 +- 10 files changed, 105 insertions(+), 25 deletions(-) diff --git a/evmrpc/association.go b/evmrpc/association.go index fc9555190..c3d14a937 100644 --- a/evmrpc/association.go +++ b/evmrpc/association.go @@ -98,7 +98,11 @@ func (t *AssociationAPI) GetSeiAddress(_ context.Context, ethAddress common.Addr func (t *AssociationAPI) GetEVMAddress(_ context.Context, seiAddress string) (result string, returnErr error) { startTime := time.Now() defer recordMetrics("sei_getEVMAddress", startTime, returnErr == nil) - ethAddress, found := t.keeper.GetEVMAddress(t.ctxProvider(LatestCtxHeight), sdk.MustAccAddressFromBech32(seiAddress)) + seiAddr, err := sdk.AccAddressFromBech32(seiAddress) + if err != nil { + return "", err + } + ethAddress, found := t.keeper.GetEVMAddress(t.ctxProvider(LatestCtxHeight), seiAddr) if !found { return "", fmt.Errorf("failed to find EVM address for %s", seiAddress) } diff --git a/precompiles/addr/addr.go b/precompiles/addr/addr.go index 4ce312aac..939e9b28a 100644 --- a/precompiles/addr/addr.go +++ b/precompiles/addr/addr.go @@ -124,7 +124,11 @@ func (p Precompile) getEvmAddr(ctx sdk.Context, method *abi.Method, args []inter if err := pcommon.ValidateArgsLength(args, 1); err != nil { return nil, err } - evmAddr := p.evmKeeper.GetEVMAddressFromBech32OrDefault(ctx, args[0].(string)) + + evmAddr, err := p.evmKeeper.GetEVMAddressFromBech32OrDefault(ctx, args[0].(string)) + if err != nil { + return nil, err + } return method.Outputs.Pack(evmAddr) } diff --git a/precompiles/common/expected_keepers.go b/precompiles/common/expected_keepers.go index ecebf3aba..5779955ba 100644 --- a/precompiles/common/expected_keepers.go +++ b/precompiles/common/expected_keepers.go @@ -25,7 +25,7 @@ type EVMKeeper interface { GetSeiAddress(sdk.Context, common.Address) (sdk.AccAddress, bool) GetSeiAddressOrDefault(ctx sdk.Context, evmAddress common.Address) sdk.AccAddress GetEVMAddress(sdk.Context, sdk.AccAddress) (common.Address, bool) - GetEVMAddressFromBech32OrDefault(ctx sdk.Context, seiAddress string) common.Address + GetEVMAddressFromBech32OrDefault(ctx sdk.Context, seiAddress string) (common.Address, error) GetCodeHash(sdk.Context, common.Address) common.Hash IsCodeHashWhitelistedForDelegateCall(ctx sdk.Context, h common.Hash) bool IsCodeHashWhitelistedForBankSend(ctx sdk.Context, h common.Hash) bool diff --git a/precompiles/pointer/pointer.go b/precompiles/pointer/pointer.go index be607b5fc..0a2bc27c2 100644 --- a/precompiles/pointer/pointer.go +++ b/precompiles/pointer/pointer.go @@ -171,7 +171,11 @@ func (p Precompile) AddCW20(ctx sdk.Context, method *ethabi.Method, caller commo if exists && existingVersion >= cw20.CurrentVersion { return nil, 0, fmt.Errorf("pointer at %s with version %d exists when trying to set pointer for version %d", existingAddr.Hex(), existingVersion, cw20.CurrentVersion) } - res, err := p.wasmdKeeper.QuerySmart(ctx, sdk.MustAccAddressFromBech32(cwAddr), []byte("{\"token_info\":{}}")) + 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 } @@ -214,7 +218,11 @@ func (p Precompile) AddCW721(ctx sdk.Context, method *ethabi.Method, caller comm if exists && existingVersion >= cw721.CurrentVersion { return nil, 0, fmt.Errorf("pointer at %s with version %d exists when trying to set pointer for version %d", existingAddr.Hex(), existingVersion, cw721.CurrentVersion) } - res, err := p.wasmdKeeper.QuerySmart(ctx, sdk.MustAccAddressFromBech32(cwAddr), []byte("{\"contract_info\":{}}")) + 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 } diff --git a/x/evm/client/wasm/query.go b/x/evm/client/wasm/query.go index 77418f40a..8e94e306b 100644 --- a/x/evm/client/wasm/query.go +++ b/x/evm/client/wasm/query.go @@ -23,7 +23,10 @@ func NewEVMQueryHandler(k *keeper.Keeper) *EVMQueryHandler { } func (h *EVMQueryHandler) HandleStaticCall(ctx sdk.Context, from string, to string, data []byte) ([]byte, error) { - fromAddr := sdk.MustAccAddressFromBech32(from) + fromAddr, err := sdk.AccAddressFromBech32(from) + if err != nil { + return nil, err + } var toAddr *common.Address if to != "" { toSeiAddr := common.HexToAddress(to) @@ -37,7 +40,10 @@ func (h *EVMQueryHandler) HandleERC20TransferPayload(ctx sdk.Context, recipient if err != nil { return nil, err } - evmAddr := h.k.GetEVMAddressFromBech32OrDefault(ctx, recipient) + evmAddr, err := h.k.GetEVMAddressFromBech32OrDefault(ctx, recipient) + if err != nil { + return nil, err + } bz, err := abi.Pack("transfer", evmAddr, amount.BigInt()) if err != nil { return nil, err @@ -186,8 +192,14 @@ func (h *EVMQueryHandler) HandleERC721TransferPayload(ctx sdk.Context, from stri if err != nil { return nil, err } - fromEvmAddr := h.k.GetEVMAddressFromBech32OrDefault(ctx, from) - toEvmAddr := h.k.GetEVMAddressFromBech32OrDefault(ctx, recipient) + fromEvmAddr, err := h.k.GetEVMAddressFromBech32OrDefault(ctx, from) + if err != nil { + return nil, err + } + toEvmAddr, err := h.k.GetEVMAddressFromBech32OrDefault(ctx, recipient) + if err != nil { + return nil, err + } t, ok := sdk.NewIntFromString(tokenId) if !ok { return nil, errors.New("invalid token ID for ERC20, must be a big Int") @@ -202,8 +214,12 @@ func (h *EVMQueryHandler) HandleERC721TransferPayload(ctx sdk.Context, from stri func (h *EVMQueryHandler) HandleERC721ApprovePayload(ctx sdk.Context, spender string, tokenId string) ([]byte, error) { spenderEvmAddr := common.Address{} // empty address if approval should be revoked (i.e. spender string is empty) + var err error if spender != "" { - spenderEvmAddr = h.k.GetEVMAddressFromBech32OrDefault(ctx, spender) + spenderEvmAddr, err = h.k.GetEVMAddressFromBech32OrDefault(ctx, spender) + if err != nil { + return nil, err + } } abi, err := cw721.Cw721MetaData.GetAbi() if err != nil { @@ -222,7 +238,10 @@ func (h *EVMQueryHandler) HandleERC721ApprovePayload(ctx sdk.Context, spender st } func (h *EVMQueryHandler) HandleERC721SetApprovalAllPayload(ctx sdk.Context, to string, approved bool) ([]byte, error) { - evmAddr := h.k.GetEVMAddressFromBech32OrDefault(ctx, to) + evmAddr, err := h.k.GetEVMAddressFromBech32OrDefault(ctx, to) + if err != nil { + return nil, err + } abi, err := cw721.Cw721MetaData.GetAbi() if err != nil { return nil, err @@ -240,8 +259,14 @@ func (h *EVMQueryHandler) HandleERC20TransferFromPayload(ctx sdk.Context, owner if err != nil { return nil, err } - ownerEvmAddr := h.k.GetEVMAddressFromBech32OrDefault(ctx, owner) - recipientEvmAddr := h.k.GetEVMAddressFromBech32OrDefault(ctx, recipient) + ownerEvmAddr, err := h.k.GetEVMAddressFromBech32OrDefault(ctx, owner) + if err != nil { + return nil, err + } + recipientEvmAddr, err := h.k.GetEVMAddressFromBech32OrDefault(ctx, recipient) + if err != nil { + return nil, err + } bz, err := abi.Pack("transferFrom", ownerEvmAddr, recipientEvmAddr, amount.BigInt()) if err != nil { return nil, err @@ -255,7 +280,10 @@ func (h *EVMQueryHandler) HandleERC20ApprovePayload(ctx sdk.Context, spender str if err != nil { return nil, err } - spenderEvmAddr := h.k.GetEVMAddressFromBech32OrDefault(ctx, spender) + spenderEvmAddr, err := h.k.GetEVMAddressFromBech32OrDefault(ctx, spender) + if err != nil { + return nil, err + } bz, err := abi.Pack("approve", spenderEvmAddr, amount.BigInt()) if err != nil { @@ -267,11 +295,17 @@ func (h *EVMQueryHandler) HandleERC20ApprovePayload(ctx sdk.Context, spender str func (h *EVMQueryHandler) HandleERC20Allowance(ctx sdk.Context, contractAddress string, owner string, spender string) ([]byte, error) { // Get the evm address of the owner - ownerAddr := sdk.MustAccAddressFromBech32(owner) + ownerAddr, err := sdk.AccAddressFromBech32(owner) + if err != nil { + return nil, err + } ownerEvmAddr := h.k.GetEVMAddressOrDefault(ctx, ownerAddr) // Get the evm address of spender - spenderEvmAddr := h.k.GetEVMAddressFromBech32OrDefault(ctx, spender) + spenderEvmAddr, err := h.k.GetEVMAddressFromBech32OrDefault(ctx, spender) + if err != nil { + return nil, err + } // Fetch the contract ABI contract := common.HexToAddress(contractAddress) @@ -341,8 +375,14 @@ func (h *EVMQueryHandler) HandleERC721IsApprovedForAll(ctx sdk.Context, caller s if err != nil { return nil, err } - ownerEvmAddr := h.k.GetEVMAddressFromBech32OrDefault(ctx, owner) - operatorEvmAddr := h.k.GetEVMAddressFromBech32OrDefault(ctx, operator) + ownerEvmAddr, err := h.k.GetEVMAddressFromBech32OrDefault(ctx, owner) + if err != nil { + return nil, err + } + operatorEvmAddr, err := h.k.GetEVMAddressFromBech32OrDefault(ctx, operator) + if err != nil { + return nil, err + } contract := common.HexToAddress(contractAddress) abi, err := cw721.Cw721MetaData.GetAbi() if err != nil { diff --git a/x/evm/keeper/address.go b/x/evm/keeper/address.go index 685d16d45..0049ca711 100644 --- a/x/evm/keeper/address.go +++ b/x/evm/keeper/address.go @@ -45,8 +45,12 @@ func (k *Keeper) GetEVMAddressOrDefault(ctx sdk.Context, seiAddress sdk.AccAddre return common.BytesToAddress(seiAddress) } -func (k *Keeper) GetEVMAddressFromBech32OrDefault(ctx sdk.Context, seiAddress string) common.Address { - return k.GetEVMAddressOrDefault(ctx, sdk.MustAccAddressFromBech32(seiAddress)) +func (k *Keeper) GetEVMAddressFromBech32OrDefault(ctx sdk.Context, seiAddress string) (common.Address, error) { + seiAddr, err := sdk.AccAddressFromBech32(seiAddress) + if err != nil { + return common.Address{}, err + } + return k.GetEVMAddressOrDefault(ctx, seiAddr), nil } func (k *Keeper) GetSeiAddress(ctx sdk.Context, evmAddress common.Address) (sdk.AccAddress, bool) { diff --git a/x/evm/keeper/evm.go b/x/evm/keeper/evm.go index 692247359..a32e10859 100644 --- a/x/evm/keeper/evm.go +++ b/x/evm/keeper/evm.go @@ -23,7 +23,11 @@ func (k *Keeper) HandleInternalEVMCall(ctx sdk.Context, req *types.MsgInternalEV addr := common.HexToAddress(req.To) to = &addr } - ret, err := k.CallEVM(ctx, sdk.MustAccAddressFromBech32(req.Sender), to, req.Value, req.Data) + senderAddr, err := sdk.AccAddressFromBech32(req.Sender) + if err != nil { + return nil, err + } + ret, err := k.CallEVM(ctx, senderAddr, to, req.Value, req.Data) if err != nil { return nil, err } @@ -40,7 +44,11 @@ func (k *Keeper) HandleInternalEVMDelegateCall(ctx sdk.Context, req *types.MsgIn to = &addr } zeroInt := sdk.ZeroInt() - ret, err := k.CallEVM(ctx, sdk.MustAccAddressFromBech32(req.Sender), to, &zeroInt, req.Data) + senderAddr, err := sdk.AccAddressFromBech32(req.Sender) + if err != nil { + return nil, err + } + ret, err := k.CallEVM(ctx, senderAddr, to, &zeroInt, req.Data) if err != nil { return nil, err } diff --git a/x/evm/keeper/grpc_query.go b/x/evm/keeper/grpc_query.go index a94393cd2..3c0905ef0 100644 --- a/x/evm/keeper/grpc_query.go +++ b/x/evm/keeper/grpc_query.go @@ -41,7 +41,11 @@ func (q Querier) EVMAddressBySeiAddress(c context.Context, req *types.QueryEVMAd if req.SeiAddress == "" { return nil, sdkerrors.ErrInvalidRequest } - addr, found := q.Keeper.GetEVMAddress(ctx, sdk.MustAccAddressFromBech32(req.SeiAddress)) + seiAddr, err := sdk.AccAddressFromBech32(req.SeiAddress) + if err != nil { + return nil, err + } + addr, found := q.Keeper.GetEVMAddress(ctx, seiAddr) if !found { return &types.QueryEVMAddressBySeiAddressResponse{Associated: false}, nil } diff --git a/x/evm/types/message_internal_evm_call.go b/x/evm/types/message_internal_evm_call.go index e82bd99e5..83e33eb7c 100644 --- a/x/evm/types/message_internal_evm_call.go +++ b/x/evm/types/message_internal_evm_call.go @@ -18,7 +18,11 @@ func NewMessageInternalEVMCall(from sdk.AccAddress, to string, value *sdk.Int, d } func (msg *MsgInternalEVMCall) GetSigners() []sdk.AccAddress { - return []sdk.AccAddress{sdk.MustAccAddressFromBech32(msg.Sender)} + senderAddr, err := sdk.AccAddressFromBech32(msg.Sender) + if err != nil { + return []sdk.AccAddress{} + } + return []sdk.AccAddress{senderAddr} } func (msg *MsgInternalEVMCall) ValidateBasic() error { diff --git a/x/evm/types/message_internal_evm_delegate_call.go b/x/evm/types/message_internal_evm_delegate_call.go index f8c21738e..c38cb6042 100644 --- a/x/evm/types/message_internal_evm_delegate_call.go +++ b/x/evm/types/message_internal_evm_delegate_call.go @@ -19,7 +19,11 @@ func NewMessageInternalEVMDelegateCall(from sdk.AccAddress, to string, codeHash } func (msg *MsgInternalEVMDelegateCall) GetSigners() []sdk.AccAddress { - return []sdk.AccAddress{sdk.MustAccAddressFromBech32(msg.FromContract)} + contractAddr, err := sdk.AccAddressFromBech32(msg.FromContract) + if err != nil { + return []sdk.AccAddress{} + } + return []sdk.AccAddress{contractAddr} } func (msg *MsgInternalEVMDelegateCall) ValidateBasic() error { From cc2d088481bfb010d3769b10aa4bf521d89ad051 Mon Sep 17 00:00:00 2001 From: codchen Date: Sun, 7 Apr 2024 12:23:33 +0800 Subject: [PATCH 21/34] Fix Int rounding issues (#1514) --- evmrpc/block.go | 2 +- evmrpc/info.go | 4 ++-- precompiles/common/precompiles.go | 2 +- precompiles/ibc/ibc.go | 2 +- precompiles/wasmd/wasmd.go | 2 +- x/evm/ante/fee.go | 6 +++--- x/evm/ante/fee_test.go | 2 +- x/evm/ante/gas.go | 2 +- x/evm/keeper/evm.go | 6 +++--- x/evm/keeper/keeper.go | 4 ++-- x/evm/keeper/msg_server.go | 2 +- 11 files changed, 17 insertions(+), 17 deletions(-) diff --git a/evmrpc/block.go b/evmrpc/block.go index fc6b671a5..246d2726b 100644 --- a/evmrpc/block.go +++ b/evmrpc/block.go @@ -208,7 +208,7 @@ func EncodeTmBlock( "size": hexutil.Uint64(block.Block.Size()), "uncles": []common.Hash{}, // inapplicable to Sei "transactions": transactions, - "baseFeePerGas": (*hexutil.Big)(k.GetBaseFeePerGas(ctx).RoundInt().BigInt()), + "baseFeePerGas": (*hexutil.Big)(k.GetBaseFeePerGas(ctx).TruncateInt().BigInt()), } if fullTx { result["totalDifficulty"] = (*hexutil.Big)(big.NewInt(0)) // inapplicable to Sei diff --git a/evmrpc/info.go b/evmrpc/info.go index 6b268d805..e165cc3cf 100644 --- a/evmrpc/info.go +++ b/evmrpc/info.go @@ -78,11 +78,11 @@ func (i *InfoAPI) GasPrice(ctx context.Context) (result *hexutil.Big, returnErr } if len(feeHist.Reward) == 0 || len(feeHist.Reward[0]) == 0 { // if there is no EVM tx in the most recent block, return the minimum fee param - return (*hexutil.Big)(i.keeper.GetMinimumFeePerGas(i.ctxProvider(LatestCtxHeight)).RoundInt().BigInt()), nil + return (*hexutil.Big)(i.keeper.GetMinimumFeePerGas(i.ctxProvider(LatestCtxHeight)).TruncateInt().BigInt()), nil } return (*hexutil.Big)(new(big.Int).Add( feeHist.Reward[0][0].ToInt(), - i.keeper.GetBaseFeePerGas(i.ctxProvider(LatestCtxHeight)).RoundInt().BigInt(), + i.keeper.GetBaseFeePerGas(i.ctxProvider(LatestCtxHeight)).TruncateInt().BigInt(), )), nil } diff --git a/precompiles/common/precompiles.go b/precompiles/common/precompiles.go index f305bb656..620fe624e 100644 --- a/precompiles/common/precompiles.go +++ b/precompiles/common/precompiles.go @@ -95,7 +95,7 @@ func HandlePaymentUseiWei(ctx sdk.Context, precompileAddr sdk.AccAddress, payer func GetRemainingGas(ctx sdk.Context, evmKeeper EVMKeeper) uint64 { gasMultipler := evmKeeper.GetPriorityNormalizer(ctx) seiGasRemaining := ctx.GasMeter().Limit() - ctx.GasMeter().GasConsumedToLimit() - return new(big.Int).Mul(new(big.Int).SetUint64(seiGasRemaining), gasMultipler.RoundInt().BigInt()).Uint64() + return sdk.NewDecFromInt(sdk.NewIntFromUint64(seiGasRemaining)).Mul(gasMultipler).TruncateInt().Uint64() } func ValidateCaller(ctx sdk.Context, evmKeeper EVMKeeper, caller common.Address, callingContract common.Address) error { diff --git a/precompiles/ibc/ibc.go b/precompiles/ibc/ibc.go index a446ceac4..87ca36aaf 100644 --- a/precompiles/ibc/ibc.go +++ b/precompiles/ibc/ibc.go @@ -102,7 +102,7 @@ func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, calli } gasMultiplier := p.evmKeeper.GetPriorityNormalizer(ctx) - gasLimitBigInt := new(big.Int).Mul(new(big.Int).SetUint64(suppliedGas), gasMultiplier.RoundInt().BigInt()) + gasLimitBigInt := new(big.Int).Mul(new(big.Int).SetUint64(suppliedGas), gasMultiplier.TruncateInt().BigInt()) if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { gasLimitBigInt = utils.BigMaxU64 } diff --git a/precompiles/wasmd/wasmd.go b/precompiles/wasmd/wasmd.go index 572ae618a..320778e3b 100644 --- a/precompiles/wasmd/wasmd.go +++ b/precompiles/wasmd/wasmd.go @@ -119,7 +119,7 @@ func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, calli return nil, 0, err } gasMultipler := p.evmKeeper.GetPriorityNormalizer(ctx) - gasLimitBigInt := new(big.Int).Mul(new(big.Int).SetUint64(suppliedGas), gasMultipler.RoundInt().BigInt()) + gasLimitBigInt := sdk.NewDecFromInt(sdk.NewIntFromUint64(suppliedGas)).Mul(gasMultipler).TruncateInt().BigInt() if gasLimitBigInt.Cmp(utils.BigMaxU64) > 0 { gasLimitBigInt = utils.BigMaxU64 } diff --git a/x/evm/ante/fee.go b/x/evm/ante/fee.go index af408219b..a3210b3f2 100644 --- a/x/evm/ante/fee.go +++ b/x/evm/ante/fee.go @@ -73,18 +73,18 @@ func (fc EVMFeeCheckDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate b // fee per gas to be burnt func (fc EVMFeeCheckDecorator) getBaseFee(ctx sdk.Context) *big.Int { - return fc.evmKeeper.GetBaseFeePerGas(ctx).RoundInt().BigInt() + return fc.evmKeeper.GetBaseFeePerGas(ctx).TruncateInt().BigInt() } // lowest allowed fee per gas func (fc EVMFeeCheckDecorator) getMinimumFee(ctx sdk.Context) *big.Int { - return fc.evmKeeper.GetMinimumFeePerGas(ctx).RoundInt().BigInt() + return fc.evmKeeper.GetMinimumFeePerGas(ctx).TruncateInt().BigInt() } // CalculatePriority returns a priority based on the effective gas price of the transaction func (fc EVMFeeCheckDecorator) CalculatePriority(ctx sdk.Context, txData ethtx.TxData) *big.Int { gp := txData.EffectiveGasPrice(utils.Big0) - priority := new(big.Int).Quo(gp, fc.evmKeeper.GetPriorityNormalizer(ctx).RoundInt().BigInt()) + priority := sdk.NewDecFromBigInt(gp).Quo(fc.evmKeeper.GetPriorityNormalizer(ctx)).TruncateInt().BigInt() if priority.Cmp(big.NewInt(antedecorators.MaxPriority)) > 0 { priority = big.NewInt(antedecorators.MaxPriority) } diff --git a/x/evm/ante/fee_test.go b/x/evm/ante/fee_test.go index 733858f42..4fcb3572b 100644 --- a/x/evm/ante/fee_test.go +++ b/x/evm/ante/fee_test.go @@ -61,7 +61,7 @@ func TestEVMFeeCheckDecoratorCancun(t *testing.T) { }) require.NotNil(t, err) - txData.GasFeeCap = k.GetMinimumFeePerGas(ctx).RoundInt().BigInt() + txData.GasFeeCap = k.GetMinimumFeePerGas(ctx).TruncateInt().BigInt() tx, err = ethtypes.SignTx(ethtypes.NewTx(&txData), signer, key) require.Nil(t, err) typedTx, err = ethtx.NewDynamicFeeTx(tx) diff --git a/x/evm/ante/gas.go b/x/evm/ante/gas.go index 17f528443..5a04f2f51 100644 --- a/x/evm/ante/gas.go +++ b/x/evm/ante/gas.go @@ -23,6 +23,6 @@ func (gl GasLimitDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool } adjustedGasLimit := gl.evmKeeper.GetPriorityNormalizer(ctx).MulInt64(int64(txData.GetGas())) - ctx = ctx.WithGasMeter(sdk.NewGasMeter(adjustedGasLimit.RoundInt().Uint64())) + ctx = ctx.WithGasMeter(sdk.NewGasMeter(adjustedGasLimit.TruncateInt().Uint64())) return next(ctx, tx, simulate) } diff --git a/x/evm/keeper/evm.go b/x/evm/keeper/evm.go index a32e10859..f7d016714 100644 --- a/x/evm/keeper/evm.go +++ b/x/evm/keeper/evm.go @@ -105,8 +105,8 @@ func (k *Keeper) callEVM(ctx sdk.Context, from sdk.AccAddress, to *common.Addres // infinite gas meter (used in queries) seiGasRemaining = math.MaxUint64 } - multiplier := k.GetPriorityNormalizer(ctx).RoundInt().BigInt() - evmGasRemaining := new(big.Int).Quo(new(big.Int).SetUint64(seiGasRemaining), multiplier) + multiplier := k.GetPriorityNormalizer(ctx) + evmGasRemaining := sdk.NewDecFromInt(sdk.NewIntFromUint64(seiGasRemaining)).Quo(multiplier).TruncateInt().BigInt() if evmGasRemaining.Cmp(MaxUint64BigInt) > 0 { evmGasRemaining = MaxUint64BigInt } @@ -115,7 +115,7 @@ func (k *Keeper) callEVM(ctx sdk.Context, from sdk.AccAddress, to *common.Addres value = val.BigInt() } ret, leftoverGas, err := f(vm.AccountRef(sender), to, data, evmGasRemaining.Uint64(), value) - ctx.GasMeter().ConsumeGas(ctx.GasMeter().Limit()-new(big.Int).Mul(new(big.Int).SetUint64(leftoverGas), multiplier).Uint64(), "call EVM") + ctx.GasMeter().ConsumeGas(ctx.GasMeter().Limit()-sdk.NewDecFromInt(sdk.NewIntFromUint64(leftoverGas)).Mul(multiplier).TruncateInt().Uint64(), "call EVM") if err != nil { return nil, err } diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index f17c69d04..b2ca1c73f 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -188,8 +188,8 @@ func (k *Keeper) GetVMBlockContext(ctx sdk.Context, gp core.GasPool) (*vm.BlockC GasLimit: gp.Gas(), BlockNumber: big.NewInt(ctx.BlockHeight()), Time: uint64(ctx.BlockHeader().Time.Unix()), - Difficulty: utils.Big0, // only needed for PoW - BaseFee: k.GetBaseFeePerGas(ctx).RoundInt().BigInt(), // feemarket not enabled + Difficulty: utils.Big0, // only needed for PoW + BaseFee: k.GetBaseFeePerGas(ctx).TruncateInt().BigInt(), // feemarket not enabled Random: &rh, }, nil } diff --git a/x/evm/keeper/msg_server.go b/x/evm/keeper/msg_server.go index 04b3d5064..554efb0c8 100644 --- a/x/evm/keeper/msg_server.go +++ b/x/evm/keeper/msg_server.go @@ -124,7 +124,7 @@ func (server msgServer) EVMTransaction(goCtx context.Context, msg *types.MsgEVMT // to Sei transactions' priority, which is based on gas limit in // Sei unit, so we use the same coefficient to convert gas unit here. adjustedGasUsed := server.GetPriorityNormalizer(ctx).MulInt64(int64(serverRes.GasUsed)) - originalGasMeter.ConsumeGas(adjustedGasUsed.RoundInt().Uint64(), "evm transaction") + originalGasMeter.ConsumeGas(adjustedGasUsed.TruncateInt().Uint64(), "evm transaction") }() res, applyErr := server.applyEVMMessage(ctx, emsg, stateDB, gp) From 424c8d90be4a0e185e85718929cf9f41da47897b Mon Sep 17 00:00:00 2001 From: codchen Date: Sun, 7 Apr 2024 12:48:22 +0800 Subject: [PATCH 22/34] Clear self-destructed account state at the end of tx (#1513) --- x/evm/state/state.go | 12 +++++++++--- x/evm/state/state_test.go | 11 ++++++++--- x/evm/state/statedb.go | 4 ++++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/x/evm/state/state.go b/x/evm/state/state.go index 5856c8593..7405c5818 100644 --- a/x/evm/state/state.go +++ b/x/evm/state/state.go @@ -70,9 +70,6 @@ func (s *DBImpl) SelfDestruct(acc common.Address) { s.SubBalance(acc, s.GetBalance(acc), tracing.BalanceDecreaseSelfdestruct) - // clear account state - s.clearAccountState(acc) - // mark account as self-destructed s.MarkAccount(acc, AccountDeleted) } @@ -111,6 +108,15 @@ func (s *DBImpl) RevertToSnapshot(rev int) { s.Snapshot() } +func (s *DBImpl) clearAccountStateIfDestructed(st *TemporaryState) { + for acc, status := range st.transientAccounts { + if !bytes.Equal(status, AccountDeleted) { + return + } + s.clearAccountState(common.HexToAddress(acc)) + } +} + func (s *DBImpl) clearAccountState(acc common.Address) { s.k.PrepareReplayedAddr(s.ctx, acc) s.k.PurgePrefix(s.ctx, types.StateKey(acc)) diff --git a/x/evm/state/state_test.go b/x/evm/state/state_test.go index 84f1a9c36..fe4cc57ff 100644 --- a/x/evm/state/state_test.go +++ b/x/evm/state/state_test.go @@ -37,13 +37,16 @@ func TestState(t *testing.T) { tval := common.BytesToHash([]byte("mno")) statedb.SetTransientState(evmAddr, tkey, tval) require.Equal(t, tval, statedb.GetTransientState(evmAddr, tkey)) - // destruct should clear balance and state, but keep transient state. Committed state should also be accessible + // destruct should clear balance, but keep state. Committed state should also be accessible + // state would be cleared after finalize statedb.SelfDestruct(evmAddr) require.Equal(t, tval, statedb.GetTransientState(evmAddr, tkey)) - require.Equal(t, common.Hash{}, statedb.GetState(evmAddr, key)) + require.NotEqual(t, common.Hash{}, statedb.GetState(evmAddr, key)) require.Equal(t, common.Hash{}, statedb.GetCommittedState(evmAddr, key)) require.Equal(t, big.NewInt(0), statedb.GetBalance(evmAddr)) require.True(t, statedb.HasSelfDestructed(evmAddr)) + statedb.Finalize() + require.Equal(t, common.Hash{}, statedb.GetState(evmAddr, key)) // set storage statedb.SetStorage(evmAddr, map[common.Hash]common.Hash{{}: {}}) require.Equal(t, common.Hash{}, statedb.GetState(evmAddr, common.Hash{})) @@ -108,11 +111,13 @@ func TestSelfDestructAssociated(t *testing.T) { // Selfdestruct6780 is equivalent to SelfDestruct if account is created in the same block statedb.Selfdestruct6780(evmAddr) require.Equal(t, tval, statedb.GetTransientState(evmAddr, tkey)) - require.Equal(t, common.Hash{}, statedb.GetState(evmAddr, key)) + require.NotEqual(t, common.Hash{}, statedb.GetState(evmAddr, key)) require.Equal(t, big.NewInt(0), statedb.GetBalance(evmAddr)) require.Equal(t, big.NewInt(0), k.BankKeeper().GetBalance(ctx, seiAddr, k.GetBaseDenom(ctx)).Amount.BigInt()) require.True(t, statedb.HasSelfDestructed(evmAddr)) require.False(t, statedb.Created(evmAddr)) + statedb.Finalize() + require.Equal(t, common.Hash{}, statedb.GetState(evmAddr, key)) // association should also be removed _, ok := k.GetSeiAddress(statedb.Ctx(), evmAddr) require.False(t, ok) diff --git a/x/evm/state/statedb.go b/x/evm/state/statedb.go index dcf818781..5ffe9bac1 100644 --- a/x/evm/state/statedb.go +++ b/x/evm/state/statedb.go @@ -78,9 +78,13 @@ func (s *DBImpl) Finalize() (surplus sdk.Int, err error) { for i := len(s.snapshottedCtxs) - 1; i > 0; i-- { s.flushCtx(s.snapshottedCtxs[i]) } + + // delete state of self-destructed accoutns + s.clearAccountStateIfDestructed(s.tempStateCurrent) surplus = s.tempStateCurrent.surplus for _, ts := range s.tempStatesHist { surplus = surplus.Add(ts.surplus) + s.clearAccountStateIfDestructed(ts) } if surplus.IsNegative() { err = fmt.Errorf("negative surplus value: %s", surplus.String()) From 54b90cf1aacac4df1b5c649a10bc7d34f2afbef5 Mon Sep 17 00:00:00 2001 From: codchen Date: Mon, 8 Apr 2024 10:23:11 +0800 Subject: [PATCH 23/34] Write receipt for txs with non VM errors (#1512) --- x/evm/keeper/keeper.go | 11 ++++++++++- x/evm/keeper/msg_server.go | 1 + x/evm/module.go | 8 ++++++++ x/evm/module_test.go | 9 +++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index b2ca1c73f..baa50337d 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -81,6 +81,7 @@ type EvmTxDeferredInfo struct { TxHash common.Hash TxBloom ethtypes.Bloom Surplus sdk.Int + Error string } type AddressNoncePair struct { @@ -222,7 +223,7 @@ func (k *Keeper) GetEVMTxDeferredInfo(ctx sdk.Context) (res []EvmTxDeferredInfo) ctx.Logger().Error(fmt.Sprintf("getting invalid tx index in EVM deferred info: %d, num of txs: %d", txIdx, len(k.txResults))) return true } - if k.txResults[txIdx].Code == 0 { + if k.txResults[txIdx].Code == 0 || value.(*EvmTxDeferredInfo).Error != "" { res = append(res, *(value.(*EvmTxDeferredInfo))) } return true @@ -240,6 +241,14 @@ func (k *Keeper) AppendToEvmTxDeferredInfo(ctx sdk.Context, bloom ethtypes.Bloom }) } +func (k *Keeper) AppendErrorToEvmTxDeferredInfo(ctx sdk.Context, txHash common.Hash, err string) { + k.deferredInfo.Store(ctx.TxIndex(), &EvmTxDeferredInfo{ + TxIndx: ctx.TxIndex(), + TxHash: txHash, + Error: err, + }) +} + func (k *Keeper) ClearEVMTxDeferredInfo() { k.deferredInfo = &sync.Map{} } diff --git a/x/evm/keeper/msg_server.go b/x/evm/keeper/msg_server.go index 554efb0c8..cfb25b10c 100644 --- a/x/evm/keeper/msg_server.go +++ b/x/evm/keeper/msg_server.go @@ -68,6 +68,7 @@ func (server msgServer) EVMTransaction(goCtx context.Context, msg *types.MsgEVMT debug.PrintStack() ctx.Logger().Error(fmt.Sprintf("EVM PANIC: %s", pe)) telemetry.IncrCounter(1, types.ModuleName, "panics") + server.AppendErrorToEvmTxDeferredInfo(ctx, tx.Hash(), fmt.Sprintf("%s", pe)) panic(pe) } diff --git a/x/evm/module.go b/x/evm/module.go index f3585b60c..208038fdb 100644 --- a/x/evm/module.go +++ b/x/evm/module.go @@ -215,6 +215,14 @@ func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.Val denom := am.keeper.GetBaseDenom(ctx) surplus := utils.Sdk0 for _, deferredInfo := range evmTxDeferredInfoList { + if deferredInfo.Error != "" { + _ = am.keeper.SetReceipt(ctx, deferredInfo.TxHash, &types.Receipt{ + TxHashHex: deferredInfo.TxHash.Hex(), + TransactionIndex: uint32(deferredInfo.TxIndx), + VmError: deferredInfo.Error, + }) + continue + } idx := deferredInfo.TxIndx coinbaseAddress := state.GetCoinbaseAddress(idx) balance := am.keeper.BankKeeper().GetBalance(ctx, coinbaseAddress, denom) diff --git a/x/evm/module_test.go b/x/evm/module_test.go index ea9dd2b25..108603d85 100644 --- a/x/evm/module_test.go +++ b/x/evm/module_test.go @@ -65,4 +65,13 @@ func TestABCI(t *testing.T) { m.EndBlock(ctx, abci.RequestEndBlock{}) require.Equal(t, uint64(1), k.BankKeeper().GetBalance(ctx, k.AccountKeeper().GetModuleAddress(types.ModuleName), "usei").Amount.Uint64()) require.Equal(t, uint64(2), k.BankKeeper().GetBalance(ctx, k.AccountKeeper().GetModuleAddress(authtypes.FeeCollectorName), "usei").Amount.Uint64()) + + // third block + m.BeginBlock(ctx, abci.RequestBeginBlock{}) + k.AppendErrorToEvmTxDeferredInfo(ctx.WithTxIndex(0), common.Hash{1}, "test error") + k.SetTxResults([]*abci.ExecTxResult{{Code: 1}}) + m.EndBlock(ctx, abci.RequestEndBlock{}) + receipt, err := k.GetReceipt(ctx, common.Hash{1}) + require.Nil(t, err) + require.Equal(t, receipt.VmError, "test error") } From 91866013eecd7e03782b42f61ab1a913cdac8bd7 Mon Sep 17 00:00:00 2001 From: codchen Date: Mon, 8 Apr 2024 14:26:37 +0800 Subject: [PATCH 24/34] Use registry instead of code hash to determine precompile whitelist (#1506) --- contracts/test/EVMPrecompileTester.js | 2 + contracts/test/deploy_atom_erc20.sh | 6 +- docker/localnode/scripts/step2_genesis.sh | 1 + precompiles/bank/bank.go | 19 ++--- precompiles/common/expected_keepers.go | 2 - precompiles/common/precompiles.go | 13 ---- precompiles/ibc/ibc.go | 8 +- precompiles/ibc/ibc_test.go | 11 +-- precompiles/pointer/pointer.go | 4 +- precompiles/pointer/pointer_test.go | 6 +- precompiles/wasmd/wasmd.go | 18 +++-- precompiles/wasmd/wasmd_test.go | 33 ++++----- scripts/initialize_local_chain.sh | 1 + x/evm/artifacts/native/artifacts_test.go | 1 + x/evm/keeper/evm.go | 7 +- x/evm/keeper/evm_test.go | 1 + x/evm/keeper/msg_server.go | 3 - x/evm/keeper/msg_server_test.go | 3 +- x/evm/keeper/whitelist.go | 89 ----------------------- x/evm/keeper/whitelist_test.go | 27 ------- 20 files changed, 63 insertions(+), 192 deletions(-) delete mode 100644 x/evm/keeper/whitelist_test.go diff --git a/contracts/test/EVMPrecompileTester.js b/contracts/test/EVMPrecompileTester.js index d7dd7d66e..cf16772be 100644 --- a/contracts/test/EVMPrecompileTester.js +++ b/contracts/test/EVMPrecompileTester.js @@ -16,6 +16,8 @@ describe("EVM Test", function () { let signer2 before(async function() { contractAddress = readDeploymentOutput('erc20_deploy_addr.txt'); + console.log("ERC20 address is:"); + console.log(contractAddress); await sleep(1000); // Create a signer diff --git a/contracts/test/deploy_atom_erc20.sh b/contracts/test/deploy_atom_erc20.sh index ceed1766b..f5408f313 100644 --- a/contracts/test/deploy_atom_erc20.sh +++ b/contracts/test/deploy_atom_erc20.sh @@ -16,9 +16,11 @@ printf "12345678\n" | seid tx evm send $owner1 1000000000000000000 --from admin printf "12345678\n" | seid tx evm send $owner2 1000000000000000000 --from admin echo "Deploying ERC20 pointer contract for UATOM..." -deployment_output=$(printf "12345678\n" | seid tx evm deploy-erc20 uatom UATOM UATOM 6 --from admin --evm-rpc=$endpoint) +printf "12345678\n" | seid tx evm call-contract 0x000000000000000000000000000000000000100b c31d960f000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000057561746f6d000000000000000000000000000000000000000000000000000000 --from admin --evm-rpc=$endpoint +sleep 3 +pointer_output=$(seid q evm pointer NATIVE uatom --output json) -erc20_deploy_addr=$(echo "$deployment_output" | grep 'Deployed to:' | awk '{print $3}') +erc20_deploy_addr=$(echo "$pointer_output" | jq .pointer | tr -d '"') echo $erc20_deploy_addr > contracts/erc20_deploy_addr.txt # wait for deployment to finish on live chain diff --git a/docker/localnode/scripts/step2_genesis.sh b/docker/localnode/scripts/step2_genesis.sh index c4f54a3b6..0db68437e 100755 --- a/docker/localnode/scripts/step2_genesis.sh +++ b/docker/localnode/scripts/step2_genesis.sh @@ -33,6 +33,7 @@ override_genesis ".app_state[\"mint\"][\"params\"][\"token_release_schedule\"]=[ override_genesis '.app_state["auth"]["accounts"]=[]' override_genesis '.app_state["bank"]["balances"]=[]' override_genesis '.app_state["genutil"]["gen_txs"]=[]' +override_genesis '.app_state["bank"]["denom_metadata"]=[{"denom_units":[{"denom":"UATOM","exponent":6,"aliases":["UATOM"]}],"base":"uatom","display":"uatom","name":"UATOM","symbol":"UATOM"}]' # gov parameters override_genesis '.app_state["gov"]["deposit_params"]["min_deposit"][0]["denom"]="usei"' diff --git a/precompiles/bank/bank.go b/precompiles/bank/bank.go index 2def02947..cc10a5662 100644 --- a/precompiles/bank/bank.go +++ b/precompiles/bank/bank.go @@ -125,10 +125,7 @@ func (p Precompile) Run(evm *vm.EVM, caller common.Address, input []byte, value switch method.Name { case SendMethod: - if err := p.validateCaller(ctx, caller); err != nil { - return nil, err - } - return p.send(ctx, method, args, value) + return p.send(ctx, caller, method, args, value) case SendNativeMethod: return p.sendNative(ctx, method, args, caller, value) case BalanceMethod: @@ -145,15 +142,7 @@ func (p Precompile) Run(evm *vm.EVM, caller common.Address, input []byte, value return } -func (p Precompile) validateCaller(ctx sdk.Context, caller common.Address) error { - codeHash := p.evmKeeper.GetCodeHash(ctx, caller) - if p.evmKeeper.IsCodeHashWhitelistedForBankSend(ctx, codeHash) { - return nil - } - return fmt.Errorf("caller %s with code hash %s is not whitelisted for arbitrary bank send", caller.Hex(), codeHash.Hex()) -} - -func (p Precompile) send(ctx sdk.Context, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { +func (p Precompile) send(ctx sdk.Context, caller common.Address, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { if err := pcommon.ValidateNonPayable(value); err != nil { return nil, err } @@ -165,6 +154,10 @@ func (p Precompile) send(ctx sdk.Context, method *abi.Method, args []interface{} 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 diff --git a/precompiles/common/expected_keepers.go b/precompiles/common/expected_keepers.go index 5779955ba..f0525a1b2 100644 --- a/precompiles/common/expected_keepers.go +++ b/precompiles/common/expected_keepers.go @@ -27,8 +27,6 @@ type EVMKeeper interface { GetEVMAddress(sdk.Context, sdk.AccAddress) (common.Address, bool) GetEVMAddressFromBech32OrDefault(ctx sdk.Context, seiAddress string) (common.Address, error) GetCodeHash(sdk.Context, common.Address) common.Hash - IsCodeHashWhitelistedForDelegateCall(ctx sdk.Context, h common.Hash) bool - IsCodeHashWhitelistedForBankSend(ctx sdk.Context, h common.Hash) bool GetPriorityNormalizer(ctx sdk.Context) sdk.Dec GetBaseDenom(ctx sdk.Context) string SetERC20NativePointer(ctx sdk.Context, token string, addr common.Address) error diff --git a/precompiles/common/precompiles.go b/precompiles/common/precompiles.go index 620fe624e..472f5d50a 100644 --- a/precompiles/common/precompiles.go +++ b/precompiles/common/precompiles.go @@ -8,7 +8,6 @@ import ( 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/vm" "github.com/sei-protocol/sei-chain/x/evm/state" ) @@ -98,18 +97,6 @@ func GetRemainingGas(ctx sdk.Context, evmKeeper EVMKeeper) uint64 { return sdk.NewDecFromInt(sdk.NewIntFromUint64(seiGasRemaining)).Mul(gasMultipler).TruncateInt().Uint64() } -func ValidateCaller(ctx sdk.Context, evmKeeper EVMKeeper, caller common.Address, callingContract common.Address) error { - if caller == callingContract { - // not a delegate call - return nil - } - codeHash := evmKeeper.GetCodeHash(ctx, callingContract) - if evmKeeper.IsCodeHashWhitelistedForDelegateCall(ctx, codeHash) { - return nil - } - return fmt.Errorf("calling contract %s with code hash %s is not whitelisted for delegate calls", callingContract.Hex(), codeHash.Hex()) -} - func ExtractMethodID(input []byte) ([]byte, error) { // Check if the input has at least the length needed for methodID if len(input) < 4 { diff --git a/precompiles/ibc/ibc.go b/precompiles/ibc/ibc.go index 87ca36aaf..9020c4c5f 100644 --- a/precompiles/ibc/ibc.go +++ b/precompiles/ibc/ibc.go @@ -15,6 +15,7 @@ import ( 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" ) @@ -28,6 +29,7 @@ const ( ) var _ vm.PrecompiledContract = &Precompile{} +var _ vm.DynamicGasPrecompiledContract = &Precompile{} // Embed abi json file to the executable binary. Needed when importing as dependency. // @@ -92,13 +94,13 @@ func (p Precompile) RequiredGas(input []byte) uint64 { 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) (ret []byte, remainingGas uint64, err error) { +func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks) (ret []byte, remainingGas uint64, err error) { ctx, method, args, err := p.Prepare(evm, input) if err != nil { return nil, 0, err } - if err = pcommon.ValidateCaller(ctx, p.evmKeeper, caller, callingContract); err != nil { - return nil, 0, err + if caller.Cmp(callingContract) != 0 { + return nil, 0, errors.New("cannot delegatecall IBC") } gasMultiplier := p.evmKeeper.GetPriorityNormalizer(ctx) diff --git a/precompiles/ibc/ibc_test.go b/precompiles/ibc/ibc_test.go index f408a40ee..5a6352307 100644 --- a/precompiles/ibc/ibc_test.go +++ b/precompiles/ibc/ibc_test.go @@ -2,6 +2,10 @@ package ibc_test import ( "errors" + "math/big" + "reflect" + "testing" + sdk "github.com/cosmos/cosmos-sdk/types" clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" "github.com/ethereum/go-ethereum/common" @@ -12,9 +16,6 @@ import ( "github.com/sei-protocol/sei-chain/x/evm/state" "github.com/stretchr/testify/require" tmtypes "github.com/tendermint/tendermint/proto/tendermint/types" - "math/big" - "reflect" - "testing" ) type MockTransferKeeper struct{} @@ -109,7 +110,7 @@ func TestPrecompile_Run(t *testing.T) { args: args{caller: senderEvmAddress, callingContract: common.Address{}, input: commonArgs.input, suppliedGas: 1000000, value: nil}, wantBz: nil, wantErr: true, - wantErrMsg: "calling contract 0x0000000000000000000000000000000000000000 with code hash 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is not whitelisted for delegate calls", + wantErrMsg: "cannot delegatecall IBC", }, { name: "failed transfer: empty sourcePort", @@ -222,7 +223,7 @@ func TestPrecompile_Run(t *testing.T) { tt.args.input.sourcePort, tt.args.input.sourceChannel, tt.args.input.denom, tt.args.input.amount, tt.args.input.revisionNumber, tt.args.input.revisionHeight, tt.args.input.timeoutTimestamp) require.Nil(t, err) - gotBz, gotRemainingGas, err := p.RunAndCalculateGas(&evm, tt.args.caller, tt.args.callingContract, append(p.TransferID, inputs...), tt.args.suppliedGas, tt.args.value) + gotBz, gotRemainingGas, err := p.RunAndCalculateGas(&evm, tt.args.caller, tt.args.callingContract, append(p.TransferID, inputs...), tt.args.suppliedGas, tt.args.value, nil) if (err != nil) != tt.wantErr { t.Errorf("Run() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/precompiles/pointer/pointer.go b/precompiles/pointer/pointer.go index 0a2bc27c2..0a2aceaa3 100644 --- a/precompiles/pointer/pointer.go +++ b/precompiles/pointer/pointer.go @@ -11,6 +11,7 @@ import ( 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" "github.com/sei-protocol/sei-chain/utils" @@ -28,6 +29,7 @@ const ( const PointerAddress = "0x000000000000000000000000000000000000100b" var _ vm.PrecompiledContract = &Precompile{} +var _ vm.DynamicGasPrecompiledContract = &Precompile{} // Embed abi json file to the executable binary. Needed when importing as dependency. // @@ -89,7 +91,7 @@ func (p Precompile) Address() common.Address { return p.address } -func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, _ common.Address, input []byte, suppliedGas uint64, value *big.Int) (ret []byte, remainingGas uint64, err error) { +func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, _ common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks) (ret []byte, remainingGas uint64, err error) { ctx, method, args, err := p.Prepare(evm, input) if err != nil { return nil, 0, err diff --git a/precompiles/pointer/pointer_test.go b/precompiles/pointer/pointer_test.go index 11060b796..23d6b82ae 100644 --- a/precompiles/pointer/pointer_test.go +++ b/precompiles/pointer/pointer_test.go @@ -34,7 +34,7 @@ func TestAddNative(t *testing.T) { statedb := state.NewDBImpl(ctx, &testApp.EvmKeeper, true) blockCtx, _ := testApp.EvmKeeper.GetVMBlockContext(ctx, core.GasPool(suppliedGas)) evm := vm.NewEVM(*blockCtx, vm.TxContext{}, statedb, cfg, vm.Config{}) - _, g, err := p.RunAndCalculateGas(evm, caller, caller, append(p.AddNativePointerID, args...), suppliedGas, nil) + _, g, err := p.RunAndCalculateGas(evm, caller, caller, append(p.AddNativePointerID, args...), suppliedGas, nil, nil) require.NotNil(t, err) require.Equal(t, uint64(0), g) _, _, exists := testApp.EvmKeeper.GetERC20NativePointer(statedb.Ctx(), "test") @@ -53,7 +53,7 @@ func TestAddNative(t *testing.T) { }) statedb = state.NewDBImpl(ctx, &testApp.EvmKeeper, true) evm = vm.NewEVM(*blockCtx, vm.TxContext{}, statedb, cfg, vm.Config{}) - ret, g, err := p.RunAndCalculateGas(evm, caller, caller, append(p.AddNativePointerID, args...), suppliedGas, nil) + ret, g, err := p.RunAndCalculateGas(evm, caller, caller, append(p.AddNativePointerID, args...), suppliedGas, nil, nil) require.Nil(t, err) require.Equal(t, uint64(8907806), g) outputs, err := m.Outputs.Unpack(ret) @@ -67,7 +67,7 @@ func TestAddNative(t *testing.T) { // pointer already exists statedb = state.NewDBImpl(statedb.Ctx(), &testApp.EvmKeeper, true) evm = vm.NewEVM(*blockCtx, vm.TxContext{}, statedb, cfg, vm.Config{}) - _, g, err = p.RunAndCalculateGas(evm, caller, caller, append(p.AddNativePointerID, args...), suppliedGas, nil) + _, g, err = p.RunAndCalculateGas(evm, caller, caller, append(p.AddNativePointerID, args...), suppliedGas, nil, nil) require.NotNil(t, err) require.Equal(t, uint64(0), g) } diff --git a/precompiles/wasmd/wasmd.go b/precompiles/wasmd/wasmd.go index 320778e3b..b3f73cc57 100644 --- a/precompiles/wasmd/wasmd.go +++ b/precompiles/wasmd/wasmd.go @@ -12,6 +12,7 @@ import ( 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" "github.com/sei-protocol/sei-chain/utils" @@ -26,6 +27,7 @@ const ( const WasmdAddress = "0x0000000000000000000000000000000000001002" var _ vm.PrecompiledContract = &Precompile{} +var _ vm.DynamicGasPrecompiledContract = &Precompile{} // Embed abi json file to the executable binary. Needed when importing as dependency. // @@ -110,14 +112,11 @@ func (p Precompile) Address() common.Address { return p.address } -func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int) (ret []byte, remainingGas uint64, err error) { +func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks) (ret []byte, remainingGas uint64, err error) { ctx, method, args, err := p.Prepare(evm, input) if err != nil { return nil, 0, err } - if err := pcommon.ValidateCaller(ctx, p.evmKeeper, caller, callingContract); 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 { @@ -129,7 +128,7 @@ func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, calli case InstantiateMethod: return p.instantiate(ctx, method, caller, args, value) case ExecuteMethod: - return p.execute(ctx, method, caller, args, value) + return p.execute(ctx, method, caller, callingContract, args, value) case QueryMethod: return p.query(ctx, method, args, value) } @@ -215,7 +214,7 @@ func (p Precompile) instantiate(ctx sdk.Context, method *abi.Method, caller comm return } -func (p Precompile) execute(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { +func (p Precompile) execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { defer func() { if err := recover(); err != nil { ret = nil @@ -231,6 +230,13 @@ func (p Precompile) execute(ctx sdk.Context, method *abi.Method, caller common.A // 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 { diff --git a/precompiles/wasmd/wasmd_test.go b/precompiles/wasmd/wasmd_test.go index eb8959974..75fb4a408 100644 --- a/precompiles/wasmd/wasmd_test.go +++ b/precompiles/wasmd/wasmd_test.go @@ -63,7 +63,7 @@ func TestInstantiate(t *testing.T) { StateDB: statedb, } suppliedGas := uint64(1000000) - res, g, err := p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.InstantiateID, args...), suppliedGas, nil) + res, g, err := p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.InstantiateID, args...), suppliedGas, nil, nil) require.Nil(t, err) outputs, err := instantiateMethod.Outputs.Unpack(res) require.Nil(t, err) @@ -80,16 +80,16 @@ func TestInstantiate(t *testing.T) { "test", amtsbz, ) - _, g, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.InstantiateID, args...), suppliedGas, nil) + _, g, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.InstantiateID, args...), suppliedGas, nil, nil) require.NotNil(t, err) require.Equal(t, uint64(0), g) // bad inputs badArgs, _ := instantiateMethod.Inputs.Pack(codeID, "not bech32", []byte("{}"), "test", amtsbz) - _, _, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.InstantiateID, badArgs...), suppliedGas, nil) + _, _, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.InstantiateID, badArgs...), suppliedGas, nil, nil) require.NotNil(t, err) badArgs, _ = instantiateMethod.Inputs.Pack(codeID, mockAddr.String(), []byte("{}"), "test", []byte("bad coins")) - _, _, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.InstantiateID, badArgs...), suppliedGas, nil) + _, _, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.InstantiateID, badArgs...), suppliedGas, nil, nil) require.NotNil(t, err) } @@ -125,14 +125,14 @@ func TestExecute(t *testing.T) { } suppliedGas := uint64(1000000) testApp.BankKeeper.SendCoins(ctx, mockAddr, testApp.EvmKeeper.GetSeiAddressOrDefault(ctx, common.HexToAddress(wasmd.WasmdAddress)), amts) - _, _, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.ExecuteID, args...), suppliedGas, nil) + _, _, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.ExecuteID, args...), suppliedGas, nil, nil) require.NotNil(t, err) // used coins instead of `value` to send usei to the contract amtsbz, err = sdk.NewCoins().MarshalJSON() require.Nil(t, err) args, err = executeMethod.Inputs.Pack(contractAddr.String(), []byte("{\"echo\":{\"message\":\"test msg\"}}"), amtsbz) require.Nil(t, err) - res, g, err := p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.ExecuteID, args...), suppliedGas, big.NewInt(1000_000_000_000_000)) + res, g, err := p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.ExecuteID, args...), suppliedGas, big.NewInt(1000_000_000_000_000), nil) require.Nil(t, err) outputs, err := executeMethod.Outputs.Unpack(res) require.Nil(t, err) @@ -143,29 +143,28 @@ func TestExecute(t *testing.T) { // allowed delegatecall contractAddrAllowed := common.BytesToAddress([]byte("contractA")) - testApp.EvmKeeper.SetCode(ctx, contractAddrAllowed, []byte("allowed")) - testApp.EvmKeeper.AddCodeHashWhitelistedForDelegateCall(ctx, testApp.EvmKeeper.GetCodeHash(ctx, contractAddrAllowed)) - _, _, err = p.RunAndCalculateGas(&evm, mockEVMAddr, contractAddrAllowed, append(p.ExecuteID, args...), suppliedGas, nil) + testApp.EvmKeeper.SetERC20CW20Pointer(ctx, contractAddr.String(), contractAddrAllowed) + _, _, err = p.RunAndCalculateGas(&evm, mockEVMAddr, contractAddrAllowed, append(p.ExecuteID, args...), suppliedGas, nil, nil) require.Nil(t, err) // disallowed delegatecall contractAddrDisallowed := common.BytesToAddress([]byte("contractB")) - _, _, err = p.RunAndCalculateGas(&evm, mockEVMAddr, contractAddrDisallowed, append(p.ExecuteID, args...), suppliedGas, nil) + _, _, err = p.RunAndCalculateGas(&evm, mockEVMAddr, contractAddrDisallowed, append(p.ExecuteID, args...), suppliedGas, nil, nil) require.NotNil(t, err) // bad contract address args, _ = executeMethod.Inputs.Pack(mockAddr.String(), []byte("{\"echo\":{\"message\":\"test msg\"}}"), amtsbz) - _, g, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.ExecuteID, args...), suppliedGas, nil) + _, g, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.ExecuteID, args...), suppliedGas, nil, nil) require.NotNil(t, err) require.Equal(t, uint64(0), g) // bad inputs args, _ = executeMethod.Inputs.Pack("not bech32", []byte("{\"echo\":{\"message\":\"test msg\"}}"), amtsbz) - _, g, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.ExecuteID, args...), suppliedGas, nil) + _, g, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.ExecuteID, args...), suppliedGas, nil, nil) require.NotNil(t, err) require.Equal(t, uint64(0), g) args, _ = executeMethod.Inputs.Pack(contractAddr.String(), []byte("{\"echo\":{\"message\":\"test msg\"}}"), []byte("bad coins")) - _, g, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.ExecuteID, args...), suppliedGas, nil) + _, g, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.ExecuteID, args...), suppliedGas, nil, nil) require.NotNil(t, err) require.Equal(t, uint64(0), g) } @@ -193,7 +192,7 @@ func TestQuery(t *testing.T) { StateDB: statedb, } suppliedGas := uint64(1000000) - res, g, err := p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(p.QueryID, args...), suppliedGas, nil) + res, g, err := p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(p.QueryID, args...), suppliedGas, nil, nil) require.Nil(t, err) outputs, err := queryMethod.Outputs.Unpack(res) require.Nil(t, err) @@ -203,17 +202,17 @@ func TestQuery(t *testing.T) { // bad contract address args, _ = queryMethod.Inputs.Pack(mockAddr.String(), []byte("{\"info\":{}}")) - _, g, err = p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(p.ExecuteID, args...), suppliedGas, nil) + _, g, err = p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(p.ExecuteID, args...), suppliedGas, nil, nil) require.NotNil(t, err) require.Equal(t, uint64(0), g) // bad input args, _ = queryMethod.Inputs.Pack("not bech32", []byte("{\"info\":{}}")) - _, g, err = p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(p.ExecuteID, args...), suppliedGas, nil) + _, g, err = p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(p.ExecuteID, args...), suppliedGas, nil, nil) require.NotNil(t, err) require.Equal(t, uint64(0), g) args, _ = queryMethod.Inputs.Pack(contractAddr.String(), []byte("{\"bad\":{}}")) - _, g, err = p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(p.ExecuteID, args...), suppliedGas, nil) + _, g, err = p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(p.ExecuteID, args...), suppliedGas, nil, nil) require.NotNil(t, err) require.Equal(t, uint64(0), g) } diff --git a/scripts/initialize_local_chain.sh b/scripts/initialize_local_chain.sh index c1dafc722..08b3d6b01 100755 --- a/scripts/initialize_local_chain.sh +++ b/scripts/initialize_local_chain.sh @@ -60,6 +60,7 @@ cat ~/.sei/config/genesis.json | jq '.app_state["oracle"]["params"]["whitelist"] cat ~/.sei/config/genesis.json | jq '.app_state["distribution"]["params"]["community_tax"]="0.000000000000000000"' > ~/.sei/config/tmp_genesis.json && mv ~/.sei/config/tmp_genesis.json ~/.sei/config/genesis.json cat ~/.sei/config/genesis.json | jq '.consensus_params["block"]["max_gas"]="35000000"' > ~/.sei/config/tmp_genesis.json && mv ~/.sei/config/tmp_genesis.json ~/.sei/config/genesis.json cat ~/.sei/config/genesis.json | jq '.app_state["staking"]["params"]["max_voting_power_ratio"]="1.000000000000000000"' > ~/.sei/config/tmp_genesis.json && mv ~/.sei/config/tmp_genesis.json ~/.sei/config/genesis.json +cat ~/.sei/config/genesis.json | jq '.app_state["bank"]["denom_metadata"]=[{"denom_units":[{"denom":"UATOM","exponent":6,"aliases":["UATOM"]}],"base":"uatom","display":"uatom","name":"UATOM","symbol":"UATOM"}]' > ~/.sei/config/tmp_genesis.json && mv ~/.sei/config/tmp_genesis.json ~/.sei/config/genesis.json # Use the Python command to get the dates START_DATE=$($PYTHON_CMD -c "from datetime import datetime; print(datetime.now().strftime('%Y-%m-%d'))") diff --git a/x/evm/artifacts/native/artifacts_test.go b/x/evm/artifacts/native/artifacts_test.go index 17876eceb..46451776c 100644 --- a/x/evm/artifacts/native/artifacts_test.go +++ b/x/evm/artifacts/native/artifacts_test.go @@ -69,6 +69,7 @@ func TestSimple(t *testing.T) { require.Nil(t, err) require.NotNil(t, receipt) require.Equal(t, uint32(ethtypes.ReceiptStatusSuccessful), receipt.Status) + k.SetERC20NativePointer(ctx, "test", common.HexToAddress(receipt.ContractAddress)) // send transaction to the contract contractAddr := common.HexToAddress(receipt.ContractAddress) diff --git a/x/evm/keeper/evm.go b/x/evm/keeper/evm.go index f7d016714..ae64cd5ba 100644 --- a/x/evm/keeper/evm.go +++ b/x/evm/keeper/evm.go @@ -60,7 +60,6 @@ func (k *Keeper) CallEVM(ctx sdk.Context, from sdk.AccAddress, to *common.Addres if err != nil { return nil, err } - var createdContractAddress common.Address defer func() { if finalizer != nil { if err := finalizer(); err != nil { @@ -68,16 +67,12 @@ func (k *Keeper) CallEVM(ctx sdk.Context, from sdk.AccAddress, to *common.Addres return } } - if reterr == nil && to == nil { - k.AddToWhitelistIfApplicable(ctx, data, createdContractAddress) - } }() var f EVMCallFunc if to == nil { // contract creation f = func(caller vm.ContractRef, _ *common.Address, input []byte, gas uint64, value *big.Int) ([]byte, uint64, error) { - ret, ca, leftoverGas, err := evm.Create(caller, input, gas, value) - createdContractAddress = ca + ret, _, leftoverGas, err := evm.Create(caller, input, gas, value) return ret, leftoverGas, err } } else { diff --git a/x/evm/keeper/evm_test.go b/x/evm/keeper/evm_test.go index 4ecd5e3e9..940e246ec 100644 --- a/x/evm/keeper/evm_test.go +++ b/x/evm/keeper/evm_test.go @@ -59,6 +59,7 @@ func TestInternalCall(t *testing.T) { contractAddr := crypto.CreateAddress(senderEvmAddr, 0) require.NotEmpty(t, k.GetCode(ctx, contractAddr)) require.Equal(t, ret.Data, k.GetCode(ctx, contractAddr)) + k.SetERC20NativePointer(ctx, "test", contractAddr) receiverAddr, evmAddr := testkeeper.MockAddressPair() k.SetAddressMapping(ctx, receiverAddr, evmAddr) diff --git a/x/evm/keeper/msg_server.go b/x/evm/keeper/msg_server.go index cfb25b10c..3cd7de979 100644 --- a/x/evm/keeper/msg_server.go +++ b/x/evm/keeper/msg_server.go @@ -115,9 +115,6 @@ func (server msgServer) EVMTransaction(goCtx context.Context, msg *types.MsgEVMT bloom := ethtypes.Bloom{} bloom.SetBytes(receipt.LogsBloom) server.AppendToEvmTxDeferredInfo(ctx, bloom, tx.Hash(), surplus) - if serverRes.VmError == "" && tx.To() == nil { - server.AddToWhitelistIfApplicable(ctx, tx.Data(), common.HexToAddress(receipt.ContractAddress)) - } // GasUsed in serverRes is in EVM's gas unit, not Sei's gas unit. // PriorityNormalizer is the coefficient that's used to adjust EVM diff --git a/x/evm/keeper/msg_server_test.go b/x/evm/keeper/msg_server_test.go index 194476cde..5661a77ae 100644 --- a/x/evm/keeper/msg_server_test.go +++ b/x/evm/keeper/msg_server_test.go @@ -302,8 +302,7 @@ func TestEVMPrecompiles(t *testing.T) { require.Nil(t, err) require.NotNil(t, receipt) require.Equal(t, uint32(ethtypes.ReceiptStatusSuccessful), receipt.Status) - codeHash := k.GetCodeHash(ctx, common.HexToAddress(receipt.ContractAddress)) - k.AddCodeHashWhitelistedForBankSend(ctx, codeHash) + k.SetERC20NativePointer(ctx, k.GetBaseDenom(ctx), common.HexToAddress(receipt.ContractAddress)) // call sendall addr1, evmAddr1 := testkeeper.MockAddressPair() diff --git a/x/evm/keeper/whitelist.go b/x/evm/keeper/whitelist.go index deade054b..5e85bfbf4 100644 --- a/x/evm/keeper/whitelist.go +++ b/x/evm/keeper/whitelist.go @@ -2,66 +2,10 @@ package keeper import ( "bytes" - "fmt" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" - "github.com/sei-protocol/sei-chain/x/evm/artifacts/cw20" - "github.com/sei-protocol/sei-chain/x/evm/artifacts/cw721" - "github.com/sei-protocol/sei-chain/x/evm/artifacts/native" - "github.com/sei-protocol/sei-chain/x/evm/types" ) -func (k *Keeper) AddToWhitelistIfApplicable(ctx sdk.Context, data []byte, contractAddress common.Address) { - if native.IsCodeFromBin(data) { - codeHash := k.GetCodeHash(ctx, contractAddress) - if (codeHash != common.Hash{}) { - k.AddCodeHashWhitelistedForBankSend(ctx, codeHash) - } - return - } - if cw20.IsCodeFromBin(data) || cw721.IsCodeFromBin(data) { - codeHash := k.GetCodeHash(ctx, contractAddress) - if (codeHash != common.Hash{}) { - k.AddCodeHashWhitelistedForDelegateCall(ctx, codeHash) - } - return - } -} - -func (k *Keeper) IsCodeHashWhitelistedForBankSend(ctx sdk.Context, h common.Hash) bool { - if w := k.GetCodeHashWhitelistedForBankSend(ctx); w != nil { - return w.IsHashInWhiteList(h) - } - return false -} - -func (k *Keeper) AddCodeHashWhitelistedForBankSend(ctx sdk.Context, h common.Hash) { - store := ctx.KVStore(k.storeKey) - w := k.GetCodeHashWhitelistedForBankSend(ctx) - if w == nil { - w = &types.Whitelist{Hashes: []string{h.Hex()}} - } else if !w.IsHashInWhiteList(h) { - w.Hashes = append(w.Hashes, h.Hex()) - } - bz, _ := w.Marshal() - store.Set(types.WhitelistedCodeHashesForBankSendPrefix, bz) -} - -func (k *Keeper) GetCodeHashWhitelistedForBankSend(ctx sdk.Context) *types.Whitelist { - store := ctx.KVStore(k.storeKey) - bz := store.Get(types.WhitelistedCodeHashesForBankSendPrefix) - if bz == nil { - return &types.Whitelist{Hashes: []string{}} - } - w := &types.Whitelist{} - if err := w.Unmarshal(bz); err != nil { - ctx.Logger().Error(fmt.Sprintf("error parsing code hash whitelist for bank send: %s", err)) - return nil - } - return w -} - func (k *Keeper) IsCWCodeHashWhitelistedForEVMDelegateCall(ctx sdk.Context, h []byte) bool { for _, w := range k.WhitelistedCwCodeHashesForDelegateCall(ctx) { if bytes.Equal(w, h) { @@ -70,36 +14,3 @@ func (k *Keeper) IsCWCodeHashWhitelistedForEVMDelegateCall(ctx sdk.Context, h [] } return false } - -func (k *Keeper) IsCodeHashWhitelistedForDelegateCall(ctx sdk.Context, h common.Hash) bool { - if w := k.GetCodeHashWhitelistedForDelegateCall(ctx); w != nil { - return w.IsHashInWhiteList(h) - } - return false -} - -func (k *Keeper) AddCodeHashWhitelistedForDelegateCall(ctx sdk.Context, h common.Hash) { - store := ctx.KVStore(k.storeKey) - w := k.GetCodeHashWhitelistedForDelegateCall(ctx) - if w == nil { - w = &types.Whitelist{Hashes: []string{h.Hex()}} - } else if !w.IsHashInWhiteList(h) { - w.Hashes = append(w.Hashes, h.Hex()) - } - bz, _ := w.Marshal() - store.Set(types.WhitelistedCodeHashesForDelegateCallPrefix, bz) -} - -func (k *Keeper) GetCodeHashWhitelistedForDelegateCall(ctx sdk.Context) *types.Whitelist { - store := ctx.KVStore(k.storeKey) - bz := store.Get(types.WhitelistedCodeHashesForDelegateCallPrefix) - if bz == nil { - return &types.Whitelist{Hashes: []string{}} - } - w := &types.Whitelist{} - if err := w.Unmarshal(bz); err != nil { - ctx.Logger().Error(fmt.Sprintf("error parsing code hash whitelist for delegate call: %s", err)) - return nil - } - return w -} diff --git a/x/evm/keeper/whitelist_test.go b/x/evm/keeper/whitelist_test.go deleted file mode 100644 index 89a2a1087..000000000 --- a/x/evm/keeper/whitelist_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package keeper_test - -import ( - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/sei-protocol/sei-chain/testutil/keeper" - "github.com/stretchr/testify/require" -) - -func TestBankSendWhitelist(t *testing.T) { - k, ctx := keeper.MockEVMKeeper() - wl := k.GetCodeHashWhitelistedForBankSend(ctx) - require.Empty(t, wl.Hashes) - k.AddCodeHashWhitelistedForBankSend(ctx, common.BytesToHash([]byte("a"))) - require.True(t, k.IsCodeHashWhitelistedForBankSend(ctx, common.BytesToHash([]byte("a")))) - require.False(t, k.IsCodeHashWhitelistedForBankSend(ctx, common.BytesToHash([]byte("b")))) -} - -func TestDelegateCallWhitelist(t *testing.T) { - k, ctx := keeper.MockEVMKeeper() - wl := k.GetCodeHashWhitelistedForDelegateCall(ctx) - require.Empty(t, wl.Hashes) - k.AddCodeHashWhitelistedForDelegateCall(ctx, common.BytesToHash([]byte("a"))) - require.True(t, k.IsCodeHashWhitelistedForDelegateCall(ctx, common.BytesToHash([]byte("a")))) - require.False(t, k.IsCodeHashWhitelistedForDelegateCall(ctx, common.BytesToHash([]byte("b")))) -} From 86006f0da0da8ccd65c17b5af87f17a14a743e4a Mon Sep 17 00:00:00 2001 From: codchen Date: Mon, 8 Apr 2024 22:23:12 +0800 Subject: [PATCH 25/34] Require `from` in TransferFrom in CW721 pointer to be the owner (#1516) * Require `from` in TransferFrom in CW721 pointer to be the owner * test --- contracts/src/CW721ERC721Pointer.sol | 1 + contracts/test/CW721ERC721PointerTest.t.sol | 15 +++++++++++++++ x/evm/artifacts/cw721/CW721ERC721Pointer.bin | 2 +- x/evm/artifacts/cw721/artifacts.go | 2 +- x/evm/keeper/pointer.go | 3 ++- 5 files changed, 20 insertions(+), 3 deletions(-) diff --git a/contracts/src/CW721ERC721Pointer.sol b/contracts/src/CW721ERC721Pointer.sol index 21bc6a2b2..a8c614e18 100644 --- a/contracts/src/CW721ERC721Pointer.sol +++ b/contracts/src/CW721ERC721Pointer.sol @@ -87,6 +87,7 @@ contract CW721ERC721Pointer is ERC721 { if (to == address(0)) { revert ERC721InvalidReceiver(address(0)); } + require(from == ownerOf(tokenId), "`from` must be the owner"); string memory recipient = _formatPayload("recipient", _doubleQuotes(AddrPrecompile.getSeiAddr(to))); string memory tId = _formatPayload("token_id", _doubleQuotes(Strings.toString(tokenId))); string memory req = _curlyBrace(_formatPayload("transfer_nft", _curlyBrace(_join(recipient, tId, ",")))); diff --git a/contracts/test/CW721ERC721PointerTest.t.sol b/contracts/test/CW721ERC721PointerTest.t.sol index b7969583c..9c4602e7c 100644 --- a/contracts/test/CW721ERC721PointerTest.t.sol +++ b/contracts/test/CW721ERC721PointerTest.t.sol @@ -179,6 +179,21 @@ contract CW721ERC721PointerTest is Test { } function testTransferFrom() public { + vm.mockCall( + WASMD_PRECOMPILE_ADDRESS, + abi.encodeWithSignature("query(string,bytes)", MockCWContractAddress, bytes("{\"owner_of\":{\"token_id\":\"1\"}}")), + abi.encode("{\"owner\":\"sei1vldxw5dy5k68hqr4d744rpg9w8cqs54x4asdqe\"}") + ); + vm.mockCall( + JSON_PRECOMPILE_ADDRESS, + abi.encodeWithSignature("extractAsBytes(bytes,string)", bytes("{\"owner\":\"sei1vldxw5dy5k68hqr4d744rpg9w8cqs54x4asdqe\"}"), "owner"), + abi.encode(bytes("sei1vldxw5dy5k68hqr4d744rpg9w8cqs54x4asdqe")) + ); + vm.mockCall( + ADDR_PRECOMPILE_ADDRESS, + abi.encodeWithSignature("getEvmAddr(string)", "sei1vldxw5dy5k68hqr4d744rpg9w8cqs54x4asdqe"), + abi.encode(address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)) + ); vm.mockCall( WASMD_PRECOMPILE_ADDRESS, abi.encodeWithSignature("execute(string,bytes,bytes)", MockCWContractAddress, bytes("{\"transfer_nft\":{\"recipient\":\"sei1vldxw5dy5k68hqr4d744rpg9w8cqs54x4asdqe\",\"token_id\":\"1\"}}"), bytes("[]")), diff --git a/x/evm/artifacts/cw721/CW721ERC721Pointer.bin b/x/evm/artifacts/cw721/CW721ERC721Pointer.bin index c5dc1ca79..700510ae7 100644 --- a/x/evm/artifacts/cw721/CW721ERC721Pointer.bin +++ b/x/evm/artifacts/cw721/CW721ERC721Pointer.bin @@ -1 +1 @@ -608060405234801562000010575f80fd5b5060405162003be638038062003be68339818101604052810190620000369190620002c3565b8181815f9081620000489190620005b0565b5080600190816200005a9190620005b0565b50505061100260075f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555061100360085f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555061100460095f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508260069081620001349190620005b0565b5050505062000694565b5f604051905090565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6200019f8262000157565b810181811067ffffffffffffffff82111715620001c157620001c062000167565b5b80604052505050565b5f620001d56200013e565b9050620001e3828262000194565b919050565b5f67ffffffffffffffff82111562000205576200020462000167565b5b620002108262000157565b9050602081019050919050565b5f5b838110156200023c5780820151818401526020810190506200021f565b5f8484015250505050565b5f6200025d6200025784620001e8565b620001ca565b9050828152602081018484840111156200027c576200027b62000153565b5b620002898482856200021d565b509392505050565b5f82601f830112620002a857620002a76200014f565b5b8151620002ba84826020860162000247565b91505092915050565b5f805f60608486031215620002dd57620002dc62000147565b5b5f84015167ffffffffffffffff811115620002fd57620002fc6200014b565b5b6200030b8682870162000291565b935050602084015167ffffffffffffffff8111156200032f576200032e6200014b565b5b6200033d8682870162000291565b925050604084015167ffffffffffffffff8111156200036157620003606200014b565b5b6200036f8682870162000291565b9150509250925092565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680620003c857607f821691505b602082108103620003de57620003dd62000383565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f60088302620004427fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000405565b6200044e868362000405565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f62000498620004926200048c8462000466565b6200046f565b62000466565b9050919050565b5f819050919050565b620004b38362000478565b620004cb620004c2826200049f565b84845462000411565b825550505050565b5f90565b620004e1620004d3565b620004ee818484620004a8565b505050565b5b818110156200051557620005095f82620004d7565b600181019050620004f4565b5050565b601f82111562000564576200052e81620003e4565b6200053984620003f6565b8101602085101562000549578190505b620005616200055885620003f6565b830182620004f3565b50505b505050565b5f82821c905092915050565b5f620005865f198460080262000569565b1980831691505092915050565b5f620005a0838362000575565b9150826002028217905092915050565b620005bb8262000379565b67ffffffffffffffff811115620005d757620005d662000167565b5b620005e38254620003b0565b620005f082828562000519565b5f60209050601f83116001811462000626575f841562000611578287015190505b6200061d858262000593565b8655506200068c565b601f1984166200063686620003e4565b5f5b828110156200065f5784890151825560018201915060208501945060208101905062000638565b868310156200067f57848901516200067b601f89168262000575565b8355505b6001600288020188555050505b505050505050565b61354480620006a25f395ff3fe608060405234801561000f575f80fd5b5060043610610109575f3560e01c806370a08231116100a0578063c2aed3021161006f578063c2aed302146102b3578063c87b56dd146102d1578063de4725cc14610301578063e985e9c51461031f578063f00b02551461034f57610109565b806370a082311461022d57806395d89b411461025d578063a22cb4651461027b578063b88d4fde1461029757610109565b806323b872dd116100dc57806323b872dd146101a757806342842e0e146101c35780635c4aead7146101df5780636352211e146101fd57610109565b806301ffc9a71461010d57806306fdde031461013d578063081812fc1461015b578063095ea7b31461018b575b5f80fd5b610127600480360381019061012291906123ea565b61036d565b604051610134919061242f565b60405180910390f35b61014561044e565b60405161015291906124d2565b60405180910390f35b61017560048036038101906101709190612525565b6104dd565b604051610182919061258f565b60405180910390f35b6101a560048036038101906101a091906125d2565b610837565b005b6101c160048036038101906101bc9190612610565b610a73565b005b6101dd60048036038101906101d89190612610565b610d18565b005b6101e7610d37565b6040516101f491906124d2565b60405180910390f35b61021760048036038101906102129190612525565b610dc3565b604051610224919061258f565b60405180910390f35b61024760048036038101906102429190612660565b61104d565b604051610254919061269a565b60405180910390f35b610265611343565b60405161027291906124d2565b60405180910390f35b610295600480360381019061029091906126dd565b6113d3565b005b6102b160048036038101906102ac9190612847565b6115d6565b005b6102bb6115f3565b6040516102c89190612922565b60405180910390f35b6102eb60048036038101906102e69190612525565b611618565b6040516102f891906124d2565b60405180910390f35b610309611815565b604051610316919061295b565b60405180910390f35b61033960048036038101906103349190612974565b61183a565b604051610346919061242f565b60405180910390f35b610357611c74565b60405161036491906129d2565b60405180910390f35b5f7f80ac58cd000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916148061043757507f5b5e139f000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b80610447575061044682611c99565b5b9050919050565b60605f805461045c90612a18565b80601f016020809104026020016040519081016040528092919081815260200182805461048890612a18565b80156104d35780601f106104aa576101008083540402835291602001916104d3565b820191905f5260205f20905b8154815290600101906020018083116104b657829003601f168201915b5050505050905090565b5f8061052e6040518060400160405280600881526020017f746f6b656e5f696400000000000000000000000000000000000000000000000081525061052961052486611d02565b611dcc565b611e14565b90505f61058061057b6040518060400160405280600981526020017f617070726f76616c73000000000000000000000000000000000000000000000081525061057685611e66565b611e14565b611e66565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296006846040518363ffffffff1660e01b81526004016105e0929190612b2d565b5f60405180830381865afa1580156105fa573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906106229190612bd0565b90505f60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166387cdf621836040518263ffffffff1660e01b815260040161067f9190612c61565b5f60405180830381865afa158015610699573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906106c19190612d76565b90505f8151111561082a575f60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166308d858e5835f8151811061071d5761071c612dbd565b5b60200260200101516040518263ffffffff1660e01b81526004016107419190612e34565b5f60405180830381865afa15801561075b573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906107839190612bd0565b905060095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631778e539826040518263ffffffff1660e01b81526004016107df91906124d2565b602060405180830381865afa1580156107fa573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061081e9190612e7b565b95505050505050610832565b5f9450505050505b919050565b5f61091a6040518060400160405280600781526020017f7370656e6465720000000000000000000000000000000000000000000000000081525061091560095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed876040518263ffffffff1660e01b81526004016108ce919061258f565b5f60405180830381865afa1580156108e8573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906109109190612f44565b611dcc565b611e14565b90505f61096c6040518060400160405280600881526020017f746f6b656e5f696400000000000000000000000000000000000000000000000081525061096761096286611d02565b611dcc565b611e14565b90505f6109fd6109f86040518060400160405280600781526020017f617070726f7665000000000000000000000000000000000000000000000000008152506109f36109ee87876040518060400160405280600181526020017f2c00000000000000000000000000000000000000000000000000000000000000815250611eae565b611e66565b611e14565b611e66565b9050610a0881611efc565b50838573ffffffffffffffffffffffffffffffffffffffff16610a2a86610dc3565b73ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610ae3575f6040517f64a0ae92000000000000000000000000000000000000000000000000000000008152600401610ada919061258f565b60405180910390fd5b5f610bc66040518060400160405280600981526020017f726563697069656e740000000000000000000000000000000000000000000000815250610bc160095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed876040518263ffffffff1660e01b8152600401610b7a919061258f565b5f60405180830381865afa158015610b94573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190610bbc9190612f44565b611dcc565b611e14565b90505f610c186040518060400160405280600881526020017f746f6b656e5f6964000000000000000000000000000000000000000000000000815250610c13610c0e86611d02565b611dcc565b611e14565b90505f610ca9610ca46040518060400160405280600c81526020017f7472616e736665725f6e66740000000000000000000000000000000000000000815250610c9f610c9a87876040518060400160405280600181526020017f2c00000000000000000000000000000000000000000000000000000000000000815250611eae565b611e66565b611e14565b611e66565b9050610cb481611efc565b50838573ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a4505050505050565b610d3283838360405180602001604052805f8152506115d6565b505050565b60068054610d4490612a18565b80601f0160208091040260200160405190810160405280929190818152602001828054610d7090612a18565b8015610dbb5780601f10610d9257610100808354040283529160200191610dbb565b820191905f5260205f20905b815481529060010190602001808311610d9e57829003601f168201915b505050505081565b5f80610e146040518060400160405280600881526020017f746f6b656e5f6964000000000000000000000000000000000000000000000000815250610e0f610e0a86611d02565b611dcc565b611e14565b90505f610e66610e616040518060400160405280600881526020017f6f776e65725f6f66000000000000000000000000000000000000000000000000815250610e5c85611e66565b611e14565b611e66565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296006846040518363ffffffff1660e01b8152600401610ec6929190612b2d565b5f60405180830381865afa158015610ee0573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190610f089190612bd0565b90505f60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166308d858e5836040518263ffffffff1660e01b8152600401610f659190612fd5565b5f60405180830381865afa158015610f7f573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190610fa79190612bd0565b905060095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631778e539826040518263ffffffff1660e01b815260040161100391906124d2565b602060405180830381865afa15801561101e573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110429190612e7b565b945050505050919050565b5f8073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036110be575f6040517f89c62b640000000000000000000000000000000000000000000000000000000081526004016110b5919061258f565b60405180910390fd5b5f6111a16040518060400160405280600581526020017f6f776e657200000000000000000000000000000000000000000000000000000081525061119c60095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed876040518263ffffffff1660e01b8152600401611155919061258f565b5f60405180830381865afa15801561116f573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906111979190612f44565b611dcc565b611e14565b90505f6111f36111ee6040518060400160405280600681526020017f746f6b656e7300000000000000000000000000000000000000000000000000008152506111e985611e66565b611e14565b611e66565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296006846040518363ffffffff1660e01b8152600401611253929190612b2d565b5f60405180830381865afa15801561126d573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906112959190612bd0565b90505f60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166387cdf621836040518263ffffffff1660e01b81526004016112f29190613052565b5f60405180830381865afa15801561130c573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906113349190612d76565b90508051945050505050919050565b60606001805461135290612a18565b80601f016020809104026020016040519081016040528092919081815260200182805461137e90612a18565b80156113c95780601f106113a0576101008083540402835291602001916113c9565b820191905f5260205f20905b8154815290600101906020018083116113ac57829003601f168201915b5050505050905090565b5f6114be6114b96040518060400160405280600881526020017f6f70657261746f720000000000000000000000000000000000000000000000008152506114b460095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed886040518263ffffffff1660e01b815260040161146d919061258f565b5f60405180830381865afa158015611487573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906114af9190612f44565b611dcc565b611e14565b611e66565b9050811561151b5761151561151061150b6040518060400160405280600b81526020017f617070726f76655f616c6c00000000000000000000000000000000000000000081525084611e14565b611e66565b611efc565b5061156c565b61156a6115656115606040518060400160405280600a81526020017f7265766f6b655f616c6c0000000000000000000000000000000000000000000081525084611e14565b611e66565b611efc565b505b8273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31846040516115c9919061242f565b60405180910390a3505050565b6115e1848484610a73565b6115ed8484848461207a565b50505050565b60095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b606061162382610dc3565b505f6116746040518060400160405280600881526020017f746f6b656e5f696400000000000000000000000000000000000000000000000081525061166f61166a86611d02565b611dcc565b611e14565b90505f6116c66116c16040518060400160405280600881526020017f6e66745f696e666f0000000000000000000000000000000000000000000000008152506116bc85611e66565b611e14565b611e66565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296006846040518363ffffffff1660e01b8152600401611726929190612b2d565b5f60405180830381865afa158015611740573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906117689190612bd0565b90505f60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166308d858e5836040518263ffffffff1660e01b81526004016117c591906130cf565b5f60405180830381865afa1580156117df573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906118079190612bd0565b905080945050505050919050565b60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f8061191e6040518060400160405280600581526020017f6f776e657200000000000000000000000000000000000000000000000000000081525061191960095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed886040518263ffffffff1660e01b81526004016118d2919061258f565b5f60405180830381865afa1580156118ec573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906119149190612f44565b611dcc565b611e14565b90505f61197061196b6040518060400160405280600d81526020017f616c6c5f6f70657261746f72730000000000000000000000000000000000000081525061196685611e66565b611e14565b611e66565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296006846040518363ffffffff1660e01b81526004016119d0929190612b2d565b5f60405180830381865afa1580156119ea573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611a129190612bd0565b90505f60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166387cdf621836040518263ffffffff1660e01b8152600401611a6f919061314c565b5f60405180830381865afa158015611a89573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611ab19190612d76565b90505f5b8151811015611c65575f60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166308d858e5848481518110611b0f57611b0e612dbd565b5b60200260200101516040518263ffffffff1660e01b8152600401611b339190612e34565b5f60405180830381865afa158015611b4d573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611b759190612bd0565b90508773ffffffffffffffffffffffffffffffffffffffff1660095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631778e539836040518263ffffffff1660e01b8152600401611be891906124d2565b602060405180830381865afa158015611c03573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c279190612e7b565b73ffffffffffffffffffffffffffffffffffffffff1603611c515760019650505050505050611c6e565b508080611c5d906131ac565b915050611ab5565b505f9450505050505b92915050565b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b60605f6001611d108461222c565b0190505f8167ffffffffffffffff811115611d2e57611d2d612723565b5b6040519080825280601f01601f191660200182016040528015611d605781602001600182028036833780820191505090505b5090505f82602001820190505b600115611dc1578080600190039150507f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a8581611db657611db56131f3565b5b0494505f8503611d6d575b819350505050919050565b606081604051602001611ddf9190613280565b604051602081830303815290604052604051602001611dfe91906132a5565b6040516020818303038152906040529050919050565b6060611e5e611e2284611dcc565b836040518060400160405280600181526020017f3a00000000000000000000000000000000000000000000000000000000000000815250611eae565b905092915050565b606081604051602001611e7991906132f0565b604051602081830303815290604052604051602001611e98919061333b565b6040516020818303038152906040529050919050565b6060838284604051602001611ec4929190613360565b604051602081830303815290604052604051602001611ee4929190613360565b60405160208183030381529060405290509392505050565b60605f8061100273ffffffffffffffffffffffffffffffffffffffff166006856040518060400160405280600281526020017f5b5d000000000000000000000000000000000000000000000000000000000000815250604051602401611f6493929190613383565b6040516020818303038152906040527f44d227ae000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051611fee9190613407565b5f60405180830381855af49150503d805f8114612026576040519150601f19603f3d011682016040523d82523d5f602084013e61202b565b606091505b509150915081612070576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161206790613467565b60405180910390fd5b8092505050919050565b5f8373ffffffffffffffffffffffffffffffffffffffff163b1115612226578273ffffffffffffffffffffffffffffffffffffffff1663150b7a026120bd61237d565b8685856040518563ffffffff1660e01b81526004016120df9493929190613485565b6020604051808303815f875af192505050801561211a57506040513d601f19601f8201168201806040525081019061211791906134e3565b60015b61219b573d805f8114612148576040519150601f19603f3d011682016040523d82523d5f602084013e61214d565b606091505b505f81510361219357836040517f64a0ae9200000000000000000000000000000000000000000000000000000000815260040161218a919061258f565b60405180910390fd5b805181602001fd5b63150b7a0260e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161461222457836040517f64a0ae9200000000000000000000000000000000000000000000000000000000815260040161221b919061258f565b60405180910390fd5b505b50505050565b5f805f90507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008310612288577a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000838161227e5761227d6131f3565b5b0492506040810190505b6d04ee2d6d415b85acef810000000083106122c5576d04ee2d6d415b85acef810000000083816122bb576122ba6131f3565b5b0492506020810190505b662386f26fc1000083106122f457662386f26fc1000083816122ea576122e96131f3565b5b0492506010810190505b6305f5e100831061231d576305f5e1008381612313576123126131f3565b5b0492506008810190505b6127108310612342576127108381612338576123376131f3565b5b0492506004810190505b60648310612365576064838161235b5761235a6131f3565b5b0492506002810190505b600a8310612374576001810190505b80915050919050565b5f33905090565b5f604051905090565b5f80fd5b5f80fd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b6123c981612395565b81146123d3575f80fd5b50565b5f813590506123e4816123c0565b92915050565b5f602082840312156123ff576123fe61238d565b5b5f61240c848285016123d6565b91505092915050565b5f8115159050919050565b61242981612415565b82525050565b5f6020820190506124425f830184612420565b92915050565b5f81519050919050565b5f82825260208201905092915050565b5f5b8381101561247f578082015181840152602081019050612464565b5f8484015250505050565b5f601f19601f8301169050919050565b5f6124a482612448565b6124ae8185612452565b93506124be818560208601612462565b6124c78161248a565b840191505092915050565b5f6020820190508181035f8301526124ea818461249a565b905092915050565b5f819050919050565b612504816124f2565b811461250e575f80fd5b50565b5f8135905061251f816124fb565b92915050565b5f6020828403121561253a5761253961238d565b5b5f61254784828501612511565b91505092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61257982612550565b9050919050565b6125898161256f565b82525050565b5f6020820190506125a25f830184612580565b92915050565b6125b18161256f565b81146125bb575f80fd5b50565b5f813590506125cc816125a8565b92915050565b5f80604083850312156125e8576125e761238d565b5b5f6125f5858286016125be565b925050602061260685828601612511565b9150509250929050565b5f805f606084860312156126275761262661238d565b5b5f612634868287016125be565b9350506020612645868287016125be565b925050604061265686828701612511565b9150509250925092565b5f602082840312156126755761267461238d565b5b5f612682848285016125be565b91505092915050565b612694816124f2565b82525050565b5f6020820190506126ad5f83018461268b565b92915050565b6126bc81612415565b81146126c6575f80fd5b50565b5f813590506126d7816126b3565b92915050565b5f80604083850312156126f3576126f261238d565b5b5f612700858286016125be565b9250506020612711858286016126c9565b9150509250929050565b5f80fd5b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6127598261248a565b810181811067ffffffffffffffff8211171561277857612777612723565b5b80604052505050565b5f61278a612384565b90506127968282612750565b919050565b5f67ffffffffffffffff8211156127b5576127b4612723565b5b6127be8261248a565b9050602081019050919050565b828183375f83830152505050565b5f6127eb6127e68461279b565b612781565b9050828152602081018484840111156128075761280661271f565b5b6128128482856127cb565b509392505050565b5f82601f83011261282e5761282d61271b565b5b813561283e8482602086016127d9565b91505092915050565b5f805f806080858703121561285f5761285e61238d565b5b5f61286c878288016125be565b945050602061287d878288016125be565b935050604061288e87828801612511565b925050606085013567ffffffffffffffff8111156128af576128ae612391565b5b6128bb8782880161281a565b91505092959194509250565b5f819050919050565b5f6128ea6128e56128e084612550565b6128c7565b612550565b9050919050565b5f6128fb826128d0565b9050919050565b5f61290c826128f1565b9050919050565b61291c81612902565b82525050565b5f6020820190506129355f830184612913565b92915050565b5f612945826128f1565b9050919050565b6129558161293b565b82525050565b5f60208201905061296e5f83018461294c565b92915050565b5f806040838503121561298a5761298961238d565b5b5f612997858286016125be565b92505060206129a8858286016125be565b9150509250929050565b5f6129bc826128f1565b9050919050565b6129cc816129b2565b82525050565b5f6020820190506129e55f8301846129c3565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680612a2f57607f821691505b602082108103612a4257612a416129eb565b5b50919050565b5f819050815f5260205f209050919050565b5f8154612a6681612a18565b612a708186612452565b9450600182165f8114612a8a5760018114612aa057612ad2565b60ff198316865281151560200286019350612ad2565b612aa985612a48565b5f5b83811015612aca57815481890152600182019150602081019050612aab565b808801955050505b50505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f612aff82612adb565b612b098185612ae5565b9350612b19818560208601612462565b612b228161248a565b840191505092915050565b5f6040820190508181035f830152612b458185612a5a565b90508181036020830152612b598184612af5565b90509392505050565b5f612b74612b6f8461279b565b612781565b905082815260208101848484011115612b9057612b8f61271f565b5b612b9b848285612462565b509392505050565b5f82601f830112612bb757612bb661271b565b5b8151612bc7848260208601612b62565b91505092915050565b5f60208284031215612be557612be461238d565b5b5f82015167ffffffffffffffff811115612c0257612c01612391565b5b612c0e84828501612ba3565b91505092915050565b7f617070726f76616c7300000000000000000000000000000000000000000000005f82015250565b5f612c4b600983612452565b9150612c5682612c17565b602082019050919050565b5f6040820190508181035f830152612c798184612af5565b90508181036020830152612c8c81612c3f565b905092915050565b5f67ffffffffffffffff821115612cae57612cad612723565b5b602082029050602081019050919050565b5f80fd5b5f612cd5612cd084612c94565b612781565b90508083825260208201905060208402830185811115612cf857612cf7612cbf565b5b835b81811015612d3f57805167ffffffffffffffff811115612d1d57612d1c61271b565b5b808601612d2a8982612ba3565b85526020850194505050602081019050612cfa565b5050509392505050565b5f82601f830112612d5d57612d5c61271b565b5b8151612d6d848260208601612cc3565b91505092915050565b5f60208284031215612d8b57612d8a61238d565b5b5f82015167ffffffffffffffff811115612da857612da7612391565b5b612db484828501612d49565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f7370656e646572000000000000000000000000000000000000000000000000005f82015250565b5f612e1e600783612452565b9150612e2982612dea565b602082019050919050565b5f6040820190508181035f830152612e4c8184612af5565b90508181036020830152612e5f81612e12565b905092915050565b5f81519050612e75816125a8565b92915050565b5f60208284031215612e9057612e8f61238d565b5b5f612e9d84828501612e67565b91505092915050565b5f67ffffffffffffffff821115612ec057612ebf612723565b5b612ec98261248a565b9050602081019050919050565b5f612ee8612ee384612ea6565b612781565b905082815260208101848484011115612f0457612f0361271f565b5b612f0f848285612462565b509392505050565b5f82601f830112612f2b57612f2a61271b565b5b8151612f3b848260208601612ed6565b91505092915050565b5f60208284031215612f5957612f5861238d565b5b5f82015167ffffffffffffffff811115612f7657612f75612391565b5b612f8284828501612f17565b91505092915050565b7f6f776e65720000000000000000000000000000000000000000000000000000005f82015250565b5f612fbf600583612452565b9150612fca82612f8b565b602082019050919050565b5f6040820190508181035f830152612fed8184612af5565b9050818103602083015261300081612fb3565b905092915050565b7f746f6b656e7300000000000000000000000000000000000000000000000000005f82015250565b5f61303c600683612452565b915061304782613008565b602082019050919050565b5f6040820190508181035f83015261306a8184612af5565b9050818103602083015261307d81613030565b905092915050565b7f746f6b656e5f75726900000000000000000000000000000000000000000000005f82015250565b5f6130b9600983612452565b91506130c482613085565b602082019050919050565b5f6040820190508181035f8301526130e78184612af5565b905081810360208301526130fa816130ad565b905092915050565b7f6f70657261746f727300000000000000000000000000000000000000000000005f82015250565b5f613136600983612452565b915061314182613102565b602082019050919050565b5f6040820190508181035f8301526131648184612af5565b905081810360208301526131778161312a565b905092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6131b6826124f2565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036131e8576131e761317f565b5b600182019050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f81905092915050565b5f61323482612448565b61323e8185613220565b935061324e818560208601612462565b80840191505092915050565b7f2200000000000000000000000000000000000000000000000000000000000000815250565b5f61328b828461322a565b91506132968261325a565b60018201915081905092915050565b5f6132af8261325a565b6001820191506132bf828461322a565b915081905092915050565b7f7d00000000000000000000000000000000000000000000000000000000000000815250565b5f6132fb828461322a565b9150613306826132ca565b60018201915081905092915050565b7f7b00000000000000000000000000000000000000000000000000000000000000815250565b5f61334582613315565b600182019150613355828461322a565b915081905092915050565b5f61336b828561322a565b9150613377828461322a565b91508190509392505050565b5f6060820190508181035f83015261339b8186612a5a565b905081810360208301526133af8185612af5565b905081810360408301526133c38184612af5565b9050949350505050565b5f81905092915050565b5f6133e182612adb565b6133eb81856133cd565b93506133fb818560208601612462565b80840191505092915050565b5f61341282846133d7565b915081905092915050565b7f436f736d5761736d2065786563757465206661696c65640000000000000000005f82015250565b5f613451601783612452565b915061345c8261341d565b602082019050919050565b5f6020820190508181035f83015261347e81613445565b9050919050565b5f6080820190506134985f830187612580565b6134a56020830186612580565b6134b2604083018561268b565b81810360608301526134c48184612af5565b905095945050505050565b5f815190506134dd816123c0565b92915050565b5f602082840312156134f8576134f761238d565b5b5f613505848285016134cf565b9150509291505056fea2646970667358221220e5f30d268a1c1cbd830f6459bc10d756e5d2c01715cdaf7b88d3731c44af5bb164736f6c63430008150033 \ No newline at end of file +608060405234801562000010575f80fd5b5060405162003cc638038062003cc68339818101604052810190620000369190620002c3565b8181815f9081620000489190620005b0565b5080600190816200005a9190620005b0565b50505061100260075f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555061100360085f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555061100460095f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508260069081620001349190620005b0565b5050505062000694565b5f604051905090565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6200019f8262000157565b810181811067ffffffffffffffff82111715620001c157620001c062000167565b5b80604052505050565b5f620001d56200013e565b9050620001e3828262000194565b919050565b5f67ffffffffffffffff82111562000205576200020462000167565b5b620002108262000157565b9050602081019050919050565b5f5b838110156200023c5780820151818401526020810190506200021f565b5f8484015250505050565b5f6200025d6200025784620001e8565b620001ca565b9050828152602081018484840111156200027c576200027b62000153565b5b620002898482856200021d565b509392505050565b5f82601f830112620002a857620002a76200014f565b5b8151620002ba84826020860162000247565b91505092915050565b5f805f60608486031215620002dd57620002dc62000147565b5b5f84015167ffffffffffffffff811115620002fd57620002fc6200014b565b5b6200030b8682870162000291565b935050602084015167ffffffffffffffff8111156200032f576200032e6200014b565b5b6200033d8682870162000291565b925050604084015167ffffffffffffffff8111156200036157620003606200014b565b5b6200036f8682870162000291565b9150509250925092565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680620003c857607f821691505b602082108103620003de57620003dd62000383565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f60088302620004427fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000405565b6200044e868362000405565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f62000498620004926200048c8462000466565b6200046f565b62000466565b9050919050565b5f819050919050565b620004b38362000478565b620004cb620004c2826200049f565b84845462000411565b825550505050565b5f90565b620004e1620004d3565b620004ee818484620004a8565b505050565b5b818110156200051557620005095f82620004d7565b600181019050620004f4565b5050565b601f82111562000564576200052e81620003e4565b6200053984620003f6565b8101602085101562000549578190505b620005616200055885620003f6565b830182620004f3565b50505b505050565b5f82821c905092915050565b5f620005865f198460080262000569565b1980831691505092915050565b5f620005a0838362000575565b9150826002028217905092915050565b620005bb8262000379565b67ffffffffffffffff811115620005d757620005d662000167565b5b620005e38254620003b0565b620005f082828562000519565b5f60209050601f83116001811462000626575f841562000611578287015190505b6200061d858262000593565b8655506200068c565b601f1984166200063686620003e4565b5f5b828110156200065f5784890151825560018201915060208501945060208101905062000638565b868310156200067f57848901516200067b601f89168262000575565b8355505b6001600288020188555050505b505050505050565b61362480620006a25f395ff3fe608060405234801561000f575f80fd5b5060043610610109575f3560e01c806370a08231116100a0578063c2aed3021161006f578063c2aed302146102b3578063c87b56dd146102d1578063de4725cc14610301578063e985e9c51461031f578063f00b02551461034f57610109565b806370a082311461022d57806395d89b411461025d578063a22cb4651461027b578063b88d4fde1461029757610109565b806323b872dd116100dc57806323b872dd146101a757806342842e0e146101c35780635c4aead7146101df5780636352211e146101fd57610109565b806301ffc9a71461010d57806306fdde031461013d578063081812fc1461015b578063095ea7b31461018b575b5f80fd5b61012760048036038101906101229190612462565b61036d565b60405161013491906124a7565b60405180910390f35b61014561044e565b604051610152919061254a565b60405180910390f35b6101756004803603810190610170919061259d565b6104dd565b6040516101829190612607565b60405180910390f35b6101a560048036038101906101a0919061264a565b610837565b005b6101c160048036038101906101bc9190612688565b610a73565b005b6101dd60048036038101906101d89190612688565b610d8e565b005b6101e7610dad565b6040516101f4919061254a565b60405180910390f35b6102176004803603810190610212919061259d565b610e39565b6040516102249190612607565b60405180910390f35b610247600480360381019061024291906126d8565b6110c3565b6040516102549190612712565b60405180910390f35b6102656113b9565b604051610272919061254a565b60405180910390f35b61029560048036038101906102909190612755565b611449565b005b6102b160048036038101906102ac91906128bf565b61164c565b005b6102bb611671565b6040516102c8919061299a565b60405180910390f35b6102eb60048036038101906102e6919061259d565b611696565b6040516102f8919061254a565b60405180910390f35b610309611893565b60405161031691906129d3565b60405180910390f35b610339600480360381019061033491906129ec565b6118b8565b60405161034691906124a7565b60405180910390f35b610357611cf2565b6040516103649190612a4a565b60405180910390f35b5f7f80ac58cd000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916148061043757507f5b5e139f000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b80610447575061044682611d17565b5b9050919050565b60605f805461045c90612a90565b80601f016020809104026020016040519081016040528092919081815260200182805461048890612a90565b80156104d35780601f106104aa576101008083540402835291602001916104d3565b820191905f5260205f20905b8154815290600101906020018083116104b657829003601f168201915b5050505050905090565b5f8061052e6040518060400160405280600881526020017f746f6b656e5f696400000000000000000000000000000000000000000000000081525061052961052486611d80565b611e4a565b611e92565b90505f61058061057b6040518060400160405280600981526020017f617070726f76616c73000000000000000000000000000000000000000000000081525061057685611ee4565b611e92565b611ee4565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296006846040518363ffffffff1660e01b81526004016105e0929190612ba5565b5f60405180830381865afa1580156105fa573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906106229190612c48565b90505f60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166387cdf621836040518263ffffffff1660e01b815260040161067f9190612cd9565b5f60405180830381865afa158015610699573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906106c19190612dee565b90505f8151111561082a575f60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166308d858e5835f8151811061071d5761071c612e35565b5b60200260200101516040518263ffffffff1660e01b81526004016107419190612eac565b5f60405180830381865afa15801561075b573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906107839190612c48565b905060095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631778e539826040518263ffffffff1660e01b81526004016107df919061254a565b602060405180830381865afa1580156107fa573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061081e9190612ef3565b95505050505050610832565b5f9450505050505b919050565b5f61091a6040518060400160405280600781526020017f7370656e6465720000000000000000000000000000000000000000000000000081525061091560095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed876040518263ffffffff1660e01b81526004016108ce9190612607565b5f60405180830381865afa1580156108e8573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906109109190612fbc565b611e4a565b611e92565b90505f61096c6040518060400160405280600881526020017f746f6b656e5f696400000000000000000000000000000000000000000000000081525061096761096286611d80565b611e4a565b611e92565b90505f6109fd6109f86040518060400160405280600781526020017f617070726f7665000000000000000000000000000000000000000000000000008152506109f36109ee87876040518060400160405280600181526020017f2c00000000000000000000000000000000000000000000000000000000000000815250611f2c565b611ee4565b611e92565b611ee4565b9050610a0881611f7a565b50838573ffffffffffffffffffffffffffffffffffffffff16610a2a86610e39565b73ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610ae3575f6040517f64a0ae92000000000000000000000000000000000000000000000000000000008152600401610ada9190612607565b60405180910390fd5b610aec81610e39565b73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614610b59576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b509061304d565b60405180910390fd5b5f610c3c6040518060400160405280600981526020017f726563697069656e740000000000000000000000000000000000000000000000815250610c3760095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed876040518263ffffffff1660e01b8152600401610bf09190612607565b5f60405180830381865afa158015610c0a573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190610c329190612fbc565b611e4a565b611e92565b90505f610c8e6040518060400160405280600881526020017f746f6b656e5f6964000000000000000000000000000000000000000000000000815250610c89610c8486611d80565b611e4a565b611e92565b90505f610d1f610d1a6040518060400160405280600c81526020017f7472616e736665725f6e66740000000000000000000000000000000000000000815250610d15610d1087876040518060400160405280600181526020017f2c00000000000000000000000000000000000000000000000000000000000000815250611f2c565b611ee4565b611e92565b611ee4565b9050610d2a81611f7a565b50838573ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a4505050505050565b610da883838360405180602001604052805f81525061164c565b505050565b60068054610dba90612a90565b80601f0160208091040260200160405190810160405280929190818152602001828054610de690612a90565b8015610e315780601f10610e0857610100808354040283529160200191610e31565b820191905f5260205f20905b815481529060010190602001808311610e1457829003601f168201915b505050505081565b5f80610e8a6040518060400160405280600881526020017f746f6b656e5f6964000000000000000000000000000000000000000000000000815250610e85610e8086611d80565b611e4a565b611e92565b90505f610edc610ed76040518060400160405280600881526020017f6f776e65725f6f66000000000000000000000000000000000000000000000000815250610ed285611ee4565b611e92565b611ee4565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296006846040518363ffffffff1660e01b8152600401610f3c929190612ba5565b5f60405180830381865afa158015610f56573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190610f7e9190612c48565b90505f60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166308d858e5836040518263ffffffff1660e01b8152600401610fdb91906130b5565b5f60405180830381865afa158015610ff5573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061101d9190612c48565b905060095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631778e539826040518263ffffffff1660e01b8152600401611079919061254a565b602060405180830381865afa158015611094573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110b89190612ef3565b945050505050919050565b5f8073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603611134575f6040517f89c62b6400000000000000000000000000000000000000000000000000000000815260040161112b9190612607565b60405180910390fd5b5f6112176040518060400160405280600581526020017f6f776e657200000000000000000000000000000000000000000000000000000081525061121260095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed876040518263ffffffff1660e01b81526004016111cb9190612607565b5f60405180830381865afa1580156111e5573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061120d9190612fbc565b611e4a565b611e92565b90505f6112696112646040518060400160405280600681526020017f746f6b656e73000000000000000000000000000000000000000000000000000081525061125f85611ee4565b611e92565b611ee4565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296006846040518363ffffffff1660e01b81526004016112c9929190612ba5565b5f60405180830381865afa1580156112e3573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f8201168201806040525081019061130b9190612c48565b90505f60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166387cdf621836040518263ffffffff1660e01b81526004016113689190613132565b5f60405180830381865afa158015611382573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906113aa9190612dee565b90508051945050505050919050565b6060600180546113c890612a90565b80601f01602080910402602001604051908101604052809291908181526020018280546113f490612a90565b801561143f5780601f106114165761010080835404028352916020019161143f565b820191905f5260205f20905b81548152906001019060200180831161142257829003601f168201915b5050505050905090565b5f61153461152f6040518060400160405280600881526020017f6f70657261746f7200000000000000000000000000000000000000000000000081525061152a60095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed886040518263ffffffff1660e01b81526004016114e39190612607565b5f60405180830381865afa1580156114fd573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906115259190612fbc565b611e4a565b611e92565b611ee4565b905081156115915761158b6115866115816040518060400160405280600b81526020017f617070726f76655f616c6c00000000000000000000000000000000000000000081525084611e92565b611ee4565b611f7a565b506115e2565b6115e06115db6115d66040518060400160405280600a81526020017f7265766f6b655f616c6c0000000000000000000000000000000000000000000081525084611e92565b611ee4565b611f7a565b505b8273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c318460405161163f91906124a7565b60405180910390a3505050565b611657848484610a73565b61166b6116626120f8565b858585856120ff565b50505050565b60095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60606116a182610e39565b505f6116f26040518060400160405280600881526020017f746f6b656e5f69640000000000000000000000000000000000000000000000008152506116ed6116e886611d80565b611e4a565b611e92565b90505f61174461173f6040518060400160405280600881526020017f6e66745f696e666f00000000000000000000000000000000000000000000000081525061173a85611ee4565b611e92565b611ee4565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296006846040518363ffffffff1660e01b81526004016117a4929190612ba5565b5f60405180830381865afa1580156117be573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906117e69190612c48565b90505f60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166308d858e5836040518263ffffffff1660e01b815260040161184391906131af565b5f60405180830381865afa15801561185d573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906118859190612c48565b905080945050505050919050565b60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f8061199c6040518060400160405280600581526020017f6f776e657200000000000000000000000000000000000000000000000000000081525061199760095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630c3c20ed886040518263ffffffff1660e01b81526004016119509190612607565b5f60405180830381865afa15801561196a573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f820116820180604052508101906119929190612fbc565b611e4a565b611e92565b90505f6119ee6119e96040518060400160405280600d81526020017f616c6c5f6f70657261746f7273000000000000000000000000000000000000008152506119e485611ee4565b611e92565b611ee4565b90505f60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166306d81d296006846040518363ffffffff1660e01b8152600401611a4e929190612ba5565b5f60405180830381865afa158015611a68573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611a909190612c48565b90505f60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166387cdf621836040518263ffffffff1660e01b8152600401611aed919061322c565b5f60405180830381865afa158015611b07573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611b2f9190612dee565b90505f5b8151811015611ce3575f60085f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166308d858e5848481518110611b8d57611b8c612e35565b5b60200260200101516040518263ffffffff1660e01b8152600401611bb19190612eac565b5f60405180830381865afa158015611bcb573d5f803e3d5ffd5b505050506040513d5f823e3d601f19601f82011682018060405250810190611bf39190612c48565b90508773ffffffffffffffffffffffffffffffffffffffff1660095f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16631778e539836040518263ffffffff1660e01b8152600401611c66919061254a565b602060405180830381865afa158015611c81573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611ca59190612ef3565b73ffffffffffffffffffffffffffffffffffffffff1603611ccf5760019650505050505050611cec565b508080611cdb9061328c565b915050611b33565b505f9450505050505b92915050565b60075f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b60605f6001611d8e846122ab565b0190505f8167ffffffffffffffff811115611dac57611dab61279b565b5b6040519080825280601f01601f191660200182016040528015611dde5781602001600182028036833780820191505090505b5090505f82602001820190505b600115611e3f578080600190039150507f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a8581611e3457611e336132d3565b5b0494505f8503611deb575b819350505050919050565b606081604051602001611e5d9190613360565b604051602081830303815290604052604051602001611e7c9190613385565b6040516020818303038152906040529050919050565b6060611edc611ea084611e4a565b836040518060400160405280600181526020017f3a00000000000000000000000000000000000000000000000000000000000000815250611f2c565b905092915050565b606081604051602001611ef791906133d0565b604051602081830303815290604052604051602001611f16919061341b565b6040516020818303038152906040529050919050565b6060838284604051602001611f42929190613440565b604051602081830303815290604052604051602001611f62929190613440565b60405160208183030381529060405290509392505050565b60605f8061100273ffffffffffffffffffffffffffffffffffffffff166006856040518060400160405280600281526020017f5b5d000000000000000000000000000000000000000000000000000000000000815250604051602401611fe293929190613463565b6040516020818303038152906040527f44d227ae000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161206c91906134e7565b5f60405180830381855af49150503d805f81146120a4576040519150601f19603f3d011682016040523d82523d5f602084013e6120a9565b606091505b5091509150816120ee576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016120e590613547565b60405180910390fd5b8092505050919050565b5f33905090565b5f8373ffffffffffffffffffffffffffffffffffffffff163b11156122a4578273ffffffffffffffffffffffffffffffffffffffff1663150b7a02868685856040518563ffffffff1660e01b815260040161215d9493929190613565565b6020604051808303815f875af192505050801561219857506040513d601f19601f8201168201806040525081019061219591906135c3565b60015b612219573d805f81146121c6576040519150601f19603f3d011682016040523d82523d5f602084013e6121cb565b606091505b505f81510361221157836040517f64a0ae920000000000000000000000000000000000000000000000000000000081526004016122089190612607565b60405180910390fd5b805181602001fd5b63150b7a0260e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916146122a257836040517f64a0ae920000000000000000000000000000000000000000000000000000000081526004016122999190612607565b60405180910390fd5b505b5050505050565b5f805f90507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008310612307577a184f03e93ff9f4daa797ed6e38ed64bf6a1f01000000000000000083816122fd576122fc6132d3565b5b0492506040810190505b6d04ee2d6d415b85acef81000000008310612344576d04ee2d6d415b85acef8100000000838161233a576123396132d3565b5b0492506020810190505b662386f26fc10000831061237357662386f26fc100008381612369576123686132d3565b5b0492506010810190505b6305f5e100831061239c576305f5e1008381612392576123916132d3565b5b0492506008810190505b61271083106123c15761271083816123b7576123b66132d3565b5b0492506004810190505b606483106123e457606483816123da576123d96132d3565b5b0492506002810190505b600a83106123f3576001810190505b80915050919050565b5f604051905090565b5f80fd5b5f80fd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b6124418161240d565b811461244b575f80fd5b50565b5f8135905061245c81612438565b92915050565b5f6020828403121561247757612476612405565b5b5f6124848482850161244e565b91505092915050565b5f8115159050919050565b6124a18161248d565b82525050565b5f6020820190506124ba5f830184612498565b92915050565b5f81519050919050565b5f82825260208201905092915050565b5f5b838110156124f75780820151818401526020810190506124dc565b5f8484015250505050565b5f601f19601f8301169050919050565b5f61251c826124c0565b61252681856124ca565b93506125368185602086016124da565b61253f81612502565b840191505092915050565b5f6020820190508181035f8301526125628184612512565b905092915050565b5f819050919050565b61257c8161256a565b8114612586575f80fd5b50565b5f8135905061259781612573565b92915050565b5f602082840312156125b2576125b1612405565b5b5f6125bf84828501612589565b91505092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6125f1826125c8565b9050919050565b612601816125e7565b82525050565b5f60208201905061261a5f8301846125f8565b92915050565b612629816125e7565b8114612633575f80fd5b50565b5f8135905061264481612620565b92915050565b5f80604083850312156126605761265f612405565b5b5f61266d85828601612636565b925050602061267e85828601612589565b9150509250929050565b5f805f6060848603121561269f5761269e612405565b5b5f6126ac86828701612636565b93505060206126bd86828701612636565b92505060406126ce86828701612589565b9150509250925092565b5f602082840312156126ed576126ec612405565b5b5f6126fa84828501612636565b91505092915050565b61270c8161256a565b82525050565b5f6020820190506127255f830184612703565b92915050565b6127348161248d565b811461273e575f80fd5b50565b5f8135905061274f8161272b565b92915050565b5f806040838503121561276b5761276a612405565b5b5f61277885828601612636565b925050602061278985828601612741565b9150509250929050565b5f80fd5b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6127d182612502565b810181811067ffffffffffffffff821117156127f0576127ef61279b565b5b80604052505050565b5f6128026123fc565b905061280e82826127c8565b919050565b5f67ffffffffffffffff82111561282d5761282c61279b565b5b61283682612502565b9050602081019050919050565b828183375f83830152505050565b5f61286361285e84612813565b6127f9565b90508281526020810184848401111561287f5761287e612797565b5b61288a848285612843565b509392505050565b5f82601f8301126128a6576128a5612793565b5b81356128b6848260208601612851565b91505092915050565b5f805f80608085870312156128d7576128d6612405565b5b5f6128e487828801612636565b94505060206128f587828801612636565b935050604061290687828801612589565b925050606085013567ffffffffffffffff81111561292757612926612409565b5b61293387828801612892565b91505092959194509250565b5f819050919050565b5f61296261295d612958846125c8565b61293f565b6125c8565b9050919050565b5f61297382612948565b9050919050565b5f61298482612969565b9050919050565b6129948161297a565b82525050565b5f6020820190506129ad5f83018461298b565b92915050565b5f6129bd82612969565b9050919050565b6129cd816129b3565b82525050565b5f6020820190506129e65f8301846129c4565b92915050565b5f8060408385031215612a0257612a01612405565b5b5f612a0f85828601612636565b9250506020612a2085828601612636565b9150509250929050565b5f612a3482612969565b9050919050565b612a4481612a2a565b82525050565b5f602082019050612a5d5f830184612a3b565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680612aa757607f821691505b602082108103612aba57612ab9612a63565b5b50919050565b5f819050815f5260205f209050919050565b5f8154612ade81612a90565b612ae881866124ca565b9450600182165f8114612b025760018114612b1857612b4a565b60ff198316865281151560200286019350612b4a565b612b2185612ac0565b5f5b83811015612b4257815481890152600182019150602081019050612b23565b808801955050505b50505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f612b7782612b53565b612b818185612b5d565b9350612b918185602086016124da565b612b9a81612502565b840191505092915050565b5f6040820190508181035f830152612bbd8185612ad2565b90508181036020830152612bd18184612b6d565b90509392505050565b5f612bec612be784612813565b6127f9565b905082815260208101848484011115612c0857612c07612797565b5b612c138482856124da565b509392505050565b5f82601f830112612c2f57612c2e612793565b5b8151612c3f848260208601612bda565b91505092915050565b5f60208284031215612c5d57612c5c612405565b5b5f82015167ffffffffffffffff811115612c7a57612c79612409565b5b612c8684828501612c1b565b91505092915050565b7f617070726f76616c7300000000000000000000000000000000000000000000005f82015250565b5f612cc36009836124ca565b9150612cce82612c8f565b602082019050919050565b5f6040820190508181035f830152612cf18184612b6d565b90508181036020830152612d0481612cb7565b905092915050565b5f67ffffffffffffffff821115612d2657612d2561279b565b5b602082029050602081019050919050565b5f80fd5b5f612d4d612d4884612d0c565b6127f9565b90508083825260208201905060208402830185811115612d7057612d6f612d37565b5b835b81811015612db757805167ffffffffffffffff811115612d9557612d94612793565b5b808601612da28982612c1b565b85526020850194505050602081019050612d72565b5050509392505050565b5f82601f830112612dd557612dd4612793565b5b8151612de5848260208601612d3b565b91505092915050565b5f60208284031215612e0357612e02612405565b5b5f82015167ffffffffffffffff811115612e2057612e1f612409565b5b612e2c84828501612dc1565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f7370656e646572000000000000000000000000000000000000000000000000005f82015250565b5f612e966007836124ca565b9150612ea182612e62565b602082019050919050565b5f6040820190508181035f830152612ec48184612b6d565b90508181036020830152612ed781612e8a565b905092915050565b5f81519050612eed81612620565b92915050565b5f60208284031215612f0857612f07612405565b5b5f612f1584828501612edf565b91505092915050565b5f67ffffffffffffffff821115612f3857612f3761279b565b5b612f4182612502565b9050602081019050919050565b5f612f60612f5b84612f1e565b6127f9565b905082815260208101848484011115612f7c57612f7b612797565b5b612f878482856124da565b509392505050565b5f82601f830112612fa357612fa2612793565b5b8151612fb3848260208601612f4e565b91505092915050565b5f60208284031215612fd157612fd0612405565b5b5f82015167ffffffffffffffff811115612fee57612fed612409565b5b612ffa84828501612f8f565b91505092915050565b7f6066726f6d60206d75737420626520746865206f776e657200000000000000005f82015250565b5f6130376018836124ca565b915061304282613003565b602082019050919050565b5f6020820190508181035f8301526130648161302b565b9050919050565b7f6f776e65720000000000000000000000000000000000000000000000000000005f82015250565b5f61309f6005836124ca565b91506130aa8261306b565b602082019050919050565b5f6040820190508181035f8301526130cd8184612b6d565b905081810360208301526130e081613093565b905092915050565b7f746f6b656e7300000000000000000000000000000000000000000000000000005f82015250565b5f61311c6006836124ca565b9150613127826130e8565b602082019050919050565b5f6040820190508181035f83015261314a8184612b6d565b9050818103602083015261315d81613110565b905092915050565b7f746f6b656e5f75726900000000000000000000000000000000000000000000005f82015250565b5f6131996009836124ca565b91506131a482613165565b602082019050919050565b5f6040820190508181035f8301526131c78184612b6d565b905081810360208301526131da8161318d565b905092915050565b7f6f70657261746f727300000000000000000000000000000000000000000000005f82015250565b5f6132166009836124ca565b9150613221826131e2565b602082019050919050565b5f6040820190508181035f8301526132448184612b6d565b905081810360208301526132578161320a565b905092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6132968261256a565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036132c8576132c761325f565b5b600182019050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f81905092915050565b5f613314826124c0565b61331e8185613300565b935061332e8185602086016124da565b80840191505092915050565b7f2200000000000000000000000000000000000000000000000000000000000000815250565b5f61336b828461330a565b91506133768261333a565b60018201915081905092915050565b5f61338f8261333a565b60018201915061339f828461330a565b915081905092915050565b7f7d00000000000000000000000000000000000000000000000000000000000000815250565b5f6133db828461330a565b91506133e6826133aa565b60018201915081905092915050565b7f7b00000000000000000000000000000000000000000000000000000000000000815250565b5f613425826133f5565b600182019150613435828461330a565b915081905092915050565b5f61344b828561330a565b9150613457828461330a565b91508190509392505050565b5f6060820190508181035f83015261347b8186612ad2565b9050818103602083015261348f8185612b6d565b905081810360408301526134a38184612b6d565b9050949350505050565b5f81905092915050565b5f6134c182612b53565b6134cb81856134ad565b93506134db8185602086016124da565b80840191505092915050565b5f6134f282846134b7565b915081905092915050565b7f436f736d5761736d2065786563757465206661696c65640000000000000000005f82015250565b5f6135316017836124ca565b915061353c826134fd565b602082019050919050565b5f6020820190508181035f83015261355e81613525565b9050919050565b5f6080820190506135785f8301876125f8565b61358560208301866125f8565b6135926040830185612703565b81810360608301526135a48184612b6d565b905095945050505050565b5f815190506135bd81612438565b92915050565b5f602082840312156135d8576135d7612405565b5b5f6135e5848285016135af565b9150509291505056fea2646970667358221220a2c419c1e43b74d07c7e3f63d55af936cde414fdc186470d597ab1da5b53412e64736f6c63430008150033 \ No newline at end of file diff --git a/x/evm/artifacts/cw721/artifacts.go b/x/evm/artifacts/cw721/artifacts.go index a2d0b5697..18e5f812a 100644 --- a/x/evm/artifacts/cw721/artifacts.go +++ b/x/evm/artifacts/cw721/artifacts.go @@ -10,7 +10,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" ) -const CurrentVersion uint16 = 1 +const CurrentVersion uint16 = 2 //go:embed CW721ERC721Pointer.abi //go:embed CW721ERC721Pointer.bin diff --git a/x/evm/keeper/pointer.go b/x/evm/keeper/pointer.go index 2708f372c..e87192834 100644 --- a/x/evm/keeper/pointer.go +++ b/x/evm/keeper/pointer.go @@ -8,6 +8,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" "github.com/sei-protocol/sei-chain/x/evm/artifacts/cw20" + "github.com/sei-protocol/sei-chain/x/evm/artifacts/cw721" "github.com/sei-protocol/sei-chain/x/evm/artifacts/erc20" "github.com/sei-protocol/sei-chain/x/evm/artifacts/erc721" "github.com/sei-protocol/sei-chain/x/evm/artifacts/native" @@ -55,7 +56,7 @@ func (k *Keeper) DeleteERC20CW20Pointer(ctx sdk.Context, cw20Address string, ver } func (k *Keeper) SetERC721CW721Pointer(ctx sdk.Context, cw721Address string, addr common.Address) error { - return k.SetPointerInfo(ctx, types.PointerERC721CW721Key(cw721Address), addr[:], cw20.CurrentVersion) + return k.SetPointerInfo(ctx, types.PointerERC721CW721Key(cw721Address), addr[:], cw721.CurrentVersion) } func (k *Keeper) SetERC721CW721PointerWithVersion(ctx sdk.Context, cw721Address string, addr common.Address, version uint16) error { From 2f54f96d6f9dd0cf3b87710c8a47d6b42c9db04a Mon Sep 17 00:00:00 2001 From: codchen Date: Tue, 9 Apr 2024 13:19:29 +0800 Subject: [PATCH 26/34] add comments (#1520) --- x/evm/ante/preprocess.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x/evm/ante/preprocess.go b/x/evm/ante/preprocess.go index a609293e9..0d5ad21de 100644 --- a/x/evm/ante/preprocess.go +++ b/x/evm/ante/preprocess.go @@ -71,6 +71,8 @@ func (p *EVMPreprocessDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate // check if the account has enough balance (without charging) baseDenom := p.evmKeeper.GetBaseDenom(ctx) seiBalance := p.evmKeeper.BankKeeper().GetBalance(ctx, seiAddr, baseDenom).Amount + // no need to get wei balance here since the sei address is not used directly in EVM and thus does not + // contain any wei, so any wei balance in `sdk.AccAddress(evmAddr[:])` would not add up to 1usei anyway. castBalance := p.evmKeeper.BankKeeper().GetBalance(ctx, sdk.AccAddress(evmAddr[:]), baseDenom).Amount if new(big.Int).Add(seiBalance.BigInt(), castBalance.BigInt()).Cmp(new(big.Int).SetUint64(BalanceThreshold)) < 0 { return ctx, sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, "account needs to have at least 1Sei to force association") From a0e38d4f689cabc12500f6306c812c8aa291f30e Mon Sep 17 00:00:00 2001 From: codchen Date: Tue, 9 Apr 2024 18:28:04 +0800 Subject: [PATCH 27/34] Set unknown method call on precompiles to be non-zero (#1519) --- precompiles/addr/addr.go | 4 ++-- precompiles/bank/bank.go | 4 ++-- precompiles/bank/bank_test.go | 2 +- precompiles/common/precompiles.go | 2 ++ precompiles/distribution/distribution.go | 4 ++-- precompiles/gov/gov.go | 4 ++-- precompiles/ibc/ibc.go | 4 ++-- precompiles/json/json.go | 2 +- precompiles/oracle/oracle.go | 4 ++-- precompiles/pointer/pointer.go | 2 +- precompiles/staking/staking.go | 4 ++-- precompiles/wasmd/wasmd.go | 4 ++-- precompiles/wasmd/wasmd_test.go | 2 +- 13 files changed, 22 insertions(+), 20 deletions(-) diff --git a/precompiles/addr/addr.go b/precompiles/addr/addr.go index 939e9b28a..ee1ba2ddc 100644 --- a/precompiles/addr/addr.go +++ b/precompiles/addr/addr.go @@ -72,13 +72,13 @@ func NewPrecompile(evmKeeper pcommon.EVMKeeper) (*Precompile, error) { func (p Precompile) RequiredGas(input []byte) uint64 { methodID, err := pcommon.ExtractMethodID(input) if err != nil { - return 0 + 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 0 + return pcommon.UnknownMethodCallGas } return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) diff --git a/precompiles/bank/bank.go b/precompiles/bank/bank.go index cc10a5662..88920705e 100644 --- a/precompiles/bank/bank.go +++ b/precompiles/bank/bank.go @@ -101,13 +101,13 @@ func NewPrecompile(bankKeeper pcommon.BankKeeper, evmKeeper pcommon.EVMKeeper) ( func (p Precompile) RequiredGas(input []byte) uint64 { methodID, err := pcommon.ExtractMethodID(input) if err != nil { - return 0 + 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 0 + return pcommon.UnknownMethodCallGas } return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) diff --git a/precompiles/bank/bank_test.go b/precompiles/bank/bank_test.go index 6ac3454f6..f2cc9e863 100644 --- a/precompiles/bank/bank_test.go +++ b/precompiles/bank/bank_test.go @@ -196,7 +196,7 @@ func TestRequiredGas(t *testing.T) { balanceRequiredGas := p.RequiredGas(p.BalanceID) require.Equal(t, uint64(1000), balanceRequiredGas) // invalid method - require.Equal(t, uint64(0), p.RequiredGas([]byte{1, 1, 1, 1})) + require.Equal(t, uint64(3000), p.RequiredGas([]byte{1, 1, 1, 1})) } func TestAddress(t *testing.T) { diff --git a/precompiles/common/precompiles.go b/precompiles/common/precompiles.go index 472f5d50a..d0a2e0012 100644 --- a/precompiles/common/precompiles.go +++ b/precompiles/common/precompiles.go @@ -12,6 +12,8 @@ import ( "github.com/sei-protocol/sei-chain/x/evm/state" ) +const UnknownMethodCallGas uint64 = 3000 + type Contexter interface { Ctx() sdk.Context } diff --git a/precompiles/distribution/distribution.go b/precompiles/distribution/distribution.go index 59d456799..e90180aab 100644 --- a/precompiles/distribution/distribution.go +++ b/precompiles/distribution/distribution.go @@ -78,7 +78,7 @@ func NewPrecompile(distrKeeper pcommon.DistributionKeeper, evmKeeper pcommon.EVM func (p Precompile) RequiredGas(input []byte) uint64 { methodID, err := pcommon.ExtractMethodID(input) if err != nil { - return 0 + return pcommon.UnknownMethodCallGas } if bytes.Equal(methodID, p.SetWithdrawAddrID) { @@ -88,7 +88,7 @@ func (p Precompile) RequiredGas(input []byte) uint64 { } // This should never happen since this is going to fail during Run - return 0 + return pcommon.UnknownMethodCallGas } func (p Precompile) Address() common.Address { diff --git a/precompiles/gov/gov.go b/precompiles/gov/gov.go index 52bab5b69..d628cad4f 100644 --- a/precompiles/gov/gov.go +++ b/precompiles/gov/gov.go @@ -81,7 +81,7 @@ func NewPrecompile(govKeeper pcommon.GovKeeper, evmKeeper pcommon.EVMKeeper, ban func (p Precompile) RequiredGas(input []byte) uint64 { methodID, err := pcommon.ExtractMethodID(input) if err != nil { - return 0 + return pcommon.UnknownMethodCallGas } if bytes.Equal(methodID, p.VoteID) { @@ -91,7 +91,7 @@ func (p Precompile) RequiredGas(input []byte) uint64 { } // This should never happen since this is going to fail during Run - return 0 + return pcommon.UnknownMethodCallGas } func (p Precompile) Address() common.Address { diff --git a/precompiles/ibc/ibc.go b/precompiles/ibc/ibc.go index 9020c4c5f..953090c90 100644 --- a/precompiles/ibc/ibc.go +++ b/precompiles/ibc/ibc.go @@ -82,13 +82,13 @@ func NewPrecompile(transferKeeper pcommon.TransferKeeper, evmKeeper pcommon.EVMK func (p Precompile) RequiredGas(input []byte) uint64 { methodID, err := pcommon.ExtractMethodID(input) if err != nil { - return 0 + 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 0 + return pcommon.UnknownMethodCallGas } return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) diff --git a/precompiles/json/json.go b/precompiles/json/json.go index 098be447e..5de0ae783 100644 --- a/precompiles/json/json.go +++ b/precompiles/json/json.go @@ -75,7 +75,7 @@ func NewPrecompile() (*Precompile, error) { // RequiredGas returns the required bare minimum gas to execute the precompile. func (p Precompile) RequiredGas(input []byte) uint64 { if len(input) < 4 { - return 0 + return pcommon.UnknownMethodCallGas } return uint64(GasCostPerByte * (len(input) - 4)) } diff --git a/precompiles/oracle/oracle.go b/precompiles/oracle/oracle.go index 7b6d1be9d..20c97d578 100644 --- a/precompiles/oracle/oracle.go +++ b/precompiles/oracle/oracle.go @@ -97,13 +97,13 @@ func NewPrecompile(oracleKeeper pcommon.OracleKeeper, evmKeeper pcommon.EVMKeepe func (p Precompile) RequiredGas(input []byte) uint64 { methodID, err := pcommon.ExtractMethodID(input) if err != nil { - return 0 + 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 0 + return pcommon.UnknownMethodCallGas } return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) diff --git a/precompiles/pointer/pointer.go b/precompiles/pointer/pointer.go index 0a2aceaa3..8428178fc 100644 --- a/precompiles/pointer/pointer.go +++ b/precompiles/pointer/pointer.go @@ -84,7 +84,7 @@ func NewPrecompile(evmKeeper pcommon.EVMKeeper, bankKeeper pcommon.BankKeeper, w // RequiredGas returns the required bare minimum gas to execute the precompile. func (p Precompile) RequiredGas(input []byte) uint64 { // gas is calculated dynamically - return 0 + return pcommon.UnknownMethodCallGas } func (p Precompile) Address() common.Address { diff --git a/precompiles/staking/staking.go b/precompiles/staking/staking.go index 5a4804056..828e96cf2 100644 --- a/precompiles/staking/staking.go +++ b/precompiles/staking/staking.go @@ -86,7 +86,7 @@ func NewPrecompile(stakingKeeper pcommon.StakingKeeper, evmKeeper pcommon.EVMKee func (p Precompile) RequiredGas(input []byte) uint64 { methodID, err := pcommon.ExtractMethodID(input) if err != nil { - return 0 + return pcommon.UnknownMethodCallGas } if bytes.Equal(methodID, p.DelegateID) { @@ -98,7 +98,7 @@ func (p Precompile) RequiredGas(input []byte) uint64 { } // This should never happen since this is going to fail during Run - return 0 + return pcommon.UnknownMethodCallGas } func (p Precompile) Address() common.Address { diff --git a/precompiles/wasmd/wasmd.go b/precompiles/wasmd/wasmd.go index b3f73cc57..d68897958 100644 --- a/precompiles/wasmd/wasmd.go +++ b/precompiles/wasmd/wasmd.go @@ -85,13 +85,13 @@ func NewPrecompile(evmKeeper pcommon.EVMKeeper, wasmdKeeper pcommon.WasmdKeeper, func (p Precompile) RequiredGas(input []byte) uint64 { methodID, err := pcommon.ExtractMethodID(input) if err != nil { - return 0 + 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 0 + return pcommon.UnknownMethodCallGas } return p.Precompile.RequiredGas(input, p.IsTransaction(method.Name)) diff --git a/precompiles/wasmd/wasmd_test.go b/precompiles/wasmd/wasmd_test.go index 75fb4a408..8647a79bc 100644 --- a/precompiles/wasmd/wasmd_test.go +++ b/precompiles/wasmd/wasmd_test.go @@ -25,7 +25,7 @@ func TestRequiredGas(t *testing.T) { require.Equal(t, uint64(2000), p.RequiredGas(p.ExecuteID)) require.Equal(t, uint64(2000), p.RequiredGas(p.InstantiateID)) require.Equal(t, uint64(1000), p.RequiredGas(p.QueryID)) - require.Equal(t, uint64(0), p.RequiredGas([]byte{15, 15, 15, 15})) // invalid method + require.Equal(t, uint64(3000), p.RequiredGas([]byte{15, 15, 15, 15})) // invalid method } func TestAddress(t *testing.T) { From 2b16fd7c0a3e8edd06069e1b19b3b9b615392ae3 Mon Sep 17 00:00:00 2001 From: codchen Date: Tue, 9 Apr 2024 18:28:13 +0800 Subject: [PATCH 28/34] Fix extractAsBytesList in json precompile (#1521) --- precompiles/json/json.go | 8 ++++++-- precompiles/json/json_test.go | 8 ++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/precompiles/json/json.go b/precompiles/json/json.go index 5de0ae783..4f443eae3 100644 --- a/precompiles/json/json.go +++ b/precompiles/json/json.go @@ -155,7 +155,7 @@ func (p Precompile) extractAsBytesList(_ sdk.Context, method *abi.Method, args [ // 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{} + decoded := map[string]gjson.RawMessage{} if err := gjson.Unmarshal(bz, &decoded); err != nil { return nil, err } @@ -164,8 +164,12 @@ func (p Precompile) extractAsBytesList(_ sdk.Context, method *abi.Method, args [ 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(result, func(r gjson.RawMessage) []byte { return []byte(r) })) + 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) { diff --git a/precompiles/json/json_test.go b/precompiles/json/json_test.go index 80710c75a..a559b45ae 100644 --- a/precompiles/json/json_test.go +++ b/precompiles/json/json_test.go @@ -63,16 +63,16 @@ func TestExtractAsBytesList(t *testing.T) { expectedOutput [][]byte }{ { - []byte("{\"key\":[]}"), + []byte("{\"key\":[],\"key2\":1}"), [][]byte{}, }, { - []byte("{\"key\":[1,2,3]}"), + []byte("{\"key\":[1,2,3],\"key2\":1}"), [][]byte{[]byte("1"), []byte("2"), []byte("3")}, }, { - []byte("{\"key\":[\"1\", \"2\"]}"), + []byte("{\"key\":[\"1\", \"2\"],\"key2\":1}"), [][]byte{[]byte("\"1\""), []byte("\"2\"")}, }, { - []byte("{\"key\":[{\"nested\":1}, {\"nested\":2}]}"), + []byte("{\"key\":[{\"nested\":1}, {\"nested\":2}],\"key2\":1}"), [][]byte{[]byte("{\"nested\":1}"), []byte("{\"nested\":2}")}, }, } { From 1d380ccde6325dbfe149f7ae6c18677bd2409481 Mon Sep 17 00:00:00 2001 From: codchen Date: Tue, 9 Apr 2024 18:28:24 +0800 Subject: [PATCH 29/34] Disallow balance change for self-destructed address (#1522) --- x/evm/state/balance.go | 8 ++++++++ x/evm/state/balance_test.go | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/x/evm/state/balance.go b/x/evm/state/balance.go index 0ed16f6b1..f4e7b23a5 100644 --- a/x/evm/state/balance.go +++ b/x/evm/state/balance.go @@ -18,6 +18,10 @@ func (s *DBImpl) SubBalance(evmAddr common.Address, amt *big.Int, reason tracing s.AddBalance(evmAddr, new(big.Int).Neg(amt), reason) return } + if s.HasSelfDestructed(evmAddr) { + // redirect coins to fee collector, since simply burning here would cause coin supply mismatch + evmAddr, _ = s.k.GetFeeCollectorAddress(s.ctx) + } usei, wei := SplitUseiWeiAmount(amt) addr := s.getSeiAddress(evmAddr) @@ -50,6 +54,10 @@ func (s *DBImpl) AddBalance(evmAddr common.Address, amt *big.Int, reason tracing s.SubBalance(evmAddr, new(big.Int).Neg(amt), reason) return } + if s.HasSelfDestructed(evmAddr) { + // redirect coins to fee collector, since simply burning here would cause coin supply mismatch + evmAddr, _ = s.k.GetFeeCollectorAddress(s.ctx) + } usei, wei := SplitUseiWeiAmount(amt) addr := s.getSeiAddress(evmAddr) diff --git a/x/evm/state/balance_test.go b/x/evm/state/balance_test.go index 1201d3005..d732b2fb3 100644 --- a/x/evm/state/balance_test.go +++ b/x/evm/state/balance_test.go @@ -31,6 +31,12 @@ func TestAddBalance(t *testing.T) { require.Nil(t, db.Err()) require.Equal(t, db.GetBalance(evmAddr), big.NewInt(10000000000000)) require.Equal(t, db.GetBalance(evmAddr2), big.NewInt(5000000000000)) + + _, evmAddr3 := testkeeper.MockAddressPair() + db.SelfDestruct(evmAddr3) + db.AddBalance(evmAddr2, big.NewInt(5000000000000), tracing.BalanceChangeUnspecified) + require.Nil(t, db.Err()) + require.Equal(t, db.GetBalance(evmAddr3), big.NewInt(0)) } func TestSubBalance(t *testing.T) { @@ -62,6 +68,12 @@ func TestSubBalance(t *testing.T) { // insufficient balance db.SubBalance(evmAddr2, big.NewInt(10000000000000), tracing.BalanceChangeUnspecified) require.NotNil(t, db.Err()) + + _, evmAddr3 := testkeeper.MockAddressPair() + db.SelfDestruct(evmAddr3) + db.SubBalance(evmAddr2, big.NewInt(5000000000000), tracing.BalanceChangeUnspecified) + require.Nil(t, db.Err()) + require.Equal(t, db.GetBalance(evmAddr3), big.NewInt(0)) } func TestSetBalance(t *testing.T) { From 75d5210b7828a4bf8fab2ed301be3ac15d2ab645 Mon Sep 17 00:00:00 2001 From: codchen Date: Tue, 9 Apr 2024 22:10:35 +0800 Subject: [PATCH 30/34] Bump geth and add checks for staticcall/delegatecall (#1524) --- go.mod | 4 +--- go.sum | 7 ++---- precompiles/addr/addr.go | 2 +- precompiles/bank/bank.go | 19 +++++++++++---- precompiles/bank/bank_test.go | 26 ++++++++++---------- precompiles/distribution/distribution.go | 8 ++++++- precompiles/gov/gov.go | 8 ++++++- precompiles/ibc/ibc.go | 7 ++++-- precompiles/ibc/ibc_test.go | 2 +- precompiles/json/json.go | 2 +- precompiles/json/json_test.go | 6 ++--- precompiles/oracle/oracle.go | 2 +- precompiles/oracle/oracle_test.go | 4 ++-- precompiles/pointer/pointer.go | 11 +++++++-- precompiles/pointer/pointer_test.go | 6 ++--- precompiles/pointerview/pointerview.go | 2 +- precompiles/staking/staking.go | 8 ++++++- precompiles/wasmd/wasmd.go | 24 ++++++++++++++----- precompiles/wasmd/wasmd_test.go | 30 ++++++++++++------------ 19 files changed, 111 insertions(+), 67 deletions(-) diff --git a/go.mod b/go.mod index bc6ad8a02..a5c04de9f 100644 --- a/go.mod +++ b/go.mod @@ -125,7 +125,6 @@ require ( github.com/fzipp/gocyclo v0.5.1 // indirect github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff // indirect github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 // indirect - github.com/ghodss/yaml v1.0.0 // indirect github.com/go-critic/go-critic v0.6.3 // indirect github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.1 // indirect @@ -173,7 +172,6 @@ require ( github.com/gostaticanalysis/nilerr v0.1.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-bexpr v0.1.10 // indirect @@ -351,7 +349,7 @@ replace ( github.com/cosmos/cosmos-sdk => github.com/sei-protocol/sei-cosmos v0.2.80-seiv2 github.com/cosmos/iavl => github.com/sei-protocol/sei-iavl v0.1.9 github.com/cosmos/ibc-go/v3 => github.com/sei-protocol/sei-ibc-go/v3 v3.3.0 - github.com/ethereum/go-ethereum => github.com/sei-protocol/go-ethereum v1.13.5-sei-9.0.20240327165640-6ab0d196bac6 + github.com/ethereum/go-ethereum => github.com/sei-protocol/go-ethereum v1.13.5-sei-13 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.33 // Latest goleveldb is broken, we have to stick to this version diff --git a/go.sum b/go.sum index 0795a73d1..55dc77b06 100644 --- a/go.sum +++ b/go.sum @@ -457,7 +457,6 @@ github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2Gihuqh github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/ghemawat/stream v0.0.0-20171120220530-696b145b53b9/go.mod h1:106OIgooyS7OzLDOpUGgm9fA3bQENb/cFSyyBmMoJDs= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -766,8 +765,6 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/guptarohit/asciigraph v0.5.5/go.mod h1:dYl5wwK4gNsnFf9Zp+l06rFiDZ5YtXM6x7SRWZ3KGag= @@ -1346,8 +1343,8 @@ github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod github.com/securego/gosec/v2 v2.11.0 h1:+PDkpzR41OI2jrw1q6AdXZCbsNGNGT7pQjal0H0cArI= github.com/securego/gosec/v2 v2.11.0/go.mod h1:SX8bptShuG8reGC0XS09+a4H2BoWSJi+fscA+Pulbpo= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= -github.com/sei-protocol/go-ethereum v1.13.5-sei-9.0.20240327165640-6ab0d196bac6 h1:AU9VAvZsTGibuzzVmoX3taGOWjPJJct5ypXDg6vKKu0= -github.com/sei-protocol/go-ethereum v1.13.5-sei-9.0.20240327165640-6ab0d196bac6/go.mod h1:kcRZmuzRn1lVejiFNTz4l4W7imnpq1bDAnuKS/RyhbQ= +github.com/sei-protocol/go-ethereum v1.13.5-sei-13 h1:ied1MMoqJyTEPvT7AtJeVTn3bEuwksInfvyXxp2siLw= +github.com/sei-protocol/go-ethereum v1.13.5-sei-13/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.2.80-seiv2 h1:stu0hTZmQWvewzr/y40iZrKsKHFuCXFKNEJGsgivvL4= diff --git a/precompiles/addr/addr.go b/precompiles/addr/addr.go index ee1ba2ddc..f78a33453 100644 --- a/precompiles/addr/addr.go +++ b/precompiles/addr/addr.go @@ -88,7 +88,7 @@ func (p Precompile) Address() common.Address { return p.address } -func (p Precompile) Run(evm *vm.EVM, _ common.Address, input []byte, value *big.Int) (bz []byte, err error) { +func (p Precompile) Run(evm *vm.EVM, _ common.Address, _ common.Address, input []byte, value *big.Int, _ bool) (bz []byte, err error) { ctx, method, args, err := p.Prepare(evm, input) if err != nil { return nil, err diff --git a/precompiles/bank/bank.go b/precompiles/bank/bank.go index 88920705e..e030eed3b 100644 --- a/precompiles/bank/bank.go +++ b/precompiles/bank/bank.go @@ -117,7 +117,7 @@ func (p Precompile) Address() common.Address { return p.address } -func (p Precompile) Run(evm *vm.EVM, caller common.Address, input []byte, value *big.Int) (bz []byte, err error) { +func (p Precompile) Run(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, value *big.Int, readOnly bool) (bz []byte, err error) { ctx, method, args, err := p.Prepare(evm, input) if err != nil { return nil, err @@ -125,9 +125,9 @@ func (p Precompile) Run(evm *vm.EVM, caller common.Address, input []byte, value switch method.Name { case SendMethod: - return p.send(ctx, caller, method, args, value) + return p.send(ctx, caller, method, args, value, readOnly) case SendNativeMethod: - return p.sendNative(ctx, method, args, caller, value) + return p.sendNative(ctx, method, args, caller, callingContract, value, readOnly) case BalanceMethod: return p.balance(ctx, method, args, value) case NameMethod: @@ -142,7 +142,10 @@ func (p Precompile) Run(evm *vm.EVM, caller common.Address, input []byte, value return } -func (p Precompile) send(ctx sdk.Context, caller common.Address, method *abi.Method, args []interface{}, value *big.Int) ([]byte, error) { +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 } @@ -180,7 +183,13 @@ func (p Precompile) send(ctx sdk.Context, caller common.Address, method *abi.Met return method.Outputs.Pack(true) } -func (p Precompile) sendNative(ctx sdk.Context, method *abi.Method, args []interface{}, caller common.Address, value *big.Int) ([]byte, error) { +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 } diff --git a/precompiles/bank/bank_test.go b/precompiles/bank/bank_test.go index f2cc9e863..44e16cba1 100644 --- a/precompiles/bank/bank_test.go +++ b/precompiles/bank/bank_test.go @@ -53,7 +53,7 @@ func TestRun(t *testing.T) { require.Nil(t, err) args, err := send.Inputs.Pack(senderEVMAddr, evmAddr, "usei", big.NewInt(25)) require.Nil(t, err) - _, err = p.Run(&evm, senderEVMAddr, append(p.SendID, args...), nil) // should error because address is not whitelisted + _, err = p.Run(&evm, senderEVMAddr, senderEVMAddr, append(p.SendID, args...), nil, false) // should error because address is not whitelisted require.NotNil(t, err) // Precompile sendNative test error @@ -63,19 +63,19 @@ func TestRun(t *testing.T) { argsNativeError, err := sendNative.Inputs.Pack(seiAddrString) require.Nil(t, err) // 0 amount disallowed - _, err = p.Run(&evm, senderEVMAddr, append(p.SendNativeID, argsNativeError...), big.NewInt(0)) + _, err = p.Run(&evm, senderEVMAddr, senderEVMAddr, append(p.SendNativeID, argsNativeError...), big.NewInt(0), false) require.NotNil(t, err) argsNativeError, err = sendNative.Inputs.Pack("") require.Nil(t, err) - _, err = p.Run(&evm, senderEVMAddr, append(p.SendNativeID, argsNativeError...), big.NewInt(100)) + _, err = p.Run(&evm, senderEVMAddr, senderEVMAddr, append(p.SendNativeID, argsNativeError...), big.NewInt(100), false) require.NotNil(t, err) argsNativeError, err = sendNative.Inputs.Pack("invalidaddr") require.Nil(t, err) - _, err = p.Run(&evm, senderEVMAddr, append(p.SendNativeID, argsNativeError...), big.NewInt(100)) + _, err = p.Run(&evm, senderEVMAddr, senderEVMAddr, append(p.SendNativeID, argsNativeError...), big.NewInt(100), false) require.NotNil(t, err) argsNativeError, err = sendNative.Inputs.Pack(senderAddr.String()) require.Nil(t, err) - _, err = p.Run(&evm, evmAddr, append(p.SendNativeID, argsNativeError...), big.NewInt(100)) + _, err = p.Run(&evm, evmAddr, evmAddr, append(p.SendNativeID, argsNativeError...), big.NewInt(100), false) require.NotNil(t, err) // Send native 10_000_000_000_100, split into 10 usei 100wei @@ -117,7 +117,7 @@ func TestRun(t *testing.T) { require.Nil(t, err) args, err = balance.Inputs.Pack(evmAddr, "usei") require.Nil(t, err) - precompileRes, err := p.Run(&evm, common.Address{}, append(p.BalanceID, args...), nil) + precompileRes, err := p.Run(&evm, common.Address{}, common.Address{}, append(p.BalanceID, args...), nil, false) require.Nil(t, err) is, err := balance.Outputs.Unpack(precompileRes) require.Nil(t, err) @@ -127,15 +127,15 @@ func TestRun(t *testing.T) { require.Equal(t, big.NewInt(100), weiBalance.BigInt()) // Verify errors properly raised on bank balance calls with incorrect inputs - _, err = p.Run(&evm, common.Address{}, append(p.BalanceID, args[:1]...), nil) + _, err = p.Run(&evm, common.Address{}, common.Address{}, append(p.BalanceID, args[:1]...), nil, false) require.NotNil(t, err) args, err = balance.Inputs.Pack(evmAddr, "") require.Nil(t, err) - _, err = p.Run(&evm, common.Address{}, append(p.BalanceID, args...), nil) + _, err = p.Run(&evm, common.Address{}, common.Address{}, append(p.BalanceID, args...), nil, false) require.NotNil(t, err) // invalid input - _, err = p.Run(&evm, common.Address{}, []byte{1, 2, 3, 4}, nil) + _, err = p.Run(&evm, common.Address{}, common.Address{}, []byte{1, 2, 3, 4}, nil, false) require.NotNil(t, err) } @@ -152,7 +152,7 @@ func TestMetadata(t *testing.T) { require.Nil(t, err) args, err := name.Inputs.Pack("usei") require.Nil(t, err) - res, err := p.Run(&evm, common.Address{}, append(p.NameID, args...), nil) + res, err := p.Run(&evm, common.Address{}, common.Address{}, append(p.NameID, args...), nil, false) require.Nil(t, err) outputs, err := name.Outputs.Unpack(res) require.Nil(t, err) @@ -162,7 +162,7 @@ func TestMetadata(t *testing.T) { require.Nil(t, err) args, err = symbol.Inputs.Pack("usei") require.Nil(t, err) - res, err = p.Run(&evm, common.Address{}, append(p.SymbolID, args...), nil) + res, err = p.Run(&evm, common.Address{}, common.Address{}, append(p.SymbolID, args...), nil, false) require.Nil(t, err) outputs, err = symbol.Outputs.Unpack(res) require.Nil(t, err) @@ -172,7 +172,7 @@ func TestMetadata(t *testing.T) { require.Nil(t, err) args, err = decimal.Inputs.Pack("usei") require.Nil(t, err) - res, err = p.Run(&evm, common.Address{}, append(p.DecimalsID, args...), nil) + res, err = p.Run(&evm, common.Address{}, common.Address{}, append(p.DecimalsID, args...), nil, false) require.Nil(t, err) outputs, err = decimal.Outputs.Unpack(res) require.Nil(t, err) @@ -182,7 +182,7 @@ func TestMetadata(t *testing.T) { require.Nil(t, err) args, err = supply.Inputs.Pack("usei") require.Nil(t, err) - res, err = p.Run(&evm, common.Address{}, append(p.SupplyID, args...), nil) + res, err = p.Run(&evm, common.Address{}, common.Address{}, append(p.SupplyID, args...), nil, false) require.Nil(t, err) outputs, err = supply.Outputs.Unpack(res) require.Nil(t, err) diff --git a/precompiles/distribution/distribution.go b/precompiles/distribution/distribution.go index e90180aab..d93e5683c 100644 --- a/precompiles/distribution/distribution.go +++ b/precompiles/distribution/distribution.go @@ -95,11 +95,17 @@ func (p Precompile) Address() common.Address { return p.address } -func (p Precompile) Run(evm *vm.EVM, caller common.Address, input []byte, value *big.Int) (bz []byte, err error) { +func (p Precompile) Run(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, value *big.Int, readOnly 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: diff --git a/precompiles/gov/gov.go b/precompiles/gov/gov.go index d628cad4f..86ec7e3bd 100644 --- a/precompiles/gov/gov.go +++ b/precompiles/gov/gov.go @@ -98,11 +98,17 @@ func (p Precompile) Address() common.Address { return p.address } -func (p Precompile) Run(evm *vm.EVM, caller common.Address, input []byte, value *big.Int) (bz []byte, err error) { +func (p Precompile) Run(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, value *big.Int, readOnly 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: diff --git a/precompiles/ibc/ibc.go b/precompiles/ibc/ibc.go index 953090c90..7fdb9f9d9 100644 --- a/precompiles/ibc/ibc.go +++ b/precompiles/ibc/ibc.go @@ -94,7 +94,10 @@ func (p Precompile) RequiredGas(input []byte) uint64 { 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) (ret []byte, remainingGas uint64, err error) { +func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly 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 @@ -117,7 +120,7 @@ func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, calli return } -func (p Precompile) Run(evm *vm.EVM, caller common.Address, input []byte, value *big.Int) (bz []byte, err error) { +func (p Precompile) Run(*vm.EVM, common.Address, common.Address, []byte, *big.Int, bool) (bz []byte, err error) { panic("static gas Run is not implemented for dynamic gas precompile") } diff --git a/precompiles/ibc/ibc_test.go b/precompiles/ibc/ibc_test.go index 5a6352307..12579cc99 100644 --- a/precompiles/ibc/ibc_test.go +++ b/precompiles/ibc/ibc_test.go @@ -223,7 +223,7 @@ func TestPrecompile_Run(t *testing.T) { tt.args.input.sourcePort, tt.args.input.sourceChannel, tt.args.input.denom, tt.args.input.amount, tt.args.input.revisionNumber, tt.args.input.revisionHeight, tt.args.input.timeoutTimestamp) require.Nil(t, err) - gotBz, gotRemainingGas, err := p.RunAndCalculateGas(&evm, tt.args.caller, tt.args.callingContract, append(p.TransferID, inputs...), tt.args.suppliedGas, tt.args.value, nil) + gotBz, gotRemainingGas, err := p.RunAndCalculateGas(&evm, tt.args.caller, tt.args.callingContract, append(p.TransferID, inputs...), tt.args.suppliedGas, tt.args.value, nil, false) if (err != nil) != tt.wantErr { t.Errorf("Run() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/precompiles/json/json.go b/precompiles/json/json.go index 4f443eae3..3712bc292 100644 --- a/precompiles/json/json.go +++ b/precompiles/json/json.go @@ -88,7 +88,7 @@ func (p Precompile) Address() common.Address { return p.address } -func (p Precompile) Run(evm *vm.EVM, _ common.Address, input []byte, value *big.Int) (bz []byte, err error) { +func (p Precompile) Run(evm *vm.EVM, _ common.Address, _ common.Address, input []byte, value *big.Int, _ bool) (bz []byte, err error) { ctx, method, args, err := p.Prepare(evm, input) if err != nil { return nil, err diff --git a/precompiles/json/json_test.go b/precompiles/json/json_test.go index a559b45ae..f4688e416 100644 --- a/precompiles/json/json_test.go +++ b/precompiles/json/json_test.go @@ -41,7 +41,7 @@ func TestExtractAsBytes(t *testing.T) { args, err := method.Inputs.Pack(test.body, "key") require.Nil(t, err) input := append(p.ExtractAsBytesID, args...) - res, err := p.Run(evm, common.Address{}, input, nil) + res, err := p.Run(evm, common.Address{}, common.Address{}, input, nil, true) require.Nil(t, err) output, err := method.Outputs.Unpack(res) require.Nil(t, err) @@ -79,7 +79,7 @@ func TestExtractAsBytesList(t *testing.T) { args, err := method.Inputs.Pack(test.body, "key") require.Nil(t, err) input := append(p.ExtractAsBytesListID, args...) - res, err := p.Run(evm, common.Address{}, input, nil) + res, err := p.Run(evm, common.Address{}, common.Address{}, input, nil, true) require.Nil(t, err) output, err := method.Outputs.Unpack(res) require.Nil(t, err) @@ -113,7 +113,7 @@ func TestExtractAsUint256(t *testing.T) { args, err := method.Inputs.Pack(test.body, "key") require.Nil(t, err) input := append(p.ExtractAsUint256ID, args...) - res, err := p.Run(evm, common.Address{}, input, nil) + res, err := p.Run(evm, common.Address{}, common.Address{}, input, nil, true) require.Nil(t, err) output, err := method.Outputs.Unpack(res) require.Nil(t, err) diff --git a/precompiles/oracle/oracle.go b/precompiles/oracle/oracle.go index 20c97d578..06f1dd60a 100644 --- a/precompiles/oracle/oracle.go +++ b/precompiles/oracle/oracle.go @@ -113,7 +113,7 @@ func (p Precompile) Address() common.Address { return p.address } -func (p Precompile) Run(evm *vm.EVM, _ common.Address, input []byte, value *big.Int) (bz []byte, err error) { +func (p Precompile) Run(evm *vm.EVM, _ common.Address, _ common.Address, input []byte, value *big.Int, _ bool) (bz []byte, err error) { ctx, method, args, err := p.Prepare(evm, input) if err != nil { return nil, err diff --git a/precompiles/oracle/oracle_test.go b/precompiles/oracle/oracle_test.go index 7fdddece4..5b86bc2b6 100644 --- a/precompiles/oracle/oracle_test.go +++ b/precompiles/oracle/oracle_test.go @@ -38,7 +38,7 @@ func TestGetExchangeRate(t *testing.T) { query, err := p.ABI.MethodById(p.GetExchangeRatesId) require.Nil(t, err) - precompileRes, err := p.Run(&evm, common.Address{}, p.GetExchangeRatesId, nil) + precompileRes, err := p.Run(&evm, common.Address{}, common.Address{}, p.GetExchangeRatesId, nil, true) require.Nil(t, err) exchangeRates, err := query.Outputs.Unpack(precompileRes) require.Nil(t, err) @@ -114,7 +114,7 @@ func TestGetOracleTwaps(t *testing.T) { require.Nil(t, err) args, err := query.Inputs.Pack(uint64(3600)) require.Nil(t, err) - precompileRes, err := p.Run(&evm, common.Address{}, append(p.GetOracleTwapsId, args...), nil) + precompileRes, err := p.Run(&evm, common.Address{}, common.Address{}, append(p.GetOracleTwapsId, args...), nil, true) require.Nil(t, err) twap, err := query.Outputs.Unpack(precompileRes) require.Nil(t, err) diff --git a/precompiles/pointer/pointer.go b/precompiles/pointer/pointer.go index 8428178fc..d599c67de 100644 --- a/precompiles/pointer/pointer.go +++ b/precompiles/pointer/pointer.go @@ -4,6 +4,7 @@ import ( "bytes" "embed" "encoding/json" + "errors" "fmt" "math" "math/big" @@ -91,11 +92,17 @@ func (p Precompile) Address() common.Address { return p.address } -func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, _ common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks) (ret []byte, remainingGas uint64, err error) { +func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly 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: @@ -110,7 +117,7 @@ func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, _ com return } -func (p Precompile) Run(*vm.EVM, common.Address, []byte, *big.Int) ([]byte, error) { +func (p Precompile) Run(*vm.EVM, common.Address, common.Address, []byte, *big.Int, bool) ([]byte, error) { panic("static gas Run is not implemented for dynamic gas precompile") } diff --git a/precompiles/pointer/pointer_test.go b/precompiles/pointer/pointer_test.go index 23d6b82ae..871d02458 100644 --- a/precompiles/pointer/pointer_test.go +++ b/precompiles/pointer/pointer_test.go @@ -34,7 +34,7 @@ func TestAddNative(t *testing.T) { statedb := state.NewDBImpl(ctx, &testApp.EvmKeeper, true) blockCtx, _ := testApp.EvmKeeper.GetVMBlockContext(ctx, core.GasPool(suppliedGas)) evm := vm.NewEVM(*blockCtx, vm.TxContext{}, statedb, cfg, vm.Config{}) - _, g, err := p.RunAndCalculateGas(evm, caller, caller, append(p.AddNativePointerID, args...), suppliedGas, nil, nil) + _, g, err := p.RunAndCalculateGas(evm, caller, caller, append(p.AddNativePointerID, args...), suppliedGas, nil, nil, false) require.NotNil(t, err) require.Equal(t, uint64(0), g) _, _, exists := testApp.EvmKeeper.GetERC20NativePointer(statedb.Ctx(), "test") @@ -53,7 +53,7 @@ func TestAddNative(t *testing.T) { }) statedb = state.NewDBImpl(ctx, &testApp.EvmKeeper, true) evm = vm.NewEVM(*blockCtx, vm.TxContext{}, statedb, cfg, vm.Config{}) - ret, g, err := p.RunAndCalculateGas(evm, caller, caller, append(p.AddNativePointerID, args...), suppliedGas, nil, nil) + ret, g, err := p.RunAndCalculateGas(evm, caller, caller, append(p.AddNativePointerID, args...), suppliedGas, nil, nil, false) require.Nil(t, err) require.Equal(t, uint64(8907806), g) outputs, err := m.Outputs.Unpack(ret) @@ -67,7 +67,7 @@ func TestAddNative(t *testing.T) { // pointer already exists statedb = state.NewDBImpl(statedb.Ctx(), &testApp.EvmKeeper, true) evm = vm.NewEVM(*blockCtx, vm.TxContext{}, statedb, cfg, vm.Config{}) - _, g, err = p.RunAndCalculateGas(evm, caller, caller, append(p.AddNativePointerID, args...), suppliedGas, nil, nil) + _, g, err = p.RunAndCalculateGas(evm, caller, caller, append(p.AddNativePointerID, args...), suppliedGas, nil, nil, false) require.NotNil(t, err) require.Equal(t, uint64(0), g) } diff --git a/precompiles/pointerview/pointerview.go b/precompiles/pointerview/pointerview.go index a47f7e085..acbc97561 100644 --- a/precompiles/pointerview/pointerview.go +++ b/precompiles/pointerview/pointerview.go @@ -78,7 +78,7 @@ func (p Precompile) Address() common.Address { return p.address } -func (p Precompile) Run(evm *vm.EVM, _ common.Address, input []byte, _ *big.Int) (ret []byte, err error) { +func (p Precompile) Run(evm *vm.EVM, _ common.Address, _ common.Address, input []byte, _ *big.Int, _ bool) (ret []byte, err error) { ctx, method, args, err := p.Prepare(evm, input) if err != nil { return nil, err diff --git a/precompiles/staking/staking.go b/precompiles/staking/staking.go index 828e96cf2..1c96a5666 100644 --- a/precompiles/staking/staking.go +++ b/precompiles/staking/staking.go @@ -105,11 +105,17 @@ func (p Precompile) Address() common.Address { return p.address } -func (p Precompile) Run(evm *vm.EVM, caller common.Address, input []byte, value *big.Int) (bz []byte, err error) { +func (p Precompile) Run(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, value *big.Int, readOnly 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: diff --git a/precompiles/wasmd/wasmd.go b/precompiles/wasmd/wasmd.go index d68897958..1489d9957 100644 --- a/precompiles/wasmd/wasmd.go +++ b/precompiles/wasmd/wasmd.go @@ -112,7 +112,7 @@ func (p Precompile) Address() common.Address { return p.address } -func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks) (ret []byte, remainingGas uint64, err error) { +func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, callingContract common.Address, input []byte, suppliedGas uint64, value *big.Int, _ *tracing.Hooks, readOnly bool) (ret []byte, remainingGas uint64, err error) { ctx, method, args, err := p.Prepare(evm, input) if err != nil { return nil, 0, err @@ -126,20 +126,20 @@ func (p Precompile) RunAndCalculateGas(evm *vm.EVM, caller common.Address, calli switch method.Name { case InstantiateMethod: - return p.instantiate(ctx, method, caller, args, value) + return p.instantiate(ctx, method, caller, callingContract, args, value, readOnly) case ExecuteMethod: - return p.execute(ctx, method, caller, callingContract, args, value) + return p.execute(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, []byte, *big.Int) ([]byte, error) { +func (p Precompile) Run(*vm.EVM, common.Address, common.Address, []byte, *big.Int, 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, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { +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 @@ -148,10 +148,18 @@ func (p Precompile) instantiate(ctx sdk.Context, method *abi.Method, caller comm 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) @@ -214,7 +222,7 @@ func (p Precompile) instantiate(ctx sdk.Context, method *abi.Method, caller comm return } -func (p Precompile) execute(ctx sdk.Context, method *abi.Method, caller common.Address, callingContract common.Address, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { +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 @@ -223,6 +231,10 @@ func (p Precompile) execute(ctx sdk.Context, method *abi.Method, caller common.A return } }() + if readOnly { + rerr = errors.New("cannot call execute from staticcall") + return + } if err := pcommon.ValidateArgsLength(args, 3); err != nil { rerr = err return diff --git a/precompiles/wasmd/wasmd_test.go b/precompiles/wasmd/wasmd_test.go index 8647a79bc..926a007b1 100644 --- a/precompiles/wasmd/wasmd_test.go +++ b/precompiles/wasmd/wasmd_test.go @@ -63,7 +63,7 @@ func TestInstantiate(t *testing.T) { StateDB: statedb, } suppliedGas := uint64(1000000) - res, g, err := p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.InstantiateID, args...), suppliedGas, nil, nil) + res, g, err := p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.InstantiateID, args...), suppliedGas, nil, nil, false) require.Nil(t, err) outputs, err := instantiateMethod.Outputs.Unpack(res) require.Nil(t, err) @@ -80,16 +80,16 @@ func TestInstantiate(t *testing.T) { "test", amtsbz, ) - _, g, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.InstantiateID, args...), suppliedGas, nil, nil) + _, g, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.InstantiateID, args...), suppliedGas, nil, nil, false) require.NotNil(t, err) require.Equal(t, uint64(0), g) // bad inputs badArgs, _ := instantiateMethod.Inputs.Pack(codeID, "not bech32", []byte("{}"), "test", amtsbz) - _, _, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.InstantiateID, badArgs...), suppliedGas, nil, nil) + _, _, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.InstantiateID, badArgs...), suppliedGas, nil, nil, false) require.NotNil(t, err) badArgs, _ = instantiateMethod.Inputs.Pack(codeID, mockAddr.String(), []byte("{}"), "test", []byte("bad coins")) - _, _, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.InstantiateID, badArgs...), suppliedGas, nil, nil) + _, _, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.InstantiateID, badArgs...), suppliedGas, nil, nil, false) require.NotNil(t, err) } @@ -125,14 +125,14 @@ func TestExecute(t *testing.T) { } suppliedGas := uint64(1000000) testApp.BankKeeper.SendCoins(ctx, mockAddr, testApp.EvmKeeper.GetSeiAddressOrDefault(ctx, common.HexToAddress(wasmd.WasmdAddress)), amts) - _, _, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.ExecuteID, args...), suppliedGas, nil, nil) + _, _, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.ExecuteID, args...), suppliedGas, nil, nil, false) require.NotNil(t, err) // used coins instead of `value` to send usei to the contract amtsbz, err = sdk.NewCoins().MarshalJSON() require.Nil(t, err) args, err = executeMethod.Inputs.Pack(contractAddr.String(), []byte("{\"echo\":{\"message\":\"test msg\"}}"), amtsbz) require.Nil(t, err) - res, g, err := p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.ExecuteID, args...), suppliedGas, big.NewInt(1000_000_000_000_000), nil) + res, g, err := p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.ExecuteID, args...), suppliedGas, big.NewInt(1000_000_000_000_000), nil, false) require.Nil(t, err) outputs, err := executeMethod.Outputs.Unpack(res) require.Nil(t, err) @@ -144,27 +144,27 @@ func TestExecute(t *testing.T) { // allowed delegatecall contractAddrAllowed := common.BytesToAddress([]byte("contractA")) testApp.EvmKeeper.SetERC20CW20Pointer(ctx, contractAddr.String(), contractAddrAllowed) - _, _, err = p.RunAndCalculateGas(&evm, mockEVMAddr, contractAddrAllowed, append(p.ExecuteID, args...), suppliedGas, nil, nil) + _, _, err = p.RunAndCalculateGas(&evm, mockEVMAddr, contractAddrAllowed, append(p.ExecuteID, args...), suppliedGas, nil, nil, false) require.Nil(t, err) // disallowed delegatecall contractAddrDisallowed := common.BytesToAddress([]byte("contractB")) - _, _, err = p.RunAndCalculateGas(&evm, mockEVMAddr, contractAddrDisallowed, append(p.ExecuteID, args...), suppliedGas, nil, nil) + _, _, err = p.RunAndCalculateGas(&evm, mockEVMAddr, contractAddrDisallowed, append(p.ExecuteID, args...), suppliedGas, nil, nil, false) require.NotNil(t, err) // bad contract address args, _ = executeMethod.Inputs.Pack(mockAddr.String(), []byte("{\"echo\":{\"message\":\"test msg\"}}"), amtsbz) - _, g, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.ExecuteID, args...), suppliedGas, nil, nil) + _, g, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.ExecuteID, args...), suppliedGas, nil, nil, false) require.NotNil(t, err) require.Equal(t, uint64(0), g) // bad inputs args, _ = executeMethod.Inputs.Pack("not bech32", []byte("{\"echo\":{\"message\":\"test msg\"}}"), amtsbz) - _, g, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.ExecuteID, args...), suppliedGas, nil, nil) + _, g, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.ExecuteID, args...), suppliedGas, nil, nil, false) require.NotNil(t, err) require.Equal(t, uint64(0), g) args, _ = executeMethod.Inputs.Pack(contractAddr.String(), []byte("{\"echo\":{\"message\":\"test msg\"}}"), []byte("bad coins")) - _, g, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.ExecuteID, args...), suppliedGas, nil, nil) + _, g, err = p.RunAndCalculateGas(&evm, mockEVMAddr, mockEVMAddr, append(p.ExecuteID, args...), suppliedGas, nil, nil, false) require.NotNil(t, err) require.Equal(t, uint64(0), g) } @@ -192,7 +192,7 @@ func TestQuery(t *testing.T) { StateDB: statedb, } suppliedGas := uint64(1000000) - res, g, err := p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(p.QueryID, args...), suppliedGas, nil, nil) + res, g, err := p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(p.QueryID, args...), suppliedGas, nil, nil, false) require.Nil(t, err) outputs, err := queryMethod.Outputs.Unpack(res) require.Nil(t, err) @@ -202,17 +202,17 @@ func TestQuery(t *testing.T) { // bad contract address args, _ = queryMethod.Inputs.Pack(mockAddr.String(), []byte("{\"info\":{}}")) - _, g, err = p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(p.ExecuteID, args...), suppliedGas, nil, nil) + _, g, err = p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(p.ExecuteID, args...), suppliedGas, nil, nil, false) require.NotNil(t, err) require.Equal(t, uint64(0), g) // bad input args, _ = queryMethod.Inputs.Pack("not bech32", []byte("{\"info\":{}}")) - _, g, err = p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(p.ExecuteID, args...), suppliedGas, nil, nil) + _, g, err = p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(p.ExecuteID, args...), suppliedGas, nil, nil, false) require.NotNil(t, err) require.Equal(t, uint64(0), g) args, _ = queryMethod.Inputs.Pack(contractAddr.String(), []byte("{\"bad\":{}}")) - _, g, err = p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(p.ExecuteID, args...), suppliedGas, nil, nil) + _, g, err = p.RunAndCalculateGas(&evm, common.Address{}, common.Address{}, append(p.ExecuteID, args...), suppliedGas, nil, nil, false) require.NotNil(t, err) require.Equal(t, uint64(0), g) } From a75cd923b7bc9cd32ce5a226a7396503a292105b Mon Sep 17 00:00:00 2001 From: codchen Date: Tue, 9 Apr 2024 23:21:11 +0800 Subject: [PATCH 31/34] fix precompile remaining gas calculation (#1525) --- precompiles/common/precompiles.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/precompiles/common/precompiles.go b/precompiles/common/precompiles.go index d0a2e0012..c46db4cb4 100644 --- a/precompiles/common/precompiles.go +++ b/precompiles/common/precompiles.go @@ -93,10 +93,15 @@ func HandlePaymentUseiWei(ctx sdk.Context, precompileAddr sdk.AccAddress, payer 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)).Mul(gasMultipler).TruncateInt().Uint64() + return sdk.NewDecFromInt(sdk.NewIntFromUint64(seiGasRemaining)).Quo(gasMultipler).TruncateInt().Uint64() } func ExtractMethodID(input []byte) ([]byte, error) { From e2440de8baf19467f1136c1fda7e60a8a21a3cfb Mon Sep 17 00:00:00 2001 From: codchen Date: Wed, 10 Apr 2024 09:38:35 +0800 Subject: [PATCH 32/34] Emit event for contract registration (#1518) --- precompiles/pointer/pointer.go | 13 ++++++++++ precompiles/pointer/pointer_test.go | 10 ++++++++ x/evm/gov.go | 14 +++++++++++ x/evm/keeper/msg_server.go | 8 ++++++ x/evm/keeper/msg_server_test.go | 39 +++++++++++++++++++++++++++++ x/evm/types/events.go | 9 +++++-- 6 files changed, 91 insertions(+), 2 deletions(-) diff --git a/precompiles/pointer/pointer.go b/precompiles/pointer/pointer.go index d599c67de..8fb594b84 100644 --- a/precompiles/pointer/pointer.go +++ b/precompiles/pointer/pointer.go @@ -19,6 +19,7 @@ import ( "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 ( @@ -167,6 +168,10 @@ func (p Precompile) AddNative(ctx sdk.Context, method *ethabi.Method, caller com 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 } @@ -214,6 +219,10 @@ func (p Precompile) AddCW20(ctx sdk.Context, method *ethabi.Method, caller commo 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)))) ret, err = method.Outputs.Pack(contractAddr) return } @@ -261,6 +270,10 @@ func (p Precompile) AddCW721(ctx sdk.Context, method *ethabi.Method, caller comm 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/pointer_test.go b/precompiles/pointer/pointer_test.go index 871d02458..f621a66c3 100644 --- a/precompiles/pointer/pointer_test.go +++ b/precompiles/pointer/pointer_test.go @@ -63,6 +63,16 @@ func TestAddNative(t *testing.T) { require.Equal(t, addr, pointerAddr) require.Equal(t, native.CurrentVersion, version) require.True(t, exists) + hasRegisteredEvent := false + for _, e := range ctx.EventManager().Events() { + if e.Type != types.EventTypePointerRegistered { + continue + } + hasRegisteredEvent = true + require.Equal(t, types.EventTypePointerRegistered, e.Type) + require.Equal(t, "native", string(e.Attributes[0].Value)) + } + require.True(t, hasRegisteredEvent) // pointer already exists statedb = state.NewDBImpl(statedb.Ctx(), &testApp.EvmKeeper, true) diff --git a/x/evm/gov.go b/x/evm/gov.go index 1673810a2..879f2fa5d 100644 --- a/x/evm/gov.go +++ b/x/evm/gov.go @@ -1,6 +1,8 @@ package evm import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" "github.com/sei-protocol/sei-chain/x/evm/keeper" @@ -8,6 +10,10 @@ import ( ) func HandleAddERCNativePointerProposal(ctx sdk.Context, k *keeper.Keeper, p *types.AddERCNativePointerProposal) error { + ctx.EventManager().EmitEvent(sdk.NewEvent( + types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "native"), + sdk.NewAttribute(types.AttributeKeyPointerAddress, p.Pointer), sdk.NewAttribute(types.AttributeKeyPointee, p.Token), + sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", p.Version)))) if p.Pointer == "" { k.DeleteERC20NativePointer(ctx, p.Token, uint16(p.Version)) return nil @@ -16,6 +22,10 @@ func HandleAddERCNativePointerProposal(ctx sdk.Context, k *keeper.Keeper, p *typ } func HandleAddERCCW20PointerProposal(ctx sdk.Context, k *keeper.Keeper, p *types.AddERCCW20PointerProposal) error { + ctx.EventManager().EmitEvent(sdk.NewEvent( + types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "cw20"), + sdk.NewAttribute(types.AttributeKeyPointerAddress, p.Pointer), sdk.NewAttribute(types.AttributeKeyPointee, p.Pointee), + sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", p.Version)))) if p.Pointer == "" { k.DeleteERC20CW20Pointer(ctx, p.Pointee, uint16(p.Version)) return nil @@ -24,6 +34,10 @@ func HandleAddERCCW20PointerProposal(ctx sdk.Context, k *keeper.Keeper, p *types } func HandleAddERCCW721PointerProposal(ctx sdk.Context, k *keeper.Keeper, p *types.AddERCCW721PointerProposal) error { + ctx.EventManager().EmitEvent(sdk.NewEvent( + types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "cw721"), + sdk.NewAttribute(types.AttributeKeyPointerAddress, p.Pointer), sdk.NewAttribute(types.AttributeKeyPointee, p.Pointee), + sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", p.Version)))) if p.Pointer == "" { k.DeleteERC721CW721Pointer(ctx, p.Pointee, uint16(p.Version)) return nil diff --git a/x/evm/keeper/msg_server.go b/x/evm/keeper/msg_server.go index 3cd7de979..d589273ac 100644 --- a/x/evm/keeper/msg_server.go +++ b/x/evm/keeper/msg_server.go @@ -298,8 +298,16 @@ func (server msgServer) RegisterPointer(goCtx context.Context, msg *types.MsgReg switch msg.PointerType { case types.PointerType_ERC20: err = server.SetCW20ERC20Pointer(ctx, common.HexToAddress(msg.ErcAddress), pointerAddr.String()) + ctx.EventManager().EmitEvent(sdk.NewEvent( + types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "erc20"), + sdk.NewAttribute(types.AttributeKeyPointerAddress, pointerAddr.String()), sdk.NewAttribute(types.AttributeKeyPointee, msg.ErcAddress), + sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", erc20.CurrentVersion)))) case types.PointerType_ERC721: err = server.SetCW721ERC721Pointer(ctx, common.HexToAddress(msg.ErcAddress), pointerAddr.String()) + ctx.EventManager().EmitEvent(sdk.NewEvent( + types.EventTypePointerRegistered, sdk.NewAttribute(types.AttributeKeyPointerType, "erc721"), + sdk.NewAttribute(types.AttributeKeyPointerAddress, pointerAddr.String()), sdk.NewAttribute(types.AttributeKeyPointee, msg.ErcAddress), + sdk.NewAttribute(types.AttributeKeyPointerVersion, fmt.Sprintf("%d", erc721.CurrentVersion)))) default: panic("unknown pointer type") } diff --git a/x/evm/keeper/msg_server_test.go b/x/evm/keeper/msg_server_test.go index 5661a77ae..6dfc66605 100644 --- a/x/evm/keeper/msg_server_test.go +++ b/x/evm/keeper/msg_server_test.go @@ -472,6 +472,17 @@ func TestRegisterPointer(t *testing.T) { require.True(t, exists) require.Equal(t, erc20.CurrentVersion, version) require.Equal(t, pointer.String(), res.PointerAddress) + hasRegisteredEvent := false + for _, e := range ctx.EventManager().Events() { + if e.Type != types.EventTypePointerRegistered { + continue + } + hasRegisteredEvent = true + require.Equal(t, types.EventTypePointerRegistered, e.Type) + require.Equal(t, "erc20", string(e.Attributes[0].Value)) + } + require.True(t, hasRegisteredEvent) + ctx = ctx.WithEventManager(sdk.NewEventManager()) // already exists _, err = keeper.NewMsgServerImpl(k).RegisterPointer(sdk.WrapSDKContext(ctx), &types.MsgRegisterPointer{ @@ -480,6 +491,15 @@ func TestRegisterPointer(t *testing.T) { ErcAddress: pointee.Hex(), }) require.NotNil(t, err) + hasRegisteredEvent = false + for _, e := range ctx.EventManager().Events() { + if e.Type != types.EventTypePointerRegistered { + continue + } + hasRegisteredEvent = true + } + require.False(t, hasRegisteredEvent) + ctx = ctx.WithEventManager(sdk.NewEventManager()) res, err = keeper.NewMsgServerImpl(k).RegisterPointer(sdk.WrapSDKContext(ctx), &types.MsgRegisterPointer{ Sender: sender.String(), @@ -491,6 +511,17 @@ func TestRegisterPointer(t *testing.T) { require.True(t, exists) require.Equal(t, erc721.CurrentVersion, version) require.Equal(t, pointer.String(), res.PointerAddress) + hasRegisteredEvent = false + for _, e := range ctx.EventManager().Events() { + if e.Type != types.EventTypePointerRegistered { + continue + } + hasRegisteredEvent = true + require.Equal(t, types.EventTypePointerRegistered, e.Type) + require.Equal(t, "erc721", string(e.Attributes[0].Value)) + } + require.True(t, hasRegisteredEvent) + ctx = ctx.WithEventManager(sdk.NewEventManager()) // already exists _, err = keeper.NewMsgServerImpl(k).RegisterPointer(sdk.WrapSDKContext(ctx), &types.MsgRegisterPointer{ @@ -499,4 +530,12 @@ func TestRegisterPointer(t *testing.T) { ErcAddress: pointee.Hex(), }) require.NotNil(t, err) + hasRegisteredEvent = false + for _, e := range ctx.EventManager().Events() { + if e.Type != types.EventTypePointerRegistered { + continue + } + hasRegisteredEvent = true + } + require.False(t, hasRegisteredEvent) } diff --git a/x/evm/types/events.go b/x/evm/types/events.go index 6cb0b4541..45ec378d9 100644 --- a/x/evm/types/events.go +++ b/x/evm/types/events.go @@ -2,7 +2,12 @@ package types const ( EventTypeAddressAssociated = "address_associated" + EventTypePointerRegistered = "pointer_registered" - AttributeKeySeiAddress = "sei_addr" - AttributeKeyEvmAddress = "evm_addr" + AttributeKeySeiAddress = "sei_addr" + AttributeKeyEvmAddress = "evm_addr" + AttributeKeyPointerType = "pointer_type" + AttributeKeyPointee = "pointee" + AttributeKeyPointerAddress = "pointer_address" + AttributeKeyPointerVersion = "pointer_version" ) From e8d0ce4525a039b31c56ccd9d54a0a77412e7105 Mon Sep 17 00:00:00 2001 From: Steven Landers Date: Wed, 10 Apr 2024 09:16:17 -0400 Subject: [PATCH 33/34] [EVM] Avoid internal events for send_native (#1523) * avoid internal events for send_native * msg_server * limit to those txs with a value (proxy for send_native) * use overridden transfer function * goimports * fix test * cleanup * handle internal txs * fix test --- precompiles/bank/bank_test.go | 38 +++++++++++++++++++++++++++++++ precompiles/common/precompiles.go | 6 +++-- x/evm/client/cli/tx.go | 9 +++++++- x/evm/keeper/keeper.go | 11 ++++++++- x/evm/keeper/precompile.go | 24 +++++++++++++++++++ x/evm/keeper/precompile_test.go | 29 +++++++++++++++++++++++ x/evm/state/balance.go | 22 ++++++++++++++---- x/evm/state/statedb.go | 11 +++++++++ x/evm/state/transfer.go | 21 +++++++++++++++++ x/evm/state/transfer_test.go | 24 +++++++++++++++++++ 10 files changed, 187 insertions(+), 8 deletions(-) create mode 100644 x/evm/keeper/precompile.go create mode 100644 x/evm/keeper/precompile_test.go create mode 100644 x/evm/state/transfer.go create mode 100644 x/evm/state/transfer_test.go diff --git a/precompiles/bank/bank_test.go b/precompiles/bank/bank_test.go index 44e16cba1..c4a16dd8b 100644 --- a/precompiles/bank/bank_test.go +++ b/precompiles/bank/bank_test.go @@ -2,7 +2,9 @@ package bank_test import ( "encoding/hex" + "fmt" "math/big" + "strings" "testing" sdk "github.com/cosmos/cosmos-sdk/types" @@ -106,12 +108,48 @@ func TestRun(t *testing.T) { req, err := types.NewMsgEVMTransaction(txwrapper) require.Nil(t, err) + // send the transaction msgServer := keeper.NewMsgServerImpl(k) ante.Preprocess(ctx, req) + ctx = ctx.WithEventManager(sdk.NewEventManager()) res, err := msgServer.EVMTransaction(sdk.WrapSDKContext(ctx), req) require.Nil(t, err) require.Empty(t, res.VmError) + evts := ctx.EventManager().ABCIEvents() + + for _, evt := range evts { + var lines []string + for _, attr := range evt.Attributes { + lines = append(lines, fmt.Sprintf("%s=%s", string(attr.Key), string(attr.Value))) + } + fmt.Printf("type=%s\t%s\n", evt.Type, strings.Join(lines, "\t")) + } + + var expectedEvts sdk.Events = []sdk.Event{ + // gas is sent from sender + banktypes.NewCoinSpentEvent(senderAddr, sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(2)))), + // sender sends coin to the receiver + banktypes.NewCoinSpentEvent(senderAddr, sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(10)))), + banktypes.NewCoinReceivedEvent(seiAddr, sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(10)))), + sdk.NewEvent( + banktypes.EventTypeTransfer, + sdk.NewAttribute(banktypes.AttributeKeyRecipient, seiAddr.String()), + sdk.NewAttribute(banktypes.AttributeKeySender, senderAddr.String()), + sdk.NewAttribute(sdk.AttributeKeyAmount, sdk.NewCoin("usei", sdk.NewInt(10)).String()), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(banktypes.AttributeKeySender, senderAddr.String()), + ), + // gas refund to the sender + banktypes.NewCoinReceivedEvent(senderAddr, sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(1)))), + // tip is paid to the validator + banktypes.NewCoinReceivedEvent(sdk.MustAccAddressFromBech32("sei1v4mx6hmrda5kucnpwdjsqqqqqqqqqqqqlve8dv"), sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(0)))), + } + + require.EqualValues(t, expectedEvts.ToABCIEvents(), evts) + // Use precompile balance to verify sendNative usei amount succeeded balance, err := p.ABI.MethodById(p.BalanceID) require.Nil(t, err) diff --git a/precompiles/common/precompiles.go b/precompiles/common/precompiles.go index c46db4cb4..5a38be8d9 100644 --- a/precompiles/common/precompiles.go +++ b/precompiles/common/precompiles.go @@ -78,7 +78,8 @@ func HandlePaymentUsei(ctx sdk.Context, precompileAddr sdk.AccAddress, payer sdk } coin := sdk.NewCoin(sdk.MustGetBaseDenom(), usei) // refund payer because the following precompile logic will debit the payments from payer's account - if err := bankKeeper.SendCoins(ctx, precompileAddr, payer, sdk.NewCoins(coin)); err != nil { + // 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 @@ -87,7 +88,8 @@ func HandlePaymentUsei(ctx sdk.Context, precompileAddr sdk.AccAddress, payer sdk 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 - if err := bankKeeper.SendCoinsAndWei(ctx, precompileAddr, payer, usei, wei); err != nil { + // 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 diff --git a/x/evm/client/cli/tx.go b/x/evm/client/cli/tx.go index e4b10bf57..0de934f06 100644 --- a/x/evm/client/cli/tx.go +++ b/x/evm/client/cli/tx.go @@ -42,6 +42,7 @@ import ( const ( FlagGasFeeCap = "gas-fee-cap" FlagGas = "gas-limit" + FlagValue = "value" FlagRPC = "evm-rpc" FlagNonce = "nonce" ) @@ -502,12 +503,17 @@ func CmdCallContract() *cobra.Command { } } + value, err := cmd.Flags().GetUint64(FlagValue) + if err != nil { + return err + } + txData, err := getTxData(cmd) if err != nil { return err } txData.Nonce = nonce - txData.Value = utils.Big0 + txData.Value = big.NewInt(int64(value)) txData.Data = payload txData.To = &contract @@ -523,6 +529,7 @@ func CmdCallContract() *cobra.Command { cmd.Flags().Uint64(FlagGasFeeCap, 1000000000000, "Gas fee cap for the transaction") cmd.Flags().Uint64(FlagGas, 7000000, "Gas limit for the transaction") + cmd.Flags().Uint64(FlagValue, 0, "Value for the transaction") cmd.Flags().String(FlagRPC, fmt.Sprintf("http://%s:8545", evmrpc.LocalAddress), "RPC endpoint to send request to") cmd.Flags().Int64(FlagNonce, -1, "Nonce override for the transaction. Negative value means no override") flags.AddTxFlagsToCmd(cmd) diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index baa50337d..92f98af7c 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -181,9 +181,18 @@ func (k *Keeper) GetVMBlockContext(ctx sdk.Context, gp core.GasPool) (*vm.BlockC return nil, err } rh := common.BytesToHash(r) + + txfer := func(db vm.StateDB, sender, recipient common.Address, amount *big.Int) { + if IsPayablePrecompile(&recipient) { + state.TransferWithoutEvents(db, sender, recipient, amount) + } else { + core.Transfer(db, sender, recipient, amount) + } + } + return &vm.BlockContext{ CanTransfer: core.CanTransfer, - Transfer: core.Transfer, + Transfer: txfer, GetHash: k.GetHashFn(ctx), Coinbase: coinbase, GasLimit: gp.Gas(), diff --git a/x/evm/keeper/precompile.go b/x/evm/keeper/precompile.go new file mode 100644 index 000000000..c2a8b5119 --- /dev/null +++ b/x/evm/keeper/precompile.go @@ -0,0 +1,24 @@ +package keeper + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/sei-protocol/sei-chain/precompiles/bank" + "github.com/sei-protocol/sei-chain/precompiles/gov" + "github.com/sei-protocol/sei-chain/precompiles/staking" +) + +// add any payable precompiles here +// these will suppress transfer events to/from the precompile address +var payablePrecompiles = map[string]struct{}{ + bank.BankAddress: {}, + staking.StakingAddress: {}, + gov.GovAddress: {}, +} + +func IsPayablePrecompile(addr *common.Address) bool { + if addr == nil { + return false + } + _, ok := payablePrecompiles[addr.Hex()] + return ok +} diff --git a/x/evm/keeper/precompile_test.go b/x/evm/keeper/precompile_test.go new file mode 100644 index 000000000..956f5cb95 --- /dev/null +++ b/x/evm/keeper/precompile_test.go @@ -0,0 +1,29 @@ +package keeper_test + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + "github.com/sei-protocol/sei-chain/precompiles/bank" + "github.com/sei-protocol/sei-chain/precompiles/gov" + "github.com/sei-protocol/sei-chain/precompiles/staking" + "github.com/sei-protocol/sei-chain/testutil/keeper" + evmkeeper "github.com/sei-protocol/sei-chain/x/evm/keeper" +) + +func toAddr(addr string) *common.Address { + ca := common.HexToAddress(addr) + return &ca +} + +func TestIsPayablePrecompile(t *testing.T) { + _, evmAddr := keeper.MockAddressPair() + require.False(t, evmkeeper.IsPayablePrecompile(&evmAddr)) + require.False(t, evmkeeper.IsPayablePrecompile(nil)) + + require.True(t, evmkeeper.IsPayablePrecompile(toAddr(bank.BankAddress))) + require.True(t, evmkeeper.IsPayablePrecompile(toAddr(staking.StakingAddress))) + require.True(t, evmkeeper.IsPayablePrecompile(toAddr(gov.GovAddress))) +} diff --git a/x/evm/state/balance.go b/x/evm/state/balance.go index f4e7b23a5..376826586 100644 --- a/x/evm/state/balance.go +++ b/x/evm/state/balance.go @@ -23,13 +23,20 @@ func (s *DBImpl) SubBalance(evmAddr common.Address, amt *big.Int, reason tracing evmAddr, _ = s.k.GetFeeCollectorAddress(s.ctx) } + ctx := s.ctx + + // this avoids emitting cosmos events for ephemeral bookkeeping transfers like send_native + if s.eventsSuppressed { + ctx = ctx.WithEventManager(sdk.NewEventManager()) + } + usei, wei := SplitUseiWeiAmount(amt) addr := s.getSeiAddress(evmAddr) - s.err = s.k.BankKeeper().SubUnlockedCoins(s.ctx, addr, sdk.NewCoins(sdk.NewCoin(s.k.GetBaseDenom(s.ctx), usei)), true) + s.err = s.k.BankKeeper().SubUnlockedCoins(ctx, addr, sdk.NewCoins(sdk.NewCoin(s.k.GetBaseDenom(s.ctx), usei)), true) if s.err != nil { return } - s.err = s.k.BankKeeper().SubWei(s.ctx, addr, wei) + s.err = s.k.BankKeeper().SubWei(ctx, addr, wei) if s.err != nil { return } @@ -54,18 +61,25 @@ func (s *DBImpl) AddBalance(evmAddr common.Address, amt *big.Int, reason tracing s.SubBalance(evmAddr, new(big.Int).Neg(amt), reason) return } + if s.HasSelfDestructed(evmAddr) { // redirect coins to fee collector, since simply burning here would cause coin supply mismatch evmAddr, _ = s.k.GetFeeCollectorAddress(s.ctx) } + ctx := s.ctx + // this avoids emitting cosmos events for ephemeral bookkeeping transfers like send_native + if s.eventsSuppressed { + ctx = ctx.WithEventManager(sdk.NewEventManager()) + } + usei, wei := SplitUseiWeiAmount(amt) addr := s.getSeiAddress(evmAddr) - s.err = s.k.BankKeeper().AddCoins(s.ctx, addr, sdk.NewCoins(sdk.NewCoin(s.k.GetBaseDenom(s.ctx), usei)), true) + s.err = s.k.BankKeeper().AddCoins(ctx, addr, sdk.NewCoins(sdk.NewCoin(s.k.GetBaseDenom(s.ctx), usei)), true) if s.err != nil { return } - s.err = s.k.BankKeeper().AddWei(s.ctx, addr, wei) + s.err = s.k.BankKeeper().AddWei(ctx, addr, wei) if s.err != nil { return } diff --git a/x/evm/state/statedb.go b/x/evm/state/statedb.go index 5ffe9bac1..6686adb5f 100644 --- a/x/evm/state/statedb.go +++ b/x/evm/state/statedb.go @@ -31,6 +31,9 @@ type DBImpl struct { k EVMKeeper simulation bool + // for cases like bank.send_native, we want to suppress transfer events + eventsSuppressed bool + logger *tracing.Hooks } @@ -47,6 +50,14 @@ func NewDBImpl(ctx sdk.Context, k EVMKeeper, simulation bool) *DBImpl { return s } +func (s *DBImpl) DisableEvents() { + s.eventsSuppressed = true +} + +func (s *DBImpl) EnableEvents() { + s.eventsSuppressed = false +} + func (s *DBImpl) SetLogger(logger *tracing.Hooks) { s.logger = logger } diff --git a/x/evm/state/transfer.go b/x/evm/state/transfer.go new file mode 100644 index 000000000..9a75be9e7 --- /dev/null +++ b/x/evm/state/transfer.go @@ -0,0 +1,21 @@ +package state + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/vm" +) + +func TransferWithoutEvents(db vm.StateDB, sender, recipient common.Address, amount *big.Int) { + sdb, ok := db.(*DBImpl) + if !ok { + panic("EventlessTransfer only works with DBImpl") + } + sdb.DisableEvents() + defer sdb.EnableEvents() + + sdb.SubBalance(sender, amount, tracing.BalanceChangeTransfer) + sdb.AddBalance(recipient, amount, tracing.BalanceChangeTransfer) +} diff --git a/x/evm/state/transfer_test.go b/x/evm/state/transfer_test.go new file mode 100644 index 000000000..f633dc879 --- /dev/null +++ b/x/evm/state/transfer_test.go @@ -0,0 +1,24 @@ +package state_test + +import ( + "math/big" + "testing" + + testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper" + "github.com/sei-protocol/sei-chain/x/evm/state" + "github.com/stretchr/testify/require" +) + +func TestEventlessTransfer(t *testing.T) { + k, ctx := testkeeper.MockEVMKeeper() + db := state.NewDBImpl(ctx, k, false) + _, fromAddr := testkeeper.MockAddressPair() + _, toAddr := testkeeper.MockAddressPair() + + beforeLen := len(ctx.EventManager().ABCIEvents()) + + state.TransferWithoutEvents(db, fromAddr, toAddr, big.NewInt(100)) + + // should be unchanged + require.Len(t, ctx.EventManager().ABCIEvents(), beforeLen) +} From c432d1e3e02a13da58d6513115d4cd6746c7d873 Mon Sep 17 00:00:00 2001 From: Philip Su Date: Mon, 8 Apr 2024 15:37:39 -0700 Subject: [PATCH 34/34] Add upgrade handler for new version --- app/upgrades.go | 1 + 1 file changed, 1 insertion(+) diff --git a/app/upgrades.go b/app/upgrades.go index 2d31840ea..5ab0017cc 100644 --- a/app/upgrades.go +++ b/app/upgrades.go @@ -86,6 +86,7 @@ var upgradesList = []string{ "v4.1.5-evm-devnet", "v4.1.6-evm-devnet", "v4.1.7-evm-devnet", + "v4.1.8-evm-devnet", } // if there is an override list, use that instead, for integration tests