From 4b6e1e5c70b890a23a030b146a1510dea282773f Mon Sep 17 00:00:00 2001 From: Khanh Hoa Date: Sun, 28 Apr 2024 12:34:44 +0700 Subject: [PATCH] update custom bank --- Makefile | 19 +- app/app.go | 7 + app/keepers/keepers.go | 28 +- app/keepers/keys.go | 2 + custom/bank/keeper/keeper.go | 14 +- custom/bank/keeper/msg_server.go | 124 ++++++ custom/bank/module.go | 15 +- go.mod | 2 +- go.sum | 4 +- scripts/relayer/chains/test-1.json | 6 +- scripts/relayer/chains/test-2.json | 6 +- scripts/relayer/relayer-init.sh | 0 scripts/run-node.sh | 12 +- scripts/tests/ibc-hooks/counter/Cargo.toml | 43 ++ scripts/tests/ibc-hooks/counter/README.md | 11 + .../ibc-hooks/counter/artifacts/checksums.txt | 1 + .../artifacts/checksums_intermediate.txt | 1 + .../ibc-hooks/counter/artifacts/counter.wasm | Bin 0 -> 177531 bytes .../tests/ibc-hooks/counter/src/contract.rs | 395 ++++++++++++++++++ scripts/tests/ibc-hooks/counter/src/error.rs | 16 + .../tests/ibc-hooks/counter/src/helpers.rs | 48 +++ .../counter/src/integration_tests.rs | 71 ++++ scripts/tests/ibc-hooks/counter/src/lib.rs | 9 + scripts/tests/ibc-hooks/counter/src/msg.rs | 63 +++ scripts/tests/ibc-hooks/counter/src/state.rs | 14 + scripts/tests/ibc-hooks/increment.sh | 97 +++++ scripts/tests/init-test-framework.sh | 151 +++++++ x/ibc-hooks/relay_test.go | 14 +- x/transfermiddleware/pfm_test.go | 4 +- 29 files changed, 1135 insertions(+), 42 deletions(-) create mode 100644 custom/bank/keeper/msg_server.go mode change 100644 => 100755 scripts/relayer/relayer-init.sh create mode 100644 scripts/tests/ibc-hooks/counter/Cargo.toml create mode 100644 scripts/tests/ibc-hooks/counter/README.md create mode 100644 scripts/tests/ibc-hooks/counter/artifacts/checksums.txt create mode 100644 scripts/tests/ibc-hooks/counter/artifacts/checksums_intermediate.txt create mode 100644 scripts/tests/ibc-hooks/counter/artifacts/counter.wasm create mode 100644 scripts/tests/ibc-hooks/counter/src/contract.rs create mode 100644 scripts/tests/ibc-hooks/counter/src/error.rs create mode 100644 scripts/tests/ibc-hooks/counter/src/helpers.rs create mode 100644 scripts/tests/ibc-hooks/counter/src/integration_tests.rs create mode 100644 scripts/tests/ibc-hooks/counter/src/lib.rs create mode 100644 scripts/tests/ibc-hooks/counter/src/msg.rs create mode 100644 scripts/tests/ibc-hooks/counter/src/state.rs create mode 100755 scripts/tests/ibc-hooks/increment.sh create mode 100755 scripts/tests/init-test-framework.sh diff --git a/Makefile b/Makefile index ff3be81e1..95605c202 100644 --- a/Makefile +++ b/Makefile @@ -207,5 +207,22 @@ relayer-start: pica-upgrade: @echo "Starting upgrade" - bash ./scripts/upgrade/upgrade.sh + bash ./scripts/upgrade/upgrade. + + +############################################################################### +### Integration Tests ### +############################################################################### + +integration-test-all: init-test-framework \ + test-ibc-hooks + +init-test-framework: clean-testing-data install + @echo "Initializing both blockchains..." + ./scripts/tests/init-test-framework.sh + ./scripts/relayer/relayer-init.sh + +test-ibc-hooks: + @echo "Testing ibc-hooks..." + ./scripts/tests/ibc-hooks/increment.sh diff --git a/app/app.go b/app/app.go index e6c137c3f..a5df2033f 100644 --- a/app/app.go +++ b/app/app.go @@ -7,6 +7,8 @@ import ( tmproto "github.com/cometbft/cometbft/proto/tendermint/types" runtimeservices "github.com/cosmos/cosmos-sdk/runtime/services" authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" + alliancemodule "github.com/terra-money/alliance/x/alliance" + alliancemoduletypes "github.com/terra-money/alliance/x/alliance/types" "io" "os" "path/filepath" @@ -211,6 +213,7 @@ var ( ibctransfermiddleware.AppModuleBasic{}, circuit.AppModuleBasic{}, wasm08.AppModuleBasic{}, + alliancemodule.AppModuleBasic{}, // this line is used by starport scaffolding # stargate/app/moduleBasic ) @@ -394,6 +397,7 @@ func NewComposableApp( icaModule, ratelimitModule, circuit.NewAppModule(appCodec, app.CircuitKeeper), + alliancemodule.NewAppModule(appCodec, app.AllianceKeeper, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry, app.GetSubspace(alliancemoduletypes.ModuleName)), // this line is used by starport scaffolding # stargate/app/appModule ) @@ -442,6 +446,7 @@ func NewComposableApp( stakingmiddlewaretypes.ModuleName, ibctransfermiddlewaretypes.ModuleName, wasm08types.ModuleName, + alliancemoduletypes.ModuleName, // this line is used by starport scaffolding # stargate/app/beginBlockers ) @@ -478,6 +483,7 @@ func NewComposableApp( stakingmiddlewaretypes.ModuleName, ibctransfermiddlewaretypes.ModuleName, wasm08types.ModuleName, + alliancemoduletypes.ModuleName, ) // NOTE: The genutils module must occur after staking so that pools are @@ -518,6 +524,7 @@ func NewComposableApp( stakingmiddlewaretypes.ModuleName, ibctransfermiddlewaretypes.ModuleName, wasm08types.ModuleName, + alliancemoduletypes.ModuleName, // this line is used by starport scaffolding # stargate/app/initGenesis ) diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index 3eebb16a1..35e2e9c69 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -2,6 +2,8 @@ package keepers import ( "fmt" + alliancemodulekeeper "github.com/terra-money/alliance/x/alliance/keeper" + alliancemoduletypes "github.com/terra-money/alliance/x/alliance/types" "math" "path/filepath" "strings" @@ -153,6 +155,7 @@ type AppKeepers struct { GroupKeeper groupkeeper.Keeper Wasm08Keeper wasm08Keeper.Keeper // TODO: use this name ? WasmKeeper wasmkeeper.Keeper + AllianceKeeper alliancemodulekeeper.Keeper IBCHooksKeeper *ibchookskeeper.Keeper Ics20WasmHooks *ibc_hooks.WasmHooks HooksICS4Wrapper ibc_hooks.ICS4Middleware @@ -201,7 +204,13 @@ func (appKeepers *AppKeepers) InitNormalKeepers( appKeepers.BankKeeper = custombankkeeper.NewBaseKeeper( logger, - appCodec, runtime.NewKVStoreService(appKeepers.keys[banktypes.StoreKey]), appKeepers.AccountKeeper, appKeepers.BlacklistedModuleAccountAddrs(maccPerms), &appKeepers.TransferMiddlewareKeeper, govModAddress) + appCodec, + runtime.NewKVStoreService(appKeepers.keys[banktypes.StoreKey]), + appKeepers.AccountKeeper, + appKeepers.BlacklistedModuleAccountAddrs(maccPerms), + &appKeepers.TransferMiddlewareKeeper, + govModAddress, + ) appKeepers.AuthzKeeper = authzkeeper.NewKeeper( runtime.NewKVStoreService(appKeepers.keys[authzkeeper.StoreKey]), @@ -272,11 +281,21 @@ func (appKeepers *AppKeepers) InitNormalKeepers( appKeepers.FeeGrantKeeper = feegrantkeeper.NewKeeper(appCodec, runtime.NewKVStoreService(appKeepers.keys[feegrant.StoreKey]), appKeepers.AccountKeeper) appKeepers.UpgradeKeeper = upgradekeeper.NewKeeper(skipUpgradeHeights, runtime.NewKVStoreService(appKeepers.keys[upgradetypes.StoreKey]), appCodec, homePath, bApp, govModAddress) - appKeepers.BankKeeper.RegisterKeepers(appKeepers.StakingKeeper) + appKeepers.AllianceKeeper = alliancemodulekeeper.NewKeeper( + appCodec, + runtime.NewKVStoreService(appKeepers.keys[alliancemoduletypes.StoreKey]), + appKeepers.AccountKeeper, + appKeepers.BankKeeper, + appKeepers.StakingKeeper, + appKeepers.DistrKeeper, + authtypes.FeeCollectorName, + govModAddress, + ) + appKeepers.BankKeeper.RegisterKeepers(appKeepers.AllianceKeeper, appKeepers.StakingKeeper) // register the staking hooks // NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks appKeepers.StakingKeeper.SetHooks( - stakingtypes.NewMultiStakingHooks(appKeepers.DistrKeeper.Hooks(), appKeepers.SlashingKeeper.Hooks()), + stakingtypes.NewMultiStakingHooks(appKeepers.DistrKeeper.Hooks(), appKeepers.SlashingKeeper.Hooks(), appKeepers.AllianceKeeper.StakingHooks()), ) // ... other modules keepers @@ -334,7 +353,7 @@ func (appKeepers *AppKeepers) InitNormalKeepers( appKeepers.PfmKeeper = pfmkeeper.NewKeeper( appCodec, appKeepers.keys[pfmtypes.StoreKey], - appKeepers.TransferKeeper.Keeper, + nil, appKeepers.IBCKeeper.ChannelKeeper, &appKeepers.DistrKeeper, appKeepers.BankKeeper, @@ -583,6 +602,7 @@ func (appKeepers *AppKeepers) initParamsKeeper(appCodec codec.BinaryCodec, legac paramsKeeper.Subspace(transfermiddlewaretypes.ModuleName).WithKeyTable(transfermiddlewaretypes.ParamKeyTable()) paramsKeeper.Subspace(stakingmiddlewaretypes.ModuleName) paramsKeeper.Subspace(ibctransfermiddlewaretypes.ModuleName) + paramsKeeper.Subspace(alliancemoduletypes.ModuleName).WithKeyTable(alliancemoduletypes.ParamKeyTable()) return paramsKeeper } diff --git a/app/keepers/keys.go b/app/keepers/keys.go index e185abde7..370a0530c 100644 --- a/app/keepers/keys.go +++ b/app/keepers/keys.go @@ -6,6 +6,7 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" ibcexported "github.com/cosmos/ibc-go/v8/modules/core/exported" + alliancemoduletypes "github.com/terra-money/alliance/x/alliance/types" // bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" @@ -65,6 +66,7 @@ func (appKeepers *AppKeepers) GenerateKeys() { ibcexported.StoreKey, ibchookstypes.StoreKey, icahosttypes.StoreKey, ratelimitmoduletypes.StoreKey, txBoundaryTypes.StoreKey, + alliancemoduletypes.StoreKey, ) // Define transient store keys diff --git a/custom/bank/keeper/keeper.go b/custom/bank/keeper/keeper.go index 3ccfdd1e6..ace6a9320 100644 --- a/custom/bank/keeper/keeper.go +++ b/custom/bank/keeper/keeper.go @@ -2,28 +2,27 @@ package keeper import ( "context" - "cosmossdk.io/core/store" "cosmossdk.io/log" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" accountkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + banktypes "github.com/notional-labs/composable/v6/custom/bank/types" + alliancekeeper "github.com/terra-money/alliance/x/alliance/keeper" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" "github.com/cosmos/cosmos-sdk/x/bank/types" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - banktypes "github.com/notional-labs/composable/v6/custom/bank/types" - transfermiddlewarekeeper "github.com/notional-labs/composable/v6/x/transfermiddleware/keeper" ) type Keeper struct { bankkeeper.BaseKeeper - tfmk banktypes.TransferMiddlewareKeeper sk banktypes.StakingKeeper - acck accountkeeper.AccountKeeper + ak alliancekeeper.Keeper } var _ bankkeeper.Keeper = Keeper{} @@ -40,12 +39,13 @@ func NewBaseKeeper( keeper := Keeper{ BaseKeeper: bankkeeper.NewBaseKeeper(cdc, storeService, ak, blockedAddrs, authority, logger), tfmk: tfmk, - acck: ak, + ak: alliancekeeper.Keeper{}, } return keeper } -func (k *Keeper) RegisterKeepers(sk banktypes.StakingKeeper) { +func (k *Keeper) RegisterKeepers(ak alliancekeeper.Keeper, sk banktypes.StakingKeeper) { + k.ak = ak k.sk = sk } diff --git a/custom/bank/keeper/msg_server.go b/custom/bank/keeper/msg_server.go new file mode 100644 index 000000000..49fdeccca --- /dev/null +++ b/custom/bank/keeper/msg_server.go @@ -0,0 +1,124 @@ +package keeper + +import ( + "context" + + "cosmossdk.io/core/address" + errorsmod "cosmossdk.io/errors" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + "github.com/cosmos/cosmos-sdk/telemetry" + sdk "github.com/cosmos/cosmos-sdk/types" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/hashicorp/go-metrics" +) + +type msgServer struct { + types.MsgServer + + keeper bankkeeper.Keeper + addressCodec address.Codec +} + +var _ types.MsgServer = msgServer{} + +func NewMsgServerImpl(keeper Keeper, addressCodec address.Codec) types.MsgServer { + return &msgServer{ + MsgServer: bankkeeper.NewMsgServerImpl(keeper), + keeper: keeper, + addressCodec: addressCodec, + } +} + +func (k msgServer) Send(goCtx context.Context, msg *types.MsgSend) (*types.MsgSendResponse, error) { + from, err := k.addressCodec.StringToBytes(msg.FromAddress) + if err != nil { + return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid from address: %s", err) + } + to, err := k.addressCodec.StringToBytes(msg.ToAddress) + if err != nil { + return nil, sdkerrors.ErrInvalidAddress.Wrapf("invalid to address: %s", err) + } + + if !msg.Amount.IsValid() { + return nil, errorsmod.Wrap(sdkerrors.ErrInvalidCoins, msg.Amount.String()) + } + + if !msg.Amount.IsAllPositive() { + return nil, errorsmod.Wrap(sdkerrors.ErrInvalidCoins, msg.Amount.String()) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + if err := k.keeper.IsSendEnabledCoins(ctx, msg.Amount...); err != nil { + return nil, err + } + + if k.keeper.BlockedAddr(to) { + return nil, errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to receive funds", msg.ToAddress) + } + + err = k.keeper.SendCoins(ctx, from, to, msg.Amount) + if err != nil { + return nil, err + } + + defer func() { + for _, a := range msg.Amount { + if a.Amount.IsInt64() { + telemetry.SetGaugeWithLabels( + []string{"tx", "msg", "send"}, + float32(a.Amount.Int64()), + []metrics.Label{telemetry.NewLabel("denom", a.Denom)}, + ) + } + } + }() + + return &types.MsgSendResponse{}, nil +} + +func (k msgServer) MultiSend(goCtx context.Context, msg *types.MsgMultiSend) (*types.MsgMultiSendResponse, error) { + if len(msg.Inputs) == 0 { + return nil, types.ErrNoInputs + } + + if len(msg.Inputs) != 1 { + return nil, types.ErrMultipleSenders + } + + if len(msg.Outputs) == 0 { + return nil, types.ErrNoOutputs + } + + if err := types.ValidateInputOutputs(msg.Inputs[0], msg.Outputs); err != nil { + return nil, err + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + // NOTE: totalIn == totalOut should already have been checked + for _, in := range msg.Inputs { + if err := k.keeper.IsSendEnabledCoins(ctx, in.Coins...); err != nil { + return nil, err + } + } + + for _, out := range msg.Outputs { + accAddr, err := k.addressCodec.StringToBytes(out.Address) + if err != nil { + return nil, err + } + + if k.keeper.BlockedAddr(accAddr) { + return nil, errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to receive funds", out.Address) + } + } + + err := k.keeper.InputOutputCoins(ctx, msg.Inputs[0], msg.Outputs) + if err != nil { + return nil, err + } + + return &types.MsgMultiSendResponse{}, nil +} diff --git a/custom/bank/module.go b/custom/bank/module.go index 9b7879096..5a3b70164 100644 --- a/custom/bank/module.go +++ b/custom/bank/module.go @@ -1,6 +1,7 @@ package bank import ( + "cosmossdk.io/core/address" "fmt" "github.com/cosmos/cosmos-sdk/codec" @@ -15,17 +16,19 @@ import ( type AppModule struct { bankmodule.AppModule - keeper custombankkeeper.Keeper - subspace exported.Subspace + keeper custombankkeeper.Keeper + subspace exported.Subspace + addressCodec address.Codec } // NewAppModule creates a new AppModule object func NewAppModule(cdc codec.Codec, keeper custombankkeeper.Keeper, accountKeeper types.AccountKeeper, ss exported.Subspace) AppModule { bankModule := bankmodule.NewAppModule(cdc, keeper, accountKeeper, ss) return AppModule{ - AppModule: bankModule, - keeper: keeper, - subspace: ss, + AppModule: bankModule, + keeper: keeper, + subspace: ss, + addressCodec: accountKeeper.AddressCodec(), } } @@ -33,7 +36,7 @@ func NewAppModule(cdc codec.Codec, keeper custombankkeeper.Keeper, accountKeeper // NOTE: Overriding this method as not doing so will cause a panic // when trying to force this custom keeper into a bankkeeper.BaseKeeper func (am AppModule) RegisterServices(cfg module.Configurator) { - types.RegisterMsgServer(cfg.MsgServer(), bankkeeper.NewMsgServerImpl(am.keeper)) + types.RegisterMsgServer(cfg.MsgServer(), custombankkeeper.NewMsgServerImpl(am.keeper, am.addressCodec)) types.RegisterQueryServer(cfg.QueryServer(), am.keeper) m := bankkeeper.NewMigrator(am.keeper.BaseKeeper, am.subspace) diff --git a/go.mod b/go.mod index 7c049d859..6cff14654 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,7 @@ require ( google.golang.org/grpc v1.62.1 gotest.tools/v3 v3.5.1 mvdan.cc/gofumpt v0.4.0 + github.com/terra-money/alliance v0.4.3 ) require github.com/cometbft/cometbft-db v0.11.0 // indirect @@ -376,6 +377,5 @@ replace ( github.com/prometheus/common => github.com/prometheus/common v0.47.0 github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 -// github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v8 => github.com/notional-labs/ibc-apps/middleware/packet-forward-middleware/v8 v8.0.0-0.20240327105252-f31a09121d57 ) diff --git a/go.sum b/go.sum index d6e79579f..7c7717f92 100644 --- a/go.sum +++ b/go.sum @@ -1614,6 +1614,8 @@ github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= +github.com/terra-money/alliance v0.4.3 h1:vlTsGkXOO78T8SJHjSQshbiTw7WSdieybWMOYsWyTmY= +github.com/terra-money/alliance v0.4.3/go.mod h1:ljXUIs49+2gHHbNljOoLrIaYV+1QHthS3vdtA7hODe0= github.com/tetafro/godot v1.4.11 h1:BVoBIqAf/2QdbFmSwAWnaIqDivZdOV0ZRwEm6jivLKw= github.com/tetafro/godot v1.4.11/go.mod h1:LR3CJpxDVGlYOWn3ZZg1PgNZdTUvzsZWu8xaEohUpn8= github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI= @@ -2551,4 +2553,4 @@ rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= \ No newline at end of file +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/scripts/relayer/chains/test-1.json b/scripts/relayer/chains/test-1.json index c733b0309..9ec7eeee0 100644 --- a/scripts/relayer/chains/test-1.json +++ b/scripts/relayer/chains/test-1.json @@ -5,11 +5,11 @@ "chain-id": "test-1", "rpc-addr": "http://localhost:16657", "grpc-addr": "", - "account-prefix": "centauri", + "account-prefix": "pica", "keyring-backend": "test", - "default-denom": "stake", + "default-denom": "ppica", "gas-adjustment": 1.5, - "gas-prices": "1stake", + "gas-prices": "100ppica", "coin-type": 118, "debug": true, "timeout": "10s", diff --git a/scripts/relayer/chains/test-2.json b/scripts/relayer/chains/test-2.json index f5d1c5d3e..dbea32fa3 100644 --- a/scripts/relayer/chains/test-2.json +++ b/scripts/relayer/chains/test-2.json @@ -5,11 +5,11 @@ "chain-id": "test-2", "rpc-addr": "http://localhost:26657", "grpc-addr": "", - "account-prefix": "centauri", + "account-prefix": "pica", "keyring-backend": "test", - "default-denom": "stake", + "default-denom": "ppica", "gas-adjustment": 1.5, - "gas-prices": "100stake", + "gas-prices": "100ppica", "coin-type": 118, "debug": true, "timeout": "10s", diff --git a/scripts/relayer/relayer-init.sh b/scripts/relayer/relayer-init.sh old mode 100644 new mode 100755 diff --git a/scripts/run-node.sh b/scripts/run-node.sh index c544a6398..227ca46cd 100755 --- a/scripts/run-node.sh +++ b/scripts/run-node.sh @@ -11,10 +11,10 @@ if [ "$CONTINUE" == "true" ]; then fi rm -rf mytestnet -pkill centaurid +pkill picad -# check DENOM is set. If not, set to upica -DENOM=${2:-upica} +# check DENOM is set. If not, set to ppica +DENOM=${2:-ppica} COMMISSION_RATE=0.01 COMMISSION_MAX_RATE=0.02 @@ -61,7 +61,7 @@ $BINARY add-genesis-account $KEY "1000000000000000000000${DENOM}" --keyring-back $BINARY add-genesis-account $KEY1 "1000000000000000000000${DENOM}" --keyring-backend $KEYRING --home $HOME_DIR $BINARY add-genesis-account $KEY2 "1000000000000000000000${DENOM}" --keyring-backend $KEYRING --home $HOME_DIR -update_test_genesis '.app_state["gov"]["params"]["voting_period"]="20s"' +# update_test_genesis '.app_state["gov"]["params"]["voting_period"]="20s"' update_test_genesis '.app_state["mint"]["params"]["mint_denom"]="'$DENOM'"' update_test_genesis '.app_state["gov"]["params"]["min_deposit"]=[{"denom":"'$DENOM'","amount": "1000000"}]' update_test_genesis '.app_state["crisis"]["constant_fee"]={"denom":"'$DENOM'","amount":"1000"}' @@ -71,11 +71,11 @@ update_test_genesis '.app_state["staking"]["params"]["bond_denom"]="'$DENOM'"' $SED_BINARY -i '0,/enable = false/s//enable = true/' $HOME_DIR/config/app.toml $SED_BINARY -i 's/swagger = false/swagger = true/' $HOME_DIR/config/app.toml $SED_BINARY -i -e 's/enabled-unsafe-cors = false/enabled-unsafe-cors = true/g' $HOME_DIR/config/app.toml -$SED_BINARY -i 's/minimum-gas-prices = "0.25upica"/minimum-gas-prices = "0.0upica"/' $HOME_DIR/config/app.toml +$SED_BINARY -i 's/minimum-gas-prices = "0.25upica"/minimum-gas-prices = "0.0ppica"/' $HOME_DIR/config/app.toml # Sign genesis transaction -$BINARY gentx $KEY "1000000000000000000000${DENOM}" --commission-rate=$COMMISSION_RATE --commission-max-rate=$COMMISSION_MAX_RATE --keyring-backend $KEYRING --chain-id $CHAIN_ID --home $HOME_DIR +$BINARY gentx $KEY "1000000000000000000${DENOM}" --commission-rate=$COMMISSION_RATE --commission-max-rate=$COMMISSION_MAX_RATE --keyring-backend $KEYRING --chain-id $CHAIN_ID --home $HOME_DIR # Collect genesis tx $BINARY collect-gentxs --home $HOME_DIR diff --git a/scripts/tests/ibc-hooks/counter/Cargo.toml b/scripts/tests/ibc-hooks/counter/Cargo.toml new file mode 100644 index 000000000..f164afc0e --- /dev/null +++ b/scripts/tests/ibc-hooks/counter/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "counter" +description = "Cosmwasm counter dapp, with permissions for testing Osmosis wasmhooks" +version = "0.1.0" +authors = ["osmosis contributors"] +edition = "2021" + +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[package.metadata.scripts] +optimize = """docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/rust-optimizer:0.12.6 +""" + +[dependencies] +cosmwasm-schema = "1.1.3" +cosmwasm-std = "1.1.3" +cosmwasm-storage = "1.1.3" +cw-storage-plus = "0.16.0" +cw2 = "0.16.0" +schemars = "0.8.10" +serde = { version = "1.0.145", default-features = false, features = ["derive"] } +thiserror = { version = "1.0.31" } + +[dev-dependencies] +cw-multi-test = "0.16.0" diff --git a/scripts/tests/ibc-hooks/counter/README.md b/scripts/tests/ibc-hooks/counter/README.md new file mode 100644 index 000000000..f4394fe87 --- /dev/null +++ b/scripts/tests/ibc-hooks/counter/README.md @@ -0,0 +1,11 @@ +# Counter contract from [Osmosis Labs](https://github.com/osmosis-labs/osmosis/commit/64393a14e18b2562d72a3892eec716197a3716c7) + +This contract is a modification of the standard cosmwasm `counter` contract. +Namely, it tracks a counter, _by sender_. +This is a better way to test wasmhooks. + +This contract tracks any funds sent to it by adding it to the state under the `sender` key. + +This way we can verify that, independently of the sender, the funds will end up under the +`WasmHooksModuleAccount` address when the contract is executed via an IBC send that goes +through the wasmhooks module. diff --git a/scripts/tests/ibc-hooks/counter/artifacts/checksums.txt b/scripts/tests/ibc-hooks/counter/artifacts/checksums.txt new file mode 100644 index 000000000..1f542142d --- /dev/null +++ b/scripts/tests/ibc-hooks/counter/artifacts/checksums.txt @@ -0,0 +1 @@ +c0e7a3b40d9710f6f72322293ba5cd871714008d9accd9a91c0fb08272609054 counter.wasm diff --git a/scripts/tests/ibc-hooks/counter/artifacts/checksums_intermediate.txt b/scripts/tests/ibc-hooks/counter/artifacts/checksums_intermediate.txt new file mode 100644 index 000000000..0394e5fc2 --- /dev/null +++ b/scripts/tests/ibc-hooks/counter/artifacts/checksums_intermediate.txt @@ -0,0 +1 @@ +bffb3256d4fd5668e497ae5671844ef3dcdf1a5f2594747b894dfc02e243ab0e ./target/wasm32-unknown-unknown/release/counter.wasm diff --git a/scripts/tests/ibc-hooks/counter/artifacts/counter.wasm b/scripts/tests/ibc-hooks/counter/artifacts/counter.wasm new file mode 100644 index 0000000000000000000000000000000000000000..e3e5b53bd76f861b707222cd0cca618845dfe52b GIT binary patch literal 177531 zcmeFa3)E$2S?9Sg=l0!CT~et^3fSkQl&>H~3Kyn|q{P}aR1yRw(u|$9>6HqkG^q+9 zse~9@S*bz-M2Q+SB5GRGik-BA#ul5{q7+V~(-JjfGYe+KW$6UX%+NiV)wF{n%KZNS z=iU39uj;E?2sGV>BH!8j-TS>g_vd+^_ubhwx4b#evMm4AeAB+-@ZtQh{$~5i!@kx} zwl7z4m&>@x_I2X2?;;gue(BDAgH+PAaOb{Wl%zV*e10eG-+8Bd;crH1(^T6L7v-I5 zIyI2){rpb;mlZ!SAr z^8Nd>Of}=(>)!h2Yi?}IhPUj0(@o#>%@-eh>D9OGKXm=~ye&G}I4M4~|4oeK(A77+ z^^FJjzb%_rON;xj{ibjJ=9j*FMV<9c;Tx~H^-b5Uc+|ai{~O=>y)m1)o8P+s(A%zl z({OW>~oqstdcA1n8~Xci-~Xn!a`lZ}<0RYJ zx#g|bz9swC!8JGB@Rm2}ouA8Ruif9?J&>qf>5-}0{f2lIF5cjx!y zKb-$a{-gQ5`Fryp%YQt-FTdcb@BXgu`bYoh^{@XQ|LH$@!o{0&wnBRK>p7Bf*;C%B7a~0lR0HSmH%}9Gx?Tx6c=27fAej;_;~)J_ZA;1Ui3u%q6_}#{Ezbsu0NLl zhy2g@{Z#(v`CsJ!d;Vnpm-%1iPvsZfUA((^SMitmUBx?#A1v-K{(12;#m^S^6~9qj z@V?@=ihoi3%i_V}qs2qTzbbyI`0Zl$u20SUdhwZ}=pN4RydbNqgX3aLRu%Qe164kJ zEU)uXx9*I}Vs}++%Qn{e&3hMkye-=_?$z5a>+I&OkLPn#7^=d*&-H8=uSK0T<4(Q3 zd9lt$<$OL@FOJHpQ@`|J{pI}TdhqIn+|AI+AX=#p*LkH5&L8)9al2oM-7EKqI!X;RB{0YJIh%3!x0ThA&b*eEvU*W(gB~HtLyGy7jOIgozMQ{f)QJJ8(<-p0%Mtwp)6y`_8h94M zac57qXLe6EJX%!6)^g*X>~#yJu7=|-@26((7lb*XL8w|?fi!gh2+IUQ#tP~? z%~U*TPnE&Pq~{<-6$`vjV7*=7eYD6shl6ZO$hKs9rrDx}Yq>2G>VO7Uc|E?IP7RGR zhW76?hK&c7i)>VIwcb}M8~UnKmCMEas9R8fxftxFovz3rtArAUHh-WhjCME26{slY zdm1R@pf>|OA@X++J5L)1u7 zbu^LUycmSa+_kC1^kN{k6hK>F&ND#~I-$DSD1%7OaX^7uD2Z6T4Xx_p2bdeU^IY0sk42r2yVF; zj_C%ZN#=7Ha{@#zZyX{Q+I^s@ob&nO0_;P?Z5hb_**Y^eD&oofB9Q) zt~!_1<*g&0*WFu3Ag!9EbrDWIFd(a09$Z|khXiM6kjZIy$Sbb+t>$Q3G&Ov!=qPjL zfxFH`PLzRLBgohEq@jt(7-ZF-Ad6;$I5g7{$mVz8VIjJz!PZjZfUY2u0e{+isBl34 z?L815F#L26F#!{!B;i8sZba5gP@WnO0rOINp%iosef2deK};DC)`Xe&@Uw;rNR#|W z*>og7jjnOI*Y)=z#XTwB6%5sVIS4WUni=VEoRvQVyt`xAubo2 zgDf{f93oP9=6`}*ncO(9&k>E#VsMsQKaQg%Y(nq?(5~q*3!d&mh%o~0gRRAw%$_z2 z0YVxKC}cnZ39gsbTLT4jEzjU_ppY9169AM|PqP;T?|J(X8C9Zp@vU!<<%#?)T)Or0 zWFRZVktJn2J&kb=7+7%r)?D5b%Ri7eYH4SvrQpHU|4NAOv@kUzarbf*#h+vc7cc1K zGCWWIzi!^6^RgZvT%5zsO#kZ|dy;6$a*&D;__k#;iFz;)=;Ol6sQahcWrz6rJAM}W zl6}xgTNCA=YM|m~12e0Qxn~;+4|<`f@5)U_ zx;zq>bh2FB5j1}L0ycy+LRO!XdWfqk-@7nhl|tdXdaVMq>Z)RVdl!1eZbS&TBcRvV zM!ab9M4F2CG#|aPnHra5H-zN84q7TSLW@=?$68O!Uer%z2sNl{+)H+wI-wi%UIHsu zGvjW3AyyFOT>#2e)!&wNU^(R7fm(uQzH5Vqg6xtk=dSAO%2r2DKo@m}WvI3Vaaxgf z9d$*cZpa{qetjd#(-ZZm*}8C@3M~x~O}*hd$p?&x;7Ozreb?Ak(IOKpw-)Omdn)sO zS#NG=UX9uEl4@hHSRR*T8E?q`5&&MbiAw8x^8>o< zif{meb)*V4g)1SIPvj;N?(WTDv~xrefG|`nzy|Wt@*(MjFSl-lQRg~UxopA3kHI&3 ze>t}xMWt-bQAWFi^8~sy)EH<8{#vz8h5&{Pvw2htvlI9~x+?quZFXAt(@Iv)2@^BA zGTVaLBqI8{GIe0THG{(8fSRl!LLD2oVd@Jy#;eT#5Hm8rq_OTrMkhLu;tLvRxFWqP zfgb$Gs?Gkl5u8FRU zlIbSO)I=@$9oI-5$#fH)Nt-TGSwrORR%OW41V`fZ^nq%+7XN$e20?0Zw_WtYhT_2_q_5yeBy9Z45r}^%m zt7Pt!xN{RN=BAoRn42{5HOJgT8vWVuT~pz-n7d~wUziR9z~;s9sC;|8m>$Mhfx~bF zharqkL`D}gCIqSMKZX6m5?o_jhMG1PB}lrmWl)5Jk?coaR(#-+Y!^2O^qpvt-3R%z z^T6;s^mMoEn6f^Gp}SP>Ze;cJuimOL!8$Tn^2M@K76_ESwbekCE5b(#>Y?pl$q#Ni z$`MIh)}P5_64f8UtwQ`>N(rVjsuriate=d_ndn`m4gtME<7SM*ebwCFY7Y0#e(E4O zscoYlRHJie-sl`&cy{?{aehhOQ5xC1&vIn*csZ^t2=5$sY}D!cD%q$4P+5NoJFA(U z#5+jXTD)OLackJ%Ps?}CwhIV8*J=QR0jEmDmr2JB*;iDHNh)20V(0*L44C>BvKb0%~i8|<)n(FFlK=)@zoV^ zkIiUaa1gDo7hsm{skj$eLULOG5?1c%Xj&FXS+Br@xN8M20I={t>r0-YXehc5nt@uO znYAtajSi+U-S={FZIH~qQ3nY!aK9>CP_=y{-WC!E^2{3YG#^3+ExElRcBUM)mx&rri{TnIJn1{SA^MnTWonGYNRDPPkBM}u5%?Xt?~DxIpPWwvFo)d!dCx_T@wv;AOD4T0BIAZGkB+D7aKOoHv@qubXXy zOS5zylmsb{X0htEp5w7iq*j)wtry{{hCW5Njx##GW+pLV%PbUR@QNK|t2{$#1-sx3 zL}4|}8n1PC|yA+h0zQ^Bw)bcS2&P;+s5>kzNaUff^D8d7|%jWuLV zpbtQGs+{TN5$ID;Vw8akS=_*aC#MMXfnEg&1H&C7Y~JBhU#1A%<1Z{W9`0!6=w4Lr z0N5*@J6@{t(8>c5+h|r~$vaQ*PKS3K9a(DCvQg1#RWnzOYF?6RwtK%M)!YfyOxU|o z%{LGR0A$zk5~$}dqImf2dVVAqVmmd_6NMJepDU7po^uTim(W9SKiBWi6*!Xq=+uvUuv7;P zB8#Safz*aVFD{PSVd>OId9g(0QU9ny!3yy-y+4*hKvBIs+Vl>Zy1caeVEt(gcre^0 zJ($B()7vG%P8$l?qia+JwVGn^8fbabj&dWVK&4ueo{7mV zMN|xAYeut@#=N^x(3!<%s?PaPT)yl;rLY=>+Y_VAO3#@oW?{dYFqB7oe!Nv3sLqd? zu-MutgT>5jvsPp=Hcck=DonTs_{dmFU}goy^{XYZ##&IJ5cXWPPC9L#OHDn1ZsaXA zktc&6;xPKw2-lCghc%w1(AgpqhQB`{4*-q&d$Q5_(Ekko&2hb%|DMM+i;|<~!xP7{ zDN}V}B~M^Io@lNvwC3tUYp$}YIWbpd2|z)z1OT0M`4HQZsIC}7=HY0#8Uuu8)v_vU z>7vqZ!vp7EshXRw#EzPQvSa|uH2qIOTry1~3d90~R~kUgAQnM>JA}~MJ8?0<8T!ggkkw*~FRdnK;aYH& zLRyPLm6snE6{>Gk&?c@Tq_y6fy{6B@-4su48kKTXYo_w?!W5q;u{+|4GhOAK3;#XG*QF%2qdc?ECu;M^= za8%e_^pVIX`j9;74XNev+WCRngzpJy#kdg)BEV`P8N?88=9ZW_|(< zUVwkZNm$!^7Z$1+K^-~K&~uvf>h!PBEh`ey6l}QB!mp1x5~h1!R)Xypcl|sGIFIQ0 zTuBNUjL!#*=LZCi1HESsf7u>f0oUN;1Y7a(DPRkg7&yJKL_xhNesxeHv}WAja`DO$ z(q*~WJ)*w&+-?}NoEA38*xW8WS~A!T2+6R8kwd@z#=d0A(0JTpUvkS+w+v2aaAomh zSz@-V?gvU51PO6g1%}@FAX9TXmdOsZUUCe$QJJeLZ06OIH)1tcNusy9ZjG;QQbP~t zOoHDJ=cBdKYmX$%OsV?5v;}!IVb56Mr`1~)?2!~QF(e~kZL*c}s*t}&SziAquQ>I? z+L2oA*Y}8}%Hi)d+EqUCLdjTj;lRC)7LY|xxB3HeRxdb`N`}f2XbAmCXCZ)3f85SE z<^ic$iSQEv4_XZk!jcvNj|;hh{P8TMqUFF1zb%^EuCcS=HLXC@?n}o-UEW-mmn~$x zSk(E!#UjrzY`cvxk;eEl%_)Kz@IhIB3bVpl5+Tc9v!hra1Pv1l{mbKt?{vj@CbVx^ ze_l^jIV40|`C6`EsePdjAOUPpuh{LA;bf=W-98o83|uZ1Qtnf?2rjK6(5~to*Z00j zgu<-0wYXONntVKsryU7kdrkDu97e-X7CE$-i{XpF&m8#a0;jx9R;w3O5Jj7qsq(6d zJkMaz0#pg){5MC-V6scvMaoK>c`baQH)tP5D6~R7LyC-2)}o3!NvUqSTtnj|P0ch( zQ^J<2cR!>ww9QIGqs7e+ju+c@4vf3iA}M5a0g1T_8VbT9g0AISwF#nUlqV(6aZdB8 z^AU1v81ilyTuUN>7!&wj;e;VAk)DJQq)M366x}#(V+jx8MtI{2=*~9TLXzgt5dLRK z^Oiyw32Q}m%mqn@S(b~};8OEI-LzCLS|&wD=^kllyH=3s%b=Ax+L!78p%M?1{;HM? zJ{>4zLhu0 zVx7nvpfoLSt`IkvBZQFqnWzWKt>{&Qo_M~v-+ux4t>D&4=$r1aSmOTU+*Gp%& zahv6F82_TxtH#4d_CQm-L*-k3=+KK1u>hi?WnkE0EE$tXT#>HL4dg4=5pR2g42T5Hh-gY4cr0SC zCdvtItZ!nYypK8}W_~7&+6~izel>OaB4je!jCN(l;_pNO{c5v9m;o^{SZkQXpj*Qv zjEkpNH7?GT;m`yF&h^!9$aUU4xLkZ7ADts+eIOs%n}_`|O;-zBSo$;u%RMV240qYb zN0rU4G@atMHJxl8NOGnCz@>B~o!ZD0X|E>aQ0}hQBJq+Z+*6=`gZzJ-Vh8u+7SHht zfr4toAs2q+KX_Y!}_pf!*WHJIZBTaeyp)teOq*hV!jCKcOg{nR|epDz!U+ zxRe<<6=@jIJep@9g)Ej~$OG;GzV9~r4(9gVGEaiY5S4?Zkr9dmg@r_JOI)TzsScOq z(ohfa=>Qdf2L}}2({M-`kN92_*n=m?SX|ZsGLdJB-a(#r5pW0nf&(3yTn&RFtBG@@ z)oS2Gz(Li~(cLte3_6zip_z&12Dr}r!SR810-Vejr&ZwGN!>(KIz(82Vy?O+S)`~uMAnpaegK% zZ2yBeJgu;SS%_JSC_?{ep-j4=(GNBDSt(a1Uo1*i*f45(-1Y6wXx^J6yb$fwe=RdK z0uijLl=T-3asjUttZ;24C~nw6HQNEH+LtEQ>uIW0g`};sBOQaF0SUvMn#gR==vmY-II;%`|)nvNutW^wR(s{Bw7eBlUF6DvnnX zWfA^e)3iA(8 zV}$v0#5(KF0wzziAZJdO{SBNEw?ykbsF}T(?OrIBS17?i;2%*xH!bRgbs!sZKqW1!%m~0lq7+KO;*&V?5`b57IudL~vjhE-u7M8(Y_p2F z(W|{oq-`Xk=CTo-+TPJpi|vNfHtb#?2!v=FMw(L$D@GUAx`buux@Qr*$1hT8X(|?} zFw2;bV2VE`7oxO)n=DcV5?CKdNYHYXG)lWPHAt!}r2;k@3@9Z{1e6>sv6e`R4DcHf zZ7!8q6yg-NvZOrJ%}HMexo5T@1*-5%0IIT%p$H*VG@BDKIGd#@$g|hbtX85GzI4L) zCwqBU3T$|A+{4Qf*^jZg1{kCGsZN7dCN9NQ_p;7m$f-MgovgA!w^A&I2M6j4l^q75 zQmQQ&7@vqyTyX6SQSGssJw^TTiS<2_s(Y}WeU*v0Th9)!bPrAjc6$JrQf-wHf@h;+ zcj(6l>-^hO-QvW$_0NAHyVS$(*2Muv96(Uu0k*_xs_-CnIUuC+(HsG(Nr+qJ)DxB( z^(kYql`S4)Tu5L;qBncl5J~banf9oUND)T}Fyo?97I(m~XkiH9tOi3tkO3icV%r|X z)EWLZqo7YxIK2c*PBSFWo6c_Ds4-oV&6_azGdNKHxl$8Z*%&_0b?}!&H1X_7Jp+Y^ zlJwY9bAG6jP3MPy;D9-Gyl4s)24UwCK1J}K1do(cdgA8MieoG*ynI#RU0I4uJPE1O zwWn-Y2RsCA0wm&Z2S(%+ZDipG5nN~8sz2vFhS}xAPsYM~m2WLNkyb&c>Nz@1(R>NW zCa%v@2FbiU$h7aI8KkB&RKx}Qo)$nW7pZAmlzKm!u5&_6R=mm%!5Vz57rCj^%%s%` zme3n!BG71N>WC0nbA%A|23N2o%_e37Gn9&%5JMAYQte=-lh-#ht*jr+)Hzsp#Y|f1 z?7|Q+vgo$z2&q_|Fq5B0$ACW#H!+i*iJ6Y8+c1+Kij?ehZf_WP7L*_GfRz9+|!2V7>j&CE08}LxlElJ*x-A*3QFrH~a@rRpV^ zV8b=skh-mUhDqWitnUM3S8Rvda#C-?Nt3!0PU5jS2^+^M!dhhbn>q4GIIv*qPYWi+ zXm|kNCxS_qErRL)NZ>@DbQ^F=wM_{o(l{o9i3|q7sW<2wikpBFhiNszq}o%0Y1R4$ z=gRu6U=ry9+YakDSGosnfmF9pQhonVNRJt!ekIAKcEkyydM4SV0Zvy>ir$Y6QJa9p z@Lypw(kHYs9NT={DCly#Ud&-538SR}HMv7pFR@Kz6;Q`|@Z4k1QSJ)LC0sLn!+kX=aPs=cNdeVwq(DDO0i3)> z3Z!nU{z@s(N0zE$BL(`c6zET+z@+X(3h>x8D9yDgDO+qXcJCZ%wEStx8 zWJ65T9+nhF;g}uT>nGb+3yjj9{Fe*e8h&XHrxQ&Llu0!!c#6;%3&7$7^=q|b87$r+ z#F$u5FqeJVhzhL?O<>-#`V_>~npS3rPL$ZQ)okq8k!*9ajh%=siYUo0SPq+Zb5!Us z_xhL?C9usIB6h2)Vb-cWrwVC#7lUI48K<|$L?^ScC zSkS52aA#Jnf1IF`JES-%RSwp&9t1O@X-QHgpjF)i_2)D>_F|$-{Yvs^+7Y*c4wq$> z>aC!gcr?3d0$^aLn)>!AQ-C}0D_XqoM~`O1UuH!TCMBOnj*YJ7fufQVbE%5@g%=n1 z`S{QN>~BB*6NmTs^a#aE{NRuO{FnZ>|M=j) zfA?Q!&4VWhdd;s|h1icCoLJ>CN2}@0sSy0`)CkA(8gg~Vc%hnq{fF*Q@+7G%cTl{< zZ8guoTuJx5s%6Bj<&}3-3q0YC@6zjgUU4^x%1e8SchT)D?!HUr()^xn?_%8R?!If! zZ~y!c{KAj?a_7h#4K6U@V-J7i=RR`(f64Y7*6|oLH>?)#poh;fpS)(hVfcXpw&CTw zuJoes-yn9ji*@eF>-7hR9|NCU1RQCRj^@?Tc%kiz#bbf?gr2f${`j6~APw;K?(8q8 zI?GdM)#8IT`u5W=n7@bl;Y`#=|0DDTPBkzHz%w{dzpxq{9R6ZK94wEhHgoZb7)0kR zL-l8nbfaW~C~AZ zy@>aGWL?pVBZ0IQ9UKmmF?J4-05Wh)u4NSk5ZZf(fGr#1IY9)O_OMsnqm!Doeb+|< zlz^mtu4S9x;CLU=p$vlNxk3!Iv=a!W)g8ugj6o?f*$zVCd?CFlP?o@T0;&tHqzdw7 z)JJwbjO>aHw7jB#2yL0Vcd}vz8Y6n%87abY3@BLZ6}xM;UJ;NN3{5t`z`v37rXaMc`E{+g}={gEO!&;d-2qIaY?n)ec-6NSml7?<_u zevJ0k16Sy6sAW{xL$->@nGSVC3im9`l= zhJY1q>YN{$d+|@9x_u01K`TlHLt`=G2NVj}Og^{RD~d>r{dsy+85&G2D;fi7W!blTZy?U63Vzn8OH6& z_iB`$w-<4!uyLI2b^Wv@hb9d5BFIlmp}bX5Ci4C2O@;-j@V(WeT>I2xQL_@i$0D}S zx?j57!)aQ#gIq<*Sl}wJGos6Vz!IroVORf#WYTZx&)o1$6Zr+R(^LKXJlG~lBK&@W zo8>IKm>XhbnQ3Ig!33>M{A&Gvh4!O^6ycRA3Gq)NybR@?4C@IP;Xa@z5vkKQQWmt& zFeh8x71*Z+aQ(%6j4gup=E2$fAP-a6qY$0fnHX?ybq~ueO3T>uHET@2)4MVAy zIL@O;Sr$yL|hA`ClekLS4+7mNR z5jkYb%o6}o#&i*O&A9v!13i4p2lwQ6aFKn-xFp3NN&TAz*=wyzNhtZFX_S0&8YQv3 zYrr!odo%$%<#cufc(-az1mN8OJ_Ni@iv{T(^9>-1!z<>IMXYHVeBeQXLJSiC-!}zb zXd!`Dx}6GNoGL)tm^|AzQ1J$8DgX>D^BSDDGJ7PVkFauQA)LMz3=A;@1B=CM!A;8y zSZ)5SWQKxifNZ7MP%9Q2SonI1Ov1h%rlJccMawQKuuhUle+o2XGZ*zkP|qVj!_DV^ zx-pt_QQ($ri5dft4O>Rk(Tz7*82e z#C65SQ01LOpCmgUCUQwJDj8(!0TG*p5DBMw}Q1EQXyqjplL5 zlLoeeO4T^>hFQ$p+kFG?qV)K?Q-5ID`ko&KH2T9mxjyv=xE#KM8L~{ZPm?@bK`DBt z65Z4vRolO-KQqHORat181X1veK(>N`iOz^vI#Mt?`%D@m$>T_z8uxePnt=HPaRi@? z2T^2R3g#NmM3MOo3ea1#E~08MT_>5X1&b4Dj|ZZ-$3-9} z!zZ6@+WV54?xLiLvVjlFM%jO(%yL@d#s^b&g({u)1mm-!(8?Oc>UhbrMR7S)W zr_HY3xhK~bYX~|QT6}t%1+j8VHoS@Ukw)3@A-Pu)cX?dOQz+u{i`Wy$h@J8=@J5E4 ziq=p^Qc?6p@2)NDqgYWA-VYazF;do#Xq}g`ho;I<6Nu@ueqgE$MZvkbW&QqwaI!Ql zLY6nwb$u^ogr>6b`Y6?&n6kc`QWSqSJ|{|L2*o82kMK}xSr4&hNgGJeU;E$fd)s;E5~;)d!lrpIoDF-`=@p+g;Hil3HM>mQ8M0q*8JRNlOY z-q-NVnu$?k8*t`S0h~iMjW@W6&ftM_cNAa3?t;=4``3?`n2!|Fcud&s2pt_X)5$)jRqSCquvL!#Hwu;swK8-Xde(2o4kB~U|hfZ$WaNCODR#-?bL1|hPbt0r^kBj8VjMBanc6;v8%+o z>ZBFafwq1y&u}m_r!*VSLvfJ;G)|3RSLi>t*=`5%7-Bn!Pax2_h$1Ku@6$C3gcq2# zWq>a>vO6AQ&6UWn!8Y59q5)5-t<6S*R6TcHdyz=o6I$daK+zaaNTJQ61{vIV z^+c3mVo`RO3f&v1aQNJj4lfdOva921J`kPS9|wi?w%sl6VJrc5+z|`i0Rif8KII8 zaLLy4x+EdEmoy1GBg#Gt|Ha9-^=G(DCc~$=WWh*%Kf&dOSNQUAF0~bwQUuqyp9D+& z0m@UD<|vos!}2t>^aurUse#gHgm^%|)OeU*@p*kW zYb0DEKKYB(9qaBf?r>0f1YXt<7FXIOQq}TGFCazvi%d-=nm5uk-C54Icb1wwM}ns3 z0x0^VX-z}F8xPQ9&xYS4M7;rSUTYP@&1<*`4}hCpI$OXyVYSjDf7xO#8O)BqeaXu* z?xX&^qUR)34`0Z8$1KDr17-V&OAz_ZVCpAaG8wE|M4ay^ACr^?luz?3hxIAle=$Mq zDL{GGPad_$3jAmz8~$qUz3L9|J>BllcY9Cy*u%21A$fneKO%MhY5-*s@+QfIogV_o<3QV0vGHmFYvWo`IFA2>3%2;*>;O)yfnn z0hRG=L&nEjnclHu8L8Rxrmal&-3Yp}GUHM=Je1+6O*ytQ`JJ*d6>45-W%{@)!kV@+ zwR0g^nQOd|$X@c6qGS*tDpy#U=Re49F%wv8Wp1h(E7O~+R$7_QpRzJvFtIXOHijf4 zo`RLh0r0}{XjV;MH9ikj%|&ZvD&auu9w;O16j_-wvNH3Cw8vUhmbQBqw*GL(b*LV8 zpw74%%i!y>)BG~je(tRd59qWMiP*Spj|x}d20lMWRlQ0Z&tvC5C_U`ip9xb3oK0FzO@RrP+ z%yuvjd}KA2cH|jgK`}Vy$eA8Cs`%Z+RtM$r@&p^VE5nKtp%lerVJvdQZM}F&S6AX^ z-w;WfY&u9e{NinlZU4wBarUI`EWN<2#?Mo{gA7}JA*)b$+|omAM|ZxTkCrgw8W zDC6@Id2>At2#*Bo#&c?=04tP-(^1gQXg=DU84rB2Hwu*#(igh*3~$b}pM?YP_)q|s zWi=;UWc<)jCmIrc>EILE%ts_V4NPOMp@uGJp?n>u{Q>A+d`$%7#0p^$b7oBx?5M>w z9ah;B&2p8;0mSath?>)BEwZ1*G#j{bOmd`PbI_b!#CaVm1*{?Ra}~H7u$(kse;!k| z&W4xBHBrfiwnS$tX?AgT%b-0sc+j4%%=+Q%IE!TQ}nr zfODt>aWlzYr30#I**OF$stno`N*?tRyty$=lKvS~1`P%Z6qRJ5NLYX(ltwP}w>*m5 z!;rf5oFHQXBB&_-EhC41+c#;2)YSNpj9}{{6@eI2)%cXvVBNY37sz)GHUQ3PfSnk5 z^S2>q@C*^^7>x-lPzCoHfM%jrJ#XA+s7~&)V8b7~nmpaU5F2~Ae5u;2kKr0qec6X% z>S`ZsVz-$y)ATI4&6!toiLAH2Ayksw=1ifSDW~;P+Qnndrrc(RmfU7Cbp1XHI7{j; z4r4e)1>w2nELN%oXf+~NnmHdoKLg>Va-}6FrbcB~I`z}K(jHg1(hP7tay*f8yFt5$ zybILEup0`#qJfiXJ!aQZ@Wg}e2}-61lZSUsAgmib0F9ZBnH57xKK0i-A)~ZHeL>&_ zGXcnB002**{KO-TA0w$zgw&YfM^uV>SHvJs%*lIDxn z)UbZ!S75q&wN*g`2NPufeI>z!O**ncCLRXz7-Q&^!GtH}mB;IfE2LP#XIhKCR$L(+ z(fS0J&!5BOHh*2I#co* z#R=NDnSyBYuNop3#dE(hst^Y-v_9ZjMHTQ`n{YrARiNnWKdSJlGm0wwbmvE7{Qyj}gQ1XO{P}lq{eJ#9obnz%+WHmF8AF}U3l`;|~I*bo*`d=L%N_h}Iyez8AVEwZ}#;=uZh4z;*kKrSf z7$%PEvouRFsjWm+9EPZXd>|Q?97K?m|+kQt1?eV77dNM%lC8p=}=@8%YPf{|aBekgKbFHQ?$>9xk)Q z!Vl+Xyj?B?_CT&|P&iB!09#!Mkoj>IQkzDL8^vn)8b%E>>JP*@{BSY%Z3vP|wM__a zPp1N~m&QAuj8itCsK_7hRPB;3Ekv{(A3?#unDpFdCi~%tgMr(_{I?shAIbGC(?V<~ zhB-3ScSH55;f@eT7_9!*n|X=Om3;elcTW zYZ9|!7o$9RM#&h3E6-KitD#8Mo$hU+Y5%Bui_NstFR*ON0M_1CKfUU3+R-+{U#L5C zBc|32ZB=mQtAJCPgy2Klfxt^ZRKzbj1G4`(&n5K~y@&3=mM|S3o8uKZHVAON&1l|Qn$Nvw zO)+fuX)cJz6+53FFTr0h!zEQX24ziI^*@mJ-ov(EG7H(MivW+EGxj4M$!SMiEgN`Bd0}%h7BX9qK zyY9aCfulzbzhk`aN{*q>=c0LmF)|K2rdV3 z(V*I(AC>|1)2Y_$r$iuHgs@GLk%UD#HN;frTzrivwWU5ijP{<{D16x5dp^-J62bE? zPiVYSk$BK_*RAH1LaK$i!4-)OP2Z?r>$l5EF_2y0ZTFd(Qk)v^bs6rnUbDU(m7zx`9H8>wQxn7?dAxA)g zZQ#;2@KH6O%+}L3ASsqz&LP6cFIU(6+mGjQeU(n(G?xAGJP#&WDa|NuWjsVqZi*o& zJ4N5nqwY;wV`LRCjbgGRN;Ub&58EvX2QVSdj5dVUq6;Zf6CTDu84zj+uz9%>;HHnU zdCiCK$+jz%#?c!@JK9b$1;$-XC(RU_PprhE{ovG#_5+cI7?SUWO)c6lQ5K8#lCZI8 zkCme(>0z^I@1O=slqLCpChFJa((s?@)NSk{;Y>!2xuzxb6F#xR0+Ln|EiIk*>w|v1 ze(9{yb`f6V@Y21IwpU*xI1%|R*(9)Y9=W$mG!RR4(?ZNgzp%9IRVM}3;s{J{eA2dV zX}BqLb$$A*m@J{p2Eu7k)=(~Vyhc^^8N)RCMM=VD9X^r%q6^kbPv~O~RM2A?=@mj91QT+V@wbA7M zlwn-hfLTYkQaJ~(g?v@<-Kw}kUQA9mH;OcP%k=a$H@BcUx4wpH3xy_1J5HX&kQtLQ z%%#yB9;ps|bCJdBBV$;rIBBYm;Q(1^HYBwnfk_qGDJfZqcA5>OW7jBHqNRv19n&7; z-txA@om1FV@j@SBSTNAvO@tzD3k%1#T-ck#8=^1#@ z=GI~tU2-ys8_(!)g0AM$ z$d*t)Kx-zQLm6CQk&qjlVK!-#AL>tWI^$g`tNt`e&B)NrdM}8`VCTtamF_G~?;IFb z@mSHd=J8+YvD`f$_oLbZJ||;16p@apv?PMU0ojyfQS7WG3JazrbKJZZ|GodqRd`nt zF$hQ!*Jp2>syz^Kx_3l0l3xIdoHr=~9gi{=T-j8Xh}CIouisH_*E_G&JIlpx8#G;~ zt}szspn6Z6kU4r0Sg&J7%q%4$kck{yH=^a2RG!K-nP#nzpqI)Qb)8Fq#n z-_1@#BC%?oKA|bjMXqKa9P12D5@l3|Kb%fYh%;n4R>92CO{B%CeGW;+0u-JNs8l^; zi^}tB`alDCIF_uqe|>JPRj51?U(C08y>y^{h}k6%#su^)rtdZN^iZs_=O@{6{Cc~rFo&~XtSFv(NlaM@Qp+*)?UMX77U?N&CDd}twr`w2-6Y#zFUqqSYX>~gvOaXX$w*L~)Y*twe6FD(|Kjf^IQlE+xrv?LR z_nBh4ZW_o+0Vsak-Xp&x9Rs;MhYTd-+Iz@A)_Z%8_$d>aFlZ}6ttPb3x9hnjxd03h z-P6jmTC@+&CcfsYmztfGQcX5sAJBr$WKB{;Ao(Ey{A@RSdOy5fP>P(XCoq~zLh4{Q3()FbQG>9pB1oVz9qTBTkdE>p zAb2%MUnlH$7>^_(@6n~0#a+rAqOCz)HAj*6_F_pjTOWNEIYtB|MFNtnF-Sk*ex$3= zecGiLDx44PaK&wlZfEH(2!~FnosTQfFvnGmCoiYXNnc)$3Mk(m%u7q{nG7^wbyiGm zo3U0altrCr%Ob0gUX20|S4cBp2v5tSA&!S=HMafM5Ico zPVA)#K{l>Twrtxj*%c^2ZseiJup=ESZ9eKkw%}A$Yl9??dKP(DFcE@A@JhWZ7~QH@ zzp75_|MH%rQlecnXj2Dfk%}Y|=#!jLX=Wd*v?3ItmzVTS7Y3aKW%IWwpAqJ z`kE4o=>1tEly}{CG^?NgY!FJqkBr1Z-@M^3ifKYC+MRZVsN)XaL{S zDTsW*Q_&q%nX&?yHK~foAcuYM6z=(kVpFph{uq1VQQ+)__lmOj!du39E4*upW``Wf z=x$f;f2ZgHZ}>73j>YUGxsBH+O%SXv;vms&d`?@6G`CQBtWgoXfkt3Srl^nd=3@Ki zQ^@^7J3hxtVq||D)D91&+T*lArOqP_*?Vs6Pm(kC5RZrH@m0tp-r7wu8RO%P;{7$; zV76~kex}Wv$P%$(M+qOtGVb-N-AeiBrXN&1^RCz|#C)~%m?P#%O_>G63igc2%wYxf zD9Tz&Ku^J?x6xp6v>Pw9@)f~Eu5xyn3hHBR~<=ZnsC28e0>$y^tGZg%l%3hqp((N>8_2YIyI$ ziCY95x@O;mye$bUvikXf#&yu4K!KjGfAq*vWXy1vmLz)$?ZvJEz`x8vg`dbZUIY%_ z5=B>x`K-5=Y~m$-3YXH?V`K7jB^1NYYcSvRYS@O@YVb$EJ|t_r{2INSzDw%hpS5SY z!c#FBdCvac=<(B*HOKVXHlYBS27MA3R3(NoGSQLx>2Cuu)9Eh~SBiVyJj0o1r3O%!9tyrQC~_fR8BxgaZ+7iIzjdEm2v7 zeBly>d|5&_k#b{VXF*;I8%jYy7RkZjQpt*JB;(P?bC{iJCALRJnnx`PtlS*S#v=}; z2bkM}WEh>IvLoP4v{DdP_YIoz8n_@xp@jHEP6!=sc$0xj-4LtKvE_4Y`7E*}5vL{t_d8O(|>kZmA;6Cvpc+zI=!6sfS?v}}irOsr7w*jBSVlJ$ihr9E0? z1)#0Cv_&D)%GQ>4XsOVve+UrbFA^Lie@XZ%>=I_UzjT_)kg&F3ki_llmRF~Kor(EF zv$M${@!M&<(OEPiaf#?s219&qVm2%cjsw*x3D0|wDPcHXY5)5Dn5kWa`&2m0z|x+2 ztX0k*XbygmP>fFYPtv`&T%nR0hiI;^H$J;}?0m+3lpb3M1c9 zKCLI11oi#zJjxMK+~9-o_i0UtA`r_ty%cEYluu(5g*aTm7BouIQnE|fL{n{SpFiN| zaek&JK%ac^vphoQf~tC!rUIn&5>t#H>1vUoG>>~8YXl#h1dPk2F`A1k8R1x&i9cm zo(6tS(au9nJwztC;ufYg4=j#s!FBS;?&lbY29%Guj=<(2pKJAB2L!a7$zGm;KodNW# zHB2iw-QN$6El_E?{8V=AW5#^AAnwE*_L}ewfhk7623C=oAg3ITE?+Kd}i%l zq75EcGKS`e(&`0i2 zHBD$z2J=ZD3K)9qlE9-RuxAjK#l{L-+K*Df7; zX-XKdSnbN@%$$(=J~QVF?>ibBxtcR`kf{igk zOI)|MzS3zRvA?FPO*QLz^ys^#6Ng+;RokeD)9&I24YiXWEstuWm9BPj#hNTv+F`TI zFEy{Ix{}E1MIu3F%kG2qJ?~=pY>o4hNk06!0-->RRSHq+T|01~P|;wskV=7xP$RT> zZb$gg^ax++{&yd2#-x#`Z$`qv9@D(=W=yU9HPc@-pEGGtXO^}U5!}JlSfqz&HmpYM|^&pAqID+)6GmA&sSv>LV&f+OG-1jUBBBO=K2W@W+ z-5fj)g`w1=C>=arsR*ip9MZw#qSMBW(b?k|0^CD3dS$*ni#(k`PW^NOIn~k$OAbT;mjHx9KuMBO-8!2G5D1x7T^PIl+Cc)>Z!FBv#Xk5z=hQ>aAXlVD2@+Rs*btAqkH$tg9 z(e7S-gOxFN1mP-|*D=<>WxcThYm!0*oX88gZmfM$9X7@?9&+J3S{~zd5}cHhFe)Ow z=~zCV6Kixt06cN{SL5pl{S>rv3r`IID4+48L}(2f-IH1Pe%d;4Ays6ucIr>Xl6;D2YcL%}9Flm5@y0_QJenTx(O@kf zLV&Ee0hmmCpn%2=Off#^GpBr8XMN4{L$l+#B;d4Dfi+6EJl2QF3f$98Lo1$+;n?Mh;=Ma~&(>jNS|Cr0iC|zLs z5Ozn#KYzhc+oZtF4or*1j+R7u{)B-a0HEzhgkgsB2!>B1|SKlelpkQeqgs;e4f)0Eg*FIy$N+xl=w1ja+J`Y z%=DkL^hGwF2fOVjcKK zHZ@G%S7ql3I}oWHYRZVWhKLFeB&`H_WhX>y4CO)zmX?&L*-_$*$xWD7f#|YoNvGJt zQ!xT|2=PQ&AABteP(G5AwYs1LdJm0+y^rdMOYSaIgCPlGY|N$=1Ac=^we0VL$>`B8 zY3BYBJ&bcY(LuH$7PgqD&zNd}mfP6tCgR&r41bHMOta^yu@;$t0u%CmYgx$vZo|nQ z@kDnB8B^Z`bC6j`4H!yR5tl;l_gP03gz)^`pYl_=$t1n|A-4@aijX+zi_h9tDKVw) zy^1#hfQU;H?Ub`8%z^E1o+d?!ZuIxBUsAldNjkZ=oFtuK{iUQ6*_0{kM8P~TzH|Hn z{Y;&QtJ`~(U<~sk?8Yci9TGm?n#K@m9~GrXDIK+?5Ag1JloAjfkK0nj;P`yCMCs7l|(m>DciWejlLx8&tk6%2CbZ7gce?W8%Z(=cmUxkIWOU#J$cu$y4UZaL?OW1ml?6*fA2f z?Y5se_@1`bBhmTR9*IrP7|_QQ#4tm|56U8r&m2M;?DD@*j0OMesacu546Kjnb)7ZK zAUQZDXN=im)KTJoP&X3s&Uj>di1t z<*x}I@Rg`Y)4>|xc7AkDf|ou?-BY>71_yEU0biRvX9B;47JgivJaZ@D*Dj-ROZ0L< z5ISdgZbj9VnLEvMGIw~Ej`>}v7TU}moz5k4V8<+C2UBXa4Vdw6I_7t^%$;qL#9pp< z>f$7G=Nzr=YcO{GrZaaK9LOd8Vv@O|Xcu<>Il=*v&5f8TDRYMnqzaK*38R0d@Op!Eg{=uHT9WJL(=?g3`} z_Yxk6aWXKg2z#|Ykb5`|u_<=PKLG)ny%{>;Y7P9sIYqn+lB zX?1Iz)=g&F1buT|T)xn!O-|109IAiu$Wgh5mr{Tz6{XB@`Dk1YxZHgZ!o}LnF3R_e zX4P3en%4w&F@baUF0Aq4ZS!kj%+%zMe1|fL;^kZ@c|Q6zDl5nCxo$9L)w#hc5;W=fOM! zC0T9qPhTERDO_C5)PD)7Y!J{l)QgubI4yJ*Lp6>qCJ)&+It5eR&Wk*pJuqJ%(d;_I z-?Pa%RMrsF!Xw;371lR&)-zX(0;9<$owO;H+tmcM?BkBbB-1V56>ytw8Ok`bwpf%H zZBT==N%N03a9wSXw(dZ(Vv23qPT4|18-nF&C@PT>gk_9x%U(X3)t*OaRg2pYXu|Za zW(VyyZJbJn`bwmnfMMuV=tX|55r|g9Zlb7N+7ql!01?VXJNXviNYnL6$DT&a=IF!` zA`zDFn6!qQD1VnWj=JgfDa(W{$C3P{Fo%Y?OGES;LBnh^cH>(xcu7ctPPLJ_#%YMK z*;23>XW%*HuXmRZAlM*eyc}Gw55*;>rcb~2<_#9z^%!&0$L_hmjZww|L@bO5p~xTY z-PS-InbV*+Ht(9T(-VXmc~qQf^Dg&Q%-b z?goLaWtV)2Kd%H?2iOEt?-1IyPyySrtr~~UOZNa?G&h2JMw@DaE*nq=aBj6}Z|oC} z_3Vv%VUKD<6h&X%YWC8679z(2(Gf_d^@j2#%DltZ%e0AVVHr-zbJFdx3`rA0SDO|p znPXm=@RqJ*8<+KY3N*hsww|)$z>Rm=DyYNFMcLL%b52}M6XS)nWm!f-0Dxt{aPClf zY3%}_l=UvFNVOvE?=yN}9|-_9*EA`RR`#}^ydLcx*q(~x{GOhY@Z(cVUJI}r?lh-l zaoIMa(TnnpmjWdc8#SZzni**=T9JJRi_$i|#--B6g|dEr3xW9bLri24vXgj=+gck6pcazrX?csX(i`rej#!QCgC zn-89HfNivH+6vQa-#K2xVN7+&5hM&`jZCm2&x$#2vPF^m&XY@K0GEv!;AqK`C8{WUNrK0Ueo1i=%atXW0X}|aNtO>GC%p#;Sc)x$ z#9CsgVFoOTO*Ad~izYDx_?(u_fF>HAk2W6_BfXrpy@_H2oP=|ZtSrp zaJYHLz&MyJ7mvF0)NR9~oa&j(fTf99>%By=umPXj3}{qYycK4Eb{tK=g(9{Y(32*{ zom8Yy=G{>9nw!GwP<$}gpfckHz&QtY65dk?ar@*Rr8Y+^?Q>aI(rLpKcI?mwQ&QM5 ziO7gLN=&!$N5vSYsnS&x(upcMsMG_u)+GeLU4!p4Rx-8wN@z&k_v;t20!h)O4H{Vk zR#iXW$ofJ5qU`0D=F61sOqAJ&;>IkQWg~@Y!!J<8KgDT=TtfGHPV`@AN$XN80NXih zhaoE8T|*i#prICWmSXxB<;%kAV$hgQB8m@vEs9J{oQs01NpewS>Ti84icGx*yV#QH zqVT~gwhp$YJxElsViC}yP?xg21@C_S5{<_b4|WzwS_wZJF-ig#!QzJr&isIcRWvaxfhUmr6SUi2V> z!RrTr?Yv6M08I%0D53p+rJ%kh&XNN6d}*h>e}en6nk*4~hWkzv@>j5b2dk^p<8vre z>H2&IWhz~t&#JcR`p>Uk)V8h9pM0Zc^E~A$lPI25o2wV6$D_)b35C85TfI>cODc!L;}7U}nH6CD^2THQ)<-zdn`Nyu(=SrD-2khvE~bw` ztD~p5)9IXsiJI05@-elc_38I3OJXZGEKQ8{`Y|T>K?*^RQu{|$mFkY$rH;*MsY8>! zAU*wtYD>Ce&v#l1ZF^+#J|@H0SV5gW9C4vV3pac!rL0~clMn;65Y_Xk*p-Rx_xbb~ zx7w!iJ9iv}@;2)~&GdwFO0ZAb5{QBlR(+Zhf#3)rFscFXQ=nYgn#Y9t77E&<@smfL zIqK9|mM;9s&y-L8RBMm}$gR}X@?sJC((jYa57D1~K6fd9m(K-yK3`<4v5F*2Jk4DL zUeL&017i27Ie5&>;ciMaha*C(=AgNd%P}>FW7OAy&;&r!97qdE92o7bO!w#g;r{9y zK~=2azfi#mpHJVQ5agvJBaFq&Sk#=ME0Gdvfw*Z(l}=}S2pg-VO0&9Ve6Z}+Fabp( z8coNk(tJ>qn-WbV$;Hy(q$zWhZ0LrAwR)Ge9~2FQc>Rq!g%%1MVh~|a!XjZ?a>OMp z4>l!ol4$t$Np7?l$GOo@z7G<4fK{;fD`Z;4U`cF%+(`#qEBhb6 zp6q{>6Mez%0xgs5f4Tj7l(PRzDn6|2|8A50Pt3q8&}Z1Lvj6KGWpsSA!YgQ_D_T}C zCqu%zQJwka9IzN{(GC_w#9$-Qf4W7xMm#5&lM?;oa0av#ErT)f$4MXRk#(M8Nvu16 zs(HZg#|~7 zCxG6_V^v9jo~7c-Z5FdlKWEmiw^>X#8H*ON*Hv1(J*utPt=uJVSs=-Y+$3%D;2-+l zihG_*rnVubw#d{J7Z?j)%0C<6bCO@#5eyQds+mC5Op7X<{Xo@)(%dbk#zepH#R~ALmr{{&Ra`Ia23?pHX~+S^$IJsc5U z=ii>H>*EE-SO57hWS4s6Gqt{5FbH61)qE{*VELCFE4l>b!;j{G6r;stA$tojl5V$K zb{4r}$#6b7P1D6nP8{UpHi=?T!V#e8LZy$U_~xzu*g+P8LW&C&|e1nRol&;qQ23;vc6?S zm2*72&@W+8g1;CUq(!$C>h$L|ozm#{E73PofwIqq0L`Ro#Z;hIL(*FqJm;~ z{Ijp*WXy>6DO15xLZ>$+!K=2c8C!WkGbci**>;6iZoniuCE2t7hz_;s94~m!uwBc8 z=_VN&i3xe*IBOzR7ia|^LpMeJcy#zXx!A#HGwQ65)wUl+IJLKKV3VhpwqsCDmdLcq9(;S8ohXiI1jxzjdb+lIWl0_ACmOq zyc{=8AJiwkc+!We=g-rIUZkOxh)k>A2suUG`oxZg%JNBY;zZp+&(q|cF#c<%k3`|7 zNRg0m=oyO%PTl+oR35}vAOcNpTAJ~~iaM10E*m*!4s}KLt+)2hx0p46rbvUS1Rc_l zHRSLQe6Pem0Vq<4`1rCt;uf&A4jki!Gn~k(sH1CeBYQK*LG6jJMhnlTnE6n!UE1B* z*mq}0awj05rI3)6$NzOEvxH`+Eb>g|r-VgO|G5S+H*7>tvKLP!dOoij$T9 zou3UT+)S>!g1XmSmzx5hrVj zeWsMkT4HAm6I~P5l4`rzQ`EP$w6eZZ_UZCq-IcXOUwCqy8|;xqx7FN%I_S&Rl3$6w z4JJ|!+JPr)Nw3ISI=+=;Gy8E^OXkioq1e@)$s zl{Ysg(OEa~Z<;3fBzhQKifWyf?)|L-3)|l^2+oG?Rd-do4^}c@pP8I%Mx`ds7O>NG z>k|R%yo}RH19I_B3%Rfk;hENG4O?)!BrLz_5@?#1r%EJuzW%Jbx$0c+AqovE&TMU2=a^JD{4Iv73zX`rZR z-kP8>&7Jf#G0i7H5A!?x^788*=nl&Wq6*8%j2tmtenH}a`d0+>2+R1DXA9_!oeA(+ zxgh}KOz6BQ@yz8$+^2qf!{2h?Uj;v^g02e8xdiQ1&{u&Ox2WIIyzz{YC6&j1Ac+8y zf_?Zh;zm1!;vkTI7$|86P?N!{w7%wvsZo9lUIsZKTGN9uB`bla+&2W#Y1?Chr+tSx zpY9zc7g>BM)Pjm5)qf zHe6#8t56i=fNC;{Sth1*MYJ=%<^?|cDcUIs6OjX5*>NP|Fo{_eLcYBJ`u8~vt>#c$ zY$w%$FecCbkZ0JtCXA**CQR>&d7Fc+`iUHneBbOUaXf`qS`L;-NzR8G7YDP&$sBBV zBpJTq;E*<_Y!C4{WjJVKs}72X@gzbw#(urk_>S=<4)Ix`6V(DIi^tS=TveLNl$i1R z6EUOdn3LF69?Zu@YI;V@H#wJo!hDLgHq0ljMf6b%QsytU((o6e@hdSEI#Z^CnmtQX zA*vkYBZN?-;MrM)zy3n})oJ+)Pi(?pVWqg^vo{sik|k%xir7VKDTr8eoscDNgO`Rx zlKJroS^RPg8uRp`wpmT$@TAC|V5AHxYF2+{CHq+TnW!d85+RbpyW5_Tt2 zO^-vuq{oeDQ3J{<=4bk_Fzr3ulFj`}o;8M0{31BYgNwbqEX%9_A$k4CJ&-byAa>Pj z1@2FpTNFAACfZy%S~YBfU#=f3VSSDaY*NFp^wN^`Q1p zD1ov9je}w2X_+_x;p| z$;trKN?B}#vjaaV^M1JL_aFynn>mD^c1Z|#5>09)KE*1+6wYjG##Jgomn#U+u67>nWtO=Cp4$TY|RLM3R35X8ELzfWXE(FM{@-~8wT;7 zMlZtIE{^?Dx8;C#0oNQ*G#r3>oZr2hd)_0bKIo z>XjT&1_!98lW~AnlN$~&6T9J{A^{d~Xg4fZc~HJbU>+<)`Qji^xjw*m{D+b3B+p~! zpz}oe+Qna&GBH#8WLq3u?B-=wBHd~hmZ9k1YgpiX_!(D(1!6*#jN_9$;P*0>bIc;5 zk?cgu|A=yazmxKoZt@Do&}PdO-C@3dHM`S+?~Ey>r;!j1m6^_iTRgf+nYqRCDwV-4 z>8MInR^l7*ocIQM7B!0U?8SlZ*sU!ZSdIEI@FvvXiFE#Fi~4o`1=RnX{I@20HspU+ zSpFY-`_V?cuP*;Trx!v&oT2%yto139Gx?R7k(vQbt%ekDkoH37E`=PO%9lDv9 zDINBM4r_C}y}FNOo8_$Em(woMj7;HDM#0DMX83&_>+B|;oFDltX=^2stY`sqbwvwZ z*$lGYSGK}Rkkondu8t#6FD_2XOrX=I@ktbW$_zoVOT~wQEvuEWJ;vx+qrIfgZT@>e ztz}$wWml~DxQvZyZFg!aCXQ*X!Djj`8+<*O5C7jpc&=oFXU%v%jt&0XH=Y$Ug!nmA z1WWlZWGO2apiRT8*}i%kLO+AI|G}BAf0byDsAkX}veAxPBgYycnIw+9tFsDSWUEQw zR{YAVveLt?dIn(&=fSQBTQtKyJE6N;xFQK%~7F~yJY0D1Np zC3QZ$B&3*<4w0itRG+*a@~Ww4M@aPuYB-+LdQlS_pw;}bvoSxZyEKtSdMR{t6=mvD!BGoRB-~2tf$BZiua;5^@T7om$A9$=m9+;}evT6RWW2ci9_&FPnsXcA-4 zR_kzPwm29Hc@d>c%K$n*gMqUAM@v<{m+w&?;#@$-3GF6ux}7M~ht-ZKVGl|Q07m2* ztw>oD3RJM#+a9HB+sHL>Qcf3fNV|z!FdaX(0)_ZO1>#E(aAzq+PDJrs6q|7OS02S? zou!{~aAR5KZ5;cdMscM$c3E>UQ~e>8k|b@DU)dT? zB1aafp?+xm)AvH-GT9c0S;Qhv5dUZOV2b$pqz$kCRrC6pEZ}_g`uYb)_aDgFX&$P% z{mVX3v|T<{Zb5d%LVli>?$`=pmrOZfCChj@A77T5k`~e-%cBlx&V4*$q?Zk^8JTr#auSQ-_o{AFt174ikxXnSY&EosWjs7jp)$K7W)`LAh%3LE4P2i}`|BS?SOUzG0;h84HhG@k=eOXtGV` zwVt?ZI$3k@OhRE>Cxz^#hCce_DW2T|y0;+3$ZfS0Kgwg*mdC@tX>-$0k8ZeGx z{N77DJ$~3@<@g2v&a0WphxTl|bKK*=vn%W3R#FLi59+I^KBPxE2VPl})k2bigCzFce7Zt zhRhmf$WmpdQ;Ja1jj<}RY!on{bkbmY84zHwXRyHuZekiwQvpMp5Qw4E#32r?fB_K< zh{OpRF!B8Ud!KvXeO0BBWSoy)*h=c%k8{t*{yzKcvriU>1(V@ums6EtVTU9POLmhW zXT#!IJuF?5ho(6!b5v%{VS$u9BIN=G)kKQ&2iX8DPKg0*(|aN1#q9l5Vo+lEy#bek zQnyH!yR44(E`q9g0fuN)ZdGpf^uG6!Z01PkHhVRsGr&l;9AyXpVph8nthDcmNA%cYY(A%Oruh(HW$^|zh_mHKdhp?5Upp7Y6YILUw zo^K8Q*WpEM4;qkThp$Waq29L&RrTD(U(Uh~!>Qt;-$1} zxX9TAv04roF}YZ~#k4#M7BDy!NIbP-Z!|g%=(K<;k{2(h-&xFCjh20Hp;%Z>?;LB* zHw7r-t0ZrGpJ9PjMX)gmu%kIF4WkxYBHX${1n15uo3%Q$UvdD6q|F1&|`|X4qORjJ0bZ7 z0uPCu0^`l*tSE)9N(ct;Ftb=KXDzu>pCI4f;N_e9{i1ey%ic;GAzQcVsniC`huVI$ z>u}l8yawp3Fi;2pFctR5gOH2a6*0Trkh-01ZtT#dI22+5u0oJ#pCT_r`w(o7e{m>fV-z4Iq(=$qc5|bZ<#aet>qZ5yV0{!hmL!Mq_#C7Fk57q+XNoy^JS*n5 zVCn_Rd2moqCPxm7qpEtw;Pb`89pw%&_>QwO_>K{SEBd@&khj|jniN1Yt3n^i(H50) zSv<~mR9!l+vmrRceBlf6_Kw?2Dl?GHi`zHwwo<<9%qW-}OwTYLV{S;K4*!NCEazYD zfzuV611YKDg#*HWJl{o93H~I={pB(;_|mHpVNtz_coBthGaW|C201@-B6da7W=~8> z_9_n5tOvG6y-C&b`&Rg$pF_x1e%6rWm>5u3IcGBb)^fg>dtC{sKL{Zh$?AE$3dfRq>l3l;YTZp)PjX3SEytQ z!Ga;Yx|M7Z51MSzR7bi|4T_c3d!vK;9{LcG)c5i#)v`!I5F42uw12&ARFl5mn=Jth z+dlkil6(o;NC3(q42U2~FZ2i}Jurxf{jkY3v%a+0sq_9pW1kP-RVZD|42f~;^%YL5> z&p-eC*R2+lucW_|kV453fzo7%7-mG0)wFd*j?5LD4C7c_Wp#j@%wF$VIXd^;=mw!( zR;R$_n%caI&JsEO_52_=mJlRZc@aahA3#o8j3GyOGl4Kn8?e9qT&S57YUXaQQ4{ZF zr@D+YW23?d+H<8;usmEe!idH^ptxyTM~A%xwY))#qWaKv5fuo^F;oco0DOg@Q_{M_ z1d7Pc$*gJ0s+wt`VY104M_tI=MTR7D$^x3Yh3%Gy zB$54q+h&v+FN`ke!vR<5Wa@-DjM0Q=5k+5U=FK6JbPYXL)0dg36=lh)VF#YeA}PH3 zOhL#<#0)LHs@O4{>nA<9Q}n87Z|s94S*4GbG>J?A4I=2((oHBa!10KSEk@HPYzO_^2J_GH$*+q0}w&+0YpTk?M<7~y{Bc~0tyb>+_U zh)wMV$Z?Bx&HYgUQ=%ZXd^CKZyrv8nECNz27q0`8Uul@2cw8oci0~iQzv;YB-!vT% z-!R3-#&DzxVnsY-09;pTJ(UKBx@sFYsui}Ntk}E)B|{%2({fXv)+OctcDDwvoDv!{ zWnm}Il&hyx2goZ`KixqI1`k~XyK3#}H6EpU*?8pN+M{>0vNlJ^p_Wc2ag&KdC^rc4 z9Bx+thYYH+%?=$qDbDtvr$Khg2sdc-@IP?AQVL{cFsDcj^Fe8>0Ge)yaHVm8&Tv9@ zst~|)eYy`Z7LcD9AI0QyvOKk%4Aym^BCzc??%5%}oUjoREWmq0#;wfky01G3XV`uX z^$Y&=#?+4$RH`Bom_BzNyp@l7uOk)h6#sNP`Old;_P!Y_4SLc9TaoQl!5A!m{-jig zWoH+zgY8#fbDC8GH4*esV93Vz^QTW%T|8eW6%sJTA*av)1aVvB_3 zk~H+ujfMjwyMZT1zh9mR4j)&M*P(oJZJ&2_l!q_pN}?P04qB8Yu2O=U)E#fjXcziS zrEHVHhEIrYZ+0a>;>S{l#^c*eFB5G3C#|e=8}n5<%o!?JHndv#FZJjKI~Z3OzYX_a zkF1V}Ku*m#jRz_icx!^D;$|LFI#A0RcQ*tTQPcpgGpv%dl+0jqh*Gv&grUS%@5lUy*#N? zHaVLpI<%#JGRdH8MNUueD0B5MSO2I$9kOE0F17EVLUo(A$e?`(5tTZsN=Ug$WK3tT z%?p0o@{M2|T$}IZr*&<<$bkhR-#?u$(>sU}I!6RCS-d8@fvsoi8R+9xtH%)2gN0>@ zNHyjIhC?=n)Huw9!&n35ggz@L4A$j@K_LzF(?;+2GIwrjI%TrmoaFH9T6zmG%%t1N z8y40ELNUK zHN@;W*yPKFMdZ;is*ii9^=-4Y>8k#9Yb}`SZ5F@;p*uSclO=eo?$o`lx=&KB-jJbH zUE>$j!I$jpTiI{@ghbeAt4H{j!I1Sd)08;1eYUVXk4PDT9+R-u^fe^CSEMPU)VNb| zos244p`>(M0_jCyh*o)OT34hxq$-K}R4S?W2RWj^LJoA;rRPXp!V}^i(yo|pO4Q84 z!15=a{OHIq8kYb3_uu*HANb%? z?|V8vSKZekiOp}z&+=zJ@SC@P4^J}bfJ03Z zOdw%+lS!Vyx!JAIS0+Byr9bBqv4QlM(6>3CEkB>$K|yhOEUV8|ie_j|EN)`v)-vN8 zs)MC-<95CDmJt#Nu8uX#OLb5$Gy5{TRdUZhp$65g`e>2Pdn43F$JJANc}H)3I@?j( zSUS&CHUi)fWMTdVwyJ(xDP?Atwj#sSN~~gTXFsxt;6BMRGu6Qvbf@W?UBSdaUB}SP z$ih!`J&=W;k@!{BwuW7qu-GuxkWK%PVuONw`fnsnEBqQDe%*1b7MU;!$m6lT7)XtehSVG!}mgTLsWTDdu^Xo|cM0SMKZqoyp3OYNIS6mZZ(5kN4 z8rQ9^(QBHXs|>4gsH!QAZZht)6me;BgRz(;j=B{?^}_;gXc)@AO0lJocymA+ubw0v zQ!zmcTVrK7Ydwc&;}~WiTwb`Xyht7#3{>3KoI$2(?G9|))=)y*ZQC6NJ?v~`u+H>D zA8D(ga4-wl@ghbrrr~n{5BpFsOjn0}0Hu~WnyH+&NriX?t6B3NzeHN(j&XDWa>qP$ zu&zdjdFW%ErjBx2QiJj4$nR~wsw&%yl`2Bgfnn7ZHnVE1+qhEq_-gRz1K6CoC-*4V z>K+@&I)Ny{BA4Z+4hTI9_1KM+O;)tZd1yK+Y zHpiVs$%|$Nhf|}5gaPxTf%lX;D!eloxJHu{?WVv4kkVulVJp4-*!&hUB@hdQO!`pE z+;u6Ym%5p1?nS!e6;JYuo~hE-$ynhXJ}7!iH|a(95Z^vGfoCo(9y%BXhn4=>K7q#6 zH1>D$bY? z+!?;$-b!F`v1~4gclqINzz^IY|CCy_F=I#!4#0@E9R4DXdV}Oq->heqsCR*ro)~1qz3CHu8A4nPpX8M-F&3_hlAe5nw`ip>1TBS(+a$;b)lH z&O0sR9qn~?nTaQv@=QGGpn10$j>!^RK|y;;@RZ6Rp|uX%Tb~WSQHpV4$S3?_a1sw7 ze1am4JRZv``mOn)yTx0;%s!8rITI1{(cfLgCU+ zGYamRR#f*iKtc?O%GsX5!@#V8>f+s#Y@!6jIQ4VDBEwI}!hm!$(F{+#*2=ri+9v{| zO^AoGR~a1ngl=~^wGSjVM3cZoRWmJ7JLr{sR@Kw0%3uj9eZzviFkn{CRs8aZVhxQC z4e_a;DCM48nXlLve()5n4Kw<-1!-hdfI%luQ%U41svyM-rAn|&`Su|eR;Y*RuX<#m zs&Z%iTMNjlo;;Jsm2$e3Fy;R;V1=1(J|%Yst^l~Qm_GzG6^7S z)Cj|oAUshGfY1?D=Ua``sfh~uVm+~i^t!)jLf0zM2J}+UxeFUnz9C#L1JMf>7>we1 zMT7vQ=3+cDfOyTz`itC4>5)Us>XDnrWJydrCF9e@igj->whIfooMS~0F;a@Z>L3yf z8mfU4F*%A91-3=oUZ-Rp9qI#D9FA`^-@=xlw*gT&A~({w5>x0yLTij^tn+HziW8J( zwbqd8`)8H4C8=Z}6WptT)zVrr%4W1OQ7ZU}*D3(^K9D8|lZ^p#$n7YiW;lX6&3c&d zor&%w@p3a8f7_%+i8d1)$0e5vQ<3L9KSzp07XZwm#sG(k| zzl>XBsix4qkF?ySp|w5tX#i^#QsXksC@XPqDU%Z986c}qQ%ewp+&9vvaK@MztrqWS z63MIjUS0r*&hN!YS*;POwa-dpRQHuesqWDjW?#p9ZfwG?mFP?z#sR2^DLieM-kN73 zP^$xsn91O*P4V4C?C};0&IABj{vqCczN|Qv60Ksh)~`99InMe;3YdjR%E& z&kwFA;aVP`ayJ}TG--r0`2Do^-4iJB2EZrfD+4dQD<`mUv?hg#erwQ5(w{;u_QTE# z|6K`h;S64|hDGL&h}p#?NtmYTD& zqK&*}QR*hNb!+gX8xNF2<7D=jDFmg$+t#`_@r3C_>*iIN8?=L~Lw#55Ru(QhpqUO} zQ0T1G3lG&4{VLj=kgS8VLjI$ zH3|aw2b}cgQN7V0y#Zt*n49zlsTF~a)tq6Y?3XzL?tmk`=i;vr#0)w9e9SWIJm58{ z-+XoR=o9~zvU2@WU51xdJ=O;n(_*kUlT#C-`^W0JX*$oAHuMIx(LSwfF3JBrC6@J1 zU{yCctZCHEXGtMvnbyK>t2Ep*^qSdgjd{zkZA^Q}+%8Uy%N7$-x{V*0wE>mW3@609?2Y637p~g^HQ5IjKTY)!U|{Zy<}OJ9iSCJ4rZmkHj2N4B<+VRT7>dQ!b-1bumm$8;PS36vMpnp-|VS3y2nd zOe)UDm1399W5@pycKknMzGJj5Gs(nZ2EPe1=(k}G=gXC*g-#7YZ8LalI6P%tpfk3t zH_M8%mDcRwWjq7!@UH3D@shm{rO~5|$F%NiC5Eyk|7VjlO?*3=3bZ>m894Q$biOP-I~3JO(67%~enmj{UF@ zx=5S~nhlhNr)gGLOS$GeQIwYA_qY4viA`Ua<+4p1k8JwFY@BVrxM$NBX8LUN#d9{W z<~R&++kEllrZ0pdjodY41);lRxrEiuYqHD1q9g4}u4G9XIJvqqngJ1xs!Q6pccSH7 zwuBTmE;D!w0l6w*!Y)ob_LHFer$wlg!w?Ip&3Lg)7Rz?{@L#S&7|0^a7AbqBKSw^y zBwG>(xB*^I>3@X&E8bsgE*LGUiR&BUxH2uzUde-}sZ-{P3JxfEo#Z&zoIUU^NgpF% z*A(JXH96Ik9Zwe+Pk_e0+$tPDJIeh5Q~YFU~7ekev0cSh3OMg5vp8r?5LI%vn6oP3{yP46wZ9Fs}%XX zC&;pw9SJ8bOc-8x)*s@<(^a_l zUK8-)bbO3yr^mn&;jb&MhehPI{78CQY+v!0GbZA0BB>a*63U?6_>M8|RWg(t%@GGbnqk1xzD2@G#u7@B@Waf< zcFh0^9!N1;7e(__8@u?-Pc^&v_11wUqIiqCD{7ny=$pEtj#XV@l_-jH9#&m}B-E9? zJ_&1fWuK;6G_HPdYHU+#wC!-^ak24hvTJ~U6^)B<7yPjkC7yCY+lG?+Q1WnpgmxaG ztw0=~v+ByBnrkS8>S!qYc*@Kf38$HJH4^z&45$c^DARJA_Ko*Wd|FR6N263^kB^dC zZT8r^;BkELOLH8%peb>c{?yVd9sFF@`#_$h7MH}Eq4dN<7J`-z-ebRd@2hctwsJ{e(yHQX#Aap7-DAV3^kulPu4_{KZ7Cr++^-SK@+k!M z_lQba01C&s3*s(ju4|C?%HSt8S&rlzrNae}=p*#!JcdNEFZ7stza$-Ph{ibBh)F$p zCzuQ%D2_b`n{p`*Ga(Pfgbj1RktU3_3}dZ`34t2{JG-Aht;53%pQZsFOEA1Gh8CS2 z1=J1P19&nluxuEG$hmtXfPZ1S)9ceeafoQXar4fscPN3zYj3KRvIvN_M?l~jHRInF zMP&$#xkSy%-};rA^JnvW{z2+iITjbzwoIFtU@=>&vU|{0c2$y+6J$E&_4`v`WpRoB zNmu4|H}{Ttw9Upgr`eFVvSQJTSVyQJBv_A>0@z zP~YO8#(W(+Ry?3XcmD(-^RZ-Q=F7+#gzh=}e`HI5aPv^E%=EIP`cGJlfcteo<9S#d zwq&J-J1Snj@AOF&3GT6X$&)oirnfm=1af{_LnRYwH2C1f$ zR7UCWxttRoF;X0l+QFK7UY4V&G?TC<#?d7jb#Hd7aLZOI9B{s)H(2g6T1f0lCNWx9 z$m=`A>sAYEwIk@Ns&wLae=3r;`kC44O)u!x&sIo0H_&&Xjy{rjr$w+YSg3J`0m&6ShF-ij2i zG*Un%ixfC!ON78*Y|Fu`L8!(mdEzej)55BB+DBwF)!yEJoN#gaGeIy`*qN(8poqt6N6qou2 z>PA<+qsFEF)!7h0mti*R)ij5?R`7ppOZWrYQ8ZwcIGFtxs)C?86Rv3Xv1*h*xM)_B$7&G7xf9n2 zmIhxV)zPzxXq)hjBnkoQ3o3+_qlJLP6h%d{dTw}-vZ$^sRc@Q=Lqe~8mfpjex zLn_TsOAS&P)kof4w?<5D@Id1i%^C*p93`)Il?4IdA$sZ1mrSZQL#F`@H!IF@=u0a} zQ&0Q9{_|kWPj%Tc?f(@?5`L93{Mw;C=W*`Kwv~@%{@+;nOR4`Q=j6+><(x{+MR_PQ z$2|UTe~Ax}N6i%OTP4~w^ZFcokP{}0cxr`nu|YT~KJnA9_~1OaV7scvm|#QIFY&;6 z@xV*TjTMt7dC$h1Xoj#=F6sa#RdNwvf4GZ4{y=ncBZK?B6+LgC+&E@%-?sEWxb$^r zVsc|v$mN2x$)wpKFP$D>mdIIY&+@U`iCPasetVPTl|u}{)T}&A!Rp{F5i0uwG>6~5 z@Jp0eS->w*-uY7A-4{Z6XD$2>j`HT1{Go01NA9JEGmZ@PbwV77>E%hb6k{2*ILj6% ztrgl2@ro8|r8)3Gac8_|UKed#d^4;UYg%aj5$c!w#e#u-)nC!cNC?gUSpTRZ8hBC# z0Hom|CY=h^NLgIW`n-Db7xjQ{GFuHNxv>C%pBwczQGeATw4z5~*lCZ^mBUTwzaedVI+BI{fCdlwazLl+6hdBD4<33Yge z8|z{{zWUuP08)2h8~CLhlzeQfB>W<!MfF^KGv94M*|uW8&aYCgId$Y@J&4ne6_z~sz zLCjXEl>&Gw485o@a}e^a(@TC9HFEkMJ^SX%)BxnpXZ9d0bhxxxUR-$bRusvv7*H5N z-Hxu7u_YKeV&oF>_#f-Eu@hI$34~mU%r4=<3F8DsF_EzK{)!W}|Hio|ep7P~)0y5g zxhKSpA?`~~Ue#ZW&$Wzb)W|UmlJE*AHTw}q3I71F!(Jp}18n!1mIOjU9H(tO!%rH+ z;Hsky+K^{Fp&rDQ> zkMc|8KhG2!w^vt+Sg}^LLCMpUMEu82i~c2EjU$YZjGR0QzsXhtr?|<6XcyX4;p3>} zB~3b&RPqu5Dk`Zl{zI)SbynO8-y5(E6p3HoLhOfi5DIRQ@x(k=0s~4|)=H#7Sv0}L zV1l$iKvHZozb=+fxI%z%9P z_lz!}CQ4=Y@t|9pAT{`kU$u@W(lDqR&qmTP#Vmpe@_AQ0D%t}j#@FLK8noiO1zH}t zK(w5VL1W}%Y}&kcO+GakjG@IlkjN3F;#Crq^dNktPtZ7 zm!{=yV{VO^8XK^h@-gtWe&Ft?qej6L8I9T1XljArLJl*bIJ5tLIvvl+_8 z=E!;^UNo+zO7a(xp){YFNE5)Bc%(`cd|Jn5tO8b-GOu90x8jV+VoZ^l@Bxd)5ElmA z;|<_Kj<#?Qc)6;83cT6hl6s;*K~Cmg4L%$HM$#T(c9YHSmP*9z$WnO$cK+?!#_(kp z*pL$E?$rE6VTa!5n;jl`$Y&A84(r_5ECL*i0KJKYo&vFE*^7mqZeSVi6j@d1jf)O1 zkY}EYNKlp}uwV>pBH~9N5v999>`G^JD?TxCwNX)6_8eIyW_J6h1q)n!x;SjdPhkKF zDrAfVm1%(7R(@%e00^>g4*bh~+Nwo{x8X$m5>+D01DT|`DnFX5DtcuzbGjm82HP(? zTZ%ZFBb>L0Q(n6$^tv_`XA<{if5IG54uo~Qz=HnG|HNMc^ttaaLFtxv9GyN|S$a8dDO5RIg)|BLwA zMVq@^*4j^tJ2Sj@4QAqoHd$c1Ej|TPdB_wJ4}VYGUG@IA2zlYC+j_P%f2?JOgh#3y>U2sshCI6%xeWHa6iiPE2}_*n_v|oyhyD z#7+t0cbBYgab2It9)WO?2w)3Sg0a+e?!;>`V|~@nKs|u$itG|~Aon@<L zU4eVp^#Rtt`6)wX(Hb2Br|UT(QjtoINDRXok&8m4rQ#H<(E1K5fI3yd%G%HEi#mpq zG$)|qs${mb=rKn0mC1D3`DzBB@d67LYZNm=U=*mtUZ&Ru&!Qt#=0ar*JgZP?U^SUE zf`JSafqY6RwQkO0@3wu^lfY+E$eSv2b(Vi5ijK(TcNfled!Gxrq&r&VFbcx2^wbkG zUP@)$!S%wFekrkbn=1xgn(*xGbBYLuNn1ErgBBq2y z{qmSAo!Q602E_!LBLxv1W(Yi=j7$c2d6_}_9Wn?rr6ds{*z$Yw^|%Q^&NGsBB+qQs@~Rb)tC)SG6Pm?ca>=k`1i7RelQtF`DE<^jYS40x zaj8+i5}Gs;w}jEwDa?fkbjurUq}FxK-8Gq{5!Vk)=4|`1xTrgdix%xLPZaGiwyuKw zU|sN_M|$8=sO3_KO~qvRgu4(&674`eH__|TQR0|t0K1+NCk`ngvjsoIYmAMw?78wu z_`UjLB5W3B#sgY0^DtjVV(|aVTpkTz@&UkkJdN-i)GCUrO)AReRIQ@?wpLN>B$r^j zt>DvfvF0jjAhE`~S=Ug5Vj!{BmRM6h8fW4`L#$CYinVqv)=Wc1xef$OteMRO-vJWA zTe$Y&UBvJ%ioNmy)_;!_7sX!kJ6V-Nl3j#OP~@*9`bBh0J6&9o@un|^R$?^EnF5gY zjM6UEE~@*k4^66RDc#6t%PtfDQ42TbuZ}BMH)M|KaIaID8cL(Wlk3^Y+(`dP*${Nt z$XwL!!Mf&CmUdvWMcnqQQI3%RtI!>oTKpwScJMYyGYIJ_JMf8zN|6CU?&OjUP{WJ! zXgmpYD)x=#V#o!Z#hEN8>V>Cm?rcdQ!<$^#ZYphX^;1Tay~C_wkG=Kk!&CRIC&Y^# zqLnMvm1mPkx?BUW!u9wYugxy+TeNNb>=XKI;(R_Uy04g?bRR2`k{&3G3G%9P(ZY;n zj=Z7V<9N=(T?#^0C%~FG%d7}mKbmq$f{8WbDk=pTN z4hy00aWD$-jqt$>*g6>EFuuKdPsp5Lm?7l!Is&Kp+MBikv>S2KYiT*>W4Ktd;3$nI z#l<3d8&f@+xofgFiZ>h%?ZKRlA1M{Pzq1w{u}chlr^%AbMIK59MB9hRLeI~ehPY|= zW@JP1Dg_kl*~ECy7S5+*YXA^P(@xdXIQu6brA(@2LGd&$6awqT@ z=WP^6BJe0Y4cV`2pbO#OsZ26t7dZA?7j?X4N98MrJA~9?+Os$LJg55#7G;VZ2Q5`! zoanevr#qH)Oda$+hGxTEZDJ3oq6#Dc0WAEWE1Ax2w(l?~U%}GQrTBHExZH{tsyi~& znRC{{&p4de7Ce>;*2Y16H|l7pl-TXf+$Ikjw_swUS6tQ{~a6v z&cq#Af@VH;w6X*_=`9A8O|7k0rdV}CMxpJr>a*QIm6uyjRXPsNzc#tpk+_guD}l!k z$Q@PB$wF}t3w1>26tEXd6=?M^h1%RqgKQ9V+4X&4k`H}*D zNddp4fH8PpQov@i)(W__l>&aEp8aC;R9*@RlIXVMYR-15a-@oUm~wz{<&)p8OlTD* zX+4wQyLc_T>6nVyo>T3#9o(K~m7PP;xc){UK6Za6o{zu2$uJHvCRa zz;Y5l-Z-7`WKQ0JEKSTdq4nT(gws-+EmND%*wp)BW{aL%+GZaQFmd}=76GBOhD?`6%)nqH4{)pZKa`CN5 zESxkSK-KGS*W`6V3+y&Uc1zQ|10~^q)`vi~C8^Q<21SBX#0*zWVoE`fJ-3=3Gpo>C zhhxn$O{VKd#Q~1?td61uQT9T?V{`?S`b_2Zm1CxwS$u0fvyf%4#(gHlyoj#&v|{ib z@*vc68%>Rg+VklR6P+Eg3pT^4jw7_G&78k+tO%HnmFDz&oy?$)7shi%_Lxn-cZdqJ z0G?_L0i*{aRv-wbC^nD2p9o3p)l9(S#MjDs2NtJ|HonzBzBhX~^$GVwayQe72W)To zI2DV~k5ivgb9;qZxM!Tk-Yp~x#h{a8P`QedcxlByp;6XQgdq*)TUkTNuLE#QAZ#_L z!y3wHt_g)VC&g6Pm@=ymPiT9vGt0)M-e6~Tpza9A=dBYrR666#4W+C2c2ywinl@P!5J{AvZDgLaaxO*SBk>cii5}Uic zXf>sH_xz+dbNQweZ(X1izm&sL>GeovjrxGZihTfjmBiA~8I*5IEPBwCSgiqGq`jFW z7H)t_z-+0+Oie@u5&`z@ww~rmL1NZJyd7l=fnxdq6(Psh1{EQ#(NGcb_{YsUAxhxQ zI>EZRQ7=FuwO+uhc~-qJQU&;6D#c`aA)ZcYp2sV4gz{y}>F`oPWn@;o9bJ6B0y&e@ z-1_?Y6TqB2V(VmtQbz$yfD0+^xHTuE9A4zPno|fO*zJMe*50@|8!$p7O|B~~{ub|Kk9xn2N)E+L4D%w-qdbsQaQlbye>fs{M zH*d|ur8PD8a82N58h?novnZFbr`vhBtYPe$Z9H6c(<)YZxay|shht4wPoX~0fciIA z=a`2}DFRFp!cEjRooy@rqG4ZTav^$t%S?FUWqlzq%zcSZ zalp!xM-JpBjCW~i-N{~gb|jtD|E0>jv&AFi(fD%A*Rwmrm<}2R`j+!$pM9d_;~U$G zmQWREK=qfHPu=KM@~ERkAT5_nuy|!-DvhnO$nf4wMGr(V#?PE}%*-_V(pCl(658@B zBH=`>nkUEwD@Q*zBwkorzgowdHH<0MZlvv|YD^epHwj-_ERGtkvt53x$=9PPT{NY&BRncvjcfy%B z{UXhdkA7ih$AA0Z1`UTF8<5c;j*|DurHf_xpeRh2G0*tYjX2&lrKf2n*qa|4U<12t=zSG-y>A|{$y)DEM!i2l z0XN$1m{l3=ga=m3(p-+|{aMO-Fu<&qrA=zhnyn=>Xcmp=9>5I&jkbJk0gYzk%+^75 zldktI)>EU3dVkiJrDr{k0pHSW6-~zkQIVS#G>ZCl>y-I>(HbpFYro&5^u8iXQNEU_ zF-Q51KScRzx9s@SO8H{;V?py;kqfiFEZuClj!LMTwq@y!O

T#-=AM8e(J99=S3e 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

$zt3zc2LPj&Gl2xh=We`M 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 Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + let initial_counter = Counter { + count: msg.count, + total_funds: vec![], + owner: info.sender.clone(), + }; + COUNTERS.save(deps.storage, info.sender.clone(), &initial_counter)?; + + Ok(Response::new() + .add_attribute("method", "instantiate") + .add_attribute("owner", info.sender) + .add_attribute("count", msg.count.to_string())) +} + +pub mod utils { + use cosmwasm_std::Addr; + + use super::*; + + pub fn update_counter( + deps: DepsMut, + sender: Addr, + update_counter: &dyn Fn(&Option) -> i32, + update_funds: &dyn Fn(&Option) -> Vec, + ) -> Result { + COUNTERS + .update( + deps.storage, + sender.clone(), + |state| -> Result<_, ContractError> { + match state { + None => Ok(Counter { + count: update_counter(&None), + total_funds: update_funds(&None), + owner: sender, + }), + Some(counter) => Ok(Counter { + count: update_counter(&Some(counter.clone())), + total_funds: update_funds(&Some(counter)), + owner: sender, + }), + } + }, + ) + .map(|_r| true) + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::Increment {} => execute::increment(deps, info), + ExecuteMsg::Reset { count } => execute::reset(deps, info, count), + } +} + +pub mod execute { + use super::*; + + pub fn increment(deps: DepsMut, info: MessageInfo) -> Result { + utils::update_counter( + deps, + info.sender, + &|counter| match counter { + None => 0, + Some(counter) => counter.count + 1, + }, + &|counter| match counter { + None => info.funds.clone(), + Some(counter) => naive_add_coins(&info.funds, &counter.total_funds), + }, + )?; + Ok(Response::new().add_attribute("action", "increment")) + } + + pub fn reset(deps: DepsMut, info: MessageInfo, count: i32) -> Result { + utils::update_counter(deps, info.sender, &|_counter| count, &|_counter| vec![])?; + Ok(Response::new().add_attribute("action", "reset")) + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn sudo(deps: DepsMut, env: Env, msg: SudoMsg) -> Result { + match msg { + SudoMsg::IBCLifecycleComplete(IBCLifecycleComplete::IBCAck { + channel: _, + sequence: _, + ack: _, + success, + }) => sudo::receive_ack(deps, env.contract.address, success), + SudoMsg::IBCLifecycleComplete(IBCLifecycleComplete::IBCTimeout { + channel: _, + sequence: _, + }) => sudo::ibc_timeout(deps, env.contract.address), + } +} + +pub mod sudo { + use cosmwasm_std::Addr; + + use super::*; + + pub fn receive_ack( + deps: DepsMut, + contract: Addr, + _success: bool, + ) -> Result { + utils::update_counter( + deps, + contract, + &|counter| match counter { + None => 1, + Some(counter) => counter.count + 1, + }, + &|_counter| vec![], + )?; + Ok(Response::new().add_attribute("action", "ack")) + } + + pub(crate) fn ibc_timeout(deps: DepsMut, contract: Addr) -> Result { + utils::update_counter( + deps, + contract, + &|counter| match counter { + None => 10, + Some(counter) => counter.count + 10, + }, + &|_counter| vec![], + )?; + Ok(Response::new().add_attribute("action", "timeout")) + } +} + +pub fn naive_add_coins(lhs: &Vec, rhs: &Vec) -> Vec { + // This is a naive, inneficient implementation of Vec addition. + // This shouldn't be used in production but serves our purpose for this + // testing contract + let mut coins: HashMap = HashMap::new(); + for coin in lhs { + coins.insert(coin.denom.clone(), coin.amount); + } + + for coin in rhs { + coins + .entry(coin.denom.clone()) + .and_modify(|e| *e += coin.amount) + .or_insert(coin.amount); + } + coins.iter().map(|(d, &a)| Coin::new(a.into(), d)).collect() +} + +#[test] +fn coin_addition() { + let c1 = vec![Coin::new(1, "a"), Coin::new(2, "b")]; + let c2 = vec![Coin::new(7, "a"), Coin::new(2, "c")]; + + let mut sum = naive_add_coins(&c1, &c1); + sum.sort_by(|a, b| a.denom.cmp(&b.denom)); + assert_eq!(sum, vec![Coin::new(2, "a"), Coin::new(4, "b")]); + + let mut sum = naive_add_coins(&c1, &c2); + sum.sort_by(|a, b| a.denom.cmp(&b.denom)); + assert_eq!( + sum, + vec![Coin::new(8, "a"), Coin::new(2, "b"), Coin::new(2, "c"),] + ); + + let mut sum = naive_add_coins(&c2, &c2); + sum.sort_by(|a, b| a.denom.cmp(&b.denom)); + assert_eq!(sum, vec![Coin::new(14, "a"), Coin::new(4, "c"),]); + + let mut sum = naive_add_coins(&c2, &c1); + sum.sort_by(|a, b| a.denom.cmp(&b.denom)); + assert_eq!( + sum, + vec![Coin::new(8, "a"), Coin::new(2, "b"), Coin::new(2, "c"),] + ); + + let mut sum = naive_add_coins(&vec![], &c2); + sum.sort_by(|a, b| a.denom.cmp(&b.denom)); + assert_eq!(sum, c2); + + let mut sum = naive_add_coins(&c2, &vec![]); + sum.sort_by(|a, b| a.denom.cmp(&b.denom)); + assert_eq!(sum, c2); +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::GetCount { addr } => to_binary(&query::count(deps, addr)?), + QueryMsg::GetTotalFunds { addr } => to_binary(&query::total_funds(deps, addr)?), + } +} + +pub mod query { + use cosmwasm_std::Addr; + + use super::*; + + pub fn count(deps: Deps, addr: Addr) -> StdResult { + let state = COUNTERS.load(deps.storage, addr)?; + Ok(GetCountResponse { count: state.count }) + } + + pub fn total_funds(deps: Deps, addr: Addr) -> StdResult { + let state = COUNTERS.load(deps.storage, addr)?; + Ok(GetTotalFundsResponse { + total_funds: state.total_funds, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::Addr; + use cosmwasm_std::{coins, from_binary}; + + #[test] + fn proper_initialization() { + let mut deps = mock_dependencies(); + + let msg = InstantiateMsg { count: 17 }; + let info = mock_info("creator", &coins(1000, "earth")); + + // we can just call .unwrap() to assert this was a success + let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + assert_eq!(0, res.messages.len()); + + // it worked, let's query the state + let res = query( + deps.as_ref(), + mock_env(), + QueryMsg::GetCount { + addr: Addr::unchecked("creator"), + }, + ) + .unwrap(); + let value: GetCountResponse = from_binary(&res).unwrap(); + assert_eq!(17, value.count); + } + + #[test] + fn increment() { + let mut deps = mock_dependencies(); + + let msg = InstantiateMsg { count: 17 }; + let info = mock_info("creator", &coins(2, "token")); + let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + + let msg = InstantiateMsg { count: 17 }; + let info = mock_info("someone-else", &coins(2, "token")); + let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + + let info = mock_info("creator", &coins(2, "token")); + let msg = ExecuteMsg::Increment {}; + let _res = execute(deps.as_mut(), mock_env(), info, msg).unwrap(); + + // should increase counter by 1 + let res = query( + deps.as_ref(), + mock_env(), + QueryMsg::GetCount { + addr: Addr::unchecked("creator"), + }, + ) + .unwrap(); + let value: GetCountResponse = from_binary(&res).unwrap(); + assert_eq!(18, value.count); + + // Counter for someone else is not incremented + let res = query( + deps.as_ref(), + mock_env(), + QueryMsg::GetCount { + addr: Addr::unchecked("someone-else"), + }, + ) + .unwrap(); + let value: GetCountResponse = from_binary(&res).unwrap(); + assert_eq!(17, value.count); + } + + #[test] + fn reset() { + let mut deps = mock_dependencies(); + + let msg = InstantiateMsg { count: 17 }; + let info = mock_info("creator", &coins(2, "token")); + let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + + // beneficiary can release it + let unauth_info = mock_info("anyone", &coins(2, "token")); + let msg = ExecuteMsg::Reset { count: 7 }; + let _res = execute(deps.as_mut(), mock_env(), unauth_info, msg); + + // should be 7 + let res = query( + deps.as_ref(), + mock_env(), + QueryMsg::GetCount { + addr: Addr::unchecked("anyone"), + }, + ) + .unwrap(); + let value: GetCountResponse = from_binary(&res).unwrap(); + assert_eq!(7, value.count); + + // only the original creator can reset the counter + let auth_info = mock_info("creator", &coins(2, "token")); + let msg = ExecuteMsg::Reset { count: 5 }; + let _res = execute(deps.as_mut(), mock_env(), auth_info, msg).unwrap(); + + // should now be 5 + let res = query( + deps.as_ref(), + mock_env(), + QueryMsg::GetCount { + addr: Addr::unchecked("creator"), + }, + ) + .unwrap(); + let value: GetCountResponse = from_binary(&res).unwrap(); + assert_eq!(5, value.count); + } + + #[test] + fn acks() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let get_msg = QueryMsg::GetCount { + addr: Addr::unchecked(env.clone().contract.address), + }; + + // No acks + query(deps.as_ref(), env.clone(), get_msg.clone()).unwrap_err(); + + let msg = SudoMsg::ReceiveAck { + channel: format!("channel-0"), + sequence: 1, + ack: String::new(), + success: true, + }; + let _res = sudo(deps.as_mut(), env.clone(), msg).unwrap(); + + // should increase counter by 1 + let res = query(deps.as_ref(), env.clone(), get_msg.clone()).unwrap(); + let value: GetCountResponse = from_binary(&res).unwrap(); + assert_eq!(1, value.count); + + let msg = SudoMsg::ReceiveAck { + channel: format!("channel-0"), + sequence: 1, + ack: String::new(), + success: true, + }; + let _res = sudo(deps.as_mut(), env.clone(), msg).unwrap(); + + // should increase counter by 1 + let res = query(deps.as_ref(), env, get_msg).unwrap(); + let value: GetCountResponse = from_binary(&res).unwrap(); + assert_eq!(2, value.count); + } +} diff --git a/scripts/tests/ibc-hooks/counter/src/error.rs b/scripts/tests/ibc-hooks/counter/src/error.rs new file mode 100644 index 000000000..3caf0c5c5 --- /dev/null +++ b/scripts/tests/ibc-hooks/counter/src/error.rs @@ -0,0 +1,16 @@ +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("Custom Error val: {val:?}")] + CustomError { val: String }, + // Add any other custom errors you like here. + // Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details. +} diff --git a/scripts/tests/ibc-hooks/counter/src/helpers.rs b/scripts/tests/ibc-hooks/counter/src/helpers.rs new file mode 100644 index 000000000..c943c1365 --- /dev/null +++ b/scripts/tests/ibc-hooks/counter/src/helpers.rs @@ -0,0 +1,48 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{ + to_binary, Addr, Coin, CosmosMsg, CustomQuery, Querier, QuerierWrapper, StdResult, WasmMsg, + WasmQuery, +}; + +use crate::msg::{ExecuteMsg, GetCountResponse, QueryMsg}; + +/// CwTemplateContract is a wrapper around Addr that provides a lot of helpers +/// for working with this. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct CwTemplateContract(pub Addr); + +impl CwTemplateContract { + pub fn addr(&self) -> Addr { + self.0.clone() + } + + pub fn call>(&self, msg: T) -> StdResult { + let msg = to_binary(&msg.into())?; + Ok(WasmMsg::Execute { + contract_addr: self.addr().into(), + msg, + funds: vec![], + } + .into()) + } + + /// Get Count + pub fn count(&self, querier: &Q, addr: Addr) -> StdResult + where + Q: Querier, + T: Into, + CQ: CustomQuery, + { + let msg = QueryMsg::GetCount { addr }; + let query = WasmQuery::Smart { + contract_addr: self.addr().into(), + msg: to_binary(&msg)?, + } + .into(); + let res: GetCountResponse = QuerierWrapper::::new(querier).query(&query)?; + Ok(res) + } +} + diff --git a/scripts/tests/ibc-hooks/counter/src/integration_tests.rs b/scripts/tests/ibc-hooks/counter/src/integration_tests.rs new file mode 100644 index 000000000..4c5078465 --- /dev/null +++ b/scripts/tests/ibc-hooks/counter/src/integration_tests.rs @@ -0,0 +1,71 @@ +#[cfg(test)] +mod tests { + use crate::helpers::CwTemplateContract; + use crate::msg::InstantiateMsg; + use cosmwasm_std::{Addr, Coin, Empty, Uint128}; + use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper, Executor}; + + pub fn contract_template() -> Box> { + let contract = ContractWrapper::new( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + ); + Box::new(contract) + } + + const USER: &str = "USER"; + const ADMIN: &str = "ADMIN"; + const NATIVE_DENOM: &str = "denom"; + + fn mock_app() -> App { + AppBuilder::new().build(|router, _, storage| { + router + .bank + .init_balance( + storage, + &Addr::unchecked(USER), + vec![Coin { + denom: NATIVE_DENOM.to_string(), + amount: Uint128::new(1), + }], + ) + .unwrap(); + }) + } + + fn proper_instantiate() -> (App, CwTemplateContract) { + let mut app = mock_app(); + let cw_template_id = app.store_code(contract_template()); + + let msg = InstantiateMsg { count: 1i32 }; + let cw_template_contract_addr = app + .instantiate_contract( + cw_template_id, + Addr::unchecked(ADMIN), + &msg, + &[], + "test", + None, + ) + .unwrap(); + + let cw_template_contract = CwTemplateContract(cw_template_contract_addr); + + (app, cw_template_contract) + } + + mod count { + use super::*; + use crate::msg::ExecuteMsg; + + #[test] + fn count() { + let (mut app, cw_template_contract) = proper_instantiate(); + + let msg = ExecuteMsg::Increment {}; + let cosmos_msg = cw_template_contract.call(msg).unwrap(); + app.execute(Addr::unchecked(USER), cosmos_msg).unwrap(); + } + } +} diff --git a/scripts/tests/ibc-hooks/counter/src/lib.rs b/scripts/tests/ibc-hooks/counter/src/lib.rs new file mode 100644 index 000000000..ffd1f6ac4 --- /dev/null +++ b/scripts/tests/ibc-hooks/counter/src/lib.rs @@ -0,0 +1,9 @@ +#![allow(unused_imports)] +pub mod contract; +mod error; +pub mod helpers; +pub mod integration_tests; +pub mod msg; +pub mod state; + +pub use crate::error::ContractError; diff --git a/scripts/tests/ibc-hooks/counter/src/msg.rs b/scripts/tests/ibc-hooks/counter/src/msg.rs new file mode 100644 index 000000000..037d8c577 --- /dev/null +++ b/scripts/tests/ibc-hooks/counter/src/msg.rs @@ -0,0 +1,63 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Addr, Coin}; + +#[cw_serde] +pub struct InstantiateMsg { + pub count: i32, +} + +#[cw_serde] +pub enum ExecuteMsg { + Increment {}, + Reset { count: i32 }, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + // GetCount returns the current count as a json-encoded number + #[returns(GetCountResponse)] + GetCount { addr: Addr }, + #[returns(GetTotalFundsResponse)] + GetTotalFunds { addr: Addr }, +} + +// We define a custom struct for each query response +#[cw_serde] +pub struct GetCountResponse { + pub count: i32, +} + +#[cw_serde] +pub struct GetTotalFundsResponse { + pub total_funds: Vec, +} + +#[cw_serde] +#[serde(rename = "ibc_lifecycle_complete")] +pub enum IBCLifecycleComplete { + #[serde(rename = "ibc_ack")] + IBCAck { + /// The source channel (terra side) of the IBC packet + channel: String, + /// The sequence number that the packet was sent with + sequence: u64, + /// String encoded version of the ack as seen by OnAcknowledgementPacket(..) + ack: String, + /// Weather an ack is a success of failure according to the transfer spec + success: bool, + }, + #[serde(rename = "ibc_timeout")] + IBCTimeout { + /// The source channel (terra side) of the IBC packet + channel: String, + /// The sequence number that the packet was sent with + sequence: u64, + }, +} + +#[cw_serde] +pub enum SudoMsg { + #[serde(rename = "ibc_lifecycle_complete")] + IBCLifecycleComplete(IBCLifecycleComplete), +} diff --git a/scripts/tests/ibc-hooks/counter/src/state.rs b/scripts/tests/ibc-hooks/counter/src/state.rs new file mode 100644 index 000000000..4b8002fc4 --- /dev/null +++ b/scripts/tests/ibc-hooks/counter/src/state.rs @@ -0,0 +1,14 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{Addr, Coin}; +use cw_storage_plus::{Item, Map}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct Counter { + pub count: i32, + pub total_funds: Vec, + pub owner: Addr, +} + +pub const COUNTERS: Map = Map::new("state"); diff --git a/scripts/tests/ibc-hooks/increment.sh b/scripts/tests/ibc-hooks/increment.sh new file mode 100755 index 000000000..0c243a688 --- /dev/null +++ b/scripts/tests/ibc-hooks/increment.sh @@ -0,0 +1,97 @@ +#!/bin/bash + +echo "" +echo "#################" +echo "# IBC Hook call #" +echo "#################" +echo "" + +BINARY=picad +CHAIN_DIR=$(pwd)/data +WALLET_1=$($BINARY keys show wallet1 -a --keyring-backend test --home $CHAIN_DIR/test-1) +WALLET_2=$($BINARY keys show wallet2 -a --keyring-backend test --home $CHAIN_DIR/test-2) +DENOM=ppica + +# Deploy the smart contract on chain to test the callbacks. (find the source code under the following url: `~/scripts/tests/ibc-hooks/counter/src/contract.rs`) +echo "Deploying counter contract" + +TX_HASH=$($BINARY tx wasm store $(pwd)/scripts/tests/ibc-hooks/counter/artifacts/counter.wasm --from $WALLET_2 --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 --keyring-backend test -y --gas 10000000 --fees 6000000$DENOM -o json | jq -r '.txhash') +sleep 3 +CODE_ID=$($BINARY query tx $TX_HASH -o josn --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 | jq -r '.logs[0].events[1].attributes[1].value') + + +# Use Instantiate2 to instantiate the previous smart contract with a random hash to enable multiple instances of the same contract (when needed). +echo "Instantiating counter contract" +RANDOM_HASH=$(hexdump -vn16 -e'4/4 "%08X" 1 "\n"' /dev/urandom) +TX_HASH=$($BINARY tx wasm instantiate2 $CODE_ID '{"count": 0}' $RANDOM_HASH --no-admin --label="Label with $RANDOM_HASH" --from $WALLET_2 --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 --keyring-backend test -y --gas 10000000 --fees 6000000$DENOM -o json | jq -r '.txhash') + +echo "TX hash: $TX_HASH" +sleep 3 +CONTRACT_ADDRESS=$($BINARY query tx $TX_HASH -o josn --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 | jq -r '.logs[0].events[1].attributes[0].value') +echo "Contract address: $CONTRACT_ADDRESS" + +echo "Executing the IBC Hook to increment the counter" +# First execute an IBC transfer to create the entry in the smart contract with the sender address ... +IBC_HOOK_RES=$($BINARY tx ibc-transfer transfer transfer channel-0 $CONTRACT_ADDRESS 1$DENOM --memo='{"wasm":{"contract": "'"$CONTRACT_ADDRESS"'" ,"msg": {"increment": {}}}}' --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 --keyring-backend test --from $WALLET_1 --fees 6000000$DENOM -y -o json) +echo "IBC Hook response: $IBC_HOOK_RES" +sleep 3 +# ... then send another transfer to increments the count value from 0 to 1, send 1 more to the contract address to validate that it increased the value correctly. +IBC_HOOK_RES=$($BINARY tx ibc-transfer transfer transfer channel-0 $CONTRACT_ADDRESS 1$DENOM --memo='{"wasm":{"contract": "'"$CONTRACT_ADDRESS"'" ,"msg": {"increment": {}}}}' --chain-id test-1 --home $CHAIN_DIR/test-1 --fees 6000000$DENOM --node tcp://localhost:16657 --keyring-backend test --from $WALLET_1 -y -o json) +export WALLET_1_WASM_SENDER=$($BINARY q ibchooks wasm-sender channel-0 "$WALLET_1" --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657) + +IBC_RECEIVER_BALANCE=$($BINARY query bank balances $WALLET_1 --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 -o json) +echo "IBC Receiver balance: $IBC_RECEIVER_BALANCE" + +echo "wallet 1 wasm sender: $WALLET_1_WASM_SENDER" + +COUNT_RES="" +COUNT_FUNDS_RES="" +while [ "$COUNT_RES" != "1" ] || [ "$COUNT_FUNDS_RES" != "2" ]; do + sleep 3 + # Get count res + RES=$($BINARY query wasm contract-state smart "$CONTRACT_ADDRESS" '{"get_count": {"addr": "'"$WALLET_1_WASM_SENDER"'"}}' --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 -o json) + echo "Query response: $RES" + + # Query to assert that the counter value is 1 and the fund send are (remeber that the first time fund are send to the contract the counter is set to 0 instead of 1) + COUNT_RES=$($BINARY query wasm contract-state smart "$CONTRACT_ADDRESS" '{"get_count": {"addr": "'"$WALLET_1_WASM_SENDER"'"}}' --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 -o json | jq -r '.data.count') + COUNT_FUNDS_RES=$($BINARY query wasm contract-state smart "$CONTRACT_ADDRESS" '{"get_total_funds": {"addr": "'"$WALLET_1_WASM_SENDER"'"}}' --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 -o json | jq -r '.data.total_funds[0].amount') + echo "transaction relayed count: $COUNT_RES and relayed funds: $COUNT_FUNDS_RES" +done + +echo "Executing the IBC Hook to increment the counter on callback" +# Execute an IBC transfer with ibc_callback to test the callback acknowledgement twice. +IBC_HOOK_RES=$($BINARY tx ibc-transfer transfer transfer channel-0 $WALLET_1_WASM_SENDER 1$DENOM --memo='{"ibc_callback":"'"$CONTRACT_ADDRESS"'"}' --chain-id test-2 --home $CHAIN_DIR/test-2 --fees 6000000$DENOM --node tcp://localhost:26657 --keyring-backend test --from $WALLET_2 -y -o json) + + +sleep 3 +IBC_HOOK_RES=$($BINARY tx ibc-transfer transfer transfer channel-0 $WALLET_1_WASM_SENDER 1$DENOM --memo='{"ibc_callback":"'"$CONTRACT_ADDRESS"'"}' --chain-id test-2 --home $CHAIN_DIR/test-2 --fees 6000000$DENOM --node tcp://localhost:26657 --keyring-backend test --from $WALLET_2 -y -o json) +export WALLET_2_WASM_SENDER=$($BINARY q ibchooks wasm-sender channel-0 "$WALLET_2" --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657) + +COUNT_RES="" +while [ "$COUNT_RES" != "2" ]; do + sleep 3 + # Query the smart contract to validate that it received the callback twice (notice that the queried addess is the contract address itself). + COUNT_RES=$($BINARY query wasm contract-state smart "$CONTRACT_ADDRESS" '{"get_count": {"addr": "'"$CONTRACT_ADDRESS"'"}}' --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 -o json | jq -r '.data.count') + echo "relayed callback transaction count: $COUNT_RES" +done + +echo "Executing the IBC Hook to increment the counter on callback with timeout" +# Prepare two callback queries but this time with a timeout height that is unreachable (0-1) to test the timeout callback. +IBC_HOOK_RES=$($BINARY tx ibc-transfer transfer transfer channel-0 $WALLET_1_WASM_SENDER 1$DENOM --packet-timeout-height="0-1" --memo='{"ibc_callback":"'"$CONTRACT_ADDRESS"'"}' --chain-id test-2 --home $CHAIN_DIR/test-2 --fees 6000000$DENOM --node tcp://localhost:26657 --keyring-backend test --from $WALLET_2 -y -o json) +sleep 3 +IBC_HOOK_RES=$($BINARY tx ibc-transfer transfer transfer channel-0 $WALLET_1_WASM_SENDER 1$DENOM --packet-timeout-height="0-1" --memo='{"ibc_callback":"'"$CONTRACT_ADDRESS"'"}' --chain-id test-2 --home $CHAIN_DIR/test-2 --fees 6000000$DENOM --node tcp://localhost:26657 --keyring-backend test --from $WALLET_2 -y -o json) +export WALLET_2_WASM_SENDER=$($BINARY q ibchooks wasm-sender channel-0 "$WALLET_2" --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657) + +COUNT_RES="" +while [ "$COUNT_RES" != "22" ]; do + sleep 3 + # Query the smart contract to validate that it received the timeout callback twice and keep in mind that per each timeout the contract increases 10 counts (notice that the queried addess is the contract address itself). + COUNT_RES=$($BINARY query wasm contract-state smart "$CONTRACT_ADDRESS" '{"get_count": {"addr": "'"$CONTRACT_ADDRESS"'"}}' --chain-id test-2 --home $CHAIN_DIR/test-2 --node tcp://localhost:26657 -o json | jq -r '.data.count') + echo "relayed timeout callback transaction count: $COUNT_RES" +done + +echo "" +echo "##########################" +echo "# SUCCESS: IBC Hook call #" +echo "##########################" +echo "" diff --git a/scripts/tests/init-test-framework.sh b/scripts/tests/init-test-framework.sh new file mode 100755 index 000000000..b9e736b58 --- /dev/null +++ b/scripts/tests/init-test-framework.sh @@ -0,0 +1,151 @@ +#!/bin/bash + +BINARY=picad +CHAIN_DIR=$(pwd)/data +CHAINID_1=test-1 +CHAINID_2=test-2 + +### Custom genesis files +DENOM=ppica +GENESIS_1=$CHAIN_DIR/$CHAINID_1/config/genesis.json +TMP_GENESIS_1=$CHAIN_DIR/$CHAINID_1/config/genesis.json.tmp + +GENESIS_2=$CHAIN_DIR/$CHAINID_2/config/genesis.json +TMP_GENESIS_2=$CHAIN_DIR/$CHAINID_2/config/genesis.json.tmp + +VAL_MNEMONIC_1="clock post desk civil pottery foster expand merit dash seminar song memory figure uniform spice circle try happy obvious trash crime hybrid hood cushion" +VAL_MNEMONIC_2="angry twist harsh drastic left brass behave host shove marriage fall update business leg direct reward object ugly security warm tuna model broccoli choice" +WALLET_MNEMONIC_1="banner spread envelope side kite person disagree path silver will brother under couch edit food venture squirrel civil budget number acquire point work mass" +WALLET_MNEMONIC_2="veteran try aware erosion drink dance decade comic dawn museum release episode original list ability owner size tuition surface ceiling depth seminar capable only" +WALLET_MNEMONIC_3="vacuum burst ordinary enact leaf rabbit gather lend left chase park action dish danger green jeans lucky dish mesh language collect acquire waste load" +WALLET_MNEMONIC_4="open attitude harsh casino rent attitude midnight debris describe spare cancel crisp olive ride elite gallery leaf buffalo sheriff filter rotate path begin soldier" +RLY_MNEMONIC_1="alley afraid soup fall idea toss can goose become valve initial strong forward bright dish figure check leopard decide warfare hub unusual join cart" +RLY_MNEMONIC_2="record gift you once hip style during joke field prize dust unique length more pencil transfer quit train device arrive energy sort steak upset" + +P2PPORT_1=16656 +P2PPORT_2=26656 +RPCPORT_1=16657 +RPCPORT_2=26657 +RESTPORT_1=1316 +RESTPORT_2=1317 +ROSETTA_1=8080 +ROSETTA_2=8081 +GRPCPORT_1=8090 +GRPCPORT_2=9090 +GRPCWEB_1=8091 +GRPCWEB_2=9091 + +# Stop if it is already running +if pgrep -x "$BINARY" >/dev/null; then + echo "Terminating $BINARY..." + killall $BINARY +fi + +echo "Removing previous data..." +rm -rf $CHAIN_DIR/$CHAINID_1 &> /dev/null +rm -rf $CHAIN_DIR/$CHAINID_2 &> /dev/null + +# Add directories for both chains, exit if an error occurs +if ! mkdir -p $CHAIN_DIR/$CHAINID_1 2>/dev/null; then + echo "Failed to create chain folder. Aborting..." + exit 1 +fi + +if ! mkdir -p $CHAIN_DIR/$CHAINID_2 2>/dev/null; then + echo "Failed to create chain folder. Aborting..." + exit 1 +fi + +echo "Initializing $CHAINID_1 & $CHAINID_2..." +$BINARY init test --home $CHAIN_DIR/$CHAINID_1 --chain-id=$CHAINID_1 &> /dev/null +$BINARY init test --home $CHAIN_DIR/$CHAINID_2 --chain-id=$CHAINID_2 &> /dev/null + +echo "Adding genesis accounts..." +echo $VAL_MNEMONIC_1 | $BINARY keys add val1 --home $CHAIN_DIR/$CHAINID_1 --recover --keyring-backend=test +echo $VAL_MNEMONIC_2 | $BINARY keys add val2 --home $CHAIN_DIR/$CHAINID_2 --recover --keyring-backend=test +echo $WALLET_MNEMONIC_1 | $BINARY keys add wallet1 --home $CHAIN_DIR/$CHAINID_1 --recover --keyring-backend=test +echo $WALLET_MNEMONIC_2 | $BINARY keys add wallet2 --home $CHAIN_DIR/$CHAINID_2 --recover --keyring-backend=test +echo $WALLET_MNEMONIC_3 | $BINARY keys add wallet3 --home $CHAIN_DIR/$CHAINID_1 --recover --keyring-backend=test +echo $WALLET_MNEMONIC_4 | $BINARY keys add wallet4 --home $CHAIN_DIR/$CHAINID_2 --recover --keyring-backend=test +echo $RLY_MNEMONIC_1 | $BINARY keys add rly1 --home $CHAIN_DIR/$CHAINID_1 --recover --keyring-backend=test +echo $RLY_MNEMONIC_2 | $BINARY keys add rly2 --home $CHAIN_DIR/$CHAINID_2 --recover --keyring-backend=test + +VAL1_ADDR=$($BINARY keys show val1 --home $CHAIN_DIR/$CHAINID_1 --keyring-backend test -a) +VAL2_ADDR=$($BINARY keys show val2 --home $CHAIN_DIR/$CHAINID_2 --keyring-backend test -a) +WALLET1_ADDR=$($BINARY keys show wallet1 --home $CHAIN_DIR/$CHAINID_1 --keyring-backend test -a) +WALLET2_ADDR=$($BINARY keys show wallet2 --home $CHAIN_DIR/$CHAINID_2 --keyring-backend test -a) +WALLET3_ADDR=$($BINARY keys show wallet3 --home $CHAIN_DIR/$CHAINID_1 --keyring-backend test -a) +WALLET4_ADDR=$($BINARY keys show wallet4 --home $CHAIN_DIR/$CHAINID_2 --keyring-backend test -a) +RLY1_ADDR=$($BINARY keys show rly1 --home $CHAIN_DIR/$CHAINID_1 --keyring-backend test -a) +RLY2_ADDR=$($BINARY keys show rly2 --home $CHAIN_DIR/$CHAINID_2 --keyring-backend test -a) + +$BINARY add-genesis-account $VAL1_ADDR "1000000000000000000000${DENOM}" --home $CHAIN_DIR/$CHAINID_1 +$BINARY add-genesis-account $VAL2_ADDR "1000000000000000000000${DENOM}" --home $CHAIN_DIR/$CHAINID_2 +$BINARY add-genesis-account $WALLET1_ADDR "1000000000000000000000${DENOM}" --home $CHAIN_DIR/$CHAINID_1 +$BINARY add-genesis-account $WALLET2_ADDR "1000000000000000000000${DENOM}" --home $CHAIN_DIR/$CHAINID_2 +$BINARY add-genesis-account $WALLET3_ADDR "1000000000000000000000${DENOM}" --vesting-amount "10000000000${DENOM}" --vesting-start-time $(date +%s) --vesting-end-time $(($(date '+%s') + 100000023)) --home $CHAIN_DIR/$CHAINID_1 +$BINARY add-genesis-account $WALLET4_ADDR "1000000000000000000000${DENOM}" --vesting-amount "10000000000${DENOM}" --vesting-start-time $(date +%s) --vesting-end-time $(($(date '+%s') + 100000023)) --home $CHAIN_DIR/$CHAINID_2 +$BINARY add-genesis-account $RLY1_ADDR "1000000000000000000000${DENOM}" --home $CHAIN_DIR/$CHAINID_1 +$BINARY add-genesis-account $RLY2_ADDR "1000000000000000000000${DENOM}" --home $CHAIN_DIR/$CHAINID_2 + +echo "Creating and collecting gentx..." +$BINARY gentx val1 1000000000000000000000${DENOM} --home $CHAIN_DIR/$CHAINID_1 --chain-id $CHAINID_1 --keyring-backend test +$BINARY gentx val2 1000000000000000000000${DENOM} --home $CHAIN_DIR/$CHAINID_2 --chain-id $CHAINID_2 --keyring-backend test +$BINARY collect-gentxs --home $CHAIN_DIR/$CHAINID_1 &> /dev/null +$BINARY collect-gentxs --home $CHAIN_DIR/$CHAINID_2 &> /dev/null + +echo "Changing defaults and ports in app.toml and config.toml files..." +sed -i -e 's#"tcp://0.0.0.0:26656"#"tcp://localhost:'"$P2PPORT_1"'"#g' $CHAIN_DIR/$CHAINID_1/config/config.toml +sed -i -e 's#"tcp://127.0.0.1:26657"#"tcp://localhost:'"$RPCPORT_1"'"#g' $CHAIN_DIR/$CHAINID_1/config/config.toml +sed -i -e 's#"tcp://localhost:26657"#"tcp://localhost:'"$RPCPORT_1"'"#g' $CHAIN_DIR/$CHAINID_1/config/client.toml +sed -i -e 's/timeout_commit = "5s"/timeout_commit = "1s"/g' $CHAIN_DIR/$CHAINID_1/config/config.toml +sed -i -e 's/timeout_propose = "3s"/timeout_propose = "1s"/g' $CHAIN_DIR/$CHAINID_1/config/config.toml +sed -i -e 's/index_all_keys = false/index_all_keys = true/g' $CHAIN_DIR/$CHAINID_1/config/config.toml +sed -i -e 's/enable = false/enable = true/g' $CHAIN_DIR/$CHAINID_1/config/app.toml +sed -i -e 's/swagger = false/swagger = true/g' $CHAIN_DIR/$CHAINID_1/config/app.toml +sed -i -e 's#"tcp://localhost:1317"#"tcp://localhost:'"$RESTPORT_1"'"#g' $CHAIN_DIR/$CHAINID_1/config/app.toml +sed -i -e 's#":8080"#":'"$ROSETTA_1"'"#g' $CHAIN_DIR/$CHAINID_1/config/app.toml +sed -i -e 's/enabled-unsafe-cors = false/enabled-unsafe-cors = true/g' $CHAIN_DIR/$CHAINID_1/config/app.toml + + +sed -i -e 's#"tcp://0.0.0.0:26656"#"tcp://localhost:'"$P2PPORT_2"'"#g' $CHAIN_DIR/$CHAINID_2/config/config.toml +sed -i -e 's#"tcp://127.0.0.1:26657"#"tcp://localhost:'"$RPCPORT_2"'"#g' $CHAIN_DIR/$CHAINID_2/config/config.toml +sed -i -e 's#"tcp://localhost:26657"#"tcp://localhost:'"$RPCPORT_2"'"#g' $CHAIN_DIR/$CHAINID_2/config/client.toml +sed -i -e 's/timeout_commit = "5s"/timeout_commit = "1s"/g' $CHAIN_DIR/$CHAINID_2/config/config.toml +sed -i -e 's/timeout_propose = "3s"/timeout_propose = "1s"/g' $CHAIN_DIR/$CHAINID_2/config/config.toml +sed -i -e 's/index_all_keys = false/index_all_keys = true/g' $CHAIN_DIR/$CHAINID_2/config/config.toml +sed -i -e 's/enable = false/enable = true/g' $CHAIN_DIR/$CHAINID_2/config/app.toml +sed -i -e 's/swagger = false/swagger = true/g' $CHAIN_DIR/$CHAINID_2/config/app.toml +sed -i -e 's#"tcp://localhost:1317"#"tcp://localhost:'"$RESTPORT_2"'"#g' $CHAIN_DIR/$CHAINID_2/config/app.toml +sed -i -e 's#":8080"#":'"$ROSETTA_2"'"#g' $CHAIN_DIR/$CHAINID_2/config/app.toml +sed -i -e 's/enabled-unsafe-cors = false/enabled-unsafe-cors = true/g' $CHAIN_DIR/$CHAINID_2/config/app.toml + + +echo "Changing genesis.json..." +sed -i -e 's/"voting_period": "172800s"/"voting_period": "10s"/g' $CHAIN_DIR/$CHAINID_1/config/genesis.json +sed -i -e 's/"voting_period": "172800s"/"voting_period": "10s"/g' $CHAIN_DIR/$CHAINID_2/config/genesis.json +sed -i -e 's/"reward_delay_time": "604800s"/"reward_delay_time": "0s"/g' $CHAIN_DIR/$CHAINID_1/config/genesis.json +sed -i -e 's/"reward_delay_time": "604800s"/"reward_delay_time": "0s"/g' $CHAIN_DIR/$CHAINID_2/config/genesis.json + + +# Update the genesis file +update_test_genesis () { + jq "$1" $GENESIS_1 > $TMP_GENESIS_1 && mv $TMP_GENESIS_1 $GENESIS_1 + jq "$1" $GENESIS_2 > $TMP_GENESIS_2 && mv $TMP_GENESIS_2 $GENESIS_2 +} + +echo "update test genesis" +update_test_genesis ".app_state[\"staking\"][\"params\"][\"bond_denom\"]=\"$DENOM\"" +update_test_genesis ".app_state[\"mint\"][\"params\"][\"mint_denom\"]=\"$DENOM\"" +update_test_genesis ".app_state[\"crisis\"][\"constant_fee\"][\"denom\"]=\"$DENOM\"" +update_test_genesis ".app_state[\"gov\"][\"params\"][\"min_deposit\"][0][\"denom\"]=\"$DENOM\"" + + +# Starting the chain +echo "Starting $CHAINID_1 in $CHAIN_DIR..." +echo "Creating log file at $CHAIN_DIR/$CHAINID_1.log" +$BINARY start --log_level info --log_format json --p2p.pex=false --home $CHAIN_DIR/$CHAINID_1 --pruning=nothing --grpc.address="0.0.0.0:$GRPCPORT_1" > $CHAIN_DIR/$CHAINID_1.log 2>&1 & + +echo "Starting $CHAINID_2 in $CHAIN_DIR..." +echo "Creating log file at $CHAIN_DIR/$CHAINID_2.log" +$BINARY start --log_level info --log_format json --p2p.pex=false --home $CHAIN_DIR/$CHAINID_2 --pruning=nothing --grpc.address="0.0.0.0:$GRPCPORT_2" > $CHAIN_DIR/$CHAINID_2.log 2>&1 & diff --git a/x/ibc-hooks/relay_test.go b/x/ibc-hooks/relay_test.go index 1a38d98f5..1aef17d30 100644 --- a/x/ibc-hooks/relay_test.go +++ b/x/ibc-hooks/relay_test.go @@ -2,6 +2,7 @@ package ibchooks_test import ( "fmt" + customibctesting "github.com/cosmos/ibc-go/v8/testing" "testing" "time" @@ -11,7 +12,6 @@ import ( clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" "github.com/stretchr/testify/suite" - customibctesting "github.com/notional-labs/composable/v6/app/ibctesting" ibchookskeeper "github.com/notional-labs/composable/v6/x/ibc-hooks/keeper" ) @@ -52,8 +52,8 @@ func (suite *IBCHooksTestSuite) TestRecvHooks() { var ( transferAmount = sdkmath.NewInt(1000000000) timeoutHeight = clienttypes.NewHeight(1, 110) - // when transfer via sdk transfer from A (module) -> B (contract) - // nativeTokenSendOnChainA = sdk.NewCoin(sdk.DefaultBondDenom, transferAmount) + // when transfer via sdk transfer from A (module) -> B (contract) + // nativeTokenSendOnChainA = sdk.NewCoin(sdk.DefaultBondDenom, transferAmount) ) suite.SetupTest() // reset @@ -109,8 +109,8 @@ func (suite *IBCHooksTestSuite) TestAckHooks() { var ( transferAmount = sdkmath.NewInt(1000000000) timeoutHeight = clienttypes.NewHeight(0, 110) - // when transfer via sdk transfer from A (module) -> B (contract) - // nativeTokenSendOnChainA = sdk.NewCoin(sdk.DefaultBondDenom, transferAmount) + // when transfer via sdk transfer from A (module) -> B (contract) + // nativeTokenSendOnChainA = sdk.NewCoin(sdk.DefaultBondDenom, transferAmount) ) suite.SetupTest() // reset @@ -186,8 +186,8 @@ func (suite *IBCHooksTestSuite) TestTimeoutHooks() { var ( transferAmount = sdkmath.NewInt(1000000000) timeoutHeight = clienttypes.NewHeight(0, 500) - // when transfer via sdk transfer from A (module) -> B (contract) - // nativeTokenSendOnChainA = sdk.NewCoin(sdk.DefaultBondDenom, transferAmount) + // when transfer via sdk transfer from A (module) -> B (contract) + // nativeTokenSendOnChainA = sdk.NewCoin(sdk.DefaultBondDenom, transferAmount) ) suite.SetupTest() // reset diff --git a/x/transfermiddleware/pfm_test.go b/x/transfermiddleware/pfm_test.go index ac4e50b61..d5230931b 100644 --- a/x/transfermiddleware/pfm_test.go +++ b/x/transfermiddleware/pfm_test.go @@ -13,8 +13,6 @@ import ( transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" "github.com/stretchr/testify/suite" - - customibctesting "github.com/notional-labs/composable/v6/app/ibctesting" ) type PacketMetadata struct { @@ -34,7 +32,7 @@ type ForwardMetadata struct { type TransferMiddlewareTestSuite struct { suite.Suite - coordinator *customibctesting.Coordinator + coordinator *ibctesting.Coordinator // testing chains used for convenience and readability chainA *customibctesting.TestChain