diff --git a/contracts/ics721-base/src/lib.rs b/contracts/ics721-base/src/lib.rs index 5d699ced..f10b7a68 100644 --- a/contracts/ics721-base/src/lib.rs +++ b/contracts/ics721-base/src/lib.rs @@ -45,7 +45,7 @@ pub fn execute( } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result { Ics721Contract::default().query(deps, env, msg) } diff --git a/contracts/sg-ics721/src/lib.rs b/contracts/sg-ics721/src/lib.rs index b57043e4..30b0acf2 100644 --- a/contracts/sg-ics721/src/lib.rs +++ b/contracts/sg-ics721/src/lib.rs @@ -45,7 +45,7 @@ pub fn execute( } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result { SgIcs721Contract::default().query(deps, env, msg) } diff --git a/contracts/sg-ics721/src/testing/integration_tests.rs b/contracts/sg-ics721/src/testing/integration_tests.rs index a6bdc3f7..49cf3b00 100644 --- a/contracts/sg-ics721/src/testing/integration_tests.rs +++ b/contracts/sg-ics721/src/testing/integration_tests.rs @@ -103,7 +103,7 @@ fn execute( SgIcs721Contract::default().execute(deps, env, info, msg) } -fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { +fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result { SgIcs721Contract::default().query(deps, env, msg) } diff --git a/e2e/adversarial_test.go b/e2e/adversarial_test.go index 139a0b16..7ae3b433 100644 --- a/e2e/adversarial_test.go +++ b/e2e/adversarial_test.go @@ -1,8 +1,9 @@ -package e2e_test +package e2e import ( "encoding/json" "fmt" + "github.com/public-awesome/ics721/e2e/test_suite" "testing" "time" @@ -53,7 +54,7 @@ type AdversarialTestSuite struct { tokenIdA string } -func TestIcs721Olympics(t *testing.T) { +func TestAdversarial(t *testing.T) { suite.Run(t, new(AdversarialTestSuite)) } @@ -73,11 +74,11 @@ func (suite *AdversarialTestSuite) SetupTest() { resp = chain.StoreCodeFile("../artifacts/ics721_base_tester.wasm") require.Equal(suite.T(), uint64(3), resp.CodeID) - instantiateBridge := InstantiateICS721Bridge{ - 2, - nil, - nil, - nil, + instantiateBridge := test_suite.InstantiateICS721Bridge{ + Cw721BaseCodeId: 2, + OutgoingProxy: nil, + IncomingProxy: nil, + Pauser: nil, } instantiateBridgeRaw, err := json.Marshal(instantiateBridge) require.NoError(suite.T(), err) @@ -89,17 +90,17 @@ func (suite *AdversarialTestSuite) SetupTest() { storeCodes(suite.chainB, &suite.bridgeB) storeCodes(suite.chainC, &suite.bridgeC) - instantiateBridgeTester := InstantiateBridgeTester{ - "success", - suite.bridgeC.String(), + instantiateBridgeTester := test_suite.InstantiateBridgeTester{ + AckMode: "success", + Ics721: suite.bridgeC.String(), } instantiateBridgeTesterRaw, err := json.Marshal(instantiateBridgeTester) require.NoError(suite.T(), err) suite.bridgeC = suite.chainC.InstantiateContract(3, instantiateBridgeTesterRaw) - suite.cw721A = instantiateCw721(suite.T(), suite.chainA) + suite.cw721A = test_suite.InstantiateCw721(suite.T(), suite.chainA, 18) suite.tokenIdA = "bad kid 1" - mintNFT(suite.T(), suite.chainA, suite.cw721A.String(), suite.tokenIdA, suite.chainA.SenderAccount.GetAddress()) + test_suite.MintNFT(suite.T(), suite.chainA, suite.cw721A.String(), suite.tokenIdA, suite.chainA.SenderAccount.GetAddress()) makePath := func(chainA, chainB *wasmibctesting.TestChain, bridgeA, bridgeB sdk.AccAddress) (path *wasmibctesting.Path) { sourcePortID := chainA.ContractInfo(bridgeA).IBCPortID @@ -135,7 +136,7 @@ func (suite *AdversarialTestSuite) SetupTest() { func (suite *AdversarialTestSuite) TestUnexpectedClose() { // Make a pending IBC message across the AC path, but do not // relay it. - msg := getCw721SendIbcAwayMessage(suite.pathAC, suite.coordinator, suite.tokenIdA, suite.bridgeA, suite.chainC.SenderAccount.GetAddress(), suite.coordinator.CurrentTime.Add(time.Second*4).UnixNano(), "") + msg := test_suite.GetCw721SendIbcAwayMessage(suite.pathAC, suite.coordinator, suite.tokenIdA, suite.bridgeA, suite.chainC.SenderAccount.GetAddress(), suite.coordinator.CurrentTime.Add(time.Second*4).UnixNano(), "") _, err := suite.chainA.SendMsgs(&wasmtypes.MsgExecuteContract{ Sender: suite.chainA.SenderAccount.GetAddress().String(), Contract: suite.cw721A.String(), @@ -158,7 +159,7 @@ func (suite *AdversarialTestSuite) TestUnexpectedClose() { suite.coordinator.TimeoutPendingPackets(suite.pathAC) suite.pathAC.EndpointA.ChanCloseConfirm() - owner := queryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), "bad kid 1") + owner := test_suite.QueryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), "bad kid 1") require.Equal(suite.T(), suite.chainA.SenderAccount.GetAddress().String(), owner) require.Equal(suite.T(), channeltypes.CLOSED, suite.pathAC.Invert().EndpointA.GetChannel().State) @@ -169,11 +170,11 @@ func (suite *AdversarialTestSuite) TestUnexpectedClose() { // // As there is no falliable version of SendMsgs, we've got to // use our in house edition. - newAcc := CreateAndFundAccount(suite.T(), suite.chainA, 10) - mintNFT(suite.T(), suite.chainA, suite.cw721A.String(), "bad kid 2", newAcc.Address) + newAcc := test_suite.CreateAndFundAccount(suite.T(), suite.chainA, 10) + test_suite.MintNFT(suite.T(), suite.chainA, suite.cw721A.String(), "bad kid 2", newAcc.Address) - msg = getCw721SendIbcAwayMessage(suite.pathAC, suite.coordinator, "bad kid 2", suite.bridgeA, suite.chainC.SenderAccount.GetAddress(), suite.coordinator.CurrentTime.Add(time.Second*4).UnixNano(), "") - _, err = SendMsgsFromAccount(suite.T(), suite.chainA, newAcc, &wasmtypes.MsgExecuteContract{ + msg = test_suite.GetCw721SendIbcAwayMessage(suite.pathAC, suite.coordinator, "bad kid 2", suite.bridgeA, suite.chainC.SenderAccount.GetAddress(), suite.coordinator.CurrentTime.Add(time.Second*4).UnixNano(), "") + _, err = test_suite.SendMsgsFromAccount(suite.T(), suite.chainA, newAcc, &wasmtypes.MsgExecuteContract{ Sender: newAcc.Address.String(), Contract: suite.cw721A.String(), Msg: []byte(msg), @@ -196,13 +197,13 @@ func (suite *AdversarialTestSuite) TestUnexpectedClose() { // metadata when a new packet comes in with conflicting information. func (suite *AdversarialTestSuite) TestInvalidOnMineValidOnTheirs() { // Send a NFT to chain B from A. - ics721Nft(suite.T(), suite.chainA, suite.pathAB, suite.coordinator, suite.cw721A.String(), "bad kid 1", suite.bridgeA, suite.chainA.SenderAccount.GetAddress(), suite.chainB.SenderAccount.GetAddress(), "") + test_suite.Ics721TransferNft(suite.T(), suite.chainA, suite.pathAB, suite.coordinator, suite.cw721A.String(), "bad kid 1", suite.bridgeA, suite.chainA.SenderAccount.GetAddress(), suite.chainB.SenderAccount.GetAddress(), "") chainBClassId := fmt.Sprintf("%s/%s/%s", suite.pathAB.EndpointB.ChannelConfig.PortID, suite.pathAB.EndpointB.ChannelID, suite.cw721A.String()) // Check that the NFT has been received on chain B. - chainBCw721 := queryGetNftForClass(suite.T(), suite.chainB, suite.bridgeB.String(), chainBClassId) - chainBOwner := queryGetOwnerOf(suite.T(), suite.chainB, chainBCw721, "bad kid 1") + chainBCw721 := test_suite.QueryGetNftForClass(suite.T(), suite.chainB, suite.bridgeB.String(), chainBClassId) + chainBOwner := test_suite.QueryGetOwnerOf(suite.T(), suite.chainB, chainBCw721, "bad kid 1") require.Equal(suite.T(), suite.chainB.SenderAccount.GetAddress().String(), chainBOwner) // From chain C send a message using the chain B class ID to @@ -218,19 +219,19 @@ func (suite *AdversarialTestSuite) TestInvalidOnMineValidOnTheirs() { suite.coordinator.RelayAndAckPendingPackets(suite.pathAC.Invert()) // NFT should still be owned by the ICS721 contract on chain A. - chainAOwner := queryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), "bad kid 1") + chainAOwner := test_suite.QueryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), "bad kid 1") require.Equal(suite.T(), suite.bridgeA.String(), chainAOwner) // A new NFT should have been minted on chain A. chainAClassId := fmt.Sprintf("%s/%s/%s", suite.pathAC.EndpointA.ChannelConfig.PortID, suite.pathAC.EndpointA.ChannelID, chainBClassId) - chainACw721 := queryGetNftForClass(suite.T(), suite.chainA, suite.bridgeA.String(), chainAClassId) - chainAOwner = queryGetOwnerOf(suite.T(), suite.chainA, chainACw721, "bad kid 1") + chainACw721 := test_suite.QueryGetNftForClass(suite.T(), suite.chainA, suite.bridgeA.String(), chainAClassId) + chainAOwner = test_suite.QueryGetOwnerOf(suite.T(), suite.chainA, chainACw721, "bad kid 1") require.Equal(suite.T(), suite.chainA.SenderAccount.GetAddress().String(), chainAOwner) // Metadata should be set. - var metadata Class - err = suite.chainA.SmartQuery(suite.bridgeA.String(), ClassMetadataQuery{ - Metadata: ClassMetadataQueryData{ + var metadata test_suite.Class + err = suite.chainA.SmartQuery(suite.bridgeA.String(), test_suite.ClassMetadataQuery{ + Metadata: test_suite.ClassMetadataQueryData{ ClassId: chainAClassId, }, }, &metadata) @@ -242,9 +243,9 @@ func (suite *AdversarialTestSuite) TestInvalidOnMineValidOnTheirs() { // The newly minted NFT should be returnable to the source // chain and cause a burn when returned. - ics721Nft(suite.T(), suite.chainA, suite.pathAC, suite.coordinator, chainACw721, "bad kid 1", suite.bridgeA, suite.chainA.SenderAccount.GetAddress(), suite.chainC.SenderAccount.GetAddress(), "") + test_suite.Ics721TransferNft(suite.T(), suite.chainA, suite.pathAC, suite.coordinator, chainACw721, "bad kid 1", suite.bridgeA, suite.chainA.SenderAccount.GetAddress(), suite.chainC.SenderAccount.GetAddress(), "") - err = suite.chainA.SmartQuery(chainACw721, OwnerOfQuery{OwnerOf: OwnerOfQueryData{TokenID: suite.tokenIdA}}, &OwnerOfResponse{}) + err = suite.chainA.SmartQuery(chainACw721, test_suite.OwnerOfQuery{OwnerOf: test_suite.OwnerOfQueryData{TokenID: suite.tokenIdA}}, &test_suite.OwnerOfResponse{}) require.ErrorContains(suite.T(), err, "cw721_base::state::TokenInfo>; key: [00, 06, 74, 6F, 6B, 65, 6E, 73, 62, 61, 64, 20, 6B, 69, 64, 20, 31] not found") // Send the NFT back, this time setting new metadata for the @@ -259,9 +260,9 @@ func (suite *AdversarialTestSuite) TestInvalidOnMineValidOnTheirs() { suite.coordinator.UpdateTime() suite.coordinator.RelayAndAckPendingPackets(suite.pathAC.Invert()) - // Metadata should be set to the most up to date value. - err = suite.chainA.SmartQuery(suite.bridgeA.String(), ClassMetadataQuery{ - Metadata: ClassMetadataQueryData{ + // Metadata should be set to the most up-to-date value. + err = suite.chainA.SmartQuery(suite.bridgeA.String(), test_suite.ClassMetadataQuery{ + Metadata: test_suite.ClassMetadataQueryData{ ClassId: chainAClassId, }, }, &metadata) @@ -281,7 +282,7 @@ func (suite *AdversarialTestSuite) TestInvalidOnMineValidOnTheirs() { // - Metadata and NFT contract queries should still work. // - The NFT should be returnable. // -// However, for reasons entirely beyond me, the SDK does it's own +// However, for reasons entirely beyond me, the SDK does its own // validation on our data field and errors if the class ID is empty, // so this test capitulates and just tests that we handle the SDK // error correctly. @@ -302,14 +303,14 @@ func (suite *AdversarialTestSuite) TestEmptyClassId() { // Make sure we got the weird SDK error. var lastAck string - err = suite.chainC.SmartQuery(suite.bridgeC.String(), LastAckQuery{LastAck: LastAckQueryData{}}, &lastAck) + err = suite.chainC.SmartQuery(suite.bridgeC.String(), test_suite.LastAckQuery{LastAck: test_suite.LastAckQueryData{}}, &lastAck) require.NoError(suite.T(), err) require.Equal(suite.T(), "error", lastAck) // Make sure a NFT was not minted in spite of the weird SDK // error. chainAClassId := fmt.Sprintf("%s/%s/%s", suite.pathAC.EndpointA.ChannelConfig.PortID, suite.pathAC.EndpointA.ChannelID, "") - chainACw721 := queryGetNftForClass(suite.T(), suite.chainA, suite.bridgeA.String(), chainAClassId) + chainACw721 := test_suite.QueryGetNftForClass(suite.T(), suite.chainA, suite.bridgeA.String(), chainAClassId) require.Equal(suite.T(), "", chainACw721) } @@ -355,45 +356,45 @@ func (suite *AdversarialTestSuite) TestMetadataForwarding() { suite.pathAC.EndpointA.ChannelID, "bad kids", ) - var class_metadata Class - err = suite.chainA.SmartQuery(suite.bridgeA.String(), ClassMetadataQuery{ - Metadata: ClassMetadataQueryData{ + var classMetadata test_suite.Class + err = suite.chainA.SmartQuery(suite.bridgeA.String(), test_suite.ClassMetadataQuery{ + Metadata: test_suite.ClassMetadataQueryData{ ClassId: chainAClassId, }, - }, &class_metadata) + }, &classMetadata) require.NoError(suite.T(), err) - suite.T().Log("class:", class_metadata) - require.NotNil(suite.T(), class_metadata.URI) - suite.T().Log("class URI:", class_metadata.URI) - require.NotNil(suite.T(), class_metadata.Data) - suite.T().Log("class data:", class_metadata.Data) - require.Equal(suite.T(), "https://metadata-url.com/my-metadata", *class_metadata.URI) - require.Equal(suite.T(), "e30K", *class_metadata.Data) + suite.T().Log("class:", classMetadata) + require.NotNil(suite.T(), classMetadata.URI) + suite.T().Log("class URI:", classMetadata.URI) + require.NotNil(suite.T(), classMetadata.Data) + suite.T().Log("class data:", classMetadata.Data) + require.Equal(suite.T(), "https://metadata-url.com/my-metadata", *classMetadata.URI) + require.Equal(suite.T(), "e30K", *classMetadata.Data) // Check that token metadata was set. - var token_metadata Token - err = suite.chainA.SmartQuery(suite.bridgeA.String(), TokenMetadataQuery{ - Metadata: TokenMetadataQueryData{ + var tokenMetadata test_suite.Token + err = suite.chainA.SmartQuery(suite.bridgeA.String(), test_suite.TokenMetadataQuery{ + Metadata: test_suite.TokenMetadataQueryData{ ClassId: chainAClassId, TokenId: "bad kid 2", }, - }, &token_metadata) + }, &tokenMetadata) require.NoError(suite.T(), err) - suite.T().Log("token:", token_metadata) - require.NotNil(suite.T(), token_metadata.URI) - require.NotNil(suite.T(), token_metadata.Data) - require.Equal(suite.T(), "https://metadata-url.com/my-metadata2", *token_metadata.URI) - require.Equal(suite.T(), "e30K", *token_metadata.Data) + suite.T().Log("token:", tokenMetadata) + require.NotNil(suite.T(), tokenMetadata.URI) + require.NotNil(suite.T(), tokenMetadata.Data) + require.Equal(suite.T(), "https://metadata-url.com/my-metadata2", *tokenMetadata.URI) + require.Equal(suite.T(), "e30K", *tokenMetadata.Data) // Send bad kid 1 to chain B. var chainAAddress string - err = suite.chainA.SmartQuery(suite.bridgeA.String(), NftContractQuery{ - NftContractForClassId: NftContractQueryData{ + err = suite.chainA.SmartQuery(suite.bridgeA.String(), test_suite.NftContractQuery{ + NftContractForClassId: test_suite.NftContractQueryData{ ClassID: chainAClassId, }, }, &chainAAddress) require.NoError(suite.T(), err) - ics721Nft( + test_suite.Ics721TransferNft( suite.T(), suite.chainA, suite.pathAB, @@ -413,45 +414,45 @@ func (suite *AdversarialTestSuite) TestMetadataForwarding() { suite.pathAB.EndpointB.ChannelID, chainAClassId, ) - err = suite.chainB.SmartQuery(suite.bridgeB.String(), ClassMetadataQuery{ - Metadata: ClassMetadataQueryData{ + err = suite.chainB.SmartQuery(suite.bridgeB.String(), test_suite.ClassMetadataQuery{ + Metadata: test_suite.ClassMetadataQueryData{ ClassId: chainBClassId, }, - }, &class_metadata) + }, &classMetadata) require.NoError(suite.T(), err) - suite.T().Log("class:", class_metadata) - require.NotNil(suite.T(), class_metadata.URI) - require.NotNil(suite.T(), class_metadata.Data) - require.Equal(suite.T(), "https://metadata-url.com/my-metadata", *class_metadata.URI) - require.Equal(suite.T(), "e30K", *class_metadata.Data) + suite.T().Log("class:", classMetadata) + require.NotNil(suite.T(), classMetadata.URI) + require.NotNil(suite.T(), classMetadata.Data) + require.Equal(suite.T(), "https://metadata-url.com/my-metadata", *classMetadata.URI) + require.Equal(suite.T(), "e30K", *classMetadata.Data) // Check that token metadata has been forwarded. - token_metadata = Token{} - err = suite.chainB.SmartQuery(suite.bridgeB.String(), TokenMetadataQuery{ - Metadata: TokenMetadataQueryData{ + tokenMetadata = test_suite.Token{} + err = suite.chainB.SmartQuery(suite.bridgeB.String(), test_suite.TokenMetadataQuery{ + Metadata: test_suite.TokenMetadataQueryData{ ClassId: chainBClassId, TokenId: "bad kid 1", }, - }, &token_metadata) + }, &tokenMetadata) require.NoError(suite.T(), err) - suite.T().Log("token:", token_metadata) - require.NotNil(suite.T(), token_metadata.URI) - require.NotNil(suite.T(), token_metadata.Data) - require.Equal(suite.T(), "https://metadata-url.com/my-metadata1", *token_metadata.URI) - require.Equal(suite.T(), "e30K", *token_metadata.Data) + suite.T().Log("token:", tokenMetadata) + require.NotNil(suite.T(), tokenMetadata.URI) + require.NotNil(suite.T(), tokenMetadata.Data) + require.Equal(suite.T(), "https://metadata-url.com/my-metadata1", *tokenMetadata.URI) + require.Equal(suite.T(), "e30K", *tokenMetadata.Data) // Return the token to chain A. // // The ICS721 contract should remove the token's metadata from storage // and burn the token. var chainBAddress string - err = suite.chainB.SmartQuery(suite.bridgeB.String(), NftContractQuery{ - NftContractForClassId: NftContractQueryData{ + err = suite.chainB.SmartQuery(suite.bridgeB.String(), test_suite.NftContractQuery{ + NftContractForClassId: test_suite.NftContractQueryData{ ClassID: chainBClassId, }, }, &chainBAddress) require.NoError(suite.T(), err) - ics721Nft(suite.T(), + test_suite.Ics721TransferNft(suite.T(), suite.chainB, suite.pathAB.Invert(), suite.coordinator, @@ -465,11 +466,11 @@ func (suite *AdversarialTestSuite) TestMetadataForwarding() { suite.coordinator.UpdateTime() // Check that the returned token was burned. - var info NftInfoQueryResponse + var info test_suite.NftInfoQueryResponse err = suite.chainB.SmartQuery( chainBAddress, - NftInfoQuery{ - Nftinfo: NftInfoQueryData{ + test_suite.NftInfoQuery{ + Nftinfo: test_suite.NftInfoQueryData{ TokenID: "bad kid 1", }, }, @@ -482,16 +483,16 @@ func (suite *AdversarialTestSuite) TestMetadataForwarding() { ) // Check that token metadata was cleared. - token_metadata = Token{} - err = suite.chainB.SmartQuery(suite.bridgeB.String(), TokenMetadataQuery{ - Metadata: TokenMetadataQueryData{ + tokenMetadata = test_suite.Token{} + err = suite.chainB.SmartQuery(suite.bridgeB.String(), test_suite.TokenMetadataQuery{ + Metadata: test_suite.TokenMetadataQueryData{ ClassId: chainBClassId, TokenId: "bad kid 1", }, - }, &token_metadata) + }, &tokenMetadata) require.NoError(suite.T(), err) - require.Nil(suite.T(), token_metadata.Data) - require.Nil(suite.T(), token_metadata.URI) + require.Nil(suite.T(), tokenMetadata.Data) + require.Nil(suite.T(), tokenMetadata.URI) } // Are ACK fails returned by this contract parseable? @@ -514,7 +515,7 @@ func (suite *AdversarialTestSuite) TestSimpleAckFail() { // Make sure we responded with an ACK success. var lastAck string - err = suite.chainC.SmartQuery(suite.bridgeC.String(), LastAckQuery{LastAck: LastAckQueryData{}}, &lastAck) + err = suite.chainC.SmartQuery(suite.bridgeC.String(), test_suite.LastAckQuery{LastAck: test_suite.LastAckQueryData{}}, &lastAck) require.NoError(suite.T(), err) require.Equal(suite.T(), "error", lastAck) } @@ -538,7 +539,7 @@ func (suite *AdversarialTestSuite) TestSimpleAckSuccess() { // Make sure we responded with an ACK success. var lastAck string - err = suite.chainC.SmartQuery(suite.bridgeC.String(), LastAckQuery{LastAck: LastAckQueryData{}}, &lastAck) + err = suite.chainC.SmartQuery(suite.bridgeC.String(), test_suite.LastAckQuery{LastAck: test_suite.LastAckQueryData{}}, &lastAck) require.NoError(suite.T(), err) require.Equal(suite.T(), "success", lastAck) } @@ -562,7 +563,7 @@ func (suite *AdversarialTestSuite) TestDifferentUriAndIdLengths() { // Make sure we responded with an ACK fail. var lastAck string - err = suite.chainC.SmartQuery(suite.bridgeC.String(), LastAckQuery{LastAck: LastAckQueryData{}}, &lastAck) + err = suite.chainC.SmartQuery(suite.bridgeC.String(), test_suite.LastAckQuery{LastAck: test_suite.LastAckQueryData{}}, &lastAck) require.NoError(suite.T(), err) require.Equal(suite.T(), "error", lastAck) } @@ -592,7 +593,7 @@ func (suite *AdversarialTestSuite) TestZeroLengthUriAndData() { suite.coordinator.RelayAndAckPendingPackets(suite.pathAC.Invert()) var lastAck string - err = suite.chainC.SmartQuery(suite.bridgeC.String(), LastAckQuery{LastAck: LastAckQueryData{}}, &lastAck) + err = suite.chainC.SmartQuery(suite.bridgeC.String(), test_suite.LastAckQuery{LastAck: test_suite.LastAckQueryData{}}, &lastAck) require.NoError(suite.T(), err) require.Equal(suite.T(), "success", lastAck) } @@ -618,7 +619,7 @@ func (suite *AdversarialTestSuite) TestSendReplayAttack() { // First one should work. var lastAck string - err = suite.chainC.SmartQuery(suite.bridgeC.String(), LastAckQuery{LastAck: LastAckQueryData{}}, &lastAck) + err = suite.chainC.SmartQuery(suite.bridgeC.String(), test_suite.LastAckQuery{LastAck: test_suite.LastAckQueryData{}}, &lastAck) require.NoError(suite.T(), err) require.Equal(suite.T(), "success", lastAck) @@ -634,14 +635,14 @@ func (suite *AdversarialTestSuite) TestSendReplayAttack() { suite.coordinator.RelayAndAckPendingPackets(suite.pathAC.Invert()) // Second one should fail as the NFT has already been sent. - err = suite.chainC.SmartQuery(suite.bridgeC.String(), LastAckQuery{LastAck: LastAckQueryData{}}, &lastAck) + err = suite.chainC.SmartQuery(suite.bridgeC.String(), test_suite.LastAckQuery{LastAck: test_suite.LastAckQueryData{}}, &lastAck) require.NoError(suite.T(), err) require.Equal(suite.T(), "error", lastAck) // Make sure the receiver is the owner of the token. chainAClassId := fmt.Sprintf("%s/%s/%s", suite.pathAC.EndpointA.ChannelConfig.PortID, suite.pathAC.EndpointA.ChannelID, "classID") - chainACw721 := queryGetNftForClass(suite.T(), suite.chainA, suite.bridgeA.String(), chainAClassId) - chainAOwner := queryGetOwnerOf(suite.T(), suite.chainA, chainACw721, "bad kid 1") + chainACw721 := test_suite.QueryGetNftForClass(suite.T(), suite.chainA, suite.bridgeA.String(), chainAClassId) + chainAOwner := test_suite.QueryGetOwnerOf(suite.T(), suite.chainA, chainACw721, "bad kid 1") require.Equal(suite.T(), suite.chainA.SenderAccount.GetAddress().String(), chainAOwner) } @@ -664,41 +665,41 @@ func (suite *AdversarialTestSuite) TestDoubleSendInSingleMessage() { // Should fail. var lastAck string - err = suite.chainC.SmartQuery(suite.bridgeC.String(), LastAckQuery{LastAck: LastAckQueryData{}}, &lastAck) + err = suite.chainC.SmartQuery(suite.bridgeC.String(), test_suite.LastAckQuery{LastAck: test_suite.LastAckQueryData{}}, &lastAck) require.NoError(suite.T(), err) require.Equal(suite.T(), "error", lastAck) // No NFT should have been created. chainAClassId := fmt.Sprintf("%s/%s/%s", suite.pathAC.EndpointA.ChannelConfig.PortID, suite.pathAC.EndpointA.ChannelID, "classID") - chainACw721 := queryGetNftForClass(suite.T(), suite.chainA, suite.bridgeA.String(), chainAClassId) + chainACw721 := test_suite.QueryGetNftForClass(suite.T(), suite.chainA, suite.bridgeA.String(), chainAClassId) require.Equal(suite.T(), "", chainACw721) } // NOTE - we comment original test because this case no longer should be handled successfully. // please check below test for what should be done in this case. -// func (suite *AdversarialTestSuite) TestReceiveMultipleNtsDifferentActions() { +// func (test_suite *AdversarialTestSuite) TestReceiveMultipleNtsDifferentActions() { // // Send a NFT from chain A to the evil chain. -// ics721Nft(suite.T(), suite.chainA, suite.pathAC, suite.coordinator, suite.cw721A.String(), suite.tokenIdA, suite.bridgeA, suite.chainA.SenderAccount.GetAddress(), suite.chainB.SenderAccount.GetAddress(), "") +// test_suite.Ics721TransferNft(test_suite.T(), test_suite.chainA, test_suite.pathAC, test_suite.coordinator, test_suite.cw721A.String(), test_suite.tokenIdA, test_suite.bridgeA, test_suite.chainA.SenderAccount.GetAddress(), test_suite.chainB.SenderAccount.GetAddress(), "") -// chainAOwner := queryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), suite.tokenIdA) -// require.Equal(suite.T(), suite.bridgeA.String(), chainAOwner) +// chainAOwner := queryGetOwnerOf(test_suite.T(), test_suite.chainA, test_suite.cw721A.String(), test_suite.tokenIdA) +// require.Equal(test_suite.T(), test_suite.bridgeA.String(), chainAOwner) -// pathCA := suite.pathAC.Invert() -// chainCClassId := fmt.Sprintf("%s/%s/%s", pathCA.EndpointA.ChannelConfig.PortID, pathCA.EndpointA.ChannelID, suite.cw721A) +// pathCA := test_suite.pathAC.Invert() +// chainCClassId := fmt.Sprintf("%s/%s/%s", pathCA.EndpointA.ChannelConfig.PortID, pathCA.EndpointA.ChannelID, test_suite.cw721A) // // Evil chain responds with: // // // // class ID: class ID of sent NFT // // token IDs: [chainAToken, chainAToken] -// _, err := suite.chainC.SendMsgs(&wasmtypes.MsgExecuteContract{ -// Sender: suite.chainC.SenderAccount.GetAddress().String(), -// Contract: suite.bridgeC.String(), -// Msg: []byte(fmt.Sprintf(`{ "send_packet": { "channel_id": "%s", "timeout": { "timestamp": "%d" }, "data": {"classId":"%s","classUri":"https://metadata-url.com/my-metadata","tokenIds":["%s", "%s"],"tokenUris":["https://metadata-url.com/my-metadata1", "https://moonphase.is/image.svg"],"sender":"%s","receiver":"%s", "memo": "{'ignore': ''}"} }}`, pathCA.EndpointA.ChannelID, suite.coordinator.CurrentTime.Add(time.Hour*100).UnixNano(), chainCClassId, suite.tokenIdA, suite.tokenIdA, suite.chainC.SenderAccount.GetAddress().String(), suite.chainA.SenderAccount.GetAddress().String())), +// _, err := test_suite.chainC.SendMsgs(&wasmtypes.MsgExecuteContract{ +// Sender: test_suite.chainC.SenderAccount.GetAddress().String(), +// Contract: test_suite.bridgeC.String(), +// Msg: []byte(fmt.Sprintf(`{ "send_packet": { "channel_id": "%s", "timeout": { "timestamp": "%d" }, "data": {"classId":"%s","classUri":"https://metadata-url.com/my-metadata","tokenIds":["%s", "%s"],"tokenUris":["https://metadata-url.com/my-metadata1", "https://moonphase.is/image.svg"],"sender":"%s","receiver":"%s", "memo": "{'ignore': ''}"} }}`, pathCA.EndpointA.ChannelID, test_suite.coordinator.CurrentTime.Add(time.Hour*100).UnixNano(), chainCClassId, test_suite.tokenIdA, test_suite.tokenIdA, test_suite.chainC.SenderAccount.GetAddress().String(), test_suite.chainA.SenderAccount.GetAddress().String())), // Funds: []sdk.Coin{}, // }) -// require.NoError(suite.T(), err) -// suite.coordinator.UpdateTime() -// suite.coordinator.RelayAndAckPendingPackets(suite.pathAC.Invert()) +// require.NoError(test_suite.T(), err) +// test_suite.coordinator.UpdateTime() +// test_suite.coordinator.RelayAndAckPendingPackets(test_suite.pathAC.Invert()) // // All assumptions have now been violated. // // @@ -713,27 +714,27 @@ func (suite *AdversarialTestSuite) TestDoubleSendInSingleMessage() { // // rules. ICS721 contract says: // // // // > I know one of those tokens is valid and corresponds to the -// // > NFT I previously sent away so I will return that one to +// // > NFT I previously sent away, so I will return that one to // // > the recipient. For all I know chain C social norms allow // // > for more than one collection with the same ID, so for // // > that one I will create a new collection (so that it // // > follows my chain's social norms) and give a token for // // > that collection for the receiver. -// chainAOwner = queryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), suite.tokenIdA) -// require.Equal(suite.T(), suite.chainA.SenderAccount.GetAddress().String(), chainAOwner) +// chainAOwner = queryGetOwnerOf(test_suite.T(), test_suite.chainA, test_suite.cw721A.String(), test_suite.tokenIdA) +// require.Equal(test_suite.T(), test_suite.chainA.SenderAccount.GetAddress().String(), chainAOwner) -// chainAClassId := fmt.Sprintf("%s/%s/%s/%s/%s", suite.pathAC.EndpointA.ChannelConfig.PortID, suite.pathAC.EndpointA.ChannelID, pathCA.EndpointA.ChannelConfig.PortID, pathCA.EndpointA.ChannelID, suite.cw721A) -// chainANft := queryGetNftForClass(suite.T(), suite.chainA, suite.bridgeA.String(), chainAClassId) -// chainAOwner = queryGetOwnerOf(suite.T(), suite.chainA, chainANft, "bad kid 1") -// require.Equal(suite.T(), suite.chainA.SenderAccount.GetAddress().String(), chainAOwner) +// chainAClassId := fmt.Sprintf("%s/%s/%s/%s/%s", test_suite.pathAC.EndpointA.ChannelConfig.PortID, test_suite.pathAC.EndpointA.ChannelID, pathCA.EndpointA.ChannelConfig.PortID, pathCA.EndpointA.ChannelID, test_suite.cw721A) +// chainANft := queryGetNftForClass(test_suite.T(), test_suite.chainA, test_suite.bridgeA.String(), chainAClassId) +// chainAOwner = queryGetOwnerOf(test_suite.T(), test_suite.chainA, chainANft, "bad kid 1") +// require.Equal(test_suite.T(), test_suite.chainA.SenderAccount.GetAddress().String(), chainAOwner) // } func (suite *AdversarialTestSuite) TestReceiveMultipleNtsDifferentActions() { // Send a NFT from chain A to the evil chain. - ics721Nft(suite.T(), suite.chainA, suite.pathAC, suite.coordinator, suite.cw721A.String(), suite.tokenIdA, suite.bridgeA, suite.chainA.SenderAccount.GetAddress(), suite.chainB.SenderAccount.GetAddress(), "") + test_suite.Ics721TransferNft(suite.T(), suite.chainA, suite.pathAC, suite.coordinator, suite.cw721A.String(), suite.tokenIdA, suite.bridgeA, suite.chainA.SenderAccount.GetAddress(), suite.chainB.SenderAccount.GetAddress(), "") // Verify nft is escrowed by the bridge on A - chainAOwner := queryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), suite.tokenIdA) + chainAOwner := test_suite.QueryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), suite.tokenIdA) require.Equal(suite.T(), suite.bridgeA.String(), chainAOwner) pathCA := suite.pathAC.Invert() @@ -771,6 +772,6 @@ func (suite *AdversarialTestSuite) TestReceiveMultipleNtsDifferentActions() { // will still work, I catch what I can. // The NFT should still be escrowed by the bridge on chain A, tx failed. - chainAOwner = queryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), suite.tokenIdA) + chainAOwner = test_suite.QueryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), suite.tokenIdA) require.Equal(suite.T(), suite.bridgeA.String(), chainAOwner) } diff --git a/e2e/basic_test.go b/e2e/basic_test.go new file mode 100644 index 00000000..3a542783 --- /dev/null +++ b/e2e/basic_test.go @@ -0,0 +1,60 @@ +package e2e + +import ( + "encoding/json" + "testing" + + wasmibctesting "github.com/CosmWasm/wasmd/x/wasm/ibctesting" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/public-awesome/ics721/e2e/test_suite" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type BasicTestSuite struct { + suite.Suite + + coordinator *wasmibctesting.Coordinator + + // testing chains used for convenience and readability + chainA *wasmibctesting.TestChain + + chainABridge sdk.AccAddress +} + +func TestBasic(t *testing.T) { + suite.Run(t, new(BasicTestSuite)) +} + +func (suite *BasicTestSuite) SetupTest() { + suite.coordinator = wasmibctesting.NewCoordinator(suite.T(), 2) + suite.chainA = suite.coordinator.GetChain(wasmibctesting.GetChainID(0)) +} + +func (suite *BasicTestSuite) TestStoreCodes() { + // Store the ICS721 contract. + chainAStoreResp := suite.chainA.StoreCodeFile("../artifacts/ics721_base.wasm") + require.Equal(suite.T(), uint64(1), chainAStoreResp.CodeID) + + // Store the cw721 contract. + chainAStoreResp = suite.chainA.StoreCodeFile("../external-wasms/cw721_base_v0.18.0.wasm") + require.Equal(suite.T(), uint64(2), chainAStoreResp.CodeID) +} + +func (suite *BasicTestSuite) TestInstantiateIcs721() { + // Store the ICS721 contract. + chainAStoreResp := suite.chainA.StoreCodeFile("../artifacts/ics721_base.wasm") + require.Equal(suite.T(), uint64(1), chainAStoreResp.CodeID) + + // Instantiate the ICS721 contract. + instantiateICS721 := test_suite.InstantiateICS721Bridge{ + Cw721BaseCodeId: 1, + // no pauser nor proxy by default. + OutgoingProxy: nil, + IncomingProxy: nil, + Pauser: nil, + } + instantiateICS721Raw, err := json.Marshal(instantiateICS721) + require.NoError(suite.T(), err) + suite.chainABridge = suite.chainA.InstantiateContract(1, instantiateICS721Raw) +} diff --git a/e2e/callback_test.go b/e2e/callback_test.go index 8ecf69c1..e7846b39 100644 --- a/e2e/callback_test.go +++ b/e2e/callback_test.go @@ -1,12 +1,13 @@ -package e2e_test +package e2e import ( - b64 "encoding/base64" "encoding/json" "fmt" "testing" "time" + "github.com/public-awesome/ics721/e2e/test_suite" + wasmibctesting "github.com/CosmWasm/wasmd/x/wasm/ibctesting" wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -16,7 +17,7 @@ import ( "github.com/stretchr/testify/suite" ) -type CbTestSuite struct { +type CallbackTestSuite struct { suite.Suite coordinator *wasmibctesting.Coordinator @@ -42,16 +43,15 @@ type CbTestSuite struct { cw721B sdk.AccAddress } -func TestCallbacks(t *testing.T) { - suite.Run(t, new(CbTestSuite)) +func TestCallback(t *testing.T) { + suite.Run(t, new(CallbackTestSuite)) } -func (suite *CbTestSuite) SetupTest() { +func (suite *CallbackTestSuite) SetupTest() { suite.coordinator = wasmibctesting.NewCoordinator(suite.T(), 3) suite.chainA = suite.coordinator.GetChain(wasmibctesting.GetChainID(0)) suite.chainB = suite.coordinator.GetChain(wasmibctesting.GetChainID(1)) suite.chainC = suite.coordinator.GetChain(wasmibctesting.GetChainID(2)) - // suite.coordinator.CommitBlock(suite.chainA, suite.chainB) // Store codes and instantiate contracts storeCodes := func(chain *wasmibctesting.TestChain, bridge *sdk.AccAddress, tester *sdk.AccAddress, num int) { @@ -66,11 +66,11 @@ func (suite *CbTestSuite) SetupTest() { // init dummy contracts based on how much we need for i := 0; i < num; i++ { - cw721Instantiate := InstantiateCw721{ - "bad/kids", - "bad/kids", - suite.chainA.SenderAccount.GetAddress().String(), - nil, + cw721Instantiate := test_suite.InstantiateCw721v18{ + Name: "bad/kids", + Symbol: "bad/kids", + Minter: suite.chainA.SenderAccount.GetAddress().String(), + WithdrawAddress: nil, } instantiateRaw, err := json.Marshal(cw721Instantiate) require.NoError(suite.T(), err) @@ -79,11 +79,11 @@ func (suite *CbTestSuite) SetupTest() { } // init ics721 - instantiateBridge := InstantiateICS721Bridge{ - 2, - nil, - nil, - nil, + instantiateBridge := test_suite.InstantiateICS721Bridge{ + Cw721BaseCodeId: 2, + OutgoingProxy: nil, + IncomingProxy: nil, + Pauser: nil, } instantiateBridgeRaw, err := json.Marshal(instantiateBridge) require.NoError(suite.T(), err) @@ -91,9 +91,9 @@ func (suite *CbTestSuite) SetupTest() { *bridge = chain.InstantiateContract(1, instantiateBridgeRaw) // init tester - instantiateBridgeTester := InstantiateBridgeTester{ - "success", - bridge.String(), + instantiateBridgeTester := test_suite.InstantiateBridgeTester{ + AckMode: "success", + Ics721: bridge.String(), } instantiateBridgeTesterRaw, err := json.Marshal(instantiateBridgeTester) require.NoError(suite.T(), err) @@ -131,11 +131,11 @@ func (suite *CbTestSuite) SetupTest() { suite.pathBC = initPath(suite.chainB, suite.chainC, suite.bridgeB, suite.bridgeC) // init cw721 on chain A - cw721Instantiate := InstantiateCw721{ - "bad/kids", - "bad/kids", - suite.chainA.SenderAccount.GetAddress().String(), - nil, + cw721Instantiate := test_suite.InstantiateCw721v18{ + Name: "bad/kids", + Symbol: "bad/kids", + Minter: suite.chainA.SenderAccount.GetAddress().String(), + WithdrawAddress: nil, } instantiateRaw, err := json.Marshal(cw721Instantiate) require.NoError(suite.T(), err) @@ -151,10 +151,10 @@ func (suite *CbTestSuite) SetupTest() { require.NoError(suite.T(), err) //Send NFT to chain B - ics721Nft(suite.T(), suite.chainA, suite.pathAB, suite.coordinator, suite.cw721A.String(), "1", suite.bridgeA, suite.chainA.SenderAccount.GetAddress(), suite.chainB.SenderAccount.GetAddress(), "") + test_suite.Ics721TransferNft(suite.T(), suite.chainA, suite.pathAB, suite.coordinator, suite.cw721A.String(), "1", suite.bridgeA, suite.chainA.SenderAccount.GetAddress(), suite.chainB.SenderAccount.GetAddress(), "") classIdChainB := fmt.Sprintf("%s/%s/%s", suite.pathAB.EndpointB.ChannelConfig.PortID, suite.pathAB.EndpointB.ChannelID, suite.cw721A.String()) - addr := queryGetNftForClass(suite.T(), suite.chainB, suite.bridgeB.String(), classIdChainB) + addr := test_suite.QueryGetNftForClass(suite.T(), suite.chainB, suite.bridgeB.String(), classIdChainB) suite.cw721B, err = sdk.AccAddressFromBech32(addr) require.NoError(suite.T(), err) @@ -195,351 +195,202 @@ func (suite *CbTestSuite) SetupTest() { suite.T().Logf("chain B cw721) = (%s)", suite.cw721B.String()) } -func callbackMemo(src_cb, src_receiver, dest_cb, dest_receiver string) string { - src_cb = parseOptional(src_cb) - src_receiver = parseOptional(src_receiver) - dest_cb = parseOptional(dest_cb) - dest_receiver = parseOptional(dest_receiver) - memo := fmt.Sprintf(`{ "callbacks": { "ack_callback_data": %s, "ack_callback_addr": %s, "receive_callback_data": %s, "receive_callback_addr": %s } }`, src_cb, src_receiver, dest_cb, dest_receiver) - return b64.StdEncoding.EncodeToString([]byte(memo)) -} - -func nftSentCb() string { - return b64.StdEncoding.EncodeToString([]byte(`{ "nft_sent": {}}`)) -} - -func nftReceivedCb() string { - return b64.StdEncoding.EncodeToString([]byte(`{ "nft_received": {}}`)) -} - -func failedCb() string { - return b64.StdEncoding.EncodeToString([]byte(`{ "fail_callback": {}}`)) -} - -func sendIcsFromChainAToB(suite *CbTestSuite, nft, token_id, memo string, relay bool) { - msg := fmt.Sprintf(`{ "send_nft": {"cw721": "%s", "ics721": "%s", "token_id": "%s", "recipient":"%s", "channel_id":"%s", "memo":"%s"}}`, nft, suite.bridgeA.String(), token_id, suite.testerB.String(), suite.pathAB.EndpointA.ChannelID, memo) - _, err := suite.chainA.SendMsgs(&wasmtypes.MsgExecuteContract{ - Sender: suite.chainA.SenderAccount.GetAddress().String(), - Contract: suite.testerA.String(), - Msg: []byte(msg), - Funds: []sdk.Coin{}, - }) - require.NoError(suite.T(), err) - - if relay { - suite.coordinator.UpdateTime() - suite.coordinator.RelayAndAckPendingPackets(suite.pathAB) - suite.coordinator.UpdateTime() - } -} - -func sendIcsFromChainAToC(suite *CbTestSuite, nft, token_id, memo string, relay bool) { - msg := fmt.Sprintf(`{ "send_nft": {"cw721": "%s", "ics721": "%s", "token_id": "%s", "recipient":"%s", "channel_id":"%s", "memo":"%s"}}`, nft, suite.bridgeA.String(), token_id, suite.testerC.String(), suite.pathAC.EndpointA.ChannelID, memo) - _, err := suite.chainA.SendMsgs(&wasmtypes.MsgExecuteContract{ - Sender: suite.chainA.SenderAccount.GetAddress().String(), - Contract: suite.testerA.String(), - Msg: []byte(msg), - Funds: []sdk.Coin{}, - }) - require.NoError(suite.T(), err) +func (suite *CallbackTestSuite) TestSuccessfulTransfer() { + memo := test_suite.CreateCallbackMemo(test_suite.NftCallbackSent(), "", test_suite.NftCallbackReceived(), "") - if relay { - suite.coordinator.UpdateTime() - suite.coordinator.RelayAndAckPendingPackets(suite.pathAC) - suite.coordinator.UpdateTime() - } -} - -func sendIcsFromChainBToA(suite *CbTestSuite, nft, token_id, memo string, relay bool) { - msg := fmt.Sprintf(`{ "send_nft": {"cw721": "%s", "ics721": "%s", "token_id": "%s", "recipient":"%s", "channel_id":"%s", "memo":"%s"}}`, nft, suite.bridgeB.String(), token_id, suite.testerA.String(), suite.pathAB.EndpointB.ChannelID, memo) - _, err := suite.chainB.SendMsgs(&wasmtypes.MsgExecuteContract{ - Sender: suite.chainB.SenderAccount.GetAddress().String(), - Contract: suite.testerB.String(), - Msg: []byte(msg), - Funds: []sdk.Coin{}, - }) - require.NoError(suite.T(), err) - - if relay { - suite.coordinator.UpdateTime() - suite.coordinator.RelayAndAckPendingPackets(suite.pathAB.Invert()) - } -} - -func sendIcsFromChainBToC(suite *CbTestSuite, nft, token_id, memo string, relay bool) { - msg := fmt.Sprintf(`{ "send_nft": {"cw721": "%s", "ics721": "%s", "token_id": "%s", "recipient":"%s", "channel_id":"%s", "memo":"%s"}}`, nft, suite.bridgeB.String(), token_id, suite.testerC.String(), suite.pathBC.EndpointA.ChannelID, memo) - _, err := suite.chainB.SendMsgs(&wasmtypes.MsgExecuteContract{ - Sender: suite.chainB.SenderAccount.GetAddress().String(), - Contract: suite.testerB.String(), - Msg: []byte(msg), - Funds: []sdk.Coin{}, - }) - require.NoError(suite.T(), err) - - if relay { - suite.coordinator.UpdateTime() - suite.coordinator.RelayAndAckPendingPackets(suite.pathBC) - suite.coordinator.UpdateTime() - } -} - -func sendIcsFromChainCToB(suite *CbTestSuite, nft, token_id, memo string, relay bool) { - msg := fmt.Sprintf(`{ "send_nft": {"cw721": "%s", "ics721": "%s", "token_id": "%s", "recipient":"%s", "channel_id":"%s", "memo":"%s"}}`, nft, suite.bridgeC.String(), token_id, suite.testerB.String(), suite.pathBC.EndpointB.ChannelID, memo) - _, err := suite.chainC.SendMsgs(&wasmtypes.MsgExecuteContract{ - Sender: suite.chainC.SenderAccount.GetAddress().String(), - Contract: suite.testerC.String(), - Msg: []byte(msg), - Funds: []sdk.Coin{}, - }) - require.NoError(suite.T(), err) - - if relay { - suite.coordinator.UpdateTime() - suite.coordinator.RelayAndAckPendingPackets(suite.pathBC.Invert()) - suite.coordinator.UpdateTime() - } -} - -func sendIcsFromChainCToA(suite *CbTestSuite, nft, token_id, memo string, relay bool) { - msg := fmt.Sprintf(`{ "send_nft": {"cw721": "%s", "ics721": "%s", "token_id": "%s", "recipient":"%s", "channel_id":"%s", "memo":"%s"}}`, nft, suite.bridgeC.String(), token_id, suite.testerA.String(), suite.pathAC.EndpointB.ChannelID, memo) - _, err := suite.chainC.SendMsgs(&wasmtypes.MsgExecuteContract{ - Sender: suite.chainC.SenderAccount.GetAddress().String(), - Contract: suite.testerC.String(), - Msg: []byte(msg), - Funds: []sdk.Coin{}, - }) - require.NoError(suite.T(), err) - - if relay { - suite.coordinator.UpdateTime() - suite.coordinator.RelayAndAckPendingPackets(suite.pathAC.Invert()) - } -} - -func queryTesterSent(t *testing.T, chain *wasmibctesting.TestChain, tester string) string { - resp := TesterResponse{} - testerSentQuery := TesterSentQuery{ - GetSentCallback: EmptyData{}, - } - err := chain.SmartQuery(tester, testerSentQuery, &resp) - require.NoError(t, err) - return *resp.Owner -} - -func queryTesterReceived(t *testing.T, chain *wasmibctesting.TestChain, tester string) string { - resp := TesterResponse{} - testerReceivedQuery := TesterReceivedQuery{ - GetReceivedCallback: EmptyData{}, - } - err := chain.SmartQuery(tester, testerReceivedQuery, &resp) - require.NoError(t, err) - return *resp.Owner -} - -func queryTesterNftContract(t *testing.T, chain *wasmibctesting.TestChain, tester string) string { - resp := "" - testerReceivedQuery := TesterNftContractQuery{ - GetNftContract: EmptyData{}, - } - err := chain.SmartQuery(tester, testerReceivedQuery, &resp) - require.NoError(t, err) - return resp -} - -func queryTesterReceivedErr(t *testing.T, chain *wasmibctesting.TestChain, tester string) error { - resp := TesterResponse{} - testerReceivedQuery := TesterReceivedQuery{ - GetReceivedCallback: EmptyData{}, - } - err := chain.SmartQuery(tester, testerReceivedQuery, &resp) - return err -} - -func (suite *CbTestSuite) TestSuccessfulTransfer() { - memo := callbackMemo(nftSentCb(), "", nftReceivedCb(), "") - sendIcsFromChainAToB(suite, suite.cw721A.String(), "2", memo, true) + // A -> B token_id 2 + test_suite.SendNftToIcs721AndRelay(suite.T(), suite.coordinator, suite.chainA, suite.bridgeA, suite.testerA, suite.testerB, suite.pathAB, suite.pathAB.EndpointA, suite.cw721A.String(), "2", memo, true) // Query the owner of NFT on cw721 - chainAOwner := queryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), "2") + chainAOwner := test_suite.QueryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), "2") require.Equal(suite.T(), chainAOwner, suite.bridgeA.String()) - chainBOwner := queryGetOwnerOf(suite.T(), suite.chainB, suite.cw721B.String(), "2") + chainBOwner := test_suite.QueryGetOwnerOf(suite.T(), suite.chainB, suite.cw721B.String(), "2") require.Equal(suite.T(), chainBOwner, suite.testerB.String()) // We query the data we have on the tester contract // This ensures that the callbacks are called after all the messages was completed // and the transfer was successful - testerDataOwnerA := queryTesterSent(suite.T(), suite.chainA, suite.testerA.String()) + testerDataOwnerA := test_suite.QueryTesterSent(suite.T(), suite.chainA, suite.testerA.String()) require.Equal(suite.T(), testerDataOwnerA, suite.bridgeA.String()) - testerNftContract := queryTesterNftContract(suite.T(), suite.chainB, suite.testerB.String()) + testerNftContract := test_suite.QueryTesterNftContract(suite.T(), suite.chainB, suite.testerB.String()) require.Equal(suite.T(), testerNftContract, suite.cw721B.String()) - testerDataOwnerB := queryTesterReceived(suite.T(), suite.chainB, suite.testerB.String()) + testerDataOwnerB := test_suite.QueryTesterReceived(suite.T(), suite.chainB, suite.testerB.String()) require.Equal(suite.T(), testerDataOwnerB, suite.testerB.String()) } -func (suite *CbTestSuite) TestSuccessfulTransferWithReceivers() { - memo := callbackMemo(nftSentCb(), suite.testerA.String(), nftReceivedCb(), suite.testerB.String()) +func (suite *CallbackTestSuite) TestSuccessfulTransferWithReceivers() { + memo := test_suite.CreateCallbackMemo(test_suite.NftCallbackSent(), suite.testerA.String(), test_suite.NftCallbackReceived(), suite.testerB.String()) // Send NFT to chain B - ics721Nft(suite.T(), suite.chainA, suite.pathAB, suite.coordinator, suite.cw721A.String(), "3", suite.bridgeA, suite.chainA.SenderAccount.GetAddress(), suite.chainB.SenderAccount.GetAddress(), memo) + test_suite.Ics721TransferNft(suite.T(), suite.chainA, suite.pathAB, suite.coordinator, suite.cw721A.String(), "3", suite.bridgeA, suite.chainA.SenderAccount.GetAddress(), suite.chainB.SenderAccount.GetAddress(), memo) // Query the owner of NFT on cw721 - chainAOwner := queryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), "3") + chainAOwner := test_suite.QueryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), "3") require.Equal(suite.T(), chainAOwner, suite.bridgeA.String()) - chainBOwner := queryGetOwnerOf(suite.T(), suite.chainB, suite.cw721B.String(), "3") + chainBOwner := test_suite.QueryGetOwnerOf(suite.T(), suite.chainB, suite.cw721B.String(), "3") require.Equal(suite.T(), chainBOwner, suite.chainB.SenderAccount.GetAddress().String()) // We query the data we have on the tester contract // This ensures that the callbacks are called after all the messages was completed // and the transfer was successful - testerDataOwnerA := queryTesterSent(suite.T(), suite.chainA, suite.testerA.String()) + testerDataOwnerA := test_suite.QueryTesterSent(suite.T(), suite.chainA, suite.testerA.String()) require.Equal(suite.T(), testerDataOwnerA, suite.bridgeA.String()) - testerNftContract := queryTesterNftContract(suite.T(), suite.chainB, suite.testerB.String()) + testerNftContract := test_suite.QueryTesterNftContract(suite.T(), suite.chainB, suite.testerB.String()) require.Equal(suite.T(), testerNftContract, suite.cw721B.String()) - testerDataOwnerB := queryTesterReceived(suite.T(), suite.chainB, suite.testerB.String()) + testerDataOwnerB := test_suite.QueryTesterReceived(suite.T(), suite.chainB, suite.testerB.String()) require.Equal(suite.T(), testerDataOwnerB, suite.chainB.SenderAccount.GetAddress().String()) } -func (suite *CbTestSuite) TestTimeoutTransfer() { - memo := callbackMemo(nftSentCb(), "", nftReceivedCb(), "") - sendIcsFromChainAToB(suite, suite.cw721A.String(), "2", memo, false) +func (suite *CallbackTestSuite) TestTimeoutTransfer() { + memo := test_suite.CreateCallbackMemo(test_suite.NftCallbackSent(), "", test_suite.NftCallbackReceived(), "") + // A -> B token_id 2 + test_suite.SendNftToIcs721AndRelay(suite.T(), suite.coordinator, suite.chainA, suite.bridgeA, suite.testerA, suite.testerB, suite.pathAB, suite.pathAB.EndpointA, suite.cw721A.String(), "2", memo, false) + suite.coordinator.IncrementTimeBy(time.Second * 2001) suite.coordinator.UpdateTime() suite.coordinator.TimeoutPendingPackets(suite.pathAB) // Query the owner of NFT on cw721 - chainAOwner := queryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), "2") + chainAOwner := test_suite.QueryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), "2") require.Equal(suite.T(), chainAOwner, suite.testerA.String()) - err := queryGetOwnerOfErr(suite.T(), suite.chainB, suite.cw721B.String(), "2") + err := test_suite.QueryGetOwnerOfErr(suite.T(), suite.chainB, suite.cw721B.String(), "2") require.Error(suite.T(), err) // callbacks should update sender contract of the failed transfer // so we query the contract to see who is the new owner // if the query is working and owner is correct, we can confirm the callback was called successfully - testerDataOwnerA := queryTesterSent(suite.T(), suite.chainA, suite.testerA.String()) + testerDataOwnerA := test_suite.QueryTesterSent(suite.T(), suite.chainA, suite.testerA.String()) require.Equal(suite.T(), testerDataOwnerA, suite.testerA.String()) // Querying the receving end, should fail because we did not receive the NFT // so the callback should not have been called. - err = queryTesterReceivedErr(suite.T(), suite.chainB, suite.testerB.String()) + err = test_suite.QueryTesterReceivedErr(suite.T(), suite.chainB, suite.testerB.String()) require.Error(suite.T(), err) } -func (suite *CbTestSuite) TestFailedCallbackTransfer() { - memo := callbackMemo(nftSentCb(), "", failedCb(), "") - sendIcsFromChainAToB(suite, suite.cw721A.String(), "2", memo, true) +func (suite *CallbackTestSuite) TestFailedCallbackTransfer() { + memo := test_suite.CreateCallbackMemo(test_suite.NftCallbackSent(), "", test_suite.NftCallbackFailed(), "") + // A -> B token_id 2 + test_suite.SendNftToIcs721AndRelay(suite.T(), suite.coordinator, suite.chainA, suite.bridgeA, suite.testerA, suite.testerB, suite.pathAB, suite.pathAB.EndpointA, suite.cw721A.String(), "2", memo, true) // Query the owner of NFT on cw721 - chainAOwner := queryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), "2") + chainAOwner := test_suite.QueryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), "2") require.Equal(suite.T(), chainAOwner, suite.testerA.String()) - err := queryGetOwnerOfErr(suite.T(), suite.chainB, suite.cw721B.String(), "2") + err := test_suite.QueryGetOwnerOfErr(suite.T(), suite.chainB, suite.cw721B.String(), "2") require.Error(suite.T(), err) // callbacks should update sender contract of the failed transfer // so we query the contract to see who is the new owner // if the query is working and owner is correct, we can confirm the callback was called successfully - testerDataOwnerA := queryTesterSent(suite.T(), suite.chainA, suite.testerA.String()) + testerDataOwnerA := test_suite.QueryTesterSent(suite.T(), suite.chainA, suite.testerA.String()) require.Equal(suite.T(), testerDataOwnerA, suite.testerA.String()) // Querying the receving end, should fail because we did not receive the NFT // so the callback should not have been called. - err = queryTesterReceivedErr(suite.T(), suite.chainB, suite.testerB.String()) + err = test_suite.QueryTesterReceivedErr(suite.T(), suite.chainB, suite.testerB.String()) require.Error(suite.T(), err) } -func (suite *CbTestSuite) TestFailedCallbackOnAck() { +func (suite *CallbackTestSuite) TestFailedCallbackOnAck() { // Transfer to chain B - memo := callbackMemo("", "", "", "") - sendIcsFromChainAToB(suite, suite.cw721A.String(), "2", memo, true) + memo := test_suite.CreateCallbackMemo("", "", "", "") + // A -> B token_id 2 + test_suite.SendNftToIcs721AndRelay(suite.T(), suite.coordinator, suite.chainA, suite.bridgeA, suite.testerA, suite.testerB, suite.pathAB, suite.pathAB.EndpointA, suite.cw721A.String(), "2", memo, true) // Transfer from B to chain A, // We fail the ack callback and see if the NFT was burned or not // Because the transfer should be successful even if the ack callback is failing // we make sure that the NFT was burned on chain B, and that the owner is correct on chain A - memo = callbackMemo(failedCb(), "", "", "") - sendIcsFromChainBToA(suite, suite.cw721B.String(), "2", memo, true) + memo = test_suite.CreateCallbackMemo(test_suite.NftCallbackFailed(), "", "", "") + // B -> A token_id 2 + test_suite.SendNftToIcs721AndRelay(suite.T(), suite.coordinator, suite.chainB, suite.bridgeB, suite.testerB, suite.testerA, suite.pathAB.Invert(), suite.pathAB.EndpointB, suite.cw721B.String(), "2", memo, true) // Transfer was successful, so the owner on chain A should be the testerA - chainAOwner := queryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), "2") + chainAOwner := test_suite.QueryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), "2") require.Equal(suite.T(), chainAOwner, suite.testerA.String()) // Transfer was successful, so nft "2" should be burned and fail the query - err := queryGetOwnerOfErr(suite.T(), suite.chainB, suite.cw721B.String(), "2") + err := test_suite.QueryGetOwnerOfErr(suite.T(), suite.chainB, suite.cw721B.String(), "2") require.Error(suite.T(), err) // We don't do any query on tester, because we don't have receive callback set // and the ack callback should fail, so no data to show. } -func (suite *CbTestSuite) TestMultipleChainsTransfers() { - confirmNftContracts := func(ackChain *wasmibctesting.TestChain, receiveChain *wasmibctesting.TestChain, testerAck string, testerReceive string, expectAck string, expectReceive string) { - ackContract := queryTesterNftContract(suite.T(), ackChain, testerAck) +func (suite *CallbackTestSuite) TestMultipleChainsTransfers() { + confirmNftContracts := func(ackChain *wasmibctesting.TestChain, receiveChain *wasmibctesting.TestChain, testerAck, testerReceive, expectAck, expectReceive string) { + ackContract := test_suite.QueryTesterNftContract(suite.T(), ackChain, testerAck) require.Equal(suite.T(), ackContract, expectAck) - receiveContract := queryTesterNftContract(suite.T(), receiveChain, testerReceive) + receiveContract := test_suite.QueryTesterNftContract(suite.T(), receiveChain, testerReceive) require.Equal(suite.T(), receiveContract, expectReceive) } - memo := callbackMemo(nftSentCb(), "", nftReceivedCb(), "") - sendIcsFromChainAToB(suite, suite.cw721A.String(), "2", memo, true) + memo := test_suite.CreateCallbackMemo(test_suite.NftCallbackSent(), "", test_suite.NftCallbackReceived(), "") + + // A -> B token_id 2 + test_suite.SendNftToIcs721AndRelay(suite.T(), suite.coordinator, suite.chainA, suite.bridgeA, suite.testerA, suite.testerB, suite.pathAB, suite.pathAB.EndpointA, suite.cw721A.String(), "2", memo, true) // Owner should be the bridge on chain A - chainAOwner := queryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), "2") + chainAOwner := test_suite.QueryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), "2") require.Equal(suite.T(), chainAOwner, suite.bridgeA.String()) - chainBOwner := queryGetOwnerOf(suite.T(), suite.chainB, suite.cw721B.String(), "2") + chainBOwner := test_suite.QueryGetOwnerOf(suite.T(), suite.chainB, suite.cw721B.String(), "2") require.Equal(suite.T(), chainBOwner, suite.testerB.String()) confirmNftContracts(suite.chainA, suite.chainB, suite.testerA.String(), suite.testerB.String(), suite.cw721A.String(), suite.cw721B.String()) - // Send from ChainB to ChainC - sendIcsFromChainBToC(suite, suite.cw721B.String(), "2", memo, true) + // B -> C again token_id 2 + test_suite.SendNftToIcs721AndRelay(suite.T(), suite.coordinator, suite.chainB, suite.bridgeB, suite.testerB, suite.testerC, suite.pathBC, suite.pathBC.EndpointA, suite.cw721B.String(), "2", memo, true) // Get the cw721 address on ChainC when received from ChainB BCClassId := fmt.Sprintf("%s/%s/%s/%s/%s", suite.pathBC.EndpointB.ChannelConfig.PortID, suite.pathBC.EndpointB.ChannelID, suite.pathAB.EndpointB.ChannelConfig.PortID, suite.pathAB.EndpointB.ChannelID, suite.cw721A) - BCCw721 := queryGetNftForClass(suite.T(), suite.chainC, suite.bridgeC.String(), BCClassId) + BCCw721 := test_suite.QueryGetNftForClass(suite.T(), suite.chainC, suite.bridgeC.String(), BCClassId) - chainBOwner = queryGetOwnerOf(suite.T(), suite.chainB, suite.cw721B.String(), "2") + chainBOwner = test_suite.QueryGetOwnerOf(suite.T(), suite.chainB, suite.cw721B.String(), "2") require.Equal(suite.T(), chainBOwner, suite.bridgeB.String()) // Make sure the transfer was correct and successful - chainCOwner := queryGetOwnerOf(suite.T(), suite.chainC, BCCw721, "2") + chainCOwner := test_suite.QueryGetOwnerOf(suite.T(), suite.chainC, BCCw721, "2") require.Equal(suite.T(), chainCOwner, suite.testerC.String()) confirmNftContracts(suite.chainB, suite.chainC, suite.testerB.String(), suite.testerC.String(), suite.cw721B.String(), BCCw721) - // Send from ChainA to ChainC - sendIcsFromChainAToC(suite, suite.cw721A.String(), "4", memo, true) + // A -> C token_id 4 + test_suite.SendNftToIcs721AndRelay(suite.T(), suite.coordinator, suite.chainA, suite.bridgeA, suite.testerA, suite.testerC, suite.pathAC, suite.pathAC.EndpointA, suite.cw721A.String(), "4", memo, true) // Get the cw721 address on ChainC when received from ChainB ACClassId := fmt.Sprintf("%s/%s/%s", suite.pathAC.EndpointB.ChannelConfig.PortID, suite.pathAC.EndpointB.ChannelID, suite.cw721A) - ACCw721 := queryGetNftForClass(suite.T(), suite.chainC, suite.bridgeC.String(), ACClassId) + ACCw721 := test_suite.QueryGetNftForClass(suite.T(), suite.chainC, suite.bridgeC.String(), ACClassId) // Confirm tester is the owner on Chain C of the nft id "4" - chainCOwner = queryGetOwnerOf(suite.T(), suite.chainC, ACCw721, "4") + chainCOwner = test_suite.QueryGetOwnerOf(suite.T(), suite.chainC, ACCw721, "4") require.Equal(suite.T(), chainCOwner, suite.testerC.String()) confirmNftContracts(suite.chainA, suite.chainC, suite.testerA.String(), suite.testerC.String(), suite.cw721A.String(), ACCw721) // Let send back all NFTs to Chain A - sendIcsFromChainCToA(suite, ACCw721, "4", memo, true) + // C -> A token_id 4 + test_suite.SendNftToIcs721AndRelay(suite.T(), suite.coordinator, suite.chainC, suite.bridgeC, suite.testerC, suite.testerA, suite.pathAC.Invert(), suite.pathAC.EndpointB, ACCw721, "4", memo, true) confirmNftContracts(suite.chainC, suite.chainA, suite.testerC.String(), suite.testerA.String(), ACCw721, suite.cw721A.String()) - sendIcsFromChainCToB(suite, BCCw721, "2", memo, true) + // C -> B token_id 2 + test_suite.SendNftToIcs721AndRelay(suite.T(), suite.coordinator, suite.chainC, suite.bridgeC, suite.testerC, suite.testerB, suite.pathBC.Invert(), suite.pathBC.EndpointB, BCCw721, "2", memo, true) confirmNftContracts(suite.chainC, suite.chainB, suite.testerC.String(), suite.testerB.String(), BCCw721, suite.cw721B.String()) - - sendIcsFromChainBToA(suite, suite.cw721B.String(), "2", memo, true) + // B -> A again token_id 2 + test_suite.SendNftToIcs721AndRelay(suite.T(), suite.coordinator, suite.chainB, suite.bridgeB, suite.testerB, suite.testerA, suite.pathAB.Invert(), suite.pathAB.EndpointB, suite.cw721B.String(), "2", memo, true) confirmNftContracts(suite.chainB, suite.chainA, suite.testerB.String(), suite.testerA.String(), suite.cw721B.String(), suite.cw721A.String()) - chainAOwner1 := queryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), "2") + chainAOwner1 := test_suite.QueryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), "2") require.Equal(suite.T(), chainAOwner1, suite.testerA.String()) - chainAOwner2 := queryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), "4") + chainAOwner2 := test_suite.QueryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), "4") require.Equal(suite.T(), chainAOwner2, suite.testerA.String()) // NFTs should no exist on Chain B and Chain C, they should be burned and query for owner should error - err := queryGetOwnerOfErr(suite.T(), suite.chainB, suite.cw721B.String(), "2") + err := test_suite.QueryGetOwnerOfErr(suite.T(), suite.chainB, suite.cw721B.String(), "2") require.Error(suite.T(), err) - err = queryGetOwnerOfErr(suite.T(), suite.chainC, BCCw721, "2") + err = test_suite.QueryGetOwnerOfErr(suite.T(), suite.chainC, BCCw721, "2") require.Error(suite.T(), err) - err = queryGetOwnerOfErr(suite.T(), suite.chainC, ACCw721, "4") + err = test_suite.QueryGetOwnerOfErr(suite.T(), suite.chainC, ACCw721, "4") require.Error(suite.T(), err) } diff --git a/e2e/execute.go b/e2e/execute.go deleted file mode 100644 index cc2861b3..00000000 --- a/e2e/execute.go +++ /dev/null @@ -1,28 +0,0 @@ -package e2e_test - -import ( - "fmt" - "testing" - - wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" - wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/public-awesome/stargaze/v12/app" - "github.com/stretchr/testify/require" -) - -func InstantiateBridge(t *testing.T, ctx sdk.Context, app *app.App, creatorAddress string, cw721CodeID uint64, bridgeCodeID uint64) *wasmtypes.MsgInstantiateContractResponse { - msgServer := wasmkeeper.NewMsgServerImpl(wasmkeeper.NewDefaultPermissionKeeper(app.WasmKeeper)) - - instantiateMsgRaw := []byte(fmt.Sprintf(`{ "cw721_base_code_id": %d }`, cw721CodeID)) - instantiateRes, err := msgServer.InstantiateContract(sdk.WrapSDKContext(ctx), &wasmtypes.MsgInstantiateContract{ - Sender: creatorAddress, - Admin: "", - CodeID: bridgeCodeID, - Label: "ICS721 contract", - Msg: instantiateMsgRaw, - Funds: []sdk.Coin{}, - }) - require.NoError(t, err) - return instantiateRes -} diff --git a/e2e/full_test.go b/e2e/full_test.go deleted file mode 100644 index beb54dd5..00000000 --- a/e2e/full_test.go +++ /dev/null @@ -1,141 +0,0 @@ -package e2e_test - -import ( - "io/ioutil" - "testing" - "time" - - wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" - wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - 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" - "github.com/public-awesome/stargaze/v12/app" - "github.com/public-awesome/stargaze/v12/testutil/simapp" - "github.com/stretchr/testify/require" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" -) - -func GetAccounts() []Account { - accounts := make([]Account, 0, 150) - for i := 0; i < 150; i++ { - priv := secp256k1.GenPrivKey() - pub := priv.PubKey() - addr := sdk.AccAddress(pub.Address()) - acc := Account{ - PrivKey: priv, - PubKey: pub, - Address: addr, - } - accounts = append(accounts, acc) - } - return accounts -} - -func GetAccountsAndBalances(accs []Account) ([]authtypes.GenesisAccount, []banktypes.Balance) { - genAccs := make([]authtypes.GenesisAccount, 0, len(accs)) - balances := make([]banktypes.Balance, 0, len(accs)) - for _, a := range accs { - genAcc := authtypes.BaseAccount{ - Address: a.Address.String(), - } - balance := banktypes.Balance{ - Address: a.Address.String(), - Coins: sdk.NewCoins(sdk.NewInt64Coin("ustars", 2_000_000_000)), - } - genAccs = append(genAccs, &genAcc) - balances = append(balances, balance) - } - return genAccs, balances -} - -func LoadChain(t *testing.T) (sdk.AccAddress, sdk.Context, *app.App, []Account) { - accs := GetAccounts() - genAccs, balances := GetAccountsAndBalances(accs) - - app := simapp.SetupWithGenesisAccounts(t, t.TempDir(), genAccs, balances...) - - startDateTime, err := time.Parse(time.RFC3339Nano, "2022-03-11T20:59:00Z") - require.NoError(t, err) - ctx := app.BaseApp.NewContext(false, tmproto.Header{Height: 1, ChainID: "stargaze-1", Time: startDateTime}) - - // wasm params - wasmParams := app.WasmKeeper.GetParams(ctx) - wasmParams.CodeUploadAccess = wasmtypes.AllowEverybody - - app.WasmKeeper.SetParams(ctx, wasmParams) - - priv1 := secp256k1.GenPrivKey() - pub1 := priv1.PubKey() - addr1 := sdk.AccAddress(pub1.Address()) - return addr1, ctx, app, accs -} - -// Stores a WASM file given a FILE path. Tests are run from the e2e -// directory so paths should be relative to that. -func storeWasmFile(t *testing.T, file string, creator sdk.AccAddress, ctx sdk.Context, app *app.App) uint64 { - b, err := ioutil.ReadFile(file) - require.NoError(t, err) - - // For reasons entirely beyond me we need to create a new - // message server for every store code operation. Otherwise, - // every stored code will have a code ID of 1. - msgServer := wasmkeeper.NewMsgServerImpl(wasmkeeper.NewDefaultPermissionKeeper(app.WasmKeeper)) - - res, err := msgServer.StoreCode(sdk.WrapSDKContext(ctx), &wasmtypes.MsgStoreCode{ - Sender: creator.String(), - WASMByteCode: b, - }) - require.NoError(t, err) - require.NotNil(t, res) - - return res.CodeID - -} - -func StoreICS721Bridge(t *testing.T, creator sdk.AccAddress, ctx sdk.Context, app *app.App) uint64 { - return storeWasmFile(t, "../artifacts/ics721_base.wasm", creator, ctx, app) -} - -func StoreCw721Base(t *testing.T, creator sdk.AccAddress, ctx sdk.Context, app *app.App) uint64 { - return storeWasmFile(t, "../external-wasms/cw721_base_v0.18.0.wasm", creator, ctx, app) -} - -func TestLoadChain(t *testing.T) { - LoadChain(t) -} - -func TestStoreBridge(t *testing.T) { - creator, ctx, app, _ := LoadChain(t) - bridgeCodeID := StoreICS721Bridge(t, creator, ctx, app) - require.Equal(t, uint64(1), bridgeCodeID) -} - -func TestStoreCw721(t *testing.T) { - creator, ctx, app, _ := LoadChain(t) - cw721CodeID := StoreCw721Base(t, creator, ctx, app) - require.Equal(t, uint64(1), cw721CodeID) -} - -func TestStoreMultiple(t *testing.T) { - creator, ctx, app, _ := LoadChain(t) - - bridgeCodeID := StoreICS721Bridge(t, creator, ctx, app) - require.Equal(t, uint64(1), bridgeCodeID) - - cw721CodeID := StoreCw721Base(t, creator, ctx, app) - require.Equal(t, uint64(2), cw721CodeID) -} - -func TestInstantiateBridge(t *testing.T) { - creator, ctx, app, _ := LoadChain(t) - - bridgeCodeID := StoreICS721Bridge(t, creator, ctx, app) - require.Equal(t, uint64(1), bridgeCodeID) - - cw721CodeID := StoreCw721Base(t, creator, ctx, app) - require.Equal(t, uint64(2), cw721CodeID) - - InstantiateBridge(t, ctx, app, creator.String(), cw721CodeID, bridgeCodeID) -} diff --git a/e2e/init.go b/e2e/init.go index d8e64a6f..922668b7 100644 --- a/e2e/init.go +++ b/e2e/init.go @@ -1,4 +1,4 @@ -package e2e_test +package e2e import ( wasmd "github.com/CosmWasm/wasmd/app" @@ -7,5 +7,4 @@ import ( func init() { // override default gas wasmd.DefaultGas = 3_000_000 - } diff --git a/e2e/migrate_update_test.go b/e2e/migrate_update_test.go new file mode 100644 index 00000000..55124815 --- /dev/null +++ b/e2e/migrate_update_test.go @@ -0,0 +1,222 @@ +package e2e + +import ( + "encoding/json" + "fmt" + "github.com/public-awesome/ics721/e2e/test_suite" + "testing" + + wasmibctesting "github.com/CosmWasm/wasmd/x/wasm/ibctesting" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + 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/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type MigrateUpdateTestSuite struct { + suite.Suite + + coordinator *wasmibctesting.Coordinator + + // testing chains used for convenience and readability + chainA *wasmibctesting.TestChain + chainB *wasmibctesting.TestChain + + bridgeA sdk.AccAddress + bridgeB sdk.AccAddress + + pathAB *wasmibctesting.Path + + testerA sdk.AccAddress + testerB sdk.AccAddress + + cw721A sdk.AccAddress + cw721B sdk.AccAddress +} + +func TestMigrateWithUpgrade(t *testing.T) { + suite.Run(t, new(MigrateUpdateTestSuite)) +} + +func (suite *MigrateUpdateTestSuite) SetupTest() { + suite.coordinator = wasmibctesting.NewCoordinator(suite.T(), 3) + suite.chainA = suite.coordinator.GetChain(wasmibctesting.GetChainID(0)) + suite.chainB = suite.coordinator.GetChain(wasmibctesting.GetChainID(1)) + + // Store codes and instantiate contracts + storeCodes := func(chain *wasmibctesting.TestChain, bridge *sdk.AccAddress, tester *sdk.AccAddress, num int) { + resp := chain.StoreCodeFile("../artifacts/ics721_base.wasm") + require.Equal(suite.T(), uint64(1), resp.CodeID) + + resp = chain.StoreCodeFile("../external-wasms/cw721_base_v0.18.0.wasm") + require.Equal(suite.T(), uint64(2), resp.CodeID) + + resp = chain.StoreCodeFile("../artifacts/ics721_base_tester.wasm") + require.Equal(suite.T(), uint64(3), resp.CodeID) + + // store a newer version of the cw721 contract + resp = chain.StoreCodeFile("../external-wasms/cw721_base_v0.17.0.wasm") + require.Equal(suite.T(), uint64(4), resp.CodeID) + + // init dummy contracts based on how much we need + for i := 0; i < num; i++ { + cw721Instantiate := test_suite.InstantiateCw721v18{ + Name: "bad/kids", + Symbol: "bad/kids", + Minter: suite.chainA.SenderAccount.GetAddress().String(), + WithdrawAddress: nil, + } + instantiateRaw, err := json.Marshal(cw721Instantiate) + require.NoError(suite.T(), err) + + chain.InstantiateContract(2, instantiateRaw) + } + + // init ics721 + instantiateBridge := test_suite.InstantiateICS721Bridge{ + Cw721BaseCodeId: 2, + OutgoingProxy: nil, + IncomingProxy: nil, + Pauser: nil, + } + instantiateBridgeRaw, err := json.Marshal(instantiateBridge) + require.NoError(suite.T(), err) + + *bridge = chain.InstantiateContract(1, instantiateBridgeRaw) + + // init tester + instantiateBridgeTester := test_suite.InstantiateBridgeTester{ + AckMode: "success", + Ics721: bridge.String(), + } + instantiateBridgeTesterRaw, err := json.Marshal(instantiateBridgeTester) + require.NoError(suite.T(), err) + + *tester = chain.InstantiateContract(3, instantiateBridgeTesterRaw) + } + + storeCodes(suite.chainA, &suite.bridgeA, &suite.testerA, 0) + storeCodes(suite.chainB, &suite.bridgeB, &suite.testerB, 3) + + // Helper function to init ibc paths + initPath := func(chain1 *wasmibctesting.TestChain, chain2 *wasmibctesting.TestChain, contract1 sdk.AccAddress, contract2 sdk.AccAddress) *wasmibctesting.Path { + sourcePortID := chain1.ContractInfo(contract1).IBCPortID + counterpartPortID := chain2.ContractInfo(contract2).IBCPortID + path := wasmibctesting.NewPath(chain1, chain2) + path.EndpointA.ChannelConfig = &ibctesting.ChannelConfig{ + PortID: sourcePortID, + Version: "ics721-1", + Order: channeltypes.UNORDERED, + } + path.EndpointB.ChannelConfig = &ibctesting.ChannelConfig{ + PortID: counterpartPortID, + Version: "ics721-1", + Order: channeltypes.UNORDERED, + } + suite.coordinator.SetupConnections(path) + suite.coordinator.CreateChannels(path) + return path + } + + // init ibc path between chains + suite.pathAB = initPath(suite.chainA, suite.chainB, suite.bridgeA, suite.bridgeB) + + // init cw721 on chain A + cw721Instantiate := test_suite.InstantiateCw721v16{ + Name: "bad/kids", + Symbol: "bad/kids", + Minter: suite.chainA.SenderAccount.GetAddress().String(), + //WithdrawAddress: nil, + } + instantiateRaw, err := json.Marshal(cw721Instantiate) + require.NoError(suite.T(), err) + + suite.cw721A = suite.chainA.InstantiateContract(2, instantiateRaw) + + _, err = suite.chainA.SendMsgs(&wasmtypes.MsgExecuteContract{ + Sender: suite.chainA.SenderAccount.GetAddress().String(), + Contract: suite.cw721A.String(), + Msg: []byte(fmt.Sprintf(`{ "mint": { "token_id": "1", "owner": "%s" } }`, suite.chainA.SenderAccount.GetAddress().String())), + Funds: []sdk.Coin{}, + }) + require.NoError(suite.T(), err) + + //Send NFT to chain B + test_suite.Ics721TransferNft(suite.T(), suite.chainA, suite.pathAB, suite.coordinator, suite.cw721A.String(), "1", suite.bridgeA, suite.chainA.SenderAccount.GetAddress(), suite.chainB.SenderAccount.GetAddress(), "") + + classIdChainB := fmt.Sprintf("%s/%s/%s", suite.pathAB.EndpointB.ChannelConfig.PortID, suite.pathAB.EndpointB.ChannelID, suite.cw721A.String()) + addr := test_suite.QueryGetNftForClass(suite.T(), suite.chainB, suite.bridgeB.String(), classIdChainB) + suite.cw721B, err = sdk.AccAddressFromBech32(addr) + require.NoError(suite.T(), err) + + // mint 2x working NFT to tester + _, err = suite.chainA.SendMsgs(&wasmtypes.MsgExecuteContract{ + Sender: suite.chainA.SenderAccount.GetAddress().String(), + Contract: suite.cw721A.String(), + Msg: []byte(fmt.Sprintf(`{ "mint": { "token_id": "2", "owner": "%s" } }`, suite.testerA.String())), + Funds: []sdk.Coin{}, + }) + require.NoError(suite.T(), err) + _, err = suite.chainA.SendMsgs(&wasmtypes.MsgExecuteContract{ + Sender: suite.chainA.SenderAccount.GetAddress().String(), + Contract: suite.cw721A.String(), + Msg: []byte(fmt.Sprintf(`{ "mint": { "token_id": "3", "owner": "%s" } }`, suite.testerA.String())), + Funds: []sdk.Coin{}, + }) + require.NoError(suite.T(), err) + + suite.T().Logf("chain A bridge = (%s)", suite.bridgeA.String()) + suite.T().Logf("chain B bridge = (%s)", suite.bridgeB.String()) + suite.T().Logf("chain A tester = (%s)", suite.testerA.String()) + suite.T().Logf("chain B tester = (%s)", suite.testerB.String()) + suite.T().Logf("chain A cw721) = (%s)", suite.cw721A.String()) + suite.T().Logf("chain B cw721) = (%s)", suite.cw721B.String()) +} + +func (suite *MigrateUpdateTestSuite) TestSuccessfulTransferWithMigrateUpdate() { + memo := test_suite.CreateCallbackMemo(test_suite.NftCallbackSent(), "", test_suite.NftCallbackReceived(), "") + + // A -> B token_id 2 + test_suite.SendNftToIcs721AndRelay(suite.T(), suite.coordinator, suite.chainA, suite.bridgeA, suite.testerA, suite.testerB, suite.pathAB, suite.pathAB.EndpointA, suite.cw721A.String(), "2", memo, true) + + // Query the owner of NFT on cw721 + chainAOwner := test_suite.QueryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), "2") + require.Equal(suite.T(), chainAOwner, suite.bridgeA.String()) + chainBOwner := test_suite.QueryGetOwnerOf(suite.T(), suite.chainB, suite.cw721B.String(), "2") + require.Equal(suite.T(), chainBOwner, suite.testerB.String()) + + // We query the data we have on the tester contract + // This ensures that the callbacks are called after all the messages was completed + // and the transfer was successful + testerDataOwnerA := test_suite.QueryTesterSent(suite.T(), suite.chainA, suite.testerA.String()) + require.Equal(suite.T(), testerDataOwnerA, suite.bridgeA.String()) + testerNftContract := test_suite.QueryTesterNftContract(suite.T(), suite.chainB, suite.testerB.String()) + require.Equal(suite.T(), testerNftContract, suite.cw721B.String()) + testerDataOwnerB := test_suite.QueryTesterReceived(suite.T(), suite.chainB, suite.testerB.String()) + require.Equal(suite.T(), testerDataOwnerB, suite.testerB.String()) + + // Execute a migration over the ics721 but keeping the same actual code ID. We just want to pass the with_update struct changing the cw721BaseCodeId + // In this test we are starting from v0.18.0, and we downgrade to v0.17.0 since the collection is already created with v0.18.0 and so it would be used for the transfer, not v0.17.0 + test_suite.MigrateWithUpdate(suite.T(), suite.chainA, suite.bridgeA.String(), 1, 4) + + // A -> B token_id 3 + test_suite.SendNftToIcs721AndRelay(suite.T(), suite.coordinator, suite.chainA, suite.bridgeA, suite.testerA, suite.testerB, suite.pathAB, suite.pathAB.EndpointA, suite.cw721A.String(), "3", memo, true) + + // Query the owner of NFT on cw721 + chainAOwner = test_suite.QueryGetOwnerOf(suite.T(), suite.chainA, suite.cw721A.String(), "3") + require.Equal(suite.T(), chainAOwner, suite.bridgeA.String()) + chainBOwner = test_suite.QueryGetOwnerOf(suite.T(), suite.chainB, suite.cw721B.String(), "3") + require.Equal(suite.T(), chainBOwner, suite.testerB.String()) + + // We query the data we have on the tester contract + // This ensures that the callbacks are called after all the messages was completed + // and the transfer was successful + testerDataOwnerA = test_suite.QueryTesterSent(suite.T(), suite.chainA, suite.testerA.String()) + require.Equal(suite.T(), testerDataOwnerA, suite.bridgeA.String()) + testerNftContract = test_suite.QueryTesterNftContract(suite.T(), suite.chainB, suite.testerB.String()) + require.Equal(suite.T(), testerNftContract, suite.cw721B.String()) + testerDataOwnerB = test_suite.QueryTesterReceived(suite.T(), suite.chainB, suite.testerB.String()) + require.Equal(suite.T(), testerDataOwnerB, suite.testerB.String()) +} diff --git a/e2e/query.go b/e2e/query.go deleted file mode 100644 index 64b4b1bd..00000000 --- a/e2e/query.go +++ /dev/null @@ -1,76 +0,0 @@ -package e2e_test - -import ( - "testing" - - wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/public-awesome/stargaze/v12/app" - "github.com/stretchr/testify/require" -) - -func RunQueryEmpty(t *testing.T, ctx sdk.Context, app *app.App, - instantiateRes *wasmtypes.MsgInstantiateContractResponse, creator Account, queryMsgRaw []byte) { - escrow721Address := instantiateRes.Address - - addr, _ := sdk.AccAddressFromBech32(escrow721Address) - result, err := app.WasmKeeper.QuerySmart( - ctx, addr, queryMsgRaw) - expected_result := "" - require.Equal(t, string(result), expected_result) - require.EqualError(t, err, "cw721_base_ibc::state::TokenInfo not found: query wasm contract failed") -} - -func RunGetOwner(t *testing.T, ctx sdk.Context, app *app.App, msgServer wasmtypes.MsgServer, accs []Account, - instantiateRes *wasmtypes.MsgInstantiateContractResponse, getOwnerMsgRaw []byte, expected_response string) { - escrow721Address := instantiateRes.Address - - addr, _ := sdk.AccAddressFromBech32(escrow721Address) - result, _ := app.WasmKeeper.QuerySmart( - ctx, addr, getOwnerMsgRaw) - require.Equal(t, string(result), expected_response) -} - -func RunGetNFTInfo(t *testing.T, ctx sdk.Context, app *app.App, msgServer wasmtypes.MsgServer, accs []Account, - instantiateRes *wasmtypes.MsgInstantiateContractResponse, getNFTInfoMsgRaw []byte, expected_response string) { - escrow721Address := instantiateRes.Address - - addr, _ := sdk.AccAddressFromBech32(escrow721Address) - result, _ := app.WasmKeeper.QuerySmart( - ctx, addr, getNFTInfoMsgRaw) - - require.Equal(t, string(result), expected_response) -} - -func RunHasClass(t *testing.T, ctx sdk.Context, app *app.App, msgServer wasmtypes.MsgServer, accs []Account, - instantiateRes *wasmtypes.MsgInstantiateContractResponse, hasClassMsgRaw []byte, expected string) { - escrow721Address := instantiateRes.Address - - addr, _ := sdk.AccAddressFromBech32(escrow721Address) - result, _ := app.WasmKeeper.QuerySmart( - ctx, addr, hasClassMsgRaw) - - require.Equal(t, string(expected), string(result)) -} - -func RunGetClass(t *testing.T, ctx sdk.Context, app *app.App, msgServer wasmtypes.MsgServer, accs []Account, - instantiateRes *wasmtypes.MsgInstantiateContractResponse, getClassMsgRaw []byte, expected string) { - escrow721Address := instantiateRes.Address - - addr, _ := sdk.AccAddressFromBech32(escrow721Address) - result, _ := app.WasmKeeper.QuerySmart( - ctx, addr, getClassMsgRaw) - - require.Equal(t, string(expected), string(result)) -} - -func RunGetClassError(t *testing.T, ctx sdk.Context, app *app.App, msgServer wasmtypes.MsgServer, accs []Account, - instantiateRes *wasmtypes.MsgInstantiateContractResponse, getClassMsgRaw []byte, expected string) { - escrow721Address := instantiateRes.Address - - addr, _ := sdk.AccAddressFromBech32(escrow721Address) - _, err := app.WasmKeeper.QuerySmart( - ctx, addr, getClassMsgRaw) - - require.EqualError(t, err, expected) -} diff --git a/e2e/suite_helpers.go b/e2e/suite_helpers.go deleted file mode 100644 index f13d94c0..00000000 --- a/e2e/suite_helpers.go +++ /dev/null @@ -1,94 +0,0 @@ -package e2e_test - -import ( - "encoding/hex" - "fmt" - "testing" - - wasmibctesting "github.com/CosmWasm/wasmd/x/wasm/ibctesting" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - sdk "github.com/cosmos/cosmos-sdk/types" - - wasmd "github.com/CosmWasm/wasmd/app" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" - "github.com/stretchr/testify/require" -) - -func parseOptional(memo string) string { - r := "" - if memo != "" { - r = fmt.Sprintf("\"%s\"", memo) - } else { - r = "null" - } - return r -} - -// Creates and funds a new account for CHAIN. ACCOUNT_NUMBER is the -// number of accounts that have been previously created on CHAIN. -func CreateAndFundAccount(t *testing.T, chain *wasmibctesting.TestChain, accountNumber uint64) Account { - privkey := secp256k1.GenPrivKey() - pubkey := privkey.PubKey() - addr := sdk.AccAddress(pubkey.Address()) - - bondDenom := chain.App.StakingKeeper.BondDenom(chain.GetContext()) - coins := sdk.NewCoins(sdk.NewCoin(bondDenom, sdk.NewInt(1000000))) - - // Unclear to me exactly why we need to mint coins into this - // "mint" module and then transfer. Why can't we just mint - // directly to an address? - err := chain.App.BankKeeper.MintCoins(chain.GetContext(), minttypes.ModuleName, coins) - require.NoError(t, err) - - err = chain.App.BankKeeper.SendCoinsFromModuleToAccount(chain.GetContext(), minttypes.ModuleName, addr, coins) - require.NoError(t, err) - - baseAcc := authtypes.NewBaseAccount(addr, pubkey, accountNumber, 0) - - return Account{PrivKey: privkey, PubKey: pubkey, Address: addr, Acc: baseAcc} -} - -// Same as SendMsgs on the chain type, but sends from a different -// account than the sender account. -func SendMsgsFromAccount(t *testing.T, chain *wasmibctesting.TestChain, account Account, msgs ...sdk.Msg) (*sdk.Result, error) { - chain.Coordinator.UpdateTimeForChain(chain) - - _, r, err := wasmd.SignAndDeliver( - t, - chain.TxConfig, - chain.App.BaseApp, - chain.GetContext().BlockHeader(), - msgs, - chain.ChainID, - []uint64{account.Acc.GetAccountNumber()}, - []uint64{account.Acc.GetSequence()}, - account.PrivKey, - ) - if err != nil { - return nil, err - } - - // SignAndDeliver calls app.Commit() - chain.NextBlock() - - // increment sequence for successful transaction execution - err = account.Acc.SetSequence(account.Acc.GetSequence() + 1) - if err != nil { - return nil, err - } - - chain.Coordinator.IncrementTime() - chain.CaptureIBCEvents(r) - - return r, nil -} - -func AccAddressFromHex(address string) (addr sdk.AccAddress, err error) { - bz, err := addressBytesFromHexString(address) - return sdk.AccAddress(bz), err -} - -func addressBytesFromHexString(address string) ([]byte, error) { - return hex.DecodeString(address) -} diff --git a/e2e/template.go b/e2e/template.go index 0a5341af..5dfc6ad8 100644 --- a/e2e/template.go +++ b/e2e/template.go @@ -1,4 +1,4 @@ -package e2e_test +package e2e var ( escrow721Template = ` diff --git a/e2e/account.go b/e2e/test_suite/account.go similarity index 94% rename from e2e/account.go rename to e2e/test_suite/account.go index dd7734c0..f092df59 100644 --- a/e2e/account.go +++ b/e2e/test_suite/account.go @@ -1,4 +1,4 @@ -package e2e_test +package test_suite import ( cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" diff --git a/e2e/test_suite/actions.go b/e2e/test_suite/actions.go new file mode 100644 index 00000000..b05db322 --- /dev/null +++ b/e2e/test_suite/actions.go @@ -0,0 +1,212 @@ +package test_suite + +import ( + "encoding/json" + "fmt" + "testing" + + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + + "github.com/stretchr/testify/require" + + wasmibctesting "github.com/CosmWasm/wasmd/x/wasm/ibctesting" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + + wasmd "github.com/CosmWasm/wasmd/app" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" +) + +func StoreCodes(t *testing.T, chain *wasmibctesting.TestChain, bridge *sdk.AccAddress) { + resp := chain.StoreCodeFile("../artifacts/ics721_base.wasm") + require.Equal(t, uint64(1), resp.CodeID) + + resp = chain.StoreCodeFile("../external-wasms/cw721_base_v0.18.0.wasm") + require.Equal(t, uint64(2), resp.CodeID) + + resp = chain.StoreCodeFile("../artifacts/ics721_base_tester.wasm") + require.Equal(t, uint64(3), resp.CodeID) + + instantiateBridge := InstantiateICS721Bridge{ + Cw721BaseCodeId: 2, + OutgoingProxy: nil, + IncomingProxy: nil, + Pauser: nil, + } + instantiateBridgeRaw, err := json.Marshal(instantiateBridge) + require.NoError(t, err) + + *bridge = chain.InstantiateContract(1, instantiateBridgeRaw) +} + +// InstantiateBridge Instantiates a ICS721 contract on CHAIN. Returns the address of the +// instantiated contract. +func InstantiateBridge(t *testing.T, chain *wasmibctesting.TestChain) sdk.AccAddress { + // Store the contracts. + bridgeresp := chain.StoreCodeFile("../artifacts/ics721_base.wasm") + cw721resp := chain.StoreCodeFile("../external-wasms/cw721_base_v0.18.0.wasm") + + // Instantiate the ICS721 contract. + instantiateICS721 := InstantiateICS721Bridge{ + Cw721BaseCodeId: cw721resp.CodeID, + OutgoingProxy: nil, + IncomingProxy: nil, + Pauser: nil, + } + instantiateICS721Raw, err := json.Marshal(instantiateICS721) + require.NoError(t, err) + return chain.InstantiateContract(bridgeresp.CodeID, instantiateICS721Raw) +} + +func InstantiateCw721(t *testing.T, chain *wasmibctesting.TestChain, version int) sdk.AccAddress { + versionStr := fmt.Sprintf("v0.%d.0", version) + cw721resp := chain.StoreCodeFile(fmt.Sprintf("../external-wasms/cw721_base_%s.wasm", versionStr)) + + var instantiateRaw []byte + var err error + + // Since v0.18.0, the cw721 contract has an additional field in the instantiate message. + if version > 17 { + cw721InstantiateV18 := InstantiateCw721v18{ + Name: "bad/kids", + Symbol: "bad/kids", + Minter: chain.SenderAccount.GetAddress().String(), // withdraw_address + } + instantiateRaw, err = json.Marshal(cw721InstantiateV18) + } else { + cw721InstantiateV16 := InstantiateCw721v16{ + Name: "bad/kids", + Symbol: "bad/kids", + Minter: chain.SenderAccount.GetAddress().String(), + } + instantiateRaw, err = json.Marshal(cw721InstantiateV16) + } + + require.NoError(t, err) + return chain.InstantiateContract(cw721resp.CodeID, instantiateRaw) +} + +// MigrateWithUpdate Migrates the ICS721 contract on CHAIN to use a different CW721 contract code ID +func MigrateWithUpdate(t *testing.T, chain *wasmibctesting.TestChain, ics721 string, codeId, cw721BaseCodeId uint64) { + _, err := chain.SendMsgs(&wasmtypes.MsgMigrateContract{ + // Sender account is the minter in our test universe. + Sender: chain.SenderAccount.GetAddress().String(), + Contract: ics721, + CodeID: codeId, + Msg: []byte(fmt.Sprintf(`{ "with_update": { "cw721_base_code_id": %d } }`, cw721BaseCodeId)), + }) + require.NoError(t, err) +} + +func MintNFT(t *testing.T, chain *wasmibctesting.TestChain, cw721 string, id string, receiver sdk.AccAddress) { + _, err := chain.SendMsgs(&wasmtypes.MsgExecuteContract{ + // Sender account is the minter in our test universe. + Sender: chain.SenderAccount.GetAddress().String(), + Contract: cw721, + Msg: []byte(fmt.Sprintf(`{ "mint": { "token_id": "%s", "owner": "%s" } }`, id, receiver.String())), + Funds: []sdk.Coin{}, + }) + require.NoError(t, err) +} + +func TransferNft(t *testing.T, chain *wasmibctesting.TestChain, nftContract string, tokenId string, sender, receiver sdk.AccAddress) { + _, err := chain.SendMsgs(&wasmtypes.MsgExecuteContract{ + Sender: sender.String(), + Contract: nftContract, + Msg: []byte(fmt.Sprintf(`{"transfer_nft": { "recipient": "%s", "token_id": "%s" }}`, receiver, tokenId)), + Funds: []sdk.Coin{}, + }) + require.NoError(t, err) +} + +func Ics721TransferNft(t *testing.T, chain *wasmibctesting.TestChain, path *wasmibctesting.Path, coordinator *wasmibctesting.Coordinator, nftContract string, tokenId string, bridge, sender, receiver sdk.AccAddress, memo string) *sdk.Result { // Send the NFT away. + res, err := chain.SendMsgs(&wasmtypes.MsgExecuteContract{ + Sender: sender.String(), + Contract: nftContract, + Msg: []byte(GetCw721SendIbcAwayMessage(path, coordinator, tokenId, bridge, receiver, coordinator.CurrentTime.UnixNano()+1000000000000, memo)), + Funds: []sdk.Coin{}, + }) + require.NoError(t, err) + err = coordinator.RelayAndAckPendingPackets(path) + if err != nil { + return nil + } + return res +} + +func SendNftToIcs721AndRelay(t *testing.T, coordinator *wasmibctesting.Coordinator, sourceChain *wasmibctesting.TestChain, sourceBridge sdk.AccAddress, sourceTester sdk.AccAddress, destinationTester sdk.AccAddress, path *wasmibctesting.Path, endpoint *wasmibctesting.Endpoint, nftContract string, tokenId string, memo string, relay bool) { + msg := fmt.Sprintf(`{ "send_nft": {"cw721": "%s", "ics721": "%s", "token_id": "%s", "recipient":"%s", "channel_id":"%s", "memo":"%s"}}`, nftContract, sourceBridge.String(), tokenId, destinationTester.String(), endpoint.ChannelID, memo) + _, err := sourceChain.SendMsgs(&wasmtypes.MsgExecuteContract{ + Sender: sourceChain.SenderAccount.GetAddress().String(), + Contract: sourceTester.String(), + Msg: []byte(msg), + Funds: []sdk.Coin{}, + }) + // this checks the local send_nft to the ics721 contract succeed + require.NoError(t, err) + if relay { + coordinator.UpdateTime() + coordinator.RelayAndAckPendingPackets(path) + coordinator.UpdateTime() + } +} + +// CreateAndFundAccount Creates and funds a new account for CHAIN. ACCOUNT_NUMBER is the +// number of accounts that have been previously created on CHAIN. +func CreateAndFundAccount(t *testing.T, chain *wasmibctesting.TestChain, accountNumber uint64) Account { + privkey := secp256k1.GenPrivKey() + pubkey := privkey.PubKey() + addr := sdk.AccAddress(pubkey.Address()) + + bondDenom := chain.App.StakingKeeper.BondDenom(chain.GetContext()) + coins := sdk.NewCoins(sdk.NewCoin(bondDenom, sdk.NewInt(1000000))) + + // Unclear to me exactly why we need to mint coins into this + // "mint" module and then transfer. Why can't we just mint + // directly to an address? + err := chain.App.BankKeeper.MintCoins(chain.GetContext(), minttypes.ModuleName, coins) + require.NoError(t, err) + + err = chain.App.BankKeeper.SendCoinsFromModuleToAccount(chain.GetContext(), minttypes.ModuleName, addr, coins) + require.NoError(t, err) + + baseAcc := authtypes.NewBaseAccount(addr, pubkey, accountNumber, 0) + + return Account{PrivKey: privkey, PubKey: pubkey, Address: addr, Acc: baseAcc} +} + +// SendMsgsFromAccount Same as SendMsgs on the chain type, but sends from a different +// account than the sender account. +func SendMsgsFromAccount(t *testing.T, chain *wasmibctesting.TestChain, account Account, msgs ...sdk.Msg) (*sdk.Result, error) { + chain.Coordinator.UpdateTimeForChain(chain) + + _, r, err := wasmd.SignAndDeliver( + t, + chain.TxConfig, + chain.App.BaseApp, + chain.GetContext().BlockHeader(), + msgs, + chain.ChainID, + []uint64{account.Acc.GetAccountNumber()}, + []uint64{account.Acc.GetSequence()}, + account.PrivKey, + ) + if err != nil { + return nil, err + } + + // SignAndDeliver calls app.Commit() + chain.NextBlock() + + // increment sequence for successful transaction execution + err = account.Acc.SetSequence(account.Acc.GetSequence() + 1) + if err != nil { + return nil, err + } + + chain.Coordinator.IncrementTime() + chain.CaptureIBCEvents(r) + + return r, nil +} diff --git a/e2e/test_suite/helpers.go b/e2e/test_suite/helpers.go new file mode 100644 index 00000000..e8710bb6 --- /dev/null +++ b/e2e/test_suite/helpers.go @@ -0,0 +1,59 @@ +package test_suite + +import ( + "encoding/hex" + "fmt" + + b64 "encoding/base64" + + wasmibctesting "github.com/CosmWasm/wasmd/x/wasm/ibctesting" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func CreateCallbackMemo(srcCallback, srcReceiver, dstCallback, dstReceiver string) string { + srcCallback = ParseOptional(srcCallback) + srcReceiver = ParseOptional(srcReceiver) + dstCallback = ParseOptional(dstCallback) + dstReceiver = ParseOptional(dstReceiver) + // if "receive_callback_addr" is not specified it will be the same as the ics721 contract address + memo := fmt.Sprintf(`{ "callbacks": { "ack_callback_data": %s, "ack_callback_addr": %s, "receive_callback_data": %s, "receive_callback_addr": %s } }`, srcCallback, srcReceiver, dstCallback, dstReceiver) + return b64.StdEncoding.EncodeToString([]byte(memo)) +} + +func NftCallbackSent() string { + return b64.StdEncoding.EncodeToString([]byte(`{ "nft_sent": {}}`)) +} + +func NftCallbackReceived() string { + return b64.StdEncoding.EncodeToString([]byte(`{ "nft_received": {}}`)) +} + +func NftCallbackFailed() string { + return b64.StdEncoding.EncodeToString([]byte(`{ "fail_callback": {}}`)) +} + +func ParseOptional(memo string) string { + r := "" + if memo != "" { + r = fmt.Sprintf("\"%s\"", memo) + } else { + r = "null" + } + return r +} + +func GetCw721SendIbcAwayMessage(path *wasmibctesting.Path, coordinator *wasmibctesting.Coordinator, tokenId string, bridge, receiver sdk.AccAddress, timeout int64, memo string) string { + memo = ParseOptional(memo) + ibcAway := fmt.Sprintf(`{ "receiver": "%s", "channel_id": "%s", "timeout": { "timestamp": "%d" }, "memo": %s }`, receiver.String(), path.EndpointA.ChannelID, timeout, memo) + ibcAwayEncoded := b64.StdEncoding.EncodeToString([]byte(ibcAway)) + return fmt.Sprintf(`{ "send_nft": { "contract": "%s", "token_id": "%s", "msg": "%s" } }`, bridge, tokenId, ibcAwayEncoded) +} + +func AccAddressFromHex(address string) (addr sdk.AccAddress, err error) { + bz, err := addressBytesFromHexString(address) + return sdk.AccAddress(bz), err +} + +func addressBytesFromHexString(address string) ([]byte, error) { + return hex.DecodeString(address) +} diff --git a/e2e/test_suite/query.go b/e2e/test_suite/query.go new file mode 100644 index 00000000..e541b291 --- /dev/null +++ b/e2e/test_suite/query.go @@ -0,0 +1,165 @@ +package test_suite + +import ( + "testing" + + wasmibctesting "github.com/CosmWasm/wasmd/x/wasm/ibctesting" + // "github.com/cosmos/interchain-accounts/app" + "github.com/public-awesome/stargaze/v12/app" + + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func QueryGetNftForClass(t *testing.T, chain *wasmibctesting.TestChain, bridge, classID string) string { + getClassQuery := NftContractQuery{ + NftContractForClassId: NftContractQueryData{ + ClassID: classID, + }, + } + cw721 := "" + err := chain.SmartQuery(bridge, getClassQuery, &cw721) + require.NoError(t, err) + return cw721 +} + +func QueryGetNftContracts(t *testing.T, chain *wasmibctesting.TestChain, bridge string) [][]string { + getClassQuery := NftContractsQuery{ + NftContracts: NftContractsQueryData{}, + } + var cw721 [][]string + err := chain.SmartQuery(bridge, getClassQuery, &cw721) + require.NoError(t, err) + return cw721 +} + +func QueryGetOwnerOf(t *testing.T, chain *wasmibctesting.TestChain, nftContract string, tokenId string) string { + resp := OwnerOfResponse{} + ownerOfQuery := OwnerOfQuery{ + OwnerOf: OwnerOfQueryData{ + TokenID: tokenId, + }, + } + err := chain.SmartQuery(nftContract, ownerOfQuery, &resp) + require.NoError(t, err) + return resp.Owner +} + +func QueryGetOwnerOfErr(t *testing.T, chain *wasmibctesting.TestChain, nftContract string, tokenId string) error { + resp := OwnerOfResponse{} + ownerOfQuery := OwnerOfQuery{ + OwnerOf: OwnerOfQueryData{ + TokenID: tokenId, + }, + } + err := chain.SmartQuery(nftContract, ownerOfQuery, &resp) + return err +} + +// Tester queries and Tester responses + +func QueryTesterSent(t *testing.T, chain *wasmibctesting.TestChain, contractAddress string) string { + resp := TesterResponse{} + testerSentQuery := TesterSentQuery{ + GetSentCallback: EmptyData{}, + } + err := chain.SmartQuery(contractAddress, testerSentQuery, &resp) + require.NoError(t, err) + return *resp.Owner +} + +func QueryTesterReceived(t *testing.T, chain *wasmibctesting.TestChain, tester string) string { + resp := TesterResponse{} + testerReceivedQuery := TesterReceivedQuery{ + GetReceivedCallback: EmptyData{}, + } + err := chain.SmartQuery(tester, testerReceivedQuery, &resp) + require.NoError(t, err) + return *resp.Owner +} + +func QueryTesterNftContract(t *testing.T, chain *wasmibctesting.TestChain, tester string) string { + resp := "" + testerReceivedQuery := TesterNftContractQuery{ + GetNftContract: EmptyData{}, + } + err := chain.SmartQuery(tester, testerReceivedQuery, &resp) + require.NoError(t, err) + return resp +} + +func QueryTesterReceivedErr(_ *testing.T, chain *wasmibctesting.TestChain, tester string) error { + resp := TesterResponse{} + testerReceivedQuery := TesterReceivedQuery{ + GetReceivedCallback: EmptyData{}, + } + err := chain.SmartQuery(tester, testerReceivedQuery, &resp) + return err +} + +func RunQueryEmpty(t *testing.T, ctx sdk.Context, app *app.App, + instantiateRes *wasmtypes.MsgInstantiateContractResponse, creator Account, queryMsgRaw []byte) { + escrow721Address := instantiateRes.Address + + addr, _ := sdk.AccAddressFromBech32(escrow721Address) + result, err := app.WasmKeeper.QuerySmart( + ctx, addr, queryMsgRaw) + expectedResult := "" + require.Equal(t, string(result), expectedResult) + require.EqualError(t, err, "cw721_base_ibc::state::TokenInfo not found: query wasm contract failed") +} + +func RunGetOwner(t *testing.T, ctx sdk.Context, app *app.App, msgServer wasmtypes.MsgServer, accs []Account, + instantiateRes *wasmtypes.MsgInstantiateContractResponse, getOwnerMsgRaw []byte, expectedResponse string) { + escrow721Address := instantiateRes.Address + + addr, _ := sdk.AccAddressFromBech32(escrow721Address) + result, _ := app.WasmKeeper.QuerySmart( + ctx, addr, getOwnerMsgRaw) + require.Equal(t, string(result), expectedResponse) +} + +func RunGetNFTInfo(t *testing.T, ctx sdk.Context, app *app.App, msgServer wasmtypes.MsgServer, accs []Account, + instantiateRes *wasmtypes.MsgInstantiateContractResponse, getNFTInfoMsgRaw []byte, expectedResponse string) { + escrow721Address := instantiateRes.Address + + addr, _ := sdk.AccAddressFromBech32(escrow721Address) + result, _ := app.WasmKeeper.QuerySmart( + ctx, addr, getNFTInfoMsgRaw) + + require.Equal(t, string(result), expectedResponse) +} + +func RunHasClass(t *testing.T, ctx sdk.Context, app *app.App, msgServer wasmtypes.MsgServer, accs []Account, + instantiateRes *wasmtypes.MsgInstantiateContractResponse, hasClassMsgRaw []byte, expected string) { + escrow721Address := instantiateRes.Address + + addr, _ := sdk.AccAddressFromBech32(escrow721Address) + result, _ := app.WasmKeeper.QuerySmart( + ctx, addr, hasClassMsgRaw) + + require.Equal(t, string(expected), string(result)) +} + +func RunGetClass(t *testing.T, ctx sdk.Context, app *app.App, msgServer wasmtypes.MsgServer, accs []Account, + instantiateRes *wasmtypes.MsgInstantiateContractResponse, getClassMsgRaw []byte, expected string) { + escrow721Address := instantiateRes.Address + + addr, _ := sdk.AccAddressFromBech32(escrow721Address) + result, _ := app.WasmKeeper.QuerySmart( + ctx, addr, getClassMsgRaw) + + require.Equal(t, string(expected), string(result)) +} + +func RunGetClassError(t *testing.T, ctx sdk.Context, app *app.App, msgServer wasmtypes.MsgServer, accs []Account, + instantiateRes *wasmtypes.MsgInstantiateContractResponse, getClassMsgRaw []byte, expected string) { + escrow721Address := instantiateRes.Address + + addr, _ := sdk.AccAddressFromBech32(escrow721Address) + _, err := app.WasmKeeper.QuerySmart( + ctx, addr, getClassMsgRaw) + + require.EqualError(t, err, expected) +} diff --git a/e2e/types.go b/e2e/test_suite/types.go similarity index 66% rename from e2e/types.go rename to e2e/test_suite/types.go index 42baaa35..fa9bfef9 100644 --- a/e2e/types.go +++ b/e2e/test_suite/types.go @@ -1,6 +1,6 @@ -package e2e_test +package test_suite -// The `Class` type as defined in `token_types.rs` and returned by the +// Class The `Class` type as defined in `token_types.rs` and returned by the // `class_metadata { class_id }` query. type Class struct { ID string `json:"id"` @@ -8,7 +8,7 @@ type Class struct { Data *string `json:"data"` } -// The `Token` type as defined in `token_types.rs` and returned by the +// Token The `Token` type as defined in `token_types.rs` and returned by the // `token_metadata { class_id, token_id }` query. type Token struct { ID string `json:"id"` @@ -24,17 +24,25 @@ type ModuleInstantiateInfo struct { } type InstantiateICS721Bridge struct { - CW721CodeID uint64 `json:"cw721_base_code_id"` - OutgoingProxy *ModuleInstantiateInfo `json:"outgoing_proxy"` - IncomingProxy *ModuleInstantiateInfo `json:"incoming_proxy"` - Pauser *string `json:"pauser"` + Cw721BaseCodeId uint64 `json:"cw721_base_code_id"` + OutgoingProxy *ModuleInstantiateInfo `json:"outgoing_proxy"` + IncomingProxy *ModuleInstantiateInfo `json:"incoming_proxy"` + Pauser *string `json:"pauser"` } -type InstantiateCw721 struct { +// InstantiateCw721v18 v18 introduced the withdraw_address field +type InstantiateCw721v18 struct { + Name string `json:"name"` + Symbol string `json:"symbol"` + Minter string `json:"minter"` + WithdrawAddress *string `json:"withdraw_address"` +} + +// InstantiateCw721v16 valid for v17 too +type InstantiateCw721v16 struct { Name string `json:"name"` Symbol string `json:"symbol"` Minter string `json:"minter"` - WithdrawAddress *string `json:"withdraw_address"` } type InstantiateBridgeTester struct { @@ -44,15 +52,15 @@ type InstantiateBridgeTester struct { type OwnerOfResponse struct { Owner string `json:"owner"` - // There is also an approvals field here but we don't care - // about it so we just don't unmarshal. + // There is also an approvals field here, but we don't care + // about it, so we just don't unmarshal. } type TesterResponse struct { Owner *string `json:"owner"` } -// Owner query for ICS721 contract. +// OwnerQueryData Owner query for ICS721 contract. type OwnerQueryData struct { TokenID string `json:"token_id"` ClassID string `json:"class_id"` @@ -61,7 +69,7 @@ type OwnerQuery struct { Owner OwnerQueryData `json:"owner"` } -// ICS721 contract query for obtaining a NFT contract address given a class ID. +// NftContractQueryData ICS721 contract query for obtaining a NFT contract address given a class ID. type NftContractQueryData struct { ClassID string `json:"class_id"` } @@ -76,7 +84,7 @@ type NftContractsQuery struct { NftContracts NftContractsQueryData `json:"nft_contracts"` } -// Query for getting class ID given NFT contract. +// ClassIdQueryData Query for getting class ID given NFT contract. type ClassIdQueryData struct { Contract string `json:"contract"` } @@ -84,7 +92,7 @@ type ClassIdQuery struct { ClassIdForNFTContract ClassIdQueryData `json:"class_id"` } -// Query for getting metadata for a class ID from the ICS721 contract. +// ClassMetadataQueryData Query for getting metadata for a class ID from the ICS721 contract. type ClassMetadataQueryData struct { ClassId string `json:"class_id"` } @@ -92,7 +100,7 @@ type ClassMetadataQuery struct { Metadata ClassMetadataQueryData `json:"class_metadata"` } -// Query for getting token metadata. +// TokenMetadataQueryData Query for getting token metadata. type TokenMetadataQueryData struct { ClassId string `json:"class_id"` TokenId string `json:"token_id"` @@ -101,7 +109,7 @@ type TokenMetadataQuery struct { Metadata TokenMetadataQueryData `json:"token_metadata"` } -// Owner query for cw721 contract. +// OwnerOfQueryData Owner query for cw721 contract. type OwnerOfQueryData struct { TokenID string `json:"token_id"` } @@ -123,7 +131,7 @@ type TesterNftContractQuery struct { GetNftContract EmptyData `json:"get_nft_contract"` } -// cw721 contract info query. +// ContractInfoQueryData cw721 contract info query. type ContractInfoQueryData struct{} type ContractInfoQuery struct { ContractInfo ContractInfoQueryData `json:"contract_info"` @@ -133,13 +141,13 @@ type ContractInfoResponse struct { Symbol string `json:"symbol"` } -// Query for getting last ACK from tester contract. +// LastAckQueryData Query for getting last ACK from tester contract. type LastAckQueryData struct{} type LastAckQuery struct { LastAck LastAckQueryData `json:"last_ack"` } -// cw721 token info query +// NftInfoQueryData cw721 token info query type NftInfoQueryData struct { TokenID string `json:"token_id"` } diff --git a/e2e/suite_test.go b/e2e/transfer_test.go similarity index 69% rename from e2e/suite_test.go rename to e2e/transfer_test.go index 3524ad8d..af9a89b5 100644 --- a/e2e/suite_test.go +++ b/e2e/transfer_test.go @@ -1,9 +1,11 @@ -package e2e_test +package e2e import ( "encoding/json" "testing" + "github.com/public-awesome/ics721/e2e/test_suite" + b64 "encoding/base64" "fmt" @@ -30,11 +32,15 @@ type TransferTestSuite struct { chainBBridge sdk.AccAddress } +func TestTransfer(t *testing.T) { + suite.Run(t, new(TransferTestSuite)) +} + func (suite *TransferTestSuite) SetupTest() { suite.coordinator = wasmibctesting.NewCoordinator(suite.T(), 2) suite.chainA = suite.coordinator.GetChain(wasmibctesting.GetChainID(0)) suite.chainB = suite.coordinator.GetChain(wasmibctesting.GetChainID(1)) - // suite.coordinator.CommitBlock(suite.chainA, suite.chainB) + // test_suite.coordinator.CommitBlock(test_suite.chainA, test_suite.chainB) // Store the ICS721 contract. chainAStoreResp := suite.chainA.StoreCodeFile("../artifacts/ics721_base.wasm") @@ -48,12 +54,12 @@ func (suite *TransferTestSuite) SetupTest() { chainBStoreResp = suite.chainB.StoreCodeFile("../external-wasms/cw721_base_v0.18.0.wasm") require.Equal(suite.T(), uint64(2), chainBStoreResp.CodeID) - instantiateICS721 := InstantiateICS721Bridge{ - 2, + instantiateICS721 := test_suite.InstantiateICS721Bridge{ + Cw721BaseCodeId: 2, // no pauser nor proxy by default. - nil, - nil, - nil, + OutgoingProxy: nil, + IncomingProxy: nil, + Pauser: nil, } instantiateICS721Raw, err := json.Marshal(instantiateICS721) require.NoError(suite.T(), err) @@ -68,8 +74,8 @@ func (suite *TransferTestSuite) TestEstablishConnection() { sourcePortID = suite.chainA.ContractInfo(suite.chainABridge).IBCPortID counterpartPortID = suite.chainB.ContractInfo(suite.chainBBridge).IBCPortID ) - // suite.coordinator.CommitBlock(suite.chainA, suite.chainB) - // suite.coordinator.UpdateTime() + // test_suite.coordinator.CommitBlock(test_suite.chainA, test_suite.chainB) + // test_suite.coordinator.UpdateTime() require.Equal(suite.T(), suite.chainA.CurrentHeader.Time, suite.chainB.CurrentHeader.Time) path := wasmibctesting.NewPath(suite.chainA, suite.chainB) @@ -111,11 +117,11 @@ func (suite *TransferTestSuite) TestIBCSendNFT() { suite.coordinator.CreateChannels(path) // Instantiate a cw721 to send on chain A. - cw721Instantiate := InstantiateCw721{ - "bad/kids", - "bad/kids", - suite.chainA.SenderAccount.GetAddress().String(), - nil, + cw721Instantiate := test_suite.InstantiateCw721v18{ + Name: "bad/kids", + Symbol: "bad/kids", + Minter: suite.chainA.SenderAccount.GetAddress().String(), + WithdrawAddress: nil, } instantiateRaw, err := json.Marshal(cw721Instantiate) require.NoError(suite.T(), err) @@ -150,9 +156,9 @@ func (suite *TransferTestSuite) TestIBCSendNFT() { suite.T().Logf("chain a sender: %s", suite.chainA.SenderAccount.GetAddress().String()) // Check that the NFT has been transfered away from the sender // on chain A. - resp := OwnerOfResponse{} - ownerOfQuery := OwnerOfQuery{ - OwnerOf: OwnerOfQueryData{ + resp := test_suite.OwnerOfResponse{} + ownerOfQuery := test_suite.OwnerOfQuery{ + OwnerOf: test_suite.OwnerOfQueryData{ TokenID: "1", }, } @@ -163,8 +169,8 @@ func (suite *TransferTestSuite) TestIBCSendNFT() { chainBClassID := fmt.Sprintf(`%s/%s/%s`, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, cw721.String()) // Check that the receiver on the receiving chain now owns the NFT. - getOwnerQuery := OwnerQuery{ - Owner: OwnerQueryData{ + getOwnerQuery := test_suite.OwnerQuery{ + Owner: test_suite.OwnerQueryData{ TokenID: "1", ClassID: chainBClassID, }, @@ -174,8 +180,8 @@ func (suite *TransferTestSuite) TestIBCSendNFT() { require.Equal(suite.T(), suite.chainB.SenderAccount.GetAddress().String(), resp.Owner) // Get the address of the instantiated cw721. - getClassQuery := NftContractQuery{ - NftContractForClassId: NftContractQueryData{ + getClassQuery := test_suite.NftContractQuery{ + NftContractForClassId: test_suite.NftContractQueryData{ ClassID: chainBClassID, }, } @@ -186,8 +192,8 @@ func (suite *TransferTestSuite) TestIBCSendNFT() { suite.T().Logf("Chain B cw721: %s", chainBCw721) // Check that the classID for the contract has been set properly. - getClassIDQuery := ClassIdQuery{ - ClassIdForNFTContract: ClassIdQueryData{ + getClassIDQuery := test_suite.ClassIdQuery{ + ClassIdForNFTContract: test_suite.ClassIdQueryData{ Contract: chainBCw721, }, } @@ -198,13 +204,13 @@ func (suite *TransferTestSuite) TestIBCSendNFT() { // Check that the contract info for the instantiated cw721 was // set correctly. - contractInfo := ContractInfoResponse{} - contractInfoQuery := ContractInfoQuery{ - ContractInfo: ContractInfoQueryData{}, + contractInfo := test_suite.ContractInfoResponse{} + contractInfoQuery := test_suite.ContractInfoQuery{ + ContractInfo: test_suite.ContractInfoQueryData{}, } err = suite.chainB.SmartQuery(chainBCw721, contractInfoQuery, &contractInfo) require.NoError(suite.T(), err) - require.Equal(suite.T(), ContractInfoResponse{ + require.Equal(suite.T(), test_suite.ContractInfoResponse{ Name: "bad/kids", Symbol: "bad/kids", }, contractInfo) @@ -234,8 +240,8 @@ func (suite *TransferTestSuite) TestIBCSendNFT() { // Check that the GetClass query returns what we expect for // local NFTs. - getClassQuery = NftContractQuery{ - NftContractForClassId: NftContractQueryData{ + getClassQuery = test_suite.NftContractQuery{ + NftContractForClassId: test_suite.NftContractQueryData{ ClassID: cw721.String(), }, } @@ -254,131 +260,22 @@ func (suite *TransferTestSuite) TestIBCSendNFT() { require.ErrorContains(suite.T(), err, "wasm, code: 9: query wasm contract failed") } -func TestIBC(t *testing.T) { - suite.Run(t, new(TransferTestSuite)) -} - -// Instantiates a ICS721 contract on CHAIN. Returns the address of the -// instantiated contract. -func instantiateBridge(t *testing.T, chain *wasmibctesting.TestChain) sdk.AccAddress { - // Store the contracts. - bridgeresp := chain.StoreCodeFile("../artifacts/ics721_base.wasm") - cw721resp := chain.StoreCodeFile("../external-wasms/cw721_base_v0.18.0.wasm") - - // Instantiate the ICS721 contract. - instantiateICS721 := InstantiateICS721Bridge{ - cw721resp.CodeID, - nil, - nil, - nil, - } - instantiateICS721Raw, err := json.Marshal(instantiateICS721) - require.NoError(t, err) - return chain.InstantiateContract(bridgeresp.CodeID, instantiateICS721Raw) -} - -func instantiateCw721(t *testing.T, chain *wasmibctesting.TestChain) sdk.AccAddress { - cw721resp := chain.StoreCodeFile("../external-wasms/cw721_base_v0.18.0.wasm") - cw721Instantiate := InstantiateCw721{ - "bad/kids", - "bad/kids", - chain.SenderAccount.GetAddress().String(), - nil, - } - instantiateRaw, err := json.Marshal(cw721Instantiate) - require.NoError(t, err) - return chain.InstantiateContract(cw721resp.CodeID, instantiateRaw) -} - -func mintNFT(t *testing.T, chain *wasmibctesting.TestChain, cw721 string, id string, receiver sdk.AccAddress) { - _, err := chain.SendMsgs(&wasmtypes.MsgExecuteContract{ - // Sender account is the minter in our test universe. - Sender: chain.SenderAccount.GetAddress().String(), - Contract: cw721, - Msg: []byte(fmt.Sprintf(`{ "mint": { "token_id": "%s", "owner": "%s" } }`, id, receiver.String())), - Funds: []sdk.Coin{}, - }) - require.NoError(t, err) -} - -func transferNft(t *testing.T, chain *wasmibctesting.TestChain, nft, token_id string, sender, receiver sdk.AccAddress) { - _, err := chain.SendMsgs(&wasmtypes.MsgExecuteContract{ - Sender: sender.String(), - Contract: nft, - Msg: []byte(fmt.Sprintf(`{"transfer_nft": { "recipient": "%s", "token_id": "%s" }}`, receiver, token_id)), - Funds: []sdk.Coin{}, - }) - require.NoError(t, err) -} - -func getCw721SendIbcAwayMessage(path *wasmibctesting.Path, coordinator *wasmibctesting.Coordinator, tokenId string, bridge, receiver sdk.AccAddress, timeout int64, memo string) string { - memo = parseOptional(memo) - ibcAway := fmt.Sprintf(`{ "receiver": "%s", "channel_id": "%s", "timeout": { "timestamp": "%d" }, "memo": %s }`, receiver.String(), path.EndpointA.ChannelID, timeout, memo) - ibcAwayEncoded := b64.StdEncoding.EncodeToString([]byte(ibcAway)) - return fmt.Sprintf(`{ "send_nft": { "contract": "%s", "token_id": "%s", "msg": "%s" } }`, bridge, tokenId, ibcAwayEncoded) -} - -func ics721Nft(t *testing.T, chain *wasmibctesting.TestChain, path *wasmibctesting.Path, coordinator *wasmibctesting.Coordinator, nft string, token_id string, bridge, sender, receiver sdk.AccAddress, memo string) *sdk.Result { // Send the NFT away. - res, err := chain.SendMsgs(&wasmtypes.MsgExecuteContract{ - Sender: sender.String(), - Contract: nft, - Msg: []byte(getCw721SendIbcAwayMessage(path, coordinator, token_id, bridge, receiver, coordinator.CurrentTime.UnixNano()+1000000000000, memo)), - Funds: []sdk.Coin{}, - }) - require.NoError(t, err) - coordinator.RelayAndAckPendingPackets(path) - return res -} - -func queryGetNftForClass(t *testing.T, chain *wasmibctesting.TestChain, bridge, classID string) string { - getClassQuery := NftContractQuery{ - NftContractForClassId: NftContractQueryData{ - ClassID: classID, - }, - } - cw721 := "" - err := chain.SmartQuery(bridge, getClassQuery, &cw721) - require.NoError(t, err) - return cw721 -} - -func queryGetNftContracts(t *testing.T, chain *wasmibctesting.TestChain, bridge string) [][]string { - getClassQuery := NftContractsQuery{ - NftContracts: NftContractsQueryData{}, - } - var cw721 [][]string - err := chain.SmartQuery(bridge, getClassQuery, &cw721) - require.NoError(t, err) - return cw721 +func TestSendBetweenThreeIdenticalChainsV16(t *testing.T) { + runTestSendBetweenThreeIdenticalChains(t, 16) } -func queryGetOwnerOf(t *testing.T, chain *wasmibctesting.TestChain, nft string, token_id string) string { - resp := OwnerOfResponse{} - ownerOfQuery := OwnerOfQuery{ - OwnerOf: OwnerOfQueryData{ - TokenID: token_id, - }, - } - err := chain.SmartQuery(nft, ownerOfQuery, &resp) - require.NoError(t, err) - return resp.Owner +func TestSendBetweenThreeIdenticalChainsV17(t *testing.T) { + runTestSendBetweenThreeIdenticalChains(t, 17) } -func queryGetOwnerOfErr(t *testing.T, chain *wasmibctesting.TestChain, nft string, token_id string) error { - resp := OwnerOfResponse{} - ownerOfQuery := OwnerOfQuery{ - OwnerOf: OwnerOfQueryData{ - TokenID: token_id, - }, - } - err := chain.SmartQuery(nft, ownerOfQuery, &resp) - return err +func TestSendBetweenThreeIdenticalChainsV18(t *testing.T) { + runTestSendBetweenThreeIdenticalChains(t, 18) } // Builds three identical chains A, B, and C then sends along the path // A -> B -> C -> A -> C -> B -> A. If this works, likely most other // things do too. :) -func TestSendBetweenThreeIdenticalChains(t *testing.T) { +func runTestSendBetweenThreeIdenticalChains(t *testing.T, version int) { coordinator := wasmibctesting.NewCoordinator(t, 3) chainA := coordinator.GetChain(wasmibctesting.GetChainID(0)) @@ -386,12 +283,12 @@ func TestSendBetweenThreeIdenticalChains(t *testing.T) { chainC := coordinator.GetChain(wasmibctesting.GetChainID(2)) // Chains are identical, so only one ICS721 contract address. - bridge := instantiateBridge(t, chainA) - instantiateBridge(t, chainB) - instantiateBridge(t, chainC) + bridge := test_suite.InstantiateBridge(t, chainA) + test_suite.InstantiateBridge(t, chainB) + test_suite.InstantiateBridge(t, chainC) - chainANft := instantiateCw721(t, chainA).String() - mintNFT(t, chainA, chainANft, "bad kid 1", chainA.SenderAccount.GetAddress()) + chainANft := test_suite.InstantiateCw721(t, chainA, version).String() + test_suite.MintNFT(t, chainA, chainANft, "bad kid 1", chainA.SenderAccount.GetAddress()) type Connection struct { Start int @@ -434,89 +331,94 @@ func TestSendBetweenThreeIdenticalChains(t *testing.T) { // A -> B path := getPath(0, 1) - ics721Nft(t, chainA, path, coordinator, chainANft, "bad kid 1", bridge, chainA.SenderAccount.GetAddress(), chainB.SenderAccount.GetAddress(), "") + test_suite.Ics721TransferNft(t, chainA, path, coordinator, chainANft, "bad kid 1", bridge, chainA.SenderAccount.GetAddress(), chainB.SenderAccount.GetAddress(), "") + + // After Sending NFT via ICS721 "bridge" contract, check that the NFT is escrowed by ICS721 on chain A. + chainANftOwner := test_suite.QueryGetOwnerOf(t, chainA, chainANft, "bad kid 1") + require.Equal(t, chainANftOwner, bridge.String()) // Check that chain B received the NFT. chainBClassID := fmt.Sprintf(`%s/%s/%s`, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, chainANft) - chainBNft := queryGetNftForClass(t, chainB, bridge.String(), chainBClassID) + chainBNft := test_suite.QueryGetNftForClass(t, chainB, bridge.String(), chainBClassID) t.Logf("chain B cw721: %s", chainBNft) + // require.NotEqual(chainBNft, "", "NFT address on chain B is empty") - ownerB := queryGetOwnerOf(t, chainB, chainBNft, "bad kid 1") + ownerB := test_suite.QueryGetOwnerOf(t, chainB, chainBNft, "bad kid 1") require.Equal(t, chainB.SenderAccount.GetAddress().String(), ownerB) // Make sure chain A has the NFT in its ICS721 contract. - ownerA := queryGetOwnerOf(t, chainA, chainANft, "bad kid 1") + ownerA := test_suite.QueryGetOwnerOf(t, chainA, chainANft, "bad kid 1") require.Equal(t, ownerA, bridge.String()) // B -> C path = getPath(1, 2) - ics721Nft(t, chainB, path, coordinator, chainBNft, "bad kid 1", bridge, chainB.SenderAccount.GetAddress(), chainC.SenderAccount.GetAddress(), "") + test_suite.Ics721TransferNft(t, chainB, path, coordinator, chainBNft, "bad kid 1", bridge, chainB.SenderAccount.GetAddress(), chainC.SenderAccount.GetAddress(), "") chainCClassID := fmt.Sprintf(`%s/%s/%s`, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, chainBClassID) - chainCNft := queryGetNftForClass(t, chainC, bridge.String(), chainCClassID) + chainCNft := test_suite.QueryGetNftForClass(t, chainC, bridge.String(), chainCClassID) t.Logf("chain C cw721: %s", chainCNft) - ownerC := queryGetOwnerOf(t, chainC, chainCNft, "bad kid 1") + ownerC := test_suite.QueryGetOwnerOf(t, chainC, chainCNft, "bad kid 1") require.Equal(t, chainC.SenderAccount.GetAddress().String(), ownerC) // Make sure the NFT is locked in the ICS721 contract on chain B. - ownerB = queryGetOwnerOf(t, chainB, chainBNft, "bad kid 1") + ownerB = test_suite.QueryGetOwnerOf(t, chainB, chainBNft, "bad kid 1") require.Equal(t, bridge.String(), ownerB) // C -> A path = getPath(2, 0) - ics721Nft(t, chainC, path, coordinator, chainCNft, "bad kid 1", bridge, chainC.SenderAccount.GetAddress(), chainA.SenderAccount.GetAddress(), "") + test_suite.Ics721TransferNft(t, chainC, path, coordinator, chainCNft, "bad kid 1", bridge, chainC.SenderAccount.GetAddress(), chainA.SenderAccount.GetAddress(), "") chainAClassID := fmt.Sprintf(`%s/%s/%s`, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, chainCClassID) // This is a derivative and not actually the original chain A nft. - chainANftDerivative := queryGetNftForClass(t, chainA, bridge.String(), chainAClassID) + chainANftDerivative := test_suite.QueryGetNftForClass(t, chainA, bridge.String(), chainAClassID) require.NotEqual(t, chainANft, chainANftDerivative) t.Logf("chain A cw721 derivative: %s", chainANftDerivative) - ownerA = queryGetOwnerOf(t, chainA, chainANftDerivative, "bad kid 1") + ownerA = test_suite.QueryGetOwnerOf(t, chainA, chainANftDerivative, "bad kid 1") require.Equal(t, chainA.SenderAccount.GetAddress().String(), ownerA) // Make sure that the NFT is held in the ICS721 contract now. - ownerC = queryGetOwnerOf(t, chainC, chainCNft, "bad kid 1") + ownerC = test_suite.QueryGetOwnerOf(t, chainC, chainCNft, "bad kid 1") require.Equal(t, bridge.String(), ownerC) // Now, lets unwind the stack. // A -> C path = getPath(0, 2) - ics721Nft(t, chainA, path, coordinator, chainANftDerivative, "bad kid 1", bridge, chainA.SenderAccount.GetAddress(), chainC.SenderAccount.GetAddress(), "") + test_suite.Ics721TransferNft(t, chainA, path, coordinator, chainANftDerivative, "bad kid 1", bridge, chainA.SenderAccount.GetAddress(), chainC.SenderAccount.GetAddress(), "") // NFT should now be burned on chain A. We can't ask the // contract "is this burned" so we just query and make sure it // now errors with a storage load failure. - err := chainA.SmartQuery(chainANftDerivative, OwnerOfQuery{OwnerOf: OwnerOfQueryData{TokenID: "bad kid 1"}}, &OwnerOfResponse{}) + err := chainA.SmartQuery(chainANftDerivative, test_suite.OwnerOfQuery{OwnerOf: test_suite.OwnerOfQueryData{TokenID: "bad kid 1"}}, &test_suite.OwnerOfResponse{}) require.ErrorContains(t, err, "cw721_base::state::TokenInfo>; key: [00, 06, 74, 6F, 6B, 65, 6E, 73, 62, 61, 64, 20, 6B, 69, 64, 20, 31] not found") // NFT should belong to chainC sender on chain C. - ownerC = queryGetOwnerOf(t, chainC, chainCNft, "bad kid 1") + ownerC = test_suite.QueryGetOwnerOf(t, chainC, chainCNft, "bad kid 1") require.Equal(t, chainC.SenderAccount.GetAddress().String(), ownerC) // C -> B path = getPath(2, 1) - ics721Nft(t, chainC, path, coordinator, chainCNft, "bad kid 1", bridge, chainC.SenderAccount.GetAddress(), chainB.SenderAccount.GetAddress(), "") + test_suite.Ics721TransferNft(t, chainC, path, coordinator, chainCNft, "bad kid 1", bridge, chainC.SenderAccount.GetAddress(), chainB.SenderAccount.GetAddress(), "") // Received on B. - ownerB = queryGetOwnerOf(t, chainB, chainBNft, "bad kid 1") + ownerB = test_suite.QueryGetOwnerOf(t, chainB, chainBNft, "bad kid 1") require.Equal(t, chainB.SenderAccount.GetAddress().String(), ownerB) // Burned on C. - err = chainC.SmartQuery(chainCNft, OwnerOfQuery{OwnerOf: OwnerOfQueryData{TokenID: "bad kid 1"}}, &OwnerOfResponse{}) + err = chainC.SmartQuery(chainCNft, test_suite.OwnerOfQuery{OwnerOf: test_suite.OwnerOfQueryData{TokenID: "bad kid 1"}}, &test_suite.OwnerOfResponse{}) require.ErrorContains(t, err, "cw721_base::state::TokenInfo>; key: [00, 06, 74, 6F, 6B, 65, 6E, 73, 62, 61, 64, 20, 6B, 69, 64, 20, 31] not found") // B -> A path = getPath(1, 0) - ics721Nft(t, chainB, path, coordinator, chainBNft, "bad kid 1", bridge, chainB.SenderAccount.GetAddress(), chainA.SenderAccount.GetAddress(), "") + test_suite.Ics721TransferNft(t, chainB, path, coordinator, chainBNft, "bad kid 1", bridge, chainB.SenderAccount.GetAddress(), chainA.SenderAccount.GetAddress(), "") // Received on chain A. - ownerA = queryGetOwnerOf(t, chainA, chainANft, "bad kid 1") + ownerA = test_suite.QueryGetOwnerOf(t, chainA, chainANft, "bad kid 1") require.Equal(t, chainA.SenderAccount.GetAddress().String(), ownerA) // Burned on chain B. - err = chainB.SmartQuery(chainBNft, OwnerOfQuery{OwnerOf: OwnerOfQueryData{TokenID: "bad kid 1"}}, &OwnerOfResponse{}) + err = chainB.SmartQuery(chainBNft, test_suite.OwnerOfQuery{OwnerOf: test_suite.OwnerOfQueryData{TokenID: "bad kid 1"}}, &test_suite.OwnerOfResponse{}) require.ErrorContains(t, err, "cw721_base::state::TokenInfo>; key: [00, 06, 74, 6F, 6B, 65, 6E, 73, 62, 61, 64, 20, 6B, 69, 64, 20, 31] not found") // Hooray! We have completed the journey between three @@ -543,26 +445,26 @@ func (suite *TransferTestSuite) TestMultipleAddressesInvolved() { suite.coordinator.SetupConnections(path) suite.coordinator.CreateChannels(path) - chainANft := instantiateCw721(suite.T(), suite.chainA) - mintNFT(suite.T(), suite.chainA, chainANft.String(), "bad kid 1", suite.chainA.SenderAccount.GetAddress()) + chainANft := test_suite.InstantiateCw721(suite.T(), suite.chainA, 18) + test_suite.MintNFT(suite.T(), suite.chainA, chainANft.String(), "bad kid 1", suite.chainA.SenderAccount.GetAddress()) - ics721Nft(suite.T(), suite.chainA, path, suite.coordinator, chainANft.String(), "bad kid 1", suite.chainABridge, suite.chainA.SenderAccount.GetAddress(), suite.chainB.SenderAccount.GetAddress(), "") + test_suite.Ics721TransferNft(suite.T(), suite.chainA, path, suite.coordinator, chainANft.String(), "bad kid 1", suite.chainABridge, suite.chainA.SenderAccount.GetAddress(), suite.chainB.SenderAccount.GetAddress(), "") chainBClassID := fmt.Sprintf(`%s/%s/%s`, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, chainANft) - chainBNft := queryGetNftForClass(suite.T(), suite.chainB, suite.chainBBridge.String(), chainBClassID) + chainBNft := test_suite.QueryGetNftForClass(suite.T(), suite.chainB, suite.chainBBridge.String(), chainBClassID) // Generate a new account and transfer the NFT to it. For // reasons entirely beyond me, the first account we create // has an account number of ten. The second has 18. - newAccount := CreateAndFundAccount(suite.T(), suite.chainB, 18) - transferNft(suite.T(), suite.chainB, chainBNft, "bad kid 1", suite.chainB.SenderAccount.GetAddress(), newAccount.Address) + newAccount := test_suite.CreateAndFundAccount(suite.T(), suite.chainB, 18) + test_suite.TransferNft(suite.T(), suite.chainB, chainBNft, "bad kid 1", suite.chainB.SenderAccount.GetAddress(), newAccount.Address) // IBC away the transfered NFT. ibcAway := fmt.Sprintf(`{ "receiver": "%s", "channel_id": "%s", "timeout": { "timestamp": "%d" } }`, suite.chainA.SenderAccount.GetAddress().String(), path.EndpointB.ChannelID, suite.coordinator.CurrentTime.UnixNano()+1000000000000) ibcAwayEncoded := b64.StdEncoding.EncodeToString([]byte(ibcAway)) // Send the NFT away. - _, err := SendMsgsFromAccount(suite.T(), suite.chainB, newAccount, &wasmtypes.MsgExecuteContract{ + _, err := test_suite.SendMsgsFromAccount(suite.T(), suite.chainB, newAccount, &wasmtypes.MsgExecuteContract{ Sender: newAccount.Address.String(), Contract: chainBNft, Msg: []byte(fmt.Sprintf(`{ "send_nft": { "contract": "%s", "token_id": "bad kid 1", "msg": "%s" } }`, suite.chainBBridge.String(), ibcAwayEncoded)), @@ -572,15 +474,15 @@ func (suite *TransferTestSuite) TestMultipleAddressesInvolved() { suite.coordinator.RelayAndAckPendingPackets(path.Invert()) // Make sure the NFT was burned on chain B - err = suite.chainB.SmartQuery(chainBNft, OwnerOfQuery{OwnerOf: OwnerOfQueryData{TokenID: "bad kid 1"}}, &OwnerOfResponse{}) + err = suite.chainB.SmartQuery(chainBNft, test_suite.OwnerOfQuery{OwnerOf: test_suite.OwnerOfQueryData{TokenID: "bad kid 1"}}, &test_suite.OwnerOfResponse{}) require.ErrorContains(suite.T(), err, "cw721_base::state::TokenInfo>; key: [00, 06, 74, 6F, 6B, 65, 6E, 73, 62, 61, 64, 20, 6B, 69, 64, 20, 31] not found") // Make another account on chain B and transfer to the new account. - anotherAcount := CreateAndFundAccount(suite.T(), suite.chainB, 19) - ics721Nft(suite.T(), suite.chainA, path, suite.coordinator, chainANft.String(), "bad kid 1", suite.chainABridge, suite.chainA.SenderAccount.GetAddress(), anotherAcount.Address, "") + anotherAcount := test_suite.CreateAndFundAccount(suite.T(), suite.chainB, 19) + test_suite.Ics721TransferNft(suite.T(), suite.chainA, path, suite.coordinator, chainANft.String(), "bad kid 1", suite.chainABridge, suite.chainA.SenderAccount.GetAddress(), anotherAcount.Address, "") // Transfer it back to chain A using this new account. - _, err = SendMsgsFromAccount(suite.T(), suite.chainB, anotherAcount, &wasmtypes.MsgExecuteContract{ + _, err = test_suite.SendMsgsFromAccount(suite.T(), suite.chainB, anotherAcount, &wasmtypes.MsgExecuteContract{ Sender: anotherAcount.Address.String(), Contract: chainBNft, Msg: []byte(fmt.Sprintf(`{ "send_nft": { "contract": "%s", "token_id": "bad kid 1", "msg": "%s" } }`, suite.chainBBridge.String(), ibcAwayEncoded)), @@ -590,12 +492,12 @@ func (suite *TransferTestSuite) TestMultipleAddressesInvolved() { suite.coordinator.RelayAndAckPendingPackets(path.Invert()) // Make sure it was burned on B. - err = suite.chainB.SmartQuery(chainBNft, OwnerOfQuery{OwnerOf: OwnerOfQueryData{TokenID: "bad kid 1"}}, &OwnerOfResponse{}) + err = suite.chainB.SmartQuery(chainBNft, test_suite.OwnerOfQuery{OwnerOf: test_suite.OwnerOfQueryData{TokenID: "bad kid 1"}}, &test_suite.OwnerOfResponse{}) require.ErrorContains(suite.T(), err, "cw721_base::state::TokenInfo>; key: [00, 06, 74, 6F, 6B, 65, 6E, 73, 62, 61, 64, 20, 6B, 69, 64, 20, 31] not found") // Make sure it is owned by the correct address on A. - resp := OwnerOfResponse{} - err = suite.chainA.SmartQuery(chainANft.String(), OwnerOfQuery{OwnerOf: OwnerOfQueryData{TokenID: "bad kid 1"}}, &resp) + resp := test_suite.OwnerOfResponse{} + err = suite.chainA.SmartQuery(chainANft.String(), test_suite.OwnerOfQuery{OwnerOf: test_suite.OwnerOfQueryData{TokenID: "bad kid 1"}}, &resp) require.NoError(suite.T(), err) require.Equal(suite.T(), resp.Owner, suite.chainA.SenderAccount.GetAddress().String()) } @@ -618,11 +520,11 @@ func TestCloseRejected(t *testing.T) { require.Equal(t, uint64(2), chainBStoreResp.CodeID) // Store the cw721_base contract. - instantiateICS721 := InstantiateICS721Bridge{ - 2, - nil, - nil, - nil, + instantiateICS721 := test_suite.InstantiateICS721Bridge{ + Cw721BaseCodeId: 2, + OutgoingProxy: nil, + IncomingProxy: nil, + Pauser: nil, } instantiateICS721Raw, err := json.Marshal(instantiateICS721) require.NoError(t, err) @@ -658,7 +560,7 @@ func TestCloseRejected(t *testing.T) { // For this version we are account number 17. Why not 10? Why // not 1? These are some of the world's great mysteries. - newAccount := CreateAndFundAccount(t, chainA, 17) + newAccount := test_suite.CreateAndFundAccount(t, chainA, 17) // Make sure ChanCloseInit is rejected. msg := channeltypes.NewMsgChannelCloseInit(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, newAccount.Address.String()) @@ -728,8 +630,8 @@ func (suite *TransferTestSuite) TestPacketTimeoutCausesRefund() { suite.coordinator.SetupConnections(path) suite.coordinator.CreateChannels(path) - cw721 := instantiateCw721(suite.T(), suite.chainA) - mintNFT(suite.T(), suite.chainA, cw721.String(), "bad kid 1", suite.chainA.SenderAccount.GetAddress()) + cw721 := test_suite.InstantiateCw721(suite.T(), suite.chainA, 18) + test_suite.MintNFT(suite.T(), suite.chainA, cw721.String(), "bad kid 1", suite.chainA.SenderAccount.GetAddress()) // IBC away message that will expire in one second. ibcAway := fmt.Sprintf(`{ "receiver": "%s", "channel_id": "%s", "timeout": { "timestamp": "%d" } }`, suite.chainB.SenderAccount.GetAddress().String(), path.EndpointA.ChannelID, suite.coordinator.CurrentTime.UnixNano()+1000000) @@ -745,7 +647,7 @@ func (suite *TransferTestSuite) TestPacketTimeoutCausesRefund() { suite.coordinator.TimeoutPendingPackets(path) // NFTs should be returned to sender on packet timeout. - owner := queryGetOwnerOf(suite.T(), suite.chainA, cw721.String(), "bad kid 1") + owner := test_suite.QueryGetOwnerOf(suite.T(), suite.chainA, cw721.String(), "bad kid 1") require.Equal(suite.T(), suite.chainA.SenderAccount.GetAddress().String(), owner) } @@ -772,8 +674,8 @@ func (suite *TransferTestSuite) TestRefundOnAckFail() { suite.coordinator.SetupConnections(path) suite.coordinator.CreateChannels(path) - chainANft := instantiateCw721(suite.T(), suite.chainA) - mintNFT(suite.T(), suite.chainA, chainANft.String(), "bad kid 1", suite.chainA.SenderAccount.GetAddress()) + chainANft := test_suite.InstantiateCw721(suite.T(), suite.chainA, 18) + test_suite.MintNFT(suite.T(), suite.chainA, chainANft.String(), "bad kid 1", suite.chainA.SenderAccount.GetAddress()) // Send the NFT, but use an invalid address in the receiver // field. This will cause processing to fail. The counterparty @@ -794,10 +696,10 @@ func (suite *TransferTestSuite) TestRefundOnAckFail() { suite.coordinator.RelayAndAckPendingPackets(path) chainBClassID := fmt.Sprintf(`%s/%s/%s`, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, chainANft) - chainBNft := queryGetNftForClass(suite.T(), suite.chainB, suite.chainBBridge.String(), chainBClassID) + chainBNft := test_suite.QueryGetNftForClass(suite.T(), suite.chainB, suite.chainBBridge.String(), chainBClassID) require.Equal(suite.T(), chainBNft, "") // Check that the NFT was returned to the sender due to the failure. - ownerA := queryGetOwnerOf(suite.T(), suite.chainA, chainANft.String(), "bad kid 1") + ownerA := test_suite.QueryGetOwnerOf(suite.T(), suite.chainA, chainANft.String(), "bad kid 1") require.Equal(suite.T(), suite.chainA.SenderAccount.GetAddress().String(), ownerA) } diff --git a/external-wasms/cw721_base_v0.16.0.wasm b/external-wasms/cw721_base_v0.16.0.wasm new file mode 100644 index 00000000..52721d54 Binary files /dev/null and b/external-wasms/cw721_base_v0.16.0.wasm differ diff --git a/external-wasms/cw721_base_v0.17.0.wasm b/external-wasms/cw721_base_v0.17.0.wasm new file mode 100644 index 00000000..881e9e3d Binary files /dev/null and b/external-wasms/cw721_base_v0.17.0.wasm differ diff --git a/packages/ics721-types/src/token_types.rs b/packages/ics721-types/src/token_types.rs index 78a9590d..01535c5d 100644 --- a/packages/ics721-types/src/token_types.rs +++ b/packages/ics721-types/src/token_types.rs @@ -79,6 +79,12 @@ impl From for String { } } +impl From for ClassId { + fn from(s: String) -> Self { + Self(s) + } +} + impl From for String { fn from(t: TokenId) -> Self { t.0 diff --git a/packages/ics721/schema/ics721.json b/packages/ics721/schema/ics721.json index b59a2e48..305a3fd8 100644 --- a/packages/ics721/schema/ics721.json +++ b/packages/ics721/schema/ics721.json @@ -899,6 +899,36 @@ }, "additionalProperties": false }, + { + "description": "Returns predictable NFT contract using instantiate2. If no cw721_code_id is provided, default cw721_code_id from storage is used.", + "type": "object", + "required": [ + "get_instantiate2_nft_contract" + ], + "properties": { + "get_instantiate2_nft_contract": { + "type": "object", + "required": [ + "class_id" + ], + "properties": { + "class_id": { + "type": "string" + }, + "cw721_code_id": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Gets the class level metadata URI for the provided class_id. If there is no metadata, returns None. Returns `Option`.", "type": "object", @@ -1320,6 +1350,12 @@ "format": "uint64", "minimum": 0.0 }, + "get_instantiate2_nft_contract": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, "incoming_channels": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Array_of_Tuple_of_Tuple_of_ClassId_and_TokenId_and_String", diff --git a/packages/ics721/schema/raw/query.json b/packages/ics721/schema/raw/query.json index b6c82f81..1b27072d 100644 --- a/packages/ics721/schema/raw/query.json +++ b/packages/ics721/schema/raw/query.json @@ -46,6 +46,36 @@ }, "additionalProperties": false }, + { + "description": "Returns predictable NFT contract using instantiate2. If no cw721_code_id is provided, default cw721_code_id from storage is used.", + "type": "object", + "required": [ + "get_instantiate2_nft_contract" + ], + "properties": { + "get_instantiate2_nft_contract": { + "type": "object", + "required": [ + "class_id" + ], + "properties": { + "class_id": { + "type": "string" + }, + "cw721_code_id": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Gets the class level metadata URI for the provided class_id. If there is no metadata, returns None. Returns `Option`.", "type": "object", diff --git a/packages/ics721/schema/raw/response_to_get_instantiate2_nft_contract.json b/packages/ics721/schema/raw/response_to_get_instantiate2_nft_contract.json new file mode 100644 index 00000000..4c7f1934 --- /dev/null +++ b/packages/ics721/schema/raw/response_to_get_instantiate2_nft_contract.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Addr", + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" +} diff --git a/packages/ics721/src/execute.rs b/packages/ics721/src/execute.rs index 5e604303..67d4297d 100644 --- a/packages/ics721/src/execute.rs +++ b/packages/ics721/src/execute.rs @@ -2,7 +2,7 @@ use std::fmt::Debug; use cosmwasm_std::{ from_json, to_json_binary, Addr, Binary, ContractInfoResponse, Deps, DepsMut, Empty, Env, - IbcMsg, MessageInfo, Order, Response, StdResult, SubMsg, WasmMsg, + Event, IbcMsg, MessageInfo, Order, Response, StdResult, SubMsg, WasmMsg, }; use cw_storage_plus::Map; use ics721_types::{ @@ -157,7 +157,7 @@ where let token_id = TokenId::new(token_id); let child_class_id = ClassId::new(child_class_id); let child_collection = deps.api.addr_validate(&child_collection)?; - match query_nft_contract_for_class_id(deps.storage, child_class_id.to_string())? { + match query_nft_contract_for_class_id(deps.storage, child_class_id.clone())? { Some(cw721_addr) => { if cw721_addr != child_collection { return Err(ContractError::NoNftContractMatch { @@ -240,7 +240,7 @@ where // check given home class id and home collection is the same as stored in the contract let home_class_id = ClassId::new(home_class_id); let home_collection = deps.api.addr_validate(&home_collection)?; - match query_nft_contract_for_class_id(deps.storage, home_class_id.to_string())? { + match query_nft_contract_for_class_id(deps.storage, home_class_id.clone())? { Some(cw721_addr) => { if cw721_addr != home_collection { return Err(ContractError::NoNftContractMatch { @@ -524,15 +524,23 @@ where msg: to_json_binary(&ExecuteMsg::Callback(CallbackMsg::Mint { class_id: class.id.clone(), receiver, - tokens, + tokens: tokens.clone(), }))?, funds: vec![], }; - let instantiate = self.create_instantiate_msg(deps, &env, class.clone())?; + let (class_id_info, instantiate) = + self.create_instantiate_msg(deps, &env, class.clone())?; + + let token_ids = format!("{:?}", tokens); + let event = Event::new("ics721_receive_create_vouchers") + .add_attribute("class_id", class_id_info.class_id) + .add_attribute("nft_contract", class_id_info.address) + .add_attribute("token_ids", token_ids); Ok(Response::::default() .add_attribute("method", "callback_create_vouchers") + .add_event(event) .add_submessages(instantiate) .add_message(mint)) } @@ -542,9 +550,11 @@ where deps: DepsMut, env: &Env, class: Class, - ) -> Result>, ContractError> { - if CLASS_ID_AND_NFT_CONTRACT_INFO.has(deps.storage, class.id.to_string().as_str()) { - Ok(vec![]) + ) -> Result<(ClassIdInfo, Vec>), ContractError> { + let maybe_class_id_info = + CLASS_ID_AND_NFT_CONTRACT_INFO.may_load(deps.as_ref().storage, &class.id)?; + if let Some(nft_contract) = maybe_class_id_info { + Ok((nft_contract, vec![])) } else { let class_id = ClassId::new(class.id.clone()); let cw721_code_id = CW721_CODE_ID.load(deps.storage)?; @@ -586,7 +596,7 @@ where }, INSTANTIATE_CW721_REPLY_ID, ); - Ok(vec![message]) + Ok((class_id_info, vec![message])) } } @@ -629,8 +639,16 @@ where let VoucherRedemption { class, token_ids } = redeem; let nft_contract = load_nft_contract_for_class_id(deps.storage, class.id.to_string())?; let receiver = deps.api.addr_validate(&receiver)?; + + let token_ids_string = format!("{:?}", token_ids); + let event = Event::new("ics721_receive_redeem_vouchers") + .add_attribute("class_id", class.id) + .add_attribute("nft_contract", nft_contract.clone()) + .add_attribute("token_ids", token_ids_string); + Ok(Response::default() .add_attribute("method", "callback_redeem_vouchers") + .add_event(event) .add_messages( token_ids .into_iter() diff --git a/packages/ics721/src/ibc.rs b/packages/ics721/src/ibc.rs index 5f7cdaf8..fa96eef6 100644 --- a/packages/ics721/src/ibc.rs +++ b/packages/ics721/src/ibc.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{ - from_json, to_json_binary, DepsMut, Empty, Env, IbcBasicResponse, IbcChannelCloseMsg, + from_json, to_json_binary, DepsMut, Empty, Env, Event, IbcBasicResponse, IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg, IbcChannelOpenResponse, IbcPacket, IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse, Never, Reply, Response, StdResult, SubMsgResult, WasmMsg, @@ -162,14 +162,23 @@ where None => vec![], }; + let token_ids = format!("{:?}", msg.token_ids); + let event = Event::new("ics721_ack_burn_vouchers") + .add_attribute("nft_contract", nft_contract.clone()) + .add_attribute("class_id", msg.class_id.to_string()) + .add_attribute("token_ids", token_ids.clone()); + Ok(IbcBasicResponse::new() - .add_messages(burn_notices) - .add_submessages(callback) - .add_attribute("method", "acknowledge") + .add_attribute("burn_notices", (!burn_notices.is_empty()).to_string()) + .add_attribute("method", "ibc_packet_ack_success") .add_attribute("sender", msg.sender) .add_attribute("receiver", msg.receiver) + .add_attribute("nft_contract", nft_contract) .add_attribute("classId", msg.class_id) - .add_attribute("token_ids", format!("{:?}", msg.token_ids))) + .add_attribute("token_ids", token_ids) + .add_messages(burn_notices) + .add_submessages(callback) + .add_event(event)) } } @@ -225,7 +234,7 @@ where Ok(IbcBasicResponse::new() .add_messages(messages) .add_submessages(callback) - .add_attribute("method", "handle_packet_fail") + .add_attribute("method", "ibc_packet_ack_fail") .add_attribute("token_ids", format!("{:?}", message.token_ids)) .add_attribute("class_id", message.class_id) .add_attribute("channel_id", packet.src.channel_id) diff --git a/packages/ics721/src/ibc_packet_receive.rs b/packages/ics721/src/ibc_packet_receive.rs index 5aff2200..7180dec6 100644 --- a/packages/ics721/src/ibc_packet_receive.rs +++ b/packages/ics721/src/ibc_packet_receive.rs @@ -2,18 +2,17 @@ use cosmwasm_std::{ from_json, to_json_binary, Addr, Binary, Deps, DepsMut, Empty, Env, IbcPacket, IbcReceiveResponse, StdResult, SubMsg, WasmMsg, }; -use sha2::{Digest, Sha256}; use zip_optional::Zippable; use crate::{ - helpers::{ - generate_receive_callback_msg, get_incoming_proxy_msg, get_instantiate2_address, - get_receive_callback, - }, + helpers::{generate_receive_callback_msg, get_incoming_proxy_msg, get_receive_callback}, ibc::ACK_AND_DO_NOTHING_REPLY_ID, ibc_helpers::{get_endpoint_prefix, try_pop_source_prefix}, msg::{CallbackMsg, ExecuteMsg}, - query::load_nft_contract_for_class_id, + query::{ + load_nft_contract_for_class_id, query_get_instantiate2_nft_contract, + query_nft_contract_for_class_id, + }, state::{CW721_CODE_ID, OUTGOING_CLASS_TOKEN_TO_CHANNEL, PO}, token_types::{VoucherCreation, VoucherRedemption}, ContractError, @@ -56,14 +55,35 @@ pub(crate) fn receive_ibc_packet( let incoming_proxy_msg = get_incoming_proxy_msg(deps.as_ref().storage, packet.clone(), data.clone())?; // - one optional callback message - let callback_msg = create_callback_msg( - deps.as_ref(), - &env, - &data, - is_redemption, - callback, - local_class_id, - )?; + // callback require the nft contract, get it using the class id from the action + let nft_contract = if is_redemption { + // If its a redemption, it means we already have the contract address in storage + + load_nft_contract_for_class_id(deps.storage, local_class_id.to_string()) + .map_err(|_| ContractError::NoNftContractForClassId(local_class_id.to_string())) + } else { + let nft_contract = + match query_nft_contract_for_class_id(deps.storage, local_class_id.clone()) { + Ok(nft_contract) => nft_contract, + Err(_) => None, // not found, occurs on initial transfer when we don't have the contract address + }; + match nft_contract { + Some(nft_contract) => Ok(nft_contract), + None => { + // contract not yet instantiated, so we use instantiate2 to get the contract address + let cw721_code_id = CW721_CODE_ID.load(deps.storage)?; + query_get_instantiate2_nft_contract( + deps.as_ref(), + &env, + local_class_id.clone(), + Some(cw721_code_id), + ) + } + } + }?; + + let callback_msg = + create_callback_msg(deps.as_ref(), &data, nft_contract.to_string(), callback)?; let submessage = into_submessage( env.contract.address, @@ -82,6 +102,8 @@ pub(crate) fn receive_ibc_packet( Ok(response .add_submessage(submessage) .add_attribute("method", "receive_ibc_packet") + .add_attribute("nft_contract", nft_contract.to_string()) + .add_attribute("is_redemption", is_redemption.to_string()) .add_attribute("class_id", data.class_id) .add_attribute("local_channel", packet.dest.channel_id) .add_attribute("counterparty_channel", packet.src.channel_id)) @@ -218,35 +240,11 @@ fn create_voucher_and_channel_messages( fn create_callback_msg( deps: Deps, - env: &Env, data: &NonFungibleTokenPacketData, - is_redemption: bool, + nft_contract: String, callback: Option<(Binary, Option)>, - local_class_id: ClassId, ) -> Result, ContractError> { if let Some((receive_callback_data, receive_callback_addr)) = callback { - // callback require the nft contract, get it using the class id from the action - let nft_contract = if is_redemption { - // If its a redemption, it means we already have the contract address in storage - - load_nft_contract_for_class_id(deps.storage, local_class_id.to_string()) - .map_err(|_| ContractError::NoNftContractForClassId(local_class_id.to_string())) - } else { - // If its a creation action, we can use the instantiate2 function to get the nft contract - // we don't care of the contract is instantiated yet or not, as later submessage will instantiate it if its not. - // The reason we use instantiate2 here is because we don't know if it was already instantiated or not. - - let cw721_code_id = CW721_CODE_ID.load(deps.storage)?; - // for creating a predictable nft contract using, using instantiate2, we need: checksum, creator, and salt: - // - using class id as salt for instantiating nft contract guarantees a) predictable address and b) uniqueness - // for this salt must be of length 32 bytes, so we use sha256 to hash class id - let mut hasher = Sha256::new(); - hasher.update(local_class_id.as_bytes()); - let salt = hasher.finalize().to_vec(); - - get_instantiate2_address(deps, env.contract.address.as_str(), &salt, cw721_code_id) - }?; - Ok(generate_receive_callback_msg( deps, data, diff --git a/packages/ics721/src/msg.rs b/packages/ics721/src/msg.rs index e2f58ebf..4d2563a3 100644 --- a/packages/ics721/src/msg.rs +++ b/packages/ics721/src/msg.rs @@ -125,6 +125,14 @@ pub enum QueryMsg { #[returns(Option<::cosmwasm_std::Addr>)] NftContract { class_id: String }, + /// Returns predictable NFT contract using instantiate2. If no + /// cw721_code_id is provided, default cw721_code_id from storage is used. + #[returns(::cosmwasm_std::Addr)] + GetInstantiate2NftContract { + class_id: String, + cw721_code_id: Option, + }, + /// Gets the class level metadata URI for the provided /// class_id. If there is no metadata, returns None. Returns /// `Option`. diff --git a/packages/ics721/src/query.rs b/packages/ics721/src/query.rs index afafc25a..4f756cd3 100644 --- a/packages/ics721/src/query.rs +++ b/packages/ics721/src/query.rs @@ -1,57 +1,65 @@ use cosmwasm_std::{to_json_binary, Addr, Binary, Deps, Env, Order, StdError, StdResult, Storage}; use cw_storage_plus::{Bound, Map}; +use sha2::{Digest, Sha256}; use crate::{ + helpers::get_instantiate2_address, msg::QueryMsg, state::{ UniversalAllNftInfoResponse, ADMIN_USED_FOR_CW721, CLASS_ID_AND_NFT_CONTRACT_INFO, CLASS_ID_TO_CLASS, CONTRACT_ADDR_LENGTH, CW721_CODE_ID, INCOMING_CLASS_TOKEN_TO_CHANNEL, INCOMING_PROXY, OUTGOING_CLASS_TOKEN_TO_CHANNEL, OUTGOING_PROXY, PO, TOKEN_METADATA, }, + ContractError, }; use ics721_types::token_types::{Class, ClassId, ClassToken, Token, TokenId}; pub trait Ics721Query { - fn query(&self, deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + fn query(&self, deps: Deps, env: Env, msg: QueryMsg) -> Result { match msg { - QueryMsg::ClassId { contract } => { - to_json_binary(&query_class_id_for_nft_contract(deps, contract)?) - } - QueryMsg::NftContract { class_id } => { - to_json_binary(&query_nft_contract_for_class_id(deps.storage, class_id)?) - } + QueryMsg::ClassId { contract } => Ok(to_json_binary( + &query_class_id_for_nft_contract(deps, contract)?, + )?), + QueryMsg::NftContract { class_id } => Ok(to_json_binary( + &query_nft_contract_for_class_id(deps.storage, class_id.into())?, + )?), + QueryMsg::GetInstantiate2NftContract { + class_id, + cw721_code_id, + } => Ok(to_json_binary(&query_get_instantiate2_nft_contract( + deps, + &env, + class_id.into(), + cw721_code_id, + )?)?), QueryMsg::ClassMetadata { class_id } => { - to_json_binary(&query_class_metadata(deps, class_id)?) - } - QueryMsg::TokenMetadata { class_id, token_id } => { - to_json_binary(&query_token_metadata(deps, class_id, token_id)?) + Ok(to_json_binary(&query_class_metadata(deps, class_id)?)?) } + QueryMsg::TokenMetadata { class_id, token_id } => Ok(to_json_binary( + &query_token_metadata(deps, class_id, token_id)?, + )?), QueryMsg::Owner { class_id, token_id } => { - to_json_binary(&query_owner(deps, class_id, token_id)?) - } - QueryMsg::Pauser {} => to_json_binary(&PO.query_pauser(deps.storage)?), - QueryMsg::Paused {} => to_json_binary(&PO.query_paused(deps.storage)?), - QueryMsg::OutgoingProxy {} => to_json_binary(&OUTGOING_PROXY.load(deps.storage)?), - QueryMsg::IncomingProxy {} => to_json_binary(&INCOMING_PROXY.load(deps.storage)?), - QueryMsg::Cw721CodeId {} => to_json_binary(&query_cw721_code_id(deps)?), - QueryMsg::Cw721Admin {} => to_json_binary(&ADMIN_USED_FOR_CW721.load(deps.storage)?), - QueryMsg::ContractAddrLength {} => { - to_json_binary(&CONTRACT_ADDR_LENGTH.may_load(deps.storage)?) + Ok(to_json_binary(&query_owner(deps, class_id, token_id)?)?) } - QueryMsg::NftContracts { start_after, limit } => { - to_json_binary(&query_nft_contracts(deps, start_after, limit)?) + QueryMsg::Pauser {} => Ok(to_json_binary(&PO.query_pauser(deps.storage)?)?), + QueryMsg::Paused {} => Ok(to_json_binary(&PO.query_paused(deps.storage)?)?), + QueryMsg::OutgoingProxy {} => Ok(to_json_binary(&OUTGOING_PROXY.load(deps.storage)?)?), + QueryMsg::IncomingProxy {} => Ok(to_json_binary(&INCOMING_PROXY.load(deps.storage)?)?), + QueryMsg::Cw721CodeId {} => Ok(to_json_binary(&query_cw721_code_id(deps)?)?), + QueryMsg::Cw721Admin {} => { + Ok(to_json_binary(&ADMIN_USED_FOR_CW721.load(deps.storage)?)?) } - QueryMsg::OutgoingChannels { start_after, limit } => to_json_binary(&query_channels( - deps, - &OUTGOING_CLASS_TOKEN_TO_CHANNEL, - start_after, - limit, + QueryMsg::ContractAddrLength {} => Ok(to_json_binary( + &CONTRACT_ADDR_LENGTH.may_load(deps.storage)?, )?), - QueryMsg::IncomingChannels { start_after, limit } => to_json_binary(&query_channels( - deps, - &INCOMING_CLASS_TOKEN_TO_CHANNEL, - start_after, - limit, + QueryMsg::NftContracts { start_after, limit } => Ok(to_json_binary( + &query_nft_contracts(deps, start_after, limit)?, + )?), + QueryMsg::OutgoingChannels { start_after, limit } => Ok(to_json_binary( + &query_channels(deps, &OUTGOING_CLASS_TOKEN_TO_CHANNEL, start_after, limit)?, + )?), + QueryMsg::IncomingChannels { start_after, limit } => Ok(to_json_binary( + &query_channels(deps, &INCOMING_CLASS_TOKEN_TO_CHANNEL, start_after, limit)?, )?), } } @@ -75,21 +83,39 @@ pub fn load_class_id_for_nft_contract( pub fn query_nft_contract_for_class_id( storage: &dyn Storage, - class_id: String, + class_id: ClassId, ) -> StdResult> { - // Convert the class_id string to ClassId type if necessary - let class_id_key = ClassId::new(class_id); - // Query the IndexedMap using the class_id index CLASS_ID_AND_NFT_CONTRACT_INFO .idx .class_id - .item(storage, class_id_key) + .item(storage, class_id) .map(|e| e.map(|(_, v)| v.address)) } +pub fn query_get_instantiate2_nft_contract( + deps: Deps, + env: &Env, + class_id: ClassId, + cw721_code_id: Option, +) -> Result { + let cw721_code_id = if let Some(cw721_code_id) = cw721_code_id { + cw721_code_id + } else { + CW721_CODE_ID.load(deps.storage)? + }; + + let mut hasher = Sha256::new(); + hasher.update(class_id.as_bytes()); + let salt = hasher.finalize().to_vec(); + + let nft_contract = + get_instantiate2_address(deps, env.contract.address.as_str(), &salt, cw721_code_id)?; + Ok(nft_contract) +} + pub fn load_nft_contract_for_class_id(storage: &dyn Storage, class_id: String) -> StdResult { - query_nft_contract_for_class_id(storage, class_id.clone())?.map_or_else( + query_nft_contract_for_class_id(storage, class_id.clone().into())?.map_or_else( || { Err(StdError::NotFound { kind: format!("NFT contract not found for class id {}", class_id), @@ -118,8 +144,7 @@ pub fn query_token_metadata( // metadata entry, we have no entry for this token at all. return Ok(None); }; - let Some(nft_contract) = query_nft_contract_for_class_id(deps.storage, class_id.to_string())? - else { + let Some(nft_contract) = query_nft_contract_for_class_id(deps.storage, class_id)? else { debug_assert!(false, "token_metadata != None => token_contract != None"); return Ok(None); }; diff --git a/packages/ics721/src/testing/contract.rs b/packages/ics721/src/testing/contract.rs index 8f057ea7..6980a4f3 100644 --- a/packages/ics721/src/testing/contract.rs +++ b/packages/ics721/src/testing/contract.rs @@ -652,10 +652,10 @@ fn test_migrate() { assert_eq!(nft_contract_and_class_id_list[1].1, NFT_CONTRACT_2); // test query and indexers for class id and addr are working let nft_contract_1 = - query_nft_contract_for_class_id(&deps.storage, CLASS_ID_1.to_string()).unwrap(); + query_nft_contract_for_class_id(&deps.storage, CLASS_ID_1.to_string().into()).unwrap(); assert_eq!(nft_contract_1, Some(Addr::unchecked(NFT_CONTRACT_1))); let nft_contract_2 = - query_nft_contract_for_class_id(&deps.storage, CLASS_ID_2.to_string()).unwrap(); + query_nft_contract_for_class_id(&deps.storage, CLASS_ID_2.to_string().into()).unwrap(); assert_eq!(nft_contract_2, Some(Addr::unchecked(NFT_CONTRACT_2))); let class_id_1 = query_class_id_for_nft_contract(deps.as_ref(), NFT_CONTRACT_1.to_string()).unwrap(); diff --git a/packages/ics721/src/testing/ibc_tests.rs b/packages/ics721/src/testing/ibc_tests.rs index d1ecb44b..32313df0 100644 --- a/packages/ics721/src/testing/ibc_tests.rs +++ b/packages/ics721/src/testing/ibc_tests.rs @@ -16,7 +16,7 @@ use crate::{ ibc_helpers::{ack_fail, ack_success, try_get_ack_error}, msg::{CallbackMsg, ExecuteMsg, InstantiateMsg, QueryMsg}, query::Ics721Query, - state::{ClassIdInfo, CollectionData, CLASS_ID_AND_NFT_CONTRACT_INFO, PO}, + state::{ClassIdInfo, CollectionData, CLASS_ID_AND_NFT_CONTRACT_INFO, CW721_CODE_ID, PO}, utils::get_collection_data, ContractError, }; @@ -34,7 +34,7 @@ const DEFAULT_TIMEOUT: u64 = 42; // Seconds. const ADDR1: &str = "addr1"; const RELAYER_ADDR: &str = "relayer"; -const CW721_CODE_ID: u64 = 0; +const CW721_BASE_CODE_ID: u64 = 0; #[derive(Default)] pub struct Ics721Contract {} @@ -106,7 +106,7 @@ fn add_channel(mut deps: DepsMut, env: Env, channel_id: &str) { fn do_instantiate(deps: DepsMut, env: Env, sender: &str) -> StdResult { let msg = InstantiateMsg { - cw721_base_code_id: CW721_CODE_ID, + cw721_base_code_id: CW721_BASE_CODE_ID, incoming_proxy: None, outgoing_proxy: None, pauser: None, @@ -437,6 +437,22 @@ fn test_ibc_channel_connect_invalid_version_counterparty() { #[test] fn test_ibc_packet_receive() { + let mut deps = mock_dependencies(); + CW721_CODE_ID + .save(&mut deps.storage, &CW721_BASE_CODE_ID) + .unwrap(); + let dest_class_id = format!("{}/{}/{}", CONTRACT_PORT, CHANNEL_ID, "id"); + CLASS_ID_AND_NFT_CONTRACT_INFO + .save( + &mut deps.storage, + &ClassId::new(dest_class_id.clone()), + &ClassIdInfo { + class_id: ClassId::new(dest_class_id), + address: Addr::unchecked("cosmos2contract"), + }, + ) + .unwrap(); + let data = to_json_binary(&NonFungibleTokenPacketData { class_id: ClassId::new("id"), class_uri: None, @@ -451,7 +467,6 @@ fn test_ibc_packet_receive() { .unwrap(); let ibc_packet = mock_packet(data); let packet = IbcPacketReceiveMsg::new(ibc_packet.clone(), Addr::unchecked(RELAYER_ADDR)); - let mut deps = mock_dependencies(); let env = mock_env(); PO.set_pauser(&mut deps.storage, &deps.api, None).unwrap(); let response = Ics721Contract::default() @@ -539,6 +554,22 @@ fn test_ibc_packet_receive_invalid_packet_data() { #[test] fn test_ibc_packet_receive_emits_memo() { + let mut deps = mock_dependencies(); + CW721_CODE_ID + .save(&mut deps.storage, &CW721_BASE_CODE_ID) + .unwrap(); + let dest_class_id = format!("{}/{}/{}", CONTRACT_PORT, CHANNEL_ID, "id"); + CLASS_ID_AND_NFT_CONTRACT_INFO + .save( + &mut deps.storage, + &ClassId::new(dest_class_id.clone()), + &ClassIdInfo { + class_id: ClassId::new(dest_class_id), + address: Addr::unchecked("cosmos2contract"), + }, + ) + .unwrap(); + let data = to_json_binary(&NonFungibleTokenPacketData { class_id: ClassId::new("id"), class_uri: None, @@ -552,12 +583,12 @@ fn test_ibc_packet_receive_emits_memo() { }) .unwrap(); let packet = IbcPacketReceiveMsg::new(mock_packet(data), Addr::unchecked(RELAYER_ADDR)); - let mut deps = mock_dependencies(); let env = mock_env(); PO.set_pauser(&mut deps.storage, &deps.api, None).unwrap(); let res = Ics721Contract::default() .ibc_packet_receive(deps.as_mut(), env, packet) .unwrap(); + println!(">>>>>>>>>>> memo: {:?}", res.attributes); assert!(res.attributes.contains(&Attribute { key: "ics721_memo".to_string(), value: "memo".to_string() @@ -700,6 +731,22 @@ fn test_no_receive_when_paused() { #[test] fn test_different_memo_ignored() { + let mut deps = mock_dependencies(); + CW721_CODE_ID + .save(&mut deps.storage, &CW721_BASE_CODE_ID) + .unwrap(); + let dest_class_id = format!("{}/{}/{}", CONTRACT_PORT, CHANNEL_ID, "id"); + CLASS_ID_AND_NFT_CONTRACT_INFO + .save( + &mut deps.storage, + &ClassId::new(dest_class_id.clone()), + &ClassIdInfo { + class_id: ClassId::new(dest_class_id), + address: Addr::unchecked("cosmos2contract"), + }, + ) + .unwrap(); + #[cw_serde] struct DifferentMemo { different: Option, @@ -732,7 +779,6 @@ fn test_different_memo_ignored() { }; let ibc_packet = mock_packet(to_json_binary(&data).unwrap()); let packet = IbcPacketReceiveMsg::new(ibc_packet, Addr::unchecked(RELAYER_ADDR)); - let mut deps = mock_dependencies(); let env = mock_env(); PO.set_pauser(&mut deps.storage, &deps.api, None).unwrap(); diff --git a/packages/ics721/src/testing/integration_tests.rs b/packages/ics721/src/testing/integration_tests.rs index 09e4e904..bda6f06b 100644 --- a/packages/ics721/src/testing/integration_tests.rs +++ b/packages/ics721/src/testing/integration_tests.rs @@ -101,7 +101,7 @@ fn execute( Ics721Contract::default().execute(deps, env, info, msg) } -fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { +fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result { Ics721Contract::default().query(deps, env, msg) }