From a03a0657c7430d32e6329d86de78bb4aab9a9aa7 Mon Sep 17 00:00:00 2001 From: gregnuj Date: Tue, 29 Aug 2023 10:23:48 -0500 Subject: [PATCH 01/13] Add codecommit.yml --- .github/workflows/codecommit.yml | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 .github/workflows/codecommit.yml diff --git a/.github/workflows/codecommit.yml b/.github/workflows/codecommit.yml deleted file mode 100644 index 12553b1c..00000000 --- a/.github/workflows/codecommit.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: sync to codecommit - -on: - push: - branches: - - '*' - -jobs: - sync: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v1 - with: - aws-access-key-id: ${{ secrets.CC_ACCESS_KEY }} - aws-secret-access-key: ${{ secrets.CC_SECRET_KEY }} - aws-region: us-east-1 - - - name: Sync up to CodeCommit - uses: terra-money/sync-up-to-codecommit-action@v1 - with: - repository_name: ${{ github.event.repository.name }} - aws_region: us-east-1 From 5aa73e255ebfec09ec73ba0bf34557dc8a427db4 Mon Sep 17 00:00:00 2001 From: emidev98 Date: Fri, 3 Nov 2023 16:29:28 +0200 Subject: [PATCH 02/13] wip: feeshare module --- app/ante/ante.go | 2 - app/app.go | 8 + app/post/post.go | 21 + proto/juno/feeshare/v1/events.proto | 18 + scripts/protocgen.sh | 3 +- x/feeshare/{ante => post}/expected_keepers.go | 0 x/feeshare/{ante/ante.go => post/post.go} | 127 +++--- .../{ante/ante_test.go => post/post_test.go} | 0 x/feeshare/types/events.pb.go | 389 ++++++++++++++++++ 9 files changed, 503 insertions(+), 65 deletions(-) create mode 100644 app/post/post.go create mode 100644 proto/juno/feeshare/v1/events.proto rename x/feeshare/{ante => post}/expected_keepers.go (100%) rename x/feeshare/{ante/ante.go => post/post.go} (67%) rename x/feeshare/{ante/ante_test.go => post/post_test.go} (100%) create mode 100644 x/feeshare/types/events.pb.go diff --git a/app/ante/ante.go b/app/ante/ante.go index bfd25243..c14b383d 100644 --- a/app/ante/ante.go +++ b/app/ante/ante.go @@ -6,7 +6,6 @@ import ( "github.com/skip-mev/pob/mempool" pobante "github.com/skip-mev/pob/x/builder/ante" pobkeeper "github.com/skip-mev/pob/x/builder/keeper" - feeshareante "github.com/terra-money/core/v2/x/feeshare/ante" feesharekeeper "github.com/terra-money/core/v2/x/feeshare/keeper" "github.com/cosmos/cosmos-sdk/client" @@ -73,7 +72,6 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { ante.NewValidateMemoDecorator(options.AccountKeeper), ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker), - feeshareante.NewFeeSharePayoutDecorator(options.BankKeeper, options.FeeShareKeeper), // SetPubKeyDecorator must be called before all signature verification decorators ante.NewSetPubKeyDecorator(options.AccountKeeper), ante.NewValidateSigCountDecorator(options.AccountKeeper), diff --git a/app/app.go b/app/app.go index 0baffbef..bdbe9aa8 100644 --- a/app/app.go +++ b/app/app.go @@ -13,6 +13,7 @@ import ( wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" "github.com/terra-money/core/v2/app/keepers" + "github.com/terra-money/core/v2/app/post" "github.com/terra-money/core/v2/app/rpc" tokenfactorybindings "github.com/terra-money/core/v2/x/tokenfactory/bindings" @@ -279,6 +280,12 @@ func NewTerraApp( if err != nil { panic(err) } + postHandler := post.NewPostHandler( + post.HandlerOptions{ + FeeShareKeeper: app.Keepers.FeeShareKeeper, + BankKeeper: app.Keepers.BankKeeper, + }, + ) // Create the proposal handler that will be used to build and validate blocks. handler := pobabci.NewProposalHandler( @@ -304,6 +311,7 @@ func NewTerraApp( app.SetInitChainer(app.InitChainer) app.SetBeginBlocker(app.BeginBlocker) app.SetAnteHandler(anteHandler) + app.SetPostHandler(postHandler) app.SetEndBlocker(app.EndBlocker) app.SetMempool(pobMempool) app.SetCheckTx(checkTxHandler.CheckTx()) diff --git a/app/post/post.go b/app/post/post.go new file mode 100644 index 00000000..aaf58440 --- /dev/null +++ b/app/post/post.go @@ -0,0 +1,21 @@ +package post + +import ( + feesharepost "github.com/terra-money/core/v2/x/feeshare/post" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type HandlerOptions struct { + FeeShareKeeper feesharepost.FeeShareKeeper + BankKeeper feesharepost.BankKeeper +} + +func NewPostHandler(options HandlerOptions) sdk.PostHandler { + + postDecorators := []sdk.PostDecorator{ + feesharepost.NewFeeSharePayoutDecorator(options.FeeShareKeeper, options.BankKeeper), + } + + return sdk.ChainPostDecorators(postDecorators...) +} diff --git a/proto/juno/feeshare/v1/events.proto b/proto/juno/feeshare/v1/events.proto new file mode 100644 index 00000000..ccefaa1f --- /dev/null +++ b/proto/juno/feeshare/v1/events.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; +package juno.feeshare.v1; + +import "cosmos/base/v1beta1/coin.proto"; +import "gogoproto/gogo.proto"; + +option go_package = "github.com/CosmosContracts/juno/x/feeshare/types"; + +// FeeShareEvent defines an instance that organizes fee distribution conditions for +// the owner of a given smart contract +message FeePayoutEvent { + // Address of the account that will receive the payout + string withdraw_address = 1; + // Amount of the payout + repeated cosmos.base.v1beta1.Coin fees_paid = 2 [ + (gogoproto.nullable) = false + ]; +} diff --git a/scripts/protocgen.sh b/scripts/protocgen.sh index 7b227cba..bedd668b 100755 --- a/scripts/protocgen.sh +++ b/scripts/protocgen.sh @@ -5,7 +5,7 @@ set -e echo "Generating gogo proto code" cd proto -proto_dirs=$(find ./osmosis -path -prune -o -name '*.proto' -print0 | xargs -0 -n1 dirname | sort | uniq) +proto_dirs=$(find ./osmosis ./juno -path -prune -o -name '*.proto' -print0 | xargs -0 -n1 dirname | sort | uniq) for dir in $proto_dirs; do for file in $(find "${dir}" -maxdepth 1 -name '*.proto'); do # this regex checks if a proto file has its go_package set to cosmossdk.io/api/... @@ -21,4 +21,5 @@ cd .. # move proto files to the right places cp -r github.com/osmosis-labs/osmosis/v17/* ./ +cp -r github.com/CosmosContracts/juno/* ./ rm -rf github.com diff --git a/x/feeshare/ante/expected_keepers.go b/x/feeshare/post/expected_keepers.go similarity index 100% rename from x/feeshare/ante/expected_keepers.go rename to x/feeshare/post/expected_keepers.go diff --git a/x/feeshare/ante/ante.go b/x/feeshare/post/post.go similarity index 67% rename from x/feeshare/ante/ante.go rename to x/feeshare/post/post.go index e447702a..c9d9bd1b 100644 --- a/x/feeshare/ante/ante.go +++ b/x/feeshare/post/post.go @@ -12,83 +12,57 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/cosmos/cosmos-sdk/x/authz" + "github.com/terra-money/core/v2/x/feeshare/types" feeshare "github.com/terra-money/core/v2/x/feeshare/types" ) -// FeeSharePayoutDecorator Run his after we already deduct the fee from the account with -// the ante.NewDeductFeeDecorator() decorator. We pull funds from the FeeCollector ModuleAccount type FeeSharePayoutDecorator struct { - bankKeeper BankKeeper feesharekeeper FeeShareKeeper + bankKeeper BankKeeper } -func NewFeeSharePayoutDecorator(bk BankKeeper, fs FeeShareKeeper) FeeSharePayoutDecorator { +func NewFeeSharePayoutDecorator(fs FeeShareKeeper, bk BankKeeper) FeeSharePayoutDecorator { return FeeSharePayoutDecorator{ - bankKeeper: bk, feesharekeeper: fs, + bankKeeper: bk, } } -func (fsd FeeSharePayoutDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { +// FeeSharePostHandler if the feeshare module is neabled +// takes the total fees paid for each transaction and +// split these fees equaly between all the contacts +// involved in the transactin based on the module params. +func (fsd FeeSharePayoutDecorator) PostHandle( + ctx sdk.Context, + tx sdk.Tx, simulate, + success bool, + next sdk.PostHandler, +) (newCtx sdk.Context, err error) { + // Check if fee share is enabled + params := fsd.feesharekeeper.GetParams(ctx) + if !params.EnableFeeShare { + return ctx, nil + } + // Parse the transactions to FeeTx + // to get the total fees paid in the future feeTx, ok := tx.(sdk.FeeTx) if !ok { return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") } - err = FeeSharePayout(ctx, fsd.bankKeeper, feeTx.GetFee(), fsd.feesharekeeper, tx.GetMsgs()) + err = fsd.feeSharePayout(ctx, fsd.bankKeeper, fsd.feesharekeeper, feeTx) if err != nil { return ctx, errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds, err.Error()) } - return next(ctx, tx, simulate) -} - -// FeePayLogic takes the total fees and splits them based on the governance params -// and the number of contracts we are executing on. -// This returns the amount of fees each contract developer should get. -// tested in ante_test.go -func FeePayLogic(fees sdk.Coins, govPercent sdk.Dec, numPairs int) sdk.Coins { - var splitFees sdk.Coins - for _, c := range fees.Sort() { - rewardAmount := govPercent.MulInt(c.Amount).QuoInt64(int64(numPairs)).RoundInt() - if !rewardAmount.IsZero() { - splitFees = splitFees.Add(sdk.NewCoin(c.Denom, rewardAmount)) - } - } - return splitFees -} - -type FeeSharePayoutEventOutput struct { - WithdrawAddress sdk.AccAddress `json:"withdraw_address"` - FeesPaid sdk.Coins `json:"fees_paid"` + return next(ctx, tx, simulate, success) } - -func addNewFeeSharePayoutsForMsg(ctx sdk.Context, fsk FeeShareKeeper, toPay *[]sdk.AccAddress, m sdk.Msg) error { - if msg, ok := m.(*wasmtypes.MsgExecuteContract); ok { - contractAddr, err := sdk.AccAddressFromBech32(msg.Contract) - if err != nil { - return err - } - - shareData, _ := fsk.GetFeeShare(ctx, contractAddr) - - withdrawAddr := shareData.GetWithdrawerAddr() - if withdrawAddr != nil && !withdrawAddr.Empty() { - *toPay = append(*toPay, withdrawAddr) - } - } - - return nil -} - -// FeeSharePayout takes the total fees and redistributes 50% (or param set) to the contract developers -// provided they opted-in to payments. -func FeeSharePayout(ctx sdk.Context, bankKeeper BankKeeper, totalFees sdk.Coins, fsk FeeShareKeeper, msgs []sdk.Msg) error { - params := fsk.GetParams(ctx) - if !params.EnableFeeShare { - return nil - } - +func (fsd FeeSharePayoutDecorator) feeSharePayout( + ctx sdk.Context, + bankKeeper BankKeeper, + feesharekeeper FeeShareKeeper, + msgs sdk.FeeTx, +) error { // Get valid withdraw addresses from contracts toPay := make([]sdk.AccAddress, 0) @@ -101,7 +75,7 @@ func FeeSharePayout(ctx sdk.Context, bankKeeper BankKeeper, totalFees sdk.Coins, return errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "cannot unmarshal authz exec msgs") } - err := addNewFeeSharePayoutsForMsg(ctx, fsk, &toPay, innerMsg) + err := addNewFeeSharePayoutsForMsg(ctx, feesharekeeper, &toPay, innerMsg) if err != nil { return err } @@ -118,7 +92,7 @@ func FeeSharePayout(ctx sdk.Context, bankKeeper BankKeeper, totalFees sdk.Coins, continue } - if err := addNewFeeSharePayoutsForMsg(ctx, fsk, &toPay, m); err != nil { + if err := addNewFeeSharePayoutsForMsg(ctx, feesharekeeper, &toPay, m); err != nil { return err } } @@ -132,9 +106,9 @@ func FeeSharePayout(ctx sdk.Context, bankKeeper BankKeeper, totalFees sdk.Coins, var fees sdk.Coins if len(params.AllowedDenoms) == 0 { // If empty, we allow all denoms to be used as payment - fees = totalFees + fees = txFees } else { - for _, fee := range totalFees.Sort() { + for _, fee := range txFees.Sort() { for _, allowed := range params.AllowedDenoms { if fee.Denom == allowed { fees = fees.Add(fee) @@ -145,7 +119,7 @@ func FeeSharePayout(ctx sdk.Context, bankKeeper BankKeeper, totalFees sdk.Coins, numPairs := len(toPay) - feesPaidOutput := make([]FeeSharePayoutEventOutput, numPairs) + feesPaidOutput := make([]types.FeePayoutEvent, numPairs) if numPairs > 0 { govPercent := params.DeveloperShares splitFees := FeePayLogic(fees, govPercent, numPairs) @@ -153,8 +127,8 @@ func FeeSharePayout(ctx sdk.Context, bankKeeper BankKeeper, totalFees sdk.Coins, // pay fees evenly between all withdraw addresses for i, withdrawAddr := range toPay { err := bankKeeper.SendCoinsFromModuleToAccount(ctx, authtypes.FeeCollectorName, withdrawAddr, splitFees) - feesPaidOutput[i] = FeeSharePayoutEventOutput{ - WithdrawAddress: withdrawAddr, + feesPaidOutput[i] = types.FeePayoutEvent{ + WithdrawAddress: withdrawAddr.String(), FeesPaid: splitFees, } @@ -177,3 +151,32 @@ func FeeSharePayout(ctx sdk.Context, bankKeeper BankKeeper, totalFees sdk.Coins, return nil } + +func FeePayLogic(fees sdk.Coins, govPercent sdk.Dec, numPairs int) sdk.Coins { + var splitFees sdk.Coins + for _, c := range fees.Sort() { + rewardAmount := govPercent.MulInt(c.Amount).QuoInt64(int64(numPairs)).RoundInt() + if !rewardAmount.IsZero() { + splitFees = splitFees.Add(sdk.NewCoin(c.Denom, rewardAmount)) + } + } + return splitFees +} + +func addNewFeeSharePayoutsForMsg(ctx sdk.Context, feesharekeeper FeeShareKeeper, toPay *[]sdk.AccAddress, m sdk.Msg) error { + if msg, ok := m.(*wasmtypes.MsgExecuteContract); ok { + contractAddr, err := sdk.AccAddressFromBech32(msg.Contract) + if err != nil { + return err + } + + shareData, _ := feesharekeeper.GetFeeShare(ctx, contractAddr) + + withdrawAddr := shareData.GetWithdrawerAddr() + if withdrawAddr != nil && !withdrawAddr.Empty() { + *toPay = append(*toPay, withdrawAddr) + } + } + + return nil +} diff --git a/x/feeshare/ante/ante_test.go b/x/feeshare/post/post_test.go similarity index 100% rename from x/feeshare/ante/ante_test.go rename to x/feeshare/post/post_test.go diff --git a/x/feeshare/types/events.pb.go b/x/feeshare/types/events.pb.go new file mode 100644 index 00000000..41ca7a98 --- /dev/null +++ b/x/feeshare/types/events.pb.go @@ -0,0 +1,389 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: juno/feeshare/v1/events.proto + +package types + +import ( + fmt "fmt" + types "github.com/cosmos/cosmos-sdk/types" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// FeeShareEvent defines an instance that organizes fee distribution conditions for +// the owner of a given smart contract +type FeePayoutEvent struct { + // Address of the account that will receive the payout + WithdrawAddress string `protobuf:"bytes,1,opt,name=withdraw_address,json=withdrawAddress,proto3" json:"withdraw_address,omitempty"` + // Amount of the payout + FeesPaid []types.Coin `protobuf:"bytes,2,rep,name=fees_paid,json=feesPaid,proto3" json:"fees_paid"` +} + +func (m *FeePayoutEvent) Reset() { *m = FeePayoutEvent{} } +func (m *FeePayoutEvent) String() string { return proto.CompactTextString(m) } +func (*FeePayoutEvent) ProtoMessage() {} +func (*FeePayoutEvent) Descriptor() ([]byte, []int) { + return fileDescriptor_19637fb89a9eac93, []int{0} +} +func (m *FeePayoutEvent) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *FeePayoutEvent) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_FeePayoutEvent.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *FeePayoutEvent) XXX_Merge(src proto.Message) { + xxx_messageInfo_FeePayoutEvent.Merge(m, src) +} +func (m *FeePayoutEvent) XXX_Size() int { + return m.Size() +} +func (m *FeePayoutEvent) XXX_DiscardUnknown() { + xxx_messageInfo_FeePayoutEvent.DiscardUnknown(m) +} + +var xxx_messageInfo_FeePayoutEvent proto.InternalMessageInfo + +func (m *FeePayoutEvent) GetWithdrawAddress() string { + if m != nil { + return m.WithdrawAddress + } + return "" +} + +func (m *FeePayoutEvent) GetFeesPaid() []types.Coin { + if m != nil { + return m.FeesPaid + } + return nil +} + +func init() { + proto.RegisterType((*FeePayoutEvent)(nil), "juno.feeshare.v1.FeePayoutEvent") +} + +func init() { proto.RegisterFile("juno/feeshare/v1/events.proto", fileDescriptor_19637fb89a9eac93) } + +var fileDescriptor_19637fb89a9eac93 = []byte{ + // 267 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x44, 0x90, 0xb1, 0x4e, 0xc3, 0x30, + 0x18, 0x84, 0x13, 0x40, 0x88, 0x06, 0x09, 0xaa, 0x88, 0xa1, 0x54, 0xc2, 0x54, 0x4c, 0x65, 0xb1, + 0x09, 0xac, 0x2c, 0x34, 0x82, 0x81, 0xa9, 0xea, 0xc8, 0x52, 0x39, 0xf1, 0x4f, 0x62, 0xa4, 0xfa, + 0x8f, 0x6c, 0x27, 0x25, 0x6f, 0xc1, 0x63, 0x75, 0xec, 0xc8, 0x84, 0x50, 0xf2, 0x22, 0xc8, 0x09, + 0x55, 0xb7, 0xd3, 0x9d, 0xcf, 0xfe, 0x7c, 0xc1, 0xd5, 0x47, 0xa9, 0x90, 0xbd, 0x03, 0x98, 0x9c, + 0x6b, 0x60, 0x55, 0xc4, 0xa0, 0x02, 0x65, 0x0d, 0x2d, 0x34, 0x5a, 0x0c, 0x87, 0x2e, 0xa6, 0xbb, + 0x98, 0x56, 0xd1, 0x98, 0xa4, 0x68, 0x56, 0x68, 0x58, 0xc2, 0x8d, 0x3b, 0x9e, 0x80, 0xe5, 0x11, + 0x4b, 0x51, 0xaa, 0xbe, 0x31, 0xbe, 0xc8, 0x30, 0xc3, 0x4e, 0x32, 0xa7, 0x7a, 0xf7, 0xa6, 0x0e, + 0xce, 0x5e, 0x00, 0xe6, 0xbc, 0xc6, 0xd2, 0x3e, 0xbb, 0x07, 0xc2, 0xdb, 0x60, 0xb8, 0x96, 0x36, + 0x17, 0x9a, 0xaf, 0x97, 0x5c, 0x08, 0x0d, 0xc6, 0x8c, 0xfc, 0x89, 0x3f, 0x1d, 0x2c, 0xce, 0x77, + 0xfe, 0x53, 0x6f, 0x87, 0x8f, 0xc1, 0xc0, 0x11, 0x2c, 0x0b, 0x2e, 0xc5, 0xe8, 0x60, 0x72, 0x38, + 0x3d, 0xbd, 0xbf, 0xa4, 0x3d, 0x06, 0x75, 0x18, 0xf4, 0x1f, 0x83, 0xc6, 0x28, 0xd5, 0xec, 0x68, + 0xf3, 0x73, 0xed, 0x2d, 0x4e, 0x5c, 0x63, 0xce, 0xa5, 0x98, 0xbd, 0x6e, 0x1a, 0xe2, 0x6f, 0x1b, + 0xe2, 0xff, 0x36, 0xc4, 0xff, 0x6a, 0x89, 0xb7, 0x6d, 0x89, 0xf7, 0xdd, 0x12, 0xef, 0xed, 0x2e, + 0x93, 0x36, 0x2f, 0x13, 0x9a, 0xe2, 0x8a, 0xc5, 0xdd, 0x75, 0x31, 0x2a, 0xab, 0x79, 0x6a, 0x0d, + 0xeb, 0x66, 0xf9, 0xdc, 0x0f, 0x63, 0xeb, 0x02, 0x4c, 0x72, 0xdc, 0xfd, 0xe6, 0xe1, 0x2f, 0x00, + 0x00, 0xff, 0xff, 0xc2, 0xe4, 0xe9, 0x71, 0x36, 0x01, 0x00, 0x00, +} + +func (m *FeePayoutEvent) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *FeePayoutEvent) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *FeePayoutEvent) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.FeesPaid) > 0 { + for iNdEx := len(m.FeesPaid) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.FeesPaid[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.WithdrawAddress) > 0 { + i -= len(m.WithdrawAddress) + copy(dAtA[i:], m.WithdrawAddress) + i = encodeVarintEvents(dAtA, i, uint64(len(m.WithdrawAddress))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintEvents(dAtA []byte, offset int, v uint64) int { + offset -= sovEvents(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *FeePayoutEvent) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.WithdrawAddress) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + if len(m.FeesPaid) > 0 { + for _, e := range m.FeesPaid { + l = e.Size() + n += 1 + l + sovEvents(uint64(l)) + } + } + return n +} + +func sovEvents(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozEvents(x uint64) (n int) { + return sovEvents(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *FeePayoutEvent) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: FeePayoutEvent: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: FeePayoutEvent: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field WithdrawAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.WithdrawAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FeesPaid", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FeesPaid = append(m.FeesPaid, types.Coin{}) + if err := m.FeesPaid[len(m.FeesPaid)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipEvents(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowEvents + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowEvents + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowEvents + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthEvents + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupEvents + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthEvents + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthEvents = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowEvents = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupEvents = fmt.Errorf("proto: unexpected end of group") +) From 535298391809cd09c6a25b826d3832c807687839 Mon Sep 17 00:00:00 2001 From: emidev98 Date: Mon, 6 Nov 2023 12:16:00 +0200 Subject: [PATCH 03/13] feat: feeshare on post.go --- app/app_test/test_helpers.go | 10 + .../src/modules/feeshare.test.ts | 8 +- x/feeshare/post/post.go | 212 +++++++++--------- x/feeshare/post/post_test.go | 183 ++++++++++++--- 4 files changed, 277 insertions(+), 136 deletions(-) diff --git a/app/app_test/test_helpers.go b/app/app_test/test_helpers.go index 90bdc805..7d77b1cd 100644 --- a/app/app_test/test_helpers.go +++ b/app/app_test/test_helpers.go @@ -121,6 +121,16 @@ func (s *AppTestSuite) FundAcc(acc sdk.AccAddress, amounts sdk.Coins) (err error return s.App.Keepers.BankKeeper.SendCoinsFromModuleToAccount(s.Ctx, minttypes.ModuleName, acc, amounts) } +// FundAcc funds target address with specified amount. +func (s *AppTestSuite) FundModule(moduleAccount string, amounts sdk.Coins) (err error) { + s.Require().NoError(err) + if err := s.App.Keepers.BankKeeper.MintCoins(s.Ctx, minttypes.ModuleName, amounts); err != nil { + return err + } + + return s.App.Keepers.BankKeeper.SendCoinsFromModuleToModule(s.Ctx, minttypes.ModuleName, moduleAccount, amounts) +} + func SetupGenesisValSet( valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, diff --git a/integration-tests/src/modules/feeshare.test.ts b/integration-tests/src/modules/feeshare.test.ts index b1db4e19..fa6e62c7 100644 --- a/integration-tests/src/modules/feeshare.test.ts +++ b/integration-tests/src/modules/feeshare.test.ts @@ -124,7 +124,8 @@ describe("Feeshare Module (https://github.com/terra-money/core/tree/release/v2.6 // Check that querying all feeshares returns at least one feeshares let feesharesByWallet = await LCD.chain1.feeshare.feeshares("test-1"); expect(feesharesByWallet.feeshare.length).toBeGreaterThan(0); - + await blockInclusion(); + // Send an execute message to the reflect contract let msgExecute = new MsgExecuteContract( feeshareAccountAddress, @@ -145,6 +146,8 @@ describe("Feeshare Module (https://github.com/terra-money/core/tree/release/v2.6 // Check the tx logs have the expected events txResult = await LCD.chain1.tx.txInfo(result.txhash, "test-1") as any; + console.log(result.txhash) + console.log(JSON.stringify(txResult.logs)) expect(txResult.logs[0].events) .toMatchObject([{ "type": "message", @@ -180,7 +183,8 @@ describe("Feeshare Module (https://github.com/terra-money/core/tree/release/v2.6 }] } ]) - + await blockInclusion() + // Query the random account (new owner of the contract) // and validate that the account has received 50% of the fees const bankAmount = await LCD.chain1.bank.balance(randomAccountAddress); diff --git a/x/feeshare/post/post.go b/x/feeshare/post/post.go index c9d9bd1b..bf72ce94 100644 --- a/x/feeshare/post/post.go +++ b/x/feeshare/post/post.go @@ -1,18 +1,15 @@ package ante import ( - "encoding/json" - - wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + "slices" errorsmod "cosmossdk.io/errors" + "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/cosmos/cosmos-sdk/x/authz" - "github.com/terra-money/core/v2/x/feeshare/types" feeshare "github.com/terra-money/core/v2/x/feeshare/types" ) @@ -34,149 +31,152 @@ func NewFeeSharePayoutDecorator(fs FeeShareKeeper, bk BankKeeper) FeeSharePayout // involved in the transactin based on the module params. func (fsd FeeSharePayoutDecorator) PostHandle( ctx sdk.Context, - tx sdk.Tx, simulate, + tx sdk.Tx, + simulate bool, success bool, next sdk.PostHandler, ) (newCtx sdk.Context, err error) { - // Check if fee share is enabled + // Check if fee share is enabled and shares + // to be distribute are greater than zero. params := fsd.feesharekeeper.GetParams(ctx) - if !params.EnableFeeShare { - return ctx, nil + if !params.EnableFeeShare || params.DeveloperShares.IsZero() { + return next(ctx, tx, simulate, success) } - // Parse the transactions to FeeTx - // to get the total fees paid in the future + + // Parse the transactions to FeeTx to get the total fees paid feeTx, ok := tx.(sdk.FeeTx) if !ok { return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") } + if feeTx.GetFee().Empty() || feeTx.GetFee().IsZero() { + return next(ctx, tx, simulate, success) + } - err = fsd.feeSharePayout(ctx, fsd.bankKeeper, fsd.feesharekeeper, feeTx) + err = fsd.FeeSharePayout(ctx, feeTx.GetFee(), params.DeveloperShares, params.AllowedDenoms) if err != nil { - return ctx, errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds, err.Error()) + return ctx, errorsmod.Wrapf(sdkerrors.ErrLogic, err.Error()) } return next(ctx, tx, simulate, success) } -func (fsd FeeSharePayoutDecorator) feeSharePayout( - ctx sdk.Context, - bankKeeper BankKeeper, - feesharekeeper FeeShareKeeper, - msgs sdk.FeeTx, -) error { - // Get valid withdraw addresses from contracts - toPay := make([]sdk.AccAddress, 0) - - // validAuthz checks if the msg is an authz exec msg and if so, call the validExecuteMsg for each - // inner msg. If it is a CosmWasm execute message, that logic runs for nested functions. - validAuthz := func(execMsg *authz.MsgExec) error { - for _, v := range execMsg.Msgs { - var innerMsg sdk.Msg - if err := json.Unmarshal(v.Value, &innerMsg); err != nil { - return errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "cannot unmarshal authz exec msgs") - } - err := addNewFeeSharePayoutsForMsg(ctx, feesharekeeper, &toPay, innerMsg) - if err != nil { - return err - } - } +// FeeSharePayout takes the total fees paid for a transaction and +// split these fees equaly between all the contacts involved in the +// transaction based on the module params. +func (fsd FeeSharePayoutDecorator) FeeSharePayout(ctx sdk.Context, txFees sdk.Coins, devShares types.Dec, allowedDenoms []string) (err error) { + events := ctx.EventManager().Events() + contractAddresses, err := ExtractContractAddrs(events) + if err != nil { + return err + } + if len(contractAddresses) == 0 { + return err + } - return nil + withdrawerAddrs, err := GetWithdrawalAddressFromContract(ctx, contractAddresses, fsd.feesharekeeper) + if err != nil { + return err + } + if len(withdrawerAddrs) == 0 { + return err } - for _, m := range msgs { - if msg, ok := m.(*authz.MsgExec); ok { - if err := validAuthz(msg); err != nil { - return err - } - continue - } + feeToBePaid := CalculateFee(txFees, devShares, len(withdrawerAddrs), allowedDenoms) + if feeToBePaid.IsZero() { + return err + } - if err := addNewFeeSharePayoutsForMsg(ctx, feesharekeeper, &toPay, m); err != nil { + // pay the fees to the withdrawer addresses + for _, withdrawerAddrs := range withdrawerAddrs { + err = fsd.bankKeeper.SendCoinsFromModuleToAccount(ctx, authtypes.FeeCollectorName, withdrawerAddrs, feeToBePaid) + if err != nil { return err } + ctx.EventManager().EmitTypedEvent( + &feeshare.FeePayoutEvent{ + WithdrawAddress: withdrawerAddrs.String(), + FeesPaid: feeToBePaid, + }, + ) } - // Do nothing if no one needs payment - if len(toPay) == 0 { - return nil - } + return err +} - // Get only allowed governance fees to be paid (helps for taxes) - var fees sdk.Coins - if len(params.AllowedDenoms) == 0 { - // If empty, we allow all denoms to be used as payment - fees = txFees - } else { - for _, fee := range txFees.Sort() { - for _, allowed := range params.AllowedDenoms { - if fee.Denom == allowed { - fees = fees.Add(fee) - } +// Iterate the events and search for the execute event then iterate the +// attributes in search for _contract_address and get the value which +// is the contract address to search for all the beneficiaries, info: +// https://github.com/CosmWasm/wasmd/blob/main/EVENTS.md#validation-rules +func ExtractContractAddrs(events sdk.Events) ([]string, error) { + contractAddresses := []string{} + for _, ev := range events { + if ev.Type != "execute" { + continue + } + + for _, attr := range ev.Attributes { + if attr.Key != "_contract_address" { + continue } + // if the contract address has already been + // added just skip it to avoid duplicates + if slices.Contains(contractAddresses, attr.Value) { + continue + } + + contractAddresses = append(contractAddresses, attr.Value) } } - numPairs := len(toPay) + return contractAddresses, nil +} - feesPaidOutput := make([]types.FeePayoutEvent, numPairs) - if numPairs > 0 { - govPercent := params.DeveloperShares - splitFees := FeePayLogic(fees, govPercent, numPairs) +// Iterate the contract addresses and get the +// withdrawer address from the module store +func GetWithdrawalAddressFromContract(ctx sdk.Context, contractAddresses []string, fsk FeeShareKeeper) ([]sdk.AccAddress, error) { + var withdrawerAddrs []sdk.AccAddress - // pay fees evenly between all withdraw addresses - for i, withdrawAddr := range toPay { - err := bankKeeper.SendCoinsFromModuleToAccount(ctx, authtypes.FeeCollectorName, withdrawAddr, splitFees) - feesPaidOutput[i] = types.FeePayoutEvent{ - WithdrawAddress: withdrawAddr.String(), - FeesPaid: splitFees, - } + for _, contractAddr := range contractAddresses { + parsedContractAddr, err := sdk.AccAddressFromBech32(contractAddr) + if err != nil { + return nil, err + } - if err != nil { - return errorsmod.Wrapf(feeshare.ErrFeeSharePayment, "failed to pay fees to contract developer: %s", err.Error()) - } + shareData, hasfeeshare := fsk.GetFeeShare(ctx, parsedContractAddr) + + if !hasfeeshare { + continue } - } - bz, err := json.Marshal(feesPaidOutput) - if err != nil { - return errorsmod.Wrapf(feeshare.ErrFeeSharePayment, "failed to marshal feesPaidOutput: %s", err.Error()) + withdrawerAddr := shareData.GetWithdrawerAddr() + if withdrawerAddr != nil && !withdrawerAddr.Empty() { + withdrawerAddrs = append(withdrawerAddrs, withdrawerAddr) + } } - ctx.EventManager().EmitEvent( - sdk.NewEvent( - feeshare.EventTypePayoutFeeShare, - sdk.NewAttribute(feeshare.AttributeWithdrawPayouts, string(bz))), - ) - - return nil + return withdrawerAddrs, nil } -func FeePayLogic(fees sdk.Coins, govPercent sdk.Dec, numPairs int) sdk.Coins { +// CalculateFee takes the total fees paid for a transaction and split +// these fees equaly between all number of pairs considering allwoedDenoms +func CalculateFee(fees sdk.Coins, govPercent sdk.Dec, pairs int, allowedDenoms []string) sdk.Coins { + var alloedFeesDenoms sdk.Coins + if len(allowedDenoms) == 0 { + alloedFeesDenoms = fees + } else { + for _, fee := range fees { + if slices.Contains(allowedDenoms, fee.Denom) { + alloedFeesDenoms = alloedFeesDenoms.Add(fee) + } + } + } + var splitFees sdk.Coins - for _, c := range fees.Sort() { - rewardAmount := govPercent.MulInt(c.Amount).QuoInt64(int64(numPairs)).RoundInt() + for _, c := range alloedFeesDenoms.Sort() { + rewardAmount := govPercent.MulInt(c.Amount).QuoInt64(int64(pairs)).RoundInt() if !rewardAmount.IsZero() { splitFees = splitFees.Add(sdk.NewCoin(c.Denom, rewardAmount)) } } return splitFees } - -func addNewFeeSharePayoutsForMsg(ctx sdk.Context, feesharekeeper FeeShareKeeper, toPay *[]sdk.AccAddress, m sdk.Msg) error { - if msg, ok := m.(*wasmtypes.MsgExecuteContract); ok { - contractAddr, err := sdk.AccAddressFromBech32(msg.Contract) - if err != nil { - return err - } - - shareData, _ := feesharekeeper.GetFeeShare(ctx, contractAddr) - - withdrawAddr := shareData.GetWithdrawerAddr() - if withdrawAddr != nil && !withdrawAddr.Empty() { - *toPay = append(*toPay, withdrawAddr) - } - } - - return nil -} diff --git a/x/feeshare/post/post_test.go b/x/feeshare/post/post_test.go index 9aa1040c..03fa8e5c 100644 --- a/x/feeshare/post/post_test.go +++ b/x/feeshare/post/post_test.go @@ -7,26 +7,149 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" - ante "github.com/terra-money/core/v2/x/feeshare/ante" + app "github.com/terra-money/core/v2/app/app_test" + post "github.com/terra-money/core/v2/x/feeshare/post" + "github.com/terra-money/core/v2/x/feeshare/types" ) type AnteTestSuite struct { - suite.Suite + app.AppTestSuite } func TestAnteSuite(t *testing.T) { suite.Run(t, new(AnteTestSuite)) } -func (suite *AnteTestSuite) TestFeeLogic() { - // We expect all to pass - feeCoins := sdk.NewCoins(sdk.NewCoin("ujuno", sdk.NewInt(500)), sdk.NewCoin("utoken", sdk.NewInt(250))) +func (suite *AnteTestSuite) TestExtractContractAddrs() { + testCases := []struct { + name string + events sdk.Events + expectedAddresses []string + }{ + { + "no events", + sdk.Events{}, + []string{}, + }, + { + "no execute events", + sdk.Events{ + sdk.NewEvent("transfer", + sdk.NewAttribute("_contract_address", "address1"), + ), + }, + []string{}, + }, + { + "single execute event", + sdk.Events{ + sdk.NewEvent("execute", + sdk.NewAttribute("_contract_address", "address1"), + ), + }, + []string{"address1"}, + }, + { + "multiple execute events", + sdk.Events{ + sdk.NewEvent("execute", + sdk.NewAttribute("_contract_address", "address1"), + ), + sdk.NewEvent("execute", + sdk.NewAttribute("_contract_address", "address2"), + ), + }, + []string{"address1", "address2"}, + }, + { + "duplicate execute events", + sdk.Events{ + sdk.NewEvent("execute", + sdk.NewAttribute("_contract_address", "address1"), + ), + sdk.NewEvent("execute", + sdk.NewAttribute("_contract_address", "address1"), + ), + }, + []string{"address1"}, + }, + } + + for _, tc := range testCases { + addresses, err := post.ExtractContractAddrs(tc.events) + suite.Require().NoError(err, tc.name) + suite.Require().Equal(tc.expectedAddresses, addresses, tc.name) + } +} + +func (suite *AnteTestSuite) TestGetWithdrawalAddressFromContract() { + suite.Setup() + + feeshareKeeper := suite.AppTestSuite.App.Keepers.FeeShareKeeper + feeshareKeeper.SetFeeShare(suite.Ctx, types.FeeShare{ + ContractAddress: "terra1jwyzzsaag4t0evnuukc35ysyrx9arzdde2kg9cld28alhjurtthq0prs2s", + DeployerAddress: "", + WithdrawerAddress: "terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je", + }) + feeshareKeeper.SetFeeShare(suite.Ctx, types.FeeShare{ + ContractAddress: "terra1mdpvgjc8jmv60a4x68nggsh9w8uyv69sqls04a76m9med5hsqmwsse8sxa", + DeployerAddress: "", + WithdrawerAddress: "", + }) + + testCases := []struct { + name string + contractAddresses []string + expectedWithdrawerAddrs []sdk.AccAddress + expectErr bool + }{ + { + "valid contract addresses", + []string{"terra1jwyzzsaag4t0evnuukc35ysyrx9arzdde2kg9cld28alhjurtthq0prs2s"}, + []sdk.AccAddress{ + sdk.MustAccAddressFromBech32("terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je"), + }, + false, + }, + { + "without withdrawer contract addresses", + []string{"terra1mdpvgjc8jmv60a4x68nggsh9w8uyv69sqls04a76m9med5hsqmwsse8sxa"}, + []sdk.AccAddress(nil), + false, + }, + { + "invalid contract address", + []string{"invalidAddress"}, + nil, + true, + }, + } + + for _, tc := range testCases { + withdrawerAddrs, err := post.GetWithdrawalAddressFromContract( + suite.Ctx, + tc.contractAddresses, + feeshareKeeper, + ) + + if tc.expectErr { + suite.Require().Error(err, tc.name) + } else { + suite.Require().NoError(err, tc.name) + suite.Require().Equal(tc.expectedWithdrawerAddrs, withdrawerAddrs, tc.name) + } + } +} + +func (suite *AnteTestSuite) TestCalculateFee() { + feeCoins := sdk.NewCoins(sdk.NewCoin("uluna", sdk.NewInt(500)), sdk.NewCoin("utoken", sdk.NewInt(250))) testCases := []struct { name string incomingFee sdk.Coins govPercent sdk.Dec numContracts int + allowdDenoms []string expectedFeePayment sdk.Coins }{ { @@ -34,82 +157,86 @@ func (suite *AnteTestSuite) TestFeeLogic() { feeCoins, sdk.NewDecWithPrec(100, 2), 1, - sdk.NewCoins(sdk.NewCoin("ujuno", sdk.NewInt(500)), sdk.NewCoin("utoken", sdk.NewInt(250))), + []string{}, + sdk.NewCoins(sdk.NewCoin("uluna", sdk.NewInt(500)), sdk.NewCoin("utoken", sdk.NewInt(250))), }, { "100% fee / 2 contracts", feeCoins, sdk.NewDecWithPrec(100, 2), 2, - sdk.NewCoins(sdk.NewCoin("ujuno", sdk.NewInt(250)), sdk.NewCoin("utoken", sdk.NewInt(125))), + []string{}, + sdk.NewCoins(sdk.NewCoin("uluna", sdk.NewInt(250)), sdk.NewCoin("utoken", sdk.NewInt(125))), }, { - "100% fee / 10 contracts", + "100% fee / 10 contracts / 1 allowed denom", feeCoins, sdk.NewDecWithPrec(100, 2), 10, - sdk.NewCoins(sdk.NewCoin("ujuno", sdk.NewInt(50)), sdk.NewCoin("utoken", sdk.NewInt(25))), + []string{"uluna"}, + sdk.NewCoins(sdk.NewCoin("uluna", sdk.NewInt(50))), }, { "67% fee / 7 contracts", feeCoins, sdk.NewDecWithPrec(67, 2), 7, - sdk.NewCoins(sdk.NewCoin("ujuno", sdk.NewInt(48)), sdk.NewCoin("utoken", sdk.NewInt(24))), + []string{}, + sdk.NewCoins(sdk.NewCoin("uluna", sdk.NewInt(48)), sdk.NewCoin("utoken", sdk.NewInt(24))), }, { - "50% fee / 1 contracts", + "50% fee / 1 contracts / 1 allowed denom", feeCoins, sdk.NewDecWithPrec(50, 2), 1, - sdk.NewCoins(sdk.NewCoin("ujuno", sdk.NewInt(250)), sdk.NewCoin("utoken", sdk.NewInt(125))), + []string{"utoken"}, + sdk.NewCoins(sdk.NewCoin("utoken", sdk.NewInt(125))), }, { - "50% fee / 2 contracts", + "50% fee / 2 contracts / 2 allowed denoms", feeCoins, sdk.NewDecWithPrec(50, 2), 2, - sdk.NewCoins(sdk.NewCoin("ujuno", sdk.NewInt(125)), sdk.NewCoin("utoken", sdk.NewInt(62))), + []string{"uluna", "utoken"}, + sdk.NewCoins(sdk.NewCoin("uluna", sdk.NewInt(125)), sdk.NewCoin("utoken", sdk.NewInt(62))), }, { "50% fee / 3 contracts", feeCoins, sdk.NewDecWithPrec(50, 2), 3, - sdk.NewCoins(sdk.NewCoin("ujuno", sdk.NewInt(83)), sdk.NewCoin("utoken", sdk.NewInt(42))), + []string{}, + sdk.NewCoins(sdk.NewCoin("uluna", sdk.NewInt(83)), sdk.NewCoin("utoken", sdk.NewInt(42))), }, { "25% fee / 2 contracts", feeCoins, sdk.NewDecWithPrec(25, 2), 2, - sdk.NewCoins(sdk.NewCoin("ujuno", sdk.NewInt(62)), sdk.NewCoin("utoken", sdk.NewInt(31))), + []string{}, + sdk.NewCoins(sdk.NewCoin("uluna", sdk.NewInt(62)), sdk.NewCoin("utoken", sdk.NewInt(31))), }, { - "15% fee / 3 contracts", + "15% fee / 3 contracts / inexistent denom", feeCoins, sdk.NewDecWithPrec(15, 2), 3, - sdk.NewCoins(sdk.NewCoin("ujuno", sdk.NewInt(25)), sdk.NewCoin("utoken", sdk.NewInt(12))), + []string{"ubtc"}, + sdk.Coins(nil), }, { "1% fee / 2 contracts", feeCoins, sdk.NewDecWithPrec(1, 2), 2, - sdk.NewCoins(sdk.NewCoin("ujuno", sdk.NewInt(2)), sdk.NewCoin("utoken", sdk.NewInt(1))), + []string{}, + sdk.NewCoins(sdk.NewCoin("uluna", sdk.NewInt(2)), sdk.NewCoin("utoken", sdk.NewInt(1))), }, } for _, tc := range testCases { - coins := ante.FeePayLogic(tc.incomingFee, tc.govPercent, tc.numContracts) - - for _, coin := range coins { - for _, expectedCoin := range tc.expectedFeePayment { - if coin.Denom == expectedCoin.Denom { - suite.Require().Equal(expectedCoin.Amount.Int64(), coin.Amount.Int64(), tc.name) - } - } - } + feeToBePaid := post.CalculateFee(tc.incomingFee, tc.govPercent, tc.numContracts, tc.allowdDenoms) + + suite.Require().Equal(tc.expectedFeePayment, feeToBePaid, tc.name) } } From 7facf2d639f9a994ac221042b8d7c8e58304f781 Mon Sep 17 00:00:00 2001 From: emidev98 Date: Wed, 8 Nov 2023 10:53:45 +0200 Subject: [PATCH 04/13] feat: feeshre custom wasm module --- app/app.go | 8 +-- app/keepers/keepers.go | 7 ++- app/modules.go | 7 ++- custom/wasmd/keeper/keeper.go | 72 ++++++++++++++++++++++++ custom/wasmd/keeper/msg_server.go | 93 +++++++++++++++++++++++++++++++ custom/wasmd/module.go | 64 +++++++++++++++++++++ custom/wasmd/types/interface.go | 34 +++++++++++ x/feeshare/keeper/keeper.go | 6 +- 8 files changed, 278 insertions(+), 13 deletions(-) create mode 100644 custom/wasmd/keeper/keeper.go create mode 100644 custom/wasmd/keeper/msg_server.go create mode 100644 custom/wasmd/module.go create mode 100644 custom/wasmd/types/interface.go diff --git a/app/app.go b/app/app.go index bdbe9aa8..bba6d554 100644 --- a/app/app.go +++ b/app/app.go @@ -45,7 +45,6 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" vestingexported "github.com/cosmos/cosmos-sdk/x/auth/vesting/exported" authzmodule "github.com/cosmos/cosmos-sdk/x/authz/module" - "github.com/cosmos/cosmos-sdk/x/bank" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/cosmos/cosmos-sdk/x/capability" "github.com/cosmos/cosmos-sdk/x/crisis" @@ -53,6 +52,8 @@ import ( distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" "github.com/cosmos/cosmos-sdk/x/evidence" feegrantmodule "github.com/cosmos/cosmos-sdk/x/feegrant/module" + custombankmodule "github.com/terra-money/core/v2/custom/bank" + customwasmodule "github.com/terra-money/core/v2/custom/wasmd" "github.com/cosmos/cosmos-sdk/x/gov" govclient "github.com/cosmos/cosmos-sdk/x/gov/client" @@ -78,7 +79,6 @@ import ( ibc "github.com/cosmos/ibc-go/v7/modules/core" ibcclientclient "github.com/cosmos/ibc-go/v7/modules/core/02-client/client" - "github.com/CosmWasm/wasmd/x/wasm" wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" "github.com/terra-money/alliance/x/alliance" @@ -549,7 +549,7 @@ func (app *TerraApp) SimulationManager() *module.SimulationManager { sm := module.NewSimulationManager( auth.NewAppModule(appCodec, app.Keepers.AccountKeeper, authsims.RandomGenesisAccounts, app.Keepers.GetSubspace(authtypes.ModuleName)), authzmodule.NewAppModule(appCodec, app.Keepers.AuthzKeeper, app.Keepers.AccountKeeper, app.Keepers.BankKeeper, app.interfaceRegistry), - bank.NewAppModule(appCodec, app.Keepers.BankKeeper, app.Keepers.AccountKeeper, app.Keepers.GetSubspace(banktypes.ModuleName)), + custombankmodule.NewAppModule(appCodec, app.Keepers.BankKeeper, app.Keepers.AccountKeeper, app.Keepers.GetSubspace(banktypes.ModuleName)), capability.NewAppModule(appCodec, *app.Keepers.CapabilityKeeper, false), feegrantmodule.NewAppModule(appCodec, app.Keepers.AccountKeeper, app.Keepers.BankKeeper, app.Keepers.FeeGrantKeeper, app.interfaceRegistry), gov.NewAppModule(appCodec, &app.Keepers.GovKeeper, app.Keepers.AccountKeeper, app.Keepers.BankKeeper, app.Keepers.GetSubspace(govtypes.ModuleName)), @@ -564,7 +564,7 @@ func (app *TerraApp) SimulationManager() *module.SimulationManager { ibcfee.NewAppModule(app.Keepers.IBCFeeKeeper), ica.NewAppModule(&app.Keepers.ICAControllerKeeper, &app.Keepers.ICAHostKeeper), router.NewAppModule(&app.Keepers.RouterKeeper), - wasm.NewAppModule(appCodec, &app.Keepers.WasmKeeper, app.Keepers.StakingKeeper, app.Keepers.AccountKeeper, app.Keepers.BankKeeper, app.BaseApp.MsgServiceRouter(), app.Keepers.GetSubspace(wasmtypes.ModuleName)), + customwasmodule.NewAppModule(appCodec, &app.Keepers.WasmKeeper, app.Keepers.StakingKeeper, app.Keepers.AccountKeeper, app.Keepers.BankKeeper, app.BaseApp.MsgServiceRouter(), app.Keepers.GetSubspace(wasmtypes.ModuleName)), alliance.NewAppModule(appCodec, app.Keepers.AllianceKeeper, app.Keepers.StakingKeeper, app.Keepers.AccountKeeper, app.Keepers.BankKeeper, app.interfaceRegistry, app.Keepers.GetSubspace(alliancetypes.ModuleName)), feeshare.NewAppModule(app.Keepers.FeeShareKeeper, app.Keepers.AccountKeeper, app.GetSubspace(feesharetypes.ModuleName)), ) diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index 9c9cef4f..4fe15470 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -85,6 +85,7 @@ import ( wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" icahost "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/host" + customwasmkeeper "github.com/terra-money/core/v2/custom/wasmd/keeper" icacontroller "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/controller" icacontrollerkeeper "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/controller/keeper" @@ -173,7 +174,7 @@ type TerraAppKeepers struct { ScopedICAHostKeeper capabilitykeeper.ScopedKeeper ScopedICQKeeper capabilitykeeper.ScopedKeeper - WasmKeeper wasmkeeper.Keeper + WasmKeeper customwasmkeeper.Keeper scopedWasmKeeper capabilitykeeper.ScopedKeeper // BuilderKeeper is the keeper that handles processing auction transactions @@ -453,7 +454,7 @@ func NewTerraAppKeepers( panic("error while reading wasm config: " + err.Error()) } availableCapabilities := "iterator,staking,stargate,cosmwasm_1_1,cosmwasm_1_2,cosmwasm_1_3,cosmwasm_1_4,token_factory" - keepers.WasmKeeper = wasmkeeper.NewKeeper( + keepers.WasmKeeper = customwasmkeeper.NewKeeper( appCodec, keys[wasmtypes.StoreKey], keepers.AccountKeeper, @@ -474,7 +475,7 @@ func NewTerraAppKeepers( wasmOpts..., ) - keepers.Ics20WasmHooks.ContractKeeper = &keepers.WasmKeeper + keepers.Ics20WasmHooks.ContractKeeper = keepers.WasmKeeper.Keeper // Setup the contract keepers.WasmKeeper before the // hook for the BankKeeper othrwise the WasmKeeper // will be nil inside the hooks. diff --git a/app/modules.go b/app/modules.go index 6bd9e2ab..710e04c0 100644 --- a/app/modules.go +++ b/app/modules.go @@ -68,11 +68,12 @@ import ( ibchooks "github.com/cosmos/ibc-apps/modules/ibc-hooks/v7" "github.com/CosmWasm/wasmd/x/wasm" + custombankmodule "github.com/terra-money/core/v2/custom/bank" + customwasmodule "github.com/terra-money/core/v2/custom/wasmd" "github.com/terra-money/core/v2/x/tokenfactory" "github.com/terra-money/alliance/x/alliance" - terracustombank "github.com/terra-money/core/v2/custom/bank" feeshare "github.com/terra-money/core/v2/x/feeshare" pob "github.com/skip-mev/pob/x/builder" @@ -130,7 +131,7 @@ func appModules(app *TerraApp, encodingConfig terrappsparams.EncodingConfig, ski ), auth.NewAppModule(app.appCodec, app.Keepers.AccountKeeper, nil, app.GetSubspace(authtypes.ModuleName)), vesting.NewAppModule(app.Keepers.AccountKeeper, app.Keepers.BankKeeper, app.Keepers.DistrKeeper, app.Keepers.StakingKeeper), - terracustombank.NewAppModule(app.appCodec, app.Keepers.BankKeeper, app.Keepers.AccountKeeper, app.GetSubspace(banktypes.ModuleName)), + custombankmodule.NewAppModule(app.appCodec, app.Keepers.BankKeeper, app.Keepers.AccountKeeper, app.GetSubspace(banktypes.ModuleName)), capability.NewAppModule(app.appCodec, *app.Keepers.CapabilityKeeper, false), crisis.NewAppModule(&app.Keepers.CrisisKeeper, skipGenesisInvariants, app.GetSubspace(crisistypes.ModuleName)), feegrantmodule.NewAppModule(app.appCodec, app.Keepers.AccountKeeper, app.Keepers.BankKeeper, app.Keepers.FeeGrantKeeper, app.interfaceRegistry), @@ -149,7 +150,7 @@ func appModules(app *TerraApp, encodingConfig terrappsparams.EncodingConfig, ski ibcfee.NewAppModule(app.Keepers.IBCFeeKeeper), ica.NewAppModule(&app.Keepers.ICAControllerKeeper, &app.Keepers.ICAHostKeeper), router.NewAppModule(&app.Keepers.RouterKeeper), - wasm.NewAppModule(app.appCodec, &app.Keepers.WasmKeeper, app.Keepers.StakingKeeper, app.Keepers.AccountKeeper, app.Keepers.BankKeeper, app.MsgServiceRouter(), app.GetSubspace(wasmtypes.ModuleName)), + customwasmodule.NewAppModule(app.appCodec, &app.Keepers.WasmKeeper, app.Keepers.StakingKeeper, app.Keepers.AccountKeeper, app.Keepers.BankKeeper, app.MsgServiceRouter(), app.GetSubspace(wasmtypes.ModuleName)), ibchooks.NewAppModule(app.Keepers.AccountKeeper), tokenfactory.NewAppModule(app.Keepers.TokenFactoryKeeper, app.Keepers.AccountKeeper, app.Keepers.BankKeeper, app.GetSubspace(tokenfactorytypes.ModuleName)), alliance.NewAppModule(app.appCodec, app.Keepers.AllianceKeeper, app.Keepers.StakingKeeper, app.Keepers.AccountKeeper, app.Keepers.BankKeeper, app.interfaceRegistry, app.GetSubspace(alliancetypes.ModuleName)), diff --git a/custom/wasmd/keeper/keeper.go b/custom/wasmd/keeper/keeper.go new file mode 100644 index 00000000..980ddab6 --- /dev/null +++ b/custom/wasmd/keeper/keeper.go @@ -0,0 +1,72 @@ +package keeper + +import ( + "fmt" + + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + "github.com/cosmos/cosmos-sdk/codec" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/CosmWasm/wasmd/x/wasm/types" + keepertypes "github.com/terra-money/core/v2/custom/wasmd/types" +) + +var _ keepertypes.KeeperInterface = Keeper{} + +type Keeper struct { + *wasmkeeper.Keeper +} + +func NewKeeper( + cdc codec.Codec, + storeKey storetypes.StoreKey, + accountKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, + stakingKeeper types.StakingKeeper, + distrKeeper types.DistributionKeeper, + ics4Wrapper types.ICS4Wrapper, + channelKeeper types.ChannelKeeper, + portKeeper types.PortKeeper, + capabilityKeeper types.CapabilityKeeper, + portSource types.ICS20TransferPortSource, + router wasmkeeper.MessageRouter, + grpcQueryRouter wasmkeeper.GRPCQueryRouter, + homeDir string, + wasmConfig types.WasmConfig, + availableCapabilities string, + authority string, + opts ...wasmkeeper.Option, +) Keeper { + keeper := wasmkeeper.NewKeeper( + cdc, + storeKey, + accountKeeper, + bankKeeper, + stakingKeeper, + distrKeeper, + ics4Wrapper, + channelKeeper, + portKeeper, + capabilityKeeper, + portSource, + router, + grpcQueryRouter, + homeDir, + wasmConfig, + availableCapabilities, + authority, + opts..., + ) + + return Keeper{ + &keeper, + } +} + +func (k Keeper) AfterExecuteContract(ctx sdk.Context, msg *types.MsgExecuteContract, res *types.MsgExecuteContractResponse) error { + fmt.Print("\n\n\n\n\n\n\n\nAfterExecuteContract\n") + fmt.Print(ctx.EventManager().Events()) + fmt.Print("\n\n\n\n\n\n\n") + return nil +} diff --git a/custom/wasmd/keeper/msg_server.go b/custom/wasmd/keeper/msg_server.go new file mode 100644 index 00000000..5bccdb3a --- /dev/null +++ b/custom/wasmd/keeper/msg_server.go @@ -0,0 +1,93 @@ +package keeper + +import ( + "context" + + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + "github.com/CosmWasm/wasmd/x/wasm/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var _ types.MsgServer = customMsgServer{} + +// grpc message server implementation +type customMsgServer struct { + msgServer types.MsgServer + keeper *Keeper +} + +// NewCustomMsgServerImpl default constructor +func NewCustomMsgServerImpl(k *Keeper) types.MsgServer { + msgServer := wasmkeeper.NewMsgServerImpl(k.Keeper) + + return customMsgServer{ + msgServer: msgServer, + keeper: k, + } +} + +// EexcuteContract wraps the original call but it collects +// the addresses of contracts involved in the transaction +func (m customMsgServer) ExecuteContract(goCtx context.Context, msg *types.MsgExecuteContract) (*types.MsgExecuteContractResponse, error) { + res, err := m.msgServer.ExecuteContract(goCtx, msg) + if err != nil { + return nil, err + } + + ctx := sdk.UnwrapSDKContext(goCtx) + err = m.keeper.AfterExecuteContract(ctx, msg, res) + if err != nil { + return nil, err + } + + return res, nil +} + +func (m customMsgServer) StoreCode(goCtx context.Context, msg *types.MsgStoreCode) (*types.MsgStoreCodeResponse, error) { + return m.msgServer.StoreCode(goCtx, msg) +} +func (m customMsgServer) InstantiateContract(goCtx context.Context, msg *types.MsgInstantiateContract) (*types.MsgInstantiateContractResponse, error) { + return m.msgServer.InstantiateContract(goCtx, msg) +} +func (m customMsgServer) InstantiateContract2(goCtx context.Context, msg *types.MsgInstantiateContract2) (*types.MsgInstantiateContract2Response, error) { + return m.msgServer.InstantiateContract2(goCtx, msg) +} +func (m customMsgServer) MigrateContract(goCtx context.Context, msg *types.MsgMigrateContract) (*types.MsgMigrateContractResponse, error) { + return m.msgServer.MigrateContract(goCtx, msg) +} +func (m customMsgServer) UpdateAdmin(goCtx context.Context, msg *types.MsgUpdateAdmin) (*types.MsgUpdateAdminResponse, error) { + return m.msgServer.UpdateAdmin(goCtx, msg) +} +func (m customMsgServer) ClearAdmin(goCtx context.Context, msg *types.MsgClearAdmin) (*types.MsgClearAdminResponse, error) { + return m.msgServer.ClearAdmin(goCtx, msg) +} +func (m customMsgServer) UpdateInstantiateConfig(goCtx context.Context, msg *types.MsgUpdateInstantiateConfig) (*types.MsgUpdateInstantiateConfigResponse, error) { + return m.msgServer.UpdateInstantiateConfig(goCtx, msg) +} +func (m customMsgServer) UpdateParams(goCtx context.Context, req *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { + return m.msgServer.UpdateParams(goCtx, req) +} +func (m customMsgServer) PinCodes(goCtx context.Context, req *types.MsgPinCodes) (*types.MsgPinCodesResponse, error) { + return m.msgServer.PinCodes(goCtx, req) +} +func (m customMsgServer) UnpinCodes(goCtx context.Context, req *types.MsgUnpinCodes) (*types.MsgUnpinCodesResponse, error) { + return m.msgServer.UnpinCodes(goCtx, req) +} +func (m customMsgServer) SudoContract(goCtx context.Context, req *types.MsgSudoContract) (*types.MsgSudoContractResponse, error) { + return m.msgServer.SudoContract(goCtx, req) +} +func (m customMsgServer) StoreAndInstantiateContract(goCtx context.Context, req *types.MsgStoreAndInstantiateContract) (*types.MsgStoreAndInstantiateContractResponse, error) { + return m.msgServer.StoreAndInstantiateContract(goCtx, req) +} +func (m customMsgServer) AddCodeUploadParamsAddresses(goCtx context.Context, req *types.MsgAddCodeUploadParamsAddresses) (*types.MsgAddCodeUploadParamsAddressesResponse, error) { + return m.msgServer.AddCodeUploadParamsAddresses(goCtx, req) +} +func (m customMsgServer) RemoveCodeUploadParamsAddresses(goCtx context.Context, req *types.MsgRemoveCodeUploadParamsAddresses) (*types.MsgRemoveCodeUploadParamsAddressesResponse, error) { + return m.msgServer.RemoveCodeUploadParamsAddresses(goCtx, req) +} +func (m customMsgServer) StoreAndMigrateContract(goCtx context.Context, req *types.MsgStoreAndMigrateContract) (*types.MsgStoreAndMigrateContractResponse, error) { + return m.msgServer.StoreAndMigrateContract(goCtx, req) +} +func (m customMsgServer) UpdateContractLabel(goCtx context.Context, msg *types.MsgUpdateContractLabel) (*types.MsgUpdateContractLabelResponse, error) { + return m.msgServer.UpdateContractLabel(goCtx, msg) +} diff --git a/custom/wasmd/module.go b/custom/wasmd/module.go new file mode 100644 index 00000000..1e213b19 --- /dev/null +++ b/custom/wasmd/module.go @@ -0,0 +1,64 @@ +package wasm + +import ( + "github.com/cosmos/cosmos-sdk/baseapp" + + "github.com/cosmos/cosmos-sdk/codec" + + "github.com/CosmWasm/wasmd/x/wasm" + "github.com/CosmWasm/wasmd/x/wasm/exported" + "github.com/CosmWasm/wasmd/x/wasm/keeper" + "github.com/CosmWasm/wasmd/x/wasm/simulation" + "github.com/CosmWasm/wasmd/x/wasm/types" + + "github.com/cosmos/cosmos-sdk/types/module" + customwasmkeeper "github.com/terra-money/core/v2/custom/wasmd/keeper" +) + +// AppModule implements an application module for the wasm module. +type AppModule struct { + wasm.AppModule + keeper *customwasmkeeper.Keeper + legacySubspace exported.Subspace + msgServer types.MsgServer +} + +// NewAppModule creates a new AppModule object +func NewAppModule( + cdc codec.Codec, + keeper *customwasmkeeper.Keeper, + validatorSetSource keeper.ValidatorSetSource, + ak types.AccountKeeper, + bk simulation.BankKeeper, + router *baseapp.MsgServiceRouter, + ss exported.Subspace, +) AppModule { + appModule := wasm.NewAppModule(cdc, keeper.Keeper, validatorSetSource, ak, bk, router, ss) + msgServer := customwasmkeeper.NewCustomMsgServerImpl(keeper) + + return AppModule{ + AppModule: appModule, + keeper: keeper, + legacySubspace: ss, + msgServer: msgServer, + } +} + +func (am AppModule) RegisterServices(cfg module.Configurator) { + types.RegisterMsgServer(cfg.MsgServer(), am.msgServer) + types.RegisterQueryServer(cfg.QueryServer(), keeper.Querier(am.keeper.Keeper)) + + m := keeper.NewMigrator(*am.keeper.Keeper, am.legacySubspace) + err := cfg.RegisterMigration(types.ModuleName, 1, m.Migrate1to2) + if err != nil { + panic(err) + } + err = cfg.RegisterMigration(types.ModuleName, 2, m.Migrate2to3) + if err != nil { + panic(err) + } + err = cfg.RegisterMigration(types.ModuleName, 3, m.Migrate3to4) + if err != nil { + panic(err) + } +} diff --git a/custom/wasmd/types/interface.go b/custom/wasmd/types/interface.go new file mode 100644 index 00000000..86c43606 --- /dev/null +++ b/custom/wasmd/types/interface.go @@ -0,0 +1,34 @@ +package types + +import ( + "github.com/cometbft/cometbft/libs/log" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/CosmWasm/wasmd/x/wasm/types" +) + +type KeeperInterface interface { + GetParams(ctx sdk.Context) types.Params + SetParams(ctx sdk.Context, ps types.Params) error + GetAuthority() string + GetGasRegister() types.GasRegister + Sudo(ctx sdk.Context, contractAddress sdk.AccAddress, msg []byte) ([]byte, error) + IterateContractsByCreator(ctx sdk.Context, creator sdk.AccAddress, cb func(address sdk.AccAddress) bool) + IterateContractsByCode(ctx sdk.Context, codeID uint64, cb func(address sdk.AccAddress) bool) + GetContractHistory(ctx sdk.Context, contractAddr sdk.AccAddress) []types.ContractCodeHistoryEntry + QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) ([]byte, error) + QueryRaw(ctx sdk.Context, contractAddress sdk.AccAddress, key []byte) []byte + GetContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) *types.ContractInfo + HasContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) bool + IterateContractInfo(ctx sdk.Context, cb func(sdk.AccAddress, types.ContractInfo) bool) + IterateContractState(ctx sdk.Context, contractAddress sdk.AccAddress, cb func(key, value []byte) bool) + GetCodeInfo(ctx sdk.Context, codeID uint64) *types.CodeInfo + IterateCodeInfos(ctx sdk.Context, cb func(uint64, types.CodeInfo) bool) + GetByteCode(ctx sdk.Context, codeID uint64) ([]byte, error) + IsPinnedCode(ctx sdk.Context, codeID uint64) bool + InitializePinnedCodes(ctx sdk.Context) error + PeekAutoIncrementID(ctx sdk.Context, sequenceKey []byte) uint64 + Logger(ctx sdk.Context) log.Logger + QueryGasLimit() sdk.Gas +} diff --git a/x/feeshare/keeper/keeper.go b/x/feeshare/keeper/keeper.go index d4189fee..abcd9f92 100644 --- a/x/feeshare/keeper/keeper.go +++ b/x/feeshare/keeper/keeper.go @@ -3,7 +3,7 @@ package keeper import ( "fmt" - wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + customwasmkeeper "github.com/terra-money/core/v2/custom/wasmd/keeper" "github.com/cometbft/cometbft/libs/log" @@ -21,7 +21,7 @@ type Keeper struct { cdc codec.BinaryCodec bankKeeper revtypes.BankKeeper - wasmKeeper wasmkeeper.Keeper + wasmKeeper customwasmkeeper.Keeper accountKeeper revtypes.AccountKeeper feeCollectorName string @@ -36,7 +36,7 @@ func NewKeeper( cdc codec.BinaryCodec, storeKey storetypes.StoreKey, bk revtypes.BankKeeper, - wk wasmkeeper.Keeper, + wk customwasmkeeper.Keeper, ak revtypes.AccountKeeper, feeCollector string, authority string, From ee3c8de141281b779fd49e265a01b89dd4cfe623 Mon Sep 17 00:00:00 2001 From: emidev98 Date: Wed, 8 Nov 2023 16:17:55 +0200 Subject: [PATCH 05/13] feat: feeshare module --- Makefile | 2 +- app/app.go | 5 +- app/keepers/keepers.go | 4 +- app/modules.go | 4 +- app/post/post.go | 9 +- proto/terra/wasm/v1/executed_contracts.proto | 11 + scripts/protocgen.sh | 3 +- {custom => x}/bank/keeper/hooks.go | 2 +- {custom => x}/bank/keeper/keeper.go | 2 +- {custom => x}/bank/module.go | 2 +- {custom => x}/bank/types/errors.go | 0 {custom => x}/bank/types/expected_hooks.go | 0 x/feeshare/keeper/keeper.go | 2 +- x/feeshare/post/post.go | 42 +-- x/feeshare/post/post_test.go | 62 ---- x/tokenfactory/keeper/keeper.go | 2 +- x/wasm/keeper/contracts.go | 32 ++ {custom/wasmd => x/wasm}/keeper/keeper.go | 46 ++- {custom/wasmd => x/wasm}/keeper/msg_server.go | 0 {custom/wasmd => x/wasm}/module.go | 2 +- x/wasm/post/post.go | 24 ++ x/wasm/types/executed_contracts.pb.go | 324 ++++++++++++++++++ {custom/wasmd => x/wasm}/types/interface.go | 0 x/wasm/types/keys.go | 9 + x/wasmd/types/executed_contracts.pb.go | 324 ++++++++++++++++++ 25 files changed, 794 insertions(+), 119 deletions(-) create mode 100644 proto/terra/wasm/v1/executed_contracts.proto rename {custom => x}/bank/keeper/hooks.go (95%) rename {custom => x}/bank/keeper/keeper.go (98%) rename {custom => x}/bank/module.go (96%) rename {custom => x}/bank/types/errors.go (100%) rename {custom => x}/bank/types/expected_hooks.go (100%) create mode 100644 x/wasm/keeper/contracts.go rename {custom/wasmd => x/wasm}/keeper/keeper.go (61%) rename {custom/wasmd => x/wasm}/keeper/msg_server.go (100%) rename {custom/wasmd => x/wasm}/module.go (96%) create mode 100644 x/wasm/post/post.go create mode 100644 x/wasm/types/executed_contracts.pb.go rename {custom/wasmd => x/wasm}/types/interface.go (100%) create mode 100644 x/wasm/types/keys.go create mode 100644 x/wasmd/types/executed_contracts.pb.go diff --git a/Makefile b/Makefile index e18b6487..dc670180 100644 --- a/Makefile +++ b/Makefile @@ -260,7 +260,7 @@ update-swagger-docs: statik proto-all: proto-gen proto-swagger update-swagger-docs -.PHONY: proto-gen gen-swagger update-swagger-docs apply-swagger proto-all +.PHONY: proto-gen gen-swagger update-swagger-docs proto-all ######################################## ### Tools & dependencies diff --git a/app/app.go b/app/app.go index bba6d554..7d53b3cc 100644 --- a/app/app.go +++ b/app/app.go @@ -52,8 +52,8 @@ import ( distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" "github.com/cosmos/cosmos-sdk/x/evidence" feegrantmodule "github.com/cosmos/cosmos-sdk/x/feegrant/module" - custombankmodule "github.com/terra-money/core/v2/custom/bank" - customwasmodule "github.com/terra-money/core/v2/custom/wasmd" + custombankmodule "github.com/terra-money/core/v2/x/bank" + customwasmodule "github.com/terra-money/core/v2/x/wasm" "github.com/cosmos/cosmos-sdk/x/gov" govclient "github.com/cosmos/cosmos-sdk/x/gov/client" @@ -284,6 +284,7 @@ func NewTerraApp( post.HandlerOptions{ FeeShareKeeper: app.Keepers.FeeShareKeeper, BankKeeper: app.Keepers.BankKeeper, + WasmKeeper: app.Keepers.WasmKeeper, }, ) diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index 4fe15470..4bbd70d4 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -85,7 +85,7 @@ import ( wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" icahost "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/host" - customwasmkeeper "github.com/terra-money/core/v2/custom/wasmd/keeper" + customwasmkeeper "github.com/terra-money/core/v2/x/wasm/keeper" icacontroller "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/controller" icacontrollerkeeper "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/controller/keeper" @@ -96,7 +96,7 @@ import ( "github.com/terra-money/alliance/x/alliance" alliancekeeper "github.com/terra-money/alliance/x/alliance/keeper" alliancetypes "github.com/terra-money/alliance/x/alliance/types" - custombankkeeper "github.com/terra-money/core/v2/custom/bank/keeper" + custombankkeeper "github.com/terra-money/core/v2/x/bank/keeper" feesharekeeper "github.com/terra-money/core/v2/x/feeshare/keeper" feesharetypes "github.com/terra-money/core/v2/x/feeshare/types" diff --git a/app/modules.go b/app/modules.go index 710e04c0..3b1e98be 100644 --- a/app/modules.go +++ b/app/modules.go @@ -68,8 +68,8 @@ import ( ibchooks "github.com/cosmos/ibc-apps/modules/ibc-hooks/v7" "github.com/CosmWasm/wasmd/x/wasm" - custombankmodule "github.com/terra-money/core/v2/custom/bank" - customwasmodule "github.com/terra-money/core/v2/custom/wasmd" + custombankmodule "github.com/terra-money/core/v2/x/bank" + customwasmodule "github.com/terra-money/core/v2/x/wasm" "github.com/terra-money/core/v2/x/tokenfactory" diff --git a/app/post/post.go b/app/post/post.go index aaf58440..4eb194ef 100644 --- a/app/post/post.go +++ b/app/post/post.go @@ -1,20 +1,23 @@ package post import ( - feesharepost "github.com/terra-money/core/v2/x/feeshare/post" - sdk "github.com/cosmos/cosmos-sdk/types" + feesharepost "github.com/terra-money/core/v2/x/feeshare/post" + customwasmkeeper "github.com/terra-money/core/v2/x/wasm/keeper" + wasmpost "github.com/terra-money/core/v2/x/wasm/post" ) type HandlerOptions struct { FeeShareKeeper feesharepost.FeeShareKeeper BankKeeper feesharepost.BankKeeper + WasmKeeper customwasmkeeper.Keeper } func NewPostHandler(options HandlerOptions) sdk.PostHandler { postDecorators := []sdk.PostDecorator{ - feesharepost.NewFeeSharePayoutDecorator(options.FeeShareKeeper, options.BankKeeper), + feesharepost.NewFeeSharePayoutDecorator(options.FeeShareKeeper, options.BankKeeper, options.WasmKeeper), + wasmpost.NewWasmdDecorator(options.WasmKeeper), } return sdk.ChainPostDecorators(postDecorators...) diff --git a/proto/terra/wasm/v1/executed_contracts.proto b/proto/terra/wasm/v1/executed_contracts.proto new file mode 100644 index 00000000..b101aa12 --- /dev/null +++ b/proto/terra/wasm/v1/executed_contracts.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; +package terra.wasm.v1; + + +option go_package = "github.com/terra-money/core/v2/x/wasmd/types"; + +// ExecutedContracts is a structure type that contains the list of executed contracts +// in a specific transaction. +message ExecutedContracts { + repeated string contract_addresses = 1; +} diff --git a/scripts/protocgen.sh b/scripts/protocgen.sh index bedd668b..888c3d90 100755 --- a/scripts/protocgen.sh +++ b/scripts/protocgen.sh @@ -5,7 +5,7 @@ set -e echo "Generating gogo proto code" cd proto -proto_dirs=$(find ./osmosis ./juno -path -prune -o -name '*.proto' -print0 | xargs -0 -n1 dirname | sort | uniq) +proto_dirs=$(find ./osmosis ./juno ./terra -path -prune -o -name '*.proto' -print0 | xargs -0 -n1 dirname | sort | uniq) for dir in $proto_dirs; do for file in $(find "${dir}" -maxdepth 1 -name '*.proto'); do # this regex checks if a proto file has its go_package set to cosmossdk.io/api/... @@ -22,4 +22,5 @@ cd .. # move proto files to the right places cp -r github.com/osmosis-labs/osmosis/v17/* ./ cp -r github.com/CosmosContracts/juno/* ./ +cp -r github.com/terra-money/core/v2/* ./ rm -rf github.com diff --git a/custom/bank/keeper/hooks.go b/x/bank/keeper/hooks.go similarity index 95% rename from custom/bank/keeper/hooks.go rename to x/bank/keeper/hooks.go index 33560d1c..b2343766 100644 --- a/custom/bank/keeper/hooks.go +++ b/x/bank/keeper/hooks.go @@ -1,7 +1,7 @@ package keeper import ( - customterratypes "github.com/terra-money/core/v2/custom/bank/types" + customterratypes "github.com/terra-money/core/v2/x/bank/types" sdk "github.com/cosmos/cosmos-sdk/types" ) diff --git a/custom/bank/keeper/keeper.go b/x/bank/keeper/keeper.go similarity index 98% rename from custom/bank/keeper/keeper.go rename to x/bank/keeper/keeper.go index b55953a6..90b667e7 100644 --- a/custom/bank/keeper/keeper.go +++ b/x/bank/keeper/keeper.go @@ -10,7 +10,7 @@ import ( errorsmod "cosmossdk.io/errors" custombankkeeper "github.com/terra-money/alliance/custom/bank/keeper" - customterratypes "github.com/terra-money/core/v2/custom/bank/types" + customterratypes "github.com/terra-money/core/v2/x/bank/types" ) type Keeper struct { diff --git a/custom/bank/module.go b/x/bank/module.go similarity index 96% rename from custom/bank/module.go rename to x/bank/module.go index 682ec284..6b8f2be7 100644 --- a/custom/bank/module.go +++ b/x/bank/module.go @@ -8,7 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank/types" customalliancemod "github.com/terra-money/alliance/custom/bank" - custombankkeeper "github.com/terra-money/core/v2/custom/bank/keeper" + custombankkeeper "github.com/terra-money/core/v2/x/bank/keeper" "github.com/cosmos/cosmos-sdk/types/module" bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" diff --git a/custom/bank/types/errors.go b/x/bank/types/errors.go similarity index 100% rename from custom/bank/types/errors.go rename to x/bank/types/errors.go diff --git a/custom/bank/types/expected_hooks.go b/x/bank/types/expected_hooks.go similarity index 100% rename from custom/bank/types/expected_hooks.go rename to x/bank/types/expected_hooks.go diff --git a/x/feeshare/keeper/keeper.go b/x/feeshare/keeper/keeper.go index abcd9f92..0ca7862a 100644 --- a/x/feeshare/keeper/keeper.go +++ b/x/feeshare/keeper/keeper.go @@ -3,7 +3,7 @@ package keeper import ( "fmt" - customwasmkeeper "github.com/terra-money/core/v2/custom/wasmd/keeper" + customwasmkeeper "github.com/terra-money/core/v2/x/wasm/keeper" "github.com/cometbft/cometbft/libs/log" diff --git a/x/feeshare/post/post.go b/x/feeshare/post/post.go index bf72ce94..0e91037c 100644 --- a/x/feeshare/post/post.go +++ b/x/feeshare/post/post.go @@ -5,23 +5,25 @@ import ( errorsmod "cosmossdk.io/errors" - "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" feeshare "github.com/terra-money/core/v2/x/feeshare/types" + customwasmkeeper "github.com/terra-money/core/v2/x/wasm/keeper" ) type FeeSharePayoutDecorator struct { feesharekeeper FeeShareKeeper bankKeeper BankKeeper + wasmKeeper customwasmkeeper.Keeper } -func NewFeeSharePayoutDecorator(fs FeeShareKeeper, bk BankKeeper) FeeSharePayoutDecorator { +func NewFeeSharePayoutDecorator(fs FeeShareKeeper, bk BankKeeper, wk customwasmkeeper.Keeper) FeeSharePayoutDecorator { return FeeSharePayoutDecorator{ feesharekeeper: fs, bankKeeper: bk, + wasmKeeper: wk, } } @@ -63,12 +65,12 @@ func (fsd FeeSharePayoutDecorator) PostHandle( // FeeSharePayout takes the total fees paid for a transaction and // split these fees equaly between all the contacts involved in the // transaction based on the module params. -func (fsd FeeSharePayoutDecorator) FeeSharePayout(ctx sdk.Context, txFees sdk.Coins, devShares types.Dec, allowedDenoms []string) (err error) { - events := ctx.EventManager().Events() - contractAddresses, err := ExtractContractAddrs(events) - if err != nil { +func (fsd FeeSharePayoutDecorator) FeeSharePayout(ctx sdk.Context, txFees sdk.Coins, devShares sdk.Dec, allowedDenoms []string) (err error) { + executedContracts, found := fsd.wasmKeeper.GetExecutedContractAddresses(ctx) + if !found { return err } + contractAddresses := executedContracts.ContractAddresses if len(contractAddresses) == 0 { return err } @@ -103,34 +105,6 @@ func (fsd FeeSharePayoutDecorator) FeeSharePayout(ctx sdk.Context, txFees sdk.Co return err } -// Iterate the events and search for the execute event then iterate the -// attributes in search for _contract_address and get the value which -// is the contract address to search for all the beneficiaries, info: -// https://github.com/CosmWasm/wasmd/blob/main/EVENTS.md#validation-rules -func ExtractContractAddrs(events sdk.Events) ([]string, error) { - contractAddresses := []string{} - for _, ev := range events { - if ev.Type != "execute" { - continue - } - - for _, attr := range ev.Attributes { - if attr.Key != "_contract_address" { - continue - } - // if the contract address has already been - // added just skip it to avoid duplicates - if slices.Contains(contractAddresses, attr.Value) { - continue - } - - contractAddresses = append(contractAddresses, attr.Value) - } - } - - return contractAddresses, nil -} - // Iterate the contract addresses and get the // withdrawer address from the module store func GetWithdrawalAddressFromContract(ctx sdk.Context, contractAddresses []string, fsk FeeShareKeeper) ([]sdk.AccAddress, error) { diff --git a/x/feeshare/post/post_test.go b/x/feeshare/post/post_test.go index 03fa8e5c..6d06de52 100644 --- a/x/feeshare/post/post_test.go +++ b/x/feeshare/post/post_test.go @@ -20,68 +20,6 @@ func TestAnteSuite(t *testing.T) { suite.Run(t, new(AnteTestSuite)) } -func (suite *AnteTestSuite) TestExtractContractAddrs() { - testCases := []struct { - name string - events sdk.Events - expectedAddresses []string - }{ - { - "no events", - sdk.Events{}, - []string{}, - }, - { - "no execute events", - sdk.Events{ - sdk.NewEvent("transfer", - sdk.NewAttribute("_contract_address", "address1"), - ), - }, - []string{}, - }, - { - "single execute event", - sdk.Events{ - sdk.NewEvent("execute", - sdk.NewAttribute("_contract_address", "address1"), - ), - }, - []string{"address1"}, - }, - { - "multiple execute events", - sdk.Events{ - sdk.NewEvent("execute", - sdk.NewAttribute("_contract_address", "address1"), - ), - sdk.NewEvent("execute", - sdk.NewAttribute("_contract_address", "address2"), - ), - }, - []string{"address1", "address2"}, - }, - { - "duplicate execute events", - sdk.Events{ - sdk.NewEvent("execute", - sdk.NewAttribute("_contract_address", "address1"), - ), - sdk.NewEvent("execute", - sdk.NewAttribute("_contract_address", "address1"), - ), - }, - []string{"address1"}, - }, - } - - for _, tc := range testCases { - addresses, err := post.ExtractContractAddrs(tc.events) - suite.Require().NoError(err, tc.name) - suite.Require().Equal(tc.expectedAddresses, addresses, tc.name) - } -} - func (suite *AnteTestSuite) TestGetWithdrawalAddressFromContract() { suite.Setup() diff --git a/x/tokenfactory/keeper/keeper.go b/x/tokenfactory/keeper/keeper.go index a5ad4d56..cab562cc 100644 --- a/x/tokenfactory/keeper/keeper.go +++ b/x/tokenfactory/keeper/keeper.go @@ -11,7 +11,7 @@ import ( storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" - customtypes "github.com/terra-money/core/v2/custom/bank/keeper" + customtypes "github.com/terra-money/core/v2/x/bank/keeper" "github.com/terra-money/core/v2/x/tokenfactory/types" ) diff --git a/x/wasm/keeper/contracts.go b/x/wasm/keeper/contracts.go new file mode 100644 index 00000000..d085e0f2 --- /dev/null +++ b/x/wasm/keeper/contracts.go @@ -0,0 +1,32 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/terra-money/core/v2/x/wasm/types" +) + +func (k Keeper) GetExecutedContractAddresses(ctx sdk.Context) (contracts types.ExecutedContracts, found bool) { + store := ctx.KVStore(k.storeKey) + contractAddressesKey := types.GetExecutedContractsKey() + b := store.Get(contractAddressesKey) + if b == nil { + return contracts, false + } + + k.cdc.MustUnmarshal(b, &contracts) + return contracts, true +} + +func (k Keeper) SetExecutedContractAddresses(ctx sdk.Context, contracts types.ExecutedContracts) error { + store := ctx.KVStore(k.storeKey) + contractAddressesKey := types.GetExecutedContractsKey() + b := k.cdc.MustMarshal(&contracts) + store.Set(contractAddressesKey, b) + return nil +} + +func (k Keeper) DeleteExecutedContractAddresses(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + contractAddressesKey := types.GetExecutedContractsKey() + store.Delete(contractAddressesKey) +} diff --git a/custom/wasmd/keeper/keeper.go b/x/wasm/keeper/keeper.go similarity index 61% rename from custom/wasmd/keeper/keeper.go rename to x/wasm/keeper/keeper.go index 980ddab6..c8e2e57e 100644 --- a/custom/wasmd/keeper/keeper.go +++ b/x/wasm/keeper/keeper.go @@ -1,7 +1,7 @@ package keeper import ( - "fmt" + "slices" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" "github.com/cosmos/cosmos-sdk/codec" @@ -9,13 +9,15 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/CosmWasm/wasmd/x/wasm/types" - keepertypes "github.com/terra-money/core/v2/custom/wasmd/types" + keepertypes "github.com/terra-money/core/v2/x/wasm/types" ) var _ keepertypes.KeeperInterface = Keeper{} type Keeper struct { *wasmkeeper.Keeper + storeKey storetypes.StoreKey + cdc codec.Codec } func NewKeeper( @@ -60,13 +62,45 @@ func NewKeeper( ) return Keeper{ - &keeper, + Keeper: &keeper, + storeKey: storeKey, + cdc: cdc, } } func (k Keeper) AfterExecuteContract(ctx sdk.Context, msg *types.MsgExecuteContract, res *types.MsgExecuteContractResponse) error { - fmt.Print("\n\n\n\n\n\n\n\nAfterExecuteContract\n") - fmt.Print(ctx.EventManager().Events()) - fmt.Print("\n\n\n\n\n\n\n") + events := ctx.EventManager().Events() + contractAddresses := []string{} + + for _, ev := range events { + if ev.Type != "execute" { + continue + } + + for _, attr := range ev.Attributes { + if attr.Key != "_contract_address" { + continue + } + // if the contract address has already been + // added just skip it to avoid duplicates + if slices.Contains(contractAddresses, attr.Value) { + continue + } + + contractAddresses = append(contractAddresses, attr.Value) + } + } + + if len(contractAddresses) != 0 { + executedContracts := keepertypes.ExecutedContracts{ + ContractAddresses: contractAddresses, + } + + err := k.SetExecutedContractAddresses(ctx, executedContracts) + if err != nil { + return err + } + } + return nil } diff --git a/custom/wasmd/keeper/msg_server.go b/x/wasm/keeper/msg_server.go similarity index 100% rename from custom/wasmd/keeper/msg_server.go rename to x/wasm/keeper/msg_server.go diff --git a/custom/wasmd/module.go b/x/wasm/module.go similarity index 96% rename from custom/wasmd/module.go rename to x/wasm/module.go index 1e213b19..5ebd5e38 100644 --- a/custom/wasmd/module.go +++ b/x/wasm/module.go @@ -12,7 +12,7 @@ import ( "github.com/CosmWasm/wasmd/x/wasm/types" "github.com/cosmos/cosmos-sdk/types/module" - customwasmkeeper "github.com/terra-money/core/v2/custom/wasmd/keeper" + customwasmkeeper "github.com/terra-money/core/v2/x/wasm/keeper" ) // AppModule implements an application module for the wasm module. diff --git a/x/wasm/post/post.go b/x/wasm/post/post.go new file mode 100644 index 00000000..f1b4a91f --- /dev/null +++ b/x/wasm/post/post.go @@ -0,0 +1,24 @@ +package post + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + customwasmkeeper "github.com/terra-money/core/v2/x/wasm/keeper" +) + +type WasmdDecorator struct { + wasmKeeper customwasmkeeper.Keeper +} + +func NewWasmdDecorator(wk customwasmkeeper.Keeper) WasmdDecorator { + return WasmdDecorator{ + wasmKeeper: wk, + } +} + +// MUST: this should always be the latest decorator executed on tx processing +// as it clears the executed contract addresses +func (fsd WasmdDecorator) PostHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, success bool, next sdk.PostHandler) (newCtx sdk.Context, err error) { + fsd.wasmKeeper.DeleteExecutedContractAddresses(ctx) + return next(ctx, tx, simulate, success) +} diff --git a/x/wasm/types/executed_contracts.pb.go b/x/wasm/types/executed_contracts.pb.go new file mode 100644 index 00000000..0726c1ce --- /dev/null +++ b/x/wasm/types/executed_contracts.pb.go @@ -0,0 +1,324 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: terra/wasm/v1/executed_contracts.proto + +package types + +import ( + fmt "fmt" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// ExecutedContracts is a structure type that contains the list of executed contracts +// in a specific transaction. +type ExecutedContracts struct { + ContractAddresses []string `protobuf:"bytes,1,rep,name=contract_addresses,json=contractAddresses,proto3" json:"contract_addresses,omitempty"` +} + +func (m *ExecutedContracts) Reset() { *m = ExecutedContracts{} } +func (m *ExecutedContracts) String() string { return proto.CompactTextString(m) } +func (*ExecutedContracts) ProtoMessage() {} +func (*ExecutedContracts) Descriptor() ([]byte, []int) { + return fileDescriptor_200f5f891d4ef8d1, []int{0} +} +func (m *ExecutedContracts) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ExecutedContracts) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ExecutedContracts.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ExecutedContracts) XXX_Merge(src proto.Message) { + xxx_messageInfo_ExecutedContracts.Merge(m, src) +} +func (m *ExecutedContracts) XXX_Size() int { + return m.Size() +} +func (m *ExecutedContracts) XXX_DiscardUnknown() { + xxx_messageInfo_ExecutedContracts.DiscardUnknown(m) +} + +var xxx_messageInfo_ExecutedContracts proto.InternalMessageInfo + +func (m *ExecutedContracts) GetContractAddresses() []string { + if m != nil { + return m.ContractAddresses + } + return nil +} + +func init() { + proto.RegisterType((*ExecutedContracts)(nil), "terra.wasm.v1.ExecutedContracts") +} + +func init() { + proto.RegisterFile("terra/wasm/v1/executed_contracts.proto", fileDescriptor_200f5f891d4ef8d1) +} + +var fileDescriptor_200f5f891d4ef8d1 = []byte{ + // 186 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x2b, 0x49, 0x2d, 0x2a, + 0x4a, 0xd4, 0x2f, 0x4f, 0x2c, 0xce, 0xd5, 0x2f, 0x33, 0xd4, 0x4f, 0xad, 0x48, 0x4d, 0x2e, 0x2d, + 0x49, 0x4d, 0x89, 0x4f, 0xce, 0xcf, 0x2b, 0x29, 0x4a, 0x4c, 0x2e, 0x29, 0xd6, 0x2b, 0x28, 0xca, + 0x2f, 0xc9, 0x17, 0xe2, 0x05, 0xab, 0xd3, 0x03, 0xa9, 0xd3, 0x2b, 0x33, 0x54, 0x72, 0xe2, 0x12, + 0x74, 0x85, 0x2a, 0x75, 0x86, 0xa9, 0x14, 0xd2, 0xe5, 0x12, 0x82, 0x69, 0x8b, 0x4f, 0x4c, 0x49, + 0x29, 0x4a, 0x2d, 0x2e, 0x4e, 0x2d, 0x96, 0x60, 0x54, 0x60, 0xd6, 0xe0, 0x0c, 0x12, 0x84, 0xc9, + 0x38, 0xc2, 0x24, 0x9c, 0xdc, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, + 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0x4a, + 0x27, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x1f, 0x6c, 0xaf, 0x6e, 0x6e, + 0x7e, 0x5e, 0x6a, 0xa5, 0x7e, 0x72, 0x7e, 0x51, 0xaa, 0x7e, 0x99, 0x91, 0x7e, 0x05, 0xd8, 0xbd, + 0x29, 0xfa, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x49, 0x6c, 0x60, 0x17, 0x1a, 0x03, 0x02, 0x00, 0x00, + 0xff, 0xff, 0xdf, 0xf2, 0xb2, 0xd1, 0xcb, 0x00, 0x00, 0x00, +} + +func (m *ExecutedContracts) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ExecutedContracts) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ExecutedContracts) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ContractAddresses) > 0 { + for iNdEx := len(m.ContractAddresses) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.ContractAddresses[iNdEx]) + copy(dAtA[i:], m.ContractAddresses[iNdEx]) + i = encodeVarintExecutedContracts(dAtA, i, uint64(len(m.ContractAddresses[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintExecutedContracts(dAtA []byte, offset int, v uint64) int { + offset -= sovExecutedContracts(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *ExecutedContracts) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.ContractAddresses) > 0 { + for _, s := range m.ContractAddresses { + l = len(s) + n += 1 + l + sovExecutedContracts(uint64(l)) + } + } + return n +} + +func sovExecutedContracts(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozExecutedContracts(x uint64) (n int) { + return sovExecutedContracts(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *ExecutedContracts) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowExecutedContracts + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ExecutedContracts: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ExecutedContracts: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ContractAddresses", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowExecutedContracts + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthExecutedContracts + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthExecutedContracts + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ContractAddresses = append(m.ContractAddresses, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipExecutedContracts(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthExecutedContracts + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipExecutedContracts(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowExecutedContracts + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowExecutedContracts + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowExecutedContracts + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthExecutedContracts + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupExecutedContracts + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthExecutedContracts + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthExecutedContracts = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowExecutedContracts = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupExecutedContracts = fmt.Errorf("proto: unexpected end of group") +) diff --git a/custom/wasmd/types/interface.go b/x/wasm/types/interface.go similarity index 100% rename from custom/wasmd/types/interface.go rename to x/wasm/types/interface.go diff --git a/x/wasm/types/keys.go b/x/wasm/types/keys.go new file mode 100644 index 00000000..d4cc4a20 --- /dev/null +++ b/x/wasm/types/keys.go @@ -0,0 +1,9 @@ +package types + +var ( + executedContractsKey = []byte("terra/executedContracts") +) + +func GetExecutedContractsKey() []byte { + return executedContractsKey +} diff --git a/x/wasmd/types/executed_contracts.pb.go b/x/wasmd/types/executed_contracts.pb.go new file mode 100644 index 00000000..0726c1ce --- /dev/null +++ b/x/wasmd/types/executed_contracts.pb.go @@ -0,0 +1,324 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: terra/wasm/v1/executed_contracts.proto + +package types + +import ( + fmt "fmt" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// ExecutedContracts is a structure type that contains the list of executed contracts +// in a specific transaction. +type ExecutedContracts struct { + ContractAddresses []string `protobuf:"bytes,1,rep,name=contract_addresses,json=contractAddresses,proto3" json:"contract_addresses,omitempty"` +} + +func (m *ExecutedContracts) Reset() { *m = ExecutedContracts{} } +func (m *ExecutedContracts) String() string { return proto.CompactTextString(m) } +func (*ExecutedContracts) ProtoMessage() {} +func (*ExecutedContracts) Descriptor() ([]byte, []int) { + return fileDescriptor_200f5f891d4ef8d1, []int{0} +} +func (m *ExecutedContracts) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ExecutedContracts) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ExecutedContracts.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ExecutedContracts) XXX_Merge(src proto.Message) { + xxx_messageInfo_ExecutedContracts.Merge(m, src) +} +func (m *ExecutedContracts) XXX_Size() int { + return m.Size() +} +func (m *ExecutedContracts) XXX_DiscardUnknown() { + xxx_messageInfo_ExecutedContracts.DiscardUnknown(m) +} + +var xxx_messageInfo_ExecutedContracts proto.InternalMessageInfo + +func (m *ExecutedContracts) GetContractAddresses() []string { + if m != nil { + return m.ContractAddresses + } + return nil +} + +func init() { + proto.RegisterType((*ExecutedContracts)(nil), "terra.wasm.v1.ExecutedContracts") +} + +func init() { + proto.RegisterFile("terra/wasm/v1/executed_contracts.proto", fileDescriptor_200f5f891d4ef8d1) +} + +var fileDescriptor_200f5f891d4ef8d1 = []byte{ + // 186 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x2b, 0x49, 0x2d, 0x2a, + 0x4a, 0xd4, 0x2f, 0x4f, 0x2c, 0xce, 0xd5, 0x2f, 0x33, 0xd4, 0x4f, 0xad, 0x48, 0x4d, 0x2e, 0x2d, + 0x49, 0x4d, 0x89, 0x4f, 0xce, 0xcf, 0x2b, 0x29, 0x4a, 0x4c, 0x2e, 0x29, 0xd6, 0x2b, 0x28, 0xca, + 0x2f, 0xc9, 0x17, 0xe2, 0x05, 0xab, 0xd3, 0x03, 0xa9, 0xd3, 0x2b, 0x33, 0x54, 0x72, 0xe2, 0x12, + 0x74, 0x85, 0x2a, 0x75, 0x86, 0xa9, 0x14, 0xd2, 0xe5, 0x12, 0x82, 0x69, 0x8b, 0x4f, 0x4c, 0x49, + 0x29, 0x4a, 0x2d, 0x2e, 0x4e, 0x2d, 0x96, 0x60, 0x54, 0x60, 0xd6, 0xe0, 0x0c, 0x12, 0x84, 0xc9, + 0x38, 0xc2, 0x24, 0x9c, 0xdc, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, + 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0x4a, + 0x27, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x1f, 0x6c, 0xaf, 0x6e, 0x6e, + 0x7e, 0x5e, 0x6a, 0xa5, 0x7e, 0x72, 0x7e, 0x51, 0xaa, 0x7e, 0x99, 0x91, 0x7e, 0x05, 0xd8, 0xbd, + 0x29, 0xfa, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x49, 0x6c, 0x60, 0x17, 0x1a, 0x03, 0x02, 0x00, 0x00, + 0xff, 0xff, 0xdf, 0xf2, 0xb2, 0xd1, 0xcb, 0x00, 0x00, 0x00, +} + +func (m *ExecutedContracts) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ExecutedContracts) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ExecutedContracts) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ContractAddresses) > 0 { + for iNdEx := len(m.ContractAddresses) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.ContractAddresses[iNdEx]) + copy(dAtA[i:], m.ContractAddresses[iNdEx]) + i = encodeVarintExecutedContracts(dAtA, i, uint64(len(m.ContractAddresses[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintExecutedContracts(dAtA []byte, offset int, v uint64) int { + offset -= sovExecutedContracts(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *ExecutedContracts) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.ContractAddresses) > 0 { + for _, s := range m.ContractAddresses { + l = len(s) + n += 1 + l + sovExecutedContracts(uint64(l)) + } + } + return n +} + +func sovExecutedContracts(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozExecutedContracts(x uint64) (n int) { + return sovExecutedContracts(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *ExecutedContracts) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowExecutedContracts + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ExecutedContracts: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ExecutedContracts: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ContractAddresses", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowExecutedContracts + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthExecutedContracts + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthExecutedContracts + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ContractAddresses = append(m.ContractAddresses, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipExecutedContracts(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthExecutedContracts + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipExecutedContracts(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowExecutedContracts + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowExecutedContracts + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowExecutedContracts + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthExecutedContracts + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupExecutedContracts + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthExecutedContracts + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthExecutedContracts = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowExecutedContracts = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupExecutedContracts = fmt.Errorf("proto: unexpected end of group") +) From 442dd786d10546b4feb9ce38bc583ce4088d61aa Mon Sep 17 00:00:00 2001 From: emidev98 Date: Thu, 9 Nov 2023 08:31:42 +0200 Subject: [PATCH 06/13] chore: remove unnecessary file --- x/wasmd/types/executed_contracts.pb.go | 324 ------------------------- 1 file changed, 324 deletions(-) delete mode 100644 x/wasmd/types/executed_contracts.pb.go diff --git a/x/wasmd/types/executed_contracts.pb.go b/x/wasmd/types/executed_contracts.pb.go deleted file mode 100644 index 0726c1ce..00000000 --- a/x/wasmd/types/executed_contracts.pb.go +++ /dev/null @@ -1,324 +0,0 @@ -// Code generated by protoc-gen-gogo. DO NOT EDIT. -// source: terra/wasm/v1/executed_contracts.proto - -package types - -import ( - fmt "fmt" - proto "github.com/cosmos/gogoproto/proto" - io "io" - math "math" - math_bits "math/bits" -) - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package - -// ExecutedContracts is a structure type that contains the list of executed contracts -// in a specific transaction. -type ExecutedContracts struct { - ContractAddresses []string `protobuf:"bytes,1,rep,name=contract_addresses,json=contractAddresses,proto3" json:"contract_addresses,omitempty"` -} - -func (m *ExecutedContracts) Reset() { *m = ExecutedContracts{} } -func (m *ExecutedContracts) String() string { return proto.CompactTextString(m) } -func (*ExecutedContracts) ProtoMessage() {} -func (*ExecutedContracts) Descriptor() ([]byte, []int) { - return fileDescriptor_200f5f891d4ef8d1, []int{0} -} -func (m *ExecutedContracts) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *ExecutedContracts) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_ExecutedContracts.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *ExecutedContracts) XXX_Merge(src proto.Message) { - xxx_messageInfo_ExecutedContracts.Merge(m, src) -} -func (m *ExecutedContracts) XXX_Size() int { - return m.Size() -} -func (m *ExecutedContracts) XXX_DiscardUnknown() { - xxx_messageInfo_ExecutedContracts.DiscardUnknown(m) -} - -var xxx_messageInfo_ExecutedContracts proto.InternalMessageInfo - -func (m *ExecutedContracts) GetContractAddresses() []string { - if m != nil { - return m.ContractAddresses - } - return nil -} - -func init() { - proto.RegisterType((*ExecutedContracts)(nil), "terra.wasm.v1.ExecutedContracts") -} - -func init() { - proto.RegisterFile("terra/wasm/v1/executed_contracts.proto", fileDescriptor_200f5f891d4ef8d1) -} - -var fileDescriptor_200f5f891d4ef8d1 = []byte{ - // 186 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x2b, 0x49, 0x2d, 0x2a, - 0x4a, 0xd4, 0x2f, 0x4f, 0x2c, 0xce, 0xd5, 0x2f, 0x33, 0xd4, 0x4f, 0xad, 0x48, 0x4d, 0x2e, 0x2d, - 0x49, 0x4d, 0x89, 0x4f, 0xce, 0xcf, 0x2b, 0x29, 0x4a, 0x4c, 0x2e, 0x29, 0xd6, 0x2b, 0x28, 0xca, - 0x2f, 0xc9, 0x17, 0xe2, 0x05, 0xab, 0xd3, 0x03, 0xa9, 0xd3, 0x2b, 0x33, 0x54, 0x72, 0xe2, 0x12, - 0x74, 0x85, 0x2a, 0x75, 0x86, 0xa9, 0x14, 0xd2, 0xe5, 0x12, 0x82, 0x69, 0x8b, 0x4f, 0x4c, 0x49, - 0x29, 0x4a, 0x2d, 0x2e, 0x4e, 0x2d, 0x96, 0x60, 0x54, 0x60, 0xd6, 0xe0, 0x0c, 0x12, 0x84, 0xc9, - 0x38, 0xc2, 0x24, 0x9c, 0xdc, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, - 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0x4a, - 0x27, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x1f, 0x6c, 0xaf, 0x6e, 0x6e, - 0x7e, 0x5e, 0x6a, 0xa5, 0x7e, 0x72, 0x7e, 0x51, 0xaa, 0x7e, 0x99, 0x91, 0x7e, 0x05, 0xd8, 0xbd, - 0x29, 0xfa, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x49, 0x6c, 0x60, 0x17, 0x1a, 0x03, 0x02, 0x00, 0x00, - 0xff, 0xff, 0xdf, 0xf2, 0xb2, 0xd1, 0xcb, 0x00, 0x00, 0x00, -} - -func (m *ExecutedContracts) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ExecutedContracts) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *ExecutedContracts) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.ContractAddresses) > 0 { - for iNdEx := len(m.ContractAddresses) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.ContractAddresses[iNdEx]) - copy(dAtA[i:], m.ContractAddresses[iNdEx]) - i = encodeVarintExecutedContracts(dAtA, i, uint64(len(m.ContractAddresses[iNdEx]))) - i-- - dAtA[i] = 0xa - } - } - return len(dAtA) - i, nil -} - -func encodeVarintExecutedContracts(dAtA []byte, offset int, v uint64) int { - offset -= sovExecutedContracts(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - dAtA[offset] = uint8(v) - return base -} -func (m *ExecutedContracts) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if len(m.ContractAddresses) > 0 { - for _, s := range m.ContractAddresses { - l = len(s) - n += 1 + l + sovExecutedContracts(uint64(l)) - } - } - return n -} - -func sovExecutedContracts(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} -func sozExecutedContracts(x uint64) (n int) { - return sovExecutedContracts(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (m *ExecutedContracts) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowExecutedContracts - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ExecutedContracts: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ExecutedContracts: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ContractAddresses", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowExecutedContracts - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthExecutedContracts - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthExecutedContracts - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.ContractAddresses = append(m.ContractAddresses, string(dAtA[iNdEx:postIndex])) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipExecutedContracts(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthExecutedContracts - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func skipExecutedContracts(dAtA []byte) (n int, err error) { - l := len(dAtA) - iNdEx := 0 - depth := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowExecutedContracts - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowExecutedContracts - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if dAtA[iNdEx-1] < 0x80 { - break - } - } - case 1: - iNdEx += 8 - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowExecutedContracts - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if length < 0 { - return 0, ErrInvalidLengthExecutedContracts - } - iNdEx += length - case 3: - depth++ - case 4: - if depth == 0 { - return 0, ErrUnexpectedEndOfGroupExecutedContracts - } - depth-- - case 5: - iNdEx += 4 - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - if iNdEx < 0 { - return 0, ErrInvalidLengthExecutedContracts - } - if depth == 0 { - return iNdEx, nil - } - } - return 0, io.ErrUnexpectedEOF -} - -var ( - ErrInvalidLengthExecutedContracts = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowExecutedContracts = fmt.Errorf("proto: integer overflow") - ErrUnexpectedEndOfGroupExecutedContracts = fmt.Errorf("proto: unexpected end of group") -) From 844393fc397036825bb3cba36c7da168327283e9 Mon Sep 17 00:00:00 2001 From: emidev98 Date: Thu, 9 Nov 2023 10:14:01 +0200 Subject: [PATCH 07/13] feat: remove golang 21 features and change logos --- Makefile | 19 ++++++++++++------- app/app.go | 8 +++++--- app/keepers/keepers.go | 14 ++++++++------ app/modules.go | 16 +++++++++------- app/post/post.go | 3 ++- app/upgrade_handler.go | 9 +++++---- app/upgrades/v2.7/upgrade.go | 5 +++-- core_logo.svg => docs/core_logo.svg | 0 docs/terra_logo.svg | 1 + readme.md | 4 ++-- x/bank/types/errors.go | 1 + x/feeshare/post/post.go | 9 +++++---- x/tokenfactory/bindings/wasm.go | 3 ++- x/wasm/keeper/contracts.go | 3 ++- x/wasm/keeper/keeper.go | 10 +++++----- x/wasm/keeper/msg_server.go | 1 + x/wasm/module.go | 3 ++- 17 files changed, 65 insertions(+), 44 deletions(-) rename core_logo.svg => docs/core_logo.svg (100%) create mode 100644 docs/terra_logo.svg diff --git a/Makefile b/Makefile index dc670180..98a69f38 100644 --- a/Makefile +++ b/Makefile @@ -328,11 +328,16 @@ lint: lint-fix: golangci-lint run --fix --out-format=tab --issues-exit-code=0 - -.PHONY: lint lint-fix -format: - find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "./client/docs/statik/statik.go" -not -path "./tests/mocks/*" -not -name '*.pb.go' -not -path "./_build/*" | xargs gofmt -w -s - find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "./client/docs/statik/statik.go" -not -path "./tests/mocks/*" -not -name '*.pb.go' -not -path "./_build/*" | xargs misspell -w - find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "./client/docs/statik/statik.go" -not -path "./tests/mocks/*" -not -name '*.pb.go' -not -path "./_build/*" | xargs goimports -w -local github.com/cosmos/cosmos-sdk -.PHONY: format +format-tools: + go install mvdan.cc/gofumpt@latest + go install github.com/client9/misspell/cmd/misspell@latest + go install golang.org/x/tools/cmd/goimports@latest + go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest + +format: format-tools + find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "*statik*" -not -name '*.pb.go' | xargs gofmt -w -s + find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "*statik*" -not -name '*.pb.go' | xargs misspell -w + find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "*statik*" -not -name '*.pb.go' | xargs goimports -w -local github.com/cosmos/cosmos-sdk + +.PHONY: lint format-tools format \ No newline at end of file diff --git a/app/app.go b/app/app.go index 7d53b3cc..95270325 100644 --- a/app/app.go +++ b/app/app.go @@ -8,9 +8,10 @@ import ( "path/filepath" "reflect" // #nosec G702 - authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation" "github.com/prometheus/client_golang/prometheus" + authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation" + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" "github.com/terra-money/core/v2/app/keepers" "github.com/terra-money/core/v2/app/post" @@ -28,6 +29,9 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec/types" + custombankmodule "github.com/terra-money/core/v2/x/bank" + customwasmodule "github.com/terra-money/core/v2/x/wasm" + "github.com/cosmos/cosmos-sdk/baseapp" nodeservice "github.com/cosmos/cosmos-sdk/client/grpc/node" "github.com/cosmos/cosmos-sdk/client/grpc/tmservice" @@ -52,8 +56,6 @@ import ( distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" "github.com/cosmos/cosmos-sdk/x/evidence" feegrantmodule "github.com/cosmos/cosmos-sdk/x/feegrant/module" - custombankmodule "github.com/terra-money/core/v2/x/bank" - customwasmodule "github.com/terra-money/core/v2/x/wasm" "github.com/cosmos/cosmos-sdk/x/gov" govclient "github.com/cosmos/cosmos-sdk/x/gov/client" diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index 4bbd70d4..5186053e 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -6,6 +6,12 @@ import ( "path/filepath" + "github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v7/router" + ibctransfer "github.com/cosmos/ibc-go/v7/modules/apps/transfer" + ibcclient "github.com/cosmos/ibc-go/v7/modules/core/02-client" + ibcclienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + "github.com/spf13/cast" + "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/codec" @@ -28,11 +34,6 @@ import ( "github.com/cosmos/cosmos-sdk/x/params" paramproposal "github.com/cosmos/cosmos-sdk/x/params/types/proposal" "github.com/cosmos/cosmos-sdk/x/upgrade" - "github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v7/router" - ibctransfer "github.com/cosmos/ibc-go/v7/modules/apps/transfer" - ibcclient "github.com/cosmos/ibc-go/v7/modules/core/02-client" - ibcclienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" - "github.com/spf13/cast" "github.com/cosmos/cosmos-sdk/x/feegrant" govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" @@ -58,7 +59,6 @@ import ( routerkeeper "github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v7/router/keeper" routertypes "github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v7/router/types" - evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" icq "github.com/cosmos/ibc-apps/modules/async-icq/v7" icacontrollertypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/controller/types" icahostkeeper "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/host/keeper" @@ -71,6 +71,8 @@ import ( porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types" ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" + evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" + icqkeeper "github.com/cosmos/ibc-apps/modules/async-icq/v7/keeper" icqtypes "github.com/cosmos/ibc-apps/modules/async-icq/v7/types" diff --git a/app/modules.go b/app/modules.go index 3b1e98be..29d7767d 100644 --- a/app/modules.go +++ b/app/modules.go @@ -39,12 +39,6 @@ import ( "github.com/cosmos/cosmos-sdk/x/upgrade" wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" - vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" - capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" - consensusparamtypes "github.com/cosmos/cosmos-sdk/x/consensus/types" - evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" - paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" - upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" "github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v7/router" routertypes "github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v7/router/types" icqtypes "github.com/cosmos/ibc-apps/modules/async-icq/v7/types" @@ -60,6 +54,13 @@ import ( feesharetypes "github.com/terra-money/core/v2/x/feeshare/types" tokenfactorytypes "github.com/terra-money/core/v2/x/tokenfactory/types" + vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types" + consensusparamtypes "github.com/cosmos/cosmos-sdk/x/consensus/types" + evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + icq "github.com/cosmos/ibc-apps/modules/async-icq/v7" solomachine "github.com/cosmos/ibc-go/v7/modules/light-clients/06-solomachine" @@ -79,9 +80,10 @@ import ( pob "github.com/skip-mev/pob/x/builder" pobtype "github.com/skip-mev/pob/x/builder/types" - "github.com/cosmos/cosmos-sdk/x/feegrant" ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" terrappsparams "github.com/terra-money/core/v2/app/params" + + "github.com/cosmos/cosmos-sdk/x/feegrant" ) // ModuleBasics defines the module BasicManager is in charge of setting up basic, diff --git a/app/post/post.go b/app/post/post.go index 4eb194ef..d10d3668 100644 --- a/app/post/post.go +++ b/app/post/post.go @@ -1,10 +1,11 @@ package post import ( - sdk "github.com/cosmos/cosmos-sdk/types" feesharepost "github.com/terra-money/core/v2/x/feeshare/post" customwasmkeeper "github.com/terra-money/core/v2/x/wasm/keeper" wasmpost "github.com/terra-money/core/v2/x/wasm/post" + + sdk "github.com/cosmos/cosmos-sdk/types" ) type HandlerOptions struct { diff --git a/app/upgrade_handler.go b/app/upgrade_handler.go index f51d7984..47966114 100644 --- a/app/upgrade_handler.go +++ b/app/upgrade_handler.go @@ -1,10 +1,6 @@ package app import ( - storetypes "github.com/cosmos/cosmos-sdk/store/types" - consensusparamtypes "github.com/cosmos/cosmos-sdk/x/consensus/types" - crisistypes "github.com/cosmos/cosmos-sdk/x/crisis/types" - upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" ibchookstypes "github.com/cosmos/ibc-apps/modules/ibc-hooks/v7/types" icacontrollertypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/controller/types" ibcfeetypes "github.com/cosmos/ibc-go/v7/modules/apps/29-fee/types" @@ -20,6 +16,11 @@ import ( feesharetypes "github.com/terra-money/core/v2/x/feeshare/types" tokenfactorytypes "github.com/terra-money/core/v2/x/tokenfactory/types" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + consensusparamtypes "github.com/cosmos/cosmos-sdk/x/consensus/types" + crisistypes "github.com/cosmos/cosmos-sdk/x/crisis/types" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + icqtypes "github.com/cosmos/ibc-apps/modules/async-icq/v7/types" ) diff --git a/app/upgrades/v2.7/upgrade.go b/app/upgrades/v2.7/upgrade.go index e4be4bdd..431f11e7 100644 --- a/app/upgrades/v2.7/upgrade.go +++ b/app/upgrades/v2.7/upgrade.go @@ -1,12 +1,13 @@ package v2_7 import ( + icqkeeper "github.com/cosmos/ibc-apps/modules/async-icq/v7/keeper" + icqtypes "github.com/cosmos/ibc-apps/modules/async-icq/v7/types" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" - icqkeeper "github.com/cosmos/ibc-apps/modules/async-icq/v7/keeper" - icqtypes "github.com/cosmos/ibc-apps/modules/async-icq/v7/types" ) func CreateUpgradeHandler( diff --git a/core_logo.svg b/docs/core_logo.svg similarity index 100% rename from core_logo.svg rename to docs/core_logo.svg diff --git a/docs/terra_logo.svg b/docs/terra_logo.svg new file mode 100644 index 00000000..ec50ff71 --- /dev/null +++ b/docs/terra_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/readme.md b/readme.md index 17ea811c..0da5e8ce 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@

 

- +

@@ -98,7 +98,7 @@ If you are interested in contributing to the Terra Core source code, please revi

 

- +

© 2023 Terraform Labs, PTE LTD diff --git a/x/bank/types/errors.go b/x/bank/types/errors.go index 30fcc409..1aba6a45 100644 --- a/x/bank/types/errors.go +++ b/x/bank/types/errors.go @@ -4,6 +4,7 @@ package types import ( sdkerrors "cosmossdk.io/errors" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" ) diff --git a/x/feeshare/post/post.go b/x/feeshare/post/post.go index 0e91037c..0a343c95 100644 --- a/x/feeshare/post/post.go +++ b/x/feeshare/post/post.go @@ -1,8 +1,6 @@ package ante import ( - "slices" - errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" @@ -139,8 +137,11 @@ func CalculateFee(fees sdk.Coins, govPercent sdk.Dec, pairs int, allowedDenoms [ alloedFeesDenoms = fees } else { for _, fee := range fees { - if slices.Contains(allowedDenoms, fee.Denom) { - alloedFeesDenoms = alloedFeesDenoms.Add(fee) + for _, allowedDenom := range allowedDenoms { + if fee.Denom == allowedDenom { + alloedFeesDenoms = alloedFeesDenoms.Add(fee) + break + } } } } diff --git a/x/tokenfactory/bindings/wasm.go b/x/tokenfactory/bindings/wasm.go index 3ce9b23c..1850b774 100644 --- a/x/tokenfactory/bindings/wasm.go +++ b/x/tokenfactory/bindings/wasm.go @@ -3,8 +3,9 @@ package bindings import ( "github.com/CosmWasm/wasmd/x/wasm" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" - bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" tokenfactorykeeper "github.com/terra-money/core/v2/x/tokenfactory/keeper" + + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" ) func RegisterCustomPlugins( diff --git a/x/wasm/keeper/contracts.go b/x/wasm/keeper/contracts.go index d085e0f2..deb9f0c1 100644 --- a/x/wasm/keeper/contracts.go +++ b/x/wasm/keeper/contracts.go @@ -1,8 +1,9 @@ package keeper import ( - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/terra-money/core/v2/x/wasm/types" + + sdk "github.com/cosmos/cosmos-sdk/types" ) func (k Keeper) GetExecutedContractAddresses(ctx sdk.Context) (contracts types.ExecutedContracts, found bool) { diff --git a/x/wasm/keeper/keeper.go b/x/wasm/keeper/keeper.go index c8e2e57e..3e5c9d2d 100644 --- a/x/wasm/keeper/keeper.go +++ b/x/wasm/keeper/keeper.go @@ -1,9 +1,8 @@ package keeper import ( - "slices" - wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + "github.com/cosmos/cosmos-sdk/codec" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -83,10 +82,11 @@ func (k Keeper) AfterExecuteContract(ctx sdk.Context, msg *types.MsgExecuteContr } // if the contract address has already been // added just skip it to avoid duplicates - if slices.Contains(contractAddresses, attr.Value) { - continue + for _, item := range contractAddresses { + if item == attr.Value { + continue + } } - contractAddresses = append(contractAddresses, attr.Value) } } diff --git a/x/wasm/keeper/msg_server.go b/x/wasm/keeper/msg_server.go index 5bccdb3a..1629aa60 100644 --- a/x/wasm/keeper/msg_server.go +++ b/x/wasm/keeper/msg_server.go @@ -5,6 +5,7 @@ import ( wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" "github.com/CosmWasm/wasmd/x/wasm/types" + sdk "github.com/cosmos/cosmos-sdk/types" ) diff --git a/x/wasm/module.go b/x/wasm/module.go index 5ebd5e38..b80ba220 100644 --- a/x/wasm/module.go +++ b/x/wasm/module.go @@ -11,8 +11,9 @@ import ( "github.com/CosmWasm/wasmd/x/wasm/simulation" "github.com/CosmWasm/wasmd/x/wasm/types" - "github.com/cosmos/cosmos-sdk/types/module" customwasmkeeper "github.com/terra-money/core/v2/x/wasm/keeper" + + "github.com/cosmos/cosmos-sdk/types/module" ) // AppModule implements an application module for the wasm module. From 957cc253a5a328a49b57dd00a5c1005c6e741701 Mon Sep 17 00:00:00 2001 From: emidev98 Date: Thu, 9 Nov 2023 18:40:55 +0200 Subject: [PATCH 08/13] fix: wasm url --- proto/terra/wasm/v1/executed_contracts.proto | 2 +- x/wasm/types/executed_contracts.pb.go | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/proto/terra/wasm/v1/executed_contracts.proto b/proto/terra/wasm/v1/executed_contracts.proto index b101aa12..e37e42a6 100644 --- a/proto/terra/wasm/v1/executed_contracts.proto +++ b/proto/terra/wasm/v1/executed_contracts.proto @@ -2,7 +2,7 @@ syntax = "proto3"; package terra.wasm.v1; -option go_package = "github.com/terra-money/core/v2/x/wasmd/types"; +option go_package = "github.com/terra-money/core/v2/x/wasm/types"; // ExecutedContracts is a structure type that contains the list of executed contracts // in a specific transaction. diff --git a/x/wasm/types/executed_contracts.pb.go b/x/wasm/types/executed_contracts.pb.go index 0726c1ce..afe33f1e 100644 --- a/x/wasm/types/executed_contracts.pb.go +++ b/x/wasm/types/executed_contracts.pb.go @@ -77,19 +77,19 @@ func init() { } var fileDescriptor_200f5f891d4ef8d1 = []byte{ - // 186 bytes of a gzipped FileDescriptorProto + // 184 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x2b, 0x49, 0x2d, 0x2a, 0x4a, 0xd4, 0x2f, 0x4f, 0x2c, 0xce, 0xd5, 0x2f, 0x33, 0xd4, 0x4f, 0xad, 0x48, 0x4d, 0x2e, 0x2d, 0x49, 0x4d, 0x89, 0x4f, 0xce, 0xcf, 0x2b, 0x29, 0x4a, 0x4c, 0x2e, 0x29, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x05, 0xab, 0xd3, 0x03, 0xa9, 0xd3, 0x2b, 0x33, 0x54, 0x72, 0xe2, 0x12, 0x74, 0x85, 0x2a, 0x75, 0x86, 0xa9, 0x14, 0xd2, 0xe5, 0x12, 0x82, 0x69, 0x8b, 0x4f, 0x4c, 0x49, 0x29, 0x4a, 0x2d, 0x2e, 0x4e, 0x2d, 0x96, 0x60, 0x54, 0x60, 0xd6, 0xe0, 0x0c, 0x12, 0x84, 0xc9, - 0x38, 0xc2, 0x24, 0x9c, 0xdc, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, + 0x38, 0xc2, 0x24, 0x9c, 0x5c, 0x4f, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0x4a, - 0x27, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x1f, 0x6c, 0xaf, 0x6e, 0x6e, - 0x7e, 0x5e, 0x6a, 0xa5, 0x7e, 0x72, 0x7e, 0x51, 0xaa, 0x7e, 0x99, 0x91, 0x7e, 0x05, 0xd8, 0xbd, - 0x29, 0xfa, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x49, 0x6c, 0x60, 0x17, 0x1a, 0x03, 0x02, 0x00, 0x00, - 0xff, 0xff, 0xdf, 0xf2, 0xb2, 0xd1, 0xcb, 0x00, 0x00, 0x00, + 0x3b, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x1f, 0x6c, 0xaf, 0x6e, 0x6e, + 0x7e, 0x5e, 0x6a, 0xa5, 0x7e, 0x72, 0x7e, 0x51, 0xaa, 0x7e, 0x99, 0x91, 0x7e, 0x05, 0xc4, 0xbd, + 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x49, 0x6c, 0x60, 0x07, 0x1a, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, + 0x66, 0x6b, 0xb9, 0x17, 0xca, 0x00, 0x00, 0x00, } func (m *ExecutedContracts) Marshal() (dAtA []byte, err error) { From 9257d61a4ae2886aceebb5d405aaec9703b9287d Mon Sep 17 00:00:00 2001 From: emidev98 Date: Fri, 10 Nov 2023 17:37:15 +0200 Subject: [PATCH 09/13] wip: feeshare tests --- integration-tests/package-lock.json | 14 +-- integration-tests/package.json | 2 +- integration-tests/src/contracts/no100.wasm | Bin 0 -> 129823 bytes integration-tests/src/helpers/const.ts | 4 +- integration-tests/src/helpers/mnemonics.ts | 9 +- .../modules/{ => alliance}/alliance.test.ts | 2 +- .../src/modules/{ => auth}/auth.test.ts | 2 +- .../modules/{ => feeshare}/feeshare.test.ts | 8 +- .../src/modules/{ => gov}/gov.test.ts | 2 +- .../src/modules/{ => ica}/icav1.test.ts | 2 +- .../src/modules/{ => icq}/icqv1.test.ts | 2 +- .../src/modules/{ => pob}/pob.test.ts | 2 +- .../modules/tokenfactory/tokenfactory.test.ts | 81 ++++++++++++++++++ 13 files changed, 105 insertions(+), 25 deletions(-) create mode 100644 integration-tests/src/contracts/no100.wasm rename integration-tests/src/modules/{ => alliance}/alliance.test.ts (99%) rename integration-tests/src/modules/{ => auth}/auth.test.ts (98%) rename integration-tests/src/modules/{ => feeshare}/feeshare.test.ts (95%) rename integration-tests/src/modules/{ => gov}/gov.test.ts (99%) rename integration-tests/src/modules/{ => ica}/icav1.test.ts (99%) rename integration-tests/src/modules/{ => icq}/icqv1.test.ts (99%) rename integration-tests/src/modules/{ => pob}/pob.test.ts (98%) create mode 100644 integration-tests/src/modules/tokenfactory/tokenfactory.test.ts diff --git a/integration-tests/package-lock.json b/integration-tests/package-lock.json index 5042da70..6a45d2ee 100644 --- a/integration-tests/package-lock.json +++ b/integration-tests/package-lock.json @@ -9,7 +9,7 @@ "version": "v2.7.0", "license": "MIT", "dependencies": { - "@terra-money/feather.js": "^2.0.0-beta.6", + "@terra-money/feather.js": "^2.0.0-beta.8", "@terra-money/terra.proto": "^4.0.1", "moment": "^2.29.4" }, @@ -1803,9 +1803,9 @@ } }, "node_modules/@terra-money/feather.js": { - "version": "2.0.0-beta.6", - "resolved": "https://registry.npmjs.org/@terra-money/feather.js/-/feather.js-2.0.0-beta.6.tgz", - "integrity": "sha512-bZIyzCM2IRnh8fbhc5XEW4LY+5Wr0Aiw59L5e3Tr5Qyy/cP+Og5kWmTKBblvjk9G1XsEdeV/KdxR5/vsLI3lgQ==", + "version": "2.0.0-beta.8", + "resolved": "https://registry.npmjs.org/@terra-money/feather.js/-/feather.js-2.0.0-beta.8.tgz", + "integrity": "sha512-lIVCPTf/YXDigujVPHnjsPDg0Gnjmo4y5Z/bGVSOj4mqHcPOsKjzAeu1ZKZyRqVVTU6/y4EJhroOxoTcSbPwTg==", "dependencies": { "@ethersproject/bytes": "^5.7.0", "@terra-money/legacy.proto": "npm:@terra-money/terra.proto@^0.1.7", @@ -7008,9 +7008,9 @@ } }, "@terra-money/feather.js": { - "version": "2.0.0-beta.6", - "resolved": "https://registry.npmjs.org/@terra-money/feather.js/-/feather.js-2.0.0-beta.6.tgz", - "integrity": "sha512-bZIyzCM2IRnh8fbhc5XEW4LY+5Wr0Aiw59L5e3Tr5Qyy/cP+Og5kWmTKBblvjk9G1XsEdeV/KdxR5/vsLI3lgQ==", + "version": "2.0.0-beta.8", + "resolved": "https://registry.npmjs.org/@terra-money/feather.js/-/feather.js-2.0.0-beta.8.tgz", + "integrity": "sha512-lIVCPTf/YXDigujVPHnjsPDg0Gnjmo4y5Z/bGVSOj4mqHcPOsKjzAeu1ZKZyRqVVTU6/y4EJhroOxoTcSbPwTg==", "requires": { "@ethersproject/bytes": "^5.7.0", "@terra-money/legacy.proto": "npm:@terra-money/terra.proto@^0.1.7", diff --git a/integration-tests/package.json b/integration-tests/package.json index 8f199d3f..d1197f89 100644 --- a/integration-tests/package.json +++ b/integration-tests/package.json @@ -30,7 +30,7 @@ "typescript": "^5.2.2" }, "dependencies": { - "@terra-money/feather.js": "^2.0.0-beta.6", + "@terra-money/feather.js": "^2.0.0-beta.8", "@terra-money/terra.proto": "^4.0.1", "moment": "^2.29.4" } diff --git a/integration-tests/src/contracts/no100.wasm b/integration-tests/src/contracts/no100.wasm new file mode 100644 index 0000000000000000000000000000000000000000..8f1e50fac685ccdbf14c4f5104e0cfd1348b7644 GIT binary patch literal 129823 zcmeFa54>L0S?9a|y#LO5&wKKoBtRgP?)R0^lghEF7YRkI*+)Xbwo~djcRF)>1zMtT zLMfyvL$#Nrl45Gnrj}Z)*hWhWt#D11j$uS6^)lW}r5e4=h|2Yo-eDB4=wM|=yrP}& z?|IhRd!Ii!AwVs6K0`j7cmG*`p7pHf|61$W$(8SXdzvIk`tkI%E3)0Y)7|=;T#@g# zYyBiwqp-hDIwad)$|*eJ(QmaWORUw32s%D28_=dPr!>+V%=y?p2PE3Zmgy6bWGFLz$EYkQLF zcEVo1{T(-MuM5BPZC75GWU=aX+i%+CRR>pIb=A(xZ@lu_Yp%MI`qtcxJ)^=quDj-K zy!pZHNuruw_Ud=N{mSdAXTx`Hf7|un`fca!c-7@MZr^#$_rKda*-$^;x&3WGvh(um z-}TlV+uxm>s+Oj=U-hlu_HD0v^`biKMA+Y&y+*z1;pw`LC1bX`XkO+oY}k_|4KJ>!g%Pd6VTymL&a&ysIM1 z|4Y+04W!)!^2qaEn&?jdsLa1syVJ`D-A*T;986BqPm2;1PQiX#{p&w~N!n{Vz>;>R zIw^o!iHW>Tt67>;shzHE(_FvRwOYNT<^NgrB;1KYsgZVhsxCT$A1C29{fuThX~qrzQEt6m=+7|AKt$GRr`Ma!Wc> zJ%fsOUb*WXJ9)V7n(Mgv{wv?MUBvLtYu<6)<>&Kw^76}})hjRGe%)1I@AAv9-hSov zm%sJOcW%$Ki9?{MO#3%|<1N`Y{i8$aD|Y{Ix+i^ax;K4adTaV4>Av*+>5rx#NN-EO z`P^5|zUo^x|5`S;KYhzT|6kttjo1CtfAY`%#hYJo)4%-YbFcYdU-kW0|JU>%ZTr5< z-+I-RZ@cW0e|+id-}K#Y_{I;XulPuMd%Eo#_h!GCeLnrhSNwANuhMP5nZDxRqz|UQ zmVWaC>9$`EU0bPo;mJ{#p8Dx~q=Qq(6~;IQvNUihbD~+0SM_ zmwh4oWOi5fsqAp}Z?ehTp6dKc_9t1|+WielS?m~h&rXVTHcLkBY-^FuF_Bw~_H5Q0 zb*9q3U5$H1yKF5`=3>1odc&cY=<&YjmFdE`b6!RvN))|?@|+8x`XVip3zC-A9IO4J z=gNMCO}oyDl(ovl$}AvoJIl+4rtNYQKoP3 zx@ES!ZuxUhC*L0W%oYTEw;V3eQfqj3S|r)-GAX*V*>Kw+IfGZd1^%2V42;wB7)rP3 zZpzk=TU+v^NXn^&!k#8YYfILGEUZXbES#P655h+Q(b?IFFsw1=MSAH#RSm0Sp6wd9 z7zO{oZsRyFdjvSFFF%Y<$=2nS_aWB@<>wj$##W6pqA zpeEY}yqe8gMKb)$lqu2mh~d#~kqby>UO$<^x~4z;3Z;NJ3lQfHagi;IfnWuQGa$}@ zm>$(D5Ni~In7@KJUk)*&1mfHw&K+V#TSLsV3SwPHh+7Sa^CrYkXITsV^*_kjSZFah zgJ1A%*d5;U+$novDP>PAqwK$IMXYab2$7CR+=WO-LZmGrqAP?*rxqd|6CzduA;O)w zqAfn;*>WMGnl41z5+a?VjS%tnt#yP)5F)&)nld5M4nl+%CPbFh5(AkKLB!BVfWZMR z+Chk@Q3;VHb)yim*Cs?-06%IYD~bhxwb_ikYZs3is&INuK=loq+bGz{s?e{AWSVF%H*}fpG!>&4zejakp$$=IpS24 za1w%NZ^_ncio?rdp=|9$o*BV;p>$K0P)J3NBSw!yY@>D`0R>SYZ_CXRrnk2vUkY3zo55Lr_&sEiW;oI)%VjEF)IY6_r7sllmCW5_pU7r{RWUe^=S7f8aSmDI7RQoDN7oRoMNwyP8q{_ z+4)62pKi%F$7$KTP#%5X;biz}w9O_|tA?##&GPUQ8QM&8U(`OEQE4_iUowr~b2a5W z8y*pQ0;5TK~FP(a#gz2Xe2yacld;+-2Ej)N4 zSty@;KQ~X_3Zx&!O8q$2ty3j}upOg5$9a*H0MhijB{?UZO$coVAdamq7XBPTPA9p>StE-$nV*}m)az{3C1Hg z!P=4??WPIVF4{7|x=R&Rw^mebml}W9G!I_V^J6?Ie2FKQpp*G&@MOA#C#}*8pC6O{ zIojgILBCnRhu3Gr-MNJJfiw#3M-yZkq;Q8g`Y1Pf$)n)`4B^ol)0Rl!%TRpC^0MVd z1W(>n{%!JFo=k@)R_UkI1`!SU%&IOhUOYT^OwuNZHoh{`z&cJN!1E*)>uJ?K$q0=Kw1F{CCO4=-qUX zmJ?{$*0#ZEQ>Wq`F^pm3esXBzIDerKu!8M;0}~T^E_w9`HR8>tB2f=lw5v--e$9X=?Gmpo9#_UUH$G!N64w! z>^(xwY<80vv(Z8-CEG23Xkj{QWr)X2c8vaE$?0L5Wc~7HoTyIuq4y#zJHvmQZ5(CD zK%S;N{ivR{{VAq_FlBt7m1pfL&w5vxTr!wu$oUOrx&!kA>DH%AYk(0KW+yhW!6yl6 zsS%WWQ)yKVqTJ(`VI1vZ!0?Mc-Dm=;W&(WAxfu+lzA(97NN)^pFa?a;DgZ;Xr`I3} zx&V@)_&tu{n;flcr)Jb#g_pr3y)a~lGdzAytxXNX=$PD(`|Ke+PI>JB-wfNcBCuJb z5u>$D~Jy`ZWqqWr3&AHr98B_1!Pl2k_Pd)7=p9y1U25U&ss5nA#M(h1(Xb*!9|eu@>t{ z&}x*x!S5aE=oCSJd*I`1i%iC$*)0g%jXp9A;o0nZNfnuq?OoUu1tFXveKDt}NT;R@ zq3FscN{e+{@*Y3y^<16_wq$06Agyc^pdpM;5sozoMj&3COC}^e<`TTS8yM*nhO%SS z#r>TDt8IwWU<|yw5~B<#AlB^{&`2>MXX#|oJhiQu5-N8NW&j!~0K+xG`PkPVoJ8oK zFhf-6pYkh_zhB`BO#2mxAJ|nv7^nr)tkG~Hp&NB;5=?ncuk=DXbPy3BL4oXsC~lId zaEfvDaHgK36{htIl?7=^O$sZ(WGd2`swFb*BhRKsT|nwjtwAIX<4Qn?Nd`Hb2pn8{UX8ZR+D%rlJM@v1<%FCf7A4aa;;_ z@RNw~Qp73d1I6HX6qjjXj4EBfIQ)dCX{_1bhK}6{f}K-MOcp(nscAkJC-^w$W$%&s ze_nP|k${8pBl4#TXU5@K@|f3nr30zl{@+N`R1wL_=`WBzh%kj1B`eNeaOXLn&j>W| z;K+|22CFjUC~*!2rT5Ou=z*&qSF`-C?tdyiNg@2spUhB7tGItai`?DpZsU66N%!y1 z50gtX=0%?TIG%iIj^oQy+05=NhG7V6!%!HmDJohfp#Li}H(}vsu3jz69b!$T&@)-8 zsSFI>oGYf3SwhHQ^SC2(f~RJifcI>a%l6~RCL>7B^V&N8LW3pyt*cGTNoa@Bv1X z{NhiH2GPQBU>biH+3ie?Z1uKVA?;wRTlmZD^?d3ySVm=lWe6g-jShM)gYo%!y-R_IK7A$|xrYp?|?rPtstNw?p*H%Z^&ovYM;5Qyvr-R%eVN_d&#EE1d(sS1XzP-?;rGZM+cg|5d|r4(9n#0E}RYEFvhGJde4Ue6DT=MsKc z0lP@lv+CFr=?QfQzpuT|gH43NZHdpUHB)Z}&CGqpn)_sUcqxD#-d9Y=Ct)I%@vxDQ zkhfNYy#x!$u-mIBK*Z>?wbjUx*_tS(!fa`xuxgujM4aQ7sUf3avm>(d@tPZt`K8H` zw_q43f~}TB;+POTm+g*Vscd(X#0Z6C{=Iwlz*USc;xgZ5dc)RMn0Z@EZDal|UQX@R%c*Kl?0%BVsXmYumQ%lwRU@$l3ltwX#y*o^ z_J7e*V`gQ#AxxsCl(j+yg=o+jlBJNXo2nx{GgFo6!Ze+NgZSwUXt-!2A^4FOsjfsz zX}SCLgAPCG@VmxX!IE68-li;m(P(24+q$%=l9^>;YwSCpLi`V&i5&!&W(;b{L_SHW zB1RG=E{ZLhAhNugSk1~Y_KtWwMjAXOuR`pfkR0AM8YllmH`MlWTH1^uXo#7#1)2N zBw~~z|F5zx)r*HkX*wE}6A>AkmqN8!h0YaJ=Qp65te|rJvJ@&hJTX*Q8A&N6Ah8~X zGodTdv;tp=xFC^mH`X$t4FB4|BJ6OU*#_g?xr)Hi;z`Vz6Lp~whFV%a-Jn}v=| z@UDVeRTO5?1aR2jhCLr!SwhgB5C4a=Z1Tg_@H5E|QJ3UL8!r;GtKeSpL(A3-4UbsM zc??I&xmB&@$pA*6{$y%Oe31-sv7nijhEOdm8Q1SYvBCyn{{j$a@EBMu*K03|gd;Z= z%J&5|(>E=QC@tC9W)_22A|-loEj*Pa>v#Y9Te9`L+j3|29 zj!Aa2pqNYcfr_v`;Y*Bj$@{Zvfg-Frs*=Up_f&0oTy?LcF43oyrirU$=J{k><3Dxq z46@of0Kt;rJo=jyT&rKn{PGFb=!J@~Xwy@@l^bV7Ws* zcZYh*+@W;pjHWwew`T3);W7|2kb&|k1AIV4eJ=USln`xbx8-K?x^l>rA{{p}uG1xE zpJ-Hm!YiMq%6}SGJEpy0=!&t@m2~24b~sa9WHx&+b1ir%!_wydesHGWgE;R+7ZpW6 z7E?9%sGF@{-(A1HwZ303F=cD`npB=t({5>k(kectKmz4`vIp=<)*) z#cakX>{p|Z!#Ir^sp|c;Kl`-_VCC6jCWE%cL~N8%P`ewLsHcY(Q!qtqw+PoE z*g_4?xR5wP_v%=k9BZqz^Q^(R&T3k^P^ zVCYSJG*k1D@lAt|jIWqh+nr#1Bw(ldNWY;7xm9*wJIG<1JGGS8g(uiCG#VnQu<3>j zysI$s($@?_0^41KAqr(0RRZWP%VJxn7i+c+riGbld+XvYzSUCQ(Hf(?^r$-mvr`@-#p2^V=2D~P-egaxWFYC6SjJBU1>i-27sz*wRO znoDb|A;>OS4chTwRs&(!++t;6NL#HY)(<|O%SV|ElnT9w^4hhiv*k~CZ$gNH_h@a2 z$sm+c-Irm@_bg1eTQk{Accy7DOxg1^Pp}wR=PJJ{#jK4E(Ol@yCQ149?|%O0J2>>E znG1b07mmK~H~;w0fA9Xs7nuuM*R*vLGZ$FHKjSiq*^QUMfdgA~W)#?a<@+UX%>W4U zf#`R|#SAHOF|6mK61t7*R69VqK}xy_Galg?msKV~D5d=daHgyZHSF?6WyrO++NY$Hkk1p5U3O; zJubpM;Z_69LSUN^isIB$<%*f;bY0+*)~Uq=v;=WfgfhV!7&oIcQc&hydd;Sp;!CMW zn@Hs5zoPls)*e{-)iuS!+ks*ocsFvN$sBJ_2TFY^Q0|j(ukiUqHfW)hjaXf}Oh8FR zJs9GJ!$L99q?j;~YRDIc1A$1UY|7z;(}@{xoC5z%WJ|I7+I<`!y;&2{p|`z9afF+L ze9;J5a2Sk&pc8k#YTPgX$-;E2l_WT25a1gZBo@kvOW_dlESw|^0EQ&cX|mx0Mu<$b z6?f^Emo3~k|JC%C(P=8BRy|U|To9=k_gKu`d?_L9SHEuOV1lSjsE^P-Z9bWwoKJ@T z2d0Wpx+yQjW7d&$`=)%ucmskebnu?>X~l-`ZM>I*YB11|Vn1v{QJ*uRPwpnl3vf;Q zD`9YiJUPvFdQ23j-Hm)PlT8@yDAHZw)AiwMqz;J_@|RMEX=9V8v-qrb`$U~br{VLg zTg3K(EjQSv98s}vF0g6#XtwF=L(kz_; zUe}vUu-_ zX*#ptR>7>P!Ul^+fwQ7e#mr`(0aL5wR4 zz|*YA>Yx;|xs3DXX4$T_x#V+c`4@HsV39lgGffJT0vb*F{+4?}t7`F{m0SEw(?iPZ z&O!$)!ULBZ6n7C#W&-Zu5%Yf_wXKzE`VMPaTg>K?pORIW0lZlg#{HEgVRpm`TqGi) zD0}yqrq76*()QD5+#9*ydxI9{k`FbUqjqqPSP_(KG<`<7PCQ~;RI1ARymG(9_$6i| z`(iMtx)DRhO_7kl(Rafu3UofUVEe(0icF-!?To1{f2do2GHz{@KcsaW{GlE@KJ4hq z($~QsvN%D}Q{2B~yNPfdJfhIr=^95`>1Pyqc=4Q_p?Z%G?D+hgp|Cl{ogqnx;D)X2 z3{~?LjRTd4y2ibytEtQ`ou0W^;F7joNSQY$ zYX!#^7gGpF^hr z98e^?OIh#Kn_t?g?=X_uQm50~@EA=V%Wu^f0ark%eY(~qPIOcv6=;;y&LorE_|c4d zLK8PxE3~i@%TQ{Cw!SzeEXA&uEqRK^q(O*RlQQf0mae8U^X?nrvr3D(2G9~LRFw+b zb2t&^T$>8Rpu%sHU1iFL_)7qpNjR{==+RE=Y%x~KNtAN{BhO&-%RmG8?4l<*z4wC= z;qsubxR)RK5e)j^z7NluPy0@JdvYxt{C(i;|4Q z0!K+)QkU(G$whkxkP6IPkbD!>*yT~$(m-B5R$Xd$*M+=%;se^g^1bwBKCagE zb@@$f?|QRcX`|O=Ji&0x@%M*+CDJAO-_qjT?85MA-LqkfmS(ea+!~%Wk^vK&^7HG1 zK!vU0sQnynu*J#Yr_+Mt6H$UK{VUJn<&#A==R&0>-bKVXzE7R59 zbj$(~>t{Ot00BC3bedDag6DRKi9nipPMiT)@Uw83UzyGjX60?R4O?P^?l#e#P7$In zG7G$Dl*|&MIqqwqm>(lN+qO}Nth6_?wJ;nDT+Rrk{sM%Z3%c0Rse_V*sa#@u@sO14 zM0*|%#_>v5$6^r8Q8c=i11@T_8N37(x13 zG$+GJJ9X(G28!M*!>?0*@4|QrMj3GokY%!zcEK$zO5^AXpfiI@5Q91Q{=L@A4TmW~|4LwjUYua47Y%}I*Fh8>P@FbhWfq3H*_>HA{SO3+|Ta6$4G zT}re{KV?CZYa{Egs?H>>U8R%A4wAOZNgmj~fv}B{64x&b*M$=FU=}FlX>Klg%aGJ5 zEX{enR{8lQkev1$Ao;RIFddT6j}K0!?vjoOkIEr^zH@Mw!0VU_lR%3~?0HN?9C4+@ zPzF4Ec4bkqOG3!D$nbZ$8pe}?c>{m)`*A^->0nZmB zKO>BEYpO&@uFC1hp~_4OLbL>IVwNoBC>|pcHMPq7Vb^kUSkWi+c0&66HPso==G8TQ zQm;XuzZpyHI!*~HqpMG?=efdgO}nS|aWg7FA6)_{y5xj_KJ~)@IeYbq3J4<@ ztSz$+9x?O(sKZ{38ImhJuMVkq+zu@;{e0=;L!ytV7fc`EV42kV8zF9eJ@z%vVeG!@ zOKT0OCB9oGDhTjL9RHsBIlbWZ z&uh%HyrW=!g!z+t+khFL>v`d-_SNhdfRGfxv0ah1YWIV^MJMWYcI7BU@22ib8Y;VL zY81@qU<3}7cMZ zCFQHJzW{#OWcPT6JT4^k-r z7F)9{WO`9KgLP}QGbr1c;eZa?B`bJVL2l=KuAYdUQT+g$Hv*s{9j z`EO^|OVbBw`{DQ}K z@xV{M+5U|c#|L|z2#Vv3&$c4Cz*wZHQC7?*hqi&PPkMFO)cn-eByEInC zyTo7${e*d#|H>D?@E?EjKmXn@|Ak@5O1&_Q$S^YF2)VmsI>(c@ZRoC>Kcu*3XZTs$ z1`x1T-lm6bJ#6?f@6*Gs9vV$~^*ym|M|W~$6+6hD8U$eL?&Hjh~{v2SqsY@KTx*$NspJKO9e4!kPx z#~B3*8|K>@qpnC2wJ1cUBvDp^r7nfoV81$O8RM%B_FC@QGF}uD z5S&IkV<2&uyqs8gCEhdWRV$}ca!$TS26DMeiV7WW}_m{p3P^Uivzf^Qx$ zUD9CGk*Zn+OwT038R!F~pRIP90aL7w5V(#ji9FDrIyXjGyd!3+<)96OOiBH@C|267 z1EdHY3$;0S^DdrhGejiOt|AifcV$Li7;PdgJVe@IRpOH(UZ%DR z0mTtouvc1_BLYp@Qh;Mc;@ahU@hKlKoy;W{#sJ2kXJqZo6}?E;-6eE=#UO|wkJK8e z9G_5vEUF15P}xZnSjw`D9oNHrvpkR_V9Z^9lZ0+Lv6YYi`tN+8BTG!wK_y#tvYKf{ zRczyqHDM5_D=AoBs6w;6wlKVaMPCJ~lB_myBP>fX@YbyhavpL(WtX;`xb2gZ&@9p- z^mYB%1j$XoQQxIaSDw<0mwOZp4~rZtu;|%7bA=)`Q6d#`7@Q9IS!J=vb#t{?B=DXw z5;5RC=^a#vf*#Lo$*(UONB)W#B~44b_f_bsAz&cz<1Eo!)l~D#Vr1D>0?gzhlDWbv zi$GUxK`g>lS^1EGef%E6>1Z#uyp;>99w^c5;X-=QJ6nRP(pU;7mc~-Az?kOG6U|kj zJrdtsuSw8}4t_k3kBJyt&x_^q>$i}H2gG7W+`kWY{<7?2#xdk*obA%iN_MfZZ2FZf z%c_jEoVZdmk~CVQl(28WfZE{2JNI(8g--=1T#}5w$OU=joR{{ zw`l_fe4`!_Gn}LS`X_2M@zves02_UpexpqYypc}1#j!7P`L(sO+$76(y#y%{D+D%!+JdKjDXme}I=9aXI6Ows)F^S|dWmDH;RrGFDMX*`5L|XnrtzfNE zXe~fr81S*$2hrJ=}-_H4Be zEJ(9u z5?JLtSmZ)VL;GfEHQxJtGAH7e@)6P{an)NG-iq>a>GtdWw9TFqgG!MSbr0of4sCLI z1UUFI@THhg8{g#Sw8XXk&qOBi!BXPNr<~XNBUXrMRT`@E}%79YJmd6<b=y+M^il+Hz`r5X zps(>65NSlNvyOpI9gyR88j7dQ;t2CFCUYtrcxxVmLmDS=y^;U~qkmhG-Z%e5a`W9z zYM!P|2kikER4aoGS7UQa1Pjt372mmlWDpH=*DC3elZdOi1^lal28|>xXC0!=>cN3|&h-+Si}m{b z`?93g5lgQOsOhQ6$uyhH`HoSyHzDebierLHBM~5w(-{U9*5ax=3&U4;9jTb=&T<}B zcV3x*c*bO22zrZ?q^uLmU`Y+}IfSOy^FSYrNQW$U>dOBq9MWgB=ob_Z6r`Qfw|`x?a74_jZQZL?R0 zv4D0Aq~+Qh$pf(xFcqw^{Ge=UD|rCRrp0z_j`JNwdZ|MRt}MKm7@R^L&`J34LZAIX z^YH{@R7SxNV-&ppi>2lj)m|hE>y5^Ov`2{3W*p4*e4pt!!6OmWIrt3Grep+MSozF5 z0Q1yA=Dk1$)Zfk>R%J!^tVU+^O;tu8+yYM5*Z0JQQlw`z3gw5$aH;Ms0)0+(FQbt! z6!pZM(yg{pjh@}2SEym^Zi382A~K4;sV#v_v)mW;JuwsM$Lc)?pQFKQ$W9? zDN){1nfX&(&5Dws_2FkT4^PsV%_ptk6j;&N?Bk)?x`qZs<1KHTiO@R^UpnJzeI zwdBiQ3I-@eT`mJ-+~rK=Wo5PtPpwPdY#P#0G6UWx%ZOE5(b_8i8hu6wnEk_FED<>| zyhtDZ+7krQ9t4s;lL2}}g&^40G67DOS{pN>WbKLfq98k^(e}iGKpd`ET08>($)D)`eu9P33qKh4P3XT10OIxgdcxIK;CY zQ|$4KGfXgn#Io7z%8jk}m%y%O)>&3Nu7n1f^yC`W0^WqBm+$CvRi@oK;^cZ!M|5JR zgi=4KHLDuo(6PB;@UsrM!;22$IpG|P2?RfJY$Kcy%pqf@;M5r{KQ`xL1g=5Vdi<;r zTvIb*BS8VB5mftl=5&B2u|rj638*6?aGKnDT9Bps!U7Q;dtN0nCIuk~vZ*$Cq7QhL zU7%5fz%y?woh8Z%TncEi7^1|WXA4Abx0Th9BAR4oAW){A(k>!~!}pbp7|s770?Ztp z%^_H;BKa9cp+59El!z{U27xH!QUq}xF98TmS3r>IoIgmboSJ4iS+h>{TJ20ifSChy zI@_E2h%=uqh)qtbCR5_m+z<;n4ec3kJ(ngwQ z&XCANDZWs<>r?NQwjkvhxQIv^Y|eg&zvW9@E+w3X6;1ZklCDV z&9;{8W+A1dYzxJG%1<)1xLu_D1UIvZHd~hcn0b;fg_Z2z{P5oRiu>ER2}@#mc_Bve z@I3lwCZrD>zlLi5;B-_EP_HI)s|T}s;5*uE2q+?nu!}h9M0ko_IS|cM(V4%MfgD4b zuwlcgtOLExe}Jb4vFaH+`bBQ_MKm{L!wWxtcvN`N>C(+KUt%U{{ ztNc_obSmSwARsLKBRaz1HLvOlUR@}#aydie<0v?g{XQT&3cMVS6ug_M;4wNPhloP) zeJ&5FCA`gM>!q;xodOPx8~E)L2gt7`Qh~%j__S?L9uFuvNVm-_l*Y7Yv5-B=|f*P=}FNz)b5042egh1T#`G4dcFg{U8B?k?_m< zL5rBF1h0wyak32<^?evItS4Jd+BGaXltZNu~R!+bfd)^%8W50=&wvV%s6TrSV$Pv<* zm0&c<Ri;(Uw?~e!bQMUel zse9>et#0fSO!s6Xaud&TCZB_?GQo7SFCDMf@BY+3A7?DP*>bZYZD-N_xOhrvw0_djlciLiKU1 zI^k^W{wPvG2a}Q3;yvmN=d?i?h&L&d8|H(?=a!jCC2l8RU$)7)Qg}t#UIv zPsb`v7`BxI4*3Jh4#*7Xau^)~2wm;~2ZWVk$TF}p8Hpa6J75Vj?bh4@yFHGrsGL|b z5>b}k=qb@1$Vh=8cVGfbQ5h6~L;5hd1MEJ-d%-Hi`jcsfdErAAwM0kY-b^Ez&6s`j z0hoRB3=SwAF`ht?m@{CXa54a*Ghq3|Fhc_I37>Z=Izd|pvT^tZPhLV}qA<aZhY-ljY4Q%`b<{P5<2A)FxEyPQ1%32~o`c!p)D(P@K>SjEo&Z<*a&DNY z;aNiTp|l)e-fP02gw~H>T9|Sy$?#d;S=~Lxoy-n@r(R4lRyacn6_frFX(y!J0ly2k zhpj9Uvnson5VV=_dAWy6^H1{fS;~dfB;*?LERmpsOG^`J(y}rzg76U zEoDyEyRzcTj@-<|n``C3*mqcPU>KW$0-;p{f5xO7#8y3FdMSzgj86pdAs~K2lMl8R zNlu6B(M6IIz$nQZBKNV zoVhp|2U=%0&Ry9U>M*=Dn1Dw{{6f%=3#YWUiJ$$-yuwmboTkGK1?STQ8OKQx7gF+I z{e(L@vG-4;<+I|dZ{`_zE$`ubQ{^2$eAvHh98;znku8aJf3cNyJ?{xCSh;-x$~|ch zNlL!9!N18n;sH!ZD{jS>*1o z9)@azwxL@4jh+ZilQ-L*u4;E`jiNR@B}dm`UfM@JQU+`eZdR^YC?BXj2u(%#i`)k% zDJwr)-^f><|FC=)ON!}BC5}C6S%h~*)G(Ci1TOybB0E`Yb(qP5T1N4G-r@?s$JJyg9&a`KT>(kRU57AM;CnljlyyW*=O$T47KZbWaVSAqNX-WZE$#Ot@lF!K@m&pD z3sv23SJfl=AhZ0LeoB|e_MNq=)Od3E?cjpd6qPm3BdvIDfiD%Z6m5WZB&SF0DUJMS zE-5jx&pth@uY?KqhgG0=BlbAo>YxCOP63sL=k-agGc8SIyEyZ1621V9eGE;3Bww|& z41NG%3Dd$@G1hrwC@HI0C*-DYJ@R`yUuV+qeoHrmM$QOing#ZRx14VfsRgp>>mB!+E4 zQi|gahmOf)+J`|Wcs_;i6lW(^7N0RWQJdWEm&*o$vAkAU!c$Y+MyO$$%X@$9u!5Aw zZAdhr=22UY5L|YAPaVRg)qzr|nr%St*58llM_I;L_T^rOsW?9LJjR~L$%ZJQh4{8Uf zEsSuCG2eM+jQV9V2UPJnVc7)Pm?_A6GX4n=VFp(z9w8Y!tKw0 zpjD^FGvbjTA_5gI?o<|IRz8IUJ=4|66I{N`FZTe(S$_E}PiLf25&B%3{f}zlQtMVc zeT+-X*pGzZ(snSLfMrqnR4;;vb$7x(XZNtg8mj(?evxU&OOgoN9L0HEff}2nAr&$_ z$3J>_d#+)rxCW6_=T;V{jy?R|0s1MNkfen-H29fplTY{Fpsx?4y>8 z75A-J2_>maWnjiCa@gX|7b_vZF6_}XFtv?_=8vKhH)XfwXUFwM`i$#B&!zf%U9GG4MJaBv z)(1_#Dd!De1|-JS8l9!eceRwKjd(}PKVIzARLhBdm7E}lZJ0|t(%*hX9*;fZ$ zw%8IXbWjWMLk$6bzZM%=u+4}a!>tEW2FT2lz+YNvF4@Yg6Y_|8n=#awsM;J8(;ToT zkJhGJd51kYT0e=#+9_N8xNB#_W~zuNT27MHbIHRwV>iQTQo#|> z$~VhrEPdyc`2}mX6>GPlLUvk=+TB!T_*4dJeQ`0G<1)}>MA@#W$VcakkAlf1#D@>4 z1(S;>fvX4_M2qWOO$oNU2Ie2hmzb9dA zow~@_TAQuK5~}w-5zP2n7+VOa7tPpO_hK4bYinbRbaG~F@sX6Ku|=rkc^O+cAJ5a+ zVo~S?7+dC_y&xN;GPZD7Rx*5|nF({cIsanXJr=Dwk==u^p74aNFKD2Zu~pmOuwHFd z*3Qc9w(TVQx|v$i9g>AF#MCn1{3T;*F(T7k>@t>yzzsvbZ9qt3UvYK1E#st7DZDgH zEgUt^<>jW9EFe~}cXoZu_FCdpYJz-^6 zPqe(k#_Prt;@>>MR2+9k!Ax0QHN1X!KJ0O@49^9kh zsbXhO)HZF&D$010XM`p#(Xy93%2V6{#VE(Usy+(>W5w$B0OFeJDc0;*6mhg}HCoDM z8Q<2fm(#k9rL?$=#w;+tOzU!0n~(71ba_lDEw&(7AYLII0<59TMDcQg+Aj1lG~VoM zs|_ToZgE%QrCHeQqakVw&NiHv9o6p{=VedmHxzqDxH>`$(-j1Vxri1BtUvH5-w z?)LI=Bat(MU~y1g4LrCi4tZ7cj|PIxxpNt zjapkf!!B3Ea$;B(8hm<~7FOOUdf2{t?btzrncVl4H&I#kPz$+|Meh^S88kd%f=(y35z? zS#Q&+7pBSf{&>2Y(IijzAw9ISYNY6XX!kO;`extcb3N+A-fInguQ#FXKCI}dmgS4M z2{l8FUE1HE%`&>VLVvEdT|NF#Q31AquqDoXx7qFiz6gXG z)%I87#ule}0=QvH;$|HHiBB}{n{2Vi)~vP5NAZE9@?{My?INn^*)|RY*x2o(eRsBL z4T;RiB*tvF&rzZS+6hE(Kv+CzyM1hPI!&@NQ{hcr3@@Mv-X;VQ4<}hG840DSO-3Ot ztFX5UtgF$Xx*Ge&0zuy!PX~CFxhFhF0$fik(_|{53ILQpCH~*Y4HKsA6aMLG#Wf-- zq?LGnxbfr(ds0hz8=WNcG2w>+FT$6R!V-Kr4%H|4(uM`_HMI(^gpj#HQZyVFmOS<4 z{mw}(@3*(vwg7@BDOQgy@6-FrG*a)TVW^baQ={Mo(T8aJxzdO4Jn-!XoNsD;sqZRK zAnq{f#|2-c)BR=f{vz%@@DsQZ)5NzOSkBD2GsLN@vFl)$2*)N;n+^l@@(98)B0=&g z2JCwdvbg8qnPpt&WYgp_C!0lB)W$|5v}27u2TZmLzI{stA6$zXS$sO?L%po$g726g zne=ty0@!_Rn7~z7B}Nz```In0pIMi6(lc9q75HgmG-NY$DTyTI{#bm*?q5CA;tnHF z1DnTKE1X4HN!}X=^|f%Mqt}|j!uAB$#GBI@9P5^(ZO^)xh2SkD1w>Uh|9^GHWHW;3O7tn9zDxrs*yG`de$ z$nyS&T9x)s*leXJ+FqKK-{r)xhpK#(%LF5fcd=Dy2O^iUjg(W*66?j|q`dD(nE6N4 zs316-U!VIXG7m37roe0>(&Z!HBdkmo+H%f46gihFOfP={NhDeW^ugad zw7dUGKd%vXD&YZY4}UH|q@CH~^7xN~BcUiAKEj5S3##%7JDZa7{yPs_*`%DaLp8NcUbOJ^hhq!FkBQ^7%VrgL?oic1fCus74Sq^bwFQd? z4>myZw}vxXu#7CNK1#W>;KzDxT+)I*;f5C-09qOC(V>X0`^MF zpTA!K3_oLE!A{E$9^`gXw-{Gx`Tu^F+Zo*w?nui=bbG39bKTydy6beydRST>*4y>E zm4xBJoqE8hF>HdWL(-qNrvV{c`y4SM&6}b4TdQd%Nt$g@PKWo}uSoO$Oi3ZQ_|`$CQ@Ufg-8f&1S6k7tg@#YgcQ zL=}LOjF&bO??3b*aXB6`YR#DIcBWe*0;wW1h$HvFziN$VKC`rjk;y83Ji4rg={ss5 zq<|{Iaz`kam1muoA;b%vgNujs3$B10*v{dqr~wwZc3`r`&HvoLFJBoqV!8%~YPMT- z$orwG#YnPH+bTnT_Lr8{&|>7tO@xIrSLtK~VaP`K`^VGfo?c zbZc@UQY*c1JEB3el9ewHxHhAFrcuj zbGc&28Xn?j^@2?qMk4?{*;0R^P@aGxKnBk{0 zm^igZ!wLh!v zKC8#*PUy=@oA?cNk5|U_9#-uOF|qx=bzrd*jpj#EKekE&4|cC>U#vDWaK4}YjA=jH zift;(5KO#PzBZpM6vDXLpg-&J4*8h^3_mBRGmj%-%l&Wy{hk0+wgrb=N>Y-5P@!9A zz>OU9(vEw7G|xKj)H+*bTkUw#Ae;B9N_&Eo*IE zMizExmn**-w7TDo%(TO%%>MALA`3^7B7sH+@?_?RVSlMmUCG0o<(CpVi;ouB%-z-<{MpP$P+XC4V{vJG^q++Iqu?i?NaC%Hnk+$m`7^5@YXN zYdGl$X)>22wu)nysl`Q+7IsVpA)nh}1;m#WjO;@{PDWv9P|s|y)0FF;9;Sm5Wo27! zD;@1y*7j5x!a7K;kX3Vyic4cdTdSes~nJB3qu2_TB$yXY8wWms>?F5JLA2+x&lIj+)K3LL8{#uTSPE7EM@bP$neX_N=_^qFODX*sc(*b1G4_ ze?i^&N1S09rIZTEmN{)J&I`&Mb*$rgMU|VzQX@&G>p;0P=kflEJGa`_FO|?;InK*Z z?OvGv22zrxtNqtb6`IS-?^~GeXG#38ZAKTQo(_K&9-d3IhgJeXSp#x2qt3aQS|pX) z$q&2wcd}E$k^y9n9IrMZzOVKu!3ySTYI8MKhd9Qze_;S?j4G)OY<9v%JJ?uzVJ}ZO z3PiqV6o!9vyyNJoK1r*mz#Ejso(YrSF+ zE1kn1lK!9?3#(J95;+id2LPHcROoEV4{xyE!1F1uwU0uoKsx+s$^t}`pdkYn9VQYy zuXhs=$q|Q9NuI>_V`9TcsBFWxnhra?IlB{(v3V&}82fyNN>II{0o7yxI}K+`mO{m$ zq!UA>%wDCwexjXwVK|oa*XB=Rx3t_%CSGk|z5-_hGg|N=N>bCE)gs0e=AYP6s8392 zRbWiON)WG@o@*7{s-oumn9{ra|0g$_Nu+=H+-!fIR*us@TyD1EJFBE@!*?#t#idwBdoRHv}gWUD7Q#IYwjst*$a3Mmj6NYV1C;d(v(J z5x=Q${gA9`h{3e{!+58?GG#^rGVM_`z$~L-TK<_zbScmJDPm70tepq-e2ryiv$cz~ z{ED8eQ%S+vQb`*muuAakBecl}^seQdhG??0k?}0vC;ekzm#KSKJ$3I|GIjIs1%?=2fswaP^ut<98jlVtHlG2xS~<5+F_I6-ta6bA2K?U=F^1t|EUrhSpqGEPDGGk$wZ1$ zY~a{8!@JO#;=1OCE^TQ|ANN9~EnYjDP4Dh01%tv1lOFK02@tj0((KK;wMUA%f0h68 z8_dOHWpSLB`E~WDWZ+^v&vdlL!k^}i>2x+boCNSC1^f1t8E;Z2UIBjii|M$3-+brh z@r-f@5P6!pd)$WX2{*p6wYyB(RD?ZXcoM=-%Fth@dJ+Mjud}V)ohG zzIOiHTZ@_b3vS(8tegK9{;r>&#U&Uu{^@K5d z4u3VdWiJmlga^mI^A_%Ue)rVNZ`rGty?rYE%o@GgpzCM5R%w>&zt8pb{C#`(?%k4L zD})@q!hFxh(`n9GDPK?TCK~T--bj=%8Qx7c-r0=tPP;%=uitoQbH%%<@NTN{&Sr^s zgYa(9cxUs&yXo+5y7A6thIebiyETn>VNN(+X_L`{EF1GNU3L~;YO_IF>%c?OjN+8V zvoRA!In#I-XJc)6x3=*v&IWmqZ8p|5-o@DfbMr#9Zj z**Gn{JFW5VM6-cu@Dk&V4Pg`;8l!MomX#zIH>{oBc;_>bm1l%^XEfgVOl0N8@NQ${ zozDadD=yb+ho#j<<-@e>S}nzJ&_b9YE-k;~Vl-Ge&y z-R?#SSGisys$At)9$|3X$vcZ=`ZxVnPL#2vM=}Dgy67Ctc#eB(q@BC*Fvo z(V*_z?}(%bK`b)89*T4yZL!DW1BTom{;+5p@>o>o>8eQ1T6mjG@gHzxZ?8>#WyJ-!K$++wWN^e(&iVUwV#_JICmVb}k z4&wd}Q*n zfUS8GwkC!=hNW2T{H&)JxK4W;#wilTd|O(1GVwnuM?HaC8{aMWi|{hqn2ch&JBSS1P4d;4S=ri8>leH-T;~c1R^qsLf}KsKB=*HX^fjHn(B;#SfJWKQt^ zGU>Ef))mv!6Eg`Oc49lchuf3>e2>bhh%%Q(MzH=};LZ2ns^2sO$BYGaJNHOAT>Ac*A5#)>&EUddC zM$4p+juk$NnPTm&cJ|V~1W*h_Rcsq`h^B3pF0~~h#Wsi?Ko@w!#jT=_uco(PGwq$v zM12G(LN^Ds|3?h%!Ynrr^+H4nwwu0n=^NF{+fr_!P zm~LNaFG4Yn?c+&@V%RR2SSuCd9IQe+-WmoB?66WX1T zDc0_N4SQuQc6EL+>3d}qkm6BeugoO7Yb;JR{2c3q9PaR4l9MZKmI0tkv?5m6%7U*k zC`uP&%(5aU6_lJLXhdLlJfmHnlRC1#VZ2tyLTO<$_?g_4olCmHgkr^v?@3@Sbgf@e zf1U3R#SskAVh+26B@K%v(g8={Y*%L>;F@l-Vo)CbSdhbyEnXV103?)12+}qojN8M5 zX^@tzUM4M3H5cR#V+P?aOCO5aatX3C9eyd*rbcFl(4B42_yq`3s!p3VZX>=(>|MJ< z1vQ&o^`ii&L7v&V%bNc1waJpr5`g1dGuY&-D(vvp5Bs52vseBJUP`Im(IDXw?OT0X zZp%mEkzdMFvKa$&{f}b#uCZ1CG#MO2=G(Ldp&b+iGMpjGpz6rCP?U#vZDe~(K1uwj zgoj9rOdlX3(npw7%!@lhK*AQ7L5tb?EulpYiMFkFypX6ah z8%aKz01|-(D1;n)He0hHmA-M+Owy|smAxt5Z!59fg`Ag^xRxpPcFmtDL#jTwY&Qiz-( z;|T?*;k${uM*xOOfi~EnAb*n9TZ$s=2+%Ub9bpF%|L$N=QhD!?euR4X5b>8hTtQ7(6;u#>hpD0=FrFWphqDn|1dYU zF*It5+w|bQ(iG70@V}t_7VkmY&Wc0i3qHMI>}(5<5U4Dh#Kr|-5*DvEmrF8OpS*7@ zLh$ipZ(NmZgOh6bj&a|(!d#}B%K{lt6;x0l;h>iB-(P^a1te1Dpy6}cIRVYDqkg3p9t`>}NZUzceOQ^40i3_wwf zq@#qN$tp|C%b=L{wk_(Jh=V-zEDq4R5sID<)W4KAcN1$G^%2JaxoyivG#PEU{((#Z zebGE>u|`g!i6K@drMI-JMuHqLwE>>-X))YZk|SbSZwhpn_+%vIqd(_#D6Ll>{$!-X zNCMSB&y4^WwZ+#UDX@rZN`n-dK$rnS{{f8@!I?U(YE3K*1BVGq-Dk*taHQ0fY@@1> zbT)%T$ZD*%RnLY@A@=eh@UR(%T42NJ@m%Z7pd&*xDO6CBD?b51E_24`f?Hi-QO9xA z7ivlqoAcf854N(bItO6n6?ra12I23Q)eHDu!z!_3UNy6J*N8BmyO6c z{v253E=FY^$}PV^N1vRJw9A<*I~*swN;#mk>|R0aaFIM{=(ZDw5|jf95PV?X2SP}5 z%~P9h+vHEVa4x2rVBy;sRyYWp3!K(elu^JVwh+WFZ)OdsvXVCsw4%J>V8}9p0KH4d z0Rv(Mawrc=(tejDPAia(nGo5$wpBx#Yqtd zg2AM8(6v>MX*bb&e1dtBe!k8GW0NF8sbzyO`g}>}Gx=g!0`kpDA7Pq+qfzD}C5jSA zdPUOtgNk(c%zQ)Y&Hq7zbTU=WhMWVDo{QQIvNeK{rJv#PfM!0n0a|gxrHt%H(NYQ4dLG~?S622TMn*r!_i(!z^Qw>O zD265MYLcwB<(vqtBN=`ZuGlH328g@Fy)+njzGcm~zW5RR zsQsguQ=6Y{cLDij(HmXh}>v+D=%!tB-u?^XqPWkdUx{H4MY{jW7k z+;+FbSU}qC+&D|DinN2DZqzx%O@oa@T5)<|Sd$3Pk8l1aOoF~dj$nc;pj?TGACmbq zM3eB-8}%Zqn8Y$;0zu^_0t-NZLro%5*l=F6U=@R!8wl3AN(rJ0P$jBF>HJuzLpB<* zmabRai~&f+*-w}MdrzQUjz5P{%Bja-oFR-REw=}XD2Qf!`~`?u;?j**#0ef8|HTij2vFn z^x990AE}9AIGi4v%O$zFj#3P$H9Ex!@>H^HPP${-($`2yjen}rcD3}uQvUMDkKI5H zbIH++6A#-4XXyHOGM0Gk3q(v__}0?o6qz+!mh053-l&pCTG-Olqe(5W`eP2UHA;=s z;f<(OD=DLhi+|S|ofeANS0Y=at!<4b?XXb6&BL;^A>fcKa=c+n{!C__>m3D2=nuqt zEd$QWl5kZqDK85wrzyKVa4=Av04WnNAs}p!c#g`JOp0}Zld-z#V{ON=T0(mtMLOK+ z&=1=7H6|+AxsmX8JTctK#IfH}xTr@6REDYveFxJ=7``wQ$Hj-UY_QVotk&$9ZuKW8 zdfeDk@Xs)Tx%82#bQm!!CG6f)?5$U9xSP0}#-Sa~eDUW1oy{dzXePrTl|OzixiTbS z(}$6GGNEHYOqbR9r~&w9;Z#N6Lgg`g@b^lFKyrI*Dk!Pbiz;BBAffe)w)kujPu`?S z&^LbcT`3k%%0szbYO(z=D3A@RrtAc3#~%jxz-){c%r;kjpc?h|yx?iBm* zfowSkUMGYiNS7Gt7J}tciz74N7=ha8x|bCZZ#m2^-$dYnKFeD0S=N*t4HVp5x3$^Y zI!kT6%IQH!qg3dZv2pncL)wv?IbgmX(xwM~59h+fZ1!MIC;T4D2QT*z9CXSbTHr%Z zQ>`f$psTn{&z>e(iraW|Nhqn_1iq0@*;IRTDQLf-t>HTUz*E-k;Ukw}X?fPJ@~n3$ z_9*=1H$X=N%lKp;37FF9tuPs}4KY4hZ^b!)Uh*9pU8)>275FI(pucS}GVwVz5)SMA z>1;sl5+Smt`MZbAux}5;g@_Ct)A|J+AlF*6&$ToLGQe`S+yDk%7~hT>Ai)*@GR%Te z!|WkpcFo|-sh4TmAUWp3zT!kT`>U_B@ErxDfegmA($xcZ`DAN?J_kD=?sAazIW~vU z-qpBSJgqMMMA!ij&k% zB~njcJmpdf{$VjEW)q3DeOMDZoyKqu6V`M$HPSe53x5{uvs zPF}BHNOw&ji>W5jL|UuyJ1AxXt%=NvnF?QC%}MlnwdM3_M`By$ovyt!Dd7_jMuvQs zuFVIIc#6Si=fx9=5`9qy`VXJ<)+QFj1D`Q5k_q9b5MzE6QjnWzq~TtLQHSzgR{^yX zR&*Dps#rNOW{}7glClW8$+fP&?Di!d&LzLyvM>CI6rk21Q~00{)`;n4R=0!F_Yd=a>VEGQm=N2)q(O=o%1pkZr9LUQ~Q)!@u6sb0yac zN>1VFiBVr0b1b%wl54w?(|4W(hx)t6`vzTe$!9eGs0Xc$!gzu{C`j1e47TA&52WV0 zsJXr(6;N8ExjVc$2yia>sVr(b`*L;YT%_9$n(ly{acKBX3@82a9mPnmC<4(oOb)Wx zrokwEk+3B351|%qY?gO9fyp~-Yz|01gg0nU{FSFCE7Ho6Df#e{bd z>wK_^*@z5iw#i)bA!D|=WWT!L_*7q%)Ee2$p(@?Ic68eEgeyw(W!cLtMG0waV@D1t zu(`q;R4=u7YM9yPf$%z5;7!;aYO*m00pvnqpL3E4h4(jQ59&S09=_1QrDV@sve)It zGi)n@Zuey4UZpIbNg17(fMS26jd8kfN||7ENy_9=zF?XyGn}>L%)?+j!~1rN(XKCo zaocn8@)R(rclGy__ib9{FX=m)&D|(ZU`UfE9W$G?aIdViRsCaL--POygAkJCOu^6_ z#uJxrIUnC*mJgNkIdW!-cq0=fD@iF-kO<&QCb#&!rM){Z*zAc78E-tnXrc?n*T5qw z|Ea`iBxScmi1p;!kQSH(k%oD~!@jdiZK9Ya_i-<`9EigNw%3|Op}jM1Y1NiP&m~H* zY(*GK{A)O;Lz}nX7o9MFk2?M(Vg=Z0_66QX=K2F^9tmC9BjAXD1E=hx)@JY|#W92i zLSVq0VIBnnR~}_2aHhy0QsIYHf0=SshUJx%>tP>(QU4dxCfR>&lq<=Z4Guwi$on?s z`T%`Pyqu(RRmSH^S9^1&Tt8DW!`Dc;e)-6UKHNQya*gkp~Sx(k8G^*P+NT8rxEmm|OGGI`yKWof3 zmmE+RUq9t)cEBBtA>UzK6vU4VSS;11_~B6yKl`ng+xGj}L*};q?W(^lmbH(vF>tgm z_K?Byve@R5_nY*JX8j4HSs3*z|655d*YPa8%_Z?Hl8aSf?J=-8RRTQk1s<11QvRz) zwAPtwHl@Q>)~dPdLBlp3ebI;I%{!1{rr7ffX{~L9v|8C<&>Ad@X|o5J`1OF*`)A1u)Q($V<$kqcAL_M?e>*&piB3tV{vbBD3WJ}xLPdc(i zr6r^Jo&m;v9o`B6?qL2xyiIO@h)Bff6G~iR8`R#K!5V!q++C6v9^UGzVTC!=WR0LD z;jJoya+2XKeAZQ3#Vf^1#6EvP0WK{17i{U7b@KuPT)<&+{uzs#yV-n`@s?R)?{3(M z4Ev-5Txr94bblSWE%$Hbh}H_BTQ?kQ^HUKbpL-NT*V=rEgt*M`ej>;yfHMSMwRikX}VA_w%H4U(vI3PYs8d zkE^lpYG3?pu{3EK*6rQ%>6uQDldslX%Hz;S?a-b5(WLclWaN(&&O&7c4fGTqUf5dX z;Y>FD5Jr6h80CaNla=~Br{b1KhHZQ_*c?&+k~NNLpL>qDnP2LTCr-{uln&~IGYmbx zhc{V|d$hrcqNo?bVlb-^>6&&`{*J{9#lpd8`NEim@hvRDDp*_qOz{OQEU{=|EMIBi zPjLl>ASBhyn`@1`!L^e!z9|!(I5?BgK?+% zq{@KDULt~q@^PT9UNCCzdf?3Kk7<=#<>_)ls99PmqBqHfg6~&Ze1w8g~6j` zYv`hDU-6W890!xBJPg$15TAM7OXIQlHjOApV`LVZRfE1AUC?`kH%t`^B_~D+SfH5P z*eaUQtHv~8VBYdTBL zkTH=4faX52x#!7TV)pVNH!fxut`2#4T%oHdNn{$jqW8)F#@(9$*i}{e-el~g4)1Za^|Alj%XsI*NxHQGF(6&3s0 ziZ*IgRP3e|{ZLR*gT~gj9cZWad!l*2|Jvu=duj-??{&Yxt#kI-)7opVz1G@mue~>5 z)rVqEX0P9Idz39Yr!}QRnmys#5UyR7(^R_@w0p3|K{#_6`C5NM;zMFArPS|uRFT2s zN_v-K=2Cq1SjWQd=7X7=s^;`zOzKXES8M%u@U;GbQdzfuTRME~#6;1raXV*1&~6l} z=8jJcfs}oH7|CnYxsu=)M=ht5z&x;=mx$OQE?Lg0K&Q+{$C|qpTLa8TD>_QU>qudZ zerh(XVXuJI*k&dhC!+>03-A!cu`E+x2p2u}cIC95yu&P-m5ovign-1XNQ14VKz;{2 z7yXwJ#FSM|ShwATes{f{T&sq%%yfXE&KgHppLLlbrhZg&E~XZTuF6|=<(4U15*IP{ z(NsPN_YWgH?heKL=cwMP>ttf#N-5`1GY6OeT%ZcO=Fn+w7xcwk@=2zETWiRg>-XZ5 zYkSi$X4V@P@8>ni1CG_k7*`NX7NXtWCL1Ej}tZsVIFUV}f~vS&)Dtt>TeHu$HKb4nX~q0IPsP^7I@ z@@;5w&XmPcQ*)UuPe@9x#{eHtoYtyzTK%mblrNV~tDhxW3;L2+U;5L)BG!YaSkHm?kHhTKs0aR=#Y{4K?x~kK zJEedp9X-g+2}-)7UecVWToNqON@4&rqLYPahHN^vZ%WFnk6fjjR)GFmIVw+-CJjt{!orBJW4cG0;o6vY>y01i3U94kD%yYT!>o`U>G3c}? zFKm{lS}RZxJ*#s_N0&4?dk*Q(!l$Zcjb$fwHZnDcd)QMUh58{7Ff^*z) z^nbkD#jbGY#6Oe-XB&@-|0W5plu;*$fAlezc%^Zd_~S`%flL8G{O%+;&4zva<|H`T zrp57Vl3tqUA;qA0hdrL)zjiI2|B9W_`?@U?_ge$VhINmf@#IzF_0FwFDYn z=v9u7lK)@%qk&)86LT0!U?r9W5Cf~4zks=vRe>**`!D1fbC4t{wdJ1*t07fRn&o-L zBL9#5m;Dr)=MYn3@TFD@5^O6RC_#T?0O0n7voXC`B%;dBB61${?wEa}O)`#-V|&E~ zt)Kv=;@}B{f}%q~v4H|u4^=3;>Lr&jz*Kk~w#R({B0;9U)Fccrm|+TUpY^$QEyaxM zoCI^ZdgtWTIc=fgD>>gT!B?T+6~_I`e#*h#N8`$_Vbe-Ys5!hGthtT*2@#fpJibv* z-zopOU)mFgZDIV58}@r{Hwzt~HYU%3YdYo0h@dDW%O=q1j;LV%sgOYmDOLdWY7wIY z=h&HV8wd1YH)`WX2H9ijX#~B{_c0y9BJ`!T8PSbKT|SFCA#Snfg(3fs{WrmU<`shH#Pq<0o|a%2b#!@7j6kMF%YqlRsJ5%wYU2zqXAFw5DF;1)Tz zRgZMyQRHXWzAHQZYo)=RR8+Ou83Y3im@wYvc+a`5MIN#R;n0p*jY?@jD7MH(mps-2orVvNqmM=OqJq2O(6LrNkTINeyD=afmz@U|e zpn~QK@r6Ayg2m0?j}wQn@y4I#hL{sQ`#1Tp|B`r_l*09(G?c{#6H$R3oKzuO3|t2H zy0baB546t227e)Y-qXq$3#!P1oVWugivK1JUcfO3`S|Y`I9khAX@X3EXIluQTu*a} zcVy<@s6giXgs(2KPh611hdgsf<|k`KnGID@Aa5zmgAhoCZqgn(*=QxQiX1#=jWvqJ zYR**UspN@EASlfve*YBeiCZ#xn65mfJk6OrXdO=}PirO*Gm@v2rzw+%IY}9+oB~y{ zpfquIj$BuaVbVCHC?-XSIpg6Z`;W+cJ1H<9uY(Fisrc{ju9ieYxs--DSEoTk8keOk z4aqrIG-TYMq;fP;D5tWU;X^s4Mma|Gjuur;VRShKE5{Z+3M$7|YNqXW6m5>q8a@c| z1~d}eqbM3rXe*@vbE~`=HJva;S%XVMjMWw$u4EwOh6buydtq1(u)_x7z+f7A4A+5? zyFy7C1`+4!pV;d%@m}HfDTu3m9S962#zPi8K{rFO7@x6i{_z$7`;kE8nA$3CTCdah~4pH{y>KZizANPC)0Z#r`v~?#)o4Q5=u_2asv^I%9J_* zK}g9^r{Tf`|Ia3HIG~Xu!huVEc#On3Ed{xO)En7tmJSQd<|f2~ ztmVl8R4^!3a@56w06kO3zM2$)ei5M(KuD&ZYO~g;#rjQ91RB0kSy+KJNl1a?a@B_g zCIV8jxJmS-B?VCiNQhanqZ)Elb#I08C{~&Ux{zk9+4E$gCKLz=HQs;EelKo|@8sc5 ztFq-CvkqGhr6Vs4*ybll7AJ~nfuyMn{BErePGToIB3T1>uIy$3;pqdy6>-b3cE>&^3iWw0P(-OpJwt+CC*eT{peUo!J1v#=!{2-! z?9A}WOKc7^s`po6YSD50e8s8~6YNRYh{sul%?EXgh*g2c(H-4V}&okui?m^(oDWK?%uho~jXP6I@NQ>NDSQ zQ{DLUWYbhQNG7^^n+HsheUpKZbAHl7#dM6G<^p3p!bq}ZKZwKLZ5i5IGs4*q_37`v zAjsFx9?PLYGhx*pbTGueqn6 zG=tUQ>Hu`CX&zzuUo#KpFz|U5$PnUqUQS@Hm*bJ}pm#~cijNH|HF9d3&NF{Ki{zm7l5sK zXwBRs^8z3?Hw&1YhO1G77S^A?3&kWKpRfzm<>L)HBC!+~hN9wtr3})P0VxrG1(uAJ zey31SBq{RS5{8zr6d$2u2p{vf4{*L0*7LYDIdNt;7YgXH#(xxeWzVD;oG?<%7sgr< z6n2UOio7*LQkH#DlIt3OPKworE0m*_tbJojh9{Osil58QM`|^L;C_h!j7@eGLDFs_s z5IYrFnwT_a)5I`d@tW#`lPrX)l-&ERxQx2kiYE3a!TfIw@~1<}?czYsDnmY=yo(7* z^Ex*>dLmi{3y(2j&+Rs8@U-fMO|@&5v4VqLX9#8+9#4+MC|^%3i(96TS}l~lLLTXw zO)%L)sfOE4#RSya*Jd_g+rcg<8NY8&Z%k2nT7i2~UIPqg3Wi!jPbclYN}V z!13#{1*HOOthPZ3b%jX?sLK-AWFku{5=|GH&XbXPPU#7f>4=Z~NaiA0)kVvTQVvX( zA!B>upa0~;KPVv9%QBwQ7=%;h4!?YnrFde!PHL}|=P~M?G-7w#rJ;g4zAc(9UxM=C zL@3h50p8Hi40}?so^qkTw?h9mNDrQ4X*!?EXe@l$3hJtyy2r>Wi&ZNKs=>=AZGHd~ zzzQJsO@LKhc~!Y4Igm${Xw=XXcZ?lpN+qyUA5T; zMyR>L)M}xA9;TavxU(vm1klIIW@^kQCkLfH;Wf0%k7q!$lqkJD-lE%TIBOL9aWj*N zlCFUB&2|X&a(5iAj^c-yM09r?4|gv8P5bw2?c=~_(s_}V8PJQQJaPd}c`nLKD_5RX zC#~tY{&vk9gc+$uB$*Voj=2bpWJ|BV9md*7iS^2*%-(I$I_ieB>f|*G#^jP~ax|I4 z;YSxHCpA7-$9(26WX_+@T(j1ixn>O*lWW#!$o2d7RVUq!S>|ZX{u@wYK$j(?GWsi; zrC^`DZ_UgxHPVIblI3=Z!y%~2Wqk?=YEDx=Ed-U7YdnJBQxhSm)jmHB6pe53Bm;8- z)N!TH1gMjr9SCYUEP|$3c~6CcxD@LdP|%UeH6B5@_DzJKqxJb&QP43eVH^b=rLPlG z5cU%x=!nB2=vbHaDNqnY{TU!=hH{NZkgV$y46)Bi zDPd%)a$5l$OEboVsTt$aOa>OuKBt}4axXV|iPv$=Ws{UW^2$omZa(1z1;-)tutb(WgWV}F zX`uolbzPUDN*OkzYfgxwwjpDoF6*?Rc;@bbQOS&FQs|ISziiDH} zR=nHmJd|sj%*tF~anFjSOO>A4@91K)$dYxC-T;UK1dvvtBeN3}fGMu7NWd9~Db?wq z!fKF06%56vW}u?GnY2sx=@{*#Hy`+1D(^IRe?SPcRRAE457a}nH-2nXXutAVO641= zhm`N8dWZ(ax7I@{<*qb@X~|iOX%-L@b^q7*%X_BsL0>i$QL%_R#?TFEdjw?{Z1W>j zKbbT*9#4h_Ss!F6IZfhN1{kxOxVu?ojtNjq$%2w3h1IUHqhDk{u?uSCRDHv}Nn7HZ zk^b>X@qQ@*x`*##84rP?ubN)f8wF=mK>$TMuLWMi?Y+{ebSxZ%B%f}X=vnr}!@Z&V z0h>Q4{pBM8Dx+J(l}lf;jvsl9ff#q^z?Y7baU4ORRV2o57QkdZGh40sO#ZVesll4i z`HbB1d1rSE;`_%N(o#R@B8qY(jH7&2-iQ50cC_?B(jdMAR$;Exsda87DYTJ;Mm{6( z?g7m8l|p==$Z#M`X*&dfU1p=R`s3ZF`47%PNaehIoe>7{FM-Ai3{fH|!TdE*Ip(lCjihfU$D2V^XP=@(8rP?vx`>j5- zzyu>unuXqEcrO8G=b)(#Rh-9+&VG0dTW0By&G

nC2j|1EpU9}*{|^O);}Fa+2h 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 new Promise((resolve) => setTimeout(()=>resolve(SAFE_BLOCK_INCLUSION_TIME), SAFE_BLOCK_INCLUSION_TIME)); -export const votingPeriod = () => new Promise((resolve) => setTimeout(()=>resolve(SAFE_VOTING_PERIOD_TIME), SAFE_VOTING_PERIOD_TIME)); +export const blockInclusion = () => new Promise((resolve) => setTimeout(() => resolve(SAFE_BLOCK_INCLUSION_TIME), SAFE_BLOCK_INCLUSION_TIME)); +export const votingPeriod = () => new Promise((resolve) => setTimeout(() => resolve(SAFE_VOTING_PERIOD_TIME), SAFE_VOTING_PERIOD_TIME)); diff --git a/integration-tests/src/helpers/mnemonics.ts b/integration-tests/src/helpers/mnemonics.ts index 639daea6..b352f382 100644 --- a/integration-tests/src/helpers/mnemonics.ts +++ b/integration-tests/src/helpers/mnemonics.ts @@ -39,9 +39,9 @@ export function getMnemonics() { let icaMnemonic = new MnemonicKey({ mnemonic: "unit question bulk desk slush answer share bird earth brave book wing special gorilla ozone release permit mercy luxury version advice impact unfair drama" }) - // let mnemonic = new MnemonicKey({ - // mnemonic: "year aim panel oyster sunny faint dress skin describe chair guilt possible venue pottery inflict mass debate poverty multiply pulse ability purse situate inmate" - // }) + let tokenFactoryMnemonic = new MnemonicKey({ + mnemonic: "year aim panel oyster sunny faint dress skin describe chair guilt possible venue pottery inflict mass debate poverty multiply pulse ability purse situate inmate" + }) return { val1, @@ -54,6 +54,7 @@ export function getMnemonics() { pobMnemonic1, genesisVesting, genesisVesting1, - icaMnemonic + icaMnemonic, + tokenFactoryMnemonic } } \ No newline at end of file diff --git a/integration-tests/src/modules/alliance.test.ts b/integration-tests/src/modules/alliance/alliance.test.ts similarity index 99% rename from integration-tests/src/modules/alliance.test.ts rename to integration-tests/src/modules/alliance/alliance.test.ts index 4ff40f31..d776951f 100644 --- a/integration-tests/src/modules/alliance.test.ts +++ b/integration-tests/src/modules/alliance/alliance.test.ts @@ -1,4 +1,4 @@ -import { getLCDClient, getMnemonics, blockInclusion, votingPeriod } from "../helpers"; +import { getLCDClient, getMnemonics, blockInclusion, votingPeriod } from "../../helpers"; import { Coin, MsgTransfer, MsgCreateAlliance, Coins, MsgVote, Fee, MsgAllianceDelegate, MsgClaimDelegationRewards, MsgAllianceUndelegate, MsgDeleteAlliance, MsgSubmitProposal } from "@terra-money/feather.js"; import { VoteOption } from "@terra-money/terra.proto/cosmos/gov/v1beta1/gov"; import { Height } from "@terra-money/feather.js/dist/core/ibc/core/client/Height"; diff --git a/integration-tests/src/modules/auth.test.ts b/integration-tests/src/modules/auth/auth.test.ts similarity index 98% rename from integration-tests/src/modules/auth.test.ts rename to integration-tests/src/modules/auth/auth.test.ts index 45f77625..5f7139a3 100644 --- a/integration-tests/src/modules/auth.test.ts +++ b/integration-tests/src/modules/auth/auth.test.ts @@ -1,4 +1,4 @@ -import { getMnemonics, getLCDClient, blockInclusion } from "../helpers"; +import { getMnemonics, getLCDClient, blockInclusion } from "../../helpers"; import { ContinuousVestingAccount, Coins, MnemonicKey, MsgCreateVestingAccount, Coin } from "@terra-money/feather.js"; import moment from "moment"; diff --git a/integration-tests/src/modules/feeshare.test.ts b/integration-tests/src/modules/feeshare/feeshare.test.ts similarity index 95% rename from integration-tests/src/modules/feeshare.test.ts rename to integration-tests/src/modules/feeshare/feeshare.test.ts index fa6e62c7..2d5cf3e6 100644 --- a/integration-tests/src/modules/feeshare.test.ts +++ b/integration-tests/src/modules/feeshare/feeshare.test.ts @@ -1,4 +1,4 @@ -import { getMnemonics, blockInclusion, getLCDClient } from "../helpers"; +import { getMnemonics, blockInclusion, getLCDClient } from "../../helpers"; import { Coins, Fee, MnemonicKey, MsgExecuteContract, MsgInstantiateContract, MsgRegisterFeeShare, MsgStoreCode } from "@terra-money/feather.js"; import fs from "fs"; import path from 'path'; @@ -20,7 +20,7 @@ describe("Feeshare Module (https://github.com/terra-money/core/tree/release/v2.6 let tx = await wallet.createAndSignTx({ msgs: [new MsgStoreCode( feeshareAccountAddress, - fs.readFileSync(path.join(__dirname, "/../contracts/reflect.wasm")).toString("base64"), + fs.readFileSync(path.join(__dirname, "/../../contracts/reflect.wasm")).toString("base64"), )], chainID: "test-1", }); @@ -47,7 +47,7 @@ describe("Feeshare Module (https://github.com/terra-money/core/tree/release/v2.6 result = await LCD.chain1.tx.broadcastSync(tx, "test-1"); await blockInclusion(); txResult = await LCD.chain1.tx.txInfo(result.txhash, "test-1") as any; - contractAddress = txResult.logs[0].eventsByType.instantiate._contract_address[0]; + contractAddress = txResult.logs[0].events[4].attributes[0].value; expect(contractAddress).toBeDefined(); } catch (e) { @@ -146,8 +146,6 @@ describe("Feeshare Module (https://github.com/terra-money/core/tree/release/v2.6 // Check the tx logs have the expected events txResult = await LCD.chain1.tx.txInfo(result.txhash, "test-1") as any; - console.log(result.txhash) - console.log(JSON.stringify(txResult.logs)) expect(txResult.logs[0].events) .toMatchObject([{ "type": "message", diff --git a/integration-tests/src/modules/gov.test.ts b/integration-tests/src/modules/gov/gov.test.ts similarity index 99% rename from integration-tests/src/modules/gov.test.ts rename to integration-tests/src/modules/gov/gov.test.ts index 79b2a808..346a7c5a 100644 --- a/integration-tests/src/modules/gov.test.ts +++ b/integration-tests/src/modules/gov/gov.test.ts @@ -1,4 +1,4 @@ -import { getLCDClient, blockInclusion, votingPeriod, getMnemonics } from "../helpers"; +import { getLCDClient, blockInclusion, votingPeriod, getMnemonics } from "../../helpers"; import { Coins, MsgVote, Fee, MsgSubmitProposal, Proposal, Int } from "@terra-money/feather.js"; import { ProposalStatus, VoteOption } from "@terra-money/terra.proto/cosmos/gov/v1beta1/gov"; diff --git a/integration-tests/src/modules/icav1.test.ts b/integration-tests/src/modules/ica/icav1.test.ts similarity index 99% rename from integration-tests/src/modules/icav1.test.ts rename to integration-tests/src/modules/ica/icav1.test.ts index 8e9d5ffd..e7b657c5 100644 --- a/integration-tests/src/modules/icav1.test.ts +++ b/integration-tests/src/modules/ica/icav1.test.ts @@ -1,5 +1,5 @@ import { AccAddress, Coin, MsgTransfer, MsgSend, Coins } from "@terra-money/feather.js"; -import { blockInclusion, getLCDClient, getMnemonics } from "../helpers"; +import { blockInclusion, getLCDClient, getMnemonics } from "../../helpers"; import { MsgRegisterInterchainAccount, MsgSendTx } from "@terra-money/feather.js/dist/core/ica/controller/v1/msgs"; import { Height } from "@terra-money/feather.js/dist/core/ibc/core/client/Height"; import Long from "long"; diff --git a/integration-tests/src/modules/icqv1.test.ts b/integration-tests/src/modules/icq/icqv1.test.ts similarity index 99% rename from integration-tests/src/modules/icqv1.test.ts rename to integration-tests/src/modules/icq/icqv1.test.ts index 82456358..ce8f9d3b 100644 --- a/integration-tests/src/modules/icqv1.test.ts +++ b/integration-tests/src/modules/icq/icqv1.test.ts @@ -1,4 +1,4 @@ -import { getLCDClient } from "../helpers"; +import { getLCDClient } from "../../helpers"; describe("ICQ Module (https://github.com/cosmos/ibc-apps/tree/main/modules/async-icq)", () => { // Prepare environment clients, accounts and wallets diff --git a/integration-tests/src/modules/pob.test.ts b/integration-tests/src/modules/pob/pob.test.ts similarity index 98% rename from integration-tests/src/modules/pob.test.ts rename to integration-tests/src/modules/pob/pob.test.ts index 4e61573d..020de85b 100644 --- a/integration-tests/src/modules/pob.test.ts +++ b/integration-tests/src/modules/pob/pob.test.ts @@ -1,5 +1,5 @@ import { Coins, Fee, MsgSend } from "@terra-money/feather.js"; -import { getMnemonics, getLCDClient, blockInclusion } from "../helpers"; +import { getMnemonics, getLCDClient, blockInclusion } from "../../helpers"; import { MsgAuctionBid } from "@terra-money/feather.js/dist/core/pob/MsgAuctionBid"; describe("Proposer Builder Module (https://github.com/skip-mev/pob) ", () => { diff --git a/integration-tests/src/modules/tokenfactory/tokenfactory.test.ts b/integration-tests/src/modules/tokenfactory/tokenfactory.test.ts new file mode 100644 index 00000000..0abdebcc --- /dev/null +++ b/integration-tests/src/modules/tokenfactory/tokenfactory.test.ts @@ -0,0 +1,81 @@ +import { Coins, MsgInstantiateContract, MsgStoreCode } from "@terra-money/feather.js"; +import { getMnemonics, getLCDClient, blockInclusion } from "../../helpers"; +import fs from "fs"; +import path from 'path'; + +describe("Feeshare Module (https://github.com/terra-money/core/tree/release/v2.7/x/feeshare) ", () => { + // Prepare environment clients, accounts and wallets + const LCD = getLCDClient(); + const accounts = getMnemonics(); + const wallet = LCD.chain1.wallet(accounts.tokenFactoryMnemonic); + const tokenFactoryWalletAddr = accounts.tokenFactoryMnemonic.accAddress("terra"); + let contractAddress: string; + + // Reat the reflect contract, store on chain, + // instantiate to be used in the following tests + // and finally save the contract address. + beforeAll(async () => { + let tx = await wallet.createAndSignTx({ + msgs: [new MsgStoreCode( + tokenFactoryWalletAddr, + fs.readFileSync(path.join(__dirname, "/../../contracts/no100.wasm")).toString("base64"), + )], + chainID: "test-1", + }); + + let result = await LCD.chain1.tx.broadcastSync(tx, "test-1"); + await blockInclusion(); + let txResult = await LCD.chain1.tx.txInfo(result.txhash, "test-1") as any; + let codeId = Number(txResult.logs[0].events[1].attributes[1].value); + expect(codeId).toBeDefined(); + + const msgInstantiateContract = new MsgInstantiateContract( + tokenFactoryWalletAddr, + tokenFactoryWalletAddr, + codeId, + {}, + Coins.fromString("1uluna"), + "no100 contract " + Math.random(), + ); + + tx = await wallet.createAndSignTx({ + msgs: [msgInstantiateContract], + chainID: "test-1", + }); + result = await LCD.chain1.tx.broadcastSync(tx, "test-1"); + await blockInclusion(); + txResult = await LCD.chain1.tx.txInfo(result.txhash, "test-1") as any; + contractAddress = txResult.logs[0].events[4].attributes[0].value; + expect(contractAddress).toBeDefined(); + }); + + test('Must contain the correct module params', async () => { + const moduleParams = await LCD.chain1.feeshare.params("test-1"); + + expect(moduleParams) + .toMatchObject({ + "params": { + "allowed_denoms": [], + "developer_shares": "0.500000000000000000", + "enable_fee_share": true, + }, + }); + }); + + test('Must query all endpoints before creating a denom', async () => { + // // Register feeshare + // let tx = await wallet.createAndSignTx({ + // msgs: [new MsgCreateDenom( + // contractAddress, + // subdenom, + // )], + // chainID: "test-1", + // }); + // let result = await LCD.chain1.tx.broadcastSync(tx, "test-1"); + // await blockInclusion(); + // let txResult = await LCD.chain1.tx.txInfo(result.txhash, "test-1") as any; + // console.log(txResult.logs) + // console.log(randomAccountAddress) + // expect(txResult.logs).toBeDefined(); + }); +}); \ No newline at end of file From 445f83f1a09bd9e159d2af5aec30d2202aa449c6 Mon Sep 17 00:00:00 2001 From: emidev98 Date: Mon, 13 Nov 2023 16:50:43 +0200 Subject: [PATCH 10/13] fix: lint --- Makefile | 5 ++++- x/feeshare/post/post.go | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 98a69f38..85d7ce84 100644 --- a/Makefile +++ b/Makefile @@ -329,6 +329,9 @@ lint: lint-fix: golangci-lint run --fix --out-format=tab --issues-exit-code=0 +lint-docker: + docker run --rm -v $(PWD):/app -w /app golangci/golangci-lint:latest golangci-lint run --timeout 10m + format-tools: go install mvdan.cc/gofumpt@latest go install github.com/client9/misspell/cmd/misspell@latest @@ -340,4 +343,4 @@ format: format-tools find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "*statik*" -not -name '*.pb.go' | xargs misspell -w find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "*statik*" -not -name '*.pb.go' | xargs goimports -w -local github.com/cosmos/cosmos-sdk -.PHONY: lint format-tools format \ No newline at end of file +.PHONY: lint lint-fix lint-docker format-tools format \ No newline at end of file diff --git a/x/feeshare/post/post.go b/x/feeshare/post/post.go index 0a343c95..346b1bea 100644 --- a/x/feeshare/post/post.go +++ b/x/feeshare/post/post.go @@ -92,12 +92,15 @@ func (fsd FeeSharePayoutDecorator) FeeSharePayout(ctx sdk.Context, txFees sdk.Co if err != nil { return err } - ctx.EventManager().EmitTypedEvent( + err := ctx.EventManager().EmitTypedEvent( &feeshare.FeePayoutEvent{ WithdrawAddress: withdrawerAddrs.String(), FeesPaid: feeToBePaid, }, ) + if err != nil { + return err + } } return err From 69b433944c16ecc835ccac30f353b82bbe63e0bd Mon Sep 17 00:00:00 2001 From: emidev98 Date: Tue, 14 Nov 2023 15:27:09 +0200 Subject: [PATCH 11/13] feat: extend tokenfactory tests --- integration-tests/package-lock.json | 14 +- integration-tests/package.json | 4 +- .../src/modules/feeshare/feeshare.test.ts | 2 +- .../modules/tokenfactory/tokenfactory.test.ts | 525 +++++++++++++++++- scripts/tests/tokenfactory/tokenfactory.sh | 99 ---- x/tokenfactory/keeper/grpc_query.go | 4 + 6 files changed, 511 insertions(+), 137 deletions(-) delete mode 100755 scripts/tests/tokenfactory/tokenfactory.sh diff --git a/integration-tests/package-lock.json b/integration-tests/package-lock.json index 6a45d2ee..31a99be5 100644 --- a/integration-tests/package-lock.json +++ b/integration-tests/package-lock.json @@ -9,7 +9,7 @@ "version": "v2.7.0", "license": "MIT", "dependencies": { - "@terra-money/feather.js": "^2.0.0-beta.8", + "@terra-money/feather.js": "^2.0.0-beta.12", "@terra-money/terra.proto": "^4.0.1", "moment": "^2.29.4" }, @@ -1803,9 +1803,9 @@ } }, "node_modules/@terra-money/feather.js": { - "version": "2.0.0-beta.8", - "resolved": "https://registry.npmjs.org/@terra-money/feather.js/-/feather.js-2.0.0-beta.8.tgz", - "integrity": "sha512-lIVCPTf/YXDigujVPHnjsPDg0Gnjmo4y5Z/bGVSOj4mqHcPOsKjzAeu1ZKZyRqVVTU6/y4EJhroOxoTcSbPwTg==", + "version": "2.0.0-beta.12", + "resolved": "https://registry.npmjs.org/@terra-money/feather.js/-/feather.js-2.0.0-beta.12.tgz", + "integrity": "sha512-qxUQJtHOp3Ybpr5T5s1XXYiADZbH6g0HdLEhkPkJtBtjxxKN+UeLVJ+4SmkxglA7sITCuSoZebBrv8yukHsmJw==", "dependencies": { "@ethersproject/bytes": "^5.7.0", "@terra-money/legacy.proto": "npm:@terra-money/terra.proto@^0.1.7", @@ -7008,9 +7008,9 @@ } }, "@terra-money/feather.js": { - "version": "2.0.0-beta.8", - "resolved": "https://registry.npmjs.org/@terra-money/feather.js/-/feather.js-2.0.0-beta.8.tgz", - "integrity": "sha512-lIVCPTf/YXDigujVPHnjsPDg0Gnjmo4y5Z/bGVSOj4mqHcPOsKjzAeu1ZKZyRqVVTU6/y4EJhroOxoTcSbPwTg==", + "version": "2.0.0-beta.12", + "resolved": "https://registry.npmjs.org/@terra-money/feather.js/-/feather.js-2.0.0-beta.12.tgz", + "integrity": "sha512-qxUQJtHOp3Ybpr5T5s1XXYiADZbH6g0HdLEhkPkJtBtjxxKN+UeLVJ+4SmkxglA7sITCuSoZebBrv8yukHsmJw==", "requires": { "@ethersproject/bytes": "^5.7.0", "@terra-money/legacy.proto": "npm:@terra-money/terra.proto@^0.1.7", diff --git a/integration-tests/package.json b/integration-tests/package.json index d1197f89..c0085c73 100644 --- a/integration-tests/package.json +++ b/integration-tests/package.json @@ -4,7 +4,7 @@ "description": "Integration tests for Core using feather.js", "main": "index.ts", "scripts": { - "test": "jest" + "test": "jest --maxConcurrency=4" }, "repository": { "type": "git", @@ -30,7 +30,7 @@ "typescript": "^5.2.2" }, "dependencies": { - "@terra-money/feather.js": "^2.0.0-beta.8", + "@terra-money/feather.js": "^2.0.0-beta.12", "@terra-money/terra.proto": "^4.0.1", "moment": "^2.29.4" } diff --git a/integration-tests/src/modules/feeshare/feeshare.test.ts b/integration-tests/src/modules/feeshare/feeshare.test.ts index 2d5cf3e6..4dacfe5d 100644 --- a/integration-tests/src/modules/feeshare/feeshare.test.ts +++ b/integration-tests/src/modules/feeshare/feeshare.test.ts @@ -12,7 +12,7 @@ describe("Feeshare Module (https://github.com/terra-money/core/tree/release/v2.6 const randomAccountAddress = new MnemonicKey().accAddress("terra"); let contractAddress: string; - // Reat the reflect contract, store on chain, + // Read the reflect contract, store on chain, // instantiate to be used in the following tests // and finally save the contract address. beforeAll(async () => { diff --git a/integration-tests/src/modules/tokenfactory/tokenfactory.test.ts b/integration-tests/src/modules/tokenfactory/tokenfactory.test.ts index 0abdebcc..9de2e9bb 100644 --- a/integration-tests/src/modules/tokenfactory/tokenfactory.test.ts +++ b/integration-tests/src/modules/tokenfactory/tokenfactory.test.ts @@ -1,4 +1,4 @@ -import { Coins, MsgInstantiateContract, MsgStoreCode } from "@terra-money/feather.js"; +import { Coin, Coins, Fee, MnemonicKey, MsgBurn, MsgChangeAdmin, MsgCreateDenom, MsgInstantiateContract, MsgMint, MsgStoreCode, MsgSetBeforeSendHook, MsgSend } from "@terra-money/feather.js"; import { getMnemonics, getLCDClient, blockInclusion } from "../../helpers"; import fs from "fs"; import path from 'path'; @@ -9,9 +9,12 @@ describe("Feeshare Module (https://github.com/terra-money/core/tree/release/v2.7 const accounts = getMnemonics(); const wallet = LCD.chain1.wallet(accounts.tokenFactoryMnemonic); const tokenFactoryWalletAddr = accounts.tokenFactoryMnemonic.accAddress("terra"); + const randomAccountAddr = new MnemonicKey().accAddress("terra"); let contractAddress: string; + let subdenom = Math.random().toString(36).substring(7); + let factoryDenom: string | undefined = undefined - // Reat the reflect contract, store on chain, + // Read the no100 contract, store on chain, // instantiate to be used in the following tests // and finally save the contract address. beforeAll(async () => { @@ -47,35 +50,501 @@ describe("Feeshare Module (https://github.com/terra-money/core/tree/release/v2.7 txResult = await LCD.chain1.tx.txInfo(result.txhash, "test-1") as any; contractAddress = txResult.logs[0].events[4].attributes[0].value; expect(contractAddress).toBeDefined(); - }); + }) - test('Must contain the correct module params', async () => { - const moduleParams = await LCD.chain1.feeshare.params("test-1"); + // Validate the token factory having the correct params + test('Must have the correct module params', async () => { + const moduleParams = await LCD.chain1.tokenfactory.params("test-1"); expect(moduleParams) - .toMatchObject({ + .toStrictEqual({ "params": { - "allowed_denoms": [], - "developer_shares": "0.500000000000000000", - "enable_fee_share": true, - }, + "denom_creation_fee": [{ + "amount": "10000000", + "denom": "uluna" + }], + "denom_creation_gas_consume": "1000000" + } }); - }); - - test('Must query all endpoints before creating a denom', async () => { - // // Register feeshare - // let tx = await wallet.createAndSignTx({ - // msgs: [new MsgCreateDenom( - // contractAddress, - // subdenom, - // )], - // chainID: "test-1", - // }); - // let result = await LCD.chain1.tx.broadcastSync(tx, "test-1"); - // await blockInclusion(); - // let txResult = await LCD.chain1.tx.txInfo(result.txhash, "test-1") as any; - // console.log(txResult.logs) - // console.log(randomAccountAddress) - // expect(txResult.logs).toBeDefined(); - }); + }) + + // Create a denom using token factory, + // store the factoryDenom and read the + // transaction result logs to assert + // the logs are correctly formatted. + test('Must create a denom', async () => { + let tx = await wallet.createAndSignTx({ + msgs: [ + new MsgCreateDenom( + tokenFactoryWalletAddr, + subdenom, + ), + ], + chainID: "test-1", + }); + let result = await LCD.chain1.tx.broadcastSync(tx, "test-1"); + await blockInclusion(); + let txResult = await LCD.chain1.tx.txInfo(result.txhash, "test-1") as any; + factoryDenom = txResult.logs[0].eventsByType.create_denom.new_token_denom[0] as string + expect(txResult.logs[0].events).toStrictEqual([{ + "type": "message", + "attributes": [{ + "key": "action", + "value": "/osmosis.tokenfactory.v1beta1.MsgCreateDenom" + }, { + "key": "sender", + "value": tokenFactoryWalletAddr + }, { + "key": "module", + "value": "tokenfactory" + }] + }, { + "type": "coin_spent", + "attributes": [{ + "key": "spender", + "value": tokenFactoryWalletAddr + }, { + "key": "amount", + "value": "10000000uluna" + }] + }, { + "type": "coin_received", + "attributes": [{ + "key": "receiver", + "value": "terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl" + }, { + "key": "amount", + "value": "10000000uluna" + }] + }, { + "type": "transfer", + "attributes": [{ + "key": "recipient", + "value": "terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl" + }, { + "key": "sender", + "value": tokenFactoryWalletAddr + }, { + "key": "amount", + "value": "10000000uluna" + }] + }, { + "type": "message", + "attributes": [{ + "key": "sender", + "value": tokenFactoryWalletAddr + }] + }, { + "type": "create_denom", + "attributes": [{ + "key": "creator", + "value": tokenFactoryWalletAddr + }, { + "key": "new_token_denom", + "value": factoryDenom + }] + }]); + }) + + // Mint tokens to the minter address + // and assert the logs are correctly formatted. + describe("After creating the token", () => { + test('Must mint some tokens', async () => { + let tx = await wallet.createAndSignTx({ + msgs: [ + new MsgMint( + tokenFactoryWalletAddr, + Coin.fromString("1000000000" + factoryDenom) + ), + ], + chainID: "test-1", + }); + let result = await LCD.chain1.tx.broadcastSync(tx, "test-1"); + await blockInclusion(); + let txResult = await LCD.chain1.tx.txInfo(result.txhash, "test-1") as any; + expect(txResult.logs[0].events).toStrictEqual([{ + "type": "message", + "attributes": [{ + "key": "action", + "value": "/osmosis.tokenfactory.v1beta1.MsgMint" + }, { + "key": "sender", + "value": tokenFactoryWalletAddr + }, { + "key": "module", + "value": "tokenfactory" + }] + }, + { + "type": "coin_received", + "attributes": [{ + "key": "receiver", + "value": "terra19ejy8n9qsectrf4semdp9cpknflld0j6my8d0p" + }, { + "key": "amount", + "value": "1000000000" + factoryDenom + }] + }, + { + "type": "coinbase", + "attributes": [{ + "key": "minter", + "value": "terra19ejy8n9qsectrf4semdp9cpknflld0j6my8d0p" + }, { + "key": "amount", + "value": "1000000000" + factoryDenom + }] + }, + { + "type": "coin_spent", + "attributes": [{ + "key": "spender", + "value": "terra19ejy8n9qsectrf4semdp9cpknflld0j6my8d0p" + }, { + "key": "amount", + "value": "1000000000" + factoryDenom + }] + }, + { + "type": "coin_received", + "attributes": [{ + "key": "receiver", + "value": tokenFactoryWalletAddr + }, { + "key": "amount", + "value": "1000000000" + factoryDenom + }] + }, + { + "type": "transfer", + "attributes": [{ + "key": "recipient", + "value": tokenFactoryWalletAddr + }, { + "key": "sender", + "value": "terra19ejy8n9qsectrf4semdp9cpknflld0j6my8d0p" + }, { + "key": "amount", + "value": "1000000000" + factoryDenom + }] + }, + { + "type": "message", + "attributes": [{ + "key": "sender", + "value": "terra19ejy8n9qsectrf4semdp9cpknflld0j6my8d0p" + }] + }, + { + "type": "tf_mint", + "attributes": [{ + "key": "mint_to_address", + "value": tokenFactoryWalletAddr + }, { + "key": "amount", + "value": "1000000000" + factoryDenom + }] + }]); + }); + }) + + // Burn some tokens from the minter account + // and asser the logs are correctly formatted. + describe("After minting the tokens", () => { + test('Must burn some tokens', async () => { + let tx = await wallet.createAndSignTx({ + msgs: [ + new MsgBurn( + tokenFactoryWalletAddr, + Coin.fromString("500000000" + factoryDenom) + ), + ], + chainID: "test-1", + fee: new Fee(100_000, new Coins({ uluna: 100_000 })), + }); + let result = await LCD.chain1.tx.broadcastSync(tx, "test-1"); + await blockInclusion(); + let txResult = await LCD.chain1.tx.txInfo(result.txhash, "test-1") as any; + expect(txResult.logs[0].events).toStrictEqual([{ + "type": "message", + "attributes": [{ + "key": "action", + "value": "/osmosis.tokenfactory.v1beta1.MsgBurn" + }, { + "key": "sender", + "value": tokenFactoryWalletAddr + }, { + "key": "module", + "value": "tokenfactory" + }] + }, { + "type": "coin_spent", + "attributes": [{ + "key": "spender", + "value": tokenFactoryWalletAddr + }, { + "key": "amount", + "value": "500000000" + factoryDenom + }] + }, { + "type": "coin_received", + "attributes": [{ + "key": "receiver", + "value": "terra19ejy8n9qsectrf4semdp9cpknflld0j6my8d0p" + }, { + "key": "amount", + "value": "500000000" + factoryDenom + }] + }, { + "type": "transfer", + "attributes": [{ + "key": "recipient", + "value": "terra19ejy8n9qsectrf4semdp9cpknflld0j6my8d0p" + }, { + "key": "sender", + "value": tokenFactoryWalletAddr + }, { + "key": "amount", + "value": "500000000" + factoryDenom + }] + }, { + "type": "message", + "attributes": [{ + "key": "sender", + "value": tokenFactoryWalletAddr + } + ] + }, { + "type": "coin_spent", + "attributes": [{ + "key": "spender", + "value": "terra19ejy8n9qsectrf4semdp9cpknflld0j6my8d0p" + }, { + "key": "amount", + "value": "500000000" + factoryDenom + }] + }, { + "type": "burn", + "attributes": [{ + "key": "burner", + "value": "terra19ejy8n9qsectrf4semdp9cpknflld0j6my8d0p" + }, { + "key": "amount", + "value": "500000000" + factoryDenom + }] + }, { + "type": "tf_burn", + "attributes": [{ + "key": "burn_from_address", + "value": tokenFactoryWalletAddr + }, { + "key": "amount", + "value": "500000000" + factoryDenom + }] + }]) + }); + }) + + + describe("Use before send hooks", () => { + test("Must register the hooks to the no100 contract", async () => { + let tx = await wallet.createAndSignTx({ + msgs: [ + new MsgSetBeforeSendHook( + tokenFactoryWalletAddr, + factoryDenom as string, + contractAddress, + ), + ], + fee: new Fee(100_000, new Coins({ uluna: 100_000 })), + chainID: "test-1", + }); + let result = await LCD.chain1.tx.broadcastSync(tx, "test-1"); + await blockInclusion(); + let txResult = await LCD.chain1.tx.txInfo(result.txhash, "test-1") as any; + expect(txResult.logs[0].events).toStrictEqual([{ + "type": "message", + "attributes": [{ + "key": "action", + "value": "/osmosis.tokenfactory.v1beta1.MsgSetBeforeSendHook" + }, { + "key": "sender", + "value": tokenFactoryWalletAddr + }, { + "key": "module", + "value": "tokenfactory" + }] + }, { + "type": "set_before_send_hook", + "attributes": [{ + "key": "denom", + "value": factoryDenom + }, { + "key": "before_send_hook_address", + "value": contractAddress + }] + }]); + }); + + + // This test proves that the wasm contract + // is being executed on the sudo before send + // hook, one test allows transaction and the + // other one blocks the transaction. + describe("Must send tokens and be intercepted by beforesendhooks", () => { + test("1 token successfuly", async () => { + let tx = await wallet.createAndSignTx({ + msgs: [ + new MsgSend( + tokenFactoryWalletAddr, + randomAccountAddr, + Coins.fromString("1" + factoryDenom), + ), + ], + chainID: "test-1", + }); + let result = await LCD.chain1.tx.broadcastSync(tx, "test-1"); + await blockInclusion(); + let txResult = await LCD.chain1.tx.txInfo(result.txhash, "test-1") as any; + expect(txResult.logs[0].events) + .toStrictEqual([{ + "type": "message", + "attributes": [{ + "key": "action", + "value": "/cosmos.bank.v1beta1.MsgSend" + }, { + "key": "sender", + "value": tokenFactoryWalletAddr + }, { + "key": "module", + "value": "bank" + }] + }, { + "type": "coin_spent", + "attributes": [{ + "key": "spender", + "value": tokenFactoryWalletAddr + }, { + "key": "amount", + "value": "1" + factoryDenom + }] + }, { + "type": "coin_received", + "attributes": [{ + "key": "receiver", + "value": randomAccountAddr + }, { + "key": "amount", + "value": "1" + factoryDenom + }] + }, { + "type": "transfer", + "attributes": [{ + "key": "recipient", + "value": randomAccountAddr + }, { + "key": "sender", + "value": tokenFactoryWalletAddr + }, { + "key": "amount", + "value": "1" + factoryDenom + }] + }, { + "type": "message", + "attributes": [{ + "key": "sender", + "value": tokenFactoryWalletAddr + }] + }]); + }); + + test("100 token blocked by the smart contract", async () => { + let tx = await wallet.createAndSignTx({ + msgs: [ + new MsgSend( + tokenFactoryWalletAddr, + randomAccountAddr, + Coins.fromString("1000000000" + factoryDenom), + ), + ], + chainID: "test-1", + fee: new Fee(100_000, new Coins({ uluna: 100_000 })), + }); + let result = await LCD.chain1.tx.broadcastSync(tx, "test-1"); + await blockInclusion(); + let txResult = await LCD.chain1.tx.txInfo(result.txhash, "test-1") as any; + expect(txResult.raw_log) + .toStrictEqual("failed to execute message; message index: 0: {Loading CosmWasm module: sudo}: gas meter hit maximum limit"); + }); + }); + }) + + + // Change the token admin to a random account + // to validate that the functionality works and + // assert the logs are correctly formatted. + describe("After all operations", () => { + test("Must change the admin of the denom", async () => { + let tx = await wallet.createAndSignTx({ + msgs: [ + new MsgChangeAdmin( + tokenFactoryWalletAddr, + randomAccountAddr, + factoryDenom as string, + ), + ], + fee: new Fee(100_000, new Coins({ uluna: 100_000 })), + chainID: "test-1", + }); + let result = await LCD.chain1.tx.broadcastSync(tx, "test-1"); + await blockInclusion(); + let txResult = await LCD.chain1.tx.txInfo(result.txhash, "test-1") as any; + expect(txResult.logs[0].events).toStrictEqual([{ + "type": "message", + "attributes": [{ + "key": "action", + "value": "/osmosis.tokenfactory.v1beta1.MsgChangeAdmin" + }, { + "key": "sender", + "value": tokenFactoryWalletAddr + }, { + "key": "module", + "value": "tokenfactory" + }] + }, + { + "type": "change_admin", + "attributes": [{ + "key": "denom", + "value": factoryDenom + }, { + "key": "new_admin", + "value": randomAccountAddr + }] + }]); + }); + + test("Must query the new admin of the denom", async () => { + const res = await LCD.chain1.tokenfactory.authorityMetadata("test-1", encodeURIComponent(encodeURIComponent(factoryDenom as string))); + + expect(res) + .toStrictEqual({ + "authority_metadata": { + "admin": randomAccountAddr + } + }) + }) + + test("Must query the before send hook", async () => { + const res = await LCD.chain1.tokenfactory.beforeSendHook("test-1", encodeURIComponent(encodeURIComponent(factoryDenom as string))); + + expect(res) + .toStrictEqual({ "cosmwasm_address": contractAddress }) + }) + + test("Must query the before send hook", async () => { + const res = await LCD.chain1.tokenfactory.denomsFromCreator(tokenFactoryWalletAddr); + expect(res.denoms.length).toBeGreaterThanOrEqual(1); + }) + }) }); \ No newline at end of file diff --git a/scripts/tests/tokenfactory/tokenfactory.sh b/scripts/tests/tokenfactory/tokenfactory.sh deleted file mode 100755 index e619fe55..00000000 --- a/scripts/tests/tokenfactory/tokenfactory.sh +++ /dev/null @@ -1,99 +0,0 @@ -#!/bin/bash - -echo "" -echo "##########################################" -echo "# Create, Delete, Mint with Tokenfactory #" -echo "##########################################" -echo "" - -BINARY=terrad -CHAIN_DIR=$(pwd)/data -TOKEN_DENOM=utoken$RANDOM -MINT_AMOUNT=1000000 - -WALLET_1=$($BINARY keys show wallet1 -a --keyring-backend test --home $CHAIN_DIR/test-1) -WALLET_2=$($BINARY keys show wallet2 -a --keyring-backend test --home $CHAIN_DIR/test-2) -WALLET_3=$($BINARY keys show wallet3 -a --keyring-backend test --home $CHAIN_DIR/test-1) - -echo "Creating token denom $TOKEN_DENOM with $WALLET_1 on chain test-1" -TX_HASH=$($BINARY tx tokenfactory create-denom $TOKEN_DENOM --from $WALLET_1 --home $CHAIN_DIR/test-1 --chain-id test-1 --node tcp://localhost:16657 --gas 2000000 --keyring-backend test -o json -y | jq -r '.txhash') -sleep 3 -CREATED_RES_DENOM=$($BINARY query tx $TX_HASH -o josn --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 | jq -r '.logs[0].events[5].attributes[1].value') - -if [ "$CREATED_RES_DENOM" != "factory/$WALLET_1/$TOKEN_DENOM" ]; then - echo "ERROR: Tokenfactory creating denom error. Expected result 'factory/$WALLET_1/$TOKEN_DENOM', got '$CREATED_RES_DENOM'" - exit 1 -fi - -echo "Minting $MINT_AMOUNT units of $TOKEN_DENOM with $WALLET_1 on chain test-1" -TX_HASH=$($BINARY tx tokenfactory mint $MINT_AMOUNT$CREATED_RES_DENOM --from $WALLET_1 --home $CHAIN_DIR/test-1 --chain-id test-1 --node tcp://localhost:16657 --keyring-backend test -o json -y | jq -r '.txhash') -sleep 3 -MINT_RES=$($BINARY query tx $TX_HASH -o josn --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 | jq -r '.logs[0].events[2].type') -if [ "$MINT_RES" != "coinbase" ]; then - echo "ERROR: Tokenfactory minting error. Expected result 'coinbase', got '$CREATED_RES_DENOM'" - exit 1 -fi - -echo "Querying $TOKEN_DENOM from $WALLET_1 on chain test-1 to validate the amount minted" -BALANCE_RES_AMOUNT=$($BINARY query bank balances $WALLET_1 --denom $CREATED_RES_DENOM --chain-id test-2 --node tcp://localhost:16657 -o json | jq -r '.amount') -if [ "$BALANCE_RES_AMOUNT" != $MINT_AMOUNT ]; then - echo "ERROR: Tokenfactory minting error. Expected minted balance '$MINT_AMOUNT', got '$BALANCE_RES_AMOUNT'" - exit 1 -fi - -echo "Burning 1 $TOKEN_DENOM from $WALLET_1 on chain test-1" -TX_HASH=$($BINARY tx tokenfactory burn 1$CREATED_RES_DENOM --from $WALLET_1 --home $CHAIN_DIR/test-1 --chain-id test-1 --node tcp://localhost:16657 --keyring-backend test -o json -y | jq -r '.txhash') -sleep 3 -BURN_RES=$($BINARY query tx $TX_HASH -o josn --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 | jq -r '.logs[0].events[7].type') -if [ "$BURN_RES" != "tf_burn" ]; then - echo "ERROR: Tokenfactory burning error. Expected result 'tf_burn', got '$BURN_RES'" - exit 1 -fi - -echo "Querying $TOKEN_DENOM from $WALLET_1 on chain test-1 to validate the burned amount" -BALANCES_AFTER_BURNING=$($BINARY query bank balances $WALLET_1 --denom $CREATED_RES_DENOM --chain-id test-2 --node tcp://localhost:16657 -o json | jq -r '.amount') -if [ "$BALANCES_AFTER_BURNING" != 999999 ]; then - echo "ERROR: Tokenfactory minting error. Expected minted balance '999999', got '$BALANCES_AFTER_BURNING'" - exit 1 -fi - -echo "Sending 1 $TOKEN_DENOM from $WALLET_1 to $WALLET_3 on chain test-1" -TX_HASH=$($BINARY tx bank send $WALLET_1 $WALLET_3 1$CREATED_RES_DENOM --from $WALLET_1 --home $CHAIN_DIR/test-1 --chain-id test-1 --node tcp://localhost:16657 --keyring-backend test -o json -y | jq -r '.txhash') -sleep 3 -SEND_RES_MSG_TYPE=$($BINARY query tx $TX_HASH -o josn --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 | jq -r '.logs[0].events[0].attributes[0].value') -if [ "$SEND_RES_MSG_TYPE" != "/cosmos.bank.v1beta1.MsgSend" ]; then - echo "ERROR: Sending expected to be '/cosmos.bank.v1beta1.MsgSend' but got '$SEND_RES_MSG_TYPE'" - exit 1 -fi - -echo "Querying $TOKEN_DENOM from $WALLET_3 on chain test-1 to validate the funds were received" -BALANCES_RECEIVED=$($BINARY query bank balances $WALLET_3 --denom $CREATED_RES_DENOM --chain-id test-2 --node tcp://localhost:16657 -o json | jq -r '.amount') -if [ "$BALANCES_RECEIVED" != 1 ]; then - echo "ERROR: Tokenfactory minting error. Expected minted balance '1', got '$BALANCES_RECEIVED'" - exit 1 -fi - - -echo "IBC'ing 1 $TOKEN_DENOM from $WALLET_1 chain test-1 to $WALLET_2 chain test-2" -TX_HASH=$($BINARY tx ibc-transfer transfer transfer channel-0 $WALLET_2 1$CREATED_RES_DENOM --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 --keyring-backend test --from $WALLET_1 -y -o json | jq -r '.txhash') -sleep 3 -IBC_SEND_RES=$($BINARY query tx $TX_HASH -o josn --chain-id test-1 --home $CHAIN_DIR/test-1 --node tcp://localhost:16657 | jq -r '.logs[0].events[0].attributes[0].value') -if [ "$IBC_SEND_RES" != "/ibc.applications.transfer.v1.MsgTransfer" ]; then - echo "ERROR: IBC'ing expected type '/ibc.applications.transfer.v1.MsgTransfer' but got '$IBC_SEND_RES'" - exit 1 -fi - -IBC_RECEIVED_RES_AMOUNT=$($BINARY query bank balances $WALLET_2 --chain-id test-2 --node tcp://localhost:26657 -o json | jq -r '.balances[0].amount') -IBC_RECEIVED_RES_DENOM="" -while [ "$IBC_RECEIVED_RES_AMOUNT" != "1" ] || [ "${IBC_RECEIVED_RES_DENOM:0:4}" != "ibc/" ]; do - sleep 2 - IBC_RECEIVED_RES_AMOUNT=$($BINARY query bank balances $WALLET_2 --chain-id test-2 --node tcp://localhost:26657 -o json | jq -r '.balances[0].amount') - IBC_RECEIVED_RES_DENOM=$($BINARY query bank balances $WALLET_2 --chain-id test-2 --node tcp://localhost:26657 -o json | jq -r '.balances[0].denom') - echo "Received:" $IBC_RECEIVED_RES_AMOUNT $IBC_RECEIVED_RES_DENOM -done - -echo "" -echo "###################################################" -echo "# SUCCESS: Create, Delete, Mint with Tokenfactory #" -echo "###################################################" -echo "" diff --git a/x/tokenfactory/keeper/grpc_query.go b/x/tokenfactory/keeper/grpc_query.go index 048cad7f..2eac0817 100644 --- a/x/tokenfactory/keeper/grpc_query.go +++ b/x/tokenfactory/keeper/grpc_query.go @@ -41,6 +41,10 @@ func (k Keeper) DenomsFromCreator(ctx context.Context, req *types.QueryDenomsFro func (k Keeper) BeforeSendHookAddress(ctx context.Context, req *types.QueryBeforeSendHookAddressRequest) (*types.QueryBeforeSendHookAddressResponse, error) { sdkCtx := sdk.UnwrapSDKContext(ctx) + decodedDenom, err := url.QueryUnescape(req.Denom) + if err == nil { + req.Denom = decodedDenom + } cosmwasmAddress := k.GetBeforeSendHook(sdkCtx, req.GetDenom()) From 56322a9801f5f58923ec2bf47c3dd13638078b7d Mon Sep 17 00:00:00 2001 From: emidev98 Date: Mon, 20 Nov 2023 17:57:56 +0200 Subject: [PATCH 12/13] feat: use msg.ContractAddress instead of iterate events --- .../src/modules/alliance/alliance.test.ts | 1 - integration-tests/src/modules/authz.test.ts | 69 +++++++++++++++++-- integration-tests/src/modules/gov/gov.test.ts | 2 +- x/feeshare/post/post.go | 12 ++-- x/feeshare/post/post_test.go | 6 +- x/wasm/keeper/keeper.go | 41 ++++------- 6 files changed, 87 insertions(+), 44 deletions(-) diff --git a/integration-tests/src/modules/alliance/alliance.test.ts b/integration-tests/src/modules/alliance/alliance.test.ts index d776951f..8c266e70 100644 --- a/integration-tests/src/modules/alliance/alliance.test.ts +++ b/integration-tests/src/modules/alliance/alliance.test.ts @@ -52,7 +52,6 @@ describe("Alliance Module (https://github.com/terra-money/alliance/tree/release/ } } catch (e) { - console.log(e) expect(e).toBeUndefined(); } }); diff --git a/integration-tests/src/modules/authz.test.ts b/integration-tests/src/modules/authz.test.ts index df3ae39a..2aa1fc3b 100644 --- a/integration-tests/src/modules/authz.test.ts +++ b/integration-tests/src/modules/authz.test.ts @@ -18,11 +18,7 @@ describe("Authz Module (https://github.com/terra-money/cosmos-sdk/tree/release/v test('Must register the granter', async () => { let tx = await granterWallet.createAndSignTx({ - msgs: [new MsgDelegate( - granterAddr, - val2Addr, - Coin.fromString("1000000uluna"), - ),new MsgGrantAuthorization( + msgs: [new MsgGrantAuthorization( granterAddr, granteeAddr, new AuthorizationGrant( @@ -85,7 +81,68 @@ describe("Authz Module (https://github.com/terra-money/cosmos-sdk/tree/release/v let result = await LCD.chain2.tx.broadcastSync(tx, "test-2"); await blockInclusion(); - console.log(result); + let txResult = await LCD.chain2.tx.txInfo(result.txhash, "test-2") as any; + let eventsList = txResult.logs[0].events; + expect(eventsList[0]) + .toStrictEqual({ + "type": "message", + "attributes": [{ + "key": "action", + "value": "/cosmos.authz.v1beta1.MsgExec" + }, { + "key": "sender", + "value": "terra1v0eee20gjl68fuk0chyrkch2z7suw2mhg3wkxf" + }, { + "key": "module", + "value": "authz" + }] + }); + expect(eventsList[1]) + .toStrictEqual({ + "type": "cosmos.authz.v1beta1.EventRevoke", + "attributes": [{ + "key": "grantee", + "value": "\"terra1v0eee20gjl68fuk0chyrkch2z7suw2mhg3wkxf\"" + }, { + "key": "granter", + "value": "\"terra120rzk7n6cd2vufkmwrat34adqh0rgca9tkyfe5\"" + }, { + "key": "msg_type_url", + "value": "\"/cosmos.staking.v1beta1.MsgDelegate\"" + }] + }); + expect(eventsList[5]) + .toStrictEqual({ + "type": "message", + "attributes": [{ + "key": "sender", + "value": "terra1jv65s3grqf6v6jl3dp4t6c9t9rk99cd8pm7utl" + }, { + "key": "authz_msg_index", + "value": "0" + }] + }); + + expect(eventsList[9]) + .toStrictEqual({ + "type": "delegate", + "attributes": [{ + "key": "validator", + "value": "terravaloper1llgzglr9yyy4gyjh8p5kepgm5wyl358de47rqk" + }, { + "key": "delegator", + "value": "terra120rzk7n6cd2vufkmwrat34adqh0rgca9tkyfe5" + }, { + "key": "amount", + "value": "1000000uluna" + }, { + "key": "new_shares", + "value": "1000000.000000000000000000" + }, { + "key": "authz_msg_index", + "value": "0" + }] + }); } catch (e) { console.log(e) diff --git a/integration-tests/src/modules/gov/gov.test.ts b/integration-tests/src/modules/gov/gov.test.ts index 0103d06c..e5f271d9 100644 --- a/integration-tests/src/modules/gov/gov.test.ts +++ b/integration-tests/src/modules/gov/gov.test.ts @@ -221,7 +221,7 @@ describe("Governance Module (https://github.com/terra-money/cosmos-sdk/tree/rele // Query the alliance and check if it exists const res = await LCD.chain2.gov.tally("test-2", proposalId); expect(res).toBeDefined(); - expect(res.yes_count).toStrictEqual(new Int(27000000000)); + expect(res.yes_count.gte(27000000000)).toBeTruthy(); expect(res.abstain_count).toStrictEqual(new Int(0)); expect(res.no_count).toStrictEqual(new Int(0)); expect(res.no_with_veto_count).toStrictEqual(new Int(0)); diff --git a/x/feeshare/post/post.go b/x/feeshare/post/post.go index 346b1bea..e31da38f 100644 --- a/x/feeshare/post/post.go +++ b/x/feeshare/post/post.go @@ -134,15 +134,15 @@ func GetWithdrawalAddressFromContract(ctx sdk.Context, contractAddresses []strin // CalculateFee takes the total fees paid for a transaction and split // these fees equaly between all number of pairs considering allwoedDenoms -func CalculateFee(fees sdk.Coins, govPercent sdk.Dec, pairs int, allowedDenoms []string) sdk.Coins { - var alloedFeesDenoms sdk.Coins +func CalculateFee(fees sdk.Coins, devShares sdk.Dec, numOfdevs int, allowedDenoms []string) sdk.Coins { + var allowedFeesDenoms sdk.Coins if len(allowedDenoms) == 0 { - alloedFeesDenoms = fees + allowedFeesDenoms = fees } else { for _, fee := range fees { for _, allowedDenom := range allowedDenoms { if fee.Denom == allowedDenom { - alloedFeesDenoms = alloedFeesDenoms.Add(fee) + allowedFeesDenoms = allowedFeesDenoms.Add(fee) break } } @@ -150,8 +150,8 @@ func CalculateFee(fees sdk.Coins, govPercent sdk.Dec, pairs int, allowedDenoms [ } var splitFees sdk.Coins - for _, c := range alloedFeesDenoms.Sort() { - rewardAmount := govPercent.MulInt(c.Amount).QuoInt64(int64(pairs)).RoundInt() + for _, c := range allowedFeesDenoms.Sort() { + rewardAmount := devShares.MulInt(c.Amount).QuoInt64(int64(numOfdevs)).RoundInt() if !rewardAmount.IsZero() { splitFees = splitFees.Add(sdk.NewCoin(c.Denom, rewardAmount)) } diff --git a/x/feeshare/post/post_test.go b/x/feeshare/post/post_test.go index 6d06de52..6798d6ab 100644 --- a/x/feeshare/post/post_test.go +++ b/x/feeshare/post/post_test.go @@ -85,8 +85,8 @@ func (suite *AnteTestSuite) TestCalculateFee() { testCases := []struct { name string incomingFee sdk.Coins - govPercent sdk.Dec - numContracts int + devShares sdk.Dec + numOfdevs int allowdDenoms []string expectedFeePayment sdk.Coins }{ @@ -173,7 +173,7 @@ func (suite *AnteTestSuite) TestCalculateFee() { } for _, tc := range testCases { - feeToBePaid := post.CalculateFee(tc.incomingFee, tc.govPercent, tc.numContracts, tc.allowdDenoms) + feeToBePaid := post.CalculateFee(tc.incomingFee, tc.devShares, tc.numOfdevs, tc.allowdDenoms) suite.Require().Equal(tc.expectedFeePayment, feeToBePaid, tc.name) } diff --git a/x/wasm/keeper/keeper.go b/x/wasm/keeper/keeper.go index 3e5c9d2d..7247cbdd 100644 --- a/x/wasm/keeper/keeper.go +++ b/x/wasm/keeper/keeper.go @@ -67,40 +67,27 @@ func NewKeeper( } } +// After executing the contract, get all executed +// contract addresses from the store, if there is +// a store already then check if the contract address +// exists in the list, if not then update the store, +// If the contract does not exist in the store, add it. func (k Keeper) AfterExecuteContract(ctx sdk.Context, msg *types.MsgExecuteContract, res *types.MsgExecuteContractResponse) error { - events := ctx.EventManager().Events() - contractAddresses := []string{} + contracts, found := k.GetExecutedContractAddresses(ctx) - for _, ev := range events { - if ev.Type != "execute" { - continue - } - - for _, attr := range ev.Attributes { - if attr.Key != "_contract_address" { - continue - } - // if the contract address has already been - // added just skip it to avoid duplicates - for _, item := range contractAddresses { - if item == attr.Value { - continue - } + if found { + for _, contract := range contracts.ContractAddresses { + if contract == msg.Contract { + return nil } - contractAddresses = append(contractAddresses, attr.Value) } } - if len(contractAddresses) != 0 { - executedContracts := keepertypes.ExecutedContracts{ - ContractAddresses: contractAddresses, - } + contracts.ContractAddresses = append(contracts.ContractAddresses, msg.Contract) - err := k.SetExecutedContractAddresses(ctx, executedContracts) - if err != nil { - return err - } + err := k.SetExecutedContractAddresses(ctx, contracts) + if err != nil { + return err } - return nil } From 3ed296dfbe83c41fc58378dea713065a8d4748ce Mon Sep 17 00:00:00 2001 From: emidev98 Date: Tue, 21 Nov 2023 14:25:42 +0200 Subject: [PATCH 13/13] feat: feeshare post hanlder tests --- app/app_test/test_helpers.go | 13 +- app/post/mocks/post_mock.go | 92 ++++++++++ go.mod | 1 + go.sum | 3 +- x/feeshare/post/post_test.go | 345 +++++++++++++++++++++++++++++++++++ 5 files changed, 449 insertions(+), 5 deletions(-) create mode 100644 app/post/mocks/post_mock.go diff --git a/app/app_test/test_helpers.go b/app/app_test/test_helpers.go index 7d77b1cd..727c6f88 100644 --- a/app/app_test/test_helpers.go +++ b/app/app_test/test_helpers.go @@ -36,10 +36,11 @@ import ( type AppTestSuite struct { suite.Suite - App *app.TerraApp - Ctx sdk.Context - QueryHelper *baseapp.QueryServiceTestHelper - TestAccs []sdk.AccAddress + App *app.TerraApp + Ctx sdk.Context + QueryHelper *baseapp.QueryServiceTestHelper + TestAccs []sdk.AccAddress + EncodingConfig appparams.EncodingConfig } // Setup sets up basic environment for suite (App, Ctx, and test accounts) @@ -62,6 +63,7 @@ func (s *AppTestSuite) Setup() { simtestutil.EmptyAppOptions{}, wasmconfig.DefaultConfig(), ) + s.EncodingConfig = encCfg s.Ctx = s.App.NewContext(true, tmproto.Header{Height: 1, Time: time.Now()}) s.QueryHelper = &baseapp.QueryServiceTestHelper{ @@ -80,6 +82,9 @@ func (s *AppTestSuite) Setup() { err = s.App.Keepers.TokenFactoryKeeper.SetParams(s.Ctx, tokenfactorytypes.DefaultParams()) s.Require().NoError(err) + err = s.FundModule(authtypes.FeeCollectorName, sdk.NewCoins(sdk.NewCoin("uluna", sdk.NewInt(1000)), sdk.NewCoin("utoken", sdk.NewInt(500)))) + s.Require().NoError(err) + s.App.Keepers.DistrKeeper.SetFeePool(s.Ctx, distrtypes.InitialFeePool()) s.TestAccs = s.CreateRandomAccounts(3) diff --git a/app/post/mocks/post_mock.go b/app/post/mocks/post_mock.go new file mode 100644 index 00000000..c2de3e16 --- /dev/null +++ b/app/post/mocks/post_mock.go @@ -0,0 +1,92 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: types/handler.go +// +// Generated by this command: +// +// mockgen -source=types/handler.go -package=mocks -destination=types/mocks/interfaces.go PostDecorator +// +// Package mocks is a generated GoMock package. +package mocks + +import ( + reflect "reflect" + + types "github.com/cosmos/cosmos-sdk/types" + gomock "go.uber.org/mock/gomock" +) + +// MockAnteDecorator is a mock of AnteDecorator interface. +type MockAnteDecorator struct { + ctrl *gomock.Controller + recorder *MockAnteDecoratorMockRecorder +} + +// MockAnteDecoratorMockRecorder is the mock recorder for MockAnteDecorator. +type MockAnteDecoratorMockRecorder struct { + mock *MockAnteDecorator +} + +// NewMockAnteDecorator creates a new mock instance. +func NewMockAnteDecorator(ctrl *gomock.Controller) *MockAnteDecorator { + mock := &MockAnteDecorator{ctrl: ctrl} + mock.recorder = &MockAnteDecoratorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAnteDecorator) EXPECT() *MockAnteDecoratorMockRecorder { + return m.recorder +} + +// AnteHandle mocks base method. +func (m *MockAnteDecorator) AnteHandle(ctx types.Context, tx types.Tx, simulate bool, next types.AnteHandler) (types.Context, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AnteHandle", ctx, tx, simulate, next) + ret0, _ := ret[0].(types.Context) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AnteHandle indicates an expected call of AnteHandle. +func (mr *MockAnteDecoratorMockRecorder) AnteHandle(ctx, tx, simulate, next any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AnteHandle", reflect.TypeOf((*MockAnteDecorator)(nil).AnteHandle), ctx, tx, simulate, next) +} + +// MockPostDecorator is a mock of PostDecorator interface. +type MockPostDecorator struct { + ctrl *gomock.Controller + recorder *MockPostDecoratorMockRecorder +} + +// MockPostDecoratorMockRecorder is the mock recorder for MockPostDecorator. +type MockPostDecoratorMockRecorder struct { + mock *MockPostDecorator +} + +// NewMockPostDecorator creates a new mock instance. +func NewMockPostDecorator(ctrl *gomock.Controller) *MockPostDecorator { + mock := &MockPostDecorator{ctrl: ctrl} + mock.recorder = &MockPostDecoratorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPostDecorator) EXPECT() *MockPostDecoratorMockRecorder { + return m.recorder +} + +// PostHandle mocks base method. +func (m *MockPostDecorator) PostHandle(ctx types.Context, tx types.Tx, simulate, success bool, next types.PostHandler) (types.Context, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PostHandle", ctx, tx, simulate, success, next) + ret0, _ := ret[0].(types.Context) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PostHandle indicates an expected call of PostHandle. +func (mr *MockPostDecoratorMockRecorder) PostHandle(ctx, tx, simulate, success, next any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PostHandle", reflect.TypeOf((*MockPostDecorator)(nil).PostHandle), ctx, tx, simulate, success, next) +} diff --git a/go.mod b/go.mod index 86575911..c55cdccc 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,7 @@ require ( github.com/spf13/viper v1.16.0 github.com/stretchr/testify v1.8.4 github.com/terra-money/alliance v0.3.2 + go.uber.org/mock v0.3.0 google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 google.golang.org/grpc v1.58.3 ) diff --git a/go.sum b/go.sum index ad3fe576..9a4932ed 100644 --- a/go.sum +++ b/go.sum @@ -1198,7 +1198,8 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/mock v0.2.0 h1:TaP3xedm7JaAgScZO7tlvlKrqT0p7I6OsdGB5YNSMDU= +go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= +go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= diff --git a/x/feeshare/post/post_test.go b/x/feeshare/post/post_test.go index 6798d6ab..40857834 100644 --- a/x/feeshare/post/post_test.go +++ b/x/feeshare/post/post_test.go @@ -3,13 +3,20 @@ package ante_test import ( "testing" + errorsmod "cosmossdk.io/errors" "github.com/stretchr/testify/suite" + "go.uber.org/mock/gomock" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + abci "github.com/cometbft/cometbft/abci/types" app "github.com/terra-money/core/v2/app/app_test" + "github.com/terra-money/core/v2/app/post/mocks" post "github.com/terra-money/core/v2/x/feeshare/post" "github.com/terra-money/core/v2/x/feeshare/types" + customwasmtypes "github.com/terra-money/core/v2/x/wasm/types" ) type AnteTestSuite struct { @@ -49,6 +56,17 @@ func (suite *AnteTestSuite) TestGetWithdrawalAddressFromContract() { }, false, }, + { + "two valid contract addresses with one not registered", + []string{ + "terra1u3z42fpctuhh8mranz4tatacqhty6a8yk7l5wvj7dshsuytcms2qda4f5x", // not registered address + "terra1jwyzzsaag4t0evnuukc35ysyrx9arzdde2kg9cld28alhjurtthq0prs2s", + }, + []sdk.AccAddress{ + sdk.MustAccAddressFromBech32("terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je"), + }, + false, + }, { "without withdrawer contract addresses", []string{"terra1mdpvgjc8jmv60a4x68nggsh9w8uyv69sqls04a76m9med5hsqmwsse8sxa"}, @@ -178,3 +196,330 @@ func (suite *AnteTestSuite) TestCalculateFee() { suite.Require().Equal(tc.expectedFeePayment, feeToBePaid, tc.name) } } + +func (suite *AnteTestSuite) TestPostHandler() { + suite.Setup() + + // Create a mocked next post hanlder to assert the function being called. + ctrl := gomock.NewController(suite.T()) + mockedPostDecorator := mocks.NewMockPostDecorator(ctrl) + + // Register the feeshare contract... + suite.App.Keepers.FeeShareKeeper.SetFeeShare(suite.Ctx, types.FeeShare{ + ContractAddress: "terra1mdpvgjc8jmv60a4x68nggsh9w8uyv69sqls04a76m9med5hsqmwsse8sxa", + DeployerAddress: "", + WithdrawerAddress: "terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je", + }) + // ... append the executed contract addresses in the wasm keeper ... + suite.App.Keepers.WasmKeeper.SetExecutedContractAddresses(suite.Ctx, customwasmtypes.ExecutedContracts{ + ContractAddresses: []string{"terra1mdpvgjc8jmv60a4x68nggsh9w8uyv69sqls04a76m9med5hsqmwsse8sxa"}, + }) + + // build a tx with a fee amount ... + txFee := sdk.NewCoins(sdk.NewCoin("uluna", sdk.NewInt(500)), sdk.NewCoin("utoken", sdk.NewInt(250))) + txBuilder := suite.EncodingConfig.TxConfig.NewTxBuilder() + txBuilder.SetFeeAmount(txFee) + txBuilder.SetMsgs(&wasmtypes.MsgExecuteContract{ + Sender: "terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je", + Contract: "terra1mdpvgjc8jmv60a4x68nggsh9w8uyv69sqls04a76m9med5hsqmwsse8sxa", + Msg: nil, + Funds: nil, + }) + // ... create the feeshare post handler ... + handler := post.NewFeeSharePayoutDecorator( + suite.App.Keepers.FeeShareKeeper, + suite.App.Keepers.BankKeeper, + suite.App.Keepers.WasmKeeper, + ) + // Remove all events from the context to assert the events being added correctly. + suite.Ctx = suite.Ctx.WithEventManager(sdk.NewEventManager()) + + // Assert the next hanlder is called once + mockedPostDecorator. + EXPECT(). + PostHandle(gomock.Any(), gomock.Any(), false, true, gomock.Any()). + Times(1) + + // Execute the PostHandle function + _, err := handler.PostHandle( + suite.Ctx, + txBuilder.GetTx(), + false, + true, + func(ctx sdk.Context, tx sdk.Tx, simulate bool, success bool) (sdk.Context, error) { + return mockedPostDecorator.PostHandle(ctx, tx, simulate, success, nil) + }, + ) + suite.Require().NoError(err) + suite.Require().Equal(suite.Ctx.EventManager().ABCIEvents(), + []abci.Event{ + { + Type: "coin_spent", + Attributes: []abci.EventAttribute{ + {Key: "spender", Value: "terra17xpfvakm2amg962yls6f84z3kell8c5lkaeqfa", Index: false}, + {Key: "amount", Value: "250uluna,125utoken", Index: false}, + }, + }, + { + Type: "coin_received", + Attributes: []abci.EventAttribute{ + {Key: "receiver", Value: "terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je", Index: false}, + {Key: "amount", Value: "250uluna,125utoken", Index: false}, + }, + }, + { + Type: "transfer", + Attributes: []abci.EventAttribute{ + {Key: "recipient", Value: "terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je", Index: false}, + {Key: "sender", Value: "terra17xpfvakm2amg962yls6f84z3kell8c5lkaeqfa", Index: false}, + {Key: "amount", Value: "250uluna,125utoken", Index: false}, + }, + }, + { + Type: "message", + Attributes: []abci.EventAttribute{ + {Key: "sender", Value: "terra17xpfvakm2amg962yls6f84z3kell8c5lkaeqfa", Index: false}, + }, + }, + { + Type: "juno.feeshare.v1.FeePayoutEvent", + Attributes: []abci.EventAttribute{ + {Key: "fees_paid", Value: "[{\"denom\":\"uluna\",\"amount\":\"250\"},{\"denom\":\"utoken\",\"amount\":\"125\"}]", Index: false}, + {Key: "withdraw_address", Value: "\"terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je\"", Index: false}, + }, + }, + }) +} + +func (suite *AnteTestSuite) TestDisabledPostHandle() { + suite.Setup() + + // Create a mocked next post hanlder to assert the function being called. + ctrl := gomock.NewController(suite.T()) + mockedPostDecorator := mocks.NewMockPostDecorator(ctrl) + + // Disable the feeshare module... + err := suite.App.Keepers.FeeShareKeeper.SetParams(suite.Ctx, types.Params{ + EnableFeeShare: false, + DeveloperShares: sdk.MustNewDecFromStr("0.5"), + AllowedDenoms: []string{}, + }) + suite.Require().NoError(err) + + // build a tx with a fee amount ... + txFee := sdk.NewCoins(sdk.NewCoin("uluna", sdk.NewInt(500)), sdk.NewCoin("utoken", sdk.NewInt(250))) + txBuilder := suite.EncodingConfig.TxConfig.NewTxBuilder() + txBuilder.SetFeeAmount(txFee) + txBuilder.SetMsgs(&wasmtypes.MsgExecuteContract{}) + + // ... create the feeshare post handler ... + handler := post.NewFeeSharePayoutDecorator( + suite.App.Keepers.FeeShareKeeper, + suite.App.Keepers.BankKeeper, + suite.App.Keepers.WasmKeeper, + ) + + // Assert the next hanlder is called once + mockedPostDecorator. + EXPECT(). + PostHandle(gomock.Any(), gomock.Any(), false, true, gomock.Any()). + Times(1) + + // Execute the PostHandle function + _, err = handler.PostHandle( + suite.Ctx, + txBuilder.GetTx(), + false, + true, + func(ctx sdk.Context, tx sdk.Tx, simulate bool, success bool) (sdk.Context, error) { + return mockedPostDecorator.PostHandle(ctx, tx, simulate, success, nil) + }, + ) + suite.Require().NoError(err) +} + +func (suite *AnteTestSuite) TestWithZeroFeesPostHandle() { + suite.Setup() + + // Create a mocked next post hanlder to assert the function being called. + ctrl := gomock.NewController(suite.T()) + mockedPostDecorator := mocks.NewMockPostDecorator(ctrl) + + // Build a tx with a fee amount ... + txBuilder := suite.EncodingConfig.TxConfig.NewTxBuilder() + + // ... create the feeshare post handler ... + handler := post.NewFeeSharePayoutDecorator( + suite.App.Keepers.FeeShareKeeper, + suite.App.Keepers.BankKeeper, + suite.App.Keepers.WasmKeeper, + ) + + // Assert the next hanlder is called once + mockedPostDecorator. + EXPECT(). + PostHandle(gomock.Any(), gomock.Any(), false, true, gomock.Any()). + Times(1) + + // Execute the PostHandle function + _, err := handler.PostHandle( + suite.Ctx, + txBuilder.GetTx(), + false, + true, + func(ctx sdk.Context, tx sdk.Tx, simulate bool, success bool) (sdk.Context, error) { + return mockedPostDecorator.PostHandle(ctx, tx, simulate, success, nil) + }, + ) + suite.Require().NoError(err) +} + +func (suite *AnteTestSuite) TestPostHandlerWithEmptySmartContractStore() { + suite.Setup() + + // Create a mocked next post hanlder to assert the function being called. + ctrl := gomock.NewController(suite.T()) + mockedPostDecorator := mocks.NewMockPostDecorator(ctrl) + + // Register the feeshare contract... + suite.App.Keepers.FeeShareKeeper.SetFeeShare(suite.Ctx, types.FeeShare{ + ContractAddress: "terra1mdpvgjc8jmv60a4x68nggsh9w8uyv69sqls04a76m9med5hsqmwsse8sxa", + DeployerAddress: "", + WithdrawerAddress: "terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je", + }) + + // build a tx with a fee amount ... + txFee := sdk.NewCoins(sdk.NewCoin("uluna", sdk.NewInt(500)), sdk.NewCoin("utoken", sdk.NewInt(250))) + txBuilder := suite.EncodingConfig.TxConfig.NewTxBuilder() + txBuilder.SetFeeAmount(txFee) + txBuilder.SetMsgs(&wasmtypes.MsgExecuteContract{ + Sender: "terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je", + Contract: "terra1mdpvgjc8jmv60a4x68nggsh9w8uyv69sqls04a76m9med5hsqmwsse8sxa", + Msg: nil, + Funds: nil, + }) + // ... create the feeshare post handler ... + handler := post.NewFeeSharePayoutDecorator( + suite.App.Keepers.FeeShareKeeper, + suite.App.Keepers.BankKeeper, + suite.App.Keepers.WasmKeeper, + ) + + // Assert the next hanlder is called once + mockedPostDecorator. + EXPECT(). + PostHandle(gomock.Any(), gomock.Any(), false, true, gomock.Any()). + Times(1) + + // Execute the PostHandle function + _, err := handler.PostHandle( + suite.Ctx, + txBuilder.GetTx(), + false, + true, + func(ctx sdk.Context, tx sdk.Tx, simulate bool, success bool) (sdk.Context, error) { + return mockedPostDecorator.PostHandle(ctx, tx, simulate, success, nil) + }, + ) + suite.Require().NoError(err) +} + +func (suite *AnteTestSuite) TestPostHandlerNoSmartContractExecuted() { + suite.Setup() + + // Create a mocked next post hanlder to assert the function being called. + ctrl := gomock.NewController(suite.T()) + mockedPostDecorator := mocks.NewMockPostDecorator(ctrl) + + // Register the feeshare contract... + suite.App.Keepers.FeeShareKeeper.SetFeeShare(suite.Ctx, types.FeeShare{ + ContractAddress: "terra1mdpvgjc8jmv60a4x68nggsh9w8uyv69sqls04a76m9med5hsqmwsse8sxa", + DeployerAddress: "", + WithdrawerAddress: "terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je", + }) + // ... create the store key ... + suite.App.Keepers.WasmKeeper.SetExecutedContractAddresses(suite.Ctx, customwasmtypes.ExecutedContracts{ + ContractAddresses: []string{}, + }) + + // build a tx with a fee amount ... + txFee := sdk.NewCoins(sdk.NewCoin("uluna", sdk.NewInt(500)), sdk.NewCoin("utoken", sdk.NewInt(250))) + txBuilder := suite.EncodingConfig.TxConfig.NewTxBuilder() + txBuilder.SetFeeAmount(txFee) + txBuilder.SetMsgs(&wasmtypes.MsgExecuteContract{ + Sender: "terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je", + Contract: "terra1mdpvgjc8jmv60a4x68nggsh9w8uyv69sqls04a76m9med5hsqmwsse8sxa", + Msg: nil, + Funds: nil, + }) + // ... create the feeshare post handler ... + handler := post.NewFeeSharePayoutDecorator( + suite.App.Keepers.FeeShareKeeper, + suite.App.Keepers.BankKeeper, + suite.App.Keepers.WasmKeeper, + ) + + // Assert the next hanlder is called once + mockedPostDecorator. + EXPECT(). + PostHandle(gomock.Any(), gomock.Any(), false, true, gomock.Any()). + Times(1) + + // Execute the PostHandle function + _, err := handler.PostHandle( + suite.Ctx, + txBuilder.GetTx(), + false, + true, + func(ctx sdk.Context, tx sdk.Tx, simulate bool, success bool) (sdk.Context, error) { + return mockedPostDecorator.PostHandle(ctx, tx, simulate, success, nil) + }, + ) + suite.Require().NoError(err) +} + +func (suite *AnteTestSuite) TestPostHandlerWithInvalidContractAddrOnExecution() { + suite.Setup() + + // Create a mocked next post hanlder to assert the function being called. + ctrl := gomock.NewController(suite.T()) + mockedPostDecorator := mocks.NewMockPostDecorator(ctrl) + + // Register the feeshare contract... + suite.App.Keepers.FeeShareKeeper.SetFeeShare(suite.Ctx, types.FeeShare{ + ContractAddress: "terra1mdpvgjc8jmv60a4x68nggsh9w8uyv69sqls04a76m9med5hsqmwsse8sxa", + DeployerAddress: "", + WithdrawerAddress: "terra1zdpgj8am5nqqvht927k3etljyl6a52kwqup0je", + }) + // ... create the store key ... + suite.App.Keepers.WasmKeeper.SetExecutedContractAddresses(suite.Ctx, customwasmtypes.ExecutedContracts{ + ContractAddresses: []string{"invalid_contract_addr"}, + }) + + // build a tx with a fee amount ... + txFee := sdk.NewCoins(sdk.NewCoin("uluna", sdk.NewInt(500)), sdk.NewCoin("utoken", sdk.NewInt(250))) + txBuilder := suite.EncodingConfig.TxConfig.NewTxBuilder() + txBuilder.SetFeeAmount(txFee) + txBuilder.SetMsgs(&wasmtypes.MsgExecuteContract{}) + + // ... create the feeshare post handler ... + handler := post.NewFeeSharePayoutDecorator( + suite.App.Keepers.FeeShareKeeper, + suite.App.Keepers.BankKeeper, + suite.App.Keepers.WasmKeeper, + ) + + // Execute the PostHandle function + _, err := handler.PostHandle( + suite.Ctx, + txBuilder.GetTx(), + false, + true, + func(ctx sdk.Context, tx sdk.Tx, simulate bool, success bool) (sdk.Context, error) { + return mockedPostDecorator.PostHandle(ctx, tx, simulate, success, nil) + }, + ) + suite. + Require(). + ErrorIs(err, errorsmod.Wrapf(sdkerrors.ErrLogic, err.Error())) +}