mukan-ibc/e2e/tests/transfer/authz_test.go
Mukan Erkin Törük 88dd97a9f8
Some checks failed
CodeQL / Analyze (push) Waiting to run
golangci-lint / lint (push) Waiting to run
Tests / Code Coverage / build (amd64) (push) Waiting to run
Tests / Code Coverage / build (arm64) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[additional-args:-tags="test_e2e" name:e2e path:./e2e]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:08-wasm path:./modules/light-clients/08-wasm]) (push) Waiting to run
Tests / Code Coverage / unit-tests (map[name:ibc-go path:.]) (push) Waiting to run
Docker Build & Push Simapp (main) / docker-build (push) Has been cancelled
refactor: replace all github.com upstream refs with git.cw.tr/mukan-network
2026-05-11 03:36:22 +03:00

379 lines
12 KiB
Go

//go:build !test_e2e
package transfer
import (
"context"
"testing"
"github.com/cosmos/interchaintest/v10/ibc"
test "github.com/cosmos/interchaintest/v10/testutil"
testifysuite "github.com/stretchr/testify/suite"
sdkmath "cosmossdk.io/math"
codectypes "git.cw.tr/mukan-network/mukan-sdk/codec/types"
sdk "git.cw.tr/mukan-network/mukan-sdk/types"
sdkerrors "git.cw.tr/mukan-network/mukan-sdk/types/errors"
"git.cw.tr/mukan-network/mukan-sdk/x/authz"
"github.com/cosmos/ibc-go/e2e/testsuite"
"github.com/cosmos/ibc-go/e2e/testsuite/query"
"github.com/cosmos/ibc-go/e2e/testvalues"
transfertypes "git.cw.tr/mukan-network/mukan-ibc/modules/apps/transfer/types"
ibcerrors "git.cw.tr/mukan-network/mukan-ibc/modules/core/errors"
)
// compatibility:from_version: v7.10.0
func TestAuthzTransferTestSuite(t *testing.T) {
testifysuite.Run(t, new(AuthzTransferTestSuite))
}
type AuthzTransferTestSuite struct {
testsuite.E2ETestSuite
}
// SetupSuite sets up chains for the current test suite
func (s *AuthzTransferTestSuite) SetupSuite() {
s.SetupChains(context.TODO(), 2, nil)
}
func (s *AuthzTransferTestSuite) CreateAuthzTestPath(testName string) (ibc.Relayer, ibc.ChannelOutput) {
return s.CreatePaths(ibc.DefaultClientOpts(), s.TransferChannelOptions(), testName), s.GetChainAChannelForTest(testName)
}
// QueryGranterGrants returns all GrantAuthorizations for the given granterAddress.
func (*AuthzTransferTestSuite) QueryGranterGrants(ctx context.Context, chain ibc.Chain, granterAddress string) ([]*authz.GrantAuthorization, error) {
res, err := query.GRPCQuery[authz.QueryGranterGrantsResponse](ctx, chain, &authz.QueryGranterGrantsRequest{
Granter: granterAddress,
})
if err != nil {
return nil, err
}
return res.Grants, nil
}
func (s *AuthzTransferTestSuite) TestAuthz_MsgTransfer_Succeeds() {
t := s.T()
ctx := context.TODO()
testName := t.Name()
t.Parallel()
relayer, channelA := s.CreateAuthzTestPath(testName)
chainA, chainB := s.GetChains()
chainADenom := chainA.Config().Denom
granterWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount)
granterAddress := granterWallet.FormattedAddress()
granteeWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount)
granteeAddress := granteeWallet.FormattedAddress()
receiverWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount)
receiverWalletAddress := receiverWallet.FormattedAddress()
t.Run("start relayer", func(t *testing.T) {
s.StartRelayer(relayer, testName)
})
// createMsgGrantFn initializes a TransferAuthorization and broadcasts a MsgGrant message.
createMsgGrantFn := func(t *testing.T) {
t.Helper()
transferAuth := transfertypes.TransferAuthorization{
Allocations: []transfertypes.Allocation{
{
SourcePort: channelA.PortID,
SourceChannel: channelA.ChannelID,
SpendLimit: sdk.NewCoins(sdk.NewCoin(chainADenom, sdkmath.NewInt(testvalues.StartingTokenAmount))),
AllowList: []string{receiverWalletAddress},
},
},
}
protoAny, err := codectypes.NewAnyWithValue(&transferAuth)
s.Require().NoError(err)
msgGrant := &authz.MsgGrant{
Granter: granterAddress,
Grantee: granteeAddress,
Grant: authz.Grant{
Authorization: protoAny,
// no expiration
Expiration: nil,
},
}
resp := s.BroadcastMessages(context.TODO(), chainA, granterWallet, msgGrant)
s.AssertTxSuccess(resp)
}
// verifyGrantFn returns a test function which asserts chainA has a grant authorization
// with the given spend limit.
verifyGrantFn := func(expectedLimit int64) func(t *testing.T) {
t.Helper()
return func(t *testing.T) {
t.Helper()
grantAuths, err := s.QueryGranterGrants(ctx, chainA, granterAddress)
s.Require().NoError(err)
s.Require().Len(grantAuths, 1)
grantAuthorization := grantAuths[0]
transferAuth := s.extractTransferAuthorizationFromGrantAuthorization(grantAuthorization)
expectedSpendLimit := sdk.NewCoins(sdk.NewCoin(chainADenom, sdkmath.NewInt(expectedLimit)))
s.Require().Equal(expectedSpendLimit, transferAuth.Allocations[0].SpendLimit)
}
}
t.Run("broadcast MsgGrant", createMsgGrantFn)
t.Run("broadcast MsgExec for ibc MsgTransfer", func(t *testing.T) {
transferMsg := testsuite.GetMsgTransfer(
channelA.PortID,
channelA.ChannelID,
channelA.Version,
testvalues.DefaultTransferAmount(chainADenom),
granterAddress,
receiverWalletAddress,
s.GetTimeoutHeight(ctx, chainB),
0,
"",
)
protoAny, err := codectypes.NewAnyWithValue(transferMsg)
s.Require().NoError(err)
msgExec := &authz.MsgExec{
Grantee: granteeAddress,
Msgs: []*codectypes.Any{protoAny},
}
resp := s.BroadcastMessages(context.TODO(), chainA, granteeWallet, msgExec)
s.AssertTxSuccess(resp)
})
t.Run("verify granter wallet amount", func(t *testing.T) {
actualBalance, err := s.GetChainANativeBalance(ctx, granterWallet)
s.Require().NoError(err)
expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount
s.Require().Equal(expected, actualBalance)
})
s.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 := query.Balance(ctx, chainB, receiverWalletAddress, chainBIBCToken.IBCDenom())
s.Require().NoError(err)
s.Require().Equal(testvalues.IBCTransferAmount, actualBalance.Int64())
})
t.Run("granter grant spend limit reduced", verifyGrantFn(testvalues.StartingTokenAmount-testvalues.IBCTransferAmount))
t.Run("re-initialize MsgGrant", createMsgGrantFn)
t.Run("granter grant was reinitialized", verifyGrantFn(testvalues.StartingTokenAmount))
t.Run("revoke access", func(t *testing.T) {
msgRevoke := authz.MsgRevoke{
Granter: granterAddress,
Grantee: granteeAddress,
MsgTypeUrl: transfertypes.TransferAuthorization{}.MsgTypeURL(),
}
resp := s.BroadcastMessages(context.TODO(), chainA, granterWallet, &msgRevoke)
s.AssertTxSuccess(resp)
})
t.Run("exec unauthorized MsgTransfer", func(t *testing.T) {
transferMsg := testsuite.GetMsgTransfer(
channelA.PortID,
channelA.ChannelID,
channelA.Version,
testvalues.DefaultTransferAmount(chainADenom),
granterAddress,
receiverWalletAddress,
s.GetTimeoutHeight(ctx, chainB),
0,
"",
)
protoAny, err := codectypes.NewAnyWithValue(transferMsg)
s.Require().NoError(err)
msgExec := &authz.MsgExec{
Grantee: granteeAddress,
Msgs: []*codectypes.Any{protoAny},
}
resp := s.BroadcastMessages(context.TODO(), chainA, granteeWallet, msgExec)
s.AssertTxFailure(resp, authz.ErrNoAuthorizationFound)
})
}
func (s *AuthzTransferTestSuite) TestAuthz_InvalidTransferAuthorizations() {
t := s.T()
ctx := context.TODO()
testName := t.Name()
t.Parallel()
relayer, channelA := s.CreateAuthzTestPath(testName)
chainA, chainB := s.GetChains()
chainADenom := chainA.Config().Denom
chainAVersion := chainA.Config().Images[0].Version
granterWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount)
granterAddress := granterWallet.FormattedAddress()
granteeWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount)
granteeAddress := granteeWallet.FormattedAddress()
receiverWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount)
receiverWalletAddress := receiverWallet.FormattedAddress()
t.Run("start relayer", func(t *testing.T) {
s.StartRelayer(relayer, testName)
})
const spendLimit = 1000
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, sdkmath.NewInt(spendLimit))),
AllowList: []string{receiverWalletAddress},
},
},
}
protoAny, err := codectypes.NewAnyWithValue(&transferAuth)
s.Require().NoError(err)
msgGrant := &authz.MsgGrant{
Granter: granterAddress,
Grantee: granteeAddress,
Grant: authz.Grant{
Authorization: protoAny,
// no expiration
Expiration: nil,
},
}
resp := s.BroadcastMessages(context.TODO(), chainA, granterWallet, msgGrant)
s.AssertTxSuccess(resp)
})
t.Run("exceed spend limit", func(t *testing.T) {
const invalidSpendAmount = spendLimit + 1
t.Run("broadcast MsgExec for ibc MsgTransfer", func(t *testing.T) {
transferMsg := testsuite.GetMsgTransfer(
channelA.PortID,
channelA.ChannelID,
channelA.Version,
sdk.Coin{Denom: chainADenom, Amount: sdkmath.NewInt(invalidSpendAmount)},
granterAddress,
receiverWalletAddress,
s.GetTimeoutHeight(ctx, chainB),
0,
"",
)
protoAny, err := codectypes.NewAnyWithValue(transferMsg)
s.Require().NoError(err)
msgExec := &authz.MsgExec{
Grantee: granteeAddress,
Msgs: []*codectypes.Any{protoAny},
}
resp := s.BroadcastMessages(context.TODO(), chainA, granteeWallet, msgExec)
if testvalues.IbcErrorsFeatureReleases.IsSupported(chainAVersion) {
s.AssertTxFailure(resp, ibcerrors.ErrInsufficientFunds)
} else {
s.AssertTxFailure(resp, sdkerrors.ErrInsufficientFunds)
}
})
t.Run("verify granter wallet amount", func(t *testing.T) {
actualBalance, err := s.GetChainANativeBalance(ctx, granterWallet)
s.Require().NoError(err)
s.Require().Equal(testvalues.StartingTokenAmount, actualBalance)
})
t.Run("verify receiver wallet amount", func(t *testing.T) {
chainBIBCToken := testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID)
actualBalance, err := query.Balance(ctx, chainB, receiverWalletAddress, chainBIBCToken.IBCDenom())
s.Require().NoError(err)
s.Require().Equal(int64(0), actualBalance.Int64())
})
t.Run("granter grant spend limit unchanged", func(t *testing.T) {
grantAuths, err := s.QueryGranterGrants(ctx, chainA, granterAddress)
s.Require().NoError(err)
s.Require().Len(grantAuths, 1)
grantAuthorization := grantAuths[0]
transferAuth := s.extractTransferAuthorizationFromGrantAuthorization(grantAuthorization)
expectedSpendLimit := sdk.NewCoins(sdk.NewCoin(chainADenom, sdkmath.NewInt(spendLimit)))
s.Require().Equal(expectedSpendLimit, transferAuth.Allocations[0].SpendLimit)
})
})
t.Run("send funds to invalid address", func(t *testing.T) {
invalidWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount)
invalidWalletAddress := invalidWallet.FormattedAddress()
t.Run("broadcast MsgExec for ibc MsgTransfer", func(t *testing.T) {
transferMsg := testsuite.GetMsgTransfer(
channelA.PortID,
channelA.ChannelID,
channelA.Version,
sdk.Coin{Denom: chainADenom, Amount: sdkmath.NewInt(spendLimit)},
granterAddress,
invalidWalletAddress,
s.GetTimeoutHeight(ctx, chainB),
0,
"",
)
protoAny, err := codectypes.NewAnyWithValue(transferMsg)
s.Require().NoError(err)
msgExec := &authz.MsgExec{
Grantee: granteeAddress,
Msgs: []*codectypes.Any{protoAny},
}
resp := s.BroadcastMessages(context.TODO(), chainA, granteeWallet, msgExec)
if testvalues.IbcErrorsFeatureReleases.IsSupported(chainAVersion) {
s.AssertTxFailure(resp, ibcerrors.ErrInvalidAddress)
} else {
s.AssertTxFailure(resp, sdkerrors.ErrInvalidAddress)
}
})
})
}
// extractTransferAuthorizationFromGrantAuthorization extracts a TransferAuthorization from the given
// GrantAuthorization.
func (s *AuthzTransferTestSuite) extractTransferAuthorizationFromGrantAuthorization(grantAuth *authz.GrantAuthorization) *transfertypes.TransferAuthorization {
cfg := testsuite.SDKEncodingConfig()
var authorization authz.Authorization
err := cfg.InterfaceRegistry.UnpackAny(grantAuth.Authorization, &authorization)
s.Require().NoError(err)
transferAuth, ok := authorization.(*transfertypes.TransferAuthorization)
s.Require().True(ok)
return transferAuth
}