diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ac4efaf..258929db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Contains all the PRs that improved the code without changing the behaviors. ### Added - [#577](https://github.com/archway-network/archway/pull/577) - Adding a cute ascii art of Archway for the cli +- [#588](https://github.com/archway-network/archway/pull/588) - Add IBC hooks, bump IBC, allows contracts to query callback fee estimations. ### Changed - [#573](https://github.com/archway-network/archway/pull/573) - Bump cosmos-sdk to v0.50.6 and ibc to v8.2.1 diff --git a/app/app.go b/app/app.go index 0fb66514..3418126e 100644 --- a/app/app.go +++ b/app/app.go @@ -98,6 +98,9 @@ import ( stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/cosmos/gogoproto/proto" + ibchooks "github.com/cosmos/ibc-apps/modules/ibc-hooks/v8" + ibchookskeeper "github.com/cosmos/ibc-apps/modules/ibc-hooks/v8/keeper" + ibchookstypes "github.com/cosmos/ibc-apps/modules/ibc-hooks/v8/types" "github.com/cosmos/ibc-go/modules/capability" capabilitykeeper "github.com/cosmos/ibc-go/modules/capability/keeper" capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" @@ -211,6 +214,7 @@ var ( ibc.AppModuleBasic{}, ibccm.AppModuleBasic{}, ibcfee.AppModuleBasic{}, + ibchooks.AppModuleBasic{}, upgrade.AppModuleBasic{}, evidence.AppModuleBasic{}, transfer.AppModuleBasic{}, @@ -327,6 +331,7 @@ func NewArchwayApp( govtypes.StoreKey, paramstypes.StoreKey, ibcexported.StoreKey, upgradetypes.StoreKey, evidencetypes.StoreKey, ibctransfertypes.StoreKey, capabilitytypes.StoreKey, feegrant.StoreKey, authzkeeper.StoreKey, wasmdTypes.StoreKey, consensusparamtypes.StoreKey, + ibchookstypes.StoreKey, icacontrollertypes.StoreKey, icahosttypes.StoreKey, ibcfeetypes.StoreKey, crisistypes.StoreKey, group.StoreKey, nftkeeper.StoreKey, cwicatypes.StoreKey, trackingTypes.StoreKey, rewardsTypes.StoreKey, callbackTypes.StoreKey, cwfees.ModuleName, cwerrorsTypes.StoreKey, @@ -667,16 +672,21 @@ func NewArchwayApp( logger, ) + app.Keepers.IBCHooksKeeper = ibchookskeeper.NewKeeper(keys[ibchookstypes.StoreKey]) + ics20WasmHooks := ibchooks.NewWasmHooks(&app.Keepers.IBCHooksKeeper, nil, Bech32Prefix) + hooksIcs4Wrapper := ibchooks.NewICS4Middleware(app.Keepers.IBCKeeper.ChannelKeeper, ics20WasmHooks) + var transferStack porttypes.IBCModule transferStack = transfer.NewIBCModule(app.Keepers.TransferKeeper) transferStack = ibcfee.NewIBCMiddleware(transferStack, app.Keepers.IBCFeeKeeper) + transferStack = ibchooks.NewIBCMiddleware(transferStack, &hooksIcs4Wrapper) // Create Interchain Accounts Stack var icaControllerStack porttypes.IBCModule icaControllerStack = cwica.NewIBCModule(app.Keepers.CWICAKeeper) icaControllerStack = icacontroller.NewIBCMiddleware(icaControllerStack, app.Keepers.ICAControllerKeeper) - //icaControllerStack = ibcfee.NewIBCMiddleware(icaControllerStack, app.Keepers.IBCFeeKeeper) + // icaControllerStack = ibcfee.NewIBCMiddleware(icaControllerStack, app.Keepers.IBCFeeKeeper) // RecvPacket, message that originates from core IBC and goes down to app, the flow is: // channel.RecvPacket -> fee.OnRecvPacket -> icaHost.OnRecvPacket @@ -797,6 +807,7 @@ func NewArchwayApp( ibcfeetypes.ModuleName, icatypes.ModuleName, // wasm + ibchookstypes.ModuleName, wasmdTypes.ModuleName, ) @@ -826,6 +837,7 @@ func NewArchwayApp( vestingtypes.ModuleName, consensusparamtypes.ModuleName, // wasm + ibchookstypes.ModuleName, wasmdTypes.ModuleName, // wasm gas tracking trackingTypes.ModuleName, @@ -870,6 +882,7 @@ func NewArchwayApp( ibcfeetypes.ModuleName, icatypes.ModuleName, // wasm after ibc transfer + ibchookstypes.ModuleName, wasmdTypes.ModuleName, // wasm gas tracking cwfees.ModuleName, // depends on wasmd. @@ -1203,6 +1216,8 @@ func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino func getAcceptedStargateQueries() wasmdKeeper.AcceptedStargateQueries { return wasmdKeeper.AcceptedStargateQueries{ - "/archway.cwerrors.v1.Query/Errors": &cwerrorsTypes.QueryErrorsRequest{}, + "/archway.cwerrors.v1.Query/Errors": &cwerrorsTypes.QueryErrorsRequest{}, + "/archway.callback.v1.Query/EstimateCallbackFees": &callbackTypes.QueryEstimateCallbackFeesRequest{}, + "/archway.callback.v1.Query/Params": &callbackTypes.QueryParamsRequest{}, } } diff --git a/app/app_upgrades.go b/app/app_upgrades.go index 43b6c744..509877a2 100644 --- a/app/app_upgrades.go +++ b/app/app_upgrades.go @@ -14,7 +14,7 @@ import ( upgrade4_0_2 "github.com/archway-network/archway/app/upgrades/4_0_2" upgrade6_0_0 "github.com/archway-network/archway/app/upgrades/6_0_0" upgrade7_0_0 "github.com/archway-network/archway/app/upgrades/7_0_0" - upgrade8_0_0 "github.com/archway-network/archway/app/upgrades/8_0_0" + upgrade9_0_0 "github.com/archway-network/archway/app/upgrades/9_0_0" ) // UPGRADES @@ -28,7 +28,8 @@ var Upgrades = []upgrades.Upgrade{ upgrade4_0_2.Upgrade, // v4.0.2 upgrade6_0_0.Upgrade, // v6.0.0 upgrade7_0_0.Upgrade, // v7.0.0 - upgrade8_0_0.Upgrade, // v8.0.0 + // upgrade8_0_0.Upgrade, // v8.0.0: was reserved for a consensus breaking wasmd upgrade + upgrade9_0_0.Upgrade, // v9.0.0 } func (app *ArchwayApp) RegisterUpgradeHandlers() { diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index 0f2471b6..02b8de2f 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -22,6 +22,7 @@ import ( "github.com/archway-network/archway/x/cwfees" + ibchookskeeper "github.com/cosmos/ibc-apps/modules/ibc-hooks/v8/keeper" icacontrollerkeeper "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/controller/keeper" icahostkeeper "github.com/cosmos/ibc-go/v8/modules/apps/27-interchain-accounts/host/keeper" ibcfeekeeper "github.com/cosmos/ibc-go/v8/modules/apps/29-fee/keeper" @@ -52,6 +53,7 @@ type ArchwayKeepers struct { ConsensusParamsKeeper consensusparamkeeper.Keeper IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly IBCFeeKeeper ibcfeekeeper.Keeper + IBCHooksKeeper ibchookskeeper.Keeper ICAControllerKeeper icacontrollerkeeper.Keeper ICAHostKeeper icahostkeeper.Keeper EvidenceKeeper evidencekeeper.Keeper diff --git a/app/upgrades/8_0_0/upgrades.go b/app/upgrades/9_0_0/upgrades.go similarity index 69% rename from app/upgrades/8_0_0/upgrades.go rename to app/upgrades/9_0_0/upgrades.go index ee9e17d3..26371145 100644 --- a/app/upgrades/8_0_0/upgrades.go +++ b/app/upgrades/9_0_0/upgrades.go @@ -7,18 +7,19 @@ import ( upgradetypes "cosmossdk.io/x/upgrade/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" + ibchookstypes "github.com/cosmos/ibc-apps/modules/ibc-hooks/v8/types" "github.com/archway-network/archway/app/keepers" "github.com/archway-network/archway/app/upgrades" ) -const Name = "v8.0.0" -const NameAsciiArt = ` - ### ### ### - # # # # # # # # - # # ### # # # # - # # # # # # # - ### # ### # ### +const Name = "v9.0.0" +const NameAsciiArt = ` + ### ### ### + # # # # # # # # + # # ## # # # # + # # # # # # + ### # ### # ### ` @@ -35,5 +36,7 @@ var Upgrade = upgrades.Upgrade{ return migrations, nil } }, - StoreUpgrades: storetypes.StoreUpgrades{}, + StoreUpgrades: storetypes.StoreUpgrades{ + Added: []string{ibchookstypes.StoreKey}, + }, } diff --git a/cmd/archwayd/main.go b/cmd/archwayd/main.go index 434afd11..37e1a5a8 100644 --- a/cmd/archwayd/main.go +++ b/cmd/archwayd/main.go @@ -14,14 +14,14 @@ import ( ) const ArchwayASCII = ` - ##### ##### ##### ## ## ## ## ##### ## ## -####### ####### ####### ## ## ## ## ####### ## ## -## ## ## ## ## ## ## ## # ## ## ## ## ## -## ## ###### ## #### ## ####### ## ## #### -####### ## ## ## ## ## ## ####### ####### ## -## ## ## ## ####### ## ## ### ### ## ## ## -## ## ## ## ##### ## ## ## ## ## ## ## - + ##### ##### ##### ## ## ## ## ##### ## ## +####### ####### ####### ## ## ## ## ####### ## ## +## ## ## ## ## ## ## ## # ## ## ## ## ## +## ## ###### ## #### ## ####### ## ## #### +####### ## ## ## ## ## ## ####### ####### ## +## ## ## ## ####### ## ## ### ### ## ## ## +## ## ## ## ##### ## ## ## ## ## ## ## + ` func main() { @@ -30,7 +30,7 @@ func main() { rootCmd.AddCommand(ensureLibWasmVM()) if err := svrcmd.Execute(rootCmd, "ARCHWAY", app.DefaultNodeHome); err != nil { - fmt.Fprintln(rootCmd.OutOrStderr(), err) + _, _ = fmt.Fprintln(rootCmd.OutOrStderr(), err) os.Exit(1) } } diff --git a/e2e/ibchooks/ibchooks_test.go b/e2e/ibchooks/ibchooks_test.go new file mode 100644 index 00000000..a28cb296 --- /dev/null +++ b/e2e/ibchooks/ibchooks_test.go @@ -0,0 +1,433 @@ +package ibchooks + +import ( + _ "embed" + "encoding/json" + "fmt" + "testing" + + wasmKeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + "github.com/archway-network/archway/app" + ibc_hooks "github.com/cosmos/ibc-apps/modules/ibc-hooks/v8" + ibchookskeeper "github.com/cosmos/ibc-apps/modules/ibc-hooks/v8/keeper" + "github.com/cosmos/ibc-apps/modules/ibc-hooks/v8/tests/unit/mocks" + "github.com/stretchr/testify/suite" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/types" + + capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" + ibctransfer "github.com/cosmos/ibc-go/v8/modules/apps/transfer" + transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" + ibcclienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" + ibcmock "github.com/cosmos/ibc-go/v8/testing/mock" +) + +//go:embed testdata/counter.wasm +var counterWasm []byte + +//go:embed testdata/echo.wasm +var echoWasm []byte + +type HooksTestSuite struct { + suite.Suite + + App *app.ArchwayApp + Ctx sdk.Context + EchoContractAddr sdk.AccAddress + CounterContractAddr sdk.AccAddress + TestAddress *types.BaseAccount +} + +func TestIBCHooksTestSuite(t *testing.T) { + suite.Run(t, new(HooksTestSuite)) +} + +func (suite *HooksTestSuite) SetupEnv() { + // Setup the environment + app, ctx, acc := Setup(suite.T()) + + // create the echo contract + contractKeeper := wasmKeeper.NewDefaultPermissionKeeper(&app.Keepers.WASMKeeper) + contractID, _, err := contractKeeper.Create(ctx, acc.GetAddress(), counterWasm, nil) + suite.NoError(err) + counterContractAddr, _, err := contractKeeper.Instantiate( + ctx, + contractID, + acc.GetAddress(), + nil, + []byte(`{"count": 0}`), + "counter contract", + nil, + ) + suite.NoError(err) + suite.Equal("cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr", counterContractAddr.String()) + + // create the counter contract + contractID, _, err = contractKeeper.Create(ctx, acc.GetAddress(), echoWasm, nil) + suite.NoError(err) + echoContractAddr, _, err := contractKeeper.Instantiate( + ctx, + contractID, + acc.GetAddress(), + nil, + []byte(`{}`), + "echo contract", + nil, + ) + suite.NoError(err) + suite.Equal("cosmos1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrqez7la9", echoContractAddr.String()) + + suite.App = app + suite.Ctx = ctx + suite.EchoContractAddr = echoContractAddr + suite.CounterContractAddr = counterContractAddr + suite.TestAddress = acc +} + +func (suite *HooksTestSuite) TestOnRecvPacketEcho() { + // create en env + suite.SetupEnv() + + // Create the packet + recvPacket := channeltypes.Packet{ + Data: transfertypes.FungibleTokenPacketData{ + Denom: "transfer/channel-0/stake", + Amount: "1", + Sender: suite.TestAddress.GetAddress().String(), + Receiver: suite.EchoContractAddr.String(), + Memo: fmt.Sprintf(`{"wasm":{"contract": "%s", "msg":{"echo":{"msg":"test"}}}}`, suite.EchoContractAddr.String()), + }.GetBytes(), + SourcePort: "transfer", + SourceChannel: "channel-0", + } + + // send funds to the escrow address to simulate a transfer from the ibc module + escrowAddress := transfertypes.GetEscrowAddress(recvPacket.GetDestPort(), recvPacket.GetDestChannel()) + testEscrowAmount := sdk.NewInt64Coin("stake", 2) + err := suite.App.Keepers.BankKeeper.SendCoins(suite.Ctx, suite.TestAddress.GetAddress(), escrowAddress, sdk.NewCoins(testEscrowAmount)) + suite.NoError(err) + + // since ibc-go >= 7.1.0 escrow needs to be explicitly tracked + if transferKeeper, ok := any(&suite.App.Keepers.TransferKeeper).(TransferKeeperWithTotalEscrowTracking); ok { + transferKeeper.SetTotalEscrowForDenom(suite.Ctx, testEscrowAmount) + } + + // create the wasm hooks + wasmHooks := ibc_hooks.NewWasmHooks( + &suite.App.Keepers.IBCHooksKeeper, + &suite.App.Keepers.WASMKeeper, + "cosmos", + ) + + // create the ics4 middleware + ics4Middleware := ibc_hooks.NewICS4Middleware( + suite.App.Keepers.IBCKeeper.ChannelKeeper, + wasmHooks, + ) + + // create the ibc middleware + transferIBCModule := ibctransfer.NewIBCModule(suite.App.Keepers.TransferKeeper) + ibcmiddleware := ibc_hooks.NewIBCMiddleware( + transferIBCModule, + &ics4Middleware, + ) + + // call the hook twice + res := ibcmiddleware.OnRecvPacket( + suite.Ctx, + recvPacket, + suite.TestAddress.GetAddress(), + ) + suite.True(res.Success()) + var ack map[string]string // This can't be unmarshalled to Acknowledgement because it's fetched from the events + err = json.Unmarshal(res.Acknowledgement(), &ack) + suite.Require().NoError(err) + suite.Require().NotContains(ack, "error") + suite.Require().Equal(ack["result"], "eyJjb250cmFjdF9yZXN1bHQiOiJkR2hwY3lCemFHOTFiR1FnWldOb2J3PT0iLCJpYmNfYWNrIjoiZXlKeVpYTjFiSFFpT2lKQlVUMDlJbjA9In0=") +} + +func (suite *HooksTestSuite) TestOnRecvPacketCounterContract() { + // create en env + suite.SetupEnv() + + // Create the packet + recvPacket := channeltypes.Packet{ + Data: transfertypes.FungibleTokenPacketData{ + Denom: "transfer/channel-0/stake", + Amount: "1", + Sender: suite.TestAddress.GetAddress().String(), + Receiver: suite.CounterContractAddr.String(), + Memo: fmt.Sprintf(`{"wasm":{"contract": "%s", "msg":{"increment":{}}}}`, suite.CounterContractAddr.String()), + }.GetBytes(), + SourcePort: "transfer", + SourceChannel: "channel-0", + } + + // send funds to the escrow address to simulate a transfer from the ibc module + escrowAddress := transfertypes.GetEscrowAddress(recvPacket.GetDestPort(), recvPacket.GetDestChannel()) + testEscrowAmount := sdk.NewInt64Coin("stake", 2) + err := suite.App.Keepers.BankKeeper.SendCoins(suite.Ctx, suite.TestAddress.GetAddress(), escrowAddress, sdk.NewCoins(testEscrowAmount)) + suite.NoError(err) + + // since ibc-go >= 7.1.0 escrow needs to be explicitly tracked + if transferKeeper, ok := any(&suite.App.Keepers.TransferKeeper).(TransferKeeperWithTotalEscrowTracking); ok { + transferKeeper.SetTotalEscrowForDenom(suite.Ctx, testEscrowAmount) + } + + // create the wasm hooks + wasmHooks := ibc_hooks.NewWasmHooks( + &suite.App.Keepers.IBCHooksKeeper, + &suite.App.Keepers.WASMKeeper, + "cosmos", + ) + + // create the ics4 middleware + ics4Middleware := ibc_hooks.NewICS4Middleware( + suite.App.Keepers.IBCKeeper.ChannelKeeper, + wasmHooks, + ) + + // create the ibc middleware + transferIBCModule := ibctransfer.NewIBCModule(suite.App.Keepers.TransferKeeper) + ibcmiddleware := ibc_hooks.NewIBCMiddleware( + transferIBCModule, + &ics4Middleware, + ) + + // call the hook twice + res := ibcmiddleware.OnRecvPacket( + suite.Ctx, + recvPacket, + suite.TestAddress.GetAddress(), + ) + suite.True(res.Success()) + res = ibcmiddleware.OnRecvPacket( + suite.Ctx, + recvPacket, + suite.TestAddress.GetAddress(), + ) + suite.True(res.Success()) + + // get the derived account to check the count + senderBech32, err := ibchookskeeper.DeriveIntermediateSender( + recvPacket.GetDestChannel(), + suite.TestAddress.GetAddress().String(), + "cosmos", + ) + suite.NoError(err) + // query the smart contract to assert the count + count, err := suite.App.Keepers.WASMKeeper.QuerySmart( + suite.Ctx, + suite.CounterContractAddr, + []byte(fmt.Sprintf(`{"get_count":{"addr": "%s"}}`, senderBech32)), + ) + suite.NoError(err) + suite.Equal(`{"count":1}`, string(count)) +} + +func (suite *HooksTestSuite) TestOnAcknowledgementPacketCounterContract() { + suite.SetupEnv() + + callbackPacket := channeltypes.Packet{ + Data: transfertypes.FungibleTokenPacketData{ + Denom: "transfer/channel-0/stake", + Amount: "1", + Sender: suite.TestAddress.GetAddress().String(), + Receiver: suite.CounterContractAddr.String(), + Memo: fmt.Sprintf(`{"ibc_callback": "%s"}`, suite.CounterContractAddr), + }.GetBytes(), + Sequence: 1, + SourcePort: "transfer", + SourceChannel: "channel-0", + } + + // send funds to the escrow address to simulate a transfer from the ibc module + escrowAddress := transfertypes.GetEscrowAddress(callbackPacket.GetDestPort(), callbackPacket.GetDestChannel()) + testEscrowAmount := sdk.NewInt64Coin("stake", 2) + err := suite.App.Keepers.BankKeeper.SendCoins(suite.Ctx, suite.TestAddress.GetAddress(), escrowAddress, sdk.NewCoins(testEscrowAmount)) + suite.NoError(err) + + // since ibc-go >= 7.1.0 escrow needs to be explicitly tracked + if transferKeeper, ok := any(&suite.App.Keepers.TransferKeeper).(TransferKeeperWithTotalEscrowTracking); ok { + transferKeeper.SetTotalEscrowForDenom(suite.Ctx, testEscrowAmount) + } + + // create the wasm hooks + wasmHooks := ibc_hooks.NewWasmHooks( + &suite.App.Keepers.IBCHooksKeeper, + &suite.App.Keepers.WASMKeeper, + "cosmos", + ) + + // create the ics4 middleware + ics4Middleware := ibc_hooks.NewICS4Middleware( + &mocks.ICS4WrapperMock{}, + wasmHooks, + ) + + // create the ibc middleware + transferIBCModule := ibctransfer.NewIBCModule(suite.App.Keepers.TransferKeeper) + ibcmiddleware := ibc_hooks.NewIBCMiddleware( + transferIBCModule, + &ics4Middleware, + ) + + // call the hook + seq, err := ibcmiddleware.SendPacket( + suite.Ctx, + &capabilitytypes.Capability{Index: 1}, + callbackPacket.SourcePort, + callbackPacket.SourceChannel, + ibcclienttypes.Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + 1, + callbackPacket.Data, + ) + + // require to be the first sequence + suite.Equal(uint64(1), seq) + // assert the request was successful + suite.NoError(err) + + // Create the packet + recvPacket := channeltypes.Packet{ + Data: transfertypes.FungibleTokenPacketData{ + Denom: "transfer/channel-0/stake", + Amount: "1", + Sender: suite.TestAddress.GetAddress().String(), + Receiver: suite.CounterContractAddr.String(), + Memo: fmt.Sprintf(`{"wasm":{"contract": "%s", "msg":{"increment":{}}}}`, suite.CounterContractAddr.String()), + }.GetBytes(), + Sequence: 1, + SourcePort: "transfer", + SourceChannel: "channel-0", + } + suite.NoError(err) + err = wasmHooks.OnAcknowledgementPacketOverride( + ibcmiddleware, + suite.Ctx, + recvPacket, + ibcmock.MockAcknowledgement.Acknowledgement(), + suite.TestAddress.GetAddress(), + ) + // assert the request was successful + suite.NoError(err) + + // query the smart contract to assert the count + count, err := suite.App.Keepers.WASMKeeper.QuerySmart( + suite.Ctx, + suite.CounterContractAddr, + []byte(fmt.Sprintf(`{"get_count":{"addr": %q}}`, suite.CounterContractAddr.String())), + ) + suite.NoError(err) + suite.Equal(`{"count":1}`, string(count)) +} + +func (suite *HooksTestSuite) TestOnTimeoutPacketOverrideCounterContract() { + suite.SetupEnv() + callbackPacket := channeltypes.Packet{ + Data: transfertypes.FungibleTokenPacketData{ + Denom: "transfer/channel-0/stake", + Amount: "1", + Sender: suite.TestAddress.GetAddress().String(), + Receiver: suite.CounterContractAddr.String(), + Memo: fmt.Sprintf(`{"ibc_callback": "%s"}`, suite.CounterContractAddr), + }.GetBytes(), + Sequence: 1, + SourcePort: "transfer", + SourceChannel: "channel-0", + } + + // send funds to the escrow address to simulate a transfer from the ibc module + escrowAddress := transfertypes.GetEscrowAddress(callbackPacket.GetDestPort(), callbackPacket.GetDestChannel()) + testEscrowAmount := sdk.NewInt64Coin("stake", 2) + err := suite.App.Keepers.BankKeeper.SendCoins(suite.Ctx, suite.TestAddress.GetAddress(), escrowAddress, sdk.NewCoins(testEscrowAmount)) + suite.NoError(err) + + // since ibc-go >= 7.1.0 escrow needs to be explicitly tracked + if transferKeeper, ok := any(&suite.App.Keepers.TransferKeeper).(TransferKeeperWithTotalEscrowTracking); ok { + transferKeeper.SetTotalEscrowForDenom(suite.Ctx, testEscrowAmount) + } + + // create the wasm hooks + wasmHooks := ibc_hooks.NewWasmHooks( + &suite.App.Keepers.IBCHooksKeeper, + &suite.App.Keepers.WASMKeeper, + "cosmos", + ) + + // create the ics4 middleware + ics4Middleware := ibc_hooks.NewICS4Middleware( + &mocks.ICS4WrapperMock{}, + wasmHooks, + ) + + // create the ibc middleware + transferIBCModule := ibctransfer.NewIBCModule(suite.App.Keepers.TransferKeeper) + ibcmiddleware := ibc_hooks.NewIBCMiddleware( + transferIBCModule, + &ics4Middleware, + ) + + // call the hook + seq, err := ibcmiddleware.SendPacket( + suite.Ctx, + &capabilitytypes.Capability{Index: 1}, + callbackPacket.SourcePort, + callbackPacket.SourceChannel, + ibcclienttypes.Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + 1, + callbackPacket.Data, + ) + + // require to be the first sequence + suite.Equal(uint64(1), seq) + // assert the request was successful + suite.NoError(err) + + // Create the packet + recvPacket := channeltypes.Packet{ + Data: transfertypes.FungibleTokenPacketData{ + Denom: "transfer/channel-0/stake", + Amount: "1", + Sender: suite.TestAddress.GetAddress().String(), + Receiver: suite.CounterContractAddr.String(), + Memo: fmt.Sprintf(`{"wasm":{"contract": "%s", "msg":{"increment":{}}}}`, suite.CounterContractAddr.String()), + }.GetBytes(), + Sequence: 1, + SourcePort: "transfer", + SourceChannel: "channel-0", + } + suite.NoError(err) + err = wasmHooks.OnTimeoutPacketOverride( + ibcmiddleware, + suite.Ctx, + recvPacket, + suite.TestAddress.GetAddress(), + ) + // assert the request was successful + suite.NoError(err) + + // query the smart contract to assert the count + count, err := suite.App.Keepers.WASMKeeper.QuerySmart( + suite.Ctx, + suite.CounterContractAddr, + []byte(fmt.Sprintf(`{"get_count":{"addr": %q}}`, suite.CounterContractAddr.String())), + ) + suite.NoError(err) + suite.Equal(`{"count":10}`, string(count)) +} + +// TransferKeeperWithTotalEscrowTracking defines an interface to check for existing methods +// in TransferKeeper. +type TransferKeeperWithTotalEscrowTracking interface { + SetTotalEscrowForDenom(ctx sdk.Context, coin sdk.Coin) + GetTotalEscrowForDenom(ctx sdk.Context, denom string) sdk.Coin +} diff --git a/e2e/ibchooks/mocks/mocks.go b/e2e/ibchooks/mocks/mocks.go new file mode 100644 index 00000000..7d5bbddf --- /dev/null +++ b/e2e/ibchooks/mocks/mocks.go @@ -0,0 +1,43 @@ +package mocks + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + capabilitytypes "github.com/cosmos/ibc-go/modules/capability/types" + ibcclienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" + porttypes "github.com/cosmos/ibc-go/v8/modules/core/05-port/types" + "github.com/cosmos/ibc-go/v8/modules/core/exported" +) + +var _ porttypes.ICS4Wrapper = &ICS4WrapperMock{} + +type ICS4WrapperMock struct{} + +func (m *ICS4WrapperMock) SendPacket( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + sourcePort string, + sourceChannel string, + timeoutHeight ibcclienttypes.Height, + timeoutTimestamp uint64, + data []byte, +) (sequence uint64, err error) { + return 1, nil +} + +func (m *ICS4WrapperMock) WriteAcknowledgement( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + packet exported.PacketI, + ack exported.Acknowledgement, +) error { + return nil +} + +func (m *ICS4WrapperMock) GetAppVersion( + ctx sdk.Context, + portID, + channelID string, +) (string, bool) { + return "", false +} diff --git a/e2e/ibchooks/setup.go b/e2e/ibchooks/setup.go new file mode 100644 index 00000000..207641dc --- /dev/null +++ b/e2e/ibchooks/setup.go @@ -0,0 +1,210 @@ +package ibchooks + +import ( + "encoding/json" + "fmt" + "testing" + "time" + + "cosmossdk.io/log" + sdkmath "cosmossdk.io/math" + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + "github.com/archway-network/archway/app" + abci "github.com/cometbft/cometbft/abci/types" + cmttypes "github.com/cometbft/cometbft/types" + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/cosmos/cosmos-sdk/server" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/cosmos/ibc-go/v8/testing/mock" + "github.com/stretchr/testify/require" +) + +type App = app.ArchwayApp + +// Setup initializes a new SimApp. A Nop logger is set in SimApp. +func Setup(t *testing.T, opts ...wasmkeeper.Option) (*App, sdk.Context, *authtypes.BaseAccount) { + t.Helper() + + privVal := mock.NewPV() + pubKey, err := privVal.GetPubKey() + require.NoError(t, err) + + // create validator set with single validator + validator := cmttypes.NewValidator(pubKey, 1) + valSet := cmttypes.NewValidatorSet([]*cmttypes.Validator{validator}) + + // generate genesis account + senderPrivKey := secp256k1.GenPrivKey() + acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0) + balance := banktypes.Balance{ + Address: acc.GetAddress().String(), + Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(100000000000000))), + } + chainID := "testing" + + app := SetupWithGenesisValSet(t, valSet, []authtypes.GenesisAccount{acc}, chainID, opts, balance) + + ctx := app.BaseApp.NewContext(false) + ctx = ctx.WithBlockTime(time.Now()) + + return app, ctx, acc +} + +// SetupWithGenesisValSet initializes a new App with a validator set and genesis accounts +// that also act as delegators. For simplicity, each validator is bonded with a delegation +// of one consensus engine unit in the default token of the App from first genesis +// account. A Nop logger is set in App. +func SetupWithGenesisValSet( + t *testing.T, + valSet *cmttypes.ValidatorSet, + genAccs []authtypes.GenesisAccount, + chainID string, + opts []wasmkeeper.Option, + balances ...banktypes.Balance, +) *app.ArchwayApp { + t.Helper() + + app, genesisState := setup(t, chainID, true, 5) + genesisState, err := GenesisStateWithValSet(app.AppCodec(), genesisState, valSet, genAccs, balances...) + require.NoError(t, err) + + stateBytes, err := json.MarshalIndent(genesisState, "", " ") + require.NoError(t, err) + + // init chain will set the validator set and initialize the genesis accounts + consensusParams := simtestutil.DefaultConsensusParams + consensusParams.Block.MaxGas = 100 * simtestutil.DefaultGenTxGas + _, err = app.InitChain(&abci.RequestInitChain{ + ChainId: chainID, + Time: time.Now().UTC(), + Validators: []abci.ValidatorUpdate{}, + ConsensusParams: consensusParams, + InitialHeight: app.LastBlockHeight() + 1, + AppStateBytes: stateBytes, + }) + require.NoError(t, err) + + _, err = app.FinalizeBlock(&abci.RequestFinalizeBlock{ + Height: app.LastBlockHeight() + 1, + Hash: app.LastCommitID().Hash, + NextValidatorsHash: valSet.Hash(), + }) + require.NoError(t, err) + + return app +} + +// initSetup initializes a new SimApp. A Nop logger is set in SimApp. +func setup(t *testing.T, chainID string, withGenesis bool, invCheckPeriod uint) (*app.ArchwayApp, map[string]json.RawMessage) { + appOptions := make(simtestutil.AppOptionsMap) + appOptions[flags.FlagHome] = app.DefaultNodeHome + appOptions[server.FlagInvCheckPeriod] = invCheckPeriod + + archApp := app.NewArchwayApp( + log.NewNopLogger(), + dbm.NewMemDB(), + nil, + true, map[int64]bool{}, + app.DefaultNodeHome, + 1, + app.MakeEncodingConfig(), + app.EmptyBaseAppOptions{}, + []wasmkeeper.Option{}, + baseapp.SetChainID(chainID), + ) + if withGenesis { + return archApp, app.NewDefaultGenesisState(archApp.AppCodec()) + } + return archApp, map[string]json.RawMessage{} +} + +// GenesisStateWithValSet returns a new genesis state with the validator set +// copied from simtestutil with delegation not added to supply +func GenesisStateWithValSet( + codec codec.Codec, + genesisState map[string]json.RawMessage, + valSet *cmttypes.ValidatorSet, + genAccs []authtypes.GenesisAccount, + balances ...banktypes.Balance, +) (map[string]json.RawMessage, error) { + // set genesis accounts + authGenesis := authtypes.NewGenesisState(authtypes.DefaultParams(), genAccs) + genesisState[authtypes.ModuleName] = codec.MustMarshalJSON(authGenesis) + + validators := make([]stakingtypes.Validator, 0, len(valSet.Validators)) + delegations := make([]stakingtypes.Delegation, 0, len(valSet.Validators)) + + bondAmt := sdk.DefaultPowerReduction + + for _, val := range valSet.Validators { + pk, err := cryptocodec.FromCmtPubKeyInterface(val.PubKey) + if err != nil { + return nil, fmt.Errorf("failed to convert pubkey: %w", err) + } + + pkAny, err := codectypes.NewAnyWithValue(pk) + if err != nil { + return nil, fmt.Errorf("failed to create new any: %w", err) + } + + validator := stakingtypes.Validator{ + OperatorAddress: sdk.ValAddress(val.Address).String(), + ConsensusPubkey: pkAny, + Jailed: false, + Status: stakingtypes.Bonded, + Tokens: bondAmt, + DelegatorShares: sdkmath.LegacyOneDec(), + Description: stakingtypes.Description{}, + UnbondingHeight: int64(0), + UnbondingTime: time.Unix(0, 0).UTC(), + Commission: stakingtypes.NewCommission(sdkmath.LegacyZeroDec(), sdkmath.LegacyZeroDec(), sdkmath.LegacyZeroDec()), + MinSelfDelegation: sdkmath.ZeroInt(), + } + validators = append(validators, validator) + delegations = append(delegations, stakingtypes.NewDelegation(genAccs[0].GetAddress().String(), sdk.ValAddress(val.Address).String(), sdkmath.LegacyOneDec())) + + } + + // set validators and delegations + stakingGenesis := stakingtypes.NewGenesisState(stakingtypes.DefaultParams(), validators, delegations) + genesisState[stakingtypes.ModuleName] = codec.MustMarshalJSON(stakingGenesis) + + signingInfos := make([]slashingtypes.SigningInfo, len(valSet.Validators)) + for i, val := range valSet.Validators { + signingInfos[i] = slashingtypes.SigningInfo{ + Address: sdk.ConsAddress(val.Address).String(), + ValidatorSigningInfo: slashingtypes.ValidatorSigningInfo{}, + } + } + slashingGenesis := slashingtypes.NewGenesisState(slashingtypes.DefaultParams(), signingInfos, nil) + genesisState[slashingtypes.ModuleName] = codec.MustMarshalJSON(slashingGenesis) + + // add bonded amount to bonded pool module account + balances = append(balances, banktypes.Balance{ + Address: authtypes.NewModuleAddress(stakingtypes.BondedPoolName).String(), + Coins: sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, bondAmt.MulRaw(int64(len(valSet.Validators))))}, + }) + + totalSupply := sdk.NewCoins() + for _, b := range balances { + // add genesis acc tokens to total supply + totalSupply = totalSupply.Add(b.Coins...) + } + + // update total supply + bankGenesis := banktypes.NewGenesisState(banktypes.DefaultGenesisState().Params, balances, totalSupply, []banktypes.Metadata{}, []banktypes.SendEnabled{}) + genesisState[banktypes.ModuleName] = codec.MustMarshalJSON(bankGenesis) + println(string(genesisState[banktypes.ModuleName])) + return genesisState, nil +} diff --git a/e2e/ibchooks/testdata/counter.wasm b/e2e/ibchooks/testdata/counter.wasm new file mode 100644 index 00000000..40750957 Binary files /dev/null and b/e2e/ibchooks/testdata/counter.wasm differ diff --git a/e2e/ibchooks/testdata/echo.wasm b/e2e/ibchooks/testdata/echo.wasm new file mode 100644 index 00000000..e8a1bdec Binary files /dev/null and b/e2e/ibchooks/testdata/echo.wasm differ diff --git a/go.mod b/go.mod index 16a799e0..3e71c7e7 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/archway-network/archway -go 1.21 +go 1.22 + +toolchain go1.23.0 require ( cosmossdk.io/api v0.7.5 @@ -18,15 +20,16 @@ require ( cosmossdk.io/x/tx v0.13.3 cosmossdk.io/x/upgrade v0.1.2 github.com/CosmWasm/cosmwasm-go v0.5.1-0.20220822092235-974247a04ac7 - github.com/CosmWasm/wasmd v0.50.0 + github.com/CosmWasm/wasmd v0.51.0 github.com/CosmWasm/wasmvm v1.5.4 github.com/cometbft/cometbft v0.38.10 github.com/cosmos/cosmos-db v1.0.2 github.com/cosmos/cosmos-proto v1.0.0-beta.5 github.com/cosmos/cosmos-sdk v0.50.7 github.com/cosmos/gogoproto v1.4.12 + github.com/cosmos/ibc-apps/modules/ibc-hooks/v8 v8.0.0-20240820215527-05462618a4e8 github.com/cosmos/ibc-go/modules/capability v1.0.0 - github.com/cosmos/ibc-go/v8 v8.3.2 + github.com/cosmos/ibc-go/v8 v8.4.0 github.com/dvsekhvalnov/jose2go v1.6.0 github.com/gogo/protobuf v1.3.2 github.com/golang/protobuf v1.5.4 @@ -53,7 +56,7 @@ require ( cosmossdk.io/depinject v1.0.0-alpha.4 // indirect filippo.io/edwards25519 v1.0.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect - github.com/99designs/keyring v1.2.1 // indirect + github.com/99designs/keyring v1.2.2 // indirect github.com/CosmWasm/tinyjson v0.9.0 // indirect github.com/DataDog/datadog-go v3.2.0+incompatible // indirect github.com/DataDog/zstd v1.5.5 // indirect @@ -74,7 +77,7 @@ require ( github.com/cockroachdb/pebble v1.1.0 // indirect github.com/cockroachdb/redact v1.1.5 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect - github.com/cometbft/cometbft-db v0.9.1 // indirect + github.com/cometbft/cometbft-db v0.11.0 // indirect github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/gogogateway v1.2.0 // indirect diff --git a/go.sum b/go.sum index fffc5f70..eff71861 100644 --- a/go.sum +++ b/go.sum @@ -802,8 +802,8 @@ gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zum git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= -github.com/99designs/keyring v1.2.1 h1:tYLp1ULvO7i3fI5vE21ReQuj99QFSs7lGm0xWyJo87o= -github.com/99designs/keyring v1.2.1/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA= +github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0= +github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -951,8 +951,8 @@ github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1: github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/cometbft/cometbft v0.38.10 h1:2ePuglchT+j0Iao+cfmt/nw5U7K2lnGDzXSUPGVdXaU= github.com/cometbft/cometbft v0.38.10/go.mod h1:jHPx9vQpWzPHEAiYI/7EDKaB1NXhK6o3SArrrY8ExKc= -github.com/cometbft/cometbft-db v0.9.1 h1:MIhVX5ja5bXNHF8EYrThkG9F7r9kSfv8BX4LWaxWJ4M= -github.com/cometbft/cometbft-db v0.9.1/go.mod h1:iliyWaoV0mRwBJoizElCwwRA9Tf7jZJOURcRZF9m60U= +github.com/cometbft/cometbft-db v0.11.0 h1:M3Lscmpogx5NTbb1EGyGDaFRdsoLWrUWimFEyf7jej8= +github.com/cometbft/cometbft-db v0.11.0/go.mod h1:GDPJAC/iFHNjmZZPN8V8C1yr/eyityhi2W1hz2MGKSc= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -978,10 +978,12 @@ github.com/cosmos/gogoproto v1.4.12 h1:vB6Lbe/rtnYGjQuFxkPiPYiCybqFT8QvLipDZP8Jp github.com/cosmos/gogoproto v1.4.12/go.mod h1:LnZob1bXRdUoqMMtwYlcR3wjiElmlC+FkjaZRv1/eLY= github.com/cosmos/iavl v1.1.2 h1:zL9FK7C4L/P4IF1Dm5fIwz0WXCnn7Bp1M2FxH0ayM7Y= github.com/cosmos/iavl v1.1.2/go.mod h1:jLeUvm6bGT1YutCaL2fIar/8vGUE8cPZvh/gXEWDaDM= +github.com/cosmos/ibc-apps/modules/ibc-hooks/v8 v8.0.0-20240820215527-05462618a4e8 h1:n9Gn7E7g9ZxrAdXRAfHe3xOF5HmLnvdDUVgq3ea2IZw= +github.com/cosmos/ibc-apps/modules/ibc-hooks/v8 v8.0.0-20240820215527-05462618a4e8/go.mod h1:9+Z14xz3Y+5uEn5i1CvLcDN1aTthEhYUdI7pphySkY8= github.com/cosmos/ibc-go/modules/capability v1.0.0 h1:r/l++byFtn7jHYa09zlAdSeevo8ci1mVZNO9+V0xsLE= github.com/cosmos/ibc-go/modules/capability v1.0.0/go.mod h1:D81ZxzjZAe0ZO5ambnvn1qedsFQ8lOwtqicG6liLBco= -github.com/cosmos/ibc-go/v8 v8.3.2 h1:8X1oHHKt2Bh9hcExWS89rntLaCKZp2EjFTUSxKlPhGI= -github.com/cosmos/ibc-go/v8 v8.3.2/go.mod h1:WVVIsG39jGrF9Cjggjci6LzySyWGloz194sjTxiGNIE= +github.com/cosmos/ibc-go/v8 v8.4.0 h1:K2PfX0AZ+1XKZytHGEMuSjQXG/MZshPb83RSTQt2+cE= +github.com/cosmos/ibc-go/v8 v8.4.0/go.mod h1:zh6x1osR0hNvEcFrC/lhGD08sMfQmr9wHVvZ/mRWMCs= github.com/cosmos/ics23/go v0.10.0 h1:iXqLLgp2Lp+EdpIuwXTYIQU+AiHj9mOC2X9ab++bZDM= github.com/cosmos/ics23/go v0.10.0/go.mod h1:ZfJSmng/TBNTBkFemHHHj5YY7VAU/MBU980F4VU1NG0= github.com/cosmos/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM= diff --git a/interchaintest/setup.go b/interchaintest/setup.go index 8eda85d5..a7c56a24 100644 --- a/interchaintest/setup.go +++ b/interchaintest/setup.go @@ -5,14 +5,14 @@ import ( "cosmossdk.io/math" "github.com/cosmos/cosmos-sdk/types" - interchaintest "github.com/strangelove-ventures/interchaintest/v8" + "github.com/strangelove-ventures/interchaintest/v8" "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" "github.com/strangelove-ventures/interchaintest/v8/ibc" ) const ( initialVersion = "v7.0.1" // The last release of the chain. The one the mainnet is running on - upgradeName = "v8.0.0" // The next upgrade name. Should match the upgrade handler. + upgradeName = "v9.0.0" // The next upgrade name. Should match the upgrade handler. chainName = "archway" )