Skip to content

Add Transfer handling to IBCDestinationCallbackMsg #675

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

Merged
merged 3 commits into from
May 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions types/ibc.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,21 @@ type IBCTimeoutCallbackMsg struct {
type IBCDestinationCallbackMsg struct {
Ack IBCAcknowledgement `json:"ack"`
Packet IBCPacket `json:"packet"`
// When the underlying packet is a successful transfer message, this field contains information about the transfer. Otherwise it is empty.
//
// This is always empty on chains using CosmWasm < 3.0
Transfer *IBCTransferCallback `json:"transfer,omitempty"`
}

type IBCTransferCallback struct {
// The funds that were transferred.
//
// When the callback is executed, the transfer is completed already and the coins are now owned by the receiver.
Funds Array[Coin] `json:"funds"`
// Address of the receiver of the transfer. Since this is on the destination chain, this is a valid address.
Receiver string `json:"receiver"`
// Address of the sender of the transfer. Note that this is *not* a valid address on the destination chain.
Sender string `json:"sender"`
}

// TODO: test what the sdk Order.String() represents and how to parse back
Expand Down
55 changes: 54 additions & 1 deletion types/json_size.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,37 @@ const (
null int = 4 // null
)

// ExpectedJSONSize returns the expected JSON size in bytes when using
// json.Marshal with the given value.
// Since JSON marshalling does not have a guaranteed output format,
// this should be understood as a best guess and correct in most cases.
// Do not use it when a precise value is required.
func (c Coin) ExpectedJSONSize() int {
// Denom string `json:"denom"`
// Amount string `json:"amount"`
return brackets +
7 + colon + ExpectedJSONSizeString(c.Denom) + comma +
8 + colon + ExpectedJSONSizeString(c.Amount)
}

// ExpectedJSONSize returns the expected JSON size in bytes when using
// json.Marshal with the given value.
// Since JSON marshalling does not have a guaranteed output format,
// this should be understood as a best guess and correct in most cases.
// Do not use it when a precise value is required.
//
// This is a free-standing function because methods don't support constraining the generic type.
func ExpectedJSONSizeArray[T ExpectedJSONSize](a Array[T]) int {
out := brackets
for i, v := range a {
if i > 0 {
out += comma
}
out += v.ExpectedJSONSize()
}
return out
}

// ExpectedJSONSize returns the expected JSON size in bytes when using
// json.Marshal with the given value.
// Since JSON marshalling does not have a guaranteed output format,
Expand Down Expand Up @@ -440,7 +471,29 @@ func (t IBCSourceCallbackMsg) ExpectedJSONSize() int {
func (t IBCDestinationCallbackMsg) ExpectedJSONSize() int {
// Ack IBCAcknowledgement `json:"ack"`
// Packet IBCPacket `json:"packet"`
return brackets +
// Transfer *IBCTransfer `json:"transfer,omitempty"`
out := brackets +
5 + colon + t.Ack.ExpectedJSONSize() + comma +
8 + colon + t.Packet.ExpectedJSONSize()

if t.Transfer != nil {
out += comma + 10 + colon + t.Transfer.ExpectedJSONSize()
}

return out
}

// ExpectedJSONSize returns the expected JSON size in bytes when using
// json.Marshal with the given value.
// Since JSON marshalling does not have a guaranteed output format,
// this should be understood as a best guess and correct in most cases.
// Do not use it when a precise value is required.
func (t IBCTransferCallback) ExpectedJSONSize() int {
// Funds Array[Coin] `json:"funds"`
// Receiver string `json:"receiver"`
// Sender string `json:"sender"`
return brackets +
7 + colon + ExpectedJSONSizeArray(t.Funds) + comma +
10 + colon + ExpectedJSONSizeString(t.Receiver) + comma +
8 + colon + ExpectedJSONSizeString(t.Sender)
}
58 changes: 58 additions & 0 deletions types/json_size_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,3 +309,61 @@ func TestExpectedJSONSizeBool(t *testing.T) {
})
}
}

func TestExpectedJSONSizeCoin(t *testing.T) {
specs := map[string]struct {
input Coin
}{
"zero": {
input: NewCoin(0, "untrn"),
},
"three": {
input: NewCoin(3, "uatom"),
},
"max": {
input: Coin{
// 2^256 - 1
Amount: "115792089237316195423570985008687907853269984665640564039457584007913129639935",
Denom: "untrn",
},
},
}

for name, spec := range specs {
t.Run(name, func(t *testing.T) {
serialized, err := json.Marshal(spec.input)
if err != nil {
panic("Could not marshal input")
}
require.Equal(t, len(serialized), spec.input.ExpectedJSONSize())
})
}
}

func TestExpectedJSONSizeArray(t *testing.T) {
specs := map[string]struct {
input Array[Coin]
}{
"empty": {
input: Array[Coin]{},
},
"zero": {
input: Array[Coin]{NewCoin(0, "untrn")},
},
"one": {
input: Array[Coin]{NewCoin(1, "uatom")},
},
"big": {
input: Array[Coin]{NewCoin(10000000, "untrn"), NewCoin(2000000, "uatom")},
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
serialized, err := json.Marshal(spec.input)
if err != nil {
panic("Could not marshal input")
}
require.Equal(t, len(serialized), ExpectedJSONSizeArray(spec.input))
})
}
}