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 async support to wasm hooks #5072

Merged
merged 37 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
dc47387
added initial async ack support for wasm hooks
nicolaslara May 3, 2023
45833e8
added initial working implementation of async acks
nicolaslara May 5, 2023
447d8f1
removed debug prints
nicolaslara May 5, 2023
b872a7d
cleanner acks
nicolaslara May 26, 2023
62c6097
Merge branch 'main' into nicolas/async-hooks
nicolaslara May 26, 2023
cd1e9ea
Update x/ibc-hooks/README.md
nicolaslara May 31, 2023
67b59a4
added use case to readme
nicolaslara May 31, 2023
07446e7
clippy
nicolaslara May 31, 2023
415a40c
clippy
nicolaslara May 31, 2023
fa9d044
updated hooks to latest
nicolaslara May 31, 2023
25cf51a
added changelog
nicolaslara May 31, 2023
9be127c
updated deps
nicolaslara May 31, 2023
d3f7b1e
added proto fix and todo
nicolaslara May 31, 2023
5aebc19
added initial implementation of ack errors
nicolaslara Jun 2, 2023
338a15b
Merge branch 'main' into nicolas/async-hooks
nicolaslara Jun 7, 2023
80e698f
make protos
nicolaslara Jun 7, 2023
ab35895
better readme
nicolaslara Jun 7, 2023
bfc1da3
cleanup rust structs
nicolaslara Jun 30, 2023
e19b66d
initial params
nicolaslara Jun 30, 2023
17a2daa
fixed test
nicolaslara Jun 30, 2023
fbb90dc
updated async acks based on feedback. Safer this way
nicolaslara Jul 4, 2023
ef9bba1
add error ack test
nicolaslara Jul 6, 2023
eea8083
Merge branch 'main' into nicolas/async-hooks
nicolaslara Jul 7, 2023
b6208c3
added missing types
nicolaslara Jul 7, 2023
0ef4921
updated ibc-hooks
nicolaslara Jul 17, 2023
1c7208d
tidy
nicolaslara Jul 17, 2023
f056cfc
updated osmoutils
nicolaslara Jul 17, 2023
c28fbec
Merge branch 'main' into nicolas/async-hooks
nicolaslara Jul 17, 2023
36ab0d6
updated osmoutils and ibc-hooks after merge
nicolaslara Jul 17, 2023
e3b3537
Merge branch 'main' into nicolas/async-hooks
czarcas7ic Aug 7, 2023
325906e
merge main
czarcas7ic Aug 7, 2023
01fa42d
regen proto
czarcas7ic Aug 7, 2023
0ae9d16
update osmoutils
czarcas7ic Aug 7, 2023
16c217d
update ibc hooks
czarcas7ic Aug 7, 2023
bd56132
run go get
czarcas7ic Aug 7, 2023
8b472a9
Merge branch 'main' into nicolas/async-hooks
nicolaslara Aug 7, 2023
8f54f66
rl bytecode
nicolaslara Aug 7, 2023
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Features

* [#5072](https://github.com/osmosis-labs/osmosis/pull/5072) IBC-hooks: Add support for async acks when processing onRecvPacket

### State Breaking

* [#5532](https://github.com/osmosis-labs/osmosis/pull/5532) fix: Fix x/tokenfactory genesis import denoms reset x/bank existing denom metadata
Expand Down
7 changes: 6 additions & 1 deletion app/keepers/keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,11 @@ func (appKeepers *AppKeepers) InitNormalKeepers(
// Configure the hooks keeper
hooksKeeper := ibchookskeeper.NewKeeper(
appKeepers.keys[ibchookstypes.StoreKey],
appKeepers.GetSubspace(ibchookstypes.ModuleName),
appKeepers.IBCKeeper.ChannelKeeper,
nil,
)
appKeepers.IBCHooksKeeper = &hooksKeeper
appKeepers.IBCHooksKeeper = hooksKeeper

appKeepers.WireICS20PreWasmKeeper(appCodec, bApp, appKeepers.IBCHooksKeeper)

Expand Down Expand Up @@ -471,6 +474,7 @@ func (appKeepers *AppKeepers) InitNormalKeepers(
appKeepers.RateLimitingICS4Wrapper.ContractKeeper = appKeepers.ContractKeeper
appKeepers.Ics20WasmHooks.ContractKeeper = appKeepers.ContractKeeper
appKeepers.CosmwasmPoolKeeper.SetContractKeeper(appKeepers.ContractKeeper)
appKeepers.IBCHooksKeeper.ContractKeeper = appKeepers.ContractKeeper

// set token factory contract keeper
appKeepers.TokenFactoryKeeper.SetContractKeeper(appKeepers.ContractKeeper)
Expand Down Expand Up @@ -665,6 +669,7 @@ func (appKeepers *AppKeepers) initParamsKeeper(appCodec codec.BinaryCodec, legac
paramsKeeper.Subspace(icqtypes.ModuleName)
paramsKeeper.Subspace(packetforwardtypes.ModuleName).WithKeyTable(packetforwardtypes.ParamKeyTable())
paramsKeeper.Subspace(cosmwasmpooltypes.ModuleName)
paramsKeeper.Subspace(ibchookstypes.ModuleName)

return paramsKeeper
}
Expand Down
2 changes: 1 addition & 1 deletion app/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ func appModules(
tokenfactory.NewAppModule(*app.TokenFactoryKeeper, app.AccountKeeper, app.BankKeeper),
valsetprefmodule.NewAppModule(appCodec, *app.ValidatorSetPreferenceKeeper),
ibcratelimitmodule.NewAppModule(*app.RateLimitingICS4Wrapper),
ibc_hooks.NewAppModule(app.AccountKeeper),
ibc_hooks.NewAppModule(app.AccountKeeper, *app.IBCHooksKeeper),
icq.NewAppModule(*app.AppKeepers.ICQKeeper),
packetforward.NewAppModule(app.PacketForwardKeeper),
cwpoolmodule.NewAppModule(appCodec, *app.CosmwasmPoolKeeper),
Expand Down
2 changes: 1 addition & 1 deletion cosmwasm/contracts/crosschain-registry/src/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -828,7 +828,7 @@ mod tests {
let unauthorized_remove_msg = ExecuteMsg::ModifyContractAlias {
operations: vec![ContractAliasInput {
operation: Operation::Remove,
alias: alias,
alias,
address: Some(address),
new_alias: None,
}],
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ require (
github.com/ory/dockertest/v3 v3.10.0
github.com/osmosis-labs/go-mutesting v0.0.0-20221208041716-b43bcd97b3b3
github.com/osmosis-labs/osmosis/osmomath v0.0.3-dev.0.20230629191111-f375469de8b6
github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230709024311-81c831b050de
github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230717123101-c28fbec0b19b
github.com/osmosis-labs/osmosis/x/epochs v0.0.0-20230328024000-175ec88e4304
github.com/osmosis-labs/osmosis/x/ibc-hooks v0.0.0-20230602130523-f9a94d8bbd10
github.com/osmosis-labs/osmosis/x/ibc-hooks v0.0.0-20230717123101-c28fbec0b19b
github.com/pkg/errors v0.9.1
github.com/rakyll/statik v0.1.7
github.com/spf13/cast v1.5.1
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -950,12 +950,12 @@ github.com/osmosis-labs/go-mutesting v0.0.0-20221208041716-b43bcd97b3b3 h1:Ylmch
github.com/osmosis-labs/go-mutesting v0.0.0-20221208041716-b43bcd97b3b3/go.mod h1:lV6KnqXYD/ayTe7310MHtM3I2q8Z6bBfMAi+bhwPYtI=
github.com/osmosis-labs/osmosis/osmomath v0.0.3-dev.0.20230629191111-f375469de8b6 h1:Kmkx5Rh72+LB8AL6dc6fZA+IVR0INu0YIiMF2ScDhaQ=
github.com/osmosis-labs/osmosis/osmomath v0.0.3-dev.0.20230629191111-f375469de8b6/go.mod h1:JTym95/bqrSnG5MPcXr1YDhv43JdCeo3p+iDbazoX68=
github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230709024311-81c831b050de h1:W2lMduMgpNA5zheEIIialw08n1pWJ44Y4t2F924tpDU=
github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230709024311-81c831b050de/go.mod h1:Pl8Nzx6O6ow/+aqfMoMSz4hX+zz6RrnDYsooptECGxM=
github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230717123101-c28fbec0b19b h1:sfhra8bqYR1X/U6HoTMbjN5xQ476bXjNC1awYCa8at4=
github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230717123101-c28fbec0b19b/go.mod h1:Pl8Nzx6O6ow/+aqfMoMSz4hX+zz6RrnDYsooptECGxM=
github.com/osmosis-labs/osmosis/x/epochs v0.0.0-20230328024000-175ec88e4304 h1:RIrWLzIiZN5Xd2JOfSOtGZaf6V3qEQYg6EaDTAkMnCo=
github.com/osmosis-labs/osmosis/x/epochs v0.0.0-20230328024000-175ec88e4304/go.mod h1:yPWoJTj5RKrXKUChAicp+G/4Ni/uVEpp27mi/FF/L9c=
github.com/osmosis-labs/osmosis/x/ibc-hooks v0.0.0-20230602130523-f9a94d8bbd10 h1:XrES5AHZMZ/Y78boW35PTignkhN9h8VvJ1sP8EJDIu8=
github.com/osmosis-labs/osmosis/x/ibc-hooks v0.0.0-20230602130523-f9a94d8bbd10/go.mod h1:Ln6CKcXg/CJLSBE6Fd96/MIKPyA4iHuQTKSbl9q7vYo=
github.com/osmosis-labs/osmosis/x/ibc-hooks v0.0.0-20230717123101-c28fbec0b19b h1:TFRJCgCjnXCB7/5kC+RttfFwnEQzR5aNnjpXTDFTfcI=
github.com/osmosis-labs/osmosis/x/ibc-hooks v0.0.0-20230717123101-c28fbec0b19b/go.mod h1:sR0lpev9mcm9/9RY50T1og3UC3WpZAsINh/OmgrmFlg=
github.com/osmosis-labs/wasmd v0.31.0-osmo-v16 h1:X747cZYdnqc/+RV48iPVeGprpVb/fUWSaKGsZUWrdbg=
github.com/osmosis-labs/wasmd v0.31.0-osmo-v16/go.mod h1:Rf8zW/GgBQyFRRB4s62VQHWA6sTlMFSjoDQQpoq64iI=
github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
Expand Down
18 changes: 16 additions & 2 deletions osmoutils/ibc.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@ const IbcAcknowledgementErrorType = "ibc-acknowledgement-error"
// NewEmitErrorAcknowledgement creates a new error acknowledgement after having emitted an event with the
// details of the error.
func NewEmitErrorAcknowledgement(ctx sdk.Context, err error, errorContexts ...string) channeltypes.Acknowledgement {
EmitIBCErrorEvents(ctx, err, errorContexts)

return channeltypes.NewErrorAcknowledgement(err)
}

// NewSuccessAckRepresentingAnError creates a new success acknowledgement that represents an error.
// This is useful for notifying the sender that an error has occurred in a way that does not allow
// the received tokens to be reverted (which means they shouldn't be released by the sender's ics20 escrow)
func NewSuccessAckRepresentingAnError(ctx sdk.Context, err error, errorContent []byte, errorContexts ...string) channeltypes.Acknowledgement {
EmitIBCErrorEvents(ctx, err, errorContexts)

return channeltypes.NewResultAcknowledgement(errorContent)
}

// EmitIBCErrorEvents Emit and Log errors
func EmitIBCErrorEvents(ctx sdk.Context, err error, errorContexts []string) {
logger := ctx.Logger().With("module", IbcAcknowledgementErrorType)

attributes := make([]sdk.Attribute, len(errorContexts)+1)
Expand All @@ -29,8 +45,6 @@ func NewEmitErrorAcknowledgement(ctx sdk.Context, err error, errorContexts ...st
attributes...,
),
})

return channeltypes.NewErrorAcknowledgement(err)
}

// MustExtractDenomFromPacketOnRecv takes a packet with a valid ICS20 token data in the Data field and returns the
Expand Down
10 changes: 10 additions & 0 deletions proto/osmosis/ibc-hooks/genesis.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
syntax = "proto3";
package osmosis.ibchooks;

import "gogoproto/gogo.proto";
import "cosmos_proto/cosmos.proto";
import "osmosis/ibc-hooks/params.proto";

option go_package = "github.com/osmosis-labs/osmosis/v16/x/ibc-hooks/types";

message GenesisState { Params params = 1 [ (gogoproto.nullable) = false ]; }
13 changes: 13 additions & 0 deletions proto/osmosis/ibc-hooks/params.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
syntax = "proto3";
package osmosis.ibchooks;

import "gogoproto/gogo.proto";
import "cosmos_proto/cosmos.proto";
import "google/protobuf/duration.proto";

option go_package = "github.com/osmosis-labs/osmosis/v16/x/ibc-hooks/types";

message Params {
repeated string allowed_async_ack_contracts = 1
[ (gogoproto.moretags) = "yaml:\"allowed_async_ack_contracts\"" ];
}
25 changes: 25 additions & 0 deletions proto/osmosis/ibc-hooks/tx.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
syntax = "proto3";
package osmosis.ibchooks;

import "gogoproto/gogo.proto";

option go_package = "github.com/osmosis-labs/osmosis/v16/x/ibc-hooks/types";

// Msg defines the Msg service.
service Msg {
// EmitIBCAck checks the sender can emit the ack and writes the IBC
// acknowledgement
rpc EmitIBCAck(MsgEmitIBCAck) returns (MsgEmitIBCAckResponse);
}

message MsgEmitIBCAck {
string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ];
uint64 packet_sequence = 2
[ (gogoproto.moretags) = "yaml:\"packet_sequence\"" ];
string channel = 3 [ (gogoproto.moretags) = "yaml:\"channel\"" ];
}
message MsgEmitIBCAckResponse {
string contract_result = 1
[ (gogoproto.moretags) = "yaml:\"contract_result\"" ];
string ibc_ack = 2 [ (gogoproto.moretags) = "yaml:\"ibc_ack\"" ];
}
120 changes: 120 additions & 0 deletions tests/ibc-hooks/async_acks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package ibc_hooks_test

import (
"encoding/base64"
"encoding/json"
"fmt"
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
sdk "github.com/cosmos/cosmos-sdk/types"
channeltypes "github.com/cosmos/ibc-go/v4/modules/core/04-channel/types"
ibctesting "github.com/cosmos/ibc-go/v4/testing"
"github.com/osmosis-labs/osmosis/osmoutils"
"github.com/osmosis-labs/osmosis/v16/app"
"github.com/osmosis-labs/osmosis/x/ibc-hooks/types"
"github.com/tidwall/gjson"
)

func (suite *HooksTestSuite) forceContractToEmitAckForPacket(osmosisApp *app.OsmosisApp, ctx sdk.Context, contractAddr sdk.AccAddress, packet channeltypes.Packet, success bool) ([]byte, error) {
packetJson, err := json.Marshal(packet)
suite.Require().NoError(err)

msg := fmt.Sprintf(`{"force_emit_ibc_ack": {"packet": %s, "channel": "channel-0", "success": %v }}`, packetJson, success)
contractKeeper := wasmkeeper.NewDefaultPermissionKeeper(osmosisApp.WasmKeeper)
return contractKeeper.Execute(ctx, contractAddr, suite.chainA.SenderAccount.GetAddress(), []byte(msg), sdk.NewCoins())

}

func (suite *HooksTestSuite) TestWasmHooksAsyncAcks() {
sender := suite.chainB.SenderAccount.GetAddress()
osmosisApp := suite.chainA.GetOsmosisApp()

// Instantiate a contract that knows how to send async Acks
suite.chainA.StoreContractCode(&suite.Suite, "./bytecode/echo.wasm")
contractAddr := suite.chainA.InstantiateContract(&suite.Suite, "{}", 1)

// Calls that don't specify async acks work as expected
memo := fmt.Sprintf(`{"wasm": {"contract": "%s", "msg": {"async": {"use_async": false}}}}`, contractAddr)
suite.fundAccount(suite.chainB, sender)
transferMsg := NewMsgTransfer(sdk.NewCoin("token0", sdk.NewInt(2000)), sender.String(), contractAddr.String(), "channel-0", memo)
sendResult, receiveResult, ack, err := suite.FullSend(transferMsg, BtoA)
suite.Require().NoError(err)
suite.Require().NotNil(sendResult)
suite.Require().NotNil(receiveResult)
suite.Require().NotNil(ack)

// the ack has been written
allAcks := osmosisApp.IBCKeeper.ChannelKeeper.GetAllPacketAcks(suite.chainA.GetContext())
suite.Require().Equal(1, len(allAcks))

// Try to emit an ack for a packet that already has been acked. This should fail

// we extract the packet that has been acked here to test later that our contract can't emit an ack for it
alreadyAckedPacket, err := ibctesting.ParsePacketFromEvents(sendResult.GetEvents())
suite.Require().NoError(err)

_, err = suite.forceContractToEmitAckForPacket(osmosisApp, suite.chainA.GetContext(), contractAddr, alreadyAckedPacket, true)
suite.Require().Error(err)
suite.Require().Contains(err.Error(), "no ack actor set for channel channel-0 packet 1")

params := types.DefaultParams()
params.AllowedAsyncAckContracts = []string{contractAddr.String()}
osmosisApp.IBCHooksKeeper.SetParams(suite.chainA.GetContext(), params)

totalExpectedAcks := 1
testCases := []struct {
success bool
}{
{true},
{false},
}
for _, tc := range testCases {
// Calls that specify async Acks work and no Acks are sent
memo = fmt.Sprintf(`{"wasm": {"contract": "%s", "msg": {"async": {"use_async": true}}}}`, contractAddr)
suite.fundAccount(suite.chainB, sender)
transferMsg = NewMsgTransfer(sdk.NewCoin("token0", sdk.NewInt(2000)), sender.String(), contractAddr.String(), "channel-0", memo)

sendResult, err = suite.chainB.SendMsgsNoCheck(transferMsg)
suite.Require().NoError(err)

packet, err := ibctesting.ParsePacketFromEvents(sendResult.GetEvents())
suite.Require().NoError(err)

receiveResult = suite.RelayPacketNoAck(packet, BtoA)
newAck, err := ibctesting.ParseAckFromEvents(receiveResult.GetEvents())
suite.Require().Error(err) // No ack!
suite.Require().Nil(newAck)

// No new ack has been written
allAcks = osmosisApp.IBCKeeper.ChannelKeeper.GetAllPacketAcks(suite.chainA.GetContext())
suite.Require().Equal(totalExpectedAcks, len(allAcks))

// Store a second contract and ask that one to emit an ack for the packet that the first contract sent
contractAddr2 := suite.chainA.InstantiateContract(&suite.Suite, "{}", 1)
_, err = suite.forceContractToEmitAckForPacket(osmosisApp, suite.chainA.GetContext(), contractAddr2, packet, tc.success)
// This should fail because the new contract is not authorized to emit acks for that packet
suite.Require().Error(err)
suite.Require().Contains(err.Error(), "is not allowed to send an ack for channel channel-0 packet")

// only the contract that sent the packet can send an ack for that packet sequence
ctx := suite.chainA.GetContext()
_, err = suite.forceContractToEmitAckForPacket(osmosisApp, ctx, contractAddr, packet, tc.success)
totalExpectedAcks++
suite.Require().NoError(err)
writtenAck, err := ibctesting.ParseAckFromEvents(ctx.EventManager().Events())
suite.Require().NoError(err)

allAcks = osmosisApp.IBCKeeper.ChannelKeeper.GetAllPacketAcks(suite.chainA.GetContext())
suite.Require().Equal(totalExpectedAcks, len(allAcks))
suite.Require().False(osmoutils.IsAckError(writtenAck))
ackBase64 := gjson.ParseBytes(writtenAck).Get("result").String()
// decode base64
ackBytes, err := base64.StdEncoding.DecodeString(ackBase64)
suite.Require().NoError(err)
if tc.success {
suite.Require().Equal("YWNr", gjson.ParseBytes(ackBytes).Get("ibc_ack").String())
} else {
suite.Require().Equal("forced error", gjson.ParseBytes(ackBytes).Get("error").String())
}

}
}
Binary file modified tests/ibc-hooks/bytecode/counter.wasm
Binary file not shown.
Binary file modified tests/ibc-hooks/bytecode/echo.wasm
Binary file not shown.
Loading