wormchain_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. package ictest
  2. import (
  3. "fmt"
  4. "strconv"
  5. "testing"
  6. "github.com/btcsuite/btcd/btcutil/base58"
  7. "github.com/strangelove-ventures/interchaintest/v4"
  8. "github.com/strangelove-ventures/interchaintest/v4/chain/cosmos"
  9. "github.com/strangelove-ventures/interchaintest/v4/ibc"
  10. "github.com/strangelove-ventures/interchaintest/v4/testutil"
  11. "github.com/stretchr/testify/require"
  12. "github.com/wormhole-foundation/wormchain/interchaintest/guardians"
  13. "github.com/wormhole-foundation/wormchain/interchaintest/helpers"
  14. "github.com/wormhole-foundation/wormhole/sdk/vaa"
  15. sdk "github.com/cosmos/cosmos-sdk/types"
  16. transfertypes "github.com/cosmos/ibc-go/v4/modules/apps/transfer/types"
  17. )
  18. var (
  19. GaiaChainID = uint16(11)
  20. OsmoChainID = uint16(12)
  21. ExternalChainId = uint16(123)
  22. ExternalChainEmitterAddr = "0x123EmitterAddress"
  23. Asset1Name = "Wrapped BTC"
  24. Asset1Symbol = "XBTC"
  25. Asset1ContractAddr = "0xXBTC"
  26. Asset1ChainID = ExternalChainId
  27. Asset1Decimals = uint8(6)
  28. AmountExternalToGaiaUser1 = 10_000_018
  29. AmountExternalToOsmoUser1 = 1_000_001
  30. AmountExternalToOsmoUser2 = 1_000_002
  31. AmountGaiaUser1ToExternalSimple = 1_000_003
  32. AmountGaiaUser1ToExternalCC = 1_000_004
  33. AmountGaiaUser1ToOsmoUser1 = 1_000_005
  34. AmountGaiaUser1ToOsmoUser2 = 1_000_006
  35. )
  36. // TestWormchain runs through a simple test case for each deliverable
  37. // - Setup wormchain, gaia, and osmosis including contracts/allowlists/etc
  38. // - External->Cosmos: Send 10.000_018 to gaia user 1 (simple)
  39. // - External->Cosmos: Send 1.000_001 to osmo user 1 (simple)
  40. // - External->Cosmos: Send 1.000_002 to osmo user 2 (contract controlled via osmo ibc-hooks contract)
  41. // - Cosmos->External: Send 1.000_003 to external address (simple) from gaia user 1
  42. // -- gaia user 1 now has 9.000_015 of asset 1
  43. // - Cosmos->External: Send 1.000_004 to external address (contract controlled) from gaia user 1
  44. // -- gaia user 1 now has 8.000_011 of asset 1
  45. // - Cosmos->Cosmos: Send 1.000_005 to osmo user 1 (simple) from gaia user 1
  46. // -- gaia user 1 now has 7.000_006 of asset 1
  47. // -- osmo user 1 now has 2.000_006 of asset 1
  48. // - Cosmos->Cosmos: Send 1.000_006 to osmo user 2 (contract controlled via osmo ibc-hooks contract) from gaia user 1
  49. // -- gaia user 1 now has 6.000_000 of asset 1
  50. // -- osmo user 2 now has 2.000_008 of asset 1
  51. // - Verify asset 1 balance of gaia user 1, osmo user 1, osmo user 2, and cw20 contract total supply
  52. func TestWormchain(t *testing.T) {
  53. // Base setup
  54. numVals := 2
  55. guardians := guardians.CreateValSet(t, numVals)
  56. chains := CreateChains(t, "v2.24.2", *guardians)
  57. ctx, r, eRep, _ := BuildInterchain(t, chains)
  58. // Chains
  59. wormchain := chains[0].(*cosmos.CosmosChain)
  60. gaia := chains[1].(*cosmos.CosmosChain)
  61. osmosis := chains[2].(*cosmos.CosmosChain)
  62. wormchainFaucetAddrBz, err := wormchain.GetAddress(ctx, "faucet")
  63. require.NoError(t, err)
  64. wormchainFaucetAddr := sdk.MustBech32ifyAddressBytes(wormchain.Config().Bech32Prefix, wormchainFaucetAddrBz)
  65. fmt.Println("Wormchain faucet addr: ", wormchainFaucetAddr)
  66. osmoToWormChannel, err := ibc.GetTransferChannel(ctx, r, eRep, osmosis.Config().ChainID, wormchain.Config().ChainID)
  67. require.NoError(t, err)
  68. wormToOsmoChannel := osmoToWormChannel.Counterparty
  69. gaiaToWormChannel, err := ibc.GetTransferChannel(ctx, r, eRep, gaia.Config().ChainID, wormchain.Config().ChainID)
  70. require.NoError(t, err)
  71. wormToGaiaChannel := gaiaToWormChannel.Counterparty
  72. users := interchaintest.GetAndFundTestUsers(t, ctx, "default", int64(10_000_000_000), wormchain, gaia, osmosis, osmosis)
  73. _ = users[0] // Wormchain user
  74. gaiaUser := users[1]
  75. osmoUser1 := users[2]
  76. osmoUser2 := users[3]
  77. ibcHooksCodeId, err := osmosis.StoreContract(ctx, osmoUser1.KeyName, "./contracts/ibc_hooks.wasm")
  78. require.NoError(t, err)
  79. fmt.Println("IBC hooks code id: ", ibcHooksCodeId)
  80. ibcHooksContractAddr, err := osmosis.InstantiateContract(ctx, osmoUser1.KeyName, ibcHooksCodeId, "{}", true)
  81. require.NoError(t, err)
  82. fmt.Println("IBC hooks contract addr: ", ibcHooksContractAddr)
  83. err = testutil.WaitForBlocks(ctx, 2, wormchain)
  84. require.NoError(t, err, "error waiting for 2 blocks")
  85. // Store wormhole core contract
  86. coreContractCodeId := helpers.StoreContract(t, ctx, wormchain, "faucet", "./contracts/wormhole_core.wasm", guardians)
  87. fmt.Println("Core contract code id: ", coreContractCodeId)
  88. // Instantiate wormhole core contract
  89. coreInstantiateMsg := helpers.CoreContractInstantiateMsg(t, wormchainConfig, vaa.ChainIDWormchain, guardians)
  90. coreContractAddr := helpers.InstantiateContract(t, ctx, wormchain, "faucet", coreContractCodeId, "wormhole_core", coreInstantiateMsg, guardians)
  91. fmt.Println("Core contract address: ", coreContractAddr)
  92. // Store cw20_wrapped_2 contract
  93. wrappedAssetCodeId := helpers.StoreContract(t, ctx, wormchain, "faucet", "./contracts/cw20_wrapped_2.wasm", guardians)
  94. fmt.Println("CW20 wrapped_2 code id: ", wrappedAssetCodeId)
  95. // Store token bridge contract
  96. tbContractCodeId := helpers.StoreContract(t, ctx, wormchain, "faucet", "./contracts/token_bridge.wasm", guardians)
  97. fmt.Println("Token bridge contract code id: ", tbContractCodeId)
  98. // Instantiate token bridge contract
  99. tbInstantiateMsg := helpers.TbContractInstantiateMsg(t, wormchainConfig, coreContractAddr, wrappedAssetCodeId)
  100. tbContractAddr := helpers.InstantiateContract(t, ctx, wormchain, "faucet", tbContractCodeId, "token_bridge", tbInstantiateMsg, guardians)
  101. fmt.Println("Token bridge contract address: ", tbContractAddr)
  102. helpers.SubmitAllowlistInstantiateContract(t, ctx, wormchain, "faucet", wormchain.Config(), tbContractAddr, wrappedAssetCodeId, guardians)
  103. // Register a new external chain
  104. tbRegisterChainMsg := helpers.TbRegisterChainMsg(t, ExternalChainId, ExternalChainEmitterAddr, guardians)
  105. _, err = wormchain.ExecuteContract(ctx, "faucet", tbContractAddr, string(tbRegisterChainMsg))
  106. require.NoError(t, err)
  107. // Register a new foreign asset (Asset1) originating on externalChain
  108. tbRegisterForeignAssetMsg := helpers.TbRegisterForeignAsset(t, Asset1ContractAddr, Asset1ChainID, ExternalChainEmitterAddr, Asset1Decimals, Asset1Symbol, Asset1Name, guardians)
  109. _, err = wormchain.ExecuteContract(ctx, "faucet", tbContractAddr, string(tbRegisterForeignAssetMsg))
  110. require.NoError(t, err)
  111. // Store ibc translator contract
  112. ibcTranslatorCodeId := helpers.StoreContract(t, ctx, wormchain, "faucet", "./contracts/ibc_translator.wasm", guardians)
  113. fmt.Println("Ibc translator code id: ", ibcTranslatorCodeId)
  114. // Instantiate ibc translator contract
  115. ibcTranslatorInstantiateMsg := helpers.IbcTranslatorContractInstantiateMsg(t, tbContractAddr)
  116. ibcTranslatorContractAddr := helpers.InstantiateContract(t, ctx, wormchain, "faucet", ibcTranslatorCodeId, "ibc_translator", ibcTranslatorInstantiateMsg, guardians)
  117. fmt.Println("Ibc translator contract address: ", ibcTranslatorContractAddr)
  118. helpers.SetMiddlewareContract(t, ctx, wormchain, "faucet", wormchain.Config(), ibcTranslatorContractAddr, guardians)
  119. // Allowlist worm/osmo chain id / channel
  120. wormOsmoAllowlistMsg := helpers.SubmitUpdateChainToChannelMapMsg(t, OsmoChainID, wormToOsmoChannel.ChannelID, guardians)
  121. _, err = wormchain.ExecuteContract(ctx, "faucet", ibcTranslatorContractAddr, wormOsmoAllowlistMsg)
  122. require.NoError(t, err)
  123. // Allowlist worm/gaia chain id / channel
  124. wormGaiaAllowlistMsg := helpers.SubmitUpdateChainToChannelMapMsg(t, GaiaChainID, wormToGaiaChannel.ChannelID, guardians)
  125. _, err = wormchain.ExecuteContract(ctx, "faucet", ibcTranslatorContractAddr, wormGaiaAllowlistMsg)
  126. require.NoError(t, err)
  127. // Create and process a simple ibc payload3: Transfers 10.000_018 of asset1 from external chain through wormchain to gaia user
  128. simplePayload := helpers.CreateGatewayIbcTokenBridgePayloadTransfer(t, GaiaChainID, gaiaUser.Bech32Address(gaia.Config().Bech32Prefix), 0, 1)
  129. externalSender := []byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}
  130. payload3 := helpers.CreatePayload3(wormchain.Config(), uint64(AmountExternalToGaiaUser1), Asset1ContractAddr, Asset1ChainID, ibcTranslatorContractAddr, uint16(vaa.ChainIDWormchain), externalSender, simplePayload)
  131. completeTransferAndConvertMsg := helpers.IbcTranslatorCompleteTransferAndConvertMsg(t, ExternalChainId, ExternalChainEmitterAddr, payload3, guardians)
  132. _, err = wormchain.ExecuteContract(ctx, "faucet", ibcTranslatorContractAddr, completeTransferAndConvertMsg)
  133. require.NoError(t, err)
  134. // Create and process a simple ibc payload3: Transfers 1.000_001 of asset1 from external chain through wormchain to osmo user1
  135. simplePayload = helpers.CreateGatewayIbcTokenBridgePayloadTransfer(t, OsmoChainID, osmoUser1.Bech32Address(osmosis.Config().Bech32Prefix), 0, 1)
  136. payload3 = helpers.CreatePayload3(wormchain.Config(), uint64(AmountExternalToOsmoUser1), Asset1ContractAddr, Asset1ChainID, ibcTranslatorContractAddr, uint16(vaa.ChainIDWormchain), externalSender, simplePayload)
  137. completeTransferAndConvertMsg = helpers.IbcTranslatorCompleteTransferAndConvertMsg(t, ExternalChainId, ExternalChainEmitterAddr, payload3, guardians)
  138. _, err = wormchain.ExecuteContract(ctx, "faucet", ibcTranslatorContractAddr, completeTransferAndConvertMsg)
  139. require.NoError(t, err)
  140. // Create and process a contract controlled ibc payload3
  141. // Transfers 1.000_002 of asset1 from external chain through wormchain to ibc hooks contract addr
  142. // IBC hooks is used to route the contract controlled payload to a test contract which forwards tokens to osmo user2
  143. ibcHooksPayload := helpers.CreateIbcHooksMsg(t, ibcHooksContractAddr, osmoUser2.Bech32Address(osmosis.Config().Bech32Prefix))
  144. contractControlledPayload := helpers.CreateGatewayIbcTokenBridgePayloadTransferWithPayload(t, OsmoChainID, ibcHooksContractAddr, ibcHooksPayload, 1)
  145. payload3 = helpers.CreatePayload3(wormchain.Config(), uint64(AmountExternalToOsmoUser2), Asset1ContractAddr, Asset1ChainID, ibcTranslatorContractAddr, uint16(vaa.ChainIDWormchain), externalSender, contractControlledPayload)
  146. completeTransferAndConvertMsg = helpers.IbcTranslatorCompleteTransferAndConvertMsg(t, ExternalChainId, ExternalChainEmitterAddr, payload3, guardians)
  147. _, err = wormchain.ExecuteContract(ctx, "faucet", ibcTranslatorContractAddr, completeTransferAndConvertMsg)
  148. require.NoError(t, err)
  149. // wait for transfer to ack
  150. err = testutil.WaitForBlocks(ctx, 10, wormchain, gaia)
  151. require.NoError(t, err)
  152. // Query the CW20 address of asset1
  153. var tbQueryRsp helpers.TbQueryRsp
  154. tbQueryReq := helpers.CreateCW20Query(t, Asset1ChainID, Asset1ContractAddr)
  155. wormchain.QueryContract(ctx, tbContractAddr, tbQueryReq, &tbQueryRsp)
  156. cw20Address := tbQueryRsp.Data.Address
  157. fmt.Println("Asset1 cw20 addr: ", cw20Address)
  158. // Get the Gaia/IBC denom of asset1
  159. cw20AddressBz := helpers.MustAccAddressFromBech32(cw20Address, wormchain.Config().Bech32Prefix)
  160. subdenom := base58.Encode(cw20AddressBz)
  161. tokenFactoryDenom := fmt.Sprint("factory/", ibcTranslatorContractAddr, "/", subdenom)
  162. gaiaAsset1Denom := transfertypes.GetPrefixedDenom("transfer", gaiaToWormChannel.ChannelID, tokenFactoryDenom)
  163. gaiaIbcAsset1Denom := transfertypes.ParseDenomTrace(gaiaAsset1Denom).IBCDenom()
  164. // Get the Osmo/IBC denom of asset1
  165. osmoAsset1Denom := transfertypes.GetPrefixedDenom("transfer", osmoToWormChannel.ChannelID, tokenFactoryDenom)
  166. osmoIbcAsset1Denom := transfertypes.ParseDenomTrace(osmoAsset1Denom).IBCDenom()
  167. // Verify Gaia user 1 has expected asset 1 balance
  168. gaiaUser1Asset1BalanceTemp, err := gaia.GetBalance(ctx, gaiaUser.Bech32Address(gaia.Config().Bech32Prefix), gaiaIbcAsset1Denom)
  169. require.NoError(t, err)
  170. fmt.Println("Gaia user asset1 coins: ", gaiaUser1Asset1BalanceTemp)
  171. // wait for transfer to ack
  172. err = testutil.WaitForBlocks(ctx, 2, wormchain, gaia)
  173. require.NoError(t, err)
  174. // Verify Gaia user 1 has expected asset 1 balance
  175. gaiaUser1Asset1BalanceTemp, err = gaia.GetBalance(ctx, gaiaUser.Bech32Address(gaia.Config().Bech32Prefix), gaiaIbcAsset1Denom)
  176. require.NoError(t, err)
  177. fmt.Println("Gaia user asset1 coins: ", gaiaUser1Asset1BalanceTemp)
  178. // ************* Cosmos->External: Simple payload (wormhole-mw + ibc-hooks) ****************
  179. // Send 1.000_003 asset 1 from gaia user 1 to external
  180. simpleMemo := helpers.CreateIbcComposabilityMwMemoGatewayTransfer(t, Asset1ChainID, externalSender, 0, 1)
  181. transfer := ibc.WalletAmount{
  182. Address: ibcTranslatorContractAddr,
  183. Denom: gaiaIbcAsset1Denom,
  184. Amount: int64(AmountGaiaUser1ToExternalSimple),
  185. }
  186. _, err = gaia.SendIBCTransfer(ctx, gaiaToWormChannel.ChannelID, gaiaUser.KeyName, transfer, ibc.TransferOptions{Memo: simpleMemo})
  187. require.NoError(t, err)
  188. // wait for transfer to ack
  189. err = testutil.WaitForBlocks(ctx, 2, wormchain, gaia)
  190. require.NoError(t, err)
  191. // ************* Cosmos->External: Contract controlled payload (wormhole-mw + ibc-hooks) ****************
  192. // Send 1.000_004 asset 1 from gaia user 1 to external
  193. ccIbcHooksMsg := helpers.CreateIbcComposabilityMwMemoGatewayTransferWithPayload(t, Asset1ChainID, externalSender, []byte("ExternalContractPayload"), 1)
  194. transfer = ibc.WalletAmount{
  195. Address: ibcTranslatorContractAddr,
  196. Denom: gaiaIbcAsset1Denom,
  197. Amount: int64(AmountGaiaUser1ToExternalCC),
  198. }
  199. _, err = gaia.SendIBCTransfer(ctx, gaiaToWormChannel.ChannelID, gaiaUser.KeyName, transfer, ibc.TransferOptions{Memo: ccIbcHooksMsg})
  200. require.NoError(t, err)
  201. // wait for transfer to ack
  202. err = testutil.WaitForBlocks(ctx, 2, wormchain, gaia)
  203. require.NoError(t, err)
  204. // ************** Cosmos->Cosmos: Simple payload (wormhole-mw + PFM) ****************
  205. // Send 1.000_005 asset 1 from gaia user 1 to osmo user 1
  206. simplePfmMsg := helpers.CreateIbcComposabilityMwMemoGatewayTransfer(t, OsmoChainID, []byte(osmoUser1.Bech32Address(osmosis.Config().Bech32Prefix)), 0, 1)
  207. transfer = ibc.WalletAmount{
  208. Address: wormchainFaucetAddr,
  209. Denom: gaiaIbcAsset1Denom,
  210. Amount: int64(AmountGaiaUser1ToOsmoUser1),
  211. }
  212. _, err = gaia.SendIBCTransfer(ctx, gaiaToWormChannel.ChannelID, gaiaUser.KeyName, transfer, ibc.TransferOptions{
  213. Timeout: &ibc.IBCTimeout{
  214. NanoSeconds: 30_000_000_000,
  215. },
  216. Memo: simplePfmMsg,
  217. })
  218. require.NoError(t, err)
  219. // wait for transfer to ack
  220. err = testutil.WaitForBlocks(ctx, 2, wormchain, gaia)
  221. require.NoError(t, err)
  222. // ************** Cosmos->Cosmos: Contract controlled payload (wormhole-mw + PFM) ****************
  223. // Send 1.000_006 asset 1 from gaia user 1 to osmo user 2
  224. ccPayload := helpers.CreateIbcHooksMsg(t, ibcHooksContractAddr, osmoUser2.Bech32Address(osmosis.Config().Bech32Prefix))
  225. ccPfmMsg := helpers.CreateIbcComposabilityMwMemoGatewayTransferWithPayload(t, OsmoChainID, []byte(ibcHooksContractAddr), ccPayload, 1)
  226. transfer = ibc.WalletAmount{
  227. Address: ibcTranslatorContractAddr,
  228. Denom: gaiaIbcAsset1Denom,
  229. Amount: int64(AmountGaiaUser1ToOsmoUser2),
  230. }
  231. _, err = gaia.SendIBCTransfer(ctx, gaiaToWormChannel.ChannelID, gaiaUser.KeyName, transfer, ibc.TransferOptions{
  232. Timeout: &ibc.IBCTimeout{
  233. NanoSeconds: 30_000_000_000,
  234. },
  235. Memo: ccPfmMsg,
  236. })
  237. require.NoError(t, err)
  238. // wait for transfer to ack
  239. err = testutil.WaitForBlocks(ctx, 15, wormchain, gaia)
  240. require.NoError(t, err)
  241. // Verify Gaia user 1 has expected asset 1 balance
  242. gaiaUser1Asset1Balance, err := gaia.GetBalance(ctx, gaiaUser.Bech32Address(gaia.Config().Bech32Prefix), gaiaIbcAsset1Denom)
  243. require.NoError(t, err)
  244. expectedGaiaUser1Amount := AmountExternalToGaiaUser1 - AmountGaiaUser1ToExternalCC - AmountGaiaUser1ToExternalSimple - AmountGaiaUser1ToOsmoUser1 - AmountGaiaUser1ToOsmoUser2
  245. require.Equal(t, int64(expectedGaiaUser1Amount), gaiaUser1Asset1Balance)
  246. fmt.Println("Gaia user asset1 coins: ", gaiaUser1Asset1Balance)
  247. // Verify osmo user 1 has expected asset 1 balance
  248. osmoUser1Asset1Balance, err := osmosis.GetBalance(ctx, osmoUser1.Bech32Address(osmosis.Config().Bech32Prefix), osmoIbcAsset1Denom)
  249. require.NoError(t, err)
  250. require.Equal(t, int64(AmountExternalToOsmoUser1+AmountGaiaUser1ToOsmoUser1), osmoUser1Asset1Balance)
  251. fmt.Println("Osmo user1 asset1 coins: ", osmoUser1Asset1Balance)
  252. // Verify osmo user 2 has expected asset 1 balance
  253. osmoUser2Asset1Balance, err := osmosis.GetBalance(ctx, osmoUser2.Bech32Address(osmosis.Config().Bech32Prefix), osmoIbcAsset1Denom)
  254. require.NoError(t, err)
  255. require.Equal(t, int64(AmountExternalToOsmoUser2+AmountGaiaUser1ToOsmoUser2), osmoUser2Asset1Balance)
  256. fmt.Println("Osmo user2 asset1 coins: ", osmoUser2Asset1Balance)
  257. // Verify asset 1 cw20 contract has expected final total supply
  258. var cw20QueryRsp helpers.Cw20WrappedQueryRsp
  259. cw20QueryReq := helpers.Cw20WrappedQueryMsg{TokenInfo: helpers.Cw20TokenInfo{}}
  260. wormchain.QueryContract(ctx, cw20Address, cw20QueryReq, &cw20QueryRsp)
  261. fmt.Println("Asset1 supply: ", cw20QueryRsp.Data.TotalSupply)
  262. totalSupply, err := strconv.ParseUint(cw20QueryRsp.Data.TotalSupply, 10, 64)
  263. require.NoError(t, err)
  264. expectedTotalSupply := AmountExternalToGaiaUser1 + AmountExternalToOsmoUser1 + AmountExternalToOsmoUser2 - AmountGaiaUser1ToExternalSimple - AmountGaiaUser1ToExternalCC
  265. require.Equal(t, uint64(expectedTotalSupply), totalSupply)
  266. denomsMetadata := helpers.GetDenomsMetadata(t, ctx, wormchain)
  267. fmt.Println("Denoms metadata: ", denomsMetadata)
  268. }
  269. type QueryMsg struct {
  270. GuardianSetInfo *struct{} `json:"guardian_set_info,omitempty"`
  271. }
  272. type QueryRsp struct {
  273. Data *QueryRspObj `json:"data,omitempty"`
  274. }
  275. type QueryRspObj struct {
  276. GuardianSetIndex uint32 `json:"guardian_set_index"`
  277. Addresses []helpers.GuardianAddress `json:"addresses"`
  278. }