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

Add Payload fields #517

Merged
merged 4 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
16 changes: 16 additions & 0 deletions lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,11 @@ func compileCost(code WasmCode) uint64 {
return CostPerByte * uint64(len(code))
}

// hasSubMessages is an interface for contract results that can contain sub-messages.
chipshort marked this conversation as resolved.
Show resolved Hide resolved
type hasSubMessages interface {
SubMessages() []types.SubMsg
}

func DeserializeResponse(gasLimit uint64, deserCost types.UFraction, gasReport *types.GasReport, data []byte, response any) error {
gasForDeserialization := deserCost.Mul(uint64(len(data))).Floor()
if gasLimit < gasForDeserialization+gasReport.UsedInternally {
Expand All @@ -553,5 +558,16 @@ func DeserializeResponse(gasLimit uint64, deserCost types.UFraction, gasReport *
return err
}

// All responses that have sub-messages need their payload size to be checked
const ReplyPayloadMaxBytes = 128 * 1024 // 128 KiB
if response, ok := response.(hasSubMessages); ok {
for _, m := range response.SubMessages() {
// each payload needs to be below maximum size
if len(m.Payload) > ReplyPayloadMaxBytes {
return fmt.Errorf("reply payload larger than %d bytes: %d bytes", ReplyPayloadMaxBytes, len(m.Payload))
chipshort marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

return nil
}
42 changes: 42 additions & 0 deletions lib_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ package cosmwasm

import (
"encoding/json"
"fmt"
"math"
"os"
"testing"

Expand Down Expand Up @@ -368,3 +370,43 @@ func TestGetMetrics(t *testing.T) {
require.Equal(t, uint64(0), metrics.SizePinnedMemoryCache)
require.InEpsilon(t, 2832576, metrics.SizeMemoryCache, 0.25)
}

func TestLongPayloadDeserialization(t *testing.T) {
deserCost := types.UFraction{Numerator: 1, Denominator: 1}
gasReport := types.GasReport{}

// Create a valid payload
validPayload := make([]byte, 128*1024)
validPayloadJSON, err := json.Marshal(validPayload)
require.NoError(t, err)
resultJson := []byte(fmt.Sprintf(`{"ok":{"messages":[{"id":0,"msg":{"bank":{"send":{"to_address":"bob","amount":[{"denom":"ATOM","amount":"250"}]}}},"payload":%s,"reply_on":"never"}],"data":"8Auq","attributes":[],"events":[]}}`, validPayloadJSON))

// Test that a valid payload can be deserialized
var result types.ContractResult
err = DeserializeResponse(math.MaxUint64, deserCost, &gasReport, resultJson, &result)
require.NoError(t, err)
require.Equal(t, validPayload, result.Ok.Messages[0].Payload)

// Create an invalid payload (too large)
invalidPayload := make([]byte, 128*1024+1)
invalidPayloadJSON, err := json.Marshal(invalidPayload)
require.NoError(t, err)
resultJson = []byte(fmt.Sprintf(`{"ok":{"messages":[{"id":0,"msg":{"bank":{"send":{"to_address":"bob","amount":[{"denom":"ATOM","amount":"250"}]}}},"payload":%s,"reply_on":"never"}],"attributes":[],"events":[]}}`, invalidPayloadJSON))

// Test that an invalid payload cannot be deserialized
err = DeserializeResponse(math.MaxUint64, deserCost, &gasReport, resultJson, &result)
require.Error(t, err)
require.Contains(t, err.Error(), "payload")

// Test that an invalid payload cannot be deserialized to IBCBasicResult
var ibcResult types.IBCBasicResult
err = DeserializeResponse(math.MaxUint64, deserCost, &gasReport, resultJson, &ibcResult)
require.Error(t, err)
require.Contains(t, err.Error(), "payload")

// Test that an invalid payload cannot be deserialized to IBCReceiveResult
var ibcReceiveResult types.IBCReceiveResult
err = DeserializeResponse(math.MaxUint64, deserCost, &gasReport, resultJson, &ibcReceiveResult)
require.Error(t, err)
require.Contains(t, err.Error(), "payload")
}
14 changes: 14 additions & 0 deletions types/ibc.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,13 @@ type IBCBasicResult struct {
Err string `json:"error,omitempty"`
}

func (r *IBCBasicResult) SubMessages() []SubMsg {
if r.Ok != nil {
return r.Ok.Messages
}
return nil
}

// IBCBasicResponse defines the return value on a successful processing.
// This is the counterpart of [IbcBasicResponse](https://github.com/CosmWasm/cosmwasm/blob/v0.14.0-beta1/packages/std/src/ibc.rs#L194-L216).
type IBCBasicResponse struct {
Expand Down Expand Up @@ -249,6 +256,13 @@ type IBCReceiveResult struct {
Err string `json:"error,omitempty"`
}

func (r *IBCReceiveResult) SubMessages() []SubMsg {
if r.Ok != nil {
return r.Ok.Messages
}
return nil
}

// IBCReceiveResponse defines the return value on packet response processing.
// This "success" case should be returned even in application-level errors,
// Where the Acknowledgement bytes contain an encoded error message to be returned to
Expand Down
7 changes: 7 additions & 0 deletions types/msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ type ContractResult struct {
Err string `json:"error,omitempty"`
}

func (r *ContractResult) SubMessages() []SubMsg {
if r.Ok != nil {
return r.Ok.Messages
}
return nil
}

// Response defines the return value on a successful instantiate/execute/migrate.
// This is the counterpart of [Response](https://github.com/CosmWasm/cosmwasm/blob/v0.14.0-beta1/packages/std/src/results/response.rs#L73-L88)
type Response struct {
Expand Down
34 changes: 30 additions & 4 deletions types/submessages.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,29 @@ func (s *replyOn) UnmarshalJSON(b []byte) error {
// SubMsg wraps a CosmosMsg with some metadata for handling replies (ID) and optionally
// limiting the gas usage (GasLimit)
type SubMsg struct {
ID uint64 `json:"id"`
Msg CosmosMsg `json:"msg"`
GasLimit *uint64 `json:"gas_limit,omitempty"`
ReplyOn replyOn `json:"reply_on"`
// An arbitrary ID chosen by the contract.
// This is typically used to match `Reply`s in the `reply` entry point to the submessage.
ID uint64 `json:"id"`
Msg CosmosMsg `json:"msg"`
// Some arbitrary data that the contract can set in an application specific way.
// This is just passed into the `reply` entry point and is not stored to state.
// Any encoding can be used. If `id` is used to identify a particular action,
// the encoding can also be different for each of those actions since you can match `id`
// first and then start processing the `payload`.
//
// The environment restricts the length of this field in order to avoid abuse. The limit
// is environment specific and can change over time. The initial default is 128 KiB.
//
// Unset/nil/null cannot be differentiated from empty data.
//
// On chains running CosmWasm 1.x this field will be ignored.
Payload []byte `json:"payload"`
// Gas limit measured in [Cosmos SDK gas](https://github.com/CosmWasm/cosmwasm/blob/main/docs/GAS.md).
//
// Setting this to `None` means unlimited. Then the submessage execution can consume all gas of
// the current execution context.
GasLimit *uint64 `json:"gas_limit,omitempty"`
ReplyOn replyOn `json:"reply_on"`
}

// The result object returned to `reply`. We always get the ID from the submessage back and then must handle success and error cases ourselves.
Expand All @@ -68,6 +87,13 @@ type Reply struct {
// The ID that the contract set when emitting the `SubMsg`. Use this to identify which submessage triggered the `reply`.
ID uint64 `json:"id"`
Result SubMsgResult `json:"result"`
// Some arbitrary data that the contract set when emitting the `SubMsg`.
// This is just passed into the `reply` entry point and is not stored to state.
//
// Unset/nil/null cannot be differentiated from empty data.
//
// On chains running CosmWasm 1.x this field is never filled.
Payload []byte `json:"payload"`
}

// SubMsgResult is the raw response we return from wasmd after executing a SubMsg.
Expand Down
3 changes: 2 additions & 1 deletion types/submessages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@ func TestReplySerialization(t *testing.T) {
},
},
},
Payload: []byte("payload"),
}
serialized, err := json.Marshal(&reply1)
require.NoError(t, err)
require.Equal(t, `{"gas_used":4312324,"id":75,"result":{"ok":{"events":[{"type":"hi","attributes":[{"key":"si","value":"claro"}]}],"data":"PwCqXKs=","msg_responses":[{"type_url":"/cosmos.bank.v1beta1.MsgSendResponse","value":""}]}}}`, string(serialized))
require.Equal(t, `{"gas_used":4312324,"id":75,"result":{"ok":{"events":[{"type":"hi","attributes":[{"key":"si","value":"claro"}]}],"data":"PwCqXKs=","msg_responses":[{"type_url":"/cosmos.bank.v1beta1.MsgSendResponse","value":""}]}},"payload":"cGF5bG9hZA=="}`, string(serialized))
}

func TestSubMsgResponseSerialization(t *testing.T) {
Expand Down
Loading