From 3eb46a2de2281c2bdd5dcee4a3be990d560fcb5c Mon Sep 17 00:00:00 2001 From: Vladislav Varadinov Date: Mon, 15 May 2023 15:22:54 +0200 Subject: [PATCH 1/2] imp: represent unlimited approvals with MaxUint256 value (#3454) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * imp: represent unlimited approvals with a nil value * CHANGELOG * Update CHANGELOG.md * fix: updated unlimited spending limit to be represented with the MaxInt64 * Update modules/apps/transfer/types/transfer_authorization.go Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> * Update CHANGELOG.md Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> * fix: lint * Update modules/apps/transfer/types/transfer_authorization.go * fix: update failing test and add test case suggested in review * fix: moved isAllowedAddress check before coin loop * fix: use maxUint256 case so it aligns with what's being passed from the EVM extension * refactor transfer authz to remove coins iteration in favour of explicit amount checking * make format * Update modules/apps/transfer/types/transfer_authorization.go Co-authored-by: Damian Nolan * fix: add the Authorization to Updated. * moving allowlist check to before spend limit logic * Apply suggestions from code review * fix: add comment to spend limit check * review feedback * Update modules/apps/transfer/types/transfer_authorization.go Co-authored-by: Damian Nolan * Update docs/apps/transfer/authorizations.md * fix: changed to new sentinel value name --------- Co-authored-by: Carlos Rodriguez Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> Co-authored-by: Damian Nolan (cherry picked from commit 7e6eb4c66bc7340626feb7bdbdb74592d4d98d89) # Conflicts: # CHANGELOG.md # e2e/tests/core/client_test.go # e2e/testsuite/grpc_query.go # modules/apps/transfer/types/transfer_authorization.go --- CHANGELOG.md | 6 + docs/apps/transfer/authorizations.md | 4 +- e2e/tests/core/client_test.go | 335 +++++++++++++++++ e2e/testsuite/grpc_query.go | 342 ++++++++++++++++++ .../transfer/types/transfer_authorization.go | 53 +++ .../types/transfer_authorization_test.go | 50 ++- 6 files changed, 787 insertions(+), 3 deletions(-) create mode 100644 e2e/tests/core/client_test.go create mode 100644 e2e/testsuite/grpc_query.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 09d5f1ff7ea..8efc622d3d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,12 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements +<<<<<<< HEAD +======= +* (tests) [\#3138](https://github.com/cosmos/ibc-go/pull/3138) Support benchmarks and fuzz tests through `testing.TB`. +* (apps/transfer) [\#3454](https://github.com/cosmos/ibc-go/pull/3454) Support transfer authorization unlimited spending when the max `uint256` value is provided as limit. + +>>>>>>> 7e6eb4c6 (imp: represent unlimited approvals with MaxUint256 value (#3454)) ### Features ### Bug Fixes diff --git a/docs/apps/transfer/authorizations.md b/docs/apps/transfer/authorizations.md index 7be41413df0..fc5c3cc89a8 100644 --- a/docs/apps/transfer/authorizations.md +++ b/docs/apps/transfer/authorizations.md @@ -1,6 +1,6 @@ # `TransferAuthorization` -`TransferAuthorization` implements the `Authorization` interface for `ibc.applications.transfer.v1.MsgTransfer`. It allows a granter to grant a grantee the privilege to submit MsgTransfer on its behalf. Please see the [Cosmos SDK docs](https://docs.cosmos.network/v0.47/modules/authz) for more details on granting privileges via the `x/authz` module. +`TransferAuthorization` implements the `Authorization` interface for `ibc.applications.transfer.v1.MsgTransfer`. It allows a granter to grant a grantee the privilege to submit `MsgTransfer` on its behalf. Please see the [Cosmos SDK docs](https://docs.cosmos.network/v0.47/modules/authz) for more details on granting privileges via the `x/authz` module. More specifically, the granter allows the grantee to transfer funds that belong to the granter over a specified channel. @@ -13,7 +13,7 @@ It takes: - a `SourcePort` and a `SourceChannel` which together comprise the unique transfer channel identifier over which authorized funds can be transferred. -- a `SpendLimit` that specifies the maximum amount of tokens the grantee can spend. The `SpendLimit` is updated as the tokens are spent. This `SpendLimit` may also be updated to increase or decrease the limit as the granter wishes. +- a `SpendLimit` that specifies the maximum amount of tokens the grantee can transfer. The `SpendLimit` is updated as the tokens are transfered, unless the sentinel value of the maximum value for a 256-bit unsigned integer (i.e. 2^256 - 1) is used for the amount, in which case the `SpendLimit` will not be updated (please be aware that using this sentinel value will grant the grantee the privilege to transfer **all** the tokens of a given denomination available at the granter's account). The helper function `UnboundedSpendLimit` in the `types` package of the `transfer` module provides the sentinel value that can be used. This `SpendLimit` may also be updated to increase or decrease the limit as the granter wishes. - an `AllowList` list that specifies the list of addresses that are allowed to receive funds. If this list is empty, then all addresses are allowed to receive funds from the `TransferAuthorization`. diff --git a/e2e/tests/core/client_test.go b/e2e/tests/core/client_test.go new file mode 100644 index 00000000000..bc1f34a1552 --- /dev/null +++ b/e2e/tests/core/client_test.go @@ -0,0 +1,335 @@ +package e2e + +import ( + "context" + "fmt" + "sort" + "strings" + "testing" + "time" + + "github.com/cometbft/cometbft/crypto/tmhash" + tmjson "github.com/cometbft/cometbft/libs/json" + "github.com/cometbft/cometbft/privval" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + tmprotoversion "github.com/cometbft/cometbft/proto/tendermint/version" + tmtypes "github.com/cometbft/cometbft/types" + tmversion "github.com/cometbft/cometbft/version" + "github.com/cosmos/cosmos-sdk/client/grpc/tmservice" + "github.com/strangelove-ventures/interchaintest/v7/chain/cosmos" + "github.com/strangelove-ventures/interchaintest/v7/ibc" + test "github.com/strangelove-ventures/interchaintest/v7/testutil" + "github.com/stretchr/testify/suite" + + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + + "github.com/cosmos/ibc-go/e2e/dockerutil" + "github.com/cosmos/ibc-go/e2e/testsuite" + "github.com/cosmos/ibc-go/e2e/testvalues" + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v7/testing" + ibcmock "github.com/cosmos/ibc-go/v7/testing/mock" +) + +const ( + invalidHashValue = "invalid_hash" +) + +func TestClientTestSuite(t *testing.T) { + suite.Run(t, new(ClientTestSuite)) +} + +type ClientTestSuite struct { + testsuite.E2ETestSuite +} + +// Status queries the current status of the client +func (s *ClientTestSuite) Status(ctx context.Context, chain ibc.Chain, clientID string) (string, error) { + queryClient := s.GetChainGRCPClients(chain).ClientQueryClient + res, err := queryClient.ClientStatus(ctx, &clienttypes.QueryClientStatusRequest{ + ClientId: clientID, + }) + if err != nil { + return "", err + } + + return res.Status, nil +} + +func (s *ClientTestSuite) TestClientUpdateProposal_Succeeds() { + t := s.T() + ctx := context.TODO() + + var ( + pathName string + relayer ibc.Relayer + subjectClientID string + substituteClientID string + // set the trusting period to a value which will still be valid upon client creation, but invalid before the first update + badTrustingPeriod = time.Duration(time.Second * 10) + ) + + t.Run("create substitute client with correct trusting period", func(t *testing.T) { + relayer, _ = s.SetupChainsRelayerAndChannel(ctx) + + // TODO: update when client identifier created is accessible + // currently assumes first client is 07-tendermint-0 + substituteClientID = clienttypes.FormatClientIdentifier(ibcexported.Tendermint, 0) + + // TODO: replace with better handling of path names + pathName = fmt.Sprintf("%s-path-%d", s.T().Name(), 0) + pathName = strings.ReplaceAll(pathName, "/", "-") + }) + + chainA, chainB := s.GetChains() + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + + t.Run("create subject client with bad trusting period", func(t *testing.T) { + createClientOptions := ibc.CreateClientOptions{ + TrustingPeriod: badTrustingPeriod.String(), + } + + s.SetupClients(ctx, relayer, createClientOptions) + + // TODO: update when client identifier created is accessible + // currently assumes second client is 07-tendermint-1 + subjectClientID = clienttypes.FormatClientIdentifier(ibcexported.Tendermint, 1) + }) + + time.Sleep(badTrustingPeriod) + + t.Run("update substitute client", func(t *testing.T) { + s.UpdateClients(ctx, relayer, pathName) + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + t.Run("check status of each client", func(t *testing.T) { + t.Run("substitute should be active", func(t *testing.T) { + status, err := s.Status(ctx, chainA, substituteClientID) + s.Require().NoError(err) + s.Require().Equal(ibcexported.Active.String(), status) + }) + + t.Run("subject should be expired", func(t *testing.T) { + status, err := s.Status(ctx, chainA, subjectClientID) + s.Require().NoError(err) + s.Require().Equal(ibcexported.Expired.String(), status) + }) + }) + + t.Run("pass client update proposal", func(t *testing.T) { + proposal := clienttypes.NewClientUpdateProposal(ibctesting.Title, ibctesting.Description, subjectClientID, substituteClientID) + s.ExecuteGovProposal(ctx, chainA, chainAWallet, proposal) + }) + + t.Run("check status of each client", func(t *testing.T) { + t.Run("substitute should be active", func(t *testing.T) { + status, err := s.Status(ctx, chainA, substituteClientID) + s.Require().NoError(err) + s.Require().Equal(ibcexported.Active.String(), status) + }) + + t.Run("subject should be active", func(t *testing.T) { + status, err := s.Status(ctx, chainA, subjectClientID) + s.Require().NoError(err) + s.Require().Equal(ibcexported.Active.String(), status) + }) + }) +} + +func (s *ClientTestSuite) TestClient_Update_Misbehaviour() { + t := s.T() + ctx := context.TODO() + + var ( + trustedHeight clienttypes.Height + latestHeight clienttypes.Height + clientState ibcexported.ClientState + header testsuite.Header + signers []tmtypes.PrivValidator + validatorSet []*tmtypes.Validator + maliciousHeader *ibctm.Header + err error + ) + + relayer, _ := s.SetupChainsRelayerAndChannel(ctx) + chainA, chainB := s.GetChains() + + s.Require().NoError(test.WaitForBlocks(ctx, 10, chainA, chainB)) + + t.Run("update clients", func(t *testing.T) { + err := relayer.UpdateClients(ctx, s.GetRelayerExecReporter(), s.GetPathName(0)) + s.Require().NoError(err) + + clientState, err = s.QueryClientState(ctx, chainA, ibctesting.FirstClientID) + s.Require().NoError(err) + }) + + t.Run("fetch trusted height", func(t *testing.T) { + tmClientState, ok := clientState.(*ibctm.ClientState) + s.Require().True(ok) + + trustedHeight, ok = tmClientState.GetLatestHeight().(clienttypes.Height) + s.Require().True(ok) + }) + + t.Run("update clients", func(t *testing.T) { + err := relayer.UpdateClients(ctx, s.GetRelayerExecReporter(), s.GetPathName(0)) + s.Require().NoError(err) + + clientState, err = s.QueryClientState(ctx, chainA, ibctesting.FirstClientID) + s.Require().NoError(err) + }) + + t.Run("fetch client state latest height", func(t *testing.T) { + tmClientState, ok := clientState.(*ibctm.ClientState) + s.Require().True(ok) + + latestHeight, ok = tmClientState.GetLatestHeight().(clienttypes.Height) + s.Require().True(ok) + }) + + t.Run("create validator set", func(t *testing.T) { + var validators []*tmservice.Validator + + t.Run("fetch block header at latest client state height", func(t *testing.T) { + header, err = s.GetBlockHeaderByHeight(ctx, chainB, latestHeight.GetRevisionHeight()) + s.Require().NoError(err) + }) + + t.Run("get validators at latest height", func(t *testing.T) { + validators, err = s.GetValidatorSetByHeight(ctx, chainB, latestHeight.GetRevisionHeight()) + s.Require().NoError(err) + }) + + t.Run("extract validator private keys", func(t *testing.T) { + privateKeys := s.extractChainPrivateKeys(ctx, chainB) + for i, pv := range privateKeys { + pubKey, err := pv.GetPubKey() + s.Require().NoError(err) + + validator := tmtypes.NewValidator(pubKey, validators[i].VotingPower) + + validatorSet = append(validatorSet, validator) + signers = append(signers, pv) + } + }) + }) + + t.Run("create malicious header", func(t *testing.T) { + valSet := tmtypes.NewValidatorSet(validatorSet) + maliciousHeader, err = createMaliciousTMHeader(chainB.Config().ChainID, int64(latestHeight.GetRevisionHeight()), trustedHeight, + header.GetTime(), valSet, valSet, signers, header) + s.Require().NoError(err) + }) + + t.Run("update client with duplicate misbehaviour header", func(t *testing.T) { + rlyWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + msgUpdateClient, err := clienttypes.NewMsgUpdateClient(ibctesting.FirstClientID, maliciousHeader, rlyWallet.FormattedAddress()) + s.Require().NoError(err) + + txResp, err := s.BroadcastMessages(ctx, chainA, rlyWallet, msgUpdateClient) + s.Require().NoError(err) + s.AssertValidTxResponse(txResp) + }) + + t.Run("ensure client status is frozen", func(t *testing.T) { + status, err := s.QueryClientStatus(ctx, chainA, ibctesting.FirstClientID) + s.Require().NoError(err) + s.Require().Equal(ibcexported.Frozen.String(), status) + }) +} + +// extractChainPrivateKeys returns a slice of tmtypes.PrivValidator which hold the private keys for all validator +// nodes for a given chain. +func (s *ClientTestSuite) extractChainPrivateKeys(ctx context.Context, chain *cosmos.CosmosChain) []tmtypes.PrivValidator { + testContainers, err := dockerutil.GetTestContainers(s.T(), ctx, s.DockerClient) + s.Require().NoError(err) + + var filePvs []privval.FilePVKey + var pvs []tmtypes.PrivValidator + for _, container := range testContainers { + isNodeForDifferentChain := !strings.Contains(container.Names[0], chain.Config().ChainID) + isFullNode := strings.Contains(container.Names[0], fmt.Sprintf("%s-fn", chain.Config().ChainID)) + if isNodeForDifferentChain || isFullNode { + continue + } + + validatorPrivKey := fmt.Sprintf("/var/cosmos-chain/%s/config/priv_validator_key.json", chain.Config().Name) + privKeyFileContents, err := dockerutil.GetFileContentsFromContainer(ctx, s.DockerClient, container.ID, validatorPrivKey) + s.Require().NoError(err) + + var filePV privval.FilePVKey + err = tmjson.Unmarshal(privKeyFileContents, &filePV) + s.Require().NoError(err) + filePvs = append(filePvs, filePV) + } + + // We sort by address as GetValidatorSetByHeight also sorts by address. When iterating over them, the index + // will correspond to the correct ibcmock.PV. + sort.SliceStable(filePvs, func(i, j int) bool { + return filePvs[i].Address.String() < filePvs[j].Address.String() + }) + + for _, filePV := range filePvs { + pvs = append(pvs, &ibcmock.PV{ + PrivKey: &ed25519.PrivKey{Key: filePV.PrivKey.Bytes()}, + }) + } + + return pvs +} + +// createMaliciousTMHeader creates a header with the provided trusted height with an invalid app hash. +func createMaliciousTMHeader(chainID string, blockHeight int64, trustedHeight clienttypes.Height, timestamp time.Time, tmValSet, tmTrustedVals *tmtypes.ValidatorSet, signers []tmtypes.PrivValidator, oldHeader testsuite.Header) (*ibctm.Header, error) { + tmHeader := tmtypes.Header{ + Version: tmprotoversion.Consensus{Block: tmversion.BlockProtocol, App: 2}, + ChainID: chainID, + Height: blockHeight, + Time: timestamp, + LastBlockID: ibctesting.MakeBlockID(make([]byte, tmhash.Size), 10_000, make([]byte, tmhash.Size)), + LastCommitHash: oldHeader.GetLastCommitHash(), + ValidatorsHash: tmValSet.Hash(), + NextValidatorsHash: tmValSet.Hash(), + DataHash: tmhash.Sum([]byte(invalidHashValue)), + ConsensusHash: tmhash.Sum([]byte(invalidHashValue)), + AppHash: tmhash.Sum([]byte(invalidHashValue)), + LastResultsHash: tmhash.Sum([]byte(invalidHashValue)), + EvidenceHash: tmhash.Sum([]byte(invalidHashValue)), + ProposerAddress: tmValSet.Proposer.Address, //nolint:staticcheck + } + + hhash := tmHeader.Hash() + blockID := ibctesting.MakeBlockID(hhash, 3, tmhash.Sum([]byte(invalidHashValue))) + voteSet := tmtypes.NewVoteSet(chainID, blockHeight, 1, tmproto.PrecommitType, tmValSet) + + commit, err := tmtypes.MakeCommit(blockID, blockHeight, 1, voteSet, signers, timestamp) + if err != nil { + return nil, err + } + + signedHeader := &tmproto.SignedHeader{ + Header: tmHeader.ToProto(), + Commit: commit.ToProto(), + } + + valSet, err := tmValSet.ToProto() + if err != nil { + return nil, err + } + + trustedVals, err := tmTrustedVals.ToProto() + if err != nil { + return nil, err + } + + return &ibctm.Header{ + SignedHeader: signedHeader, + ValidatorSet: valSet, + TrustedHeight: trustedHeight, + TrustedValidators: trustedVals, + }, nil +} diff --git a/e2e/testsuite/grpc_query.go b/e2e/testsuite/grpc_query.go new file mode 100644 index 00000000000..a3f8db2a18d --- /dev/null +++ b/e2e/testsuite/grpc_query.go @@ -0,0 +1,342 @@ +package testsuite + +import ( + "context" + "fmt" + "sort" + "time" + + "github.com/cosmos/cosmos-sdk/client/grpc/tmservice" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/authz" + govtypesv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + govtypesv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" + grouptypes "github.com/cosmos/cosmos-sdk/x/group" + paramsproposaltypes "github.com/cosmos/cosmos-sdk/x/params/types/proposal" + intertxtypes "github.com/cosmos/interchain-accounts/x/inter-tx/types" + "github.com/strangelove-ventures/interchaintest/v7/chain/cosmos" + "github.com/strangelove-ventures/interchaintest/v7/ibc" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + controllertypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/controller/types" + feetypes "github.com/cosmos/ibc-go/v7/modules/apps/29-fee/types" + transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" +) + +// GRPCClients holds a reference to any GRPC clients that are needed by the tests. +// These should typically be used for query clients only. If we need to make changes, we should +// use E2ETestSuite.BroadcastMessages to broadcast transactions instead. +type GRPCClients struct { + ClientQueryClient clienttypes.QueryClient + ConnectionQueryClient connectiontypes.QueryClient + ChannelQueryClient channeltypes.QueryClient + TransferQueryClient transfertypes.QueryClient + FeeQueryClient feetypes.QueryClient + ICAQueryClient controllertypes.QueryClient + InterTxQueryClient intertxtypes.QueryClient + + // SDK query clients + GovQueryClient govtypesv1beta1.QueryClient + GovQueryClientV1 govtypesv1.QueryClient + GroupsQueryClient grouptypes.QueryClient + ParamsQueryClient paramsproposaltypes.QueryClient + AuthQueryClient authtypes.QueryClient + AuthZQueryClient authz.QueryClient + + ConsensusServiceClient tmservice.ServiceClient +} + +// InitGRPCClients establishes GRPC clients with the given chain. +// The created GRPCClients can be retrieved with GetChainGRCPClients. +func (s *E2ETestSuite) InitGRPCClients(chain *cosmos.CosmosChain) { + // Create a connection to the gRPC server. + grpcConn, err := grpc.Dial( + chain.GetHostGRPCAddress(), + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + s.Require().NoError(err) + s.T().Cleanup(func() { + if err := grpcConn.Close(); err != nil { + s.T().Logf("failed closing GRPC connection to chain %s: %s", chain.Config().ChainID, err) + } + }) + + if s.grpcClients == nil { + s.grpcClients = make(map[string]GRPCClients) + } + + s.grpcClients[chain.Config().ChainID] = GRPCClients{ + ClientQueryClient: clienttypes.NewQueryClient(grpcConn), + ChannelQueryClient: channeltypes.NewQueryClient(grpcConn), + TransferQueryClient: transfertypes.NewQueryClient(grpcConn), + FeeQueryClient: feetypes.NewQueryClient(grpcConn), + ICAQueryClient: controllertypes.NewQueryClient(grpcConn), + InterTxQueryClient: intertxtypes.NewQueryClient(grpcConn), + GovQueryClient: govtypesv1beta1.NewQueryClient(grpcConn), + GovQueryClientV1: govtypesv1.NewQueryClient(grpcConn), + GroupsQueryClient: grouptypes.NewQueryClient(grpcConn), + ParamsQueryClient: paramsproposaltypes.NewQueryClient(grpcConn), + AuthQueryClient: authtypes.NewQueryClient(grpcConn), + AuthZQueryClient: authz.NewQueryClient(grpcConn), + ConsensusServiceClient: tmservice.NewServiceClient(grpcConn), + } +} + +// Header defines an interface which is implemented by both the sdk block header and the cometbft Block Header. +// this interfaces allows us to use the same function to fetch the block header for both chains. +type Header interface { + GetTime() time.Time + GetLastCommitHash() []byte +} + +// QueryClientState queries the client state on the given chain for the provided clientID. +func (s *E2ETestSuite) QueryClientState(ctx context.Context, chain ibc.Chain, clientID string) (ibcexported.ClientState, error) { + queryClient := s.GetChainGRCPClients(chain).ClientQueryClient + res, err := queryClient.ClientState(ctx, &clienttypes.QueryClientStateRequest{ + ClientId: clientID, + }) + if err != nil { + return nil, err + } + + cfg := EncodingConfig() + var clientState ibcexported.ClientState + if err := cfg.InterfaceRegistry.UnpackAny(res.ClientState, &clientState); err != nil { + return nil, err + } + + return clientState, nil +} + +// QueryClientStatus queries the status of the client by clientID +func (s *E2ETestSuite) QueryClientStatus(ctx context.Context, chain ibc.Chain, clientID string) (string, error) { + queryClient := s.GetChainGRCPClients(chain).ClientQueryClient + res, err := queryClient.ClientStatus(ctx, &clienttypes.QueryClientStatusRequest{ + ClientId: clientID, + }) + if err != nil { + return "", err + } + + return res.Status, nil +} + +// QueryConnection queries the connection end using the given chain and connection id. +func (s *E2ETestSuite) QueryConnection(ctx context.Context, chain ibc.Chain, connectionID string) (connectiontypes.ConnectionEnd, error) { + queryClient := s.GetChainGRCPClients(chain).ConnectionQueryClient + res, err := queryClient.Connection(ctx, &connectiontypes.QueryConnectionRequest{ + ConnectionId: connectionID, + }) + if err != nil { + return connectiontypes.ConnectionEnd{}, err + } + + return *res.Connection, nil +} + +// QueryChannel queries the channel on a given chain for the provided portID and channelID +func (s *E2ETestSuite) QueryChannel(ctx context.Context, chain ibc.Chain, portID, channelID string) (channeltypes.Channel, error) { + queryClient := s.GetChainGRCPClients(chain).ChannelQueryClient + res, err := queryClient.Channel(ctx, &channeltypes.QueryChannelRequest{ + PortId: portID, + ChannelId: channelID, + }) + if err != nil { + return channeltypes.Channel{}, err + } + + return *res.Channel, nil +} + +// QueryPacketCommitment queries the packet commitment on the given chain for the provided channel and sequence. +func (s *E2ETestSuite) QueryPacketCommitment(ctx context.Context, chain ibc.Chain, portID, channelID string, sequence uint64) ([]byte, error) { + queryClient := s.GetChainGRCPClients(chain).ChannelQueryClient + res, err := queryClient.PacketCommitment(ctx, &channeltypes.QueryPacketCommitmentRequest{ + PortId: portID, + ChannelId: channelID, + Sequence: sequence, + }) + if err != nil { + return nil, err + } + return res.Commitment, nil +} + +// QueryTotalEscrowForDenom queries the total amount of tokens in escrow for a denom +func (s *E2ETestSuite) QueryTotalEscrowForDenom(ctx context.Context, chain ibc.Chain, denom string) (sdk.Coin, error) { + queryClient := s.GetChainGRCPClients(chain).TransferQueryClient + res, err := queryClient.TotalEscrowForDenom(ctx, &transfertypes.QueryTotalEscrowForDenomRequest{ + Denom: denom, + }) + if err != nil { + return sdk.Coin{}, err + } + + return res.Amount, nil +} + +// QueryInterchainAccount queries the interchain account for the given owner and connectionID. +func (s *E2ETestSuite) QueryInterchainAccount(ctx context.Context, chain ibc.Chain, owner, connectionID string) (string, error) { + queryClient := s.GetChainGRCPClients(chain).ICAQueryClient + res, err := queryClient.InterchainAccount(ctx, &controllertypes.QueryInterchainAccountRequest{ + Owner: owner, + ConnectionId: connectionID, + }) + if err != nil { + return "", err + } + return res.Address, nil +} + +// QueryInterchainAccountLegacy queries the interchain account for the given owner and connectionID using the intertx module. +func (s *E2ETestSuite) QueryInterchainAccountLegacy(ctx context.Context, chain ibc.Chain, owner, connectionID string) (string, error) { + queryClient := s.GetChainGRCPClients(chain).InterTxQueryClient + res, err := queryClient.InterchainAccount(ctx, &intertxtypes.QueryInterchainAccountRequest{ + Owner: owner, + ConnectionId: connectionID, + }) + if err != nil { + return "", err + } + + return res.InterchainAccountAddress, nil +} + +// QueryIncentivizedPacketsForChannel queries the incentivized packets on the specified channel. +func (s *E2ETestSuite) QueryIncentivizedPacketsForChannel( + ctx context.Context, + chain *cosmos.CosmosChain, + portId, + channelId string, +) ([]*feetypes.IdentifiedPacketFees, error) { + queryClient := s.GetChainGRCPClients(chain).FeeQueryClient + res, err := queryClient.IncentivizedPacketsForChannel(ctx, &feetypes.QueryIncentivizedPacketsForChannelRequest{ + PortId: portId, + ChannelId: channelId, + }) + if err != nil { + return nil, err + } + return res.IncentivizedPackets, err +} + +// QueryCounterPartyPayee queries the counterparty payee of the given chain and relayer address on the specified channel. +func (s *E2ETestSuite) QueryCounterPartyPayee(ctx context.Context, chain ibc.Chain, relayerAddress, channelID string) (string, error) { + queryClient := s.GetChainGRCPClients(chain).FeeQueryClient + res, err := queryClient.CounterpartyPayee(ctx, &feetypes.QueryCounterpartyPayeeRequest{ + ChannelId: channelID, + Relayer: relayerAddress, + }) + if err != nil { + return "", err + } + return res.CounterpartyPayee, nil +} + +// QueryProposal queries the governance proposal on the given chain with the given proposal ID. +func (s *E2ETestSuite) QueryProposal(ctx context.Context, chain ibc.Chain, proposalID uint64) (govtypesv1beta1.Proposal, error) { + queryClient := s.GetChainGRCPClients(chain).GovQueryClient + res, err := queryClient.Proposal(ctx, &govtypesv1beta1.QueryProposalRequest{ + ProposalId: proposalID, + }) + if err != nil { + return govtypesv1beta1.Proposal{}, err + } + + return res.Proposal, nil +} + +func (s *E2ETestSuite) QueryProposalV1(ctx context.Context, chain ibc.Chain, proposalID uint64) (govtypesv1.Proposal, error) { + queryClient := s.GetChainGRCPClients(chain).GovQueryClientV1 + res, err := queryClient.Proposal(ctx, &govtypesv1.QueryProposalRequest{ + ProposalId: proposalID, + }) + if err != nil { + return govtypesv1.Proposal{}, err + } + + return *res.Proposal, nil +} + +// GetBlockHeaderByHeight fetches the block header at a given height. +func (s *E2ETestSuite) GetBlockHeaderByHeight(ctx context.Context, chain ibc.Chain, height uint64) (Header, error) { + tmService := s.GetChainGRCPClients(chain).ConsensusServiceClient + res, err := tmService.GetBlockByHeight(ctx, &tmservice.GetBlockByHeightRequest{ + Height: int64(height), + }) + if err != nil { + return nil, err + } + + // Clean up when v6 is not supported, see: https://github.com/cosmos/ibc-go/issues/3540 + // versions newer than 0.47 SDK use the SdkBlock field while versions older + // than 0.47 SDK, which do not have the SdkBlock field, use the Block field. + if res.SdkBlock != nil { + return &res.SdkBlock.Header, nil + } + return &res.Block.Header, nil +} + +// GetValidatorSetByHeight returns the validators of the given chain at the specified height. The returned validators +// are sorted by address. +func (s *E2ETestSuite) GetValidatorSetByHeight(ctx context.Context, chain ibc.Chain, height uint64) ([]*tmservice.Validator, error) { + tmService := s.GetChainGRCPClients(chain).ConsensusServiceClient + res, err := tmService.GetValidatorSetByHeight(ctx, &tmservice.GetValidatorSetByHeightRequest{ + Height: int64(height), + }) + if err != nil { + return nil, err + } + + sort.SliceStable(res.Validators, func(i, j int) bool { + return res.Validators[i].Address < res.Validators[j].Address + }) + + return res.Validators, nil +} + +// QueryModuleAccountAddress returns the sdk.AccAddress of a given module name. +func (s *E2ETestSuite) QueryModuleAccountAddress(ctx context.Context, moduleName string, chain *cosmos.CosmosChain) (sdk.AccAddress, error) { + authClient := s.GetChainGRCPClients(chain).AuthQueryClient + + resp, err := authClient.ModuleAccountByName(ctx, &authtypes.QueryModuleAccountByNameRequest{ + Name: moduleName, + }) + if err != nil { + return nil, err + } + + cfg := EncodingConfig() + + var account authtypes.AccountI + if err := cfg.InterfaceRegistry.UnpackAny(resp.Account, &account); err != nil { + return nil, err + } + moduleAccount, ok := account.(authtypes.ModuleAccountI) + if !ok { + return nil, fmt.Errorf("failed to cast account: %T as ModuleAccount", moduleAccount) + } + + return moduleAccount.GetAddress(), nil +} + +// QueryGranterGrants returns all GrantAuthorizations for the given granterAddress. +func (s *E2ETestSuite) QueryGranterGrants(ctx context.Context, chain *cosmos.CosmosChain, granterAddress string) ([]*authz.GrantAuthorization, error) { + authzClient := s.GetChainGRCPClients(chain).AuthZQueryClient + queryRequest := &authz.QueryGranterGrantsRequest{ + Granter: granterAddress, + } + + grants, err := authzClient.GranterGrants(ctx, queryRequest) + if err != nil { + return nil, err + } + + return grants.Grants, nil +} diff --git a/modules/apps/transfer/types/transfer_authorization.go b/modules/apps/transfer/types/transfer_authorization.go index fed33b0a909..0480be73f19 100644 --- a/modules/apps/transfer/types/transfer_authorization.go +++ b/modules/apps/transfer/types/transfer_authorization.go @@ -1,10 +1,21 @@ package types import ( +<<<<<<< HEAD +======= + "math/big" + + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" +>>>>>>> 7e6eb4c6 (imp: represent unlimited approvals with MaxUint256 value (#3454)) sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/authz" +<<<<<<< HEAD +======= + ibcerrors "github.com/cosmos/ibc-go/v7/internal/errors" +>>>>>>> 7e6eb4c6 (imp: represent unlimited approvals with MaxUint256 value (#3454)) channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" host "github.com/cosmos/ibc-go/v7/modules/core/24-host" ) @@ -13,6 +24,9 @@ const gasCostPerIteration = uint64(10) var _ authz.Authorization = &TransferAuthorization{} +// maxUint256 is the maximum value for a 256 bit unsigned integer. +var maxUint256 = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1)) + // NewTransferAuthorization creates a new TransferAuthorization object. func NewTransferAuthorization(allocations ...Allocation) *TransferAuthorization { return &TransferAuthorization{ @@ -33,10 +47,35 @@ func (a TransferAuthorization) Accept(ctx sdk.Context, msg sdk.Msg) (authz.Accep } for index, allocation := range a.Allocations { +<<<<<<< HEAD if allocation.SourceChannel == msgTransfer.SourceChannel && allocation.SourcePort == msgTransfer.SourcePort { limitLeft, isNegative := allocation.SpendLimit.SafeSub(msgTransfer.Token) if isNegative { return authz.AcceptResponse{}, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "requested amount is more than spend limit") +======= + if !(allocation.SourceChannel == msgTransfer.SourceChannel && allocation.SourcePort == msgTransfer.SourcePort) { + continue + } + + if !isAllowedAddress(ctx, msgTransfer.Receiver, allocation.AllowList) { + return authz.AcceptResponse{}, errorsmod.Wrap(ibcerrors.ErrInvalidAddress, "not allowed receiver address for transfer") + } + + // If the spend limit is set to the MaxUint256 sentinel value, do not subtract the amount from the spend limit. + if allocation.SpendLimit.AmountOf(msgTransfer.Token.Denom).Equal(UnboundedSpendLimit()) { + return authz.AcceptResponse{Accept: true, Delete: false, Updated: &a}, nil + } + + limitLeft, isNegative := allocation.SpendLimit.SafeSub(msgTransfer.Token) + if isNegative { + return authz.AcceptResponse{}, errorsmod.Wrapf(ibcerrors.ErrInsufficientFunds, "requested amount is more than spend limit") + } + + if limitLeft.IsZero() { + a.Allocations = append(a.Allocations[:index], a.Allocations[index+1:]...) + if len(a.Allocations) == 0 { + return authz.AcceptResponse{Accept: true, Delete: true}, nil +>>>>>>> 7e6eb4c6 (imp: represent unlimited approvals with MaxUint256 value (#3454)) } if !isAllowedAddress(ctx, msgTransfer.Receiver, allocation.AllowList) { @@ -64,7 +103,12 @@ func (a TransferAuthorization) Accept(ctx sdk.Context, msg sdk.Msg) (authz.Accep }}, nil } } +<<<<<<< HEAD return authz.AcceptResponse{}, sdkerrors.Wrapf(sdkerrors.ErrNotFound, "requested port and channel allocation does not exist") +======= + + return authz.AcceptResponse{}, errorsmod.Wrapf(ibcerrors.ErrNotFound, "requested port and channel allocation does not exist") +>>>>>>> 7e6eb4c6 (imp: represent unlimited approvals with MaxUint256 value (#3454)) } // ValidateBasic implements Authorization.ValidateBasic. @@ -125,3 +169,12 @@ func isAllowedAddress(ctx sdk.Context, receiver string, allowedAddrs []string) b } return false } + +// UnboundedSpendLimit returns the sentinel value that can be used +// as the amount for a denomination's spend limit for which spend limit updating +// should be disabled. Please note that using this sentinel value means that a grantee +// will be granted the privilege to do ICS20 token transfers for the total amount +// of the denomination available at the granter's account. +func UnboundedSpendLimit() sdkmath.Int { + return sdk.NewIntFromBigInt(maxUint256) +} diff --git a/modules/apps/transfer/types/transfer_authorization_test.go b/modules/apps/transfer/types/transfer_authorization_test.go index f0f7f3ab0b9..39d7a827c38 100644 --- a/modules/apps/transfer/types/transfer_authorization_test.go +++ b/modules/apps/transfer/types/transfer_authorization_test.go @@ -3,7 +3,6 @@ package types_test import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/authz" - "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" ibctesting "github.com/cosmos/ibc-go/v7/testing" "github.com/cosmos/ibc-go/v7/testing/mock" @@ -86,6 +85,48 @@ func (suite *TypesTestSuite) TestTransferAuthorizationAccept() { suite.Require().Len(updatedAuthz.Allocations, 1) }, }, + { + "success: with unlimited spend limit of max uint256", + func() { + transferAuthz.Allocations[0].SpendLimit = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, types.UnboundedSpendLimit())) + }, + func(res authz.AcceptResponse, err error) { + suite.Require().NoError(err) + + updatedTransferAuthz, ok := res.Updated.(*types.TransferAuthorization) + suite.Require().True(ok) + + remainder := updatedTransferAuthz.Allocations[0].SpendLimit.AmountOf(sdk.DefaultBondDenom) + suite.Require().True(types.UnboundedSpendLimit().Equal(remainder)) + }, + }, + { + "test multiple coins does not overspend", + func() { + transferAuthz.Allocations[0].SpendLimit = transferAuthz.Allocations[0].SpendLimit.Add( + sdk.NewCoins( + sdk.NewCoin("test-denom", sdk.NewInt(100)), + sdk.NewCoin("test-denom2", sdk.NewInt(100)), + )..., + ) + msgTransfer.Token = sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(50)) + }, + func(res authz.AcceptResponse, err error) { + suite.Require().NoError(err) + + updatedTransferAuthz, ok := res.Updated.(*types.TransferAuthorization) + suite.Require().True(ok) + + remainder := updatedTransferAuthz.Allocations[0].SpendLimit.AmountOf(sdk.DefaultBondDenom) + suite.Require().True(sdk.NewInt(50).Equal(remainder)) + + remainder = updatedTransferAuthz.Allocations[0].SpendLimit.AmountOf("test-denom") + suite.Require().True(sdk.NewInt(100).Equal(remainder)) + + remainder = updatedTransferAuthz.Allocations[0].SpendLimit.AmountOf("test-denom2") + suite.Require().True(sdk.NewInt(100).Equal(remainder)) + }, + }, { "no spend limit set for MsgTransfer port/channel", func() { @@ -190,6 +231,13 @@ func (suite *TypesTestSuite) TestTransferAuthorizationValidateBasic() { }, true, }, + { + "success: with unlimited spend limit of max uint256", + func() { + transferAuthz.Allocations[0].SpendLimit = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, types.UnboundedSpendLimit())) + }, + true, + }, { "empty allocations", func() { From c19462213fd49cf4a4dd421317001176e4f4141f Mon Sep 17 00:00:00 2001 From: Damian Nolan Date: Mon, 15 May 2023 17:38:16 +0200 Subject: [PATCH 2/2] resolving conflicts --- CHANGELOG.md | 4 - e2e/tests/core/client_test.go | 335 ----------------- e2e/testsuite/grpc_query.go | 342 ------------------ .../transfer/types/transfer_authorization.go | 59 +-- 4 files changed, 15 insertions(+), 725 deletions(-) delete mode 100644 e2e/tests/core/client_test.go delete mode 100644 e2e/testsuite/grpc_query.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 8efc622d3d0..04b7208050c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,12 +46,8 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements -<<<<<<< HEAD -======= -* (tests) [\#3138](https://github.com/cosmos/ibc-go/pull/3138) Support benchmarks and fuzz tests through `testing.TB`. * (apps/transfer) [\#3454](https://github.com/cosmos/ibc-go/pull/3454) Support transfer authorization unlimited spending when the max `uint256` value is provided as limit. ->>>>>>> 7e6eb4c6 (imp: represent unlimited approvals with MaxUint256 value (#3454)) ### Features ### Bug Fixes diff --git a/e2e/tests/core/client_test.go b/e2e/tests/core/client_test.go deleted file mode 100644 index bc1f34a1552..00000000000 --- a/e2e/tests/core/client_test.go +++ /dev/null @@ -1,335 +0,0 @@ -package e2e - -import ( - "context" - "fmt" - "sort" - "strings" - "testing" - "time" - - "github.com/cometbft/cometbft/crypto/tmhash" - tmjson "github.com/cometbft/cometbft/libs/json" - "github.com/cometbft/cometbft/privval" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" - tmprotoversion "github.com/cometbft/cometbft/proto/tendermint/version" - tmtypes "github.com/cometbft/cometbft/types" - tmversion "github.com/cometbft/cometbft/version" - "github.com/cosmos/cosmos-sdk/client/grpc/tmservice" - "github.com/strangelove-ventures/interchaintest/v7/chain/cosmos" - "github.com/strangelove-ventures/interchaintest/v7/ibc" - test "github.com/strangelove-ventures/interchaintest/v7/testutil" - "github.com/stretchr/testify/suite" - - "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - - "github.com/cosmos/ibc-go/e2e/dockerutil" - "github.com/cosmos/ibc-go/e2e/testsuite" - "github.com/cosmos/ibc-go/e2e/testvalues" - clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" - ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" - ibctm "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" - ibctesting "github.com/cosmos/ibc-go/v7/testing" - ibcmock "github.com/cosmos/ibc-go/v7/testing/mock" -) - -const ( - invalidHashValue = "invalid_hash" -) - -func TestClientTestSuite(t *testing.T) { - suite.Run(t, new(ClientTestSuite)) -} - -type ClientTestSuite struct { - testsuite.E2ETestSuite -} - -// Status queries the current status of the client -func (s *ClientTestSuite) Status(ctx context.Context, chain ibc.Chain, clientID string) (string, error) { - queryClient := s.GetChainGRCPClients(chain).ClientQueryClient - res, err := queryClient.ClientStatus(ctx, &clienttypes.QueryClientStatusRequest{ - ClientId: clientID, - }) - if err != nil { - return "", err - } - - return res.Status, nil -} - -func (s *ClientTestSuite) TestClientUpdateProposal_Succeeds() { - t := s.T() - ctx := context.TODO() - - var ( - pathName string - relayer ibc.Relayer - subjectClientID string - substituteClientID string - // set the trusting period to a value which will still be valid upon client creation, but invalid before the first update - badTrustingPeriod = time.Duration(time.Second * 10) - ) - - t.Run("create substitute client with correct trusting period", func(t *testing.T) { - relayer, _ = s.SetupChainsRelayerAndChannel(ctx) - - // TODO: update when client identifier created is accessible - // currently assumes first client is 07-tendermint-0 - substituteClientID = clienttypes.FormatClientIdentifier(ibcexported.Tendermint, 0) - - // TODO: replace with better handling of path names - pathName = fmt.Sprintf("%s-path-%d", s.T().Name(), 0) - pathName = strings.ReplaceAll(pathName, "/", "-") - }) - - chainA, chainB := s.GetChains() - chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) - - t.Run("create subject client with bad trusting period", func(t *testing.T) { - createClientOptions := ibc.CreateClientOptions{ - TrustingPeriod: badTrustingPeriod.String(), - } - - s.SetupClients(ctx, relayer, createClientOptions) - - // TODO: update when client identifier created is accessible - // currently assumes second client is 07-tendermint-1 - subjectClientID = clienttypes.FormatClientIdentifier(ibcexported.Tendermint, 1) - }) - - time.Sleep(badTrustingPeriod) - - t.Run("update substitute client", func(t *testing.T) { - s.UpdateClients(ctx, relayer, pathName) - }) - - s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") - - t.Run("check status of each client", func(t *testing.T) { - t.Run("substitute should be active", func(t *testing.T) { - status, err := s.Status(ctx, chainA, substituteClientID) - s.Require().NoError(err) - s.Require().Equal(ibcexported.Active.String(), status) - }) - - t.Run("subject should be expired", func(t *testing.T) { - status, err := s.Status(ctx, chainA, subjectClientID) - s.Require().NoError(err) - s.Require().Equal(ibcexported.Expired.String(), status) - }) - }) - - t.Run("pass client update proposal", func(t *testing.T) { - proposal := clienttypes.NewClientUpdateProposal(ibctesting.Title, ibctesting.Description, subjectClientID, substituteClientID) - s.ExecuteGovProposal(ctx, chainA, chainAWallet, proposal) - }) - - t.Run("check status of each client", func(t *testing.T) { - t.Run("substitute should be active", func(t *testing.T) { - status, err := s.Status(ctx, chainA, substituteClientID) - s.Require().NoError(err) - s.Require().Equal(ibcexported.Active.String(), status) - }) - - t.Run("subject should be active", func(t *testing.T) { - status, err := s.Status(ctx, chainA, subjectClientID) - s.Require().NoError(err) - s.Require().Equal(ibcexported.Active.String(), status) - }) - }) -} - -func (s *ClientTestSuite) TestClient_Update_Misbehaviour() { - t := s.T() - ctx := context.TODO() - - var ( - trustedHeight clienttypes.Height - latestHeight clienttypes.Height - clientState ibcexported.ClientState - header testsuite.Header - signers []tmtypes.PrivValidator - validatorSet []*tmtypes.Validator - maliciousHeader *ibctm.Header - err error - ) - - relayer, _ := s.SetupChainsRelayerAndChannel(ctx) - chainA, chainB := s.GetChains() - - s.Require().NoError(test.WaitForBlocks(ctx, 10, chainA, chainB)) - - t.Run("update clients", func(t *testing.T) { - err := relayer.UpdateClients(ctx, s.GetRelayerExecReporter(), s.GetPathName(0)) - s.Require().NoError(err) - - clientState, err = s.QueryClientState(ctx, chainA, ibctesting.FirstClientID) - s.Require().NoError(err) - }) - - t.Run("fetch trusted height", func(t *testing.T) { - tmClientState, ok := clientState.(*ibctm.ClientState) - s.Require().True(ok) - - trustedHeight, ok = tmClientState.GetLatestHeight().(clienttypes.Height) - s.Require().True(ok) - }) - - t.Run("update clients", func(t *testing.T) { - err := relayer.UpdateClients(ctx, s.GetRelayerExecReporter(), s.GetPathName(0)) - s.Require().NoError(err) - - clientState, err = s.QueryClientState(ctx, chainA, ibctesting.FirstClientID) - s.Require().NoError(err) - }) - - t.Run("fetch client state latest height", func(t *testing.T) { - tmClientState, ok := clientState.(*ibctm.ClientState) - s.Require().True(ok) - - latestHeight, ok = tmClientState.GetLatestHeight().(clienttypes.Height) - s.Require().True(ok) - }) - - t.Run("create validator set", func(t *testing.T) { - var validators []*tmservice.Validator - - t.Run("fetch block header at latest client state height", func(t *testing.T) { - header, err = s.GetBlockHeaderByHeight(ctx, chainB, latestHeight.GetRevisionHeight()) - s.Require().NoError(err) - }) - - t.Run("get validators at latest height", func(t *testing.T) { - validators, err = s.GetValidatorSetByHeight(ctx, chainB, latestHeight.GetRevisionHeight()) - s.Require().NoError(err) - }) - - t.Run("extract validator private keys", func(t *testing.T) { - privateKeys := s.extractChainPrivateKeys(ctx, chainB) - for i, pv := range privateKeys { - pubKey, err := pv.GetPubKey() - s.Require().NoError(err) - - validator := tmtypes.NewValidator(pubKey, validators[i].VotingPower) - - validatorSet = append(validatorSet, validator) - signers = append(signers, pv) - } - }) - }) - - t.Run("create malicious header", func(t *testing.T) { - valSet := tmtypes.NewValidatorSet(validatorSet) - maliciousHeader, err = createMaliciousTMHeader(chainB.Config().ChainID, int64(latestHeight.GetRevisionHeight()), trustedHeight, - header.GetTime(), valSet, valSet, signers, header) - s.Require().NoError(err) - }) - - t.Run("update client with duplicate misbehaviour header", func(t *testing.T) { - rlyWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) - msgUpdateClient, err := clienttypes.NewMsgUpdateClient(ibctesting.FirstClientID, maliciousHeader, rlyWallet.FormattedAddress()) - s.Require().NoError(err) - - txResp, err := s.BroadcastMessages(ctx, chainA, rlyWallet, msgUpdateClient) - s.Require().NoError(err) - s.AssertValidTxResponse(txResp) - }) - - t.Run("ensure client status is frozen", func(t *testing.T) { - status, err := s.QueryClientStatus(ctx, chainA, ibctesting.FirstClientID) - s.Require().NoError(err) - s.Require().Equal(ibcexported.Frozen.String(), status) - }) -} - -// extractChainPrivateKeys returns a slice of tmtypes.PrivValidator which hold the private keys for all validator -// nodes for a given chain. -func (s *ClientTestSuite) extractChainPrivateKeys(ctx context.Context, chain *cosmos.CosmosChain) []tmtypes.PrivValidator { - testContainers, err := dockerutil.GetTestContainers(s.T(), ctx, s.DockerClient) - s.Require().NoError(err) - - var filePvs []privval.FilePVKey - var pvs []tmtypes.PrivValidator - for _, container := range testContainers { - isNodeForDifferentChain := !strings.Contains(container.Names[0], chain.Config().ChainID) - isFullNode := strings.Contains(container.Names[0], fmt.Sprintf("%s-fn", chain.Config().ChainID)) - if isNodeForDifferentChain || isFullNode { - continue - } - - validatorPrivKey := fmt.Sprintf("/var/cosmos-chain/%s/config/priv_validator_key.json", chain.Config().Name) - privKeyFileContents, err := dockerutil.GetFileContentsFromContainer(ctx, s.DockerClient, container.ID, validatorPrivKey) - s.Require().NoError(err) - - var filePV privval.FilePVKey - err = tmjson.Unmarshal(privKeyFileContents, &filePV) - s.Require().NoError(err) - filePvs = append(filePvs, filePV) - } - - // We sort by address as GetValidatorSetByHeight also sorts by address. When iterating over them, the index - // will correspond to the correct ibcmock.PV. - sort.SliceStable(filePvs, func(i, j int) bool { - return filePvs[i].Address.String() < filePvs[j].Address.String() - }) - - for _, filePV := range filePvs { - pvs = append(pvs, &ibcmock.PV{ - PrivKey: &ed25519.PrivKey{Key: filePV.PrivKey.Bytes()}, - }) - } - - return pvs -} - -// createMaliciousTMHeader creates a header with the provided trusted height with an invalid app hash. -func createMaliciousTMHeader(chainID string, blockHeight int64, trustedHeight clienttypes.Height, timestamp time.Time, tmValSet, tmTrustedVals *tmtypes.ValidatorSet, signers []tmtypes.PrivValidator, oldHeader testsuite.Header) (*ibctm.Header, error) { - tmHeader := tmtypes.Header{ - Version: tmprotoversion.Consensus{Block: tmversion.BlockProtocol, App: 2}, - ChainID: chainID, - Height: blockHeight, - Time: timestamp, - LastBlockID: ibctesting.MakeBlockID(make([]byte, tmhash.Size), 10_000, make([]byte, tmhash.Size)), - LastCommitHash: oldHeader.GetLastCommitHash(), - ValidatorsHash: tmValSet.Hash(), - NextValidatorsHash: tmValSet.Hash(), - DataHash: tmhash.Sum([]byte(invalidHashValue)), - ConsensusHash: tmhash.Sum([]byte(invalidHashValue)), - AppHash: tmhash.Sum([]byte(invalidHashValue)), - LastResultsHash: tmhash.Sum([]byte(invalidHashValue)), - EvidenceHash: tmhash.Sum([]byte(invalidHashValue)), - ProposerAddress: tmValSet.Proposer.Address, //nolint:staticcheck - } - - hhash := tmHeader.Hash() - blockID := ibctesting.MakeBlockID(hhash, 3, tmhash.Sum([]byte(invalidHashValue))) - voteSet := tmtypes.NewVoteSet(chainID, blockHeight, 1, tmproto.PrecommitType, tmValSet) - - commit, err := tmtypes.MakeCommit(blockID, blockHeight, 1, voteSet, signers, timestamp) - if err != nil { - return nil, err - } - - signedHeader := &tmproto.SignedHeader{ - Header: tmHeader.ToProto(), - Commit: commit.ToProto(), - } - - valSet, err := tmValSet.ToProto() - if err != nil { - return nil, err - } - - trustedVals, err := tmTrustedVals.ToProto() - if err != nil { - return nil, err - } - - return &ibctm.Header{ - SignedHeader: signedHeader, - ValidatorSet: valSet, - TrustedHeight: trustedHeight, - TrustedValidators: trustedVals, - }, nil -} diff --git a/e2e/testsuite/grpc_query.go b/e2e/testsuite/grpc_query.go deleted file mode 100644 index a3f8db2a18d..00000000000 --- a/e2e/testsuite/grpc_query.go +++ /dev/null @@ -1,342 +0,0 @@ -package testsuite - -import ( - "context" - "fmt" - "sort" - "time" - - "github.com/cosmos/cosmos-sdk/client/grpc/tmservice" - sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/cosmos/cosmos-sdk/x/authz" - govtypesv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" - govtypesv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" - grouptypes "github.com/cosmos/cosmos-sdk/x/group" - paramsproposaltypes "github.com/cosmos/cosmos-sdk/x/params/types/proposal" - intertxtypes "github.com/cosmos/interchain-accounts/x/inter-tx/types" - "github.com/strangelove-ventures/interchaintest/v7/chain/cosmos" - "github.com/strangelove-ventures/interchaintest/v7/ibc" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" - - controllertypes "github.com/cosmos/ibc-go/v7/modules/apps/27-interchain-accounts/controller/types" - feetypes "github.com/cosmos/ibc-go/v7/modules/apps/29-fee/types" - transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" - clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" - connectiontypes "github.com/cosmos/ibc-go/v7/modules/core/03-connection/types" - channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" - ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" -) - -// GRPCClients holds a reference to any GRPC clients that are needed by the tests. -// These should typically be used for query clients only. If we need to make changes, we should -// use E2ETestSuite.BroadcastMessages to broadcast transactions instead. -type GRPCClients struct { - ClientQueryClient clienttypes.QueryClient - ConnectionQueryClient connectiontypes.QueryClient - ChannelQueryClient channeltypes.QueryClient - TransferQueryClient transfertypes.QueryClient - FeeQueryClient feetypes.QueryClient - ICAQueryClient controllertypes.QueryClient - InterTxQueryClient intertxtypes.QueryClient - - // SDK query clients - GovQueryClient govtypesv1beta1.QueryClient - GovQueryClientV1 govtypesv1.QueryClient - GroupsQueryClient grouptypes.QueryClient - ParamsQueryClient paramsproposaltypes.QueryClient - AuthQueryClient authtypes.QueryClient - AuthZQueryClient authz.QueryClient - - ConsensusServiceClient tmservice.ServiceClient -} - -// InitGRPCClients establishes GRPC clients with the given chain. -// The created GRPCClients can be retrieved with GetChainGRCPClients. -func (s *E2ETestSuite) InitGRPCClients(chain *cosmos.CosmosChain) { - // Create a connection to the gRPC server. - grpcConn, err := grpc.Dial( - chain.GetHostGRPCAddress(), - grpc.WithTransportCredentials(insecure.NewCredentials()), - ) - s.Require().NoError(err) - s.T().Cleanup(func() { - if err := grpcConn.Close(); err != nil { - s.T().Logf("failed closing GRPC connection to chain %s: %s", chain.Config().ChainID, err) - } - }) - - if s.grpcClients == nil { - s.grpcClients = make(map[string]GRPCClients) - } - - s.grpcClients[chain.Config().ChainID] = GRPCClients{ - ClientQueryClient: clienttypes.NewQueryClient(grpcConn), - ChannelQueryClient: channeltypes.NewQueryClient(grpcConn), - TransferQueryClient: transfertypes.NewQueryClient(grpcConn), - FeeQueryClient: feetypes.NewQueryClient(grpcConn), - ICAQueryClient: controllertypes.NewQueryClient(grpcConn), - InterTxQueryClient: intertxtypes.NewQueryClient(grpcConn), - GovQueryClient: govtypesv1beta1.NewQueryClient(grpcConn), - GovQueryClientV1: govtypesv1.NewQueryClient(grpcConn), - GroupsQueryClient: grouptypes.NewQueryClient(grpcConn), - ParamsQueryClient: paramsproposaltypes.NewQueryClient(grpcConn), - AuthQueryClient: authtypes.NewQueryClient(grpcConn), - AuthZQueryClient: authz.NewQueryClient(grpcConn), - ConsensusServiceClient: tmservice.NewServiceClient(grpcConn), - } -} - -// Header defines an interface which is implemented by both the sdk block header and the cometbft Block Header. -// this interfaces allows us to use the same function to fetch the block header for both chains. -type Header interface { - GetTime() time.Time - GetLastCommitHash() []byte -} - -// QueryClientState queries the client state on the given chain for the provided clientID. -func (s *E2ETestSuite) QueryClientState(ctx context.Context, chain ibc.Chain, clientID string) (ibcexported.ClientState, error) { - queryClient := s.GetChainGRCPClients(chain).ClientQueryClient - res, err := queryClient.ClientState(ctx, &clienttypes.QueryClientStateRequest{ - ClientId: clientID, - }) - if err != nil { - return nil, err - } - - cfg := EncodingConfig() - var clientState ibcexported.ClientState - if err := cfg.InterfaceRegistry.UnpackAny(res.ClientState, &clientState); err != nil { - return nil, err - } - - return clientState, nil -} - -// QueryClientStatus queries the status of the client by clientID -func (s *E2ETestSuite) QueryClientStatus(ctx context.Context, chain ibc.Chain, clientID string) (string, error) { - queryClient := s.GetChainGRCPClients(chain).ClientQueryClient - res, err := queryClient.ClientStatus(ctx, &clienttypes.QueryClientStatusRequest{ - ClientId: clientID, - }) - if err != nil { - return "", err - } - - return res.Status, nil -} - -// QueryConnection queries the connection end using the given chain and connection id. -func (s *E2ETestSuite) QueryConnection(ctx context.Context, chain ibc.Chain, connectionID string) (connectiontypes.ConnectionEnd, error) { - queryClient := s.GetChainGRCPClients(chain).ConnectionQueryClient - res, err := queryClient.Connection(ctx, &connectiontypes.QueryConnectionRequest{ - ConnectionId: connectionID, - }) - if err != nil { - return connectiontypes.ConnectionEnd{}, err - } - - return *res.Connection, nil -} - -// QueryChannel queries the channel on a given chain for the provided portID and channelID -func (s *E2ETestSuite) QueryChannel(ctx context.Context, chain ibc.Chain, portID, channelID string) (channeltypes.Channel, error) { - queryClient := s.GetChainGRCPClients(chain).ChannelQueryClient - res, err := queryClient.Channel(ctx, &channeltypes.QueryChannelRequest{ - PortId: portID, - ChannelId: channelID, - }) - if err != nil { - return channeltypes.Channel{}, err - } - - return *res.Channel, nil -} - -// QueryPacketCommitment queries the packet commitment on the given chain for the provided channel and sequence. -func (s *E2ETestSuite) QueryPacketCommitment(ctx context.Context, chain ibc.Chain, portID, channelID string, sequence uint64) ([]byte, error) { - queryClient := s.GetChainGRCPClients(chain).ChannelQueryClient - res, err := queryClient.PacketCommitment(ctx, &channeltypes.QueryPacketCommitmentRequest{ - PortId: portID, - ChannelId: channelID, - Sequence: sequence, - }) - if err != nil { - return nil, err - } - return res.Commitment, nil -} - -// QueryTotalEscrowForDenom queries the total amount of tokens in escrow for a denom -func (s *E2ETestSuite) QueryTotalEscrowForDenom(ctx context.Context, chain ibc.Chain, denom string) (sdk.Coin, error) { - queryClient := s.GetChainGRCPClients(chain).TransferQueryClient - res, err := queryClient.TotalEscrowForDenom(ctx, &transfertypes.QueryTotalEscrowForDenomRequest{ - Denom: denom, - }) - if err != nil { - return sdk.Coin{}, err - } - - return res.Amount, nil -} - -// QueryInterchainAccount queries the interchain account for the given owner and connectionID. -func (s *E2ETestSuite) QueryInterchainAccount(ctx context.Context, chain ibc.Chain, owner, connectionID string) (string, error) { - queryClient := s.GetChainGRCPClients(chain).ICAQueryClient - res, err := queryClient.InterchainAccount(ctx, &controllertypes.QueryInterchainAccountRequest{ - Owner: owner, - ConnectionId: connectionID, - }) - if err != nil { - return "", err - } - return res.Address, nil -} - -// QueryInterchainAccountLegacy queries the interchain account for the given owner and connectionID using the intertx module. -func (s *E2ETestSuite) QueryInterchainAccountLegacy(ctx context.Context, chain ibc.Chain, owner, connectionID string) (string, error) { - queryClient := s.GetChainGRCPClients(chain).InterTxQueryClient - res, err := queryClient.InterchainAccount(ctx, &intertxtypes.QueryInterchainAccountRequest{ - Owner: owner, - ConnectionId: connectionID, - }) - if err != nil { - return "", err - } - - return res.InterchainAccountAddress, nil -} - -// QueryIncentivizedPacketsForChannel queries the incentivized packets on the specified channel. -func (s *E2ETestSuite) QueryIncentivizedPacketsForChannel( - ctx context.Context, - chain *cosmos.CosmosChain, - portId, - channelId string, -) ([]*feetypes.IdentifiedPacketFees, error) { - queryClient := s.GetChainGRCPClients(chain).FeeQueryClient - res, err := queryClient.IncentivizedPacketsForChannel(ctx, &feetypes.QueryIncentivizedPacketsForChannelRequest{ - PortId: portId, - ChannelId: channelId, - }) - if err != nil { - return nil, err - } - return res.IncentivizedPackets, err -} - -// QueryCounterPartyPayee queries the counterparty payee of the given chain and relayer address on the specified channel. -func (s *E2ETestSuite) QueryCounterPartyPayee(ctx context.Context, chain ibc.Chain, relayerAddress, channelID string) (string, error) { - queryClient := s.GetChainGRCPClients(chain).FeeQueryClient - res, err := queryClient.CounterpartyPayee(ctx, &feetypes.QueryCounterpartyPayeeRequest{ - ChannelId: channelID, - Relayer: relayerAddress, - }) - if err != nil { - return "", err - } - return res.CounterpartyPayee, nil -} - -// QueryProposal queries the governance proposal on the given chain with the given proposal ID. -func (s *E2ETestSuite) QueryProposal(ctx context.Context, chain ibc.Chain, proposalID uint64) (govtypesv1beta1.Proposal, error) { - queryClient := s.GetChainGRCPClients(chain).GovQueryClient - res, err := queryClient.Proposal(ctx, &govtypesv1beta1.QueryProposalRequest{ - ProposalId: proposalID, - }) - if err != nil { - return govtypesv1beta1.Proposal{}, err - } - - return res.Proposal, nil -} - -func (s *E2ETestSuite) QueryProposalV1(ctx context.Context, chain ibc.Chain, proposalID uint64) (govtypesv1.Proposal, error) { - queryClient := s.GetChainGRCPClients(chain).GovQueryClientV1 - res, err := queryClient.Proposal(ctx, &govtypesv1.QueryProposalRequest{ - ProposalId: proposalID, - }) - if err != nil { - return govtypesv1.Proposal{}, err - } - - return *res.Proposal, nil -} - -// GetBlockHeaderByHeight fetches the block header at a given height. -func (s *E2ETestSuite) GetBlockHeaderByHeight(ctx context.Context, chain ibc.Chain, height uint64) (Header, error) { - tmService := s.GetChainGRCPClients(chain).ConsensusServiceClient - res, err := tmService.GetBlockByHeight(ctx, &tmservice.GetBlockByHeightRequest{ - Height: int64(height), - }) - if err != nil { - return nil, err - } - - // Clean up when v6 is not supported, see: https://github.com/cosmos/ibc-go/issues/3540 - // versions newer than 0.47 SDK use the SdkBlock field while versions older - // than 0.47 SDK, which do not have the SdkBlock field, use the Block field. - if res.SdkBlock != nil { - return &res.SdkBlock.Header, nil - } - return &res.Block.Header, nil -} - -// GetValidatorSetByHeight returns the validators of the given chain at the specified height. The returned validators -// are sorted by address. -func (s *E2ETestSuite) GetValidatorSetByHeight(ctx context.Context, chain ibc.Chain, height uint64) ([]*tmservice.Validator, error) { - tmService := s.GetChainGRCPClients(chain).ConsensusServiceClient - res, err := tmService.GetValidatorSetByHeight(ctx, &tmservice.GetValidatorSetByHeightRequest{ - Height: int64(height), - }) - if err != nil { - return nil, err - } - - sort.SliceStable(res.Validators, func(i, j int) bool { - return res.Validators[i].Address < res.Validators[j].Address - }) - - return res.Validators, nil -} - -// QueryModuleAccountAddress returns the sdk.AccAddress of a given module name. -func (s *E2ETestSuite) QueryModuleAccountAddress(ctx context.Context, moduleName string, chain *cosmos.CosmosChain) (sdk.AccAddress, error) { - authClient := s.GetChainGRCPClients(chain).AuthQueryClient - - resp, err := authClient.ModuleAccountByName(ctx, &authtypes.QueryModuleAccountByNameRequest{ - Name: moduleName, - }) - if err != nil { - return nil, err - } - - cfg := EncodingConfig() - - var account authtypes.AccountI - if err := cfg.InterfaceRegistry.UnpackAny(resp.Account, &account); err != nil { - return nil, err - } - moduleAccount, ok := account.(authtypes.ModuleAccountI) - if !ok { - return nil, fmt.Errorf("failed to cast account: %T as ModuleAccount", moduleAccount) - } - - return moduleAccount.GetAddress(), nil -} - -// QueryGranterGrants returns all GrantAuthorizations for the given granterAddress. -func (s *E2ETestSuite) QueryGranterGrants(ctx context.Context, chain *cosmos.CosmosChain, granterAddress string) ([]*authz.GrantAuthorization, error) { - authzClient := s.GetChainGRCPClients(chain).AuthZQueryClient - queryRequest := &authz.QueryGranterGrantsRequest{ - Granter: granterAddress, - } - - grants, err := authzClient.GranterGrants(ctx, queryRequest) - if err != nil { - return nil, err - } - - return grants.Grants, nil -} diff --git a/modules/apps/transfer/types/transfer_authorization.go b/modules/apps/transfer/types/transfer_authorization.go index 0480be73f19..240ce1ee11b 100644 --- a/modules/apps/transfer/types/transfer_authorization.go +++ b/modules/apps/transfer/types/transfer_authorization.go @@ -1,27 +1,18 @@ package types import ( -<<<<<<< HEAD -======= "math/big" errorsmod "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" ->>>>>>> 7e6eb4c6 (imp: represent unlimited approvals with MaxUint256 value (#3454)) sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/authz" -<<<<<<< HEAD -======= - ibcerrors "github.com/cosmos/ibc-go/v7/internal/errors" ->>>>>>> 7e6eb4c6 (imp: represent unlimited approvals with MaxUint256 value (#3454)) channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" host "github.com/cosmos/ibc-go/v7/modules/core/24-host" ) -const gasCostPerIteration = uint64(10) - var _ authz.Authorization = &TransferAuthorization{} // maxUint256 is the maximum value for a 256 bit unsigned integer. @@ -47,18 +38,12 @@ func (a TransferAuthorization) Accept(ctx sdk.Context, msg sdk.Msg) (authz.Accep } for index, allocation := range a.Allocations { -<<<<<<< HEAD - if allocation.SourceChannel == msgTransfer.SourceChannel && allocation.SourcePort == msgTransfer.SourcePort { - limitLeft, isNegative := allocation.SpendLimit.SafeSub(msgTransfer.Token) - if isNegative { - return authz.AcceptResponse{}, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "requested amount is more than spend limit") -======= if !(allocation.SourceChannel == msgTransfer.SourceChannel && allocation.SourcePort == msgTransfer.SourcePort) { continue } if !isAllowedAddress(ctx, msgTransfer.Receiver, allocation.AllowList) { - return authz.AcceptResponse{}, errorsmod.Wrap(ibcerrors.ErrInvalidAddress, "not allowed receiver address for transfer") + return authz.AcceptResponse{}, errorsmod.Wrap(sdkerrors.ErrInvalidAddress, "not allowed receiver address for transfer") } // If the spend limit is set to the MaxUint256 sentinel value, do not subtract the amount from the spend limit. @@ -68,47 +53,31 @@ func (a TransferAuthorization) Accept(ctx sdk.Context, msg sdk.Msg) (authz.Accep limitLeft, isNegative := allocation.SpendLimit.SafeSub(msgTransfer.Token) if isNegative { - return authz.AcceptResponse{}, errorsmod.Wrapf(ibcerrors.ErrInsufficientFunds, "requested amount is more than spend limit") + return authz.AcceptResponse{}, errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds, "requested amount is more than spend limit") } if limitLeft.IsZero() { a.Allocations = append(a.Allocations[:index], a.Allocations[index+1:]...) if len(a.Allocations) == 0 { return authz.AcceptResponse{Accept: true, Delete: true}, nil ->>>>>>> 7e6eb4c6 (imp: represent unlimited approvals with MaxUint256 value (#3454)) - } - - if !isAllowedAddress(ctx, msgTransfer.Receiver, allocation.AllowList) { - return authz.AcceptResponse{}, sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "not allowed address for transfer") - } - - if limitLeft.IsZero() { - a.Allocations = append(a.Allocations[:index], a.Allocations[index+1:]...) - if len(a.Allocations) == 0 { - return authz.AcceptResponse{Accept: true, Delete: true}, nil - } - return authz.AcceptResponse{Accept: true, Delete: false, Updated: &TransferAuthorization{ - Allocations: a.Allocations, - }}, nil - } - a.Allocations[index] = Allocation{ - SourcePort: allocation.SourcePort, - SourceChannel: allocation.SourceChannel, - SpendLimit: limitLeft, - AllowList: allocation.AllowList, } - return authz.AcceptResponse{Accept: true, Delete: false, Updated: &TransferAuthorization{ Allocations: a.Allocations, }}, nil } + a.Allocations[index] = Allocation{ + SourcePort: allocation.SourcePort, + SourceChannel: allocation.SourceChannel, + SpendLimit: limitLeft, + AllowList: allocation.AllowList, + } + + return authz.AcceptResponse{Accept: true, Delete: false, Updated: &TransferAuthorization{ + Allocations: a.Allocations, + }}, nil } -<<<<<<< HEAD - return authz.AcceptResponse{}, sdkerrors.Wrapf(sdkerrors.ErrNotFound, "requested port and channel allocation does not exist") -======= - return authz.AcceptResponse{}, errorsmod.Wrapf(ibcerrors.ErrNotFound, "requested port and channel allocation does not exist") ->>>>>>> 7e6eb4c6 (imp: represent unlimited approvals with MaxUint256 value (#3454)) + return authz.AcceptResponse{}, sdkerrors.Wrapf(sdkerrors.ErrNotFound, "requested port and channel allocation does not exist") } // ValidateBasic implements Authorization.ValidateBasic. @@ -161,6 +130,8 @@ func isAllowedAddress(ctx sdk.Context, receiver string, allowedAddrs []string) b return true } + gasCostPerIteration := ctx.KVGasConfig().IterNextCostFlat + for _, addr := range allowedAddrs { ctx.GasMeter().ConsumeGas(gasCostPerIteration, "transfer authorization") if addr == receiver {