From 1829bc42cab97e706126a9883ea9a1c468fd0391 Mon Sep 17 00:00:00 2001 From: Oleg Nikonychev Date: Tue, 1 Oct 2024 18:05:18 +0400 Subject: [PATCH 1/5] feat(evm): ante handler to prohibit authz grant evm messages (#2032) * feat(evm): ante handler to prohibit authz grant evm messages * feat(authz): rejecting authz exec messages with MsgEthereumTx inside * chore: lint * chore: typo fix --------- Co-authored-by: Unique Divine <51418232+Unique-Divine@users.noreply.github.com> Co-authored-by: Kevin Yang <5478483+k-yang@users.noreply.github.com> --- CHANGELOG.md | 1 + app/ante.go | 1 + app/ante/auth_grard_test.go | 138 ++++++++++++++++++++++++++++++++++++ app/ante/authz_guard.go | 72 +++++++++++++++++-- 4 files changed, 205 insertions(+), 7 deletions(-) create mode 100644 app/ante/auth_grard_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 57d38bc11..d0590d8a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -119,6 +119,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#2023](https://github.com/NibiruChain/nibiru/pull/2023) - fix(evm)!: adjusted generation and parsing of the block bloom events - [#2030](https://github.com/NibiruChain/nibiru/pull/2030) - refactor(eth/rpc): Delete unused code and improve logging in the eth and debug namespaces - [#2031](https://github.com/NibiruChain/nibiru/pull/2031) - fix(evm): debug calls with custom tracer and tracer options +- [#2032](https://github.com/NibiruChain/nibiru/pull/2032) - feat(evm): ante handler to prohibit authz grant evm messages - [#2039](https://github.com/NibiruChain/nibiru/pull/2039) - refactor(rpc-backend): remove unnecessary interface code - [#2044](https://github.com/NibiruChain/nibiru/pull/2044) - feat(evm): evm tx indexer service implemented - [#2045](https://github.com/NibiruChain/nibiru/pull/2045) - test(evm): backend tests with test network and real txs diff --git a/app/ante.go b/app/ante.go index f8c661163..79c2d5adf 100644 --- a/app/ante.go +++ b/app/ante.go @@ -62,6 +62,7 @@ func NewAnteHandlerNonEVM( ) sdk.AnteHandler { return sdk.ChainAnteDecorators( ante.AnteDecoratorPreventEtheruemTxMsgs{}, // reject MsgEthereumTxs + ante.AnteDecoratorAuthzGuard{}, // disable certain messages in authz grant "generic" authante.NewSetUpContextDecorator(), wasmkeeper.NewLimitSimulationGasDecorator(opts.WasmConfig.SimulationGasLimit), wasmkeeper.NewCountTXDecorator(opts.TxCounterStoreKey), diff --git a/app/ante/auth_grard_test.go b/app/ante/auth_grard_test.go new file mode 100644 index 000000000..838ed804e --- /dev/null +++ b/app/ante/auth_grard_test.go @@ -0,0 +1,138 @@ +package ante_test + +import ( + "time" + + sdkclienttx "github.com/cosmos/cosmos-sdk/client/tx" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/authz" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + "github.com/NibiruChain/nibiru/v2/app" + "github.com/NibiruChain/nibiru/v2/app/ante" + "github.com/NibiruChain/nibiru/v2/x/evm" + "github.com/NibiruChain/nibiru/v2/x/evm/evmtest" +) + +func (s *AnteTestSuite) TestAnteDecoratorAuthzGuard() { + testCases := []struct { + name string + txMsg func() sdk.Msg + wantErr string + }{ + { + name: "sad: authz generic grant with evm message", + txMsg: func() sdk.Msg { + someTime := time.Now() + expiryTime := someTime.Add(time.Hour) + genericGrant, err := authz.NewGrant( + someTime, + authz.NewGenericAuthorization(sdk.MsgTypeURL(&evm.MsgEthereumTx{})), &expiryTime, + ) + s.Require().NoError(err) + return &authz.MsgGrant{Grant: genericGrant} + }, + wantErr: "not allowed", + }, + { + name: "happy: authz generic grant with non evm message", + txMsg: func() sdk.Msg { + someTime := time.Now() + expiryTime := someTime.Add(time.Hour) + genericGrant, err := authz.NewGrant( + someTime, + authz.NewGenericAuthorization(sdk.MsgTypeURL(&stakingtypes.MsgCreateValidator{})), &expiryTime, + ) + s.Require().NoError(err) + return &authz.MsgGrant{Grant: genericGrant} + }, + wantErr: "", + }, + { + name: "happy: authz non generic grant", + txMsg: func() sdk.Msg { + someTime := time.Now() + expiryTime := someTime.Add(time.Hour) + genericGrant, err := authz.NewGrant( + someTime, + &banktypes.SendAuthorization{}, + &expiryTime, + ) + s.Require().NoError(err) + return &authz.MsgGrant{Grant: genericGrant} + }, + wantErr: "", + }, + { + name: "happy: non authz message", + txMsg: func() sdk.Msg { + return &evm.MsgEthereumTx{} + }, + wantErr: "", + }, + { + name: "sad: authz exec with a single evm message", + txMsg: func() sdk.Msg { + msgExec := authz.NewMsgExec( + sdk.AccAddress("nibiuser"), + []sdk.Msg{ + &evm.MsgEthereumTx{}, + }, + ) + return &msgExec + }, + wantErr: "ExtensionOptionsEthereumTx", + }, + { + name: "sad: authz exec with evm message and non evm message", + txMsg: func() sdk.Msg { + msgExec := authz.NewMsgExec( + sdk.AccAddress("nibiuser"), + []sdk.Msg{ + &banktypes.MsgSend{}, + &evm.MsgEthereumTx{}, + }, + ) + return &msgExec + }, + wantErr: "ExtensionOptionsEthereumTx", + }, + { + name: "happy: authz exec without evm messages", + txMsg: func() sdk.Msg { + msgExec := authz.NewMsgExec( + sdk.AccAddress("nibiuser"), + []sdk.Msg{ + &banktypes.MsgSend{}, + }, + ) + return &msgExec + }, + wantErr: "", + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + deps := evmtest.NewTestDeps() + anteDec := ante.AnteDecoratorAuthzGuard{} + + encCfg := app.MakeEncodingConfig() + txBuilder, err := sdkclienttx.Factory{}. + WithChainID(s.ctx.ChainID()). + WithTxConfig(encCfg.TxConfig). + BuildUnsignedTx(tc.txMsg()) + s.Require().NoError(err) + + _, err = anteDec.AnteHandle( + deps.Ctx, txBuilder.GetTx(), false, evmtest.NextNoOpAnteHandler, + ) + if tc.wantErr != "" { + s.Require().ErrorContains(err, tc.wantErr) + return + } + s.Require().NoError(err) + }) + } +} diff --git a/app/ante/authz_guard.go b/app/ante/authz_guard.go index 9ceb8b240..c1cbb8f8d 100644 --- a/app/ante/authz_guard.go +++ b/app/ante/authz_guard.go @@ -1,9 +1,67 @@ +// Copyright (c) 2023-2024 Nibi, Inc. package ante -// TODO: https://github.com/NibiruChain/nibiru/issues/1915 -// feat(ante): Add an authz guard to disable authz Ethereum txs and provide -// additional security around the default functionality exposed by the module. -// -// Implemenetation Notes -// UD-NOTE - IsAuthzMessage fn. Use authz import with module name -// UD-NOTE - Define set of disabled txMsgs +import ( + "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + errortypes "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/authz" + + "github.com/NibiruChain/nibiru/v2/x/evm" +) + +// AnteDecoratorAuthzGuard filters autz messages +type AnteDecoratorAuthzGuard struct{} + +// AnteHandle rejects "authz grant generic --msg-type '/eth.evm.v1.MsgEthereumTx'" +// Also rejects authz exec tx.json with any MsgEthereumTx inside +func (rmd AnteDecoratorAuthzGuard) AnteHandle( + ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler, +) (newCtx sdk.Context, err error) { + for _, msg := range tx.GetMsgs() { + // Do not allow grant for MsgEthereumTx + if msgGrant, ok := msg.(*authz.MsgGrant); ok { + if msgGrant.Grant.Authorization == nil { + return ctx, errors.Wrapf( + errortypes.ErrInvalidType, + "grant authorization is missing", + ) + } + authorization, err := msgGrant.Grant.GetAuthorization() + if err != nil { + return ctx, errors.Wrapf( + errortypes.ErrInvalidType, + "failed unmarshaling generic authorization %s", err, + ) + } + if genericAuth, ok := authorization.(*authz.GenericAuthorization); ok { + if genericAuth.MsgTypeURL() == sdk.MsgTypeURL(&evm.MsgEthereumTx{}) { + return ctx, errors.Wrapf( + errortypes.ErrNotSupported, + "authz grant generic for msg type %s is not allowed", + genericAuth.MsgTypeURL(), + ) + } + } + } + // Also reject MsgEthereumTx in exec + if msgExec, ok := msg.(*authz.MsgExec); ok { + msgsInExec, err := msgExec.GetMessages() + if err != nil { + return ctx, errors.Wrapf( + errortypes.ErrInvalidType, + "failed getting exec messages %s", err, + ) + } + for _, msgInExec := range msgsInExec { + if _, ok := msgInExec.(*evm.MsgEthereumTx); ok { + return ctx, errors.Wrapf( + errortypes.ErrInvalidType, + "MsgEthereumTx needs to be contained within a tx with 'ExtensionOptionsEthereumTx' option", + ) + } + } + } + } + return next(ctx, tx, simulate) +} From 48c8146cb1a4b25c219445a46d2e949b6acf8bd2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 14:07:22 +0000 Subject: [PATCH 2/5] chore(deps): bump bufbuild/buf-setup-action from 1.36.0 to 1.42.0 (#2043) * chore(deps): bump bufbuild/buf-setup-action from 1.36.0 to 1.42.0 Bumps [bufbuild/buf-setup-action](https://github.com/bufbuild/buf-setup-action) from 1.36.0 to 1.42.0. - [Release notes](https://github.com/bufbuild/buf-setup-action/releases) - [Commits](https://github.com/bufbuild/buf-setup-action/compare/v1.36.0...v1.42.0) --- updated-dependencies: - dependency-name: bufbuild/buf-setup-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Updated changelog - dependabot --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Unique Divine <51418232+Unique-Divine@users.noreply.github.com> Co-authored-by: Unique-Divine --- .github/workflows/proto-lint.yml | 4 ++-- CHANGELOG.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/proto-lint.yml b/.github/workflows/proto-lint.yml index 1151f2994..7140276e5 100644 --- a/.github/workflows/proto-lint.yml +++ b/.github/workflows/proto-lint.yml @@ -22,7 +22,7 @@ jobs: # timeout-minutes: 5 # steps: # - uses: actions/checkout@v4 - # - uses: bufbuild/buf-setup-action@v1.36.0 + # - uses: bufbuild/buf-setup-action@v1.42.0 # - uses: bufbuild/buf-lint-action@v1 # with: # input: "proto" @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: bufbuild/buf-setup-action@v1.36.0 + - uses: bufbuild/buf-setup-action@v1.42.0 with: github_token: ${{ github.token }} - uses: bufbuild/buf-breaking-action@v1 diff --git a/CHANGELOG.md b/CHANGELOG.md index d0590d8a4..6791ff05d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -174,7 +174,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump `github.com/hashicorp/go-getter` from 1.7.1 to 1.7.5 ([#1858](https://github.com/NibiruChain/nibiru/pull/1858), [#1938](https://github.com/NibiruChain/nibiru/pull/1938)) - Bump `github.com/btcsuite/btcd` from 0.23.3 to 0.24.0 ([#1862](https://github.com/NibiruChain/nibiru/pull/1862)) - Bump `pozetroninc/github-action-get-latest-release` from 0.7.0 to 0.8.0 ([#1863](https://github.com/NibiruChain/nibiru/pull/1863)) -- Bump `bufbuild/buf-setup-action` from 1.30.1 to 1.36.0 ([#1891](https://github.com/NibiruChain/nibiru/pull/1891), [#1900](https://github.com/NibiruChain/nibiru/pull/1900), [#1923](https://github.com/NibiruChain/nibiru/pull/1923), [#1972](https://github.com/NibiruChain/nibiru/pull/1972), [#1974](https://github.com/NibiruChain/nibiru/pull/1974), [#1988](https://github.com/NibiruChain/nibiru/pull/1988)) +- Bump `bufbuild/buf-setup-action` from 1.30.1 to 1.42.0 ([#1891](https://github.com/NibiruChain/nibiru/pull/1891), [#1900](https://github.com/NibiruChain/nibiru/pull/1900), [#1923](https://github.com/NibiruChain/nibiru/pull/1923), [#1972](https://github.com/NibiruChain/nibiru/pull/1972), [#1974](https://github.com/NibiruChain/nibiru/pull/1974), [#1988](https://github.com/NibiruChain/nibiru/pull/1988), [#2043](https://github.com/NibiruChain/nibiru/pull/2043)) - Bump `axios` from 1.7.3 to 1.7.4 ([#2016](https://github.com/NibiruChain/nibiru/pull/2016)) ## [v1.5.0](https://github.com/NibiruChain/nibiru/releases/tag/v1.5.0) - 2024-06-21 From 36533cd633e3280cfef1bab0a4abb06cc5f9bf39 Mon Sep 17 00:00:00 2001 From: Unique Divine <51418232+Unique-Divine@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:01:23 -0500 Subject: [PATCH 3/5] refactor(oracle): remove unused code and collapse empty client/cli directory (#2050) * gitignore and issue comment * refactor(oracle): remove unused code and collapse empty client/cli directory * chore: changelog * fix last few import paths --- .github/workflows/gh-issues.yml | 2 + .gitignore | 1 + CHANGELOG.md | 1 + cmd/nibid/cmd/root.go | 2 +- x/common/testutil/testnetwork/query.go | 2 +- .../cli/gen_pricefeeder_delegation.go | 0 .../cli/gen_pricefeeder_delegation_test.go | 2 +- x/oracle/{client => }/cli/query.go | 0 x/oracle/{client => }/cli/tx.go | 0 x/oracle/integration/action/price.go | 56 ------------------- x/oracle/module.go | 2 +- 11 files changed, 8 insertions(+), 60 deletions(-) rename x/oracle/{client => }/cli/gen_pricefeeder_delegation.go (100%) rename x/oracle/{client => }/cli/gen_pricefeeder_delegation_test.go (97%) rename x/oracle/{client => }/cli/query.go (100%) rename x/oracle/{client => }/cli/tx.go (100%) delete mode 100644 x/oracle/integration/action/price.go diff --git a/.github/workflows/gh-issues.yml b/.github/workflows/gh-issues.yml index 95154e494..69054648e 100644 --- a/.github/workflows/gh-issues.yml +++ b/.github/workflows/gh-issues.yml @@ -1,5 +1,7 @@ name: "Auto-add GH issues to project" # Add all issues opened to the issue board for triage and assignment +# GitHub Org and Project Automation +# https://www.notion.so/nibiru/GitHub-Org-and-Project-Automation-c771d671109849ee9fda7c8b741cd66a?pvs=4 on: issues: diff --git a/.gitignore b/.gitignore index 548b7e4b3..8e690b405 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ temp* txout.json vote.json **__pycache** +scratch-paper.md ### TypeScript and Friends diff --git a/CHANGELOG.md b/CHANGELOG.md index 6791ff05d..2d15cfb76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -152,6 +152,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#1913](https://github.com/NibiruChain/nibiru/pull/1913) - fix(tests): race condition from heavy Network tests - [#1992](https://github.com/NibiruChain/nibiru/pull/1992) - chore: enabled grpc for localnet - [#1999](https://github.com/NibiruChain/nibiru/pull/1999) - chore: update nibi go package version to v2 +- [#2050](https://github.com/NibiruChain/nibiru/pull/2050) - refactor(oracle): remove unused code and collapse empty client/cli directory ### Dependencies diff --git a/cmd/nibid/cmd/root.go b/cmd/nibid/cmd/root.go index bf1f8c89c..d52d70401 100644 --- a/cmd/nibid/cmd/root.go +++ b/cmd/nibid/cmd/root.go @@ -31,7 +31,7 @@ import ( "github.com/spf13/cobra" "github.com/NibiruChain/nibiru/v2/app" - oraclecli "github.com/NibiruChain/nibiru/v2/x/oracle/client/cli" + oraclecli "github.com/NibiruChain/nibiru/v2/x/oracle/cli" ) // NewRootCmd creates a new root command for nibid. It is called once in the diff --git a/x/common/testutil/testnetwork/query.go b/x/common/testutil/testnetwork/query.go index 3599ac2e4..ff5ff189f 100644 --- a/x/common/testutil/testnetwork/query.go +++ b/x/common/testutil/testnetwork/query.go @@ -14,7 +14,7 @@ import ( "github.com/spf13/cobra" "github.com/NibiruChain/nibiru/v2/x/common/asset" - oraclecli "github.com/NibiruChain/nibiru/v2/x/oracle/client/cli" + oraclecli "github.com/NibiruChain/nibiru/v2/x/oracle/cli" oracletypes "github.com/NibiruChain/nibiru/v2/x/oracle/types" sudocli "github.com/NibiruChain/nibiru/v2/x/sudo/cli" sudotypes "github.com/NibiruChain/nibiru/v2/x/sudo/types" diff --git a/x/oracle/client/cli/gen_pricefeeder_delegation.go b/x/oracle/cli/gen_pricefeeder_delegation.go similarity index 100% rename from x/oracle/client/cli/gen_pricefeeder_delegation.go rename to x/oracle/cli/gen_pricefeeder_delegation.go diff --git a/x/oracle/client/cli/gen_pricefeeder_delegation_test.go b/x/oracle/cli/gen_pricefeeder_delegation_test.go similarity index 97% rename from x/oracle/client/cli/gen_pricefeeder_delegation_test.go rename to x/oracle/cli/gen_pricefeeder_delegation_test.go index a2bde2e6b..cd1aa881c 100644 --- a/x/oracle/client/cli/gen_pricefeeder_delegation_test.go +++ b/x/oracle/cli/gen_pricefeeder_delegation_test.go @@ -7,7 +7,7 @@ import ( "github.com/NibiruChain/nibiru/v2/app" "github.com/NibiruChain/nibiru/v2/app/appconst" "github.com/NibiruChain/nibiru/v2/x/common/testutil" - "github.com/NibiruChain/nibiru/v2/x/oracle/client/cli" + "github.com/NibiruChain/nibiru/v2/x/oracle/cli" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/stretchr/testify/require" diff --git a/x/oracle/client/cli/query.go b/x/oracle/cli/query.go similarity index 100% rename from x/oracle/client/cli/query.go rename to x/oracle/cli/query.go diff --git a/x/oracle/client/cli/tx.go b/x/oracle/cli/tx.go similarity index 100% rename from x/oracle/client/cli/tx.go rename to x/oracle/cli/tx.go diff --git a/x/oracle/integration/action/price.go b/x/oracle/integration/action/price.go deleted file mode 100644 index 2cd9114bc..000000000 --- a/x/oracle/integration/action/price.go +++ /dev/null @@ -1,56 +0,0 @@ -package action - -import ( - "time" - - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/NibiruChain/collections" - - "github.com/NibiruChain/nibiru/v2/app" - "github.com/NibiruChain/nibiru/v2/x/common/asset" - "github.com/NibiruChain/nibiru/v2/x/common/testutil/action" - "github.com/NibiruChain/nibiru/v2/x/oracle/types" -) - -func SetOraclePrice(pair asset.Pair, price sdk.Dec) action.Action { - return &setPairPrice{ - Pair: pair, - Price: price, - } -} - -type setPairPrice struct { - Pair asset.Pair - Price sdk.Dec -} - -func (s setPairPrice) Do(app *app.NibiruApp, ctx sdk.Context) (sdk.Context, error) { - app.OracleKeeper.SetPrice(ctx, s.Pair, s.Price) - - return ctx, nil -} - -func InsertOraclePriceSnapshot(pair asset.Pair, time time.Time, price sdk.Dec) action.Action { - return &insertOraclePriceSnapshot{ - Pair: pair, - Time: time, - Price: price, - } -} - -type insertOraclePriceSnapshot struct { - Pair asset.Pair - Time time.Time - Price sdk.Dec -} - -func (s insertOraclePriceSnapshot) Do(app *app.NibiruApp, ctx sdk.Context) (sdk.Context, error) { - app.OracleKeeper.PriceSnapshots.Insert(ctx, collections.Join(s.Pair, s.Time), types.PriceSnapshot{ - Pair: s.Pair, - Price: s.Price, - TimestampMs: s.Time.UnixMilli(), - }) - - return ctx, nil -} diff --git a/x/oracle/module.go b/x/oracle/module.go index d3d5fd4ec..55ba76ea5 100644 --- a/x/oracle/module.go +++ b/x/oracle/module.go @@ -18,7 +18,7 @@ import ( "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" - "github.com/NibiruChain/nibiru/v2/x/oracle/client/cli" + "github.com/NibiruChain/nibiru/v2/x/oracle/cli" "github.com/NibiruChain/nibiru/v2/x/oracle/keeper" "github.com/NibiruChain/nibiru/v2/x/oracle/simulation" "github.com/NibiruChain/nibiru/v2/x/oracle/types" From 2a46f17f98327788ae6ac2b4e0de1dca4de9892a Mon Sep 17 00:00:00 2001 From: Unique Divine <51418232+Unique-Divine@users.noreply.github.com> Date: Tue, 1 Oct 2024 22:54:04 -0500 Subject: [PATCH 4/5] feat(evm-precompile): Precompile for one-way EVM calls to invoke/execute Wasm contracts. (#2054) * Implement wasm precompile in evm/embeds * feat(evm-precompile): Precompile for one-way EVM calls to invoke/execute Wasm contracts. * impl remaining Wasm.sol methods and document thoroughly * more tests and docs --- CHANGELOG.md | 1 + README.md | 2 +- eth/rpc/backend/backend_suite_test.go | 23 +- go.mod | 4 +- x/common/testutil/testnetwork/start_node.go | 3 +- x/evm/embeds/README.md | 48 ++ .../IFunToken.json | 4 +- .../artifacts/contracts/Wasm.sol/IWasm.json | 204 ++++++ .../contracts/{IFunToken.sol => FunToken.sol} | 2 +- x/evm/embeds/contracts/Wasm.sol | 73 +++ x/evm/embeds/embeds.go | 19 +- x/evm/evmtest/erc20.go | 13 + x/evm/precompile/errors.go | 45 ++ x/evm/precompile/funtoken.go | 57 +- x/evm/precompile/funtoken_test.go | 23 +- x/evm/precompile/hello_world_counter.wasm | Bin 0 -> 203903 bytes x/evm/precompile/precompile.go | 31 +- x/evm/precompile/wasm.go | 378 +++++++++++ x/evm/precompile/wasm_parse.go | 242 ++++++++ x/evm/precompile/wasm_test.go | 587 ++++++++++++++++++ x/tokenfactory/fixture/fixture.go | 3 +- 21 files changed, 1696 insertions(+), 66 deletions(-) rename x/evm/embeds/artifacts/contracts/{IFunToken.sol => FunToken.sol}/IFunToken.json (89%) create mode 100644 x/evm/embeds/artifacts/contracts/Wasm.sol/IWasm.json rename x/evm/embeds/contracts/{IFunToken.sol => FunToken.sol} (89%) create mode 100644 x/evm/embeds/contracts/Wasm.sol create mode 100644 x/evm/precompile/errors.go create mode 100644 x/evm/precompile/hello_world_counter.wasm create mode 100644 x/evm/precompile/wasm.go create mode 100644 x/evm/precompile/wasm_parse.go create mode 100644 x/evm/precompile/wasm_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d15cfb76..c04678690 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -123,6 +123,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#2039](https://github.com/NibiruChain/nibiru/pull/2039) - refactor(rpc-backend): remove unnecessary interface code - [#2044](https://github.com/NibiruChain/nibiru/pull/2044) - feat(evm): evm tx indexer service implemented - [#2045](https://github.com/NibiruChain/nibiru/pull/2045) - test(evm): backend tests with test network and real txs +- [#2054](https://github.com/NibiruChain/nibiru/pull/2054) - feat(evm-precompile): Precompile for one-way EVM calls to invoke/execute Wasm contracts. #### Dapp modules: perp, spot, oracle, etc diff --git a/README.md b/README.md index acf559738..4b1b13efc 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ If you have questions or concerns, feel free to connect with a developer or comm | Module | Description | | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [Wasm][code-x-wasm] | Implements the execution environment for WebAssembly (WASM) smart contracts. CosmWasm smart contracts are Rust-based, Wasm smart contracts built for enhanced security, performance, and interoperability. See our [CosmWasm sandbox monorepo (cw-nibiru)](https://github.com/NibiruChain/cw-nibiru/tree/main) for the protocol's core smart contracts. | +| [Wasm][code-x-wasm] | Implements the execution environment for WebAssembly (WASM) smart contracts. CosmWasm smart contracts are Rust-based, Wasm smart contracts built for enhanced security, performance, and interoperability. See our [CosmWasm sandbox monorepo (nibiru-wasm)](https://github.com/NibiruChain/nibiru-wasm/tree/main) for the protocol's core smart contracts. | | [EVM][code-x-evm] | Implements Nibiru EVM, which manages an Ethereum Virtual Machine (EVM) state database and enables the execution of Ethereum smart contracts. Nibiru EVM is an extension of "[geth](https://github.com/ethereum/go-ethereum)" along with "web3" and "eth" JSON-RPC methods. | | [Devgas][code-x-devgas] | The `devgas` module of Nibiru Chain shares contract execution fees with smart contract developers. This aims to increase the adoption of Nibiru by offering CosmWasm smart contract developers a direct source of income based on usage. | | [Epochs][code-x-epochs] | The `epochs` module allows other modules to set hooks to be called to execute code automatically on a period basis. For example, "once a week, starting at UTC-time = x". `epochs` creates a generalized epoch interface. | diff --git a/eth/rpc/backend/backend_suite_test.go b/eth/rpc/backend/backend_suite_test.go index 540bdddb4..0623557a5 100644 --- a/eth/rpc/backend/backend_suite_test.go +++ b/eth/rpc/backend/backend_suite_test.go @@ -2,13 +2,12 @@ package backend_test import ( "context" + "crypto/ecdsa" "fmt" "math/big" "testing" "time" - "crypto/ecdsa" - sdk "github.com/cosmos/cosmos-sdk/types" gethcommon "github.com/ethereum/go-ethereum/common" gethcore "github.com/ethereum/go-ethereum/core/types" @@ -33,15 +32,21 @@ import ( "github.com/NibiruChain/nibiru/v2/x/common/testutil/testnetwork" ) -var recipient = evmtest.NewEthPrivAcc().EthAddr -var amountToSend = evm.NativeToWei(big.NewInt(1)) +var ( + recipient = evmtest.NewEthPrivAcc().EthAddr + amountToSend = evm.NativeToWei(big.NewInt(1)) +) -var transferTxBlockNumber rpc.BlockNumber -var transferTxBlockHash gethcommon.Hash -var transferTxHash gethcommon.Hash +var ( + transferTxBlockNumber rpc.BlockNumber + transferTxBlockHash gethcommon.Hash + transferTxHash gethcommon.Hash +) -var testContractAddress gethcommon.Address -var deployContractBlockNumber rpc.BlockNumber +var ( + testContractAddress gethcommon.Address + deployContractBlockNumber rpc.BlockNumber +) type BackendSuite struct { suite.Suite diff --git a/go.mod b/go.mod index 3476119b5..59bf0e292 100644 --- a/go.mod +++ b/go.mod @@ -60,6 +60,8 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/gorilla/websocket v1.5.0 github.com/rs/cors v1.8.3 + github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 + golang.org/x/crypto v0.21.0 golang.org/x/exp v0.0.0-20231006140011-7918f672742d golang.org/x/net v0.23.0 golang.org/x/text v0.14.0 @@ -201,7 +203,6 @@ require ( github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect - github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect @@ -223,7 +224,6 @@ require ( go.opentelemetry.io/otel/trace v1.21.0 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.21.0 // indirect golang.org/x/oauth2 v0.16.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.18.0 // indirect diff --git a/x/common/testutil/testnetwork/start_node.go b/x/common/testutil/testnetwork/start_node.go index a9dcaf010..93c7381ac 100644 --- a/x/common/testutil/testnetwork/start_node.go +++ b/x/common/testutil/testnetwork/start_node.go @@ -142,8 +142,7 @@ func startNodeAndServers(cfg Config, val *Validator) error { val.EthTxIndexer = evmTxIndexer val.EthTxIndexerService = evmTxIndexerService - val.jsonrpc, val.jsonrpcDone, err = - server.StartJSONRPC(val.Ctx, val.ClientCtx, tmRPCAddr, tmEndpoint, val.AppConfig, val.EthTxIndexer) + val.jsonrpc, val.jsonrpcDone, err = server.StartJSONRPC(val.Ctx, val.ClientCtx, tmRPCAddr, tmEndpoint, val.AppConfig, val.EthTxIndexer) if err != nil { return errors.Wrap(err, "failed to start JSON-RPC server") } diff --git a/x/evm/embeds/README.md b/x/evm/embeds/README.md index 6d4e207ff..e4cc8dad9 100644 --- a/x/evm/embeds/README.md +++ b/x/evm/embeds/README.md @@ -1,6 +1,54 @@ # Nibiru Contract Embeds +## Hacking + ```shell npm install npx hardhat compile ``` + +## Precompile Solidity Documentation + +Example of a well-documented contract: [[Uniswap/v4-core/.../IHooks.sol](https://github.com/Uniswap/v4-core/blob/3407bce4b39869fe41ad5ec724b2df308c34900f/src/interfaces/IHooks.sol)] + +- `@notice`: Used to explain to end users what the function does. Should be written in plain English and focus on the function's purpose. + Best practice: Include for all public and external functions. +- `@param`: Describes a function parameter. Should explain what the parameter is used for. + Best practice: Include for all function parameters, especially in interfaces. +- `@dev`: Provides additional details for developers. Used for implementation details, notes, or warnings for developers. + Best practice: Use when there's important information that doesn't fit in `@notice` but is crucial for developers. +- `@return`: Describes what a function returns. + Best practice: Use for all functions that return values, explaining each return value. + +Example from IHooks.sol: +```solidity +/@notice The hook called before liquidity is removed +/// @param sender The initial msg.sender for the remove liquidity call +/// @param key The key for the pool +/// @param params The parameters for removing liquidity +/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be be passed on to the hook +/// @return bytes4 The function selector for the hook +function beforeRemoveLiquidity( + address sender, + PoolKey calldata key, + IPoolManager.ModifyLiquidityParams calldata params, + bytes calldata hookData +) external returns (bytes4); +``` + +@inheritdoc: + +Used to inherit documentation from a parent contract or interface. +Best practice: Use when you want to reuse documentation from a base contract. + + +@title: + +Provides a title for the contract or interface. +Best practice: Include at the top of each contract or interface file. + + +@author: + +States the author of the contract. +Best practice: Optional, but can be useful in larger projects. diff --git a/x/evm/embeds/artifacts/contracts/IFunToken.sol/IFunToken.json b/x/evm/embeds/artifacts/contracts/FunToken.sol/IFunToken.json similarity index 89% rename from x/evm/embeds/artifacts/contracts/IFunToken.sol/IFunToken.json rename to x/evm/embeds/artifacts/contracts/FunToken.sol/IFunToken.json index 6fe6e838a..a2bebf939 100644 --- a/x/evm/embeds/artifacts/contracts/IFunToken.sol/IFunToken.json +++ b/x/evm/embeds/artifacts/contracts/FunToken.sol/IFunToken.json @@ -1,7 +1,7 @@ { "_format": "hh-sol-artifact-1", - "contractName": "IFunToken", - "sourceName": "contracts/IFunToken.sol", + "contractName": "FunToken", + "sourceName": "contracts/FunToken.sol", "abi": [ { "inputs": [ diff --git a/x/evm/embeds/artifacts/contracts/Wasm.sol/IWasm.json b/x/evm/embeds/artifacts/contracts/Wasm.sol/IWasm.json new file mode 100644 index 000000000..61e38453f --- /dev/null +++ b/x/evm/embeds/artifacts/contracts/Wasm.sol/IWasm.json @@ -0,0 +1,204 @@ +{ + "_format": "hh-sol-artifact-1", + "contractName": "IWasm", + "sourceName": "contracts/Wasm.sol", + "abi": [ + { + "inputs": [ + { + "internalType": "string", + "name": "contractAddr", + "type": "string" + }, + { + "internalType": "bytes", + "name": "msgArgs", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "string", + "name": "denom", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct IWasm.BankCoin[]", + "name": "funds", + "type": "tuple[]" + } + ], + "name": "execute", + "outputs": [ + { + "internalType": "bytes", + "name": "response", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "string", + "name": "contractAddr", + "type": "string" + }, + { + "internalType": "bytes", + "name": "msgArgs", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "string", + "name": "denom", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct IWasm.BankCoin[]", + "name": "funds", + "type": "tuple[]" + } + ], + "internalType": "struct IWasm.WasmExecuteMsg[]", + "name": "executeMsgs", + "type": "tuple[]" + } + ], + "name": "executeMulti", + "outputs": [ + { + "internalType": "bytes[]", + "name": "responses", + "type": "bytes[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "admin", + "type": "string" + }, + { + "internalType": "uint64", + "name": "codeID", + "type": "uint64" + }, + { + "internalType": "bytes", + "name": "msgArgs", + "type": "bytes" + }, + { + "internalType": "string", + "name": "label", + "type": "string" + }, + { + "components": [ + { + "internalType": "string", + "name": "denom", + "type": "string" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct IWasm.BankCoin[]", + "name": "funds", + "type": "tuple[]" + } + ], + "name": "instantiate", + "outputs": [ + { + "internalType": "string", + "name": "contractAddr", + "type": "string" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "contractAddr", + "type": "string" + }, + { + "internalType": "bytes", + "name": "req", + "type": "bytes" + } + ], + "name": "query", + "outputs": [ + { + "internalType": "bytes", + "name": "response", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "contractAddr", + "type": "string" + }, + { + "internalType": "bytes", + "name": "key", + "type": "bytes" + } + ], + "name": "queryRaw", + "outputs": [ + { + "internalType": "bytes", + "name": "response", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "bytecode": "0x", + "deployedBytecode": "0x", + "linkReferences": {}, + "deployedLinkReferences": {} +} diff --git a/x/evm/embeds/contracts/IFunToken.sol b/x/evm/embeds/contracts/FunToken.sol similarity index 89% rename from x/evm/embeds/contracts/IFunToken.sol rename to x/evm/embeds/contracts/FunToken.sol index db1757f1b..73fb0ed7f 100644 --- a/x/evm/embeds/contracts/IFunToken.sol +++ b/x/evm/embeds/contracts/FunToken.sol @@ -14,4 +14,4 @@ interface IFunToken { address constant FUNTOKEN_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000800; -IFunToken constant FUNTOKEN_GATEWAY = IFunToken(FUNTOKEN_PRECOMPILE_ADDRESS); +IFunToken constant FUNTOKEN_PRECOMPILE = IFunToken(FUNTOKEN_PRECOMPILE_ADDRESS); diff --git a/x/evm/embeds/contracts/Wasm.sol b/x/evm/embeds/contracts/Wasm.sol new file mode 100644 index 000000000..8a5842062 --- /dev/null +++ b/x/evm/embeds/contracts/Wasm.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.19; + +address constant WASM_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000802; + +IWasm constant WASM_PRECOMPILE = IWasm(WASM_PRECOMPILE_ADDRESS); + +interface IWasm { + struct BankCoin { + string denom; + uint256 amount; + } + + /// @notice Invoke a contract's "ExecuteMsg", which corresponds to + /// "wasm/types/MsgExecuteContract". This enables arbitrary smart contract + /// execution using the Wasm VM from the EVM. + /// @param contractAddr nibi-prefixed Bech32 address of the wasm contract + /// @param msgArgs JSON encoded wasm execute invocation + /// @param funds Optional funds to supply during the execute call. It's + /// uncommon to use this field, so you'll pass an empty array most of the time. + /// @dev The three non-struct arguments are more gas efficient than encoding a + /// single argument as a WasmExecuteMsg. + function execute( + string memory contractAddr, + bytes memory msgArgs, + BankCoin[] memory funds + ) payable external returns (bytes memory response); + + struct WasmExecuteMsg { + string contractAddr; + bytes msgArgs; + BankCoin[] funds; + } + + /// @notice Identical to "execute", except for multiple contract calls. + function executeMulti( + WasmExecuteMsg[] memory executeMsgs + ) payable external returns (bytes[] memory responses); + + /// @notice Query the public API of another contract at a known address (with + /// known ABI). + /// Implements smart query, the "WasmQuery::Smart" variant from "cosmwas_std". + /// @param contractAddr nibi-prefixed Bech32 address of the wasm contract + /// @param req JSON encoded query request + /// @return response Returns whatever type the contract returns (caller should + /// know), wrapped in a JSON encoded contract result. + function query( + string memory contractAddr, + bytes memory req + ) external view returns (bytes memory response); + + /// @notice Query the raw kv-store of the contract. + /// Implements raw query, the "WasmQuery::Raw" variant from "cosmwas_std". + /// @param contractAddr nibi-prefixed Bech32 address of the wasm contract + /// @param key contract state key. For example, a `cw_storage_plus::Item` of + /// value `Item::new("state")` creates prefix store with key, "state". + /// @return response JSON encoded, raw data stored at that key. + function queryRaw( + string memory contractAddr, + bytes memory key + ) external view returns (bytes memory response); + + /// @notice InstantiateContract creates a new smart contract instance for the + /// given code id. + function instantiate( + string memory admin, + uint64 codeID, + bytes memory msgArgs, + string memory label, + BankCoin[] memory funds + ) payable external returns (string memory contractAddr, bytes memory data); + +} diff --git a/x/evm/embeds/embeds.go b/x/evm/embeds/embeds.go index 5c358c723..0103b78c1 100644 --- a/x/evm/embeds/embeds.go +++ b/x/evm/embeds/embeds.go @@ -17,8 +17,10 @@ import ( var ( //go:embed artifacts/contracts/ERC20Minter.sol/ERC20Minter.json erc20MinterContractJSON []byte - //go:embed artifacts/contracts/IFunToken.sol/IFunToken.json - funtokenContractJSON []byte + //go:embed artifacts/contracts/FunToken.sol/IFunToken.json + funtokenPrecompileJSON []byte + //go:embed artifacts/contracts/Wasm.sol/IWasm.json + wasmPrecompileJSON []byte //go:embed artifacts/contracts/TestERC20.sol/TestERC20.json testErc20Json []byte ) @@ -32,11 +34,19 @@ var ( } // SmartContract_Funtoken: Precompile contract interface for - // "IFunToken.sol". This precompile enables transfers of ERC20 tokens + // "FunToken.sol". This precompile enables transfers of ERC20 tokens // to non-EVM accounts. Only the ABI is used. SmartContract_FunToken = CompiledEvmContract{ Name: "FunToken.sol", - EmbedJSON: funtokenContractJSON, + EmbedJSON: funtokenPrecompileJSON, + } + + // SmartContract_Funtoken: Precompile contract interface for + // "Wasm.sol". This precompile enables contract invocations in the Wasm VM + // from EVM accounts. Only the ABI is used. + SmartContract_Wasm = CompiledEvmContract{ + Name: "Wasm.sol", + EmbedJSON: wasmPrecompileJSON, } SmartContract_TestERC20 = CompiledEvmContract{ @@ -48,6 +58,7 @@ var ( func init() { SmartContract_ERC20Minter.MustLoad() SmartContract_FunToken.MustLoad() + SmartContract_Wasm.MustLoad() SmartContract_TestERC20.MustLoad() } diff --git a/x/evm/evmtest/erc20.go b/x/evm/evmtest/erc20.go index c7f20cfce..ce020036f 100644 --- a/x/evm/evmtest/erc20.go +++ b/x/evm/evmtest/erc20.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" + "github.com/NibiruChain/nibiru/v2/eth" "github.com/NibiruChain/nibiru/v2/x/common/testutil/testapp" "github.com/NibiruChain/nibiru/v2/x/evm" ) @@ -86,3 +87,15 @@ func CreateFunTokenForBankCoin( return funtoken } + +func AssertBankBalanceEqual( + t *testing.T, + deps TestDeps, + denom string, + account gethcommon.Address, + expectedBalance *big.Int, +) { + bech32Addr := eth.EthAddrToNibiruAddr(account) + actualBalance := deps.App.BankKeeper.GetBalance(deps.Ctx, bech32Addr, denom).Amount.BigInt() + assert.Zero(t, expectedBalance.Cmp(actualBalance), "expected %s, got %s", expectedBalance, actualBalance) +} diff --git a/x/evm/precompile/errors.go b/x/evm/precompile/errors.go new file mode 100644 index 000000000..5f4ee88da --- /dev/null +++ b/x/evm/precompile/errors.go @@ -0,0 +1,45 @@ +package precompile + +import ( + "errors" + "fmt" + + gethabi "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/core/vm" +) + +// Error short-hand for type validation +func ErrArgTypeValidation(solidityHint string, arg any) error { + return fmt.Errorf("type validation failed for (%s) argument: %s", solidityHint, arg) +} + +// Error when parsing method arguments +func ErrInvalidArgs(err error) error { + return fmt.Errorf("invalid method args: %w", err) +} + +func ErrMethodCalled(method *gethabi.Method, wrapped error) error { + return fmt.Errorf("%s method called: %w", method.Name, wrapped) +} + +// Check required for transactions but not needed for queries +func assertNotReadonlyTx(readOnly bool, isTx bool) error { + if readOnly && isTx { + return errors.New("cannot write state from staticcall (a read-only call)") + } + return nil +} + +// assertContractQuery checks if a contract call is a valid query operation. This +// function verifies that no funds (wei) are being sent with the query, as query +// operations should be read-only and not involve any value transfer. +func assertContractQuery(contract *vm.Contract) error { + weiValue := contract.Value() + if weiValue != nil && weiValue.Sign() != 0 { + return fmt.Errorf( + "funds (wei value) must not be expended calling a query function; received wei value %s", weiValue, + ) + } + + return nil +} diff --git a/x/evm/precompile/funtoken.go b/x/evm/precompile/funtoken.go index 365cac8e1..6eaf1bbff 100644 --- a/x/evm/precompile/funtoken.go +++ b/x/evm/precompile/funtoken.go @@ -32,23 +32,24 @@ func (p precompileFunToken) Address() gethcommon.Address { return PrecompileAddr_FunToken } -func (p precompileFunToken) RequiredGas(input []byte) (gasPrice uint64) { +// RequiredGas calculates the cost of calling the precompile in gas units. +func (p precompileFunToken) RequiredGas(input []byte) (gasCost uint64) { // Since [gethparams.TxGas] is the cost per (Ethereum) transaction that does not create // a contract, it's value can be used to derive an appropriate value for the - // precompile call. The FunToken precompile performs 3 operations, labeld 1-3 + // precompile call. The FunToken precompile performs 3 operations, labeled 1-3 // below: // 0 | Call the precompile (already counted in gas calculation) // 1 | Send ERC20 to EVM. // 2 | Convert ERC20 to coin // 3 | Send coin to recipient. - return gethparams.TxGas * 3 + return gethparams.TxGas * 2 } const ( - FunTokenMethod_BankSend FunTokenMethod = "bankSend" + FunTokenMethod_BankSend PrecompileMethod = "bankSend" ) -type FunTokenMethod string +type PrecompileMethod string // Run runs the precompiled contract func (p precompileFunToken) Run( @@ -76,13 +77,12 @@ func (p precompileFunToken) Run( return nil, err } - switch FunTokenMethod(method.Name) { + switch PrecompileMethod(method.Name) { case FunTokenMethod_BankSend: - // TODO: UD-DEBUG: Test that calling non-method on the right address does - // nothing. bz, err = p.bankSend(ctx, contract.CallerAddress, method, args, readonly) default: - // TODO: UD-DEBUG: test invalid method called + // Note that this code path should be impossible to reach since + // "DecomposeInput" parses methods directly from the ABI. err = fmt.Errorf("invalid method called with name \"%s\"", method.Name) return } @@ -104,28 +104,27 @@ type precompileFunToken struct { var executionGuard sync.Mutex -/* -bankSend: Implements "IFunToken.bankSend" - -The "args" populate the following function signature in Solidity: -```solidity -/// @dev bankSend sends ERC20 tokens as coins to a Nibiru base account -/// @param erc20 the address of the ERC20 token contract -/// @param amount the amount of tokens to send -/// @param to the receiving Nibiru base account address as a string -function bankSend(address erc20, uint256 amount, string memory to) external; -``` -*/ +// bankSend: Implements "IFunToken.bankSend" +// +// The "args" populate the following function signature in Solidity: +// +// ```solidity +// /// @dev bankSend sends ERC20 tokens as coins to a Nibiru base account +// /// @param erc20 the address of the ERC20 token contract +// /// @param amount the amount of tokens to send +// /// @param to the receiving Nibiru base account address as a string +// function bankSend(address erc20, uint256 amount, string memory to) external; +// ``` func (p precompileFunToken) bankSend( ctx sdk.Context, caller gethcommon.Address, method *gethabi.Method, - args []interface{}, + args []any, readOnly bool, ) (bz []byte, err error) { - if readOnly { - // Check required for transactions but not needed for queries - return nil, fmt.Errorf("cannot write state from staticcall (a read-only call)") + if e := assertNotReadonlyTx(readOnly, true); e != nil { + err = e + return } if !executionGuard.TryLock() { return nil, fmt.Errorf("bankSend is already in progress") @@ -196,7 +195,6 @@ func (p precompileFunToken) bankSend( } // TODO: UD-DEBUG: feat: Emit EVM events - // TODO: UD-DEBUG: feat: Emit ABCI events return method.Outputs.Pack() } @@ -207,10 +205,9 @@ func (p precompileFunToken) decomposeBankSendArgs(args []any) ( to string, err error, ) { - if len(args) != 3 { - err = fmt.Errorf("expected 3 arguments but got %d", len(args)) - return - } + // Note: The number of arguments is valiated before this function is called + // during "DecomposeInput". DecomposeInput calls "method.Inputs.Unpack", + // which validates against the the structure of the precompile's ABI. erc20, ok := args[0].(gethcommon.Address) if !ok { diff --git a/x/evm/precompile/funtoken_test.go b/x/evm/precompile/funtoken_test.go index 31f9413af..64be0360f 100644 --- a/x/evm/precompile/funtoken_test.go +++ b/x/evm/precompile/funtoken_test.go @@ -17,7 +17,17 @@ import ( "github.com/NibiruChain/nibiru/v2/x/evm/precompile" ) -func (s *Suite) TestFailToPackABI() { +// TestSuite: Runs all the tests in the suite. +func TestSuite(t *testing.T) { + suite.Run(t, new(FuntokenSuite)) + suite.Run(t, new(WasmSuite)) +} + +type FuntokenSuite struct { + suite.Suite +} + +func (s *FuntokenSuite) TestFailToPackABI() { testcases := []struct { name string methodName string @@ -67,7 +77,7 @@ func (s *Suite) TestFailToPackABI() { } } -func (s *Suite) TestHappyPath() { +func (s *FuntokenSuite) TestHappyPath() { deps := evmtest.NewTestDeps() s.T().Log("Create FunToken mapping and ERC20") @@ -127,12 +137,3 @@ func (s *Suite) TestHappyPath() { deps.App.BankKeeper.GetBalance(deps.Ctx, randomAcc, funtoken.BankDenom).Amount, ) } - -type Suite struct { - suite.Suite -} - -// TestPrecompileSuite: Runs all the tests in the suite. -func TestSuite(t *testing.T) { - suite.Run(t, new(Suite)) -} diff --git a/x/evm/precompile/hello_world_counter.wasm b/x/evm/precompile/hello_world_counter.wasm new file mode 100644 index 0000000000000000000000000000000000000000..9a532ca445d02a86d802a63f2683d66ff2df08e0 GIT binary patch literal 203903 zcmeFad!SucUGF;|>$%rjJ1b4nCQS=-Edt$<>_hOBB&0o>xo^|Q5wv)tw|Kd?4K&au zrL>{+z`3Q_r6~jn7&JoB3Q?+!+G5ozML~A4K!G9=iv)-mq-x<*iBgV5i@l%k?>FXL zYwf+0HX(TbIJbd4=NfZ7f8+NazcEI%`L(Z(qbQ0WjF(@Q?AQ_S(BJ5?bcemiyv9n@ z@944$e|c2(;90k)-fI4)ayQ;c_9(in5k8jrmCXN(LZQ<5Mz8KhFB{tMkEA6#R9$+b z8fg_Z@+7f6^wau%qyARtz>QJzOUZ(C%hlIcH^2Nf+pmjKz1J^)`K8;pY`#3I=vRYZ z|84t~*KLVn{cPIDSHI@^ErZOjeZ}UhqogQ$^_JIP=S4f4FTZ^IrPpu1>dMPEQ{Jju zXlWF9&DB@Ff-nDlOBAW3=e^=}uikuhnbv*nmRDT!!!KB~?fI8pzh(QCuY7|yvUHHV zeakE8$@WXHdELvmZFxh~RZZhtF8|>dyx{pS94WK7NPPL`>t1oi$fxS%TVDRUSNUjK z*S>Dc_BUMmiYqo>c{TmLIlU=e7+0IkYPDW%#{5^+zZ(B*G-|av2vLvY8d#AQ|3y)? z>KXM{7?UKmm|V)j-?6c=Q=$sV6pE81iWc!BiO9%%lvccqeJ}o4(KMoE`!Avgs!u(k z;Z`-Sq_ujL{#4tIcDq`OD%7Ty{lB=XcJ*JiR;{Ls;~HJ&m)ZxQb+5Lg#P%dE0$lAK-Esy>9c>*Ig;h+g9K5`Yo?`9dB1vZHA*)H#T2& z)oWg%kG~g>UB0FK_51O{?OU#W-Id$7y!KL%e*5O@Ub8*=ow!>jQtP%WuYMJ2gN&qY zf0gR`op{leS5xYhn_sa-NcP$*Uvu@P>qs8E^ipVG^QBv^zC4XvmtK0smd)2(`tr@M z-I6BFe@@~kk6Yi4CqI|0dL+Ixe&Mp^Kf2-cAIZ=8WW4U4_=E9t&iaG+5952|55<2J z|8e}`_#^S3#2<}k;`J~2@&CLlU3c+|f8vMli$4)xIJNQo^;f*=ybGqzpM2$i{V!WC z|KDCTb^Z%}>|9bo0;r)c^H=Z$6OxKUb`O$s4X+x9;NW zu7CaZ|Mup1SA26k`LppAZ;NkA;@jt{jC+ok# zmtDzQlOyq)leZ-6z8SyrSCd~$*6mMzBe^5_;dQqs>uyQ^I9d16wO^MR~TZ zUtbYr@kA2!s>y~do&+N|WYvkJ(W|xNmc8{GdQ{HWhpf@<`sW58vqm0o?bp^MmLtj< zTk}=xqo|d|d9*&NSh1|;1#3mYn$;R*wXJ!d%+yjVYe_?qZ9is>)v~m^(3=~S$`VrF zo}Ny@luF9xD_Lz@9>3TN=E>ZG`L8_|of8^NwyM*0>Zo-8I?kf34hSkcvU{(gJZf34Jlnb=YJCzy4{%LZM9Bt$RD+8* zcA~tIMS!tSsq6Y_7V|H?XnCJ!-gxoyewy!iZT`&bdog7+2T4|0lbn$yIqfaw7iFV0 z$zn}PlJmAy&j#HA3Q4|rYxgI!WFl$zVm%rgsUgbJHA#hsdfZMlD>qF~Prv^BPMoPj z6ixE3M%w)te53oP{2|2#@+o*^7)V+SMEM!Yj}3-G=dSBlNRPA1&`>HvLrLfe9n?@# zA4)nrlr#(_^`TVAWJ5`PC_KVY0LHP0lFk`Q>O*-nNh&+y8y(vl1-91!bHMhRu)Qw8 z>aE|@!?3-UH9=bY+~i}{)Ku1mIHW~ca~8I%SXLLd*Rpz1ux_=2MgiOTRMrCeS`ou` zJ{Y!JbAwVPwnGb4QZ`RZ0ozrhuzgm+0^9Agu$=){gckI^dpOo`dCMTu?pJt+co`EE z&=3VQhAE(tHADepvngO~gaR54?JxiT(01N30PabH?Fc4U3rtQ2m@IPXRW!AF>w_`@ zlfg6)(mumT`6V2Lq#8;yRy@MDRV*_SNa2s6V8v>M@dZrg({L>m*p^qte)XXSLRxc! zQvEpp6SS9!C|VQ2dvfae7{8wv8B?}GgFabp#?19W=sl~p0=@0CG4raC$`YWBv=Sy) zg~`=nOs-~CVRC&oCf7$Wx$2nwzGjl{sEBVIiMI9Og7BsXqODod{W?6_|EX6Oil|b( zy~qGcwgb`D$p{J94n@S>^4o8kiSnvsZI{t6HNLxOP=QTdmQF@(s;lT%(oS3U)<;T( zi4?hZ{d1L%43CL~yEc@@$eksL*{2$Pnm-f`L>Zl2nY?KJ;j0KEoQPhsCThB%^TIw| z8XckqA*M=;@kJCtxERK%hgl--$D`;Ym0G`&fHQm=?l3ZI(PcJDsE_R5uZx>-2=$>1uXE zzn;}L-E{+EjGr5BAi2p;I6dFpH}v%AKBHbXU(bP%{2NWyLOs9 zoSJ+yJzlhH=hR1j?M?50$8T4rTU1zQpoj1N;QKy!+jo2fE76S_H&FlIGLkso9dGS^ zBVi6^-h5w_N68K${q7jZQg-LXo#=EjeK*>guO`ppZQb31&0+^7mD6TTZT9PBt9`iX z+(uzsa~dsLD$8#2=Kd};7khJAQz21wM8kN_tAz6Qj^&Ne6V8 zNH&1`X})SJxXrI~g(DNmc?ixdX1d0s6)4s`R%woSPKYdRnoQMin5qvs)=@WMoI|R9yWd=!E*+Vh=7@NxL*1zQ?`fk} zCK3-~2NDlvMO6|Sax2QXPgQiJH|U0k2~{kWf+ z&*KQYAg^G_WgD_GC7`3%t%*=1)Rk-2B<(OW4DJMATPa{$ofo#>8Nkvso$vD7RD#GR zr275977R;Z_!k2@6U9K;^?6xye1MJfy@Ak5^WjMtOhJIag@Q$xhxO5mLUxma`OiyR zLG$k}uws~uUYURWC4GgNhp#rT(h8K9S|_6yi2+LY%O6py%}haNX?}<_F*>!sGPy?F z@G{=vft#FbD#Pe>BDqL$oEe_amzMLn$@JdiZBJdKD;M*eNG{eu3<-~_aSdr%Hj_gb zd18pvC=jVRFCzUbh_f<^)`G%PLGm{N`5{DVet(GMsI+u0Dw$$*g)2r(jqnJ(AO znsAPXiW2bmiiuY>+2}||9>_>EqggVN66j)2-Oxm6Na(<*)R#fWgrseCXH+b>)zE_r zqIerNyVB75t!m+M*s!(#zp%#vHv+(~b^j&~))Fp;)grM58WBj0o<+*py&7m(H5AF8 zjIUJ&`7)%)203ej zI=r5}I6a|8d_=OQLmOhCRx!#wMnNZP2~q3jT1hmTJi4HJC^qMcA*;Do_;arCOGsnh zm9x<>#J(En^uu42+dYcWo*oVgklLn>xv7a5%f zqy8Bn7Sr|Np?4X2>yyt(p0Oh}B`?_)`n?yHk8u@%)ad>>Zc!0F&ccIn;TRCmCZj!Q zEW+o!(rXyhCZl)8`M*UMkTh;do^M~=Zwi|7FmCHt)<^qLf`mOC>>~0dt^CNlk+{8l z@2x2Rf9Bm*N7C-+)%-4+PrC=laC`PB^c|hRe`Cu%xoJbys`}O=(4JPf4XLT#ja8qX9;lW_5+s2bPvYsB?$yC<5 z??7(Sdc(Xt`B#-#RU$T^PkG`P0_5~>xQ;X*sl#=qks83ZJZaJ0iuh_O8l|n%9Kg{c%BUypp7GMGCfl^~OQgZhN6X=41a$8#`mHck{|Jh+gmHb)mg#X=T5Fzp&j!o|OF!Ae`0T(0s}i+wi5)3J;xj zuK<@+!DMu0{*{Qe6(~Dxcc|9DSp%GDyWJvl3*TN}{K#}pY({qw7G$nM1)1Mol6gSG z!`7w~_~laYCz8vc6#P*P3_f;0lQaeY{V-2?c~IOTdhj!E^qT72B}OR}oQz%y_Ob|Z zZEuY7RWh8JY)EI(a3Cmr9){NWUoo;I8?zyy8}`OBVQ-zGF%u1^PpN)tR&88t&2{wp zk{*J}a7(p7o|J|x)AqRG*ifMhtx#KqE(%;yc?HjL0kBa3uz}Q-(LD@6t2DS$ZO zm7#;EoQ=RF&npEA&YgZ+z)E@*x@N#yr;b=pQ&A`Vc_DzN&1-v0rlMXp8(PUQ^qgMB z?+gQ2*b6Xpez!#YPPNvJ+ja7^U2WyvY>802?T|!kQkT^5^EJbh(P{$??4zi&&wNlS zH8vNWo-b-?RvG`I0`pQZuhKq6Kt`!3>YE5d;~S$*?fYc3vS58yAoSu9=%pQ4-(zu) z$(%@z#OluklFO{j8PA%35GuOms9 z@Q7PU!S;sgNP`bOuuf-tT2m-%2yCyD)rQ&L5{Gpw>uiTnOHical{ks2CjEVw&gB?R zO@VY|L*uQS%pc z4Ai@m9_zkH@T6xScP71Nd#sgZcNiD)V%}}aX%;$?`?sx zpfVF9BzS&U90Q35uQsWzwB4*&88{fo&LlKvGiRV+3T##^3&{pCgOrA`KC*GMzIFZj z=yNu*ym9?G(PvR{P|V!^dd5ZoiSo03JGj?0+>`Ve>1K=>Wg;ZW4>&S{Xe$9)uLoAw zh14t4!*LH3od9&jv>-#iK&!%Jh77adhvuW7O=Y|8V9;lPjBv~B4hD|;HRf{yYWFNd zE%S_>tak?pL$)zZJ{8Wy?utzE52IH%aV=w07U37R(kCl9uVhV(Gbo^X#XO*uu2dN&-+|!jjy$}Dy0K7`GZ~RhNC^^+wQDU4mdno=vYAfl z)q%yreiNi{ju14|p88a%o~ZXmzo_?3r9TknS!HHvmS#!2X4!Pj5-JOtI~>?>`FY?bmhQhiea1HU-DVgpv7k$aC3&!uG=(7 z`_)T_|FhOXX7~ZfBTQlfdlmdJ6_$0Wx8ER9R=H8D`D@H5-PW(lt&(+wF;p-UtFUD% zIFAYStd4a6r>VPEC}mwEdB8>}Rx+4LW7ABxO*7MN7KTl?I#!$2v14~@Y$L5ox=Ht# zmNIfiK2YBq@Pj(!*tF3b`aJb~ijAQZ(s@X+uy~t};tfY}3^_B_l<9XE;ka=_O_i*i zaTD$ts$W<_7^g*^W!OiTHjoHGI?Jm?DB^#gu zNOK?7NGT%B>J#tXXVY>|K>xer-YJ@nAT%%7zZd1^n1$O>-<@> zo#cC2fOZ}YMKxTZBM&ofGtZ2Vs~u*%o{g2*RN?Iu&3zT5RICBGNRpyS4BM*m^sz8t zJ{m(w(=t!7kFL!#X^xB4?W}0YPiFY%J^G6R2dzi;W1b#W=j&0O9@Wbpv7YHYs;fu! zqx8s_gNe}bAVR@B6P@4%nn9GmM?ME=Uw!QM)!j&v9(0L#V@1uFOe98oil^a=G?r&h zKI2A6SBuQcJK!84hDau0ChwSm1u)@8_sf<$W2*u%NY6=! z<>v=~eg?TD^M)L&$e3coxbTvET9Zs<%H7@C{oi@mdhBNs!gY!;zk!DlGfF7v|AdGl zAbQAK6Qc*@irwa7kZ~|}x-5>uKG3u186)X2R{%zDhChq@3Zw(CrOFocgBEvTr<(uf zUH55yMiCBi_i8eBY>Ahl60=J2p%7A0x2<d$Fjzz?VUX-tYQouvI)F+CDbS9&P>!atAet)!e3T<1n#saHo zO|Lu@`t;blL~<;+iR3mJ$>Gphqvl9^E^n9Eo2c(%Qdaqb;JU5d`{6XW-a(;{>urhE z!U7CEL~C4Se1L{lChH9IEX0K#!%I?J?{3LS?>wxKm(~{+Bu%H|67}Txk4Ff3fL(Ln zG+;S!M4=9>7oymFCE7Ortcvabh>!YTHLfw?mAt%t=A~miH=-^Zxr$1;-3Su% zx=@^}8k(?mcVk^Z`(kHZ7WuiUSV%FpV%6ap6c;O53+!nj)u6oK@D<{et!364^~iPb z67fM zwX%a(cfy6tWmtYSPc)(I() zlhF#aHY*QbFGi#ErhhigO;KTDDK;gLCz_!qpO7UnXBdJ6B07fco^bg~_zui3?_%v5 zgO;?jxsXC-(9bFtbXbv6DTL|-4owvK3otxAJS6U-1r)d;)yw6IX3EHNlar;73mp^N@BcdKn$s2bO#e6%>a3%qC_6ECPJ;Kf9N7lNE2#3~_#O(IB9E)WK! z5DkMt{buPB1g63cO^c8Nb*=nVH3XTp#8k5-rmDO?6**mn066_wC#uTwo{KCCL077F ziqkBGTFr&F2)C;iug+gian@7qBi_^sKV{-btQT0PIhT&WH=(F~cg2ImaW4>>U#(K^ zbX(^J2@B)GuGIv=GMnJ{#;mb%yGYHPX;g@RLF38e?JdUyN!3J)?)C0p#!g8^1%j3$ z80W5%CM#T19Y%Yalt5PdtK5$>K=L`nV8bfMjAbw2m7LC(l?dWyxQPFzd@91IIWHiWnr1_}kPYL5;u66|vTkA_i(6tr{>iRMVKdJ~Y?jv|*e# zMA8oqk+f9Br>i^8Sw<<^8rWS=*_N1xi}lUNX8;_2=W!bV`_L{G$^idBGZpeX0f_#< zNyl+?^mw47C#Iw0wvrzUq)DWsOSr)S7iSe2B@%E;QE_>+2cb^{l5{^8Lu_be#0IU! zRB_YE?u zA%RF@#dNBQ1uiToRrthI_#zsrlK<%JV5s+;lk|7gESe!Lq8WB2geaN4ravK?L2h@s z)*OTm#>r?Ja0PBwF=!Gvkd)C3N}|h9{@%n~IRy7eNvN(T!I>nWUknpWS|)*WYuW`h zivi`*+!JP#`&yui~KP{85F{>Gxd;$ z1Brz);DXE_;uCKB{9kxJg9_xOY7_%TqLebFNE>--dCwSIf7bHeGLtp6_Gv{p1Iut? zd&|JWF1;koq3L6m>rF!w$i89Ti^=E_8`wMn4dV0)XjsC^sa2BXhlk1^E&$@X(72)| zsP=0yilRxHno>f;0D)H*HHQ*6X%DjcDR2|~4)?}0=_^qPjVkdGr)p-a5K;jH@R5Lq zI(w!QHh06zEBSY(XTtJc0witGrILSWCy6azufzEw^{7HL=@)Z3)$Z=F4pW$2_HywYyV^)I&+3Hj%axD6UO*+Gw?zC&_O$v@3cq z?1S)cO|({<#$8mZ2;<$fffxPOpL!c+GPCFL~gD0a4 z1tL+7SKiioXM(EM9$Fz^l8rHdWg$qASp z8t=p%atFbO%EH2;0L;_SOpU0}at%$*DHjYAP_)uJ&EN(@>G-RPVy!R@liLV(ilWqv z{O4~I!sSieO{n6h8S4;s;bz%(*VL2o&Ak=!KPh;kiV@HH^n~h}aP-Jl^qyl&C(ssaB6<}PEBOnxfj#^R$bvbuqHtj;5>dhC)>9^c-%I-wxOhX;H}qFz6F-Baz%sZV zzEg-Kr@shKfpXZMD-+P|Ih4vccp0LFDytv@k~}}SwjdGNL`4~C*lZ=1k65$1gIGo0y9x()D$~{ z3_1-RDVQGmi66VWB0Ei}rA2H5%Hv6IB|Se&wH-+uc`Od$u>>kWt%gR{NB_crvWa~OapWqfJ5;cN5@r*QBdEj9>3vT zl&^&TK9Te+f{ig0U9MT+fepUu4u)b-bnMJB;^a(`#315Cv#D7Xx=q{xj}yuI5CXF} z(^u9}FP{iF@Ho$vslex%p~8afdj;AIMyUX4pD(09iAq%WkfD+_-jNP))*Z?zvdkY8 z^eWw=HiNNG^k{7>17glon=g*4vM?1?MsPsta081xv70wdsC6m#mL&*AterPfwN?Z_^W|#s8BFIMc$k z9OR75F$N=w>I()PVWRo+_+g|7@x5s?DthvGuwSeYP4 zH9v*Y1=$#{g=`RKMmEO(eXhj3y**%{utanxBBXK)6d23DF8ZsZX|YGcLamF>n&WiV zXH-%%zJt;;Mi7lmhn`MfuvqE^<|AY|g;pJH$YK+uV8j?qJOa&!Hjz0SJ4n$$8H}g2 zEW+aOqHJl1akKctrEdR&+R{^uoUD+?QZrl?6B3#^5yTPDF$n~8jFUcuQ5P_o?24r} zKoi_FCu2)ECLa2PC>k~^fg*qOV|v0NjwCM2M4v&HL7&!nI(Sr(Sr)E|I$QuSp#@xE z4fFNJ1wbG`@z107463JRlFLWSBKIR%WX0qP4lmI9cLQ4AEnF1GE)bhAYnfa^N$cAj zK5gC%MKb`KXMi}0OH&__7zRDunFc-h>=8i^Uk>3H-=Tgyx}A;N7eMb{%0<&fkDOOhL|dPzJW} zn{ob+3M8j4+x$Ycy=US*Xyx}4Hs9+is*Ph%EV!-LvtYSiKiPmX-;(nAOm{#hs;!ds zf1XzAMXq34W>~gPAtPj^ zMHGAX9qgpA7~g$iT$aob_RSuf8`Zx$BtLA(PfLbaPUOfLr5w4{nq%pWrf!SrNn#t6 z=-{n!VcRPr3sfjJDY3r5`#UXIQv#&U=WZ%a7W^@|-7!A;EjQX*vZ?{5MY~9dKq2yS zn;02GjA#)~1z@^ezM(G#_PuL}=H6<7KaYmF^{-2vS)KP%=Gj%H}C8l%N=W( zQ*R}`YW1EC7Rg&;N~cVyXM;HwMsG&Qv;9?8RF#}eF>Vv4MG;s-_Jbhsyyy#v@BE8N z83e#iH3QQM;VtvF#5E5D{~;6f!8#=(=74^;*ON(4QxU%CuhIp+09g@6>DoYohc+zm zws0X9;47o+7s!^7PSCd1-j(Sii9Ayi9R6sQpIb~0J~x>Re9wz+kzM$fbhP(a+17j9 zl}qH;tGKxc*@p2>Mqk8@3eniMyaS0`J3d$jpF%7v&>ZM;BBw>Mo-u#0DyFGNSU4ny zYYsJ)H4FW&4l$n5q69HrpC#G_JglWHbA0&YKs=w!J}xXZ=_!J2u#KdAFFrUGuTbb$5_ncbbA|bWQ)LHeM=vOq+)Wl+sE&P-E z?6t{0kq@(|=@PNt%3>@in1AVJM6nk3=vTK-n$}p=Qx>8IRj>$jPSngIIWt!CfOW0n zfeU1U2R@>V2gtxWt@L;fevr|#mpa1)DZx6N;xv2r_MD~k z$I{|7p##GKmlbSF1!}Ua-DN42USnRmL~%sN5lFG!gIOTSIvuw|$Djugg$*mn?hg)ym=LSqhDvxedeAN&^Q$c^ zGq14pU?JBA(+J>zk?QQ@w+Qg!Dhy1u3)@>i&?fi3dA=sU-YAZ-nO3kTBUM=cG0utR zIAvl}B=cY+9N5Ay4%S6&XAs0HG8y5VXhPLTPB>Gs<2onW3vA1qFSf>N`51>MS#!7= zdBVG))%=%t&qSesF6i#t5B3fud2 zeiv`FCYI&7Dmu?H{{Vx|&FO>UM3db`)=#KyIM)~7O0KFh;B4|JrXnNNDgv9jso0P9 z(vNlQcF+h@KVV0Gt)*9KJBs#7n3-n-Bvh&#U|JxBtTPoH43I@2p+x9Ym2>0DUOv6? z*;zpRDaWT}QXNU)Pzd9z!OILiMu({Iu6c*Zv6%}0;>aN!Y<9MfcrEN)Y>Ros-q-Oy zfTPi|@Xv~(?8%})+9+CMBigVn41wX1HG1h9I9M@R=bDLS$Ci01c$5*o*W|B?GTYQ= zx4>nTej3nW#|NL4Ek`c`Y_=I zSOcyYBX&@R{1y+VT%%`Y(P!kjHo=)%rFq7417G{-62krMgq>+l*b$Sd*!dN4cL#3| zGX(IIkQDuSnK*cRVJ&iUl66^lr$UO6nUNaVFYmXYGG4C0jI#C+-*lRT2z6+WV~52x zgVh(8j*hBacBH(djIL2iLQOhbryr|MCSN+sdJ;f)@WA40sw^tbQT2+JJ+wf9D;5+l zLK1sG+fa`#pfidpgANlNIbx>) z878YXb@+wIR~}l#r;8pP27XYIxGOd#T1gZyk8n=h(WWXHmW7U~W+`SujiS=kf)()@ zRQ&wHwN{hcA+#^|W2&$i26nsl*zw#KBL#Wlj*V9zyuB1013t5!$P+ZFA&PZ8;CdTFWQr#1SnN#oz z9nq|tG0svdy<6FnzdDhwZWt!S;8j>QmW|)hX$;M6HmIzpnVgI=VFP#wa%C}YdZDFZ zg;jyyR_LTu1=5x{qyT<-8>nE}0EgfY^WzZXFdzU;$Dsl@94`zv9HjEWiF!=JI<_>2 zCy+CahbNF09b1SnFDDT97`Vn+aaPlCY0R{&=`F(v6kk5nL0VDDL;e!f$p= z4&k}DO_@8OVRIwOsUKU$s0vgfV~I))A!;ROVV1MGiY}d*)jM`}6O*Q|$H0)fV~7i% zr;U{?c*q%1wi^h;{Lpk1VxI?F9SaGK5dRR`X&4I~dAdSK+_kv&O{^N*7e*s6_Gk#b zHOWprzxpPrsMC7oa0J^t#Vebg-JXM{ZL>27rKOGlU}O4W9-dGs*DxOEb;1OuqKv2Z z;XCZgPOIqD4Ra;hF>x53c!Sj9VN@tQ40iM{;3uXBsyWS{L*+qXw7)*UE3HtY z(D(jWaW3R^Hl8bE$3drzUM6HT%K9Kf2B!sE0<`|XL=Y7`k5`iTIvKeuj9#qNCT`f6 za2!5jR0L)g1eR*jknsxfs*%%*k;cJ6UY*(!@~VcAx1)nuVB#G6L@>7yUlkE2uZog$qE87l zLVzUryDEY)JGU`X)Qo(^cq8k09_1@`cO<+%4Kfk{?xubjKxigeEG9+|O<7@F?0y%p z=>xzj=2@zQDa^R5Ee;ms;dLb|mXjtbQF7&OM@g`K zuY5D@fT`!TM?I^HYyelTx-7y0l{Q2<>G0YipKS{}>Do0owPT8Dp^@d7V$|XRx;NTM z4R$(_nlmMUc3ic9IigyqN#8%?D~Uy`?1HCNh|l&U+S&zRn)=bV_$F?=?Hpo+6zVHY zPS4?HxtJgVhWxAF(69c~Ye5R^nj9apVID27Omw0vY1-p6IF=Nbpz^@kqN8m3MNc@| zCm`!>2*P$`>6O0W2{)8>(Q-kQqF;GLJ-WbY5e)9TnC0%>&5%?w?MY7PR=$4^CyD;O zWC`9}IwpfAH7Bg{bXinVCGtIGQEQ=9?`=@)_mb8JlO*wN+HzPs=1X|X*zchYsTcS* z%%bc*FK{MO?fl;=CL`a<<5~fwLClV1ui7mubHAq2$Z+BWniL9mpPg0l0fT-W3?dPZ zojgRy0I^D$^`ecvE;j-YeD^~zi>_VW;s?UqqiyQ0BcGYW3mx7xnBl9{N z`_T~~zPFx_V7aI*{AL7gbaYB{Ca!PrtNR8&mVdyHs+C+98;y9^Fe3lTNekVFo~p82H%O@_%g$9)CBd!3Ju0#wb{J{`XH~}tlElu3G6d0 zkOLtE;$8!HcoxtWt3+BHFadkSjd(MrWKu(tV|qu%$q+Dte)E>B`9@wX)xNxBd9R5W zH`yC7G*if`j)R!)w^-tMqu(?F`bpM`&Y)7hZh}5q>?2n&h_sWjspzG^Mc`sa4VzKj zfa9d_C+Aoc+|3Sjjj~^}Yr}--y57MC4nN~5=O=@)!hF+Jc`T`DgqGRBAVgjv#LxGA zeg+Yj@A^VzZ>4E}em0(CbYj4a7#YOSTuK}5#_)YH@BtLp(PbSHdp+-nju%*V)ZA3Ss{q5myvoV$>e=z zN~O}!vtm!_VdGM}UYRWN+fBr!nh1fWq{3HY6S5#g1U!hdYxg;{no2ak$^rAT_&e4- zh(;5GNVeif1B3IJ@ET$fK@4%nrX+((KB8P5r~CKl{t?e)LPf z_ibAuv0Q^uSn=b#g3W@{kOhC^XfgnH7+SC5I+}|)gFmZ8c2(Q0@l_l;Uh!k^v~3qr zmVcM$7|(}!YF*(QJYjM9{XE+uGrR{3udBJK=wT4T6b3>{R0IjX4CNY10n>|x6z^+S z3k@iWj}vX;7-13JgPtpRV<%apxlQx5%{z&#oBS(p6XIqIvQyYXtrRo?E<_oWW%FOG zYA30IZbD>|)v{~VVRP77hq3SIuzfabq@!6h#3T*FCdg_Dd5`1Fg+c{iNb)C88jPGl zYHecaFIk)JQlFQsO|J8BFl8a*0Q2Nq(AXpt0)oE5M|qvp;zVf2xamNU@jQx7`UoP) zCc`^E4fmLz<=sOm0rue@wm~TzqkjeLO72ZPhX=n^8y8&`L1MrbRt;#H4or? zv1&Ha|(&MU?1y3D^PDI0)9{_r7dbbuLr1~kjE)Ed2pQSPmw%x}b8bBbJHV`IT=J9&* zr;t_}^dn)=4=2`@W56K{Z8jW=0d}56rw$GcByg}|AVf@hV8zfto=w!})9xOULL}%G z$}A$BgAJp|^#LJQZGc?1^l}WEo>QHvCIRLH2~*+`cxT9PT;N8RaKI3MUAn7H>a0@ySTS#KtMiEf; zdqUO5QK|5_V289Yl2sU{NOsSFWcNUYw`xQjIOOUxPvZ#%$r@)E#?)CPS77Qe$*Dmb z2ND|`Z=jubEr1s7v-8YZnEy2DGuiJXd<&wIc$cGbr{jW)sucyW!TvY8b;PM_dC7x}^gPsh0DaT=O$$z70U_$gv1FKCq1Q~K72szpSHF9uc ziIBR|=lB3G^oE@rPTFUlI3xLEkzMDXaS?IB+>SDk<795L#!)ag zOAh)(YBBZFv5{f|+ay3lInmF4(2`)U6u0&m|u0`FNTj;Du& zr|C(-QzZYAspO6QC3j#k<&l1DGL?8#_Whc4$p0AG_gjU!ici^j4IUs~PM-7|XvBnb zc@tbGc~YAg;b$kp9!t`6xzy>}0VYRe9Ud=hl!0F`5-As-5rB!>(>lM89@AT+w~fTo z2bLXw8>JkrUTN0P42oAnS;@4 zO*#})y0N26?v78~xhKx`jIXMTos$Far_eJ>c+YD}ZtN^M?tu*CHV4$5r~&ZT;)mbA z?VXe!7n>gtNk#w{Go=;Q%L*y}ZkYk~lLKyB2|K#F`(8D5Qq?*&I4}du2SX^fSp(k( za?h&OZx*4}vjcyzb=7WZXzKvP<3xA-rUm>zAfTKi_M&Q!p;muLP04XIs&WNBc_M6} z9EWq4KQUaQ0oDMAoV6#RW_j06!pzR<@3Ck0By{`Z1;poP=o>qj2*-;%rTDAlpEh`( z9NJa2$H1MFPrKzf9C!X)V`{cm=DO7@! z5BG8$4)>BN%xem7)MaDG3#rl;Cg9U%_2fY6DTMj4A+@o?Lg8@^L`>R^aE>UIpLsHh z*rqL07!+kc_lXMluNMYS;iwETC@1Oy%^!7*;b=Z98Pmrw#gC|TD_b&aaqV%t&PTQ< zoY0mwYudV@Hx}1;g!NJ_uGwepkjQLt4Rb%N)E0|td^>J?-pbs1bQd;W#yNi1-f>vD zem<0Iou3p8hAcu`&SFi%e9A#beoz+q$RghZq>L`|eagHZgpslaFOJIBheOigfm0OQ zKL4DxXxQY+el|zfKWss&Ba4xP6-dIO=ZxMw)f$j_vh%PhOa9hE+1K?Fs2<0v_>p-k zex{~x-`rC3u8rO*bhEWlyx>nY>vP6{!;AIbqt7}HkvU~Jn#Ua$M_IMmH_xh#ok%Gs zM^R>$XNEF!+OcyOjuRs}4tKO~Poo_>Gxs?AhQX2KGn#vOiR3hTSSwokIfD^wM{tU0 zeIsa^X*Z;iEz8k771dOPIcRI7ZzuHv+w!W1In-FS31JR{QvFW;Z{TuR#p)>L=v01d>2!oBF`k;`q*?su6$I5cH+&pB)ES3S2V!)L4OPYV=Z4hqS{hHk@ z>PmH*&nPibVkzy?{9{V2E3p(%Y5plCwn+qf^BTjmW@|owomD4y9qUE|HyyDM$G2kY zZaTY6{WyJZRz@9Q2rxyaey=TNnz}nrx@k!Bq#Lm2NiPRDTEBse1v13L2@Tbb?z)$5 zAH~gh_dFD6W#;MPe{k2?Zsm;aIJ$0S{=e_KMV}le)G2d9Gi(UxD%3Cz4K26_9P${s zG`_&uj4i8gzY8FdmpHWZv zRYvBJHi#v%ez66~Licllx?noL&uTr&r#6byIMU`1wU=2_BXV|l@E#XDn zow9qN9;x#buz;z#1wx>nJ}6LcRx?xo{OS9?{;qF*@q_De|Em(KBO;3<)s#3YvTFI870{`i0jvgk?SFFJ zbY5gDK1XdD%-gF3$!UekJub)CwGO-_+-489UDH~ zi3L9BR0A#_F$LDn$_g4YF|)HeHPBx(kJmK$Ixcn5u5XGxoQLx=2^wAM>UTrIiX7d^ zKnea20i${&V3dc^<-8|iSimSbMgm4rlAb24?k!F{I)SbENBA8yC@U1uv8c@mR3C(nvuY~{)INuR zJ=p|O>nSfJ7uyI@fN9L0Ou$nhA*k0BS`qgh|6%Y z@&FP?l8VLX7z+?cnajr$_Ji~0t@^>&!I676w$ZzV;xIx@o%F+Ev1|zK$fi$(?vCPJ zM_cQIltqD{t$UI(mZzg*H#tVhkJON-SVwALs$$ajQboK9s}|@H2DzXZB!{kQ!1Bn{5gV7_OPj!}^K zurN#Elwi#~Xl>z?La0%+K6-&UbP!{N5{g|Qa4LDF3M=y<@WUVb^S9NsE!Q%9U#PE8 z`VL5E5Uol!87)5wI7UtRN0j1|78zbzQ2958aFmcdTNw~?#e=|Crb`AF`=EFZ1-^=S zd>0hyEJHHhacoWPRPeJcbymeuxg(j&Jq3X~+HDA|BF`?AYcRn~Df#@xi~*iXpKZJY z*F|xDyX9m%X2_s$KwKV9=!xk)kKv1MN_gh~$2qwwN zHYg;aaKGhTJ$KkAIW9||EZp)=bPgG{mlj{_{d71ZPlRD2PEG`xr zE}?F0Qnv1#Ug)+d)un>dI&{u9Q)o_YH-&Ag7;Msrp&+IA5Yar@9K4m&9uy4DHIl|c z+VG);TFGwOX)I0OtLHs$vOeP-XO|QE_SB<`DG2~=%Zn~{w`~^^k<{%BqWgR`)umQ7 zYCCD3vQ~O;wQ76RLJfuv&a-u<=`ApPuN$eZK9=)m-CV40X4&vg7|C=JY!2v~i4%dt z_V+e(;vm?s_ZQ{8(wv`GU7>v}Y%LTUW@v#Y5%B^$F)h)fOW9yjXi;Bb zgM?U(AH#Nhm04^lb%81tlX8O9*nBg~H4&ipG4jWDAo(QQmdr|@8lrag^Hl**}wZ=JUd$+ZKtqy18S1Md=bE*x9fNd1|X_#kU%w-kf7``Td(td#*~g% zL$p(D{F_j zfETV$Yg&qf6QyF)>gVFP8E_=k$A)ed1O>EBseKi@?I9mXkq3G<jEIF&-o0?cWO$xtd5G3#(Z~lf8bOLrw!x(Ji!FBk+L6d4NBtTfY*=)jVQI58o1k=R!|qt!;WM4s zWg=RT9sFaBoSnekena~zns>07lzkQ4##dA5L=`Jt=QCFPPOW?xW5FUik=p3g#*Tb9f^@Y! z@5yG!LS8~hcxb{Q;-_nB0Q&76GLB8si|lr42^Yz(P%KQR%4s^wR3^SeIj*ABiB>VI zyDf*2&X6dxrbSk()?q0idsbyMxY$`#fLtvlVMkHcP`qCw#QX7M)AC|eHhiisD+;y0 z%m(K1cs?>64@X-LfXmC#T0RCl=!VCb3GvyyI+0wbr?6g3p5NzL30v$B6%z?T=>Vb^ zDd+H3sG@IWR89#I=G}oe1Sjv_vF7J&;R~=n5oBR%tf$bwx@jl6s1F5|XZ|@*xtf^= zLB9VEFi5rU;Pk`C3V0fjOMVvMV;NT&fJt~Z*TQIBw> z-oPprCm)Aix1hu`Y?#L9`lG|N3mr~EJ$A7IRUaL3Op+Wkhml7? zEuy&r?Sunseqf4Z_Z$=1D334y0jjK$fi&^6aa`5m-L6ziXYuLM+JOKhMp!X!%+@=E z*1{OkBxP0ijq6Q}mB-?qrJMIyGcB^*$f+RP&EO?P(gK!c3JscAETt1JOlo31$zo22 zwsXXf)s<+_m8k3rOvAfE*VPrnG+xx1Amya|&DZ3EaoMIUG;tyq?k)kG`$?CkVTPo0 z>v8bKYFZvsLBZaZ(i92av>c}VwhNjW9Ze_bns$eS&i9#Ji9yIr6^UL7k5oqWD-+HU z6m!!QxD*Lqg2;-gN}J4IN_;Xo2Wy_uRF5*6YB?Ia(MKJ?d#lm(IO)a5VnPW3#CBqv zdMwFm8UQj%b9j_d4l1{^EWN{FoH|lGMIqu3v0jNCTEj8*D-%w^=jeLmULWuD;D9Cj zcD8JJMbih-u2b{vphE+XB={WI*fCR!1VGM9l&QG?on|VwE*P~x1>d-PBPKM+W4DsE zfki;G3cxTF9~hZhov~xho2pystu^!2_=#8*nwDcVmsV<7fuuJC6it2Rw?Fdh-+agW z9=L0VodY-ZNBjQv%fJ8oxBuG}9Owi6kmHs={KUKV|IIhQ$r*AOxl?cd_MW#r_O+k? z+)pTjRq=^;?%V(I_kZGBU(k_cj3#f;TH|D4A^_%(pU(NM4<^G$e1V0!%`wiWW3FrfgaA(K zTGL%_Nz!w=qDzR;{PQ2ZmQ35%M+6n}t9^ZbSijhN&ac|_<>yZBS4R$&-~fx!JY^L{ zo!lizj#IAB>(>&frD}6KB1McIm%ezP{O*t+PBA6+U|swQPB9xFJIgtc&@VF_YTT=) zgEL-l(&{lVtI}%fr*`NOl>woJgUSUms+4Za2c38X2uZ=6Aqo?!0nO)+qL_*s1XXN) zzlqSC(JG@PE)-y6EIXmqEhX6qLnlDl&;vQE=m9NHmGzu(6?eweAwFbsCds1bOK)Cd9F#X$?&S>gtx+4M5N78#?ym6pbh) zz2y@U<%cg~K)`Y|jpjT}Pq|ZDM}l>{@2u0nt0y@cHq}0o`3)H0g1QaGn98_atfzI7t zL{c&;jTk-}j%VWLCJ0hkmHG(uTY_Zxx`^2BakyFYgE}zZIgq$^&Iosq44>^7j)HvR zI8StB)kz_N^Bw4Ts(O3dFNjAoXAo9Kwy0&v2Zxf+7$ooAJIL+HulfP%PO_J*TPRR1 zI@^5#|Cbe}_jCQ-z{^h%mJopR0_vg8?J^a;A-m>SwEnj`3Wjjl?g>0XH)9*h1uXq0aN3K`4kNxQ{ z|Jhx8X1@9xJ1pM-Vjz36K`fzy4;lw43*Z9>X+>C1jiU}yp>|(bHOE0(F$bw2mb#X* zqg1*4+(51ZO1n=EZX3y0uv{rb*KyqW3cp{3Ymwh&LR$pCCP4+rKo72%6P|PXi$tj* zI3Y*+ji|=@Ff0$hE7p3JwD!ww?<%jTS}ccy3!jWhVsQlLFjKo?tIa?tDz~qVxcp7k zax#>^XvQt#Dxry?O5Cq^AG0-)L>|B?UAdFT z!~-PavX}9A_`RCim+|%$o|64KZgfp@xxMMyopUp-#Iugb6+Rt!9D`CztMq5}KuKE) zfZY~2qZgA)%>`r#P3_uOH1)(U>+SN{6|?FGAO=--ImAgkM>e3aY&(i~;F-w>iQke7A&*j0%%jfZMfV^l> zTM0A4Q?&_nn=B{Z&;oX6^Tr}|t}CMs{K|}|LO}eBM2FU+%3$nNfmE#@VUI~F;-nMd zw--hcw@D{04SDu>En2_g;=%29HUOI<%>xrA`1XZ+=WewL?taVQ*S!hFfK-nxfbOV6 zBuDv`$+=p3o)2616)02Sin=`n@a}|&aQc= zRt70lNVLJQufh7kpu&p=3p*AS#e>6YrRBh87zvgJz=ON#CyI(`l);I>v77jhQ6upw zP>zKsRiCbk;{2mK{ws=6{s^d<|4_acKG6L$1Z4&Zs?Ie33tQ0F0DB~BTs`Pf0aWrP zL}aWoQD7;$lx$Ebg?;SF>++?qrWmZ}y7{*0LWe zr*FXMsWY&@l3_t1vza^z!yMjv$y{%u51Ml6R$8G^QsD1OV+8rf-$&C+^TRy4e*@;^ zdv7CQEzNTJ_=9@i4{mk8rcZD*VYM%YH`MXN$bkHe{C*yWcdh^I>TfuV+L^sP0$@I% z;DJY=E8Yl=nF5WJTnH8lI3fU)@_$Q)Qz$l13P(@>P8pHm{x0f6vglm$fEI*F5kaSu zA$e{@{z&?JHtK<~i<>if}Zd)%V3_N+lb>SGxx!EiNL^oim0D zqRIO?b{3-Dq|mF#jx-0{>PJzG`zn;hud1afYCJmY8QH^`L7q?=^#JDq42t)r-F080HmsboFjl-8Lpx#+^!5& zf%{>k3Y(SrA;}7MfFvu>$A+X$uI$GAPIWe6y32W^5+;UjFnx$fCoU#J$Z?F^VQOZc z%{jYv{nZSt6Q#O)i#&VWA<<`EyMHQ1q|mq6Uw0}p*k84&pxS-bVD5NB;~e=78tR|q zFt@TU%yq3e74D;!G-nS_Ka#%7GFPf_YJDNiv}vWuKX_}hHr+!fxP->8rL|jT8LQno zdz#W`lAaw;Yq!#p&Mmb|sfiKrm#%lu&^?_8DaHk6XWpHZ4c4y@sidR4h~I3MmA)mML_D6d+cl2m%q%O|nCD(d7C@&9$;^iwbq^S@&NZhp$1(%Ub^VGi z>xtpkv||^3p&=#Q#9@Mfo5`w6VWwtvCH5s49iJ{#x<&CRhasle1Fo z`9%_3MrK+uiX&XYuOX|cB=4|qAqCb`Q7~gxiPp%+`A!8rWg+m}8xUPo#NrtRS1Z#5 zx83OMPM+L1%-z{W40iKYVq@^2A7}ElVQy_20^t=bc5~*iC_m;VF|E)5{$!rJrCdWYAeGB5E=04C;?>h)kO%TKqeuDeu!Gb zFCh`+QRLPD4#sO?9Hdk2(;y5D-3S9Je_Dh=0SE&f%*MZo2eE}0^S}anktBjvMM`Y3 z>p%kXA`U>9Wnk?0&;g~1=te!mR*4qJAasNR(-u04>Xs_G9|{~Luh)Lz>(|#1-oWO`tsge5eH_ck^Q!miZn|C)M2&HZ6pV6 zqi{W+zVX@?n4pc}^nSbEl@GG!DZ15DbjwrHHvgxaX<@<#p}UF})9WS9Y9xR2j*WjM z4QbpGX8c7JC@5FLU!{jqICnk2lamTfLgk0}HC_ICh+l`^QhehK0Mj|C;Bj7ccMq?e z!Nish*|6@n1!}2r1?iBJ3Ze)68u&0nbs6{*CZT3oMub8LQ7As z+~Ll&G;4ETlb)xoP4CsS#bt1Mj@ix~gA+0S1}D=ym^jIf{1KiBdhub2PxELVK&z-= zj`YSXrl}_-@v+EVZ@i*cU&Z4v)3^rTktN(2^L)Zm_Vdb*{n~|d4KGqfht7Yj3WOduyNk|M* zQNEWSa&o4Z&@%ielHY`TdB|bc)5y=(B=t7vqMY@%=a^@OOIVe80|k8Uwv=KF zG)U3tqBYU)stq3}&B!LtA1DhSvv)Hp>Aiz=4LSJB)qcekXdJhp3SQ-06?)>%DjVo< zjypkn6l=P$FBWigrJfP1LEQG28i1(HNQ5jxr2NS@&EU-DhuwYzK(4?6;)(~<#B@X5d%a!u%dNUXEH-JvyLntEmqCZ2wQX23j1mSRf18D>@%A!lLl$Pj z7e-;L#=00Wzg0M1p!6Po7)tL4UM7x`?#DqlaRq9Wtal{gY;fKH%zx5gma+olRT3BM zAihvt@m}nY%Wm8WCWU^mYJ`V5a;l^vVR_7cE;GUCC%TiA4-W+~O5I*_uYtc#&)^Z*&ZTBO}nQ zvU=4BVr;}>HkmtW8oH{=dpc3hC=(bjKF6A60^z2wrvpejCv}*X*!Fci@S(%Uq*z_W zqjrogq7uP2fin+}G3SK{D0C8!_p9^GqRnGwEri^Yq;*`wkeCklMgr^6u7tNLXF0Tr z>c6%7q$pSQlk>^NR)7i1D2C1wtwi!cQ5WI|xa0Z5fD><&Yy!^weH^4NQsAY^Ig}!o zv+KG=9=v>{P?i)``=j# zDU{!RoVf3}F9Au}5v!2=-5Xr}xYs16hEQ1XvsLv%%s(=e$Hlyc5K2i3pQy?%t@r8J zvW%VJyamjKM>A?xbU{QfG7>Jf9*F3}41x?1Xo6slZI{Kkh}7=7%yg~ym}MLA7xOOo zvV%MX%FJBsbe<~DhbP<+KD^$F7YZNpp}Sh**OfT=;>`=e-`ht`^xH7e4;jxycOv3s zz6`0&Ho`9d-c2)6{xy1{Ip?xEbXh54=$g`!eInf1QK&;y9&9H;>|-%7Fb%xBmgkIZ z>|-wy?L4dXARUIf6LO~#oEDd{eSj9Pn++H&EJ8f zzo_tZF$r`U&2q@ob->aNk~ZS$I)FFl0n_5S`p`UDk0z*R(8iIZce)URJA!ajO(ahw zTp>#OblO`^?TnPa4ADJZdy@X*!qbJ04MM}z3N(=eOY^SOFEx>D`Dp}h&$ zP|?y%Y^wT05Em7cP^ac#Fe~V;wMYwjbyT`a%mSeZl{D5_j=CIL$4^y3(>4urDVSDih zXM4pSlx~U$7RZh@^KMRR%iU226T~CqrB1m23z9>)PTR{-Ds>p3%dEnka82ofNZ1vi zLf4iaL8UPA3MWvQJ5a{Dy93)J;~p=^J+MNPUyzkPcrA{6!t#SN;+!fCCNlo;~m$ zGu@va?-5-Z@99(3)8jo9f%iOv{${lCu-pu&yNC5e2|g4SjZqOwT)l9J1l*%|5i{o= zMYK_IEcw6r6c8P|xWMz=@q9z5LLNmSI(Y-h3=o}pR)}{|a-HFM=}~;4@Lbmr8p}Eg zo-;oMs+4#RdJjg5>1`v$p=2X9WKN#SsVFKCPc2-6#^nm6#>XAc_9~9J)y=D@Dr6QH zRRI101(;irdC4o=3*-|e2;;(?T*g7~bl?pi@B2{;gKLn`e zj2VkCjG61&%~_24p7yPKm8fffbQc1zJK;-9q3(L3#2`z9j5mw%V%F3_usA z9(+k$dYCI;MVVyg!s7|pq$WK>Kh2$w9PnnG>9{)|WdJ7a-ej1wR61eIL4T#NCXLN6 z!cD_j(UGE^Ie6uVJSIh$ELm5iDg+uyip8S2T!f~hg&Ng#2(sM`(-fXP6m%*vv1!>C zM)0UdTIYau-y-Jv7CYR`9PA)}Aq{jtB^aQM*vB9+jj|xHpm;fffrLh2hZ0+eB9n`B zsRmd&0-?#G$tX4^NfO>{4I&Vl;!BL!m=oBa_=8b_Sa21OxSlm=#hykdUtuw1w5#W< zrP+91QVJB31`0{%QV1pI5(J%?j2=uz*p0{%7Jxq4+9;?F>7%{?P^f}oMoPFaAJx02 zo{VqKYCN8NvVQYa%3V|0n|G3H=^c2ijp~=JWhQBcnKiMu2QK&rH-jE4yt!_F zh@gk{SEMLFGuH;I2vXON(WGq5R(2}$cbXf*lavT61d_-77BX0TOA!DXg-ineVow#q z+4Kz{FqCo|9CEN+u$Nzh<$~S(8Y~xJ$rhcxhgU_bQ2X7y;^EipoxI|_*Xw>>Ee;YhjpzoJDO{A zDfBLN*u0sxzoBkhBk<9$lr!@Zf|V?M!X2Q6OSf;3izxOeHyYr#X1G$m(dWf^5ELuZ z%a}*=-a42gfdr##X|{AKzM;SDr!4FSb-ypY7Dpik1v>s=;sWAJBp(nuicX5m*~1w* zgDjWGJB9aBF$*|s6rV;DNT44{&Le+0C{8uwXW^W+@j$aA?StJ#iWFH{xKZC@DY#0@ zly_RnH-=K)YAIhEq{x%{(zVe|RH>~+jL>}8_)>YZlOf<4;-To>i07kC1hLhETwM=N z^YKdf%TkI>i=F#d={J2^remKM<@=UR->pUu%tI8B_V<>y$J0)?G=X(j++V&nx|9qu zWS9@-MZ@gJ!b5%tO*fDx=*2j@6Mjr2mt_^XvCO>=02$6ZQhv(?%^%!wL-UfnNIL-| zQJIy!P-DU64#~SuY-yNODPaV^tCFGO>FTw~3W_tWnQF25JcBnTcz~+_rD5jK(yi`u z57R1e{Krh|3M@aLR-{)XjbF|Q!2|}1i-9EKHq6U_iM9q$Ib#S5bi4c=7_Y&ZjsuI> z#X4SN!yD$+&PgC%Hh7wh9>Z3p3hG7>&o9?nK!s^A^w1y6*nPQ_VjY#(S0Lm4T4f|$ zu}S?+kO^1A@VCb(&y#%{6TX}k=a1Rm7V6W5@vJ_uD3CSm?F>AlY@EtdbovZqeppy4{?X#^X z?F+A$*+tfL+X-6zxq_GyzDjozZ4*=D3new7rs24C(%$GKFjFV7C@8xE>@UYcq^>@b z+#bm$LhcKLA?1F(WI?c0W-`iQMg68yjcDa2iE@}xe~dH@#)i9+0g4x2B5L0k<3D6E zG*s1O4)MH4tj95G9^%Lj5QyJKK8t_NGe+w?V6e`|KE{Cp^x%7Df?o?RcC0m-SOAe5 zz6O`5fvjK)NgOKcZulBlgV-B#!zbkSOiyD?CWKQ@*_v9-*VJnI#X-75Yic!HQ+ulQ zqaSczv0CjYr(y*Ohb}aN7QF{^7(u@uZ1yn0Vl7A<&b=E&3K*lDcrc0-Sbx?ke`y)c zJsjb?bZ~h%rj6RrGbWO~FfMbh!nF>QB7P07LjP#7m*%(3%tlS5tNHaXzH^Tk-m9mGUj2> zN7!jXh`Pd{6Dfs?ozNmuVz!noA|?3@bX1$Wax{m1P2T_Ac6=cI3S4KIK+wP^WFQL`)<9ek|o=31eLn7ZK!h~YORX)s{$ZN`tv%- zG&&rHhoxJOO4%dm(i8~O{Jz0$wmo{Z7YtTH{B*jLuT|a!scfEM)({=ujTVWGiYh4 zRy-$rC*Q&?_3jocUus`D9zol4t-;tdU}vV=GkV%In1#7dUC|7hfsD!)Z3ZLM zKB#s+JTko*)SP(E&mbP#S%Oq`tRL4`i?3Q}#VJb~dyv4^+=;d|q zg|}S42+n(zOAJ&avPEKDoV^MUEqF>#615QBFtS&<*~uoJ8sO1U(J*``h4opTvY**b z7N#QX`c(z{;s$9|VH`z7WRHKyETsuSjfZo|B5nEBQM|leU26{5*gBDA>gKqu-8NlmR+}9RzJ~JBrI;ua4pg^H8 zt)kUGw*Vsg=k^|)f!!bO!&U+ZCZI5!cDusAQ6;QMlr-Z>@ESUuk&Y#OI;}&>m#fGQ z7z9aO)oo^B&MJ{pTSNL`;Ynk!Wa;QXCuMZ271uWN6)i0i5=FJGvV(KyI1^r+y-cc% zt(R8JRvw5051;?}MK%M-1Uo>JkzLEIS}MI=yFEG{FA(YHL|n<5Ek53X#iVJuodtk0 zoZtyqhV8hpl66TwCjmRru&YD^hZ#pO6xpf`XrvX|zXNhIk+9%Ie6tl2t6}e0_?EFy z2WULFHDyQk?|6VLuj0W4vaIXWO%|Yt315GrV#oF8Lavo1BXRgjkR-Dc<{lg3qdX@f zR=ed#W*7~|+O~ePX_)4?F^~~`Xco$sX+Ybn>05?fF{Y>%;>V*IyF?QyMuhqY=yzgEM~uT}f`;h&vWX$NhU2JjB3 zlYj61-9WrG^#(V>z4?#`k5XJaSpQ`RSn_L0RG*6uYrJNEL~-;(zMEm z<<05nv#$V2WEsaRKjajo=My*Xf$&GIBXdoIM4>T3;tE-kCAW=aiRn+GM`0h71ttnc zu8+AgvQYC-vYcDIYD^aa8T;32>P%SKv`ErGO7z^c7OG)3iKS1%!Gg-AiQ868(iDA5 zpa|+1Iu1qE(&1bkW49!BtXG(z5p2xxOg5EhV#>`dBKa(kIS3DXTv3WK znxj*V;S|@#;b;IKFf0#uSq_K8oxbibbf!gS)HU_SW4*Krajk1JIkwi_l37HBNriCB z!PLKZ7ySNrh=O!SCvPt~#7h?~BU@2qB;YvGM#~~ByZtvnlwpJaC_>+Vv;uZ)`x%No zTv))K=(W;Rt76x#`S5dB2aXx%yiQz!@8Xp+WspZ=YgZ~WF<+HeY>9Q!xwnZYE(0Mf)xX3jDTOss}+ULuzq32|VUm zL$uMz6v3|5|L=w+YLD%T!jil6zL*cMS?FF)3Q@pXS6(fvF72QJsOPd!7U67k!G)H9 z0tg6!q{%B`lB!K43X;P`l3}maq5LvY^=#puU{?s7__8qPNQ9*N?R<2<7!ra z$P!)+ILd2&?rQ2_>0vVgcnwzyn*usr^xuLaj!Qs%Ji;6iL6;>s8w5(sXS%|YhrCXK zVL}j}D0bFW0@Ptk*CBul6!9H!^JuXKbo`JBVb0sBLA}2fRk+ffW8u~GkkdDsWx1-b z1F2tT@6TsCtf9wRLeEsksc@oMA)QsdkHE+BnF)AhII*PDS2*K9t)mpGR8?NS#l!1G zITtH66p!%G&|tm0HKnReo9XN@Hu{HrEFn0d313w=C>LH8t~H$ABCHv;HONh#REC=! zu_+0Q1rMI#;!a!DA-mq_CBDTQpTV>muh7)S+kbWYtUy(}uDvFb*KCl@oa z*Z)}?lW#`#-qy-z6fo;(<}m|V9ZV0<^+7iDbAtdw*RE`*+*$0bCWmW)aeFXTbqCyX zIGEH*xtf5jmOH@{g4=XGWu6mKU2)qokQZHP!3^y!Sd3O*491b)PG@f}cLuSHu6c1y zv4Y#LGbh8f*n9GdFJ`89=CJN-`dLBo7(o4ogp3R~pKEp9Nh_@Q%Z{z66gX7 zlPnVKX9<6CZNQH`d={KJ5BPxcGJ4351bR>eg=3V~MIe?=mdLxrhu@vE52+r$lAHw()CI#lc-zvIQGM;xCI6V;yOB9CBhKv6_OhS5xyg zI0PY;pz%*{QqH5ff*2K8AS}Ad|1?z2Sr_xH=Q($s)b4$ z=VtZnApm~1H^p4WiF|FDTMTW{{3u8s6Fv0#W`GtakEN{d>wB;^p|cF4ptGXHd3>bp z&>XrbxcVafl3FWV)=y#PMW8UU0y~3nQ^W{|S9Co(XGY$oEpk*+#LbrX0IE_TZ;=3G zI03!ObyUV;>VWL@faPFdRiET@sFP?Z`mqA^Pz^>zaS>#$m`Gg-D>fEIvesc~aaqjKMy7jTIC`7BFtjYvuu zUwy`T@Fwl4H%Xp3SWVVDi(mHH$X~yKlY;D7RMYy4(SikxdIt4bk@K?*9XRa&=%RVX za0&Of3T|DnZ%qC5W$(SYk7T=HUvl&)c8*$P#?pn}1Z}z-Zo8gZ#B1~LX}xaJD|48K zZ|LA5&Q{gVL?J z{!4KZZ_Ty#0ZoY)>16_=fGIHQxzhb}RzDd75LtfzoY-Xl7s`_?B<_az$g^}I0VB;x z{N=p&Yb|UG=+`@$P?;^Xmf!@xC>XsOel^tO6AAs%cPuJom1jjN2e!c>B-ri->B-}DP?7)nNt+m_{tQOge7Xu*@Yeu(dblPH=W9s-tx(4Xg8*v^i|I$UH`;C~W zRiEDFe1k1}TdZ=MRq5Q@B@zlklr}j702St%v7+gyl9~~trAsXB2I~-8WatlZW7RWT zZoqd6wkyuj2-^{`LH_q0cght}Rqu>SvdEL^0a z@IyGK>ak57xOp+N%M$<1u5hn@-wd9(M+OE8^ELdXe0j)kAnA7w5ze)6PKt{mGBw@o z2Y%qtD6K6=j=>~sF2NR)>0tpZin1SkP?rt4A~IAa&%%R3wT~d>Q4F~ZFb_)ay*URg zqkqQ22>@7>t(Fm!*;3*Ti@eYjrry;FMlazJ0bp?lWZAc+k(F98gDrSt4VuDKh3bua zBF{$#GF7~CGrGYATp#8(CenubknW3$RirC$dd;$a*V3t%{fT;F0JN~(R<{MCt+M`& zAc3l~$bs zOArztrx(L9_88>BXOt;*#x5H%ZRHL+#1!&p`x1ldHbn>#Jq}Tf=>WekVc$-_&C&V- zxQ6U~xDiu@rL&sWXVaZ8a5tRud_=bO11M6+T*Lo*$-TWUs!)uQI&O6`u{)Gp3E5lG3K7GUMKK7{W$$KLw+-~P~FzUR-s$a}2iO`o{q#(N)p2j`UYRjimzqiCsiEp}2xchpnw5dv6g?WEFXc=Xnj&h72U>-UTyq z{1y{rs`*pC??Q-$;|Giije+TtO`aM29=`A5G@*Lv$w2kBxi3B7P29YUD7C4>eypiG z>75$)$L1#DBw}M$pk#!x;>+s-4YyY5*F~mqEW1>L$(=A6+S|80ey(|TeJXdtP2`KL z7?vEk6Q6PlzCW*>cVJ6JJUEDD)_uOLmQV9RB`q4UYfp(ut{9D2+JYSJz!EGFFXH5T ztvkTx6*dgle3>IbaouxLnvfFAG82>WmmmTA)2ZFeeE0>SlrUa|yzU{Vy~GU`Vi--4 zQCdJAt6BBA>)a}li_Hm7f#D3xPa3(H1$Fj77Uo{^^3E{a&)nc zl~EYzbz%{QKA;#y-L=qaF}Ff!B%V!WTqZg;?+0+ z9<8M;qI9iG4_>^r${iWi@BrW``f4s;PD)5Kbvu_V(JiyyT00vw^Hdc3g_BZl0Vg0a zq`U+gx&Re!rEYVna-$45M%EOBLK7=8vctg$MSh+&=t^diZXPh$`t4wFTM%az(6Y!C z?B!vu>u}H>EVLu@g`j6g>#4(_2U(F>8Aw^H=wA(S!i!ogh3Oi&G$w zyejhkePB>T4zaF?5=<4s^4#L3HMQf%-Z)|=elpz`Vr*9>ju__jn-0?x!i7*J2{E{+ z>?DXmMG-N!CB(qV$1wtH@6|qw^^$ai70i1H3Jux)ajF+gg&tzU!VWI0tv~r$@2+D= zH+t)~xhX1!D{d+3rZAw*j4M_Nj+D!>MMuf0+DT5cBq-y5HDt3WtY<@AHD9)c;;>=+ zhzUW#0&LM2r(ZQBr%t{FNq>EJ5kd0=H&#My9vE8~6VU1+A8CFt_OU@cIil1}ID%!+Y9f zup|r1r}P+Mx%`BeP=k%ja9+dXq`4k7ud|*3AjOlxjymbcFH+;4XzFIaOf8< za416Of~R>HF|dg)VqjlQU7i#%5I0(p6x3&}fDHkc5W~!JVo7ImewTW_P3_2w*i9PQZm2e_q>N5Zb^R;f$uQQhK9E{&FT*tr}1?R?}tg95pfw z!jX5eGEtP-a6%h|TSo~#j2AR$+QV@QdbTh+Wf7j*x_i8>I^l`i68AWkY03YVd$x0R~nXG&HPvw{4PoXdiVZZFXy1fA*=ab5V?a{{d1Xg@vBFB2%dQKLL$EyiO#ifUdv8UoX zWw45Lhi{zz)dY{JqecJbxaf;b;i(7xtZ+BM$+C;unUv3Bayi+(5s8OAEmI+EVIFVw zvzn-{`mK)3eJJ_QuB;5fHX^vhl1{2wiYwUIzMf|zoN~Mho%z`ryFG@xCQ2@pY1wwb zuiOhj=n-0pR`78jkQGa88dC$XtQ|@UYPh+@r4@eZ}6F>JmS* zudmbBHGGu?i-G0gUZt#8ej!n8ivStcb|nOVKsnmrK(jZ70{Jy=o})rX>_i^dOmBLxyNT=L3s^-xPT~oAc1g=-u+EGcv6b~FNdOR>K zPwO-Zx*g^|_7U)_xN-}sN%a!jt+wC?Q?ih)siw?iB#o7vKa4a7!QOk@qDNrYF%m6* zg*rR{rVQI~B@G!XjhM>k2YvMwMGG$#{8TFZr`%(0>Ppp1Tld|ML^>jgbnsK4 zC>IAQ!2+ST!e)*xTrpFq4$7~=MP9usygSe3=SD)O{HSbT$nmjHoA~r&P$uS^@lR*f zpDZL(v%<~wCv(qfe%exhni#KeYyGJ`{^=R@r_T7NXX+Eeql0onym#qzp{-wVmrlS< z$%4r%QUJdIXeX5V1p;*#6TiT%j@*h&n_M;{EDba_%m`e_7U`{O!!E);Z<@Q{b=AhX zi(j{QNQkGoAL4mdwRvva>-JV#=7zj&uC~r?<+-JL#@v~tK69?m+s3(te=M&j8)X+3 z9_w5`x51=65F_v*g%@FGwkyjqikSv!K}b$Q-cvY{|)#$2_85`{S6>baFhe`d8rEj8|hgh|~*%@Ng~sR^SfH zkqx2XLNXjrVss>wZ7C9uQ*tUmdy_drxr^5;KBgjL|5r+T)rG;8u24kRo|1@e!FBLk zZ(Tl9dNwWl5j(?Tc*Z_CDskfh@^|c$(>x|iiDrvrlS?35ph#_M^UIb1 z2xL=bSt;rl^MR#ZztFp)K6B@1l0K75y06UmO&c|?-w_kh5dmcN_Y4ACEG6|GgNmG4 zCUHtEhq+wvyRuQB$K9Y|Hubo@Y1|1AH>U)~)(MC8_9%I5*1V zKdIKQ+%lL%fi369??y@ZJbHrJ>h4hYc%eZ?%;td(JC_YMVhTeS#>B*K^Rz78jN`U1 z(65h5ACm`@6~n&CxsEY{kV48O zz}SJ>cs4^X_)>6=BGO1BccKw;Tq)amB$Y;vL@KC-S7KSsBgHgwB#J?Wypj{_Lfo-F zCBi}7ypqt;Lh`X3iGEN`ujKH&aCj_-$ViGdYV4IW>&W5M)aZv(ll;FsI^k2kWM+Mh z6rW4fQ3{s!ab5a&XXB%t4Wi`Q^zm@xqs7wVV?TYoukn%Se^%|9^znG(BMi+x&ZdtK zH$LK;)t#A1U-mu_yP6V?*Eq^C0?-kJK))t{UaeodcHPviU9)z*8jX6SE^ln8%NuKT zd84VzKa#<^nY?P_NEKg60VfvQ50h0`Dd`Bj#nw4(P&1>wpRK_yn$??d9S%Md9@cP- z2D5-KkiLCPbfBThu^qI6r6yX|1gBWM z`c~CXbz)9hp%VOeypo|;66AzF6((v3I#XC3f)lBNt&Lu^trr;S)r*b|5?MMKNMXF9 z^^-%e-Gtzojfy)~ac2a1!)#leKxk3^p7Jj0($Rbq!MzW9Uconu=;tDA__%Dzpuvbr#ARc4USX&PqH=?JjIH zN%M{9Rp`*R9ggyH;z^v$bdiZa|t9IMUraEKpx@yzDYUA}+?Uu6|0aOIl0^Y|; z*^mKAWl0gr%M6Q5&r(!nqD<1Rf$Lwi?-G-EindZs$#J1Y1ZkT?no@IDY#Fj=)8f1s zC%QMzZI1;C6_A5%D!?YRXDgI#Tg(R2hoh{Njqq$65xCww?0d56E8C{d6Ow-X0UIn{rD|S3Ud}-RB4k^?9MtJ}d`nU1K z4z=n`5s5}{c{pBIbrim#Dn=zFich<53&)>C-!vZ@@zTB>s;gSoH(dsryQ@b5WA5S1 zX3)3LoT4J_TNK?LWo0wdUl`p!88#(XaygHa>pTYt-Y`A({^b#G0K(QUtcdS~oyWc` z#{kICHbn^RqM(Sd$*fvWJy~~0mXl!R)}+YI>%;x;5kNMOM^ghQN{(GK?%1`y!ZD~t zLE;EelgiJ$8-K1#+{ZXK6eqLC-jQ0z)5}3x( z09Q?(hs~kpgZfFxa*E=v0k`0Bf%~MZD?0_@Oe3AvA;kz{ZBq50ubv#U9r2ehJ#_^d zd-n=;&(4#X^R67Q{{{%_hy;)GcoeH2CL3tk5YLi*V|`J++D=?o9+#NI4w7RJy~}r{ zSdGg0xPzKn{wK~fU&nHs0Au|3e_b(?ZI)!*gSgW~O@#6l&^nxdvvVr8(L`jIZK>M3 zl}8{ZZUFl4uzh7u`mVf}V7azIm-qt!Q)qEp#fvEn?PP=mn2KwAjZ=_pud#zw_S7Hq zn_+K`rOTGf5j&l*IqiFOFd(c!ed*A-&ATwnX*K{ZdsAYZ^rp+6n2v*o{eKA`Y}zy) z`yvUZj!ae4n&63QRw40G*^9Z0UNLeW@u7L8?RQ7=0g47)VL z;+Ek|IP;q2J;9k~24|Y(AGFz2m3!y5uGF+~fVMcm>d=}zHBfLU+(8ldo`xI}s5hln z2TN~Nut4`K&CHCqGg_vf9A2vod={vz2Ff+7fztTy^vR*M_SB$-Dg4Pm3(md@5-Fni z3V6(2u}>YhHm(McGfswExPtaD`JWWG&N?+{vFZF|ptWUHXl-2?TF>xyo+55N({PKB zlq*o<*#gRPG8L~IOtaIk%Tk8~oRGO%3mQh=2@i?KiRp=Z@?O4jID>P9hnne{NdxXF zr+5r&(;Qn&3#{8}=GdvGov(JWt*1pBhi`n(za1V(O{Ahn>Y`m)Md_LN>I6J=*bl`u zOZea#+NE*d)L%Q!g4XbXukH;XK_pwlHM^H*gXi``YY%x_%Rj5vta+!2bkieZ3Q`8~ z3oPzV9WRg?ZSv#QNfyS5_oLg!1%*$$KqR><-3(&(v#tZBk40k1tsb?RrFAL zOGO{6i>l`RsU0+ssFbv>)0i;9D})xKVt0-1<3O%il72GJI)2FA*0@B-&7~85SmK$@ zDcynu!pP6X617KGcp{IOh{|sU8lnB8(h4+6j4D8Km+bzNQXFwf?SGz#p?)4~@?4H? zNGL5VoH+Y>dsaU-!=l#?mUaDs=IOWDOhS`>DD&3EESw*1`W+hiOBzX9aog2c4gc^V zzS$PVef$dlQ&^ykWkzjcakR;ms15$;^O#~a6mFwR&gZJD;V%8YS8aTeHuls<&T>o( zS~IP%Fi=1*?7LB&2Di12Kwov|N;pYWSN(uXJY8%0tZVujsyPYeHq>4Tog6Roj!qsE zfvd%Okg4*2l^T<7C-q5&bw&1pE9(HD&>e#T3LpLC+XQZ#7<~!w>F3g=hn6@E2_;$V zLJqQl`v#DPpbM?2xs=w}8l8|S?Su@tf$_;bq*0nVr=SJ0B8}OZK*hVusk7Vzd!R?^ z9$gwT=4%4}Em!kx(>3z)HH^c(?e8iZ3AK5 z0?TEW6E<>%2zQ#tg-47`rlq2;e=wAcuEMw74!VeZlkf2H-%be!e+zUPgqu>1gtVn0 z?N{S0vznMlvn&8{J~fnpMRTtHaZr&F3v$Euu5Ivsux$_BNF(8A0yAH(*}Z$%(Y`tb zbOM##aV(I`9VCLyraNFgWJb5HmWa{+`9$iWM10j@O(0BSNiuKQwel_fwzs9&L0x`+ ziaAmyY0)-Eg}my3l>nP*%_71!(~6O~po$`Ac%QMZq$y^y>R2Q=c!?7|KQ`?0MLwKJ zRZWEZ>8TlNf*&pIlZFlDmCIPC!iXu~B77uLMii!5lOLmdWj@6$-hK$MAN|0Ze zGTe%&^VwQ!!b3^a&K`&+8T7zw?;(M~@VPQ$iLR7!SHHq~Os0p)Wc^4}UD#Cr9Gs*k z;>^j-d{i^@fgF)rW&q_#BXg5Zsf`Y^g9Yl8jtub-z@2lfso7y>&B2fSoccTRyc8n= z#b9B@&ME^jbo5$*+^9q-QdSE7X`y;NUZpc4bE!?>H~WuB#838W-!*|>WYaj=EBjN! zE~7GjvUgdTy?4hcdw*W!wbwM=tOs7~j6>HU{hH(0bif0c3kKBc=Zr<85@gIYA+t+7 z;;lp2FguM8Y3AnxD35%Si!^g=}K1&clD5w z5u6^4cF8pRk!x(PKF#X0^7p|2bzBh(3=PGI_>npd`WVH91HbEJLKNz^>lcT)HBXO{ zZ0K2INd4o7m-WQ(w1$jHDN(WY-Dny@={xrH?h_EiWE{&YB*HiZ*b+f&aJgX*+#PHd znT+MRv5hcRi5FqJNg0C%Qzr+ZwvOlYv1#jozrlobesus$gK`ioc1@JkC5!G&R8|Pq9nFx&Mf)68KPZi@We3?K3Iy|?#RDZVD(aOu0M$|F7znWyeyL=N z%zS7lY(_l)l`1harX5&7`%nl2u_ja$kuF+{k!jUd{DfRZ#85CAx@phY(j}za4Fq&f zIS*gwiOri|+U@~0_@yvPn|N;17>TJ+$o5#Q047?Nw?YDwAWw-An4%6>gDDTsQU;IR zkO{d261915xotU1GljJ^)?-lVS^oA1!qf#&Ew|r;F=Bs z#Vi5K5NIc*iUs=pKMqwO*>v)K#KqYnN-2?05~@EbLbr+j?T^u=QQdVNVa24nJ+UBpUx- z;IOq0xB`d$>^LqxCCvFyS!;;nIz&Vg1+m57PRfowSf`Eg&n7;&R)p_`7%fZb6|_hg zrq*wqb)h~zKtVhIp0IQDR8t6$$>GFoPa&fw+8O_KWW#~Ut{^1|T7bK@=FkisS`U-8 zHgqU-H}Cbunn}`WAxKxHLR!Z;cSpx9l7?c7OM04XwoE}OL5FwP0;Rg7;npjWz3&rq zTehm$PqJyaR3e2>J`XWE1f~xbd`g3-(ip$stzxCNK+ru9G~_D+vebgH<|uD#B?;;4 zshV07Fym~RBI>ibMA5DDj|3lWuP~dbK7UG0jtcM8u#0cSEyLDqC(oFxOztqNOcN-e zxlhv+b-sFA#}J|}Xt2gB<`q8-w#CI`#vM`6Dt$>qcxhW zFu8|+e?`V{gyh5+q++t_dW!7en=OyUyc1iI*@rP=ij&?x662=MqZy#p;|r-ri?HiE zi?l|R;nXx<@nAEL+09xk8^lyG5sX*12@@W*Y#`19kN$x3tG2|A{NDI0e!9-@6Ko_ibWQWt%FXi3BpFCI8(wC%yoJ3=cP=0e$7w;-DSsngri zL|cP1Topdk2PI*OjekRRrmpkU#G2P#9Nqd++t@yYzfK5jIxT_aYZ%(4%sfith5?AZ|NMPsViI&0Ww;|8=?mZ z9FvmN+H%1PAU7jLkO6|;xkYXV%If$ytws;on)5yq9Lao%tpgBY%=0M>Up5AP7jdfV zb(aI-lO!CMr^@M#g12cGfaf8ZLV@1R=#1I`u_!Yhc3i95(%Vu84>dZdy#@{dQ#pk@0*oO(C4mbkL8o1lA=d`0gnL$*D1v*9 zDmy)R0Ewpx5B!fUR@L%~R)Zi8!00u&FdGIL@?pNf8X;^_5CG}5%P<<2>HiE%W+z%@ zk!RXD2{$%~Kp?--CIEcJ1{N~MS(1OPtZ#(XA@Bs@31&uaTR@oL7TP5N^CExsfAdE}_c zFoftWa+F=^smM`)(IZC{z$}K6c@&?ARM(^GGSsNAHX3?B)qhtwMk{y}DX|mc9=$$s zah!anBKffD;kG=PC3(VA6i7-$!TiYg-{TTv&}C?Z5X;%|&a&{eKYQOh+ApMfRuu7@ zh`inWMii$@*=|p9sHPlHjA%D}3n+zxc1EaUT!lS*5zPV@I<RL1q!qW6X+3Ck&OkgUl zI<2cLHoTK)090at?gA|9W*Ya~pQx{5|5oS;`wCO185W@}jcGr?(SY{p$!PYnX#rD{ zLyFBzh3hxoKnf+H;C**nuWuTGR$wOGXNe+&z;<27fPMo&zms2qu^m(k7;!N5z@Z&b zoj%dE98fWE9xUpE`ubrLSK|GmVlJO;aX<@vK8$|JXJ)W}gPqUijxJGnxPfhMHJ`v4 zzGlhq@}rODm7JP?m^l?wy_M;Ce)d&+I~OrB+_NUw*@N~bq|g@nao#kN zV|wc9KZ_PfrydmTf%7;h#2)K}z%7WSjkh?3f3;8qII=`haLAzcFwQwzrlvl}_~Qp@ zW%|sX4vV(JF|K%Lr1o34vnP80CR|GZIia?r2LwC|zX$mfw89%`K~4!K@eTRRbkAXl z3yTAq3a%Wp$v3pq)Ue6^{Ot8GiSDHk*>15TnG9gA;|P)02=N>Wcw^_o3hLR~A7^Wy z%@((w!c%lsZv+$s(oApq3>#qslE+3|%;W;uh>FHAzgUbUgC!=}pdd^bAtW1^Wa1f) zWP`V{p_UH!TSG1#9x(9P74qQU{l&0!=@7ZSc#)bts{YUdKR=<1&yYb^TX@MN6>Ogl zMrk1?C!$oaFp)Eo3T++fo=iamE!`wSTO6!(iEuC7QtN8uBWi@_y#@zOL296u2x^2_ zf~KI@N2EL8HHr?4jk*@Cj#|b}T8{IxduoYbWnw4963ZwNT#+(XXbKjrP}GULQ*kL? zR2EXucG8hET_2r=6y)HS9hq_lTaGw$qV4dI(c}u8`FQF27T8WVoO>RVT1K+~B;3^) z*ZU%j?$Y?omt|6O4jA|#2EPnn1b;i6E;F@O6u0@g>y+Mu9=z^ZN8-h9}-0m2_ z1e~AUu_E^n$6J|u5d9{!yipi4>_hQz=mrryL3Y!Zivb8Y3CX)e^lT=F+zv5Hr<v=lFGy}}@GZBTea_d5xF({*u>q+MGxPTgEQ zFWv7(k{B=-*0-bXCunlFqiagVplg~ER7Uczi6iLKU~&b$uXGA8BZV8r8HINRw8Hd0 z{0F6D**Bx|u8_LzRDOWIs26JL&5?H>@HwV^*ZU?$dS9gS?oz$4tNKwT8g=bUqhS@) z(EDCrQ@NFioe-6`M|z*>dkty_wN3Bqy55HZ?dphjV$khG>+-dtw>K-apaRtqEzORw zrq7ab<=AJ2cPWTr7ZK1!d8(l%BVA6S{hf7HQ~Qg}y~BK1oxQY2?B(*95-vQ7!Qb@Vje|`_f9UKvm2bZsNXE1>JWpPP@h#5f zF+0|n%}w>+#JrtbKA2x+maTKkV>xlX>UOUkZmiDw_wY%GO7n{@W18KdMqBAuV8JG# zmh{_AvRy^}w(4By<-tAla6bW zNo;tmv#yuRYGHabo!+#gM6pdws#a*1$%EwzEmg0i$f`$sVcVv`(%wO_ZH+(43RM0i z?M_RRHJ$CBR+FXsrpew9A|*}seqkf1wz?+!nf&xL*%CSASKmfW7KMmQSd9vt8-7!( zMkwKg+Fq*3+KOsbO%@H-G+8uQ(`0@5($r*^&HU4&x%PU~L}G6RUHhI)dm6#fNXuhZ zjZfqw-L)avp1`Ux(pz;Ubw{DmY2rw3l5LSBX!+iJnN?#k^#K{vkZ$(~$l_|>9jgYn zVb?2@_r>|V%X)J9R*l;mb+Og3l2zl5{JU+{_~N~9eoNMBk zm&$_g6RXBl&lg@dMo6Yr&y6Eaw#4GSvTSGrcO8#fX-|Pw57H;DdJb4K(c>_V@YPF zNrz-@Qtu0Ljg_UXG_fOTWr-b$vs=;$wMSf6c%x)tR~G)68A1#JXFUH`>Z`US)=LqW zNk2x2X!P3m2Becu_kRcEN@n;M;@QK?k=DQtP~F=anB5g) z%LzX-b}lqoZshO#Zh_V+D5_~;h3=|Vm8q(aW@U1sq*sze~pc zpdung2R1DQA>aIH>^>By zz5BpeOirJG-=#I%D_u-0RFxaxn|5}wzN=4f#g?xqrER}TB{L`+v86-~XHqh-!tVIz|gqeEz_v5zu z)5SC}f|u0-Uaede+m>9HS?2ugN7Ir|zFZXK>1B2jksjWhY@jEX=CP8Mg!$4c{<-D2 zDN+as0Qpqpcm#{dac;zdC1~3rc*V`l`tMaa&W7Xng&dE7{;tXKDVO6@F2}{no~j&Q z|CHo7g8txlSB{%$@}ESGGZvHMTuAN)M6t7rU~=fTs|)hyY4-`0>=c~{GTS>(9t0CT zx7MGkAlHQB(O9$K=Q=OnPA3}+bJtznv>SIBi6|Pqx!B3&bZ9Pea zsJA)g32E*It#H4bH~In?$qw%M9Z2g{`Q`1JE zIj6GFGKtk0t&;Wqw476!o$)T4b1HlGz*SK?>9{F9whT^}n%V*^U1)0C|7RsNXR0xW zK88={8bgE^HzjKOHLZo^k`S>{q-#5W+Q}uq1rPN|p+vnQSmGh0)^UY-hE+#{yG*885%gvl3&K^_d0x zgxHpKpM&e_G5C_6vFAd6+u2<A$=ZjkZB&QvAaCrA1xlRkL0Ss zoAWDza?a{7MZy}gFd}t>t*#a^VfdK>syy&6PMdg87~(I4L8vczOi^N%CDcW99%DP3 z&>eYK=3y^(z+BNbBm2zFnx|s|DM1G+Ua+IMw#rrXf*~V%g?^t~UbLfpCBGQkPg4id zDu)5}KWX{44@FgdK>U&I8l6y=`#msCARD?CL)^$3b241Pz2dH^q9V_QMa93y0 z_`|xJTFqlm9)THcm)r-3=O9j<)OziJbNKkXL0eTO$a9!Oy~}uzhZ7z;5@IFoT=!Pe z*>2xZogqR|JYB=X7~O;hFWEJRrGmO;#<+loAMMhS4nN$XgYVmy1}Irp#*fG^D^nkl zrwW5m6XKkq*F|>#Ngx+e86#txPAS&0a#xv>h1bPg~n};{$<1u-EzuOF8n@rx4U*rEO zU4qEo2QkI%yl$sE$+n6PMuI1eptqYycyv*x;WqH=8$7WQS+`@{TZPI15eSXs!!I~y ztJu#h@m8qn#(Pp#Cr-U8wy;*H>h`)Sh2r&IDHM}M<6W&+EI(GL>gbcH3g^B;!JEEG z>%xD)X3?_+9Yw|#>tkjGz{l$PmD#)6U#Y7)!Y4{v3QF^STQ(~PKafCq8skS_SZVxE zrYg91v#L1M<4>k4n7@Hi0~rUUrnCXd1An}H)TaST1nsG|bk|dB32xp5&wX`m-*OZX z8vr*zmV6~lfD2OINeO+VS=B>zReyWhRT)!#mMIHJRg0I3uwDct z)k6W26z*$K#uI(xF`XEQj#YvHEgmpeBysQUc^&wWXlMnDR!RWXctDo=EI$4+(w&Mr z!dDT6uI8K|DkA|ZXtLx4oJ!rB76)B2IlmhFN;F4-vv#3h-q*WnEv&8k`fFP5L8uQaIfqe)qo}JEj1J%=7Ct&rRV{<+Wg$i2o_bfB-ek z-3#GfDq&>?A;yHge`E=!{czPp5f(kcU~37Fd4l0CJLg5X3xVXp0l_vQog`@Q{&M!qKeiXLs!0l-cAy#ho`*pWrEDq!BGeElUw?_RB_gd0W0 z!&LN$@=b+L=~0r-JREv2zgKEi!i+4b=x0=B=G2Nxcu`dGt<>sYspy*Uh#pd)A-shT z^80}PXz+v&Q-b5>qlGzSTe6pbtMZ*uNk<%#pE*0&@^IN2!3|0~` zWZ4=A{Ci&XkMyuJ{1MDG+!?UoUJqD;qf!2&DmWcJb;{)>4k({6%`(t>so}*L}}rqt3Fd3N4$B@U^B>#4dH(ElshVD9Wg?Pv$c{*jE(NEN21H5!$@?QY&fp6CWK8gJnfQfYi7b!DvH#d3i=eHp)&kK(OYOWr*WyoTP|Og zrqnX}jOJ2nW_C?~?Yi|F&agGtZ6DRlZwo`6wkg7&#MiTQyr&3XiLYDb+Es+>KjxXY z$PKOtzY$;0)-GTX-XC9oQ2X0O_)>h`CSRW-yy1wK-65Z%B77jeKAWS0ynZ#lUgXQS zw}0F-UnGyhB77#k{*WAtitr16;$Jt(9jFMu8DH1$OY!x2Qn*m4j;3xoe?vzVnMg!4Va~bG6|sJZl->nL zILAo|0!r%U%?47ZBTj#vchaHhhy^o|ty#1b7%`00%q zUuzA^y0M8;-`F3sxcV)cn>{DXS}p!J{$DexEfN)UF_s7vM0JTVuod56LVXis<3N*# zHZZ@lzmP#&(sv!-3_Dd^1}#)pWv50eTk^$|RR2q60L+q}axBCE(;fi^wS-Sd9-KAGpO1~gZJV=KbZ&qhx?eQ*RKOW|n8*iI3SX)Mb|`;iSlr)^GCTDU{ z;FM5b=a<<6q9VJRT-p-b5kxg7VP2OQa4L2aR&dD|`KxUeo66?S6F! zR0%~fqSK-rz&bojFUd#a@&+TE0&qw*J#lApzAieJ{kKP|&=ODNvLWrN8mm zKm{68&i~sG7W*rT5?=u%QYK*eh8)DNOu%eDk?lTe9Z-gVTY!xV?dYx5De1yax1Zme zx_pL<60~0y?#bq7e{jef} zy~T6ljCKzJEJy0Kj9Ic6mRyzE!C+HX2d4p-Y$C8YT`vIJ(@mh2-J~nYwqkb6r(#3u zikT(%P9d|*+Ab7NCG)g0OJtr(<|$>CKs}Yrii~UajZ?|YHk@!*u^=ms zh(Q3;gvtS*T?Yj_X+wSVWdm3T?Q-iGK}*1u%<;rt+q#sX6~34`CUG?j5i7!{vw6Ea zho3w6MT`9EUc(+Z_1O7+uV+H|HmYZ0X+0BGk7D9L!qj8YhX_m=>#=>tQ?F;Tu4mHw zvnkfo8jb5z`eTm8)*qNB*K+KE(x&(9X1hi=qqO*CfZ9z=H!giW!Mzj%&xVBqcV8z$hI;(yguVfq1-qkxXJ5Qfn?&4I9tnGi|c*DqTaYE$gj$6 zW+9H3XK9w96US(F`kHXe3zih5>zr92vvuk5-_vCqVlyBBd|@z+P8iyB4TTj<2=}2M z`CT_Or^a*YU|M4Ng}bD#1h&hi!%oLa->d5`!Wv^M3v3HnGG7<)gSa(h6T+NwF0V`5eyi~;b z21n2UX=E8ZBP-xn|3WB%!vq2dG}DrZ2?zgdQ9Hi-dHA(HEDw$H?5-29lCXIzR>{hf zPrp=d_8P0!uAW_Vq87y6(bkb*5(>BpAV6?6GIngNyDP@WY#D#!W?^d<@KXr+W;;=K zEd}UThR2eeVup0@Q2w(ND5<;0!rb=R;CB^w{n~9Pbr4WYO^9yO^cmAo;Rgf}ZTV@M zO=8NwA<9bM;jDH&mvb4_>*mEaf!);5TZacOlB zt-%t!=3|{QnYO8hx7(&0KhnjZh4fYy;^7!G;hQZF5 zqX}-k#P>3ho()B{Ee*aCHeMOve^n={s$0+)0AeYAoKy8kkA^Y&T`~kd%QZ;x)_-Ly zh9#{*6SjcJD!aZ5{?PLRsLGeC%#UQv{I(~Ly}qT;We zVG-dbnYIiYnmP828K_q^k*d;CUP=&DR@XAID-5XwN|F`=obY|2ZiJyndZ~)@&^Ag` z8Rpa+EWjs0!SLbyOnk{u>+GxfP==p?fzU>KPu1CNIg6CDASamE#{EaA_)76L#WjOx zTE@~cPK0%Pp!yTp)m3qRR@P-a4+O)W$CzE)gcye&=4ZEH&+bgLJ1hK`Blaew`dIhG zRtW8+)w1AZIvlWRB}hpf7K|~&Js=qCTN7@lP(t~&z<&DcxB0T_WvY4FyZ@IqV5?sU z^R(Ch@Th(V^C|pq7u7(XwUA_hG@q{oy++WSbfnhsB|Fnn~q1f4_ zGKT-a|2hG_A17d;C8ni7SV_gerHr?vQxX1>w}whG6HIP2ORTXxC?%$mEGto0m|B<# zreQU!m`Tl;1~?tq_tUL!4i!^Fm>>Mt*D9$y7%?-rJfe2Od>YAs_F;m6HK&WR)!>R2 zP=Dpq15pj5^ooM8<)M*H9!4>YQXz8|nYmRa z3PY{DUFy}m9DPT&x~3eJBIJe3Bt-uuHom{D)tA z;OJ{V|6k0vbMBsR|J{Q}-tvEcB7pb3XZZ9mP86>!96l~3y(XRi7>OOSWMwN<1TG4f z4oookjR=vuV2utX__RjQ0HY~#X_aewb~%hw>Mym>!dEmj%IptonJw`kp@2M9^RVz& zi;Cy$MQm0ZjY}F(!)%U(Q)x$3qIQ^D_#X`oTd)Yts2$k2Wyx#D-;L~ob=4?n+a0Q$_riT826I1 zuxK^0QVg>KZWjLB(Z=)STHKI`l_0Yt%q)b&3vMS<03YE*aP41=^O_coRjF;KZ0-Yp z{I)-N_kqRF|Mqo`t$+6OcRc!?+rIMtYlN+a&hPq%Z{Bq4^&i~(lb+$xfB5h}UjMq^ z-S<3Ycvx0=y0H@vXm^^DXt+x=2u#?A9N1D;e18ZAXy{Ce0EP#?8VxTpX{mmVsiC9Y!l z=N)1*C1>n3gv}Z3*8Kj%<7NLl`njw(CRDP4nFkf5zRG(4LR(+Lj`K6;SYiL0?!mm@ z|G0Tn<>AeGZSmUcu}@LNRj43zoeT#VFgp6zi%@wmtD9^nW>7HO1y%(6YGbkf4_jN5 zmA)BUs#;rmjM4l5wOp4kId$fYQ-m!0gZ##{7Nw6+nQsfZi@G|_WC&sO3)YgY1yEu! z&S|=vS@Fr6+2ZHZs_mv1y_eepbLFPk@>-=OILFCavY4UXQm=>q&K>utf0y#{F75fndnmh!U&eO?8zE1olL+B2jnZM-jo)7)?Gmbx6UBwai|m&gpbTm9R=S8M zr?s(q-tY#n0?gD+bY=e&V(~YC1?hecJE-6{(=Z(wXm4~ZlGeLC0{K<(<-SG-#yfUA zc5-RQuBtocUDUSm&K0_NuevDQpo^`sE ziiQ1uwa7g6KdI?rvTb5|pKY;;P2cnWjYYa{4z}^;0;E!DBz)HYWxEtt%?;=(qyDvG zn1zRLH=ZI@T2@=`eiuV5!b7B+XN0OnTK#GP7XuXvO=;Hu?-s8m`e+!#bpAPEwlblS zZUkpf*JAb@r?Q|w^d-ESYRYSV?rH>`FiIU_94-rs{sC!psY#}**cl%6f|WwvGv?G| zaH;6OiLjy-*6+O7S@*6`@67V-iYn5;+2hb_NWFuLDU^_PY^B}_8}j~J#iL`JFXY%? z4H!CST{Dp#PH2ewqdA7$oYR0aIW#P$QCE>$6{f&iqWxkKr3_w|ny^e`bh-*`D6{)8{3{aJ*oDWh3h6P`FRWn7Z9NtKrMzBXaak4z8K5gX&`*7slK*2&) z3tJG+ta(v$=;mcxAUbJRxd$7f9e>Nhg95nz&?flFW2@n(K&ur5GLI^Tw%E`B=#2^! z|5bxmM_u%4H&i_XM)tY)j=~6&^}Kq()bN4HV+&<-IGdl3KmK^<3iDOOFBir1nxA5^ zd<~oKplrTpKKzJp_H1P!X>5tUi=C;*?0?bAJ;Mn*3r}z0{AK@*T)1uoQsp`U&zlfd zgA`%<+*@a*tgC!nXtcsJu0b@3CjFciAl7DIPKJtWb|5*`s9g-{lf&GR1`Y$ZFmS+? za0lp+xCtoYGNcLPGtdK?DKn|lz5kF;Ul;BJdu_&1|Bmq6ycWO+@nss66ot?=I1mDK z!a)Ohs*MR@j_INMQIEpoXVVzFrDtEcMHA8Oy{lvR+kYF}4#ow;7;(w(M;=+g1IM^z z+LiyF;*_lsr-ZqQhZ_zoW0W8SUxe>V870nSLfF;0Vk&4{aV4Ir)!bfJnXiN;b%~Ys z2E-=Z#C{+>{YM$#MD`ORldHnQ!nIl?v0JulKKvX45lxxb{E96s9ukoCbJZ%v0O6KN zARr*2K**sxzl&Abm560T0?rGMO9flF_G;V0Zp{xko#$MLtzDyntk_xZq_AkiCGT%S z4Oi%b6q35b49ur3kyiiLAp@mOZ|3A*|55r-mGjVk>O~V6f=jp#&@U4w!N^E|Xj1k? zQErvZ^y0&YZVl%X`e_%Mwe5hYW`&!C~ba!vjAz zc|(G3CKB;v7cTK;TkMjMsv0Ot2C+9W;C|Kf>cN=W%u=!hofjIh6MBgsq|lpQKeWej0p&R--78o2W|`?%OBmHS>))HnhiHaMs47H50!s&bEPEZGyVfq8EpcSZL zlzREq!<^g3gklvxR7Z_i+H@IVHhkZtN^AZl!~>!-!#Z>ru&&ry|*7#2iB{ zuI}sg+oo2RqiJsH`vNnhl_nPrdPIOAzUzDfGfq%TCf9gyoLm-8;JZ&QKMjBYU__Go z)f^R$TD-=k&rnh-@@VRaOgix$3A+|T7&g+sW~cO2LWs!vBeX*Q&A<|gLvZ}M%Z3>m zoV0)XOPt0^uUXYY6C7GJ7Pz8(F-VvmOozv$VDP((3$1=bsJ;jzm;$0se~fmSI6!fQ z>e4`xyrt7FZ%Zb+kBsdqbPYvZ#7Yy{m#Vmw8oF2Po-sG7BzF71QB>1rNmMhn93NtH z!K|s8Fk|LduyW}UC&nrp)yRe0RC&2;tko3D*;@KHZkY*pPg(*W_(Xv)Ey3n}A6-JWR}R87Gvjg}_Z(ad*YL(`ipmXBODSKrW#pc&0Af zd;l=%#_*ca>;yhwsg1MORGt;QpnC!YBbELhOyjIGXbxQLQ5ZHKme$ zS0G)y28g6KA&piyWHVFK()t*MxQ^kScIKKBR}aY8MO-#8BbjEfrrDuTZczHjKvV7m zO(T=ajoz&_v{qh;v~cN;6qQKJGEt5C2rJeaW}LL>oR7uey~KXifVYHM1a{1Cb{vC; z5Y!=KX#gveh8O{hlF`wUNyHuiVga#Q@GXPxcl=9k;`q7*fHTl3{E zy(mRr?rgp+>P0E~a(DCPUcD$qUk*24j_O4z`f{ZCa=%`bqA&L~UykWTDf;q2^W{Ok zC`DgR=;e5P(X;;Ys9qk5U-YcAEr>ll9KYyUXWOSQkHs%~*4Yl|%U=GdQ9bKyH|k}7 z{Gw-_?Vw(6h+p)qvmMgQP4SDKb++5}a&!EmXPxaXz1$YR=vil5)XSami=K71d-Za6 z{Gw-_?WkT3$1i%;+3wfNk@!W=I@>Y5+!w#-S!a8o`6a3nD~Nh$?NOCj%IZvCaBgR+ zglPk)5*ez5IN#I6?|NF3Ck`wzh)Ks=ee%)+RTi5oyJ%(aJF>YaJuA~mh^XaY>>1av zlAw`$SV+p58M2VFSU_5^(sE=0!A+o=kOc&FPIf3tAk3Cn2GEX80t~e=W53w%obP`p z*J)0aaZIVU--7&<_gUeG>*EHFlzwWRzgoFQeM7pj9G868>LZ^AVq2s?P(y+Kz-Ql{ z0(||qm=c#BkJw(0S$>gU+PjgMXI>FPwgsS_Ee%A9MQV@{+u%$k9H?9w>%%0*&85~v z*?&a3heS7PA8OgmftB9pbKlae(t$&E+V#(LC`LS6;ypD#BriNC)~UVD|q`V)H@vbW~blaVi=3)WQ+!usz#FyqM$}z+o!3 zY<+D0S569=)7i3U<+3OSt`MMB*Bx1h8U0wGINC!c_bUU+EZokf3uRfh=$AX%fpyZV zOPlYDvyHJ0vx(hHE^3uY?eUHd7^<^23X7npB8C0hB-j=L$2okK>x`Z47GUH>bl+E% zpqN}6RU9vwZ_@GpH}b(rs;uys$a`UFA3$u1LBw~Y0SIh#!mjqyP^rJN0Dtt`MrlsX zOfWD?enY^z2+Ize-1ZIYrPh##ge3nWEXuP%XT?{_{*Y(ax6yz%vGc1L{0N1<-NM&^ z8N@`|@)>m8)b1w9$58S#v*4|z%jA()xqka*9gsKAp3TBkkz{nf>_3dADC#snHanTK zaPLt(`KFn>>)2{T>0)9)Rz=(+Oh{IHx>1C0;i1Ph(o6XDaZ)PFtr#sYuwQch(5ziR zCuS2a2LQMH;%Vlz@$pSRV&RNY0Sc&)J=>LN+CsyEET%Xq*aUJAxkJBk5fw*qN>B3}>q@)z;%QcaqqR2*hw?P!?nf%ooJ4$jY6T zP<{*G`VQ*&0$v%5dO6CU{uh~|;7m&}XSa(uH4by9%VU`lCN^Q?0_V6DRjifl)gUW( zqRO(01O&J{0ejwAL<#&n?M@apQgPS94#}dRMc5POjmI>6OgS(xe+CQ-Sft2h*p4w- zq{JCL-75eDvCBIDvr?uX~> z3O5*+C8N^LBaO`4cmpXC2=DC4p)r+(Fc*!pAp)VD)cCe`wE^jcSnnq(a z{iIbPF7a{_Sd(7F1A<+UEfZ4V??w8vV4g}8L6rYIOMLAk9+N+*Xt%C{a^H7e5v`R7B zT|iQJmj-9(eQ!=2WS;vVGdM7+Ltmae*hR9zS`v=Jk~;`v&S(Oe6aTnK7_Rmty(HlD5kvkoyB5&p1=xxOdP;c_fWLe^g9?~k{AC`rSarwS|z;yxJii-x% zB-OYIR@A|ns+oifY#g)vvar5G{9fmWTX;ovSfg{=)3{AYjm;}wyTkVU;8_@e_}w3# zGe78YgA_&fsMHVa8vLL>Yz2AOAg2eS*)N;Gp3enm)dAgqWEN``_&WxK5Tjil3smG(JW(2+&W3ug7(WDIi zap(~KhU!dC>w0HB^bREegqp*`ylXh4rX{AIshBR<7T4=(Xt9P4o)ZD6GmmG&{iqsZ zq60e8BdAgt&=j5YC_EdEehc>SF>$$B8{ZHXLXItbI#z$817Wd#J4?pVR&|!NBD$RM zOMFsvvqqND4J0s4R0%r12qY2mil_Ul2}kVUG6}L6LEN8StAeH-%(FU!MGA~VLifVv=DTD`Bzy#K5XHh5pEUKOQSFi_W zL%rp+sGUVsaDL9Bwt3iDRE~)6;T~tpx%4b5C)+uCs{D2qRT5QaQB|d%MQtxXi)v+d zLvVIVeI_TbpGocF8Xh{A16yKgOkKX40n4l}NgwV!vFQ9ma+~pB;1pbVurZ;&%YN%{ zc`?ig|0yMkz3>tqNYF9lcIINl>B!FCu{6q++POLO@FE6pJaFI0u3279JqO5t_9nOv z<0B87)Hpy-Z6oyyfB4mJahh4-OS;5?*PKj%nEaej000@2CqnVUne}xgFC*Sul$KgeotKr&^^1iWp`7QkKk$}BqDYabqsXimRfp|7@}VWn05Wt=x#1%M z0&og@ABiYpDeBG*OH$Or&2@^Q&rHRU2#8NkRz~JsC+iu>=}5yZK|TAoak9GTZD839 zi8yiCiD!!m!N4GP!?Kx(WeRfwmdS!?SVk=MJ=G)^Mv&97jGT7jdD5^<`3=h~>K$07 zjE-f+2+JsAC!Vd0=Mi?PyB~C>l<-ai(z0(dXbXrzwn>=;ncKtZch60|didZ&g$AM_YB)2k zQyhSV>#_mB3YHHbv4S`N@q{NFKx74J0OEZ~T6$g2l4cAbPskPC_A-Y|un2!BSUcCMm+gC8m2 z0+r;k66jui=!Wa41WP@nIpRm^S$QRNf5W3Ss{r64YMhvRYq%b{+NQEbXaGRFLM{2Endn9~9;f84CwZ=uSq0 zMdF~2iJx>(p54aM(L=W-a^WI!%63``sY_Y^9j0-2I3hBbQIgcaHq@?PI)T`EPNpas z9f@DvBdBODGrNbqmoOvjb69eUP_tQyl$)kk)?}ZyY^%<;8Pow{yQu@_a_`Nf2YBma zZ8Q5Tx6LIXpa*Q{YKF<}MT%fVM@$XHW!>YPk<@bu+mnQ)fbs}LlCa2-HpHWi`J3uF z;C-GbBE8;FcenyX^CC7LueQs(u2|@BCS2oofD27! z7U^pjy3YeVo#w$lZkGqwgSL2pNm4C9jk8>c_3{Gq8+M(Iz`yMxAFsGQ9f{9D7Ik!#W(l3$kZu*#`gZ z+iKa@No@5N=wVf9SCnj`J{nRVv7WQE*%T`*mB8H$BqXCEOSMgAKzw;MF`!l!k>{2# zmtAzDx@Hx|x;CnBR-ufBmV?e&g)yP5h8Gq;PLM49?0dXid6ZRi`CswT=qNX~)HQnoAr{J9$vAQ@7)`#R=YN_>rbB zV_Uqcazca;qbKzS3Jfw=gI}Meq;_b#W_nOnVBUE=9{3ws zGPdyxbKp<%;O*sb+-e8Y-ZAsWA{cU&mv|9XbY#Yq(Lq0DOWcn4M2A(sOzX%IL7Yj- zW8e6+VOKcz#(Ojf@63`8iUP>9U=|gaeE4s4nXaY|o=6=e$<3~-boDhjt>BDu$~_f& zbaePF$~A%N#iZ=Fo-opAN4v^~$Cx!fMis}GU{AhN8Q%@{_L)t@bO-l!gYf(y~)RO&%Cqt;0qXLN%jQr7g#}8<%06@+FM( zqT!QYK>*nSV{v}dVzIizh$S)c?9lFkLY7ELwj{$h8@#r)p8r$HItt-ZauH^}Of2E(Cu+de25i`9g#Z=akGL6nf z6Gi7^h09s&&Kma4^lxmBv$c~gPMdlS6iM&Gwed1WJ~96by5d*dpIgkdswPU_5l|($Z3|8!Xdn{RJ!WR+tO^lD zMGKlDO?Sa$`$^rBJw@%$qp{khXXbN9#&e5m*#3XXdlxXduB*=XJnGTi)h(55*_I_G zIaMfFj;)6sKNRJGj%?WxwqwV3Ovo@-E_IirR=2vP>TcQM8oQGi6BsiwU`QAO5qa4_ zJOs$h7$$(6WF`qQx#1ZGGT?y8FqZ(HU>+YBe1HG7_da#1)H04u`0m^*>8Q?QKh|D* z?X}ikYwf+YX$o<7=#%PSOwSBj=32aAQL8>sl~IFe!0tFep1A}I#i_7T?07=Wlm)f= zjZc`PRf+@goF`p0Wi|OC**AkO5Yh3$QP*~;tMRV5w0%v{`gE}Mj7J(IbC=~5Fj^6r zFe;pf7qNN&mrqf$Z@uiqRO}YTTI-wbszL)&!DRI_mABTH0dvT*o@u;+sD*xm)fo&) z*B3eV$FT7dSabU>0iz8mhg)1Q1I=xmtr0o&?jQQ@O>NvG7iAa`m^7riLrjoUP3M0S zYDHw`mI%&kcSd%$Lqpi6!R3%~T4KJMzr$3&CU=gnjq{0pb2susL)RL`3Q378ULzBf zTD6Xv{iq|J9GzcnZ?1`9)mhahIpbkeQUT=RM=sL@5{0rcG69a}hz@28VW=>mG&$9l z_=H@OCg*qNSYx$t6l*C9H#5D)n7hfMV~w+-{Cs^puCy$Ixpo6zjdv1**D-e&e$)+>3?S$=fmg)R_E{Ev zBUV8ug~aucxVmy;jAfP>)4n$7WPOD<>RV+K>L-sVLu^KfzCkj4=_CC@z3)d$p%LrpIi|cI4%m<;e^i zT#`~pVSti1&yiTN6f@{vY=)6;EF4gzFp9NFY;06WO!ecDSm5OhNUZ0pubW`APAmf2 zm)c`IT^WfrpBO6CX*D_yB994~lZj4h2XlqRtj5lRal*{kHkB6sq=^Bu-~?kOm^F7a zm{SLH;yS(n;|wqx7}{%opyST~vo%l~Q#hDyo8|&t~LT(kL12x!&m%MB~CI6(>&tm)M8C8qL`YZuyRiSOPDbYTd6K>-C zu465L(|HZ*hYZ!n{A<{v(HX#?H%#jxDL37yOI zhOuPMm9*jLfT@qE?2@^=X7bN^rRZ^f2|QEpU(XN0P_h!={V$cs{I4cc)i$D7X<4pxLHatmh`2QN+EZB{z+Vy z`9=9tfn!b&Z^fe>6HLulo3#2&;h}8L4OXl{;+MQuTd{pT*Ko7+B5LTuVd?eRa5X7GLA6*^2bpvf-n`H!0dq${tBA69Q1i8R|4S z;pR{?i17YMVhtAA`2ADhdoYlr@71p~403bj zTSy0kdN1xcgEQ$9G9%Bh45pX=8k{SHA&ZS-n{LT*->uJ-Y7{L4u*O=nvSkr;S=_dH zNGic7;ir1tUvo9&7ZJc4B{7*9KZJ<7fL(dQ)Mnmkr67psG$51&h{2^0rs%N|IfpSW zm_vB2IfOM+R(2~7ypCjRA8O+n#%IMkbqN!q`xJ^uSxLDVjTg%(`6h2Hh8+XRjXbx@ zl(oZ-xk#l$J3F_PNWr%G5>UH4mdE+uNyQ7S~UVXT`0eg5sG* zt4ZbK3dCbVDYCt`hmR<$sl|UHS1qlzg-O=s8c3lmgnW2sj3XxYy~S`Aa-!5IAIKXl z7G&(0D5*G_c98^{Jr9Z!Vn9XWLZ^AzwL^NwHeyOSC`v|*u9e>CS{-3@O+qJ1>V4u4 zqQvs_DSM2xa#G7uWpOI}X1yFio!8_gYoJH!9C#)m5VUPhsmHV7@Y>2;jf^QGoa9B^ae|npxFI;_xT3@s;ewkxyI$ z{&aJxYO^rNp6e7e=LS4)7!7#@X5g%VAk`Bwoz<_II9Ci*j17!v1AW5e(I=!VJ0g&_ zFP>K83q+uqh!KHppkI@t6mybk9;7sV!hE5Y-iSck=7 z?c)#I+m8*t{giwAp9kMQ?#lgz!MDGDQs(UZ=iOTZKp4E7eF>Y;38V>I7(xH0ji6&x+s(0?+# zQDMU{K~Et!j3ocff|MC)Ws4p9*EM|tFLcp-4ZvY3A zs@$|jUkfc}BvPLlgz}i}WJsfR{m>qw{{&e1bawdI$vvQv18|d zMPwo%nt9X-PqdwXI3+8wTY{7*fYH7~c#Zrk`bGxrIbDJC3|FY6BW>gqM%#AH8Owwg z%zeaoOM(&np_v#in2&Sn;JQj0>rcW}ui35stJ^^EP_1I1Rq~IhW;||MK6RF+2Lb4b zflq66#vx-IWKEEI!VV|1*q^BR(}}><;MwjZJb_6&u1s0z#U$7bIAVmN=0_M{tje%~ zhPMcc-VtIC7&;dw+pkQsqWU#_&NN5=Dc~x-)yf|<6G55(wP6jaA%*q?x3|sjN_~d> zZ~EVLcrXB7BK6Q2t#`Q&QTwy%hWvbxP~G71pr&#LOr^j_gaSnPxsGt7l*^u1DWvtK zjEkMAfV#7ZUd>dC;*_{P+`cF^<{%`VZim(6iKZ;<2W zPfZ((D9BS3Fvj{jS;0|5pbpW-#P0a6_QXwEfnmq~jful?vRpWUa7;|i9@k6IjpdIO zj}tmZ?IklDVn_93VuLLat`+SFkL_G`N4sCwd?&Wz3)7L^(apfZUDRg%#3Q?(jo(h) z*{Zj|0wiZaiGxb3Eo66k#ZdGAby(X|W;3Z+YYbRDT|yL$A;QO8x1iB$}s&?-O7Cv2Fj`?S5C2D2>h z+GECIXffpU`+;sr)Jy0PW@lCOzWR?qTEGSrh3BG8lm_tVzyO{I+ukr2HH)=s#lGX} z%KsA84Z&qFxC3pl7b!@W7+g<*Qrnflwr7@V5_*fJFfd*8A7dh8(r9328a_2lAPEYT zYoRZSb_D`u64WQqRX4#hSM9#%X2{gk>_lYhaBK%<8Up%cT1F3`B_dPj{4g>#w5S5t z9P_Lel&a$dM5$<9&@7aS9Pd-AL{MlPB@A&%t-xR$H=Q?J=~}U)s3vWNwA2;!7oDOB zMPgP$S`;Eq*{a+JoPH5PHFHa-&ZxMOOS8{ohfZ8U008&=nFJ$3HUCvx!{DGJ`8%|B z4s(pyx8Ys2%L^0WujZN&gwkqmp$X*~6WlXVE%%+PUOpH;qgl9Au<+^-<)Ziu@P5b5 z3JIXx#`lo}L_hna1MDC3GVf30|Dtlyw}=s0aW!c$FjHJ_4?9N%phwFsBQpl!^C+6!if+5U5ut)6gYi{p7zLZPR#K#xHk z>c%Tz&7i@%W=Mh5)3n5-Lc+v~fdEP2R8Tm-N^GIWiZN}MLm@F5F+9hpTO7(Kepl2h zSpQyiqdbaG?ChHG8QS3U+e&mq?^+Eeu1j4Y9^}Ey?^F7iU=fN7B9(7t@O>f3CoyzY z@?E7&js&e0t8_>Qt&skVq7XOVp31}L!B6uK$rb8{Lq)Dq`Y2h-S{)j~Neki35IpH_Bo6+c#j7?);W)=Qt=E(AQ2wpxp(=XJw>N(;aa{cA zNVj4q`B&}MzW=%1icIphzKh873v`E6;5Y=Qti;XVF+LbK|C{|L7|gUeee-uEN7CjO z-CmhCbs_(<*|zDr71D{9Q!F*;v%D^ipGnHiHl(4%CnAS&Z`HyZZwD^$+dpZ+twu3F z-m;FfEsjZeLQk?O;HoN>m+Ldg$Z4SsF2h+gDep`p{ddM!X>W_Z+{0(WdK zALpwy>KGo1@nb;|RvlItaqO<9uB6(|;E4UYZE$S0Bb(M!26b!MFh5$u1CatiaEMMo zsMvhOF7tP=oYT9Q+|mY=y-=haB(9oKN<;xi-HywP!&gJT*rk+a2cA3W=IybJw`fT> zw%4Ej+2TjYUzh%NbazVhf{+<45`X4nWIM*V0pDI~g4JYrRT z&$X>}*(zKarr#T?KvMXdt&?W41{?t$3)yx{3XE!{_kG#gbWmAi_t+Kp=z)fkUY*`B5t#BTVBl;r)$|k(=9Zg$T$OoDNQ!PS>@oG zWo-bier1Wfk%mDCiAXPn&nk7QpA=I(rk8jRw=DG2a?i?bBb1Pq%Fmshy=k4Mp4quN>J4B>{D#(1 z34hrifVPVvgyi(YOvC)%8if|WfX?4vCabb5TOzYN(v{A0XD+*Hk4IB%%sbF zTT2VEGxFV8e@lKklDbe0XZ@uEYDN;y*cgLMkfokAxFaGlzzniMR^(i?Bk{m(iXzR! zGN=bTbb}gAb~4kvnoYEoQ5ay+4ieGnCcVg#Q;ytHF%bMa_@<^Hq(lAl?^u){<#gJXh{5CEVWeQ24=9GWY~6fKyS72_7_lPo zG-@@J1qFMixp~_-*dtDAFaM3R!!SctkBm#Uy2wUZl+UXI`JljyAG0)QLjW-8k&Bxi z-i<7e_*#?ZUux$U6qldlD}J$@o~Al zVlAdLBTqx)Tx)~TxBhfAS?8>x4-j`Zf+niPW;uFWrFnl!H-)Wu&XI(QO>5A9GU~$W z#7>}h7w|IDUL*RQkW{lY?X%FGiCH*H!DK2Iav-<`-sS!O*nZ^tt!wsApE8sOLdobZ zaS<(4>#wrM^{bSF{{P_mtvm09cDRuDLJ6JsqO{6B`2YRVS!a2Xk^s4z|386==$Qc_ zOu;_{cp@Pr71y6_za~59Flqp=XAtd-D@72KaurFiKikS|!nI~v=jR`*w$5W^5JGc} z3_Wpcb+Sv;nq&DPlQZ~$2e~Nh$zoF`@^fcebeu&7Rg+ygF4=rj)*#k}kzUC+-LRpp ztpNzLu;2AJ>eq@xp@Fn%)1VsrgN@PAZP%Bu-1nhaPeV1X{c(3DSL=~rNxqs_Gg05=g1l5GII zbVj8YP2m~)#I4(E$YU)>Cv}<#1_CBv?tM|6Gh|IY;njr8m zJ`r8l(;IoENc$|0@>{G$C24GEtxa}O1j28220c$9sxYrH&h9lDbE z$?xE{^>=7b@-I9SU1#L{4Wx#kr50#I8^g_NDuK+$K@S8rn?LhxdW^eoes~oMLt>kCt2|6#~B~;~LwU#45;SECv#}AqT!Jfq47?3Vu>`eddC4P;p2qIX_USo_6;mipvDh8jy<>N&oV8UK zuIJbtQ>Ov#NkOh61Bg=WP6NZSyR`4DB~M9>VvsuhNeYtKT|!H-&&VQM4()ca%2JC% ztg<3sr7Ojcu++11-@S}lG&1<4Iz0p}J*@t-9UqY}c2;a=C$Qo|mzksSV8Wh`(t;vf ztuRh-ge*Zi@H67VlNd7S5~li6pKu6?NgMP0gG&8mKq%Hn1_90TtP`(un#0|UkGjAu zUi<;miHLGt$<(Z^%42o6BorMiCddyujk62XgAScENTuLjAhA>xj*6jQ&6-V?u%`sU zKbmvM_2H)QGQl%cO@p>E8<}Z~XV@JXaj`xFvgGLey(HmOO}QqqK8q}cAkH({75*A} zV1PnayRI%ruTr~Za;H-)MvxVw@RFv3v+hjh7KZ zusv_^-or7g1t!;vtyN2&3yBh$=51S8Bp#iuiBHvx^q|7^o2^}-x%iKqFR9{vNhpKz zB@q$jE%YPVsC-EtB0+5SCw#l9v~^DeW&^a~081g1_n%Dbv|g|cUETJM4YrmXL@qOh zfLC>`Y=mA(H;0XIk5q9Xv+|!rRG&a*t}P&5WU0UmFlK}$B2Ao=$KW%VxwBp|cZ^RB zYMqOuXb*_7zF-DvgqElSoD1lm^o?kaMF-f1Sg>_Dk)A)=tAl74+#VE&E8tW^!F4VA zyf{_(U{MDizkgXxMv0OhLZvgff=Z7_#TrHQnflU@DepEzi)j03V1ztDS>GXQ{ybTOhfIOvsBoO8zF5m(*l!RGSz?tHPu*9 zqD^hAFN?r8I-<6Rmey`?yS0|sjy_?mlh}~dVP+&vot~0+lO4>7crVXoO{e9gI=5GbnXU7fZwXHImTY953mN;yWY8 z$cP$U*I{F%9~I-Q%aBK5avFxXVOk(F2qP@9^w@Faav)2cECd-sc}h=;B8&eds4&05 znvi5HnuR80wbs0y#>)n+Sv(UT6l1wn#E#XoyvrZJ#P5uTQfbMZpDf-eR+0AR%f%Z* zXaqv#pJ;sX{18l%*{n$|AO36FPg^mVs6oLG(KhY@WNI$c3*&7j!IHet$&rogZzPoP zFJF??bxP(QHYQUc__EASXI?S)jQ?!NspXDA!FTTcM+5F+IXh5n+{!j)ELip5>drX> zqNkVrt#@S0KcH$Y0){C#v-Xw$QsUFjP%8nkYHeCuU0pr8+W$$CI8J41F^FRmM+0Fq z6AHC-p5>5==o^?tC0B@KPPnlAQG{SJ-MDc=69DJPc^sxf*bksP5Bmsn+AM1Rd+dvB znSq2FVPos|IFhb59!dr+kQzlRCX=Ggb0&B0=P9h7f1i9=XeFw!Y0eV!S9x$;WQzY< z^5EhQpyd{w5t6@41u;le5J4(b)%;nC3|QDRvC7A>xFoClE`DE^VmkwT0I%&6S_uOQ z+kLW2xMU+f)RL4w(Nj3Vqcpk3OKsZ)@YZ86x@m-C2VTXN?K}&qk=sX6Bef$J}8c2 zkU)M|2t+Xy0#_NX)p(~Pv2M9fi4M9Xj58Z4Xh|*%!mVTZ6n)EkLxkKa8g(R~_q}+M zen>M(V3ed$VsL^Jj_iX80H>UyEx-Q5vOA6oqT5n1t>!xAbmRwS- zR8LS*ZTg)KDpslFx_NU`lk4)%S;-FeDy4gMiqI4b&0$1HotnsMUapU46Be37LkyG& zX4bw<;*vt;N|XZ^T#0g&9L738Jw`4+s!oD=2I{e748i|EcR&}maW^zX1c&b>XvhjP zQUoXvi-`GiVHlRLzLtpv+_P&lG)rXS?zs7LyxkqQ*zuJ4_rx1rPjgPOINVp275Qlp zNM{5{@NQ)o6D&wvlw>4sV5#20nuI6Ke62zfYF4V{eI9b&tQY9XuCEDk=ukk^A9TZL zvuOkP5;2~JC?p>to&})U#6wIj)=C~a>U2mB9NxV4rUcV6lT-R(gi0;9HEl70;N*;i zI@VS6#gew&4P!xEm5&7}4{=q_BXL}nwL=DlIJ&0;KUm2;u1=dE8WKELgfMkvM;~9ow5hCYTf)^oj zr3sE^fCCd8e03U!7F_A3H6{@0C1QUy#xY2Raj0-37b3@<#vu#KEbu6dL(@tU$A=k` z$crLGu2$wYMTnf|HVQjvjeXOi6ai{U^D;uH!=Q#v)w$%PVmCA%Df zRq0kEbWdDfE;t1u$OU!GkX&#Hf?Oz1@2oBVlw7dv#e5G$x~?CV3pU>?$pupa`vzfY z6>cU(K%k%RnOt!5y&<`<0zG2Dc^+~h+2ye1j(%4}sp;rZ^P_Y#3=AX}JldGd|EqX3 z;zn>nE%fu+7tSNoz#~URSKnJwKLR-_Sg$TChtacmdSJaqkF1e7J%ovQ-0&di$F2(U zE;5rw4Z$(7RUBL5{Ni{j(s&+Ix53$mGvNe7PWUaLETB#N%*6K7UG=K!NIdXo_3F45 z*ZZ&1u|`~p<8|q}IOV^Dnaeu9@?Yq56S-+Q>VM*8d~?$m9Lxa;>=oY{Lc;muFX?~Z zVBi05On|v#XL}|#jBnHwXY7WF^Fe|We}7KrR<%IS%0&ulV{)jF+;t!N%c$jbL$5L3 z^ICk=m=v!k^cocMni%3((11^2JaQ$D+AGQEs&|_V%4k?r z#;IG7Ec+RojFznvudy8c)}Z!yA){r_ReukMv5(8dQ{su^`GBrGUgn9!m=wD5cwbjm z(QbB4IC_xLjY39qM@&wkD-VRROB4W=!q~YDVeA8umxYYhsK@?R$mqK8W%TNoiiGYq zqN%zeVKTbzWOTh8#_nabe3nw$24%Ea`5KbZ@Lr~ZZEcaIt)(zIOGZ~*7&{dP8I8!1 znnM`-&jKU~?5HW^IKH|dtQ&5`6EJzs__^d|6uOzTBa%H6xOX5A=V&pZ%d7Eo#41?m z5j7u4+(6U3Z*v<(5Vw4+hl`sZW)cpXA96eFaZ5P%?)Wy?#46s+z!5O}z!@*bZsTez zk1UJqO`Lj=cqFkF6&sGYq-Os%p!pQaq)80M4JGt75w45 zYu~T+dA-_4@ul{a+9vZt(Y%Dlg2#u%M@=JMwOi+Wt-g)tRS#iVaekJFekiTLdiI?k zyC)n>Y||6AK?lN81NKa;F${k#s8Nd?YS!3*cHw> zD+LMm2wAgC;j~x;O3FXR?g!>Vgg98lYv}!5r!|`HB8str+HAO3UE#lM400%hx__Q3 z8mtWhZzf={2ePfaacHx`1|I##Zi-?wC048eVT%K=3;1GC2@+rG8ff%1z(fhB0p<;0 zre9z@5(GMIInXas6X;9Q9(!P5>g7fox3^rSMwhKwtYUHC{U^jlQGC5@quPV+WAnK* zFBh$hr-5BIUJ-#fLLV71x_yv=-lm{YeoX^kfmVJt4Vtgm#ja$&(&7RWpHly|%$nL9 zSFzOvAj0~?qsKbUbVvFfn6!eUP)rOaaZGOHfa1s8AdF)tfwI`!rhWsHYFbI_!6v1s z+C0K6PG^)C7^a$V1@LNvETv|LZ+p0Gzzsb2242AqaEi8@EaaTb&MA zNPkPC45OIlW;7e60@fp04(nk63AU&`Mh~vs?M~rYsoMk6+^?~)qM-vsAmfp*#3^h6 zvu(mCWaD>rgjG?(w04`5&>Fpg%%eVmrhODeS2%N^hOtK;Jshy^8oC}=O&$|Wznf4D zYnc_su=g5IrOWw-L$4o@F2Uy{Ig^%U~YEPxC_K546@0C{Cws$QN zhtr|0J*;XAYER9pJp{ZswI_I7P3=)QgKHXnUy?#*HYnSWaE$EKWTsSH}46I#|ZHdD}7?6Ua4~}?|f9WP`Nev#%JQNOKH>M0^AsCw$0Ba z9JG@B&zy1EOs7#6j#jTw*sHS9b}eL+I#%eYG?E@^TzhM`H* zGxWr4R^xgDrBi4^I>|Nsa_EU#c1ADP)22Q8w$X*KWbMi|$Bf+45CMeSLGvA@VH_b; z8M2IGL8Z3n!^H}=Bj5+$mcoi@IQMwIt<7qY)*i+v8h&ELRv+q@%6j4j`e z@fn4K=nU5icRULpuSPSmmVKLc+T+EWT`Ii%w6G%b4w9a+?&n}O3M)`I7?3WcvJ*~v zrsE+DLC#w{x5n?A_Yr#D?=%V`^HYxP32UMe*ytdkd|cn1Lx*tjFwTsl`jBZU+N!@V z#7)8SgLcsIDE*6(=X5u=v}UMDj0*DLcyy33!ZkE(IjlVKc#Ir(w4++7E{rlaV7%&O zV&`IkJ9uXV6yxJewQsUJD;YR)?xF$1qqb_xs zxO{23Bd^2*%=)NLrIm^kVfPbi7P3L1-<^R+#T+qH(}HO~0%RPP5y93sd-cYeL?TVa zY_)13^rHMTjy6nun}9?T$f#IJcUc#xu|^C1#)`uGjpgG&W6dSt;TO)u3|qE!jtI_J z>p&Ak8DD!7%yZ&hf#oLHmPW1#HfE$x2n$}n7C{D0&jKP0?J3J4T1I3|$ph=0KKt(5 za~)|?qS`TfqWy^h&4LC$>j@LuGkq0EF(R45MSoi|A7S9)Dk(A)$Xo&$?rcVu+3eY*?3uv^ZrfD=pAi1j*3qJ{z(F-AWL(*$hnlfxs_hM#SbaE_@-Sf6Ni4td>2-=_-j%QYG~6 zSl;p~!PGlyW?nsw+7@*#-3qQ)7B$}09#cewO?S)_YkLU0h=}L%#QIQE+ojZbVr?b9 z$kTc4u*8HO9||zV_TYPbENg^_2uT5(*fFRc$;MEMb2zEwnr83_8})_RaS4h_uk(E) z7Aj*GN{m@xPxF$F$x8~4b4(T9_-Mk&hG{{1&Pxh#@{$(L5Ye4cof(Bu{(lfGq)!dSmL;xm4Xs>hILN+Sb=Umfd`_E#Oft1ZRcqNin2=aE0AUHxtqK#;$swNT{Kk!sRpSvYv%_#O zwD8?Ep$avYDOoCE4MM~p--?k$>>Hbn*m54q>=TG{$kr*=*a~6dw&#~U_%QK0)GZ|e z6;pe|y4@42Ma&`cfe;m@FN(+mh-5csS1o5cMqH}y-N&>_T0v0+?x3=qCj!Wl z=E=m{g^2-@%BubG88y~9n%1!+2RI3g$k1m?PHC@`OHj^`OcIsoE1Bt)!OU)I>b@6J zjkzkx=z3XmWfUnlL-Qn{4h@0o;A9?d0gD|s-Ieec;LTJl){tLdLAKfz%q+r*rcK%$ z8X>NE0?|1aCTp<@D61q)mOG0ux36f&dUKJvw<$^%;aBjBMOf?IWjhexF+$Ov<(mLq zugwk|QJ=E|V}1$S-s)@qx7q^tiT`!VZ+ja#TQ|5Ww?(Ye-^JG`^ZT>V*jp1gQyUG@hpm*oH5O+cjD&_V&>wHzd& z)z`wKC4ev^vDH|Z0lD0OkNy{FClNOZ;hufNVtNitFt$JpYB<3X9ImYJ*@}hF(lRE3 zc0}iM#=Lr6i}6@>+(^5Tvj|ZmqC^IMX7vOuv6+?a-0{TnV^OZ9NsTAtu)RV{z86e4 z5sCuC0K_;e-=lWYF6GaDBABp+c(W0jEf{AI^A@3Rw_8JA|I&?@wGqGmAS zWArL&K3+?+r%?(w?Ctv)!~~{W_TyspWosd)1*i+o1i`Spe5Y|$ATsi^ohR(fpXMhN zXXWLknq-q>Yk(;N0Kl9}^A6iGr;Iv>n9{x`>Qa~)?bUxEXNJXWf&|Wb%Gi@D-Itka z8DiZ0Ud_457j!722_l0pDUKyWa#M!G1>%ux`6u7@7Y_LEmxXFCuS2XBck2*Sp3JpRn^eXz4PF{q$>I=I8wE49@)simt zX-uCo359xyH`$mWPt~W4%6^|3Nz*;P0Qe|%31p6FinC|~XEjBQU5;89gT^1hBMYajLUO|V^84?7jC!QnKK^#yHMxTm zKFDj^#rgg;uKPYOO?VvYG=fbr71P6hm*`*)yl>q2gE`D$TtiEN?It>(1DjAQNkOl0 z299UkZ`1#-v)|+}e;B(dT}4K%N|b_!AqQQ8RSP_)oN#lPw{D9P6~eL{OSdz^mj%5u~w2@=z=<3 zY>3?R#-0$L{eei1`~x5}ebcXKW@H<$PTH23_RBWZd-+eJoZ6 z?CNR9i5f>Fd<@&Ex&T!3fC<9Ur$LT28Dj234qcyVlB`O@+s@gr+|DBR`k420l8tAsTnS@iRUNp178#Qz4}qq z&PR0f-R{X)-UHk>|7L*Up(_Z=^{Lye=Mzc6wmi#JHzoPK-{S~A@f(U@rskyD*YAe6+re1Sl4yf4` zmO96<+|+bIl#VZ-vVbJ?E@|Pccp#04ntK^YLAvWJYN3Vs6JI$+9U6oJ$S6VTNVEAp zMoXg7(6gj|`ngi)5C4x-QGQW=l0>F%g%=+QFZQ_}V!{aP7rPb=P?PFNbwr`gsML_w z?zru{_T9SR8;8YViZE^85kQfNwsbEE#Qd$9z0hv5aS=mN=Ao_M6ZT zXkyC^L7@sI`7%8BI(f9YCX(j^dAVB4cXF`JC(vl;(lf`h?duF0t#XAwSXZqtsv0cD zDgem64Yz!7trg|rfw58G89$hATWF+|G=A9WHvsK8)2aK!Eg1)z=5tvzPG^5L*dGyffbs|_P{LgeMJxwv^>+E|VY)z$V6ABlk!g z7=0?RZ2%y<*rDSCwy=+Gl}(NoRtx2fo|aUp<|n_~0nbje)(jo^iE{P~N5&h|M6#t) z&X(z+pq<&=P;Owz+nLbvOqxysw^UX-H);W&>0$uR)+3x*ww>T!2x0ZHc8a}t1A0Hx z8-|IqD55bLom7suXoLuWPs>c`RKFcJmH*=X#vQFdzT{+{Y5u0EC1DAw&NJ>*nb(;@{-M1tyKi~jdeFqscPj3Av( zgGIBWJ}fr#3b06VVYH@sq$eMXTX4iJs02yb2w*Jfb0)C$+TaVo!=8}Pua z*!Tr@h;EOrFb`6wu)?$i)UkYC6MMs#FbS(x5vt3C zPPr##S)+5@!oxHeHh*4bdDJioRb|Z9_}J=SYQGLO`^$EP+QEN_jCT$J|yG zP@(I4W@Mw5Ml&+^3fVOVy%8$GdjEDKh?op$ycLu467d+6AJXCh8Xlu%$Sk%Q(1dJM zbfuzNbt|6Sx9d^jtjK)MY*`?M9 zaH6?!yye@o#-Qx8@uF+6+(MBlCU?!o#90^M6xf%w$ z+=(J#Ky4`(XV}1rEm_57hI@gdQW{4KS;!pAwuyu&n!ldNCYltIEpd{KjkCODOM+y} zV@+U?{f=djXXG$;EM*sCw6et36K6Dz8p9I?__5jr=l+@|B&W`#(En|%n3H;Am*X$} z|Ft)Cb;SRP(~*%RJd}THdZ?QxN}D^|L3T*Y%By2K5oIM7L;2#L5GrP;%%1+~N&6&6 z!D+d^XrJVrI_=Y=_6cTr`llx%)dl}Xd@p0A7S*1X|C5#o>LgJ@8mQ*9G@r9HPCpk~ zNE>VLY4>VV!?k<0jS1@&3NU-ADV?Y>2nl+O!R?dRBcQ8+m=eTX+oI@!QdYn9rhYW@4 zalqmnvR4Cl0)Nau$Fo6cW9q`Vb|qgV9>}xm@V3}eMmZdTevq+vOla3s`2U}chn&p) z2ilw_HuxlCE&G@KabjK%PD^4=(`uz$i5Q(ojC06Sc%-_63GQP4%=?|5PU1GU|7+pJ ztc??Ug_mWiGv>K@=d-3ukQ+rwrZBWx2m@t3%wF9W>@%i9=b84&A?9Y0nu==9!70JZ zj45ya1?KJf%Os%Uyj42ODb1HZ;GrTp1g~YFS)07HG$g})GH&#$qXuA!!gr>9;iRTOzUt>5v2FE6f zot-g-{LjL%|0s@|{mOt^UV=@~U zgL?djm^NITf8bsGd@|1ehgs1Zzy*-`p z=Cf8pjYRQ!ZgPMTcJC*Wb@ ziPpX{kSjT(<|IdyDz=NK_mbbj1Tg?IeP=p3E^Ahe7@2%dD{Cq;hN;!=&N&C1nJmne z=%*^q+nH6>IEo@ojcYn!M};NVDcc*^6w8(H6!rnV-%dAZ8d1sL2>`}OMTBkH*jAR41kBV`H$QWZ+bWXWuqH%%!~YhaGN3* z^9LoQy+n@`ApwAC+5hCEt2cJd7%x~UnRro}#LNgG-xvX5QN8G72EJu`2X`g52lS`@G z%oMNe0a-&cjB@l}Mb^f(G!aU2uRWIZa$uqqF-vN^w-PrjP^99fhEWTY$zL}v#R(5n zoa};1VG2l58w0pbnd0h=_5qN9R|?+XTNR6LgBsiNBCDG9H_D@ya`IZNI%m&E?dV5S zXq0IhA~noTY!&Y&?L0UpPq;EE3D>h>VLKF#=HK(+!=H~9a(MX2%(1}BW6E;yH@AZ(u9qM> zU=SD<)r)YI3doAYpXep@QLB;SDF~5-?V;jorWTU0shiB?b>0){)Mg@>$aFJKEdrq*Aag=CeSf?k}?~dlut@(l$a9j&aHOp~SX6+;K2kROTrp5Mp#-c0b`ys6DXl zaze=@ipKh5>F5wKptQ_@z?^458aIvs**jxE+Kz!%n)z=BKa^$+h`TM00fAvLAZ3;_ zemhu9#bPkWiA7HESYk2U2NvT3-I+c3loswDc#e*#zYKRQeGBmM}Wsv#FS_$ij-3;FTk01K|FRjs^dLK^a~0LfvmBr+|~Y^gHC z?z2xu%k5GAf7@xPnB^+L1D}hQ&+vUj-%s89+B1D0)A!|%e_+o7*i65AL9;@#iz5OkM+^xr~h8}rZe%pv&d%6^Ch(5-NS474(ZG)c z$vZz6X^zP`>5|+UEimIM#sgmqNvMn+^zr^WR0y)c!i+Ffi_URS%`WL+BGs7M2Ld<$ z$SO@CF{`vnW>+Z(Zqs!Ae}+;3(BRM>=G1FBQ-!J6+rcU!UWF;3f+-;?ttH&dIU;`1QKVIb1$5L*{Vv5PIxORcDMFBAV#lfmRTkpNqh--a zeoDGgjUSzcBAlQeVAyJnP;VuF2A5S`+5omE@fbP65{_LT)r$<|fgk2{l7q5%<@MtX zgxc|*DTBuf$L5(ny12|U8tZRv^I?ulKnfftq z?e{Rjq?K6=I9MF$0qyC-R;8gePMM88{0f!!@Kb@Tk1|%pQVrN@^Ylb!t23NqYg1U6 z=tqcPU*u2_IwkR<4y=usOjd%*Sh%5Dj8;9NuE!$^AqMovin~;>N&?BV@R~BjmL4s))&jj@+_%A!&*N zL_{EGm8}D2l0tYh?T9E3Gl5fu8|O0y!klJ>K|4T(D$zV>?47tr+DTH^cd zz#>o%wL*ZWutFdfRhRtgl6#WgLp6P23H%3FxBsi)_<)Xw&8Ni@JYp z{@>hO+4}^&H9*Xk9)W?`Ze09mBHuW#=)!_ZL|Tn6*OFCk=0gy)oB2%GM3;V8%T9T1 zh|=h%y&7U{uM#;ztk~?`5MtoC73_@1mOQ#*VT>|OtVvdBk3jky9B*NIxa-AC#$+nP zI0*9_*%Dcxo?a5D$L!1n>giyttfGLt5eA)8#z^z_N>@i z|Lhs&J`pQDV6p`G_1dF0#I#J5KmoGLY>6&BDsI;NLSju%nY-YO6U*louiKVrm0PlB z@0fS6Hjl=c5jW24!~lL+D`6W5z(F+JF)!Lq(Bh04T+jR^vKshQ~7Xxqm$y-Fb%-$2(Wa=GFy}w)`>f`?alyveVZ1t-I+UU zQZWqL?yRB3Z7P&C_6^O=-O^J<>|K^Yof~5ij*KK=Sj1B92@eQoL=5C>@ z!a<7M_77527urFJ$X(5>(Lf~r5wclRP7|Fw2ZZ_O-{)O9neeaK;=}LcjZV9v>k0`# zM-KL(oG!i(WrW2P!uOdL)arXEE_IJah#+raM@XIXeZrWuYK-%DhZ_HE1g{;jXiuRy zVHX8z^ncmk(6P`91VsMUM0Tco@@2-*#nG~98n8rqSkKRzI-=))wC8^;p8p|kC^p2) zrt2plHiZDmo=0Cc^D0W&U;V&iiq15AnY!n{DRRz5ZGxm61tzP611aiK4y~kh0?_@< zVhsFrG^PR<#zzMe-3I*09({9%g9QA!c+=E%6D`ezl(st+EKf_$k9MFER~GX`jXqQ( z&FV3ZV=Sw4hio48q7>19u7-*tHUbbzKdPBjPyj6f{pJ%8HLL{8<^~d^!wG_N~hB2Xt+g=RRh25^26g!MUfGIJWU zy*C&uJjAk-|9s-sD7l3yIr|GFyAm)j0bj{4VAy*fOS4@y@vf|BEbt?fh*}&SfgiI5 zqZ46oUB+6Qs*sdh7Gx{QSerVxM8t$JU7#B!X~y`qjWzjXV#Y6CmNDGK3|dB|1^Ojs z0KWp&2nG`~Sg*!h!7(sO%(x2R6}*7|Y_J2Cm~7c9MbgNGZIxySYNbNP;A@})*5lKN ze%bI0HC=Vdf8y;Bb#d;pFZ8;tJR}M_~qPe4(!sOa?48{)t8Fb0r9K{KJM3_ zhUBN!9$rl3l?L=8W+&RnXkkGa;E6ztfTy_eu!2yMUG6Whomqg67nkLSsSWIh8Gxcq z=uH$u`quP5xVAqS%XVfFr`J58qTaJ}O*PWYM2RdAFnX}?efdsJ-FYi+ky&2|##;{5 zMQ|;&N0=p|*6C~6X`*OMpF)Q@!4!v6$fKqB7(AdlUIx7cRw}Y~5XUKZpwIj!8U+}j z)d+b>#B%}MufD@qXMK3nxnRfw~wHAC<-@zRr$2AMIMR-E<(jAdix+6wJmI<6B zyHp-Z66!bu5!DH2F|ElNA!-fQi){Rwc6|e1_>Q1LOQgMUOzn~TNpuN|k)gH*t`=n& zn`tqLl3#!p5Zx~Ahw4qKsL1%hJCeJWY!=$Hv*1pMFkuDF+IC0E+<0K6FM?rzMpMHB(JduEBG3+tbtO`UK zBcOj=^fkd-N*#C3QCa>2QP#;gf=N+CNj60Xa0)lfyOxch_M0=W47Z4_fP2+SaMQgW zZqEIl5Zo5=0s{MRPgn~k)cN&nVnw(|1vk4J3vLdxGZ;>Xdm_L+F$_0Qimvcr?%lvB zVi?!l>_kIzjFRYKZ&)4E$6&s)y@+JMG;$vC6eLdewNa4f*iaBi6jhjEl-co_Nr(|0 zmJe4NbOt(InWbqbfz7-e(?Un%Xqot&voymJ*-;}KH-mG=Dd0_>R@*797L#I3&sP2@ z_D4ec17bFIdbr5!qk3}cEZ^+OzCnliZx(C`8qA+8o(8|s&!uk>?p0^z{@x0U3nchB zNrKLngl|<+G1XLrsu3=o-R&ke;v=!(BX3|vR%RRhd(b)@CB=5hPXiYvF<1~NH*SD0 zV@U{IuIK*>SCivuD~Wspj*PQ5Ne~P$fuemsG2xnLR&;WnaiwZkx$4HgG_UM zCT+w6V>h?am0=sZ%KKS zWorV?BU>@7pNTRP99jp5LwljO+omtaSwm~W6qSI@&U)u|$sj}bEJhKaV)wI>@UMSb4Jk7j1_x|IraoTyfn ziHZnNbhdXJ`=3P}R-6qFel3;+%A_EXLOvPdbcu^(=EV@Dt;V+9#3cW)K(VWU%EogJLp+74zQbb+xoqy9UiPg?D_ z{mxwM1l?Q*YpG-O^0EI>B*B=NS;QbYoYi^Ha4Kq*%l`}>Z`zYGzZ)(AhB*MI=p`}J zjIScaXu;|Rsq;o93{r=L`~ql%C6RO$dy#8>V%+$V7~UJ}Wa?$)4kl_Fm^LAZKp)M4 z0l|c9d1mDyt}EALgJ zsr=L=#KD<#XUTtdh2)}rK8yBKHz4?7=zub!{bB$WAGNSx#5Lz1jRw#71QBQVt~TA_HP#U%wZ)|(RbEya1ZOFEQWy+CpFTF z>0Gb`H&b%46>({8bEP{W^kHE`a1CGsEa>9{E4gwYQCZ53&~V->p%D;VS?eyM{J}@u zLJCoGxE(0JaVoK$K`mP=I@4NcYH?xYel0W;4HJBl@R4Lql+rpxWPhNYaUC>F#Vvtm z`v46x4KGoU1<)_BrbbKb#t&d?ArEI<{@i#^ELUZcuNIF$Y`_H2#);Q~ zjWE#zG2lu|wdh>h>E^wf{YklixezN#TBstAzFA|wWWd0LMmog-0gwi0rsXB*F4$8r zh4QN^WyDHOtA;3hQZ^oyW|Gi3Sn^g&ZHRnOK@LCxQ0yu0OBO6IH83rhwUQEjF%jsd zIjMG7gg_q==qMkOS_~oys8q|>n0v{^z!Upe55n5`8EWk9xCJ&P-ogB&WI-7l2GZjYDzu?hIhv zsY`EH0pYfKH%$fcoaWxH-%Shkjr)NpHGdI_r-8Vh8IKPjyz3#$7=g9oh+~VT&k?#D`;*su=`GxM> z)V`yg$(iorRA;d>Im?@UJ}1T9>Qe5Xou8ahu7$gOR{yc2LuaSr{}j^ap00Gr!Kv8 z|NhP;mv7&G$$@R5QClE_EthWHzV%Ye*X{M@dkfomxzBF5_7>)oW;7i|(fhd8afRo9 z=bvl*j&N1D>i&L|-!ZQ8w}$^V&hG>lkk59fvfJrm2yo}lrMWwLouivCygi$r%Q|z} z?R$E?+q2`H*`;o@iu9X({?+_`of?1V(fQuu0&Sj6o6+smbCT<2TumwVrDO*G8_FaJ`hPoL1$B`rgKU`S~CG^VRCv>HSNh5h!Y* zySPvMWvbV?BQTKSd8#|xJ=j^C?=g_?==7$*;4~a$zIPSletv=M`#WPW^G6`l znd!NMQ>Eg0)gyaXbiQw?H)|a*y?;_1QlGVldHwfIJN86~dy-Cj4`F&_36NSIn!>_6N_Gj254PjnaN<~#do z{Pf%$dC?VOmM?@S%+EbN<|J~F*{bNA@%i5urSvv+h(EVx0l zG&#wbp6kN$=V$gH2~O-e-i4)uM?{15UUwl6cQf4+fwz%kZgHU)SKm*ci*}T8XZGrh zlshw?Z>lwIJl)2x##vy`p4U;WSYCnWX*dh1Fiw+R<84|z6NobdcV|YWLQ=Lg*Xwp* zHv4D0nb5s6o1QatWjD@Eb~8}2*gZ)Zuc3@>J;-Kq+vMaGS9GQ>yJE+yF2DTBORntf zf7PW|Y~Mb4<(1o~Iy)vWxnlbzoy!g!c;zd%%}(#{AtuC6jJ+(JSlA{)+O~D;wxzj+ zrK1{f-KlNB-DTKCPg2i5>S3v-jj@bh@eA>m7FV0=8@M_dBjV_6=R`Jr4gQFmK~Vx&cdc||2*Z0_x=ID z;)6?bGjsEI%oX1+&K3_rP+5m-Zr;6KAl*|w-?RLRPu_lU7J4KL-!_lBc26xGot+ja zp;Xde%{TGf;d+mEdeaEm>~_8#@@YajWqI)mzL%fX2EDtVJQwkcaaMX}w3TZc*LJQz zb4zlZAzl$s95^aUN<>!$1E}wTd>B@Pp;`#Mlg8OQ&^79`5?osky z!~L~fN~`<5T;I$k+Pa?W2Cg@7-N5fd}>2|(}HbR;gsTaV>r@C7XFU-$v5trI> z33|~bHlC)sVL0Vay$f~!OKC~nUs&32n)9H}4_NmC-hO^xmAIi7p4ofGzz)wH|GYFi zJ2$<5y0>K2qoX;Ed_iP+?YJ90rgd&HMRgrh@zJDW5>yLG!UY##XY-Mp{z+b?MQ z_AM(EU`-5(;2G~BpN4@hsW)$$>ntrEn(s}2dv|Il>e=iGjrEMt-|b~Ekx96%I0t%r zcaij8B)`<0zvFi`zX zd)qowoum9NZ0pV~9qCHj5UV^y9sdeAG{i@v{D~i-r+=na>gm%acK@1`fm!??+)H@f z&hxH&C{qJV*Cf{zSC^~&{s7Mhxejr478oBUWe;?w zQQdcD3*FfRTc;N^E$!{w&#Y_;VZV6}sTS(`a?L5d&pR(V-)wj8!jS*>0Zu~e8G1&4 zUgQAp%$+zie}|3h9i<5meZQZXwgxde5R|cm40Da7&e_#XV^lwtIzb|B>?z(cXrr;Q zt2$8rRJQ-b&S!WhTF}I&*>pm3jE9ZfD=hU=JG z3`|nW#Ql^dcwQ(Ct)xs)TvV$644f}N`94O@W7|q8k1)rZMrR)6kuOFk1(JNtzl>V? z4(chZrAyQuM*VT_@8G(V>xGW`6XeyOug9o=^I3zVUR0q!-_E6xuj~Khx{K>OxK8r@ zg{X@!n#znXGX$p>7q)dKCzp(mm?ae}-QhK`+kMvcI#?z2dSh*jO&vdZo$QuFZF?fqz0BZ=uecxTHod zEg)qJ!(n#Eq3#?kX?AjHmKhG}_W3un^VQEqCFh(xI+7`MC z*@^k3ViHj#Wy$~}BPFfrOl{4spNIWnwZ!d^UE9T1!lSFeY?KSxf!_QPOF&IBYzT-u z@Ec@>p(*R&pJC>5WL_qtt=TR7X7dNK`K2E9c>9>$96%LZ5KJm$YFb>A9J1)suEjgL zaODma%x&r3iSd+AveRL8a+W7+wn4QuyIIZ`lHPG~Xr}9TbRi5@f@XjBQ0MrxU;vbI zi?(LB&S%(J@op?yW^3VMZ+=O}Is}4v{oKVgQq4Y^$^N27^?E0=1M@vAT&C4IER560 zoNg=omTqzg=wN;h!_=%~bH9uyDMRD?Ol6d_SviN6 zj&$Y*%eZ)>gTp(7Zk?YuIxj}iq0Z5x-8m>h9l*a1!{;ExYMp!A4O_1#zxYGI$r8*_ z@-{qwzklANmYab$yA-3SO(lpanfcL{g@PBsb`EmbzGwv?N50fffHX7G+SWmK}I5`;=z<3wszJ%Ye z4=u_=+dCmn{{iaJ+~AwI#MeDs9uQ+g0EWKvQfI#j>1F;?`k)sb0(qi7uoqq}Odp)< zxVh-Sd#tt6@J?h$JH5`4F70K|2V1)Hm@Vwgwi`FPWb2!}oz}>9XZAthkiL9cpZOQJ zgi2jc`nNaxDsNJazC$}}g~q$}i@*+?U7azvcOzT#|{B_YVv`f1iK1<7Lh0 z`?>!Ct_QhvuQ|XEafQ6^8hU;=&w97$^ZqdR<-BG6;otGC{49M!?><7_AK~{sTtCXC z`yb;f>l*JJdY0~?cV8jzNq&Ev>wR3he?OPnA7K8#(DMg**1OG_Yww4+FT;#`XZnC_ z;-Yrnhf}1L{_J}tJG!)A(#L;Af8zVOe7_bST5o!?_;8Zgen(1q4t4J=3LI}aIK8ej1rbekGtB_7 zm*AO}%$+Y%g9FlkBW0Q7J|KG%JlxM$-^@47aiBW*7?f|$2`fOJvJCK}Mbbzwx}RUf zkWb5WU|O78`hq2mPV!yt4C!uUG$~9&WqjJ#bM^G`X@K2-BKvk6dONc(@?L#-m87Y; z+5%e3Jp^w1{Wb&XH|`tF_n?oEpacD!MJ@fsl%f9Vv1c%e&0^7P_K7EW&QIMQp^0DBz}&xQ<|UP zD)XtIRKB48l}VwD3oB;Y&1})Pk1Y|0(|>fKE4Y&vmL?CuC7m_D|2&1e+I(mwS7<0D zs%>&(^&EahcQ5z9m-8!|M9y!W-`DedHoslr%7cJfZ&5SSIlKpbcV}_(kfYcEuHZ>T zECkP+DX~rnSq9qY6F9ncGGSi0{~PMn7`va}D!=Q%^#-n&a<#ZNab2L_mkWC90~FWU zOtxa^6wTEJxHK8Zf41(q@fr*kSeA>?-N|NAu2=d1`RqT8R=m=;=PK`;Z@KXePC{FMrY4;Byq87TcKSNb(CpmeL~JIck=D6veG*8-Bo-clF4*X;*7 zvkSVB;+x$rEvnS&(_3Q-7b#1a3^asexdSI&&^VAoKfK4qvL78KJc{3h`z5q$$prh) zbf&F0TE`kC`Z*jj$a&d~gHbn{I@N@pVE%Jx8sbLVMod94qA3GU6HVS~zNN{i8QrGD zBM?Dbc5JmY=v_9CqYNYx0e8%QFhb5sS`^)gf_XqBtJ#{H))jd>z<%UNr<7o}0|=H% zfB;md*K;$aBb}qJQZ)=#f!{zAkEyPrN#quv>=L!YOR7Q1TXJH?0#1mRcv10Q&uJ4Q zM#8#I`t0oHrg?lWV9cJw&KSRjCI81OB}+a`WzdKpa$HUhTtD`wlzwF&!5f%5OI65k zxoYp;KB65{fV*AjwD(Z9w6IlNUr#Lfc!}sv>fTRX7jwz#5}q})%uCPT$-UC-b~aV| z^nhvlXQ_{Lf>RU)fvwFm?Yc>bb<~%1{0_l=9@O zOFe%YrA&Jpu)I^Rx#*wYJ9QIfmVFm5l3LDa;yx(7n#i}KOFcs1y4##$>+F7|_ODF& zVC%!cd5mApQ?DF))(8sUr5kN4JwL#`^qTNp^P6&dJ#Q#|*ZigYUC*0`zMo%u)|{nW z{?G9&J`n2Dv-Iom{2#d&)rIGea4%gXJpV5DFDpGiG4%XDhn~MW^!)cj&lTqWgHp+@Do?*1T5z6uyg&)Xu$F)Clszn7GqfY%H_`V`LZW0b+6o0!-GiFj>RoD3k3W z)K-Kpngo)QB*;1}g3c+m@HjBtO4(+Id0yd$(B!tJg=7Wz#977d!}D$2pHq7NR_?`h z!uK1v7u|;Et=x<5!?Wx_qVs>LehQN0W3+3IdNtP*zQeQTd)Ev-?=Wf@*&@o%-^KGQxkCMVetGHn9o+96dcLUiyo-DBm5_fo_u@* zzm;NWjCJEB&QB`au4p-EkfO~D?e)L`xfZrY?;xGp{)7A~-H-9hFpu^G?XP=hp%faW zcd{$C?4On==150j^S!OnqvTQhLmfu;3nz~3Xa4dPzG;p+Tz*J<6sd>q7_Gb1&EI~8 zv|*0S&OZhy@rYY_ z7JkC7W+&P0{pTn6UMqc<>W!P?;*57c&!xGuu3zB#&s@?(0!xsb-}K6VemTE`GRtzF zDhFWsMV>L%+wuYz-#xs%AiEs}wCk4_jJ>8b0e{_Vh=x}%^wjH4k6xae+&0_!_7nTG zx(2HJQ{ZS(m&T*y-tHoZh^U{-oV{wekisrX{_UjMNWMq8G#*P}^y7pAIQ=(K$oD;T zoOs(Waf$z{AAXtZ6qj(MKRd$=$WJJack%snCXX#m<<`55m_m%BFqAn7jU0{3PEdEH<9@-I58dS=0E&fp29 zdFd0vvA&J`k_E^3)i@un%Q6Gi1ALdRaw|9zOnx2+Yo0#f!WV62Mzm(_4xMaUkUwxcQV<_;4 zPYgYOlIQ=z^N|zDwieqvYQ~{>>Lg(dYP8 zpXvI)xIWMIYh2~`zs_@+_hmc&`FY=Elv(C|FM5VIe{^9RtII3`LF0z1MY&)u1QEyJn;wj3%k#c{I!o@Gj&#lt)Lwwisi>|+K+K7(6s%atX`ziMuxPpvu zu@S>EVsHuCZ;)PpM7O`m72v)Sg4k4gev|~7)9ri98(z@DJX>M2WeR0v?%-^fps`D~ zUiwNKy;{~aX+Fn`b~Y`1{&6aT!J{UKZYiczO)pi#X}v709GTa|iHP9zO@353rv90`XX$ zr293-Jl}#i^|2N`;iQ*Nj=8rJaUYj=Y7U34j#ukmVOOlbA=xtYZ&QcWu9c*djE99<704Y7a9ZB_N}>% z6!!kojZTX;8yIUdU6K*)fpH93fYXLqq&krc;l0lx#HY@^h%gV~d4x#_t^6)V{z%>} zd=_xhQ4{Y6VED*DL>9hUJuD>}O>#^|U?(jRI24T}PmdfRPlbiEj+ zB|WVjSZv=w9loA7oS#!r9!kM8Pw!Y$pqJL@XbxlRf%YNveqG<#51WMz!}PDCvWdQI z_3HIqWq;+*3(69GU+X8Bw#Ccv9-7};cqiiYebHD;SHEGCYplZ$S6A$}W$KS%+MS(2 z-l6Sn^z6CSV$1X%w$TcnhE& zun@2kKy(DvfLVY)LVsEny*C-*d_X7QY)qcsM*JM$9YE~;cY*(($NuYXkk(f~Q&R5P ziKbqLKgzg4m$G!|>)tcuV7=)nrsGwL@#v zkjL1QHcZ<8M?Y2Ww@619wZmAz&3Qp#kdGCU%z4c+yh{gN;mw4|a8$uBjOp~Ns%eQ% zFMwVhZtV^0@SnwkgKB!)nu8bN2=J(`&aH;+8-}8u z>&{}~)$JXaLarkd+Hlk(OVh!&168M)_bYU6Ie1VhdJvM_-i^?hi{6lsQbd!2#!B-< zH0|MN8Y?Z>4%f{`ct&;g9{@|1e~0+*0UrZ&y81Cp>it@Ywo%_$P=L^+xBB7#1l30U4L1)P2bh%t^m&fIG`CNWiz!h|b z+)lU4?RIB^o9IRzsv9Td;DI%&+qpK{6T*x;Do2YJKza;1HOPi5C{YV zp`bJ93c7=ypf~6X`h$UBFc=CUiy^!|1lA!W3*pxF2DLl~i?F%9POs5jA*;;4gD{ai zUj$G+TlIDs@hgCxQX@^dpgBrOIVCn6`Ot zgmFk4-7lnFf7(mI$u6Y0>V0bo?`qx9+11wt=US*1SYtt&BBa5IjQ_6}H}Uv=zvAp3 z{?A@G=k$h4q>Gz4bU%__Ou6YpK&fNb35FUI4SjbKgo65qSe)iI7VeWxJ=?;Ue-Ceq z;@l#r(@Tuqf@t%j8=}RZRzu}CyDhk{R_Zi7kZEEM+5hPU=H^!+b#37=e43FjJKir% zGi?=%5n@>6JP7eBE`*T!#bvNhUZZ2sC2=XBNTKPDMM6SLjA8hyftKb`>kyK>uSZDo zWbvSU+|a8UnYf;ZeW0f~l12X~>omFQ&CZPRkiUN&g+gB6Il{JLK>MgijC$|__Vl8DND^^ zW7$e^6@N&0nm^2+;NRp=IZoT&;?MBs*ahh#e@Xm^zpP&szvRCXzGjZ`l{HN>w>|R6 zKlX3A|0j?B?D@ZYT2^fS>Y906uP_> zb@h$Ynr61OFMj6Pj7&wEcE{+fiV1bI_CEHnzuBdvrRR;SnQ-ajE7y)4 z7xgjY@(Vn{iiT-3n&-@&_k)FtmxhYq;@JhZ0!$o)(6`Xxau z6*>e~UKSb55?l$H;#ga@R4h#qO|dDp`gqY{^9vPHx*|H1=Ei_0&ZCs6+Ccv7>BVYMa(aH|s1#chUN9vt zP0{2Ab-b<5KB=Ziu9P%+mdvC?L5gfymffIgk-bav>g<{vH?l(3{3T*aL`rAi2Mmgr&(js>bOcj1KDPi!&fm<9|$ctZ%2dPbo{Cb==By&uVBw6Ma zSygOFTBbeCksg;A??@06g%Kl0+LBp{n990tei+8if_{aJE z;sNz5{%h%5{yX8C?a2+dZQ1&$bN&yu+_OFNxA6(nXMFulS^0#;OIqF^+IsJf`yPM# z`4^5If9>`6KKSq&CmI4BsHm)NoVH}>UOfEy^Di8K{f!eJe2718kBWOG$@|i__MsgQ z?|kiz6LCp}6_s_3^A{~%+S1m(b;sjKa_qJ9AAEQi8>Xl9E#vE&lwA@2*9< z{`S4|@i}X{GqYMY-f{5IrWXz;r)1~UPi>k>@_)ysUmQDi`rWIae73Gy<1Z)F~ zM@4?AMt&`6sg;5z2i1C<{@rWO78Z&NY>~~WnOd@Kh8Ve9KCs7;BD!{q1LyK_=C>5t zn>g^9!t^2;Pq&JZBSMCd5cl8q;nBXlg$(%7jlbG#?w>TH&!(}{!WSb>*uU{%Ycsokdb*E`M{CAxM6MHxTZHcA&k}SJ^^i`X+AQA zW^c{iJ(lTrQ~RyMQEcCkquz~r;UAFJU^zV5G|W(1`qsAeb{lh3I!NNiG@l|3EqrVi zb#8|g=9Y}y#f?bOa~yg&=Ss=0;v%PB(!KXW3BNB~n*DXSjQdXaKXNVX|CU|!Gc7kT zHtu5JKzwVcJau=-nc3R#Y4+|Jwcgg|t1EZUn%SK@d*|WZv$+$kbK2kBJ%>A&dkc5r z;@pGpx6c3ggWLrtKia)O=Poa}$~G*Mp-dO_rEOcvfjB_Ov7apYB5CwKCk$TXdA=FyKM?`gcm9^lhQ#Ibxe&0Z!rQ8u0*&nBbh zdHk!5%F3AsTyaP^%3Z{(>@rmwgF;6KdVtIO>>nhkP`o4-2qOC@@aOnuq2BHi`piw2x#`w?B@{kC&9q$vFt`*2eOUb-Yh6?8tR0x}$jI4mm z?J{0~I)}8;s~CsY^-2=On4G|&%i`Ds5o^{wSH`Dcf&o8sRppgz@y7xe5Zx+^XUP&v z0M{ggqtwQBAx*W28c@0vE*!ba4bKK3gkU`Ji~Z2_$>z?uc$z`edga7t4&Xj=IPqO0 zLX=kI>=Sr2Gaq!cFGQgQtFTV718@*<3h)&GQv+i@PW=!8>CkQHpY8!X2>2 0 { + adminAddr = sdk.MustAccAddressFromBech32(txMsg.Admin) // validated in parse + } + contractAddr, data, err := p.Wasm.Instantiate( + ctx, txMsg.CodeID, callerBech32, adminAddr, txMsg.Msg, txMsg.Label, txMsg.Funds, + ) + if err != nil { + return + } + + return method.Outputs.Pack(contractAddr.String(), data) +} + +// executeMulti allows executing multiple Wasm contract calls in a single transaction. +// It corresponds to the "executeMulti" method in the IWasm interface. +// +// Implements "executeMulti" from evm/embeds/contracts/Wasm.sol: +// +// ```solidity +// /// @notice Identical to "execute", except for multiple contract calls. +// /// @param executeMsgs An array of WasmExecuteMsg structs, each containing: +// /// - contractAddr: nibi-prefixed Bech32 address of the wasm contract +// /// - msgArgs: JSON encoded wasm execute invocation +// /// - funds: Optional funds to supply during the execute call +// function executeMulti( +// WasmExecuteMsg[] memory executeMsgs +// ) payable external returns (bytes[] memory responses); +// ``` +func (p precompileWasm) executeMulti( + ctx sdk.Context, + caller gethcommon.Address, + method *gethabi.Method, + args []any, + readOnly bool, +) (bz []byte, err error) { + defer func() { + if err != nil { + err = ErrMethodCalled(method, err) + } + }() + if err := assertNotReadonlyTx(readOnly, true); err != nil { + return bz, err + } + + wasmExecMsgs, err := p.parseExecuteMultiArgs(args) + if err != nil { + err = ErrInvalidArgs(err) + return + } + callerBech32 := eth.EthAddrToNibiruAddr(caller) + + var responses [][]byte + for _, m := range wasmExecMsgs { + wasmContract, e := sdk.AccAddressFromBech32(m.ContractAddr) + if e != nil { + err = fmt.Errorf("Execute failed: %w", e) + return + } + var funds sdk.Coins + for _, fund := range m.Funds { + funds = append(funds, sdk.Coin{ + Denom: fund.Denom, + Amount: sdk.NewIntFromBigInt(fund.Amount), + }) + } + respBz, e := p.Wasm.Execute(ctx, wasmContract, callerBech32, m.MsgArgs, funds) + if e != nil { + err = e + return + } + responses = append(responses, respBz) + } + return method.Outputs.Pack(responses) +} + +// queryRaw queries the raw key-value store of a Wasm contract. This implements +// the 'queryRaw' method of Wasm.sol: +// +// ```solidity +// function queryRaw( +// string memory contractAddr, +// bytes memory key +// ) external view returns (bytes memory response); +// ``` +// +// Parameters: +// - ctx: The SDK context for the query +// - method: The ABI method being called +// - args: The arguments passed to the method +// - contract: The EVM contract context +// +// Returns: +// - bz: The encoded raw data stored at the queried key +// - err: Any error that occurred during the query +func (p precompileWasm) queryRaw( + ctx sdk.Context, + method *gethabi.Method, + args []any, + contract *vm.Contract, +) (bz []byte, err error) { + defer func() { + if err != nil { + err = ErrMethodCalled(method, err) + } + }() + if err := assertContractQuery(contract); err != nil { + return bz, err + } + + // Note: The number of arguments is valiated before this function is called + // during "DecomposeInput". DecomposeInput calls "method.Inputs.Unpack", + // which validates against the the structure of the precompile's ABI. + + argIdx := 0 + wasmContract, e := parseContractAddrArg(args[argIdx]) + if e != nil { + err = e + return + } + + argIdx++ + key, ok := args[argIdx].([]byte) + if !ok { + err = ErrArgTypeValidation("bytes req", args[argIdx]) + return + } + + respBz := p.Wasm.QueryRaw(ctx, wasmContract, []byte(key)) + return method.Outputs.Pack(respBz) +} diff --git a/x/evm/precompile/wasm_parse.go b/x/evm/precompile/wasm_parse.go new file mode 100644 index 000000000..2f447c340 --- /dev/null +++ b/x/evm/precompile/wasm_parse.go @@ -0,0 +1,242 @@ +package precompile + +import ( + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + + wasm "github.com/CosmWasm/wasmd/x/wasm/types" +) + +// WasmBankCoin is a naked struct for the "BankCoin" type from Wasm.sol. +// The ABI parser requires an unnamed strict, so this type is only used in tests. +type WasmBankCoin struct { + Denom string `json:"denom"` + Amount *big.Int `json:"amount"` +} + +func parseSdkCoins(unparsed []struct { + Denom string `json:"denom"` + Amount *big.Int `json:"amount"` +}, +) sdk.Coins { + parsed := sdk.Coins{} + for _, coin := range unparsed { + parsed = append( + parsed, + // Favor the sdk.Coin constructor over sdk.NewCoin because sdk.NewCoin + // is not panic-safe. Validation will be handled when the coin is used + // as an argument during the execution of a transaction. + sdk.Coin{ + Denom: coin.Denom, + Amount: sdk.NewIntFromBigInt(coin.Amount), + }, + ) + } + return parsed +} + +// Parses [sdk.Coins] from a "BankCoin[]" solidity argument: +// +// ```solidity +// BankCoin[] memory funds +// ``` +func parseFundsArg(arg any) (funds sdk.Coins, err error) { + bankCoinsUnparsed, ok := arg.([]struct { + Denom string `json:"denom"` + Amount *big.Int `json:"amount"` + }) + switch { + case arg == nil: + bankCoinsUnparsed = []struct { + Denom string `json:"denom"` + Amount *big.Int `json:"amount"` + }{} + case !ok: + err = ErrArgTypeValidation("BankCoin[] funds", arg) + return + case ok: + // Type assertion succeeded + } + funds = parseSdkCoins(bankCoinsUnparsed) + return +} + +// Parses [sdk.AccAddress] from a "string" solidity argument: +func parseContractAddrArg(arg any) (addr sdk.AccAddress, err error) { + addrStr, ok := arg.(string) + if !ok { + err = ErrArgTypeValidation("string contractAddr", arg) + return + } + addr, err = sdk.AccAddressFromBech32(addrStr) + if err != nil { + err = fmt.Errorf("%w: %s", + ErrArgTypeValidation("string contractAddr", arg), err, + ) + return + } + return addr, nil +} + +func (p precompileWasm) parseInstantiateArgs(args []any, sender string) ( + txMsg wasm.MsgInstantiateContract, + err error, +) { + // Note: The number of arguments is valiated before this function is called + // during "DecomposeInput". DecomposeInput calls "method.Inputs.Unpack", + // which validates against the the structure of the precompile's ABI. + + argIdx := 0 + admin, ok := args[argIdx].(string) + if !ok { + err = ErrArgTypeValidation("string admin", args[argIdx]) + return + } + + argIdx++ + codeID, ok := args[argIdx].(uint64) + if !ok { + err = ErrArgTypeValidation("uint64 codeID", args[argIdx]) + return + } + + argIdx++ + msgArgs, ok := args[argIdx].([]byte) + if !ok { + err = ErrArgTypeValidation("bytes msgArgs", args[argIdx]) + return + } + + argIdx++ + label, ok := args[argIdx].(string) + if !ok { + err = ErrArgTypeValidation("string label", args[argIdx]) + return + } + + argIdx++ + funds, e := parseFundsArg(args[argIdx]) + if e != nil { + err = e + return + } + + txMsg = wasm.MsgInstantiateContract{ + Sender: sender, + CodeID: codeID, + Label: label, + Msg: msgArgs, + Funds: funds, + } + if len(admin) > 0 { + txMsg.Admin = admin + } + return txMsg, txMsg.ValidateBasic() +} + +func (p precompileWasm) parseExecuteArgs(args []any) ( + wasmContract sdk.AccAddress, + msgArgs []byte, + funds sdk.Coins, + err error, +) { + // Note: The number of arguments is valiated before this function is called + // during "DecomposeInput". DecomposeInput calls "method.Inputs.Unpack", + // which validates against the the structure of the precompile's ABI. + + argIdx := 0 + contractAddrStr, ok := args[argIdx].(string) + if !ok { + err = ErrArgTypeValidation("string contractAddr", args[argIdx]) + return + } + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + if err != nil { + err = fmt.Errorf("%w: %s", + ErrArgTypeValidation("string contractAddr", args[argIdx]), err, + ) + return + } + + argIdx++ + msgArgs, ok = args[argIdx].([]byte) + if !ok { + err = ErrArgTypeValidation("bytes msgArgs", args[argIdx]) + return + } + msgArgsCopy := wasm.RawContractMessage(msgArgs) + if e := msgArgsCopy.ValidateBasic(); e != nil { + err = ErrArgTypeValidation(e.Error(), args[argIdx]) + return + } + + argIdx++ + funds, e := parseFundsArg(args[argIdx]) + if e != nil { + err = ErrArgTypeValidation(e.Error(), args[argIdx]) + return + } + + return contractAddr, msgArgs, funds, nil +} + +func (p precompileWasm) parseQueryArgs(args []any) ( + wasmContract sdk.AccAddress, + req wasm.RawContractMessage, + err error, +) { + // Note: The number of arguments is valiated before this function is called + // during "DecomposeInput". DecomposeInput calls "method.Inputs.Unpack", + // which validates against the the structure of the precompile's ABI. + + argsIdx := 0 + wasmContract, e := parseContractAddrArg(args[argsIdx]) + if e != nil { + err = e + return + } + + argsIdx++ + reqBz := args[argsIdx].([]byte) + req = wasm.RawContractMessage(reqBz) + if e := req.ValidateBasic(); e != nil { + err = e + return + } + + return wasmContract, req, nil +} + +func (p precompileWasm) parseExecuteMultiArgs(args []any) ( + wasmExecMsgs []struct { + ContractAddr string `json:"contractAddr"` + MsgArgs []byte `json:"msgArgs"` + Funds []struct { + Denom string `json:"denom"` + Amount *big.Int `json:"amount"` + } `json:"funds"` + }, + err error, +) { + // Note: The number of arguments is valiated before this function is called + // during "DecomposeInput". DecomposeInput calls "method.Inputs.Unpack", + // which validates against the the structure of the precompile's ABI. + + arg := args[0] + execMsgs, ok := arg.([]struct { + ContractAddr string `json:"contractAddr"` + MsgArgs []byte `json:"msgArgs"` + Funds []struct { + Denom string `json:"denom"` + Amount *big.Int `json:"amount"` + } `json:"funds"` + }) + if !ok { + err = ErrArgTypeValidation("BankCoin[] funds", arg) + return + } + + return execMsgs, nil +} diff --git a/x/evm/precompile/wasm_test.go b/x/evm/precompile/wasm_test.go new file mode 100644 index 000000000..6db1d7642 --- /dev/null +++ b/x/evm/precompile/wasm_test.go @@ -0,0 +1,587 @@ +package precompile_test + +import ( + "encoding/json" + "fmt" + "math/big" + "os" + + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasm "github.com/CosmWasm/wasmd/x/wasm/types" + + "github.com/NibiruChain/nibiru/v2/app" + "github.com/NibiruChain/nibiru/v2/x/common/testutil" + "github.com/NibiruChain/nibiru/v2/x/evm/embeds" + "github.com/NibiruChain/nibiru/v2/x/evm/evmtest" + "github.com/NibiruChain/nibiru/v2/x/evm/precompile" + tokenfactory "github.com/NibiruChain/nibiru/v2/x/tokenfactory/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/suite" +) + +type WasmSuite struct { + suite.Suite +} + +// SetupWasmContracts stores all Wasm bytecode and has the "deps.Sender" +// instantiate each Wasm contract using the precompile. +func SetupWasmContracts(deps *evmtest.TestDeps, s *suite.Suite) ( + contracts []sdk.AccAddress, +) { + wasmCodes := DeployWasmBytecode(s, deps.Ctx, deps.Sender.NibiruAddr, deps.App) + + otherArgs := []struct { + InstMsg []byte + Label string + }{ + { + InstMsg: []byte("{}"), + Label: "https://github.com/NibiruChain/nibiru-wasm/blob/main/contracts/nibi-stargate/src/contract.rs", + }, + { + InstMsg: []byte(`{"count": 0}`), + Label: "https://github.com/NibiruChain/nibiru-wasm/tree/ec3ab9f09587a11fbdfbd4021c7617eca3912044/contracts/00-hello-world-counter", + }, + } + + for wasmCodeIdx, wasmCode := range wasmCodes { + s.T().Logf("Instantiate using Wasm precompile: %s", wasmCode.binPath) + codeId := wasmCode.codeId + + m := wasm.MsgInstantiateContract{ + Admin: "", + CodeID: codeId, + Label: otherArgs[wasmCodeIdx].Label, + Msg: otherArgs[wasmCodeIdx].InstMsg, + Funds: []sdk.Coin{}, + } + + msgArgsBz, err := json.Marshal(m.Msg) + s.NoError(err) + + var funds []precompile.WasmBankCoin + fundsJson, err := m.Funds.MarshalJSON() + s.NoErrorf(err, "fundsJson: %s", fundsJson) + err = json.Unmarshal(fundsJson, &funds) + s.Require().NoError(err) + + callArgs := []any{m.Admin, m.CodeID, msgArgsBz, m.Label, funds} + input, err := embeds.SmartContract_Wasm.ABI.Pack( + string(precompile.WasmMethod_instantiate), + callArgs..., + ) + s.Require().NoError(err) + + ethTxResp, err := deps.EvmKeeper.CallContractWithInput( + deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, true, input, + ) + s.Require().NoError(err) + s.Require().NotEmpty(ethTxResp.Ret) + + s.T().Log("Parse the response contract addr and response bytes") + var contractAddrStr string + var data []byte + err = embeds.SmartContract_Wasm.ABI.UnpackIntoInterface( + &[]any{&contractAddrStr, &data}, + string(precompile.WasmMethod_instantiate), + ethTxResp.Ret, + ) + s.Require().NoError(err) + contractAddr, err := sdk.AccAddressFromBech32(contractAddrStr) + s.NoError(err) + contracts = append(contracts, contractAddr) + } + + return contracts +} + +// DeployWasmBytecode is a setup function that stores all Wasm bytecode used in +// the test suite. +func DeployWasmBytecode( + s *suite.Suite, + ctx sdk.Context, + sender sdk.AccAddress, + nibiru *app.NibiruApp, +) (codeIds []struct { + codeId uint64 + binPath string +}, +) { + for _, pathToWasmBin := range []string{ + // nibi_stargate.wasm is a compiled version of: + // https://github.com/NibiruChain/nibiru-wasm/blob/main/contracts/nibi-stargate/src/contract.rs + "../../tokenfactory/fixture/nibi_stargate.wasm", + + // hello_world_counter.wasm is a compiled version of: + // https://github.com/NibiruChain/nibiru-wasm/tree/ec3ab9f09587a11fbdfbd4021c7617eca3912044/contracts/00-hello-world-counter + "./hello_world_counter.wasm", + + // Add other wasm bytecode here if needed... + } { + wasmBytecode, err := os.ReadFile(pathToWasmBin) + s.Require().NoError(err) + + // The "Create" fn is private on the nibiru.WasmKeeper. By placing it as the + // decorated keeper in PermissionedKeeper type, we can access "Create" as a + // public fn. + wasmPermissionedKeeper := wasmkeeper.NewDefaultPermissionKeeper(nibiru.WasmKeeper) + instantiateAccess := &wasm.AccessConfig{ + Permission: wasm.AccessTypeEverybody, + } + codeId, _, err := wasmPermissionedKeeper.Create( + ctx, sender, wasmBytecode, instantiateAccess, + ) + s.Require().NoError(err) + codeIds = append(codeIds, struct { + codeId uint64 + binPath string + }{codeId, pathToWasmBin}) + } + + return codeIds +} + +func (s *WasmSuite) TestExecuteHappy() { + deps := evmtest.NewTestDeps() + wasmContracts := SetupWasmContracts(&deps, &s.Suite) + wasmContract := wasmContracts[0] // nibi_stargate.wasm + + s.T().Log("Execute: create denom") + msgArgsBz := []byte(` + { "create_denom": { + "subdenom": "ETH" + } + } + `) + + var funds []precompile.WasmBankCoin + fundsJson, err := json.Marshal(funds) + s.NoErrorf(err, "fundsJson: %s", fundsJson) + err = json.Unmarshal(fundsJson, &funds) + s.Require().NoError(err, "fundsJson %s, funds %s", fundsJson, funds) + + callArgs := []any{ + wasmContract.String(), + msgArgsBz, + funds, + } + input, err := embeds.SmartContract_Wasm.ABI.Pack( + string(precompile.WasmMethod_execute), + callArgs..., + ) + s.Require().NoError(err) + + ethTxResp, err := deps.EvmKeeper.CallContractWithInput( + deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, true, input, + ) + s.Require().NoError(err) + s.Require().NotEmpty(ethTxResp.Ret) + + s.T().Log("Execute: mint tokens") + coinDenom := tokenfactory.TFDenom{ + Creator: wasmContract.String(), + Subdenom: "ETH", + }.Denom().String() + msgArgsBz = []byte(fmt.Sprintf(` + { + "mint": { + "coin": { "amount": "69420", "denom": "%s" }, + "mint_to": "%s" + } + } + `, coinDenom, deps.Sender.NibiruAddr)) + callArgs = []any{ + wasmContract.String(), + msgArgsBz, + funds, + } + input, err = embeds.SmartContract_Wasm.ABI.Pack( + string(precompile.WasmMethod_execute), + callArgs..., + ) + s.Require().NoError(err) + ethTxResp, err = deps.EvmKeeper.CallContractWithInput( + deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, true, input, + ) + s.Require().NoError(err) + s.Require().NotEmpty(ethTxResp.Ret) + evmtest.AssertBankBalanceEqual( + s.T(), deps, coinDenom, deps.Sender.EthAddr, big.NewInt(69420), + ) +} + +// Result of QueryMsg::Count from the [hello_world_counter] Wasm contract: +// +// ```rust +// #[cw_serde] +// pub struct State { +// pub count: i64, +// pub owner: Addr, +// } +// ``` +// +// [hello_world_counter]: https://github.com/NibiruChain/nibiru-wasm/tree/ec3ab9f09587a11fbdfbd4021c7617eca3912044/contracts/00-hello-world-counter +type QueryMsgCountResp struct { + Count int64 `json:"count"` + Owner string `json:"owner"` +} + +func (s *WasmSuite) TestExecuteMultiHappy() { + deps := evmtest.NewTestDeps() + wasmContracts := SetupWasmContracts(&deps, &s.Suite) + wasmContract := wasmContracts[1] // hello_world_counter.wasm + + s.assertWasmCounterState(deps, wasmContract, 0) // count = 0 + s.incrementWasmCounterWithExecuteMulti(&deps, wasmContract, 2) // count += 2 + s.assertWasmCounterState(deps, wasmContract, 2) // count = 2 + s.assertWasmCounterStateRaw(deps, wasmContract, 2) + s.incrementWasmCounterWithExecuteMulti(&deps, wasmContract, 67) // count += 67 + s.assertWasmCounterState(deps, wasmContract, 69) // count = 69 + s.assertWasmCounterStateRaw(deps, wasmContract, 69) +} + +// From IWasm.query of Wasm.sol: +// +// ```solidity +// function query( +// string memory contractAddr, +// bytes memory req +// ) external view returns (bytes memory response); +// ``` +func (s *WasmSuite) assertWasmCounterState( + deps evmtest.TestDeps, + wasmContract sdk.AccAddress, + wantCount int64, +) { + msgArgsBz := []byte(` + { + "count": {} + } + `) + + callArgs := []any{ + // string memory contractAddr + wasmContract.String(), + // bytes memory req + msgArgsBz, + } + input, err := embeds.SmartContract_Wasm.ABI.Pack( + string(precompile.WasmMethod_query), + callArgs..., + ) + s.Require().NoError(err) + + ethTxResp, err := deps.EvmKeeper.CallContractWithInput( + deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, true, input, + ) + s.Require().NoError(err) + s.Require().NotEmpty(ethTxResp.Ret) + + s.T().Log("Parse the response contract addr and response bytes") + s.T().Logf("ethTxResp.Ret: %s", ethTxResp.Ret) + var queryResp []byte + err = embeds.SmartContract_Wasm.ABI.UnpackIntoInterface( + // Since there's only one return value, don't unpack as a slice. + // If there were two or more return values, we'd use + // &[]any{...} + &queryResp, + string(precompile.WasmMethod_query), + ethTxResp.Ret, + ) + s.Require().NoError(err) + s.T().Logf("queryResp: %s", queryResp) + + s.T().Log("Response is a JSON-encoded struct from the Wasm contract") + var wasmMsg wasm.RawContractMessage + err = json.Unmarshal(queryResp, &wasmMsg) + s.NoError(err) + s.NoError(wasmMsg.ValidateBasic()) + var typedResp QueryMsgCountResp + err = json.Unmarshal(wasmMsg, &typedResp) + s.NoError(err) + + s.EqualValues(wantCount, typedResp.Count) + s.EqualValues(deps.Sender.NibiruAddr.String(), typedResp.Owner) +} + +// From evm/embeds/contracts/Wasm.sol: +// +// ```solidity +// struct WasmExecuteMsg { +// string contractAddr; +// bytes msgArgs; +// BankCoin[] funds; +// } +// +// /// @notice Identical to "execute", except for multiple contract calls. +// function executeMulti( +// WasmExecuteMsg[] memory executeMsgs +// ) payable external returns (bytes[] memory responses); +// ``` +// +// The increment call corresponds to the ExecuteMsg from +// the [hello_world_counter] Wasm contract: +// +// ```rust +// #[cw_serde] +// pub enum ExecuteMsg { +// Increment {}, // Increase count by 1 +// Reset { count: i64 }, // Reset to any i64 value +// } +// ``` +// +// [hello_world_counter]: https://github.com/NibiruChain/nibiru-wasm/tree/ec3ab9f09587a11fbdfbd4021c7617eca3912044/contracts/00-hello-world-counter +func (s *WasmSuite) incrementWasmCounterWithExecuteMulti( + deps *evmtest.TestDeps, + wasmContract sdk.AccAddress, + times uint, +) { + msgArgsBz := []byte(` + { + "increment": {} + } + `) + + // Parse funds argument. + var funds []precompile.WasmBankCoin // blank funds + fundsJson, err := json.Marshal(funds) + s.NoErrorf(err, "fundsJson: %s", fundsJson) + err = json.Unmarshal(fundsJson, &funds) + s.Require().NoError(err, "fundsJson %s, funds %s", fundsJson, funds) + + // The "times" arg determines the number of messages in the executeMsgs slice + executeMsgs := []struct { + ContractAddr string `json:"contractAddr"` + MsgArgs []byte `json:"msgArgs"` + Funds []precompile.WasmBankCoin `json:"funds"` + }{ + {wasmContract.String(), msgArgsBz, funds}, + } + if times == 0 { + executeMsgs = executeMsgs[:0] // force empty + } else { + for i := uint(1); i < times; i++ { + executeMsgs = append(executeMsgs, executeMsgs[0]) + } + } + s.Require().Len(executeMsgs, int(times)) // sanity check assertion + + callArgs := []any{ + executeMsgs, + } + input, err := embeds.SmartContract_Wasm.ABI.Pack( + string(precompile.WasmMethod_executeMulti), + callArgs..., + ) + s.Require().NoError(err) + + ethTxResp, err := deps.EvmKeeper.CallContractWithInput( + deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, true, input, + ) + s.Require().NoError(err) + s.Require().NotEmpty(ethTxResp.Ret) +} + +// From IWasm.query of Wasm.sol: +// +// ```solidity +// function queryRaw( +// string memory contractAddr, +// bytes memory key +// ) external view returns (bytes memory response); +// ``` +func (s *WasmSuite) assertWasmCounterStateRaw( + deps evmtest.TestDeps, + wasmContract sdk.AccAddress, + wantCount int64, +) { + keyBz := []byte(`state`) + callArgs := []any{ + wasmContract.String(), + keyBz, + } + input, err := embeds.SmartContract_Wasm.ABI.Pack( + string(precompile.WasmMethod_queryRaw), + callArgs..., + ) + s.Require().NoError(err) + + ethTxResp, err := deps.EvmKeeper.CallContractWithInput( + deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, true, input, + ) + s.Require().NoError(err) + s.Require().NotEmpty(ethTxResp.Ret) + + s.T().Log("Parse the response contract addr and response bytes") + s.T().Logf("ethTxResp.Ret: %s", ethTxResp.Ret) + + var queryResp []byte + err = embeds.SmartContract_Wasm.ABI.UnpackIntoInterface( + &queryResp, + string(precompile.WasmMethod_queryRaw), + ethTxResp.Ret, + ) + s.Require().NoError(err) + s.T().Logf("queryResp: %s", queryResp) + + var wasmMsg wasm.RawContractMessage + s.NoError(wasmMsg.UnmarshalJSON(queryResp), queryResp) + s.T().Logf("wasmMsg: %s", wasmMsg) + s.NoError(wasmMsg.ValidateBasic()) + + var typedResp QueryMsgCountResp + s.NoError(json.Unmarshal(wasmMsg, &typedResp)) + s.EqualValues(wantCount, typedResp.Count) + s.EqualValues(deps.Sender.NibiruAddr.String(), typedResp.Owner) +} + +func (s *WasmSuite) TestSadArgsCount() { + nonsenseArgs := []any{"nonsense", "args here", "to see if", "precompile is", "called"} + testcases := []struct { + name string + methodName precompile.PrecompileMethod + callArgs []any + wantError string + }{ + { + name: "execute", + methodName: precompile.WasmMethod_execute, + callArgs: nonsenseArgs, + wantError: "argument count mismatch: got 5 for 3", + }, + { + name: "executeMulti", + methodName: precompile.WasmMethod_executeMulti, + callArgs: nonsenseArgs, + wantError: "argument count mismatch: got 5 for 1", + }, + { + name: "query", + methodName: precompile.WasmMethod_query, + callArgs: nonsenseArgs, + wantError: "argument count mismatch: got 5 for 2", + }, + { + name: "queryRaw", + methodName: precompile.WasmMethod_queryRaw, + callArgs: nonsenseArgs, + wantError: "argument count mismatch: got 5 for 2", + }, + { + name: "instantiate", + methodName: precompile.WasmMethod_instantiate, + callArgs: nonsenseArgs[:4], + wantError: "argument count mismatch: got 4 for 5", + }, + { + name: "invalid method name", + methodName: "not_a_method", + callArgs: nonsenseArgs, + wantError: "method 'not_a_method' not found", + }, + } + + abi := embeds.SmartContract_Wasm.ABI + for _, tc := range testcases { + s.Run(tc.name, func() { + callArgs := tc.callArgs + _, err := abi.Pack( + string(tc.methodName), + callArgs..., + ) + s.Require().ErrorContains(err, tc.wantError) + }) + } +} + +func (s *WasmSuite) TestSadArgsExecute() { + methodName := precompile.WasmMethod_execute + contractAddr := testutil.AccAddress().String() + wasmContractMsg := []byte(` + { "create_denom": { + "subdenom": "ETH" + } + } + `) + { + wasmMsg := wasm.RawContractMessage(wasmContractMsg) + s.Require().NoError(wasmMsg.ValidateBasic()) + } + + testcases := []struct { + name string + methodName precompile.PrecompileMethod + callArgs []any + wantError string + }{ + { + name: "valid arg types, should get VM error", + methodName: methodName, + callArgs: []any{ + // contractAddr + contractAddr, + // msgArgBz + wasmContractMsg, + // funds + []precompile.WasmBankCoin{}, + }, + wantError: "execute method called", + }, + { + name: "contractAddr", + methodName: methodName, + callArgs: []any{ + // contractAddr + contractAddr + "malformed", // mess up bech32 + // msgArgBz + wasmContractMsg, + // funds + []precompile.WasmBankCoin{}, + }, + wantError: "decoding bech32 failed", + }, + { + name: "funds populated", + methodName: methodName, + callArgs: []any{ + // contractAddr + contractAddr, + // msgArgBz + []byte(`[]`), + // funds + []precompile.WasmBankCoin{ + { + Denom: "x-123a!$", + Amount: big.NewInt(123), + }, + { + Denom: "xyz", + Amount: big.NewInt(456), + }, + }, + }, + wantError: "no such contract", + }, + } + + abi := embeds.SmartContract_Wasm.ABI + for _, tc := range testcases { + s.Run(tc.name, func() { + deps := evmtest.NewTestDeps() + + callArgs := tc.callArgs + input, err := abi.Pack( + string(tc.methodName), + callArgs..., + ) + s.Require().NoError(err) + + ethTxResp, err := deps.EvmKeeper.CallContractWithInput( + deps.Ctx, deps.Sender.EthAddr, &precompile.PrecompileAddr_Wasm, true, input, + ) + s.ErrorContains(err, tc.wantError) + s.Require().Nil(ethTxResp) + }) + } +} diff --git a/x/tokenfactory/fixture/fixture.go b/x/tokenfactory/fixture/fixture.go index 5a16f682c..cdaca74d2 100644 --- a/x/tokenfactory/fixture/fixture.go +++ b/x/tokenfactory/fixture/fixture.go @@ -1,6 +1,7 @@ package fixture const ( - // WASM_NIBI_STARGATE is a compiled version of: https://github.com/NibiruChain/cw-nibiru/blob/main/contracts/nibi-stargate/src/contract.rs + // WASM_NIBI_STARGATE is a compiled version of: + // https://github.com/NibiruChain/nibiru-wasm/blob/main/contracts/nibi-stargate/src/contract.rs WASM_NIBI_STARGATE = "nibi_stargate.wasm" ) From fa62d3550fad58d68eda149204c2463255e552c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 22:57:00 -0500 Subject: [PATCH 5/5] chore(deps): bump bufbuild/buf-setup-action from 1.42.0 to 1.43.0 (#2057) Bumps [bufbuild/buf-setup-action](https://github.com/bufbuild/buf-setup-action) from 1.42.0 to 1.43.0. - [Release notes](https://github.com/bufbuild/buf-setup-action/releases) - [Commits](https://github.com/bufbuild/buf-setup-action/compare/v1.42.0...v1.43.0) --- updated-dependencies: - dependency-name: bufbuild/buf-setup-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Unique Divine <51418232+Unique-Divine@users.noreply.github.com> --- .github/workflows/proto-lint.yml | 4 ++-- CHANGELOG.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/proto-lint.yml b/.github/workflows/proto-lint.yml index 7140276e5..11b1b9e4a 100644 --- a/.github/workflows/proto-lint.yml +++ b/.github/workflows/proto-lint.yml @@ -22,7 +22,7 @@ jobs: # timeout-minutes: 5 # steps: # - uses: actions/checkout@v4 - # - uses: bufbuild/buf-setup-action@v1.42.0 + # - uses: bufbuild/buf-setup-action@v1.43.0 # - uses: bufbuild/buf-lint-action@v1 # with: # input: "proto" @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: bufbuild/buf-setup-action@v1.42.0 + - uses: bufbuild/buf-setup-action@v1.43.0 with: github_token: ${{ github.token }} - uses: bufbuild/buf-breaking-action@v1 diff --git a/CHANGELOG.md b/CHANGELOG.md index c04678690..194df558a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -176,7 +176,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump `github.com/hashicorp/go-getter` from 1.7.1 to 1.7.5 ([#1858](https://github.com/NibiruChain/nibiru/pull/1858), [#1938](https://github.com/NibiruChain/nibiru/pull/1938)) - Bump `github.com/btcsuite/btcd` from 0.23.3 to 0.24.0 ([#1862](https://github.com/NibiruChain/nibiru/pull/1862)) - Bump `pozetroninc/github-action-get-latest-release` from 0.7.0 to 0.8.0 ([#1863](https://github.com/NibiruChain/nibiru/pull/1863)) -- Bump `bufbuild/buf-setup-action` from 1.30.1 to 1.42.0 ([#1891](https://github.com/NibiruChain/nibiru/pull/1891), [#1900](https://github.com/NibiruChain/nibiru/pull/1900), [#1923](https://github.com/NibiruChain/nibiru/pull/1923), [#1972](https://github.com/NibiruChain/nibiru/pull/1972), [#1974](https://github.com/NibiruChain/nibiru/pull/1974), [#1988](https://github.com/NibiruChain/nibiru/pull/1988), [#2043](https://github.com/NibiruChain/nibiru/pull/2043)) +- Bump `bufbuild/buf-setup-action` from 1.30.1 to 1.43.0 ([#1891](https://github.com/NibiruChain/nibiru/pull/1891), [#1900](https://github.com/NibiruChain/nibiru/pull/1900), [#1923](https://github.com/NibiruChain/nibiru/pull/1923), [#1972](https://github.com/NibiruChain/nibiru/pull/1972), [#1974](https://github.com/NibiruChain/nibiru/pull/1974), [#1988](https://github.com/NibiruChain/nibiru/pull/1988), [#2043](https://github.com/NibiruChain/nibiru/pull/2043), [#2057](https://github.com/NibiruChain/nibiru/pull/2057)) - Bump `axios` from 1.7.3 to 1.7.4 ([#2016](https://github.com/NibiruChain/nibiru/pull/2016)) ## [v1.5.0](https://github.com/NibiruChain/nibiru/releases/tag/v1.5.0) - 2024-06-21