Skip to content

Commit

Permalink
ADR 8 Interface and Packet Data Implementations (#3287)
Browse files Browse the repository at this point in the history
 Conflicts:
  • Loading branch information
AdityaSripal authored and colin-axner committed Apr 5, 2023
1 parent 174f708 commit df18b12
Show file tree
Hide file tree
Showing 7 changed files with 603 additions and 53 deletions.
75 changes: 75 additions & 0 deletions modules/apps/27-interchain-accounts/types/packet.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package types

import (
"encoding/json"
"time"

codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/ibc-go/v6/modules/core/exported"
)

// MaxMemoCharLength defines the maximum length for the InterchainAccountPacketData memo field
Expand All @@ -24,6 +26,8 @@ var (
DefaultRelativePacketTimeoutTimestamp = uint64((time.Duration(10) * time.Minute).Nanoseconds())
)

var _ exported.CallbackPacketData = (*InterchainAccountPacketData)(nil)

// ValidateBasic performs basic validation of the interchain account packet data.
// The memo may be empty.
func (iapd InterchainAccountPacketData) ValidateBasic() error {
Expand All @@ -47,6 +51,77 @@ func (iapd InterchainAccountPacketData) GetBytes() []byte {
return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&iapd))
}

/*
ADR-8 CallbackPacketData implementation
InterchainAccountPacketData implements CallbackPacketData interface. This will allow middlewares targeting specific VMs
to retrieve the desired callback address for the ICA packet on the source chain. Destination callback addresses are not
supported for ICS 27.
The Memo is used to set the desired callback addresses.
The Memo format is defined like so:
```json
{
// ... other memo fields we don't care about
"callbacks": {
"src_callback_address": {contractAddrOnSourceChain},
// optional fields
"src_callback_msg": {jsonObjectForSourceChainCallback},
}
}
```
*/

// GetSourceCallbackAddress returns the source callback address provided in the packet data memo.
// If no callback address is specified, an empty string is returned.
//
// The memo is expected to specify the callback address in the following format:
// { "callbacks": { "src_callback_address": {contractAddrOnSourceChain}}
//
// ADR-8 middleware should callback on the returned address if it is a PacketActor
// (i.e. smart contract that accepts IBC callbacks).
func (iapd InterchainAccountPacketData) GetSourceCallbackAddress() string {
if len(iapd.Memo) == 0 {
return ""
}

jsonObject := make(map[string]interface{})
err := json.Unmarshal([]byte(iapd.Memo), &jsonObject)
if err != nil {
return ""
}

callbackData, ok := jsonObject["callbacks"].(map[string]interface{})
if !ok {
return ""
}

callbackAddr, ok := callbackData["src_callback_address"].(string)
if !ok {
return ""
}

return callbackAddr
}

// GetDestCallbackAddress returns an empty string. Destination callback addresses
// are not supported for ICS 27. This feature is natively supported by
// interchain accounts host submodule transaction execution.
func (iapd InterchainAccountPacketData) GetDestCallbackAddress() string {
return ""
}

// UserDefinedGasLimit returns 0 (no-op). The gas limit of the executing
// transaction will be used.
func (iapd InterchainAccountPacketData) UserDefinedGasLimit() uint64 {
return 0
}

// GetBytes returns the JSON marshalled interchain account CosmosTx.
func (ct CosmosTx) GetBytes() []byte {
return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(&ct))
Expand Down
122 changes: 122 additions & 0 deletions modules/apps/27-interchain-accounts/types/packet_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package types_test

import (
"fmt"

"github.com/cosmos/ibc-go/v6/modules/apps/27-interchain-accounts/types"
)

Expand Down Expand Up @@ -82,3 +84,123 @@ func (suite *TypesTestSuite) TestValidateBasic() {
})
}
}

func (suite *TypesTestSuite) TestGetSourceCallbackAddress() {
const expSrcCbAddr = "srcCbAddr"

testCases := []struct {
name string
packetData types.InterchainAccountPacketData
expPass bool
}{
{
"memo is empty",
types.InterchainAccountPacketData{
Type: types.EXECUTE_TX,
Data: []byte("data"),
Memo: "",
},
false,
},
{
"memo is not json string",
types.InterchainAccountPacketData{
Type: types.EXECUTE_TX,
Data: []byte("data"),
Memo: "memo",
},
false,
},
{
"memo does not have callbacks in json struct",
types.InterchainAccountPacketData{
Type: types.EXECUTE_TX,
Data: []byte("data"),
Memo: `{"Key": 10}`,
},
false,
},
{
"memo has callbacks in json struct but does not have src_callback_address key",
types.InterchainAccountPacketData{
Type: types.EXECUTE_TX,
Data: []byte("data"),
Memo: `{"callbacks": {"Key": 10}}`,
},
false,
},
{
"memo has callbacks in json struct but does not have string value for src_callback_address key",
types.InterchainAccountPacketData{
Type: types.EXECUTE_TX,
Data: []byte("data"),
Memo: `{"callbacks": {"src_callback_address": 10}}`,
},
false,
},
{
"memo has callbacks in json struct and properly formatted src_callback_address",
types.InterchainAccountPacketData{
Type: types.EXECUTE_TX,
Data: []byte("data"),
Memo: fmt.Sprintf(`{"callbacks": {"src_callback_address": "%s"}}`, expSrcCbAddr),
},
true,
},
}

for _, tc := range testCases {
tc := tc
suite.Run(tc.name, func() {
srcCbAddr := tc.packetData.GetSourceCallbackAddress()

if tc.expPass {
suite.Require().Equal(expSrcCbAddr, srcCbAddr)
} else {
suite.Require().Equal("", srcCbAddr)
}
})
}
}

func (suite *TypesTestSuite) TestGetDestCallbackAddress() {
testCases := []struct {
name string
packetData types.InterchainAccountPacketData
}{
{
"memo is empty",
types.InterchainAccountPacketData{
Type: types.EXECUTE_TX,
Data: []byte("data"),
Memo: "",
},
},
{
"memo has dest callback address specified in json struct",
types.InterchainAccountPacketData{
Type: types.EXECUTE_TX,
Data: []byte("data"),
Memo: `{"callbacks": {"dest_callback_address": "testAddress"}}`,
},
},
}

for _, tc := range testCases {
tc := tc
suite.Run(tc.name, func() {
destCbAddr := tc.packetData.GetDestCallbackAddress()
suite.Require().Equal("", destCbAddr)
})
}
}

func (suite *TypesTestSuite) TestUserDefinedGasLimit() {
packetData := types.InterchainAccountPacketData{
Type: types.EXECUTE_TX,
Data: []byte("data"),
Memo: `{"callbacks": {"user_defined_gas_limit": 100}}`,
}

suite.Require().Equal(uint64(0), packetData.UserDefinedGasLimit(), "user defined gas limit does not return 0")
}
43 changes: 22 additions & 21 deletions modules/apps/transfer/types/msgs_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package types
package types_test

import (
"fmt"
Expand All @@ -8,6 +8,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"

"github.com/cosmos/ibc-go/v6/modules/apps/transfer/types"
clienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types"
)

Expand Down Expand Up @@ -41,20 +42,20 @@ var (

// TestMsgTransferRoute tests Route for MsgTransfer
func TestMsgTransferRoute(t *testing.T) {
msg := NewMsgTransfer(validPort, validChannel, coin, addr1, addr2, timeoutHeight, 0, "")
msg := types.NewMsgTransfer(validPort, validChannel, coin, addr1, addr2, timeoutHeight, 0, "")

require.Equal(t, RouterKey, msg.Route())
require.Equal(t, types.RouterKey, msg.Route())
}

// TestMsgTransferType tests Type for MsgTransfer
func TestMsgTransferType(t *testing.T) {
msg := NewMsgTransfer(validPort, validChannel, coin, addr1, addr2, timeoutHeight, 0, "")
msg := types.NewMsgTransfer(validPort, validChannel, coin, addr1, addr2, timeoutHeight, 0, "")

require.Equal(t, "transfer", msg.Type())
}

func TestMsgTransferGetSignBytes(t *testing.T) {
msg := NewMsgTransfer(validPort, validChannel, coin, addr1, addr2, timeoutHeight, 0, "")
msg := types.NewMsgTransfer(validPort, validChannel, coin, addr1, addr2, timeoutHeight, 0, "")
expected := fmt.Sprintf(`{"type":"cosmos-sdk/MsgTransfer","value":{"receiver":"%s","sender":"%s","source_channel":"testchannel","source_port":"testportid","timeout_height":{"revision_height":"10"},"token":{"amount":"100","denom":"atom"}}}`, addr2, addr1)
require.NotPanics(t, func() {
res := msg.GetSignBytes()
Expand All @@ -66,23 +67,23 @@ func TestMsgTransferGetSignBytes(t *testing.T) {
func TestMsgTransferValidation(t *testing.T) {
testCases := []struct {
name string
msg *MsgTransfer
msg *types.MsgTransfer
expPass bool
}{
{"valid msg with base denom", NewMsgTransfer(validPort, validChannel, coin, addr1, addr2, timeoutHeight, 0, ""), true},
{"valid msg with trace hash", NewMsgTransfer(validPort, validChannel, ibcCoin, addr1, addr2, timeoutHeight, 0, ""), true},
{"invalid ibc denom", NewMsgTransfer(validPort, validChannel, invalidIBCCoin, addr1, addr2, timeoutHeight, 0, ""), false},
{"too short port id", NewMsgTransfer(invalidShortPort, validChannel, coin, addr1, addr2, timeoutHeight, 0, ""), false},
{"too long port id", NewMsgTransfer(invalidLongPort, validChannel, coin, addr1, addr2, timeoutHeight, 0, ""), false},
{"port id contains non-alpha", NewMsgTransfer(invalidPort, validChannel, coin, addr1, addr2, timeoutHeight, 0, ""), false},
{"too short channel id", NewMsgTransfer(validPort, invalidShortChannel, coin, addr1, addr2, timeoutHeight, 0, ""), false},
{"too long channel id", NewMsgTransfer(validPort, invalidLongChannel, coin, addr1, addr2, timeoutHeight, 0, ""), false},
{"channel id contains non-alpha", NewMsgTransfer(validPort, invalidChannel, coin, addr1, addr2, timeoutHeight, 0, ""), false},
{"invalid denom", NewMsgTransfer(validPort, validChannel, invalidDenomCoin, addr1, addr2, timeoutHeight, 0, ""), false},
{"zero coin", NewMsgTransfer(validPort, validChannel, zeroCoin, addr1, addr2, timeoutHeight, 0, ""), false},
{"missing sender address", NewMsgTransfer(validPort, validChannel, coin, emptyAddr, addr2, timeoutHeight, 0, ""), false},
{"missing recipient address", NewMsgTransfer(validPort, validChannel, coin, addr1, "", timeoutHeight, 0, ""), false},
{"empty coin", NewMsgTransfer(validPort, validChannel, sdk.Coin{}, addr1, addr2, timeoutHeight, 0, ""), false},
{"valid msg with base denom", types.NewMsgTransfer(validPort, validChannel, coin, addr1, addr2, timeoutHeight, 0, ""), true},
{"valid msg with trace hash", types.NewMsgTransfer(validPort, validChannel, ibcCoin, addr1, addr2, timeoutHeight, 0, ""), true},
{"invalid ibc denom", types.NewMsgTransfer(validPort, validChannel, invalidIBCCoin, addr1, addr2, timeoutHeight, 0, ""), false},
{"too short port id", types.NewMsgTransfer(invalidShortPort, validChannel, coin, addr1, addr2, timeoutHeight, 0, ""), false},
{"too long port id", types.NewMsgTransfer(invalidLongPort, validChannel, coin, addr1, addr2, timeoutHeight, 0, ""), false},
{"port id contains non-alpha", types.NewMsgTransfer(invalidPort, validChannel, coin, addr1, addr2, timeoutHeight, 0, ""), false},
{"too short channel id", types.NewMsgTransfer(validPort, invalidShortChannel, coin, addr1, addr2, timeoutHeight, 0, ""), false},
{"too long channel id", types.NewMsgTransfer(validPort, invalidLongChannel, coin, addr1, addr2, timeoutHeight, 0, ""), false},
{"channel id contains non-alpha", types.NewMsgTransfer(validPort, invalidChannel, coin, addr1, addr2, timeoutHeight, 0, ""), false},
{"invalid denom", types.NewMsgTransfer(validPort, validChannel, invalidDenomCoin, addr1, addr2, timeoutHeight, 0, ""), false},
{"zero coin", types.NewMsgTransfer(validPort, validChannel, zeroCoin, addr1, addr2, timeoutHeight, 0, ""), false},
{"missing sender address", types.NewMsgTransfer(validPort, validChannel, coin, emptyAddr, addr2, timeoutHeight, 0, ""), false},
{"missing recipient address", types.NewMsgTransfer(validPort, validChannel, coin, addr1, "", timeoutHeight, 0, ""), false},
{"empty coin", types.NewMsgTransfer(validPort, validChannel, sdk.Coin{}, addr1, addr2, timeoutHeight, 0, ""), false},
}

for i, tc := range testCases {
Expand All @@ -99,7 +100,7 @@ func TestMsgTransferValidation(t *testing.T) {
func TestMsgTransferGetSigners(t *testing.T) {
addr := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address())

msg := NewMsgTransfer(validPort, validChannel, coin, addr.String(), addr2, timeoutHeight, 0, "")
msg := types.NewMsgTransfer(validPort, validChannel, coin, addr.String(), addr2, timeoutHeight, 0, "")
res := msg.GetSigners()

require.Equal(t, []sdk.AccAddress{addr}, res)
Expand Down
Loading

0 comments on commit df18b12

Please sign in to comment.