Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ICS20 authz E2E happy path #2996

Merged
merged 9 commits into from
Jan 11, 2023
1 change: 1 addition & 0 deletions .github/workflows/e2e-manual-simd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ on:
- TestInterchainAccountsGroupsTestSuite
- TestInterchainAccountsGovTestSuite
- TestIncentivizedInterchainAccountsTestSuite
- TestAuthzTransferTestSuite
chain-image:
description: 'The image to use for chain A'
required: true
Expand Down
8 changes: 8 additions & 0 deletions e2e/testconfig/testconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"os"
"strings"

"github.com/cosmos/cosmos-sdk/codec"
simappparams "github.com/cosmos/cosmos-sdk/simapp/params"
Expand Down Expand Up @@ -129,6 +130,13 @@ func GetChainBTag() string {
return chainBTag
}

// IsCI returns true if the tests are running in CI, false is returned
// if the tests are running locally.
// Note: github actions passes a CI env value of true by default to all runners.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice 🎉

func IsCI() bool {
return strings.ToLower(os.Getenv("CI")) == "true"
}

// ChainOptions stores chain configurations for the chains that will be
// created for the tests. They can be modified by passing ChainOptionConfiguration
// to E2ETestSuite.GetChains.
Expand Down
142 changes: 142 additions & 0 deletions e2e/tests/transfer/authz_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package transfer

import (
"context"
"testing"

codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/authz"
test "github.com/strangelove-ventures/ibctest/v6/testutil"
"github.com/stretchr/testify/suite"

"github.com/cosmos/ibc-go/e2e/testsuite"
"github.com/cosmos/ibc-go/e2e/testvalues"
transfertypes "github.com/cosmos/ibc-go/v6/modules/apps/transfer/types"
)

func TestAuthzTransferTestSuite(t *testing.T) {
suite.Run(t, new(AuthzTransferTestSuite))
}

type AuthzTransferTestSuite struct {
testsuite.E2ETestSuite
}

func (suite *AuthzTransferTestSuite) TestAuthz_MsgTransfer_Succeeds() {
t := suite.T()
ctx := context.TODO()

relayer, channelA := suite.SetupChainsRelayerAndChannel(ctx, transferChannelOptions())
chainA, chainB := suite.GetChains()

chainADenom := chainA.Config().Denom

granterWallet := suite.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount)
granterAddress := granterWallet.Bech32Address(chainA.Config().Bech32Prefix)

granteeWallet := suite.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount)
granteeAddress := granteeWallet.Bech32Address(chainA.Config().Bech32Prefix)

receiverWallet := suite.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount)
receiverWalletAddress := receiverWallet.Bech32Address(chainB.Config().Bech32Prefix)

t.Run("start relayer", func(t *testing.T) {
suite.StartRelayer(relayer)
})

t.Run("broadcast MsgGrant", func(t *testing.T) {
transferAuth := transfertypes.TransferAuthorization{
Allocations: []transfertypes.Allocation{
{
SourcePort: channelA.PortID,
SourceChannel: channelA.ChannelID,
SpendLimit: sdk.NewCoins(sdk.NewCoin(chainADenom, sdk.NewInt(testvalues.StartingTokenAmount))),
AllowList: []string{receiverWalletAddress},
},
},
}

authAny, err := codectypes.NewAnyWithValue(&transferAuth)
suite.Require().NoError(err)

msgGrant := &authz.MsgGrant{
Granter: granterAddress,
Grantee: granteeAddress,
Grant: authz.Grant{
Authorization: authAny,
// no expiration
Expiration: nil,
},
}

resp, err := suite.BroadcastMessages(context.TODO(), chainA, granterWallet, msgGrant)
suite.AssertValidTxResponse(resp)
suite.Require().NoError(err)
})

t.Run("exec MsgTransfer", func(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wonder if we should reword this a little

Suggested change
t.Run("exec MsgTransfer", func(t *testing.T) {
t.Run("broadcast MsgExec for ibc MsgTransfer", func(t *testing.T) {

transferMsg := transfertypes.MsgTransfer{
SourcePort: channelA.PortID,
SourceChannel: channelA.ChannelID,
Token: testvalues.DefaultTransferAmount(chainADenom),
Sender: granterAddress,
Receiver: receiverWalletAddress,
TimeoutHeight: suite.GetTimeoutHeight(ctx, chainB),
}

transferAny, err := codectypes.NewAnyWithValue(&transferMsg)
suite.Require().NoError(err)

msgExec := &authz.MsgExec{
Grantee: granteeAddress,
Msgs: []*codectypes.Any{transferAny},
}

resp, err := suite.BroadcastMessages(context.TODO(), chainA, granteeWallet, msgExec)
suite.AssertValidTxResponse(resp)
suite.Require().NoError(err)
})

t.Run("verify granter wallet amount", func(t *testing.T) {
actualBalance, err := suite.GetChainANativeBalance(ctx, granterWallet)
suite.Require().NoError(err)

expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount
suite.Require().Equal(expected, actualBalance)
})

suite.Require().NoError(test.WaitForBlocks(context.TODO(), 10, chainB))

t.Run("verify receiver wallet amount", func(t *testing.T) {
chainBIBCToken := testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID)
actualBalance, err := chainB.GetBalance(ctx, receiverWalletAddress, chainBIBCToken.IBCDenom())
suite.Require().NoError(err)
suite.Require().Equal(testvalues.IBCTransferAmount, actualBalance)
})

t.Run("granter grant spend limit reduced", func(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we try to broadcast another MsgGrant to make sure it overwrites the first?

grantAuths, err := suite.QueryGranterGrants(ctx, chainA, granterAddress)

suite.Require().NoError(err)
suite.Require().Len(grantAuths, 1)
grantAuthorization := grantAuths[0]

transferAuth := suite.extractTransferAuthorizationFromGrantAuthorization(grantAuthorization)
expectedSpendLimit := sdk.NewCoins(sdk.NewCoin(chainADenom, sdk.NewInt(testvalues.StartingTokenAmount-testvalues.IBCTransferAmount)))
suite.Require().Equal(expectedSpendLimit, transferAuth.Allocations[0].SpendLimit)
})
}

// extractTransferAuthorizationFromGrantAuthorization extracts a TransferAuthorization from the given
// GrantAuthorization.
func (suite *AuthzTransferTestSuite) extractTransferAuthorizationFromGrantAuthorization(grantAuth *authz.GrantAuthorization) *transfertypes.TransferAuthorization {
cfg := testsuite.EncodingConfig()
var authorization authz.Authorization
err := cfg.InterfaceRegistry.UnpackAny(grantAuth.Authorization, &authorization)
suite.Require().NoError(err)

transferAuth, ok := authorization.(*transfertypes.TransferAuthorization)
suite.Require().True(ok)
return transferAuth
}
4 changes: 4 additions & 0 deletions e2e/testsuite/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package testsuite
import (
"github.com/cosmos/cosmos-sdk/codec"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/authz"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"

transfertypes "github.com/cosmos/ibc-go/v6/modules/apps/transfer/types"
simappparams "github.com/cosmos/ibc-go/v6/testing/simapp/params"
)

Expand All @@ -24,6 +26,8 @@ func codecAndEncodingConfig() (*codec.ProtoCodec, simappparams.EncodingConfig) {
banktypes.RegisterInterfaces(cfg.InterfaceRegistry)
govv1beta1.RegisterInterfaces(cfg.InterfaceRegistry)
authtypes.RegisterInterfaces(cfg.InterfaceRegistry)
authz.RegisterInterfaces(cfg.InterfaceRegistry)
transfertypes.RegisterInterfaces(cfg.InterfaceRegistry)
cdc := codec.NewProtoCodec(cfg.InterfaceRegistry)
return cdc, cfg
}
30 changes: 29 additions & 1 deletion e2e/testsuite/testsuite.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/cosmos/cosmos-sdk/client/tx"
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"
Expand Down Expand Up @@ -81,6 +82,7 @@ type GRPCClients struct {
GroupsQueryClient grouptypes.QueryClient
ParamsQueryClient paramsproposaltypes.QueryClient
AuthQueryClient authtypes.QueryClient
AuthZQueryClient authz.QueryClient
}

// path is a pairing of two chains which will be used in a test.
Expand Down Expand Up @@ -391,6 +393,7 @@ func (s *E2ETestSuite) InitGRPCClients(chain *cosmos.CosmosChain) {
GroupsQueryClient: grouptypes.NewQueryClient(grpcConn),
ParamsQueryClient: paramsproposaltypes.NewQueryClient(grpcConn),
AuthQueryClient: authtypes.NewQueryClient(grpcConn),
AuthZQueryClient: authz.NewQueryClient(grpcConn),
}
}

Expand Down Expand Up @@ -425,7 +428,7 @@ func (s *E2ETestSuite) createCosmosChains(chainOptions testconfig.ChainOptions)

logger := zaptest.NewLogger(s.T())

numValidators, numFullNodes := 4, 1
numValidators, numFullNodes := getValidatorsAndFullNodes()

chainA := cosmos.NewCosmosChain(s.T().Name(), *chainOptions.ChainAConfig, numValidators, numFullNodes, logger)
chainB := cosmos.NewCosmosChain(s.T().Name(), *chainOptions.ChainBConfig, numValidators, numFullNodes, logger)
Expand Down Expand Up @@ -539,7 +542,32 @@ func (s *E2ETestSuite) QueryModuleAccountAddress(ctx context.Context, moduleName
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
}

// GetIBCToken returns the denomination of the full token denom sent to the receiving channel
func GetIBCToken(fullTokenDenom string, portID, channelID string) transfertypes.DenomTrace {
return transfertypes.ParseDenomTrace(fmt.Sprintf("%s/%s/%s", portID, channelID, fullTokenDenom))
}

// getValidatorsAndFullNodes returns the number of validators and full nodes respectively that should be used for
// the test. If the test is running in CI, more nodes are used, when running locally a single node is used to
// use less resources and allow the tests to run faster.
func getValidatorsAndFullNodes() (int, int) {
if testconfig.IsCI() {
return 4, 1
}
return 1, 0
}
Comment on lines +568 to +573
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated QOL change to allow running tests locally to run faster.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes i love this!