diff --git a/lib/go/templates/internal/assets/assets.go b/lib/go/templates/internal/assets/assets.go index 6891aba8..12e70788 100644 --- a/lib/go/templates/internal/assets/assets.go +++ b/lib/go/templates/internal/assets/assets.go @@ -72,7 +72,7 @@ // ../../../transactions/shardedCollection/setup_sharded_collection.cdc (1.271kB) // ../../../transactions/shardedCollection/transfer_from_sharded.cdc (1.042kB) // ../../../transactions/user/batch_transfer.cdc (1.111kB) -// ../../../transactions/user/destroy_moments.cdc (549B) +// ../../../transactions/user/destroy_moments.cdc (1.23kB) // ../../../transactions/user/setup_account.cdc (990B) // ../../../transactions/user/transfer_moment.cdc (1.509kB) // ../../../transactions/user/transfer_moment_v3_sale.cdc (1.529kB) @@ -1585,7 +1585,7 @@ func TransactionsUserBatch_transferCdc() (*asset, error) { return a, nil } -var _TransactionsUserDestroy_momentsCdc = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x5c\x90\x41\xcf\x93\x40\x10\x86\xef\xfc\x8a\xd7\x8b\xa1\x07\x59\x0f\xc6\x03\xf9\x62\x6c\xc4\xc6\x1e\xec\xd7\x08\xc6\x83\xf1\xb0\xc0\xb4\x10\x61\x87\xcc\x0e\x69\x9b\xa6\xff\xdd\x40\x0b\x25\x72\x20\xd9\x9d\x27\xcf\xbc\xfb\xd6\x6d\xc7\xa2\xd8\xb1\xdb\xf4\xee\x58\xe7\x0d\x65\xfc\x97\x1c\x0e\xc2\x2d\xde\x9f\x77\x9b\x6c\x9d\x24\x3f\xbe\xa6\x69\xf0\x20\x33\xee\xd2\x8a\x75\x02\xb2\xd7\x7d\xfa\xed\x75\x86\x02\x63\x90\x55\xb5\x87\x8a\x75\xde\x16\x5a\xb3\x43\x49\x5e\x85\x2f\x1e\x16\xae\x6f\x73\x12\xf0\x01\x2d\xb7\xe4\xd4\x83\x4f\x8e\x4a\xe4\x17\x58\xf4\x9e\x64\x34\xec\xad\xd8\x96\x94\xc4\x07\xc6\x0c\x17\x77\x78\x9b\xf8\x18\xd6\xc1\x8a\xd8\xcb\xd3\x81\x6d\xe2\x87\xd3\x6e\x93\x79\x28\x23\xa7\x69\x23\x95\x41\xb0\x08\x12\x2e\x34\xbf\x7f\x6e\x9d\x7e\xfc\xf0\x67\x85\x6b\x10\x00\x40\x43\x0a\x1d\x9e\xee\x63\x7c\xfe\xbf\x8e\xe8\x0b\x37\x0d\x8d\x92\x11\x1e\x7f\x9d\x50\x67\x85\x42\x5b\x14\x1a\x63\xdd\x6b\xb5\x2e\x0a\xee\x9d\xce\xce\xe1\xf3\xd4\x1c\xa2\xbb\x18\x2f\xef\x30\xc0\x51\xce\x22\x7c\x7a\x79\xfb\xe8\x72\x61\xff\x14\x0e\xbd\xc6\x30\x5e\x59\xec\x91\xcc\xf7\x31\xf2\x13\x58\xbd\x89\x72\xab\x45\xf5\xab\xd6\xaa\x14\x7b\x0a\xeb\xd2\xc7\xcf\x7e\x56\xe3\xde\xdb\x7d\x3d\x9d\xa9\xe8\x95\x70\x9d\xc3\x18\x33\x55\x03\xad\x68\x6c\x6c\x9e\x4d\x83\x45\xe0\x87\xec\xf6\x2f\x00\x00\xff\xff\xdc\x26\xac\xae\x25\x02\x00\x00" +var _TransactionsUserDestroy_momentsCdc = "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x74\x53\xcb\x6e\xdb\x30\x10\xbc\xeb\x2b\xa6\x39\x04\x32\xd0\x48\x2d\x5c\xf4\x60\x24\x0d\x8c\x38\x41\x8d\x22\x0f\xc4\x6a\x7a\x28\x7a\x58\x89\xeb\x88\x88\x44\x1a\xe4\xaa\x89\x51\xe4\xdf\x0b\x4a\xf2\x23\x8e\xad\x83\x40\x52\xa3\xe1\xec\xcc\xae\xae\x17\xd6\x09\x6e\xac\xb9\x6a\xcc\xa3\xce\x2b\xce\xec\x13\x1b\xcc\x9d\xad\xf1\xe9\xe5\xe6\x2a\x1b\x4f\x26\xf7\x97\xb3\x59\xd4\x23\x33\xbb\x98\x95\x56\x56\x80\xec\xf6\x6e\xf6\xfd\xf6\x00\xe8\x9a\xdc\x13\xcb\xc3\x70\x05\xbe\x1e\xdf\xff\xb8\xcc\x1e\x86\x2b\x74\x94\xa6\xc8\x4a\xed\x21\x8e\x8c\xa7\x42\xb4\x35\x50\xec\xc5\xd9\xa5\x07\xc1\x34\x75\xce\x0e\x76\x8e\xda\xd6\x6c\xc4\xc3\x3e\x1b\x56\xc8\x97\x20\x34\x9e\x5d\xcb\x70\x47\x8e\x6a\x16\x76\x3e\x4a\xd3\x70\xd0\x81\xa7\x13\x3f\x02\x19\x90\x73\xb4\xdc\x70\x60\x3a\xf1\x61\x77\x73\x95\x79\x88\x45\xce\xab\x1b\x59\x45\xd1\x96\x90\x78\x8b\xe6\xf7\xcf\xa9\x91\xaf\x5f\xfe\x0c\xf0\x2f\x8a\x00\xa0\x62\x41\x61\xab\x8a\x5b\xe8\x3d\xcf\x47\x38\xee\x8b\x4e\x2e\xd6\xe7\x2d\xb4\x7d\x2d\x1c\x2f\xc8\x71\x4c\x45\x21\x23\x8c\x1b\x29\xc7\x45\x61\x1b\x23\x81\x11\xfd\x93\xa6\x50\x5c\x69\x2f\x20\xd3\x2a\x96\x92\xd7\x95\x4b\x49\x02\x72\x8c\x00\x60\x85\x58\x82\x71\x1d\xde\x63\x6e\x1d\x72\x2b\x25\x7a\xcb\x3f\x83\x8c\xea\x37\x7f\x87\x83\xf5\x15\x7a\xde\x4a\x17\xbb\xf0\xa5\x95\x19\x55\xfc\x30\xdc\xe8\xc5\x19\x82\xc0\x24\xb7\xce\xd9\xe7\xd3\xe3\x9d\x18\x93\x80\xdf\xa0\xbf\xc5\x21\xd6\xd1\x6e\xd8\x49\xdd\x2e\x66\x62\x1d\x3d\xf2\x1d\x49\xb9\x5d\x63\x78\x82\x58\xad\xa0\xcd\x26\xa9\x1d\x44\x2f\xf5\x80\xcc\x5e\xdf\x75\xfb\x73\xac\xd5\x08\x5a\x0d\xf0\xe1\x0c\x46\x57\xef\x79\x7a\x67\x0b\x32\x05\x57\x5b\x96\x76\x4d\x19\xf6\x9e\x2a\xfe\x18\x56\x8e\xf3\x25\x14\x9f\x04\x4f\xb5\x79\x84\x96\xbd\x6c\x87\x64\x75\x57\x84\xe3\x58\xc2\x10\x4d\x27\xad\xb2\x77\x1c\xaf\xd1\xfe\xdd\x6b\xb4\x5e\x7a\xae\xe6\xc9\x9b\x06\x3b\x90\x4d\xf2\x3e\x8f\xd4\x77\xd6\xa7\x9d\x41\x1b\xc0\x5b\x25\xe7\xe7\x58\x90\xd1\x45\x7c\x74\x61\x9b\x4a\xc1\x58\x41\x47\xde\x39\xb3\xfb\x77\xc8\xab\x67\x3e\xea\x98\x7a\xbd\xfc\xc2\x45\x23\xbc\x15\x61\xd7\x62\x4f\x6c\x3c\x4e\x4f\xf6\xd4\x92\xe4\x24\x45\xf9\x4b\x4b\xa9\x1c\x3d\xc7\x5a\xf9\xd1\xa6\x15\x06\x6f\xe7\xa1\x1d\xcd\x36\xa6\x30\xb1\xeb\x6f\xeb\x0f\xed\x35\xbd\x9c\xd7\xff\x01\x00\x00\xff\xff\x72\x60\x81\x68\xce\x04\x00\x00" func TransactionsUserDestroy_momentsCdcBytes() ([]byte, error) { return bindataRead( @@ -1601,7 +1601,7 @@ func TransactionsUserDestroy_momentsCdc() (*asset, error) { } info := bindataFileInfo{name: "../../../transactions/user/destroy_moments.cdc", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x48, 0x17, 0x2b, 0x4e, 0xa, 0x79, 0xa7, 0xf7, 0xb3, 0xbf, 0x57, 0x69, 0x4e, 0x83, 0x5c, 0xb3, 0x66, 0x32, 0x75, 0x10, 0x41, 0x10, 0x7f, 0x9b, 0x1a, 0x65, 0x7a, 0x5c, 0x65, 0x94, 0x27, 0xd4}} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xb5, 0x78, 0x91, 0x78, 0x5d, 0xb3, 0xa4, 0x14, 0xa2, 0x59, 0xef, 0x99, 0xaa, 0x28, 0xe8, 0xae, 0xd6, 0xa, 0x91, 0x86, 0x85, 0x18, 0x5a, 0x3e, 0x34, 0xa6, 0x19, 0xdd, 0x3, 0xd0, 0xe5, 0xb0}} return a, nil } diff --git a/lib/go/test/topshot_test.go b/lib/go/test/topshot_test.go index 113dc648..ef5b29bb 100644 --- a/lib/go/test/topshot_test.go +++ b/lib/go/test/topshot_test.go @@ -6,6 +6,8 @@ import ( "github.com/onflow/cadence" jsoncdc "github.com/onflow/cadence/encoding/json" + fungibleToken "github.com/onflow/flow-ft/lib/go/contracts" + fungibleTokenTemplates "github.com/onflow/flow-ft/lib/go/templates" "github.com/dapperlabs/nba-smart-contracts/lib/go/contracts" "github.com/dapperlabs/nba-smart-contracts/lib/go/templates" @@ -16,6 +18,7 @@ import ( "github.com/onflow/flow-go-sdk" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const ( @@ -986,12 +989,93 @@ func TestDestroyMoments(t *testing.T) { env.TopShotAddress = topshotAddr.String() + // Should be able to deploy the token contract + tokenCode := fungibleToken.CustomToken(defaultfungibleTokenAddr, defaultTokenName, defaultTokenStorage, "1000.0") + tokenAccountKey, tokenSigner := accountKeys.NewWithSigner() + tokenAddr, err := b.CreateAccount([]*flow.AccountKey{tokenAccountKey}, []sdktemplates.Contract{ + { + Name: "DapperUtilityCoin", + Source: string(tokenCode), + }, + }) + if !assert.NoError(t, err) { + t.Log(err.Error()) + } + _, err = b.CommitBlock() + assert.NoError(t, err) + + env.DUCAddress = tokenAddr.String() + + // Setup with the first market contract + marketCode := contracts.GenerateTopShotMarketContract(defaultfungibleTokenAddr, nftAddr.String(), topshotAddr.String(), env.DUCAddress) + marketAddr, err := b.CreateAccount(nil, []sdktemplates.Contract{ + { + Name: "Market", + Source: string(marketCode), + }, + }) + if !assert.Nil(t, err) { + t.Log(err.Error()) + } + _, err = b.CommitBlock() + require.NoError(t, err) + + env.TopShotMarketAddress = marketAddr.String() + + // Should be able to deploy the third market contract + marketV3Code := contracts.GenerateTopShotMarketV3Contract(defaultfungibleTokenAddr, nftAddr.String(), topshotAddr.String(), marketAddr.String(), env.DUCAddress) + marketV3Addr, err := b.CreateAccount(nil, []sdktemplates.Contract{ + { + Name: "TopShotMarketV3", + Source: string(marketV3Code), + }, + }) + if !assert.Nil(t, err) { + t.Log(err.Error()) + } + _, err = b.CommitBlock() + require.NoError(t, err) + + env.TopShotMarketV3Address = marketV3Addr.String() + + // Should be able to deploy the token forwarding contract + forwardingCode := fungibleToken.CustomTokenForwarding(defaultfungibleTokenAddr, defaultTokenName, defaultTokenStorage) + forwardingAddr, err := b.CreateAccount(nil, []sdktemplates.Contract{ + { + Name: "TokenForwarding", + Source: string(forwardingCode), + }, + }) + if !assert.NoError(t, err) { + t.Log(err.Error()) + } + _, err = b.CommitBlock() + assert.NoError(t, err) + + env.ForwardingAddress = forwardingAddr.String() + // Create a new user account joshAccountKey, joshSigner := accountKeys.NewWithSigner() joshAddress, _ := b.CreateAccount([]*flow.AccountKey{joshAccountKey}, nil) + tx := createTxWithTemplateAndAuthorizer(b, fungibleTokenTemplates.GenerateCreateTokenScript(flow.HexToAddress(defaultfungibleTokenAddr), tokenAddr, defaultTokenName), joshAddress) + + signAndSubmit( + t, b, tx, + []flow.Address{b.ServiceKey().Address, joshAddress}, []crypto.Signer{b.ServiceKey().Signer(), joshSigner}, + false, + ) + + tx = createTxWithTemplateAndAuthorizer(b, fungibleTokenTemplates.GenerateMintTokensScript(flow.HexToAddress(defaultfungibleTokenAddr), tokenAddr, joshAddress, defaultTokenName, 80), tokenAddr) + + signAndSubmit( + t, b, tx, + []flow.Address{b.ServiceKey().Address, tokenAddr}, []crypto.Signer{b.ServiceKey().Signer(), tokenSigner}, + false, + ) + // Create moment collection - tx := createTxWithTemplateAndAuthorizer(b, templates.GenerateSetupAccountScript(env), joshAddress) + tx = createTxWithTemplateAndAuthorizer(b, templates.GenerateSetupAccountScript(env), joshAddress) signAndSubmit( t, b, tx, []flow.Address{b.ServiceKey().Address, joshAddress}, []crypto.Signer{b.ServiceKey().Signer(), joshSigner}, @@ -1090,12 +1174,59 @@ func TestDestroyMoments(t *testing.T) { false, ) + //check that moments with ids 1 and 2 exist in josh's collection + result := executeScriptAndCheck(t, b, templates.GenerateIsIDInCollectionScript(env), [][]byte{jsoncdc.MustEncode(cadence.Address(joshAddress)), jsoncdc.MustEncode(cadence.UInt64(1))}) + assert.Equal(t, cadence.NewBool(true), result) + result = executeScriptAndCheck(t, b, templates.GenerateIsIDInCollectionScript(env), [][]byte{jsoncdc.MustEncode(cadence.Address(joshAddress)), jsoncdc.MustEncode(cadence.UInt64(2))}) + assert.Equal(t, cadence.NewBool(true), result) + + ducPublicPath := cadence.Path{Domain: "public", Identifier: "dapperUtilityCoinReceiver"} + + // Create a marketv1 sale collection for Josh + // setting himself as the beneficiary with a 15% cut + tx = createTxWithTemplateAndAuthorizer(b, templates.GenerateCreateAndStartSaleScript(env), joshAddress) + + _ = tx.AddArgument(ducPublicPath) + _ = tx.AddArgument(cadence.NewAddress(joshAddress)) + _ = tx.AddArgument(CadenceUFix64("0.15")) + _ = tx.AddArgument(cadence.NewUInt64(2)) + _ = tx.AddArgument(CadenceUFix64("50.0")) + + signAndSubmit( + t, b, tx, + []flow.Address{b.ServiceKey().Address, joshAddress}, []crypto.Signer{b.ServiceKey().Signer(), joshSigner}, + false, + ) + + // Create a sale collection for josh's account, setting josh as the beneficiary + // and with a 15% cut + tx = createTxWithTemplateAndAuthorizer(b, templates.GenerateCreateAndStartSaleV3Script(env), joshAddress) + + _ = tx.AddArgument(ducPublicPath) + _ = tx.AddArgument(cadence.NewAddress(joshAddress)) + _ = tx.AddArgument(CadenceUFix64("0.15")) + _ = tx.AddArgument(cadence.NewUInt64(1)) + _ = tx.AddArgument(CadenceUFix64("50.0")) + + signAndSubmit( + t, b, tx, + []flow.Address{b.ServiceKey().Address, joshAddress}, []crypto.Signer{b.ServiceKey().Signer(), joshSigner}, + false, + ) + + momentIDs := []uint64{1, 2} + // check the price, sale length, and the sale's data + result = executeScriptAndCheck(t, b, templates.GenerateGetSaleLenV3Script(env), [][]byte{jsoncdc.MustEncode(cadence.Address(joshAddress))}) + assertEqual(t, cadence.NewInt(2), result) + for _, momentID := range momentIDs { + result = executeScriptAndCheck(t, b, templates.GenerateGetSalePriceV3Script(env), [][]byte{jsoncdc.MustEncode(cadence.Address(joshAddress)), jsoncdc.MustEncode(cadence.UInt64(momentID))}) + assertEqual(t, CadenceUFix64("50.0"), result) + + result = executeScriptAndCheck(t, b, templates.GenerateGetSaleSetIDV3Script(env), [][]byte{jsoncdc.MustEncode(cadence.Address(joshAddress)), jsoncdc.MustEncode(cadence.UInt64(momentID))}) + assertEqual(t, cadence.NewUInt32(1), result) + } + t.Run("Should destroy the 2 moments created in Josh account", func(t *testing.T) { - // check that moments with ids 1 and 2 exist in josh's collection - result := executeScriptAndCheck(t, b, templates.GenerateIsIDInCollectionScript(env), [][]byte{jsoncdc.MustEncode(cadence.Address(joshAddress)), jsoncdc.MustEncode(cadence.UInt64(1))}) - assert.Equal(t, cadence.NewBool(true), result) - result = executeScriptAndCheck(t, b, templates.GenerateIsIDInCollectionScript(env), [][]byte{jsoncdc.MustEncode(cadence.Address(joshAddress)), jsoncdc.MustEncode(cadence.UInt64(2))}) - assert.Equal(t, cadence.NewBool(true), result) tx = createTxWithTemplateAndAuthorizer(b, templates.GenerateDestroyMomentsScript(env), joshAddress) _ = tx.AddArgument(cadence.NewArray([]cadence.Value{cadence.NewUInt64(1), cadence.NewUInt64(2)})) signAndSubmit( @@ -1105,12 +1236,13 @@ func TestDestroyMoments(t *testing.T) { ) // verify that the moments no longer exist in josh's collection - r, err := b.ExecuteScript(templates.GenerateIsIDInCollectionScript(env), [][]byte{jsoncdc.MustEncode(cadence.Address(joshAddress)), jsoncdc.MustEncode(cadence.UInt64(1))}) - assert.NoError(t, err) - assert.Contains(t, r.Error.Error(), "NFT does not exist in the collection") - r, err = b.ExecuteScript(templates.GenerateIsIDInCollectionScript(env), [][]byte{jsoncdc.MustEncode(cadence.Address(joshAddress)), jsoncdc.MustEncode(cadence.UInt64(2))}) - assert.NoError(t, err) - assert.Contains(t, r.Error.Error(), "NFT does not exist in the collection") - + for _, momentID := range momentIDs { + r, err := b.ExecuteScript(templates.GenerateIsIDInCollectionScript(env), [][]byte{jsoncdc.MustEncode(cadence.Address(joshAddress)), jsoncdc.MustEncode(cadence.UInt64(momentID))}) + assert.NoError(t, err) + assert.Contains(t, r.Error.Error(), "NFT does not exist in the collection") + } + // verify no moments in sale collection + result = executeScriptAndCheck(t, b, templates.GenerateGetSaleLenV3Script(env), [][]byte{jsoncdc.MustEncode(cadence.Address(joshAddress))}) + assertEqual(t, cadence.NewInt(0), result) }) } diff --git a/transactions/user/destroy_moments.cdc b/transactions/user/destroy_moments.cdc index 38f0de31..767b5ed8 100644 --- a/transactions/user/destroy_moments.cdc +++ b/transactions/user/destroy_moments.cdc @@ -1,5 +1,6 @@ import NonFungibleToken from 0xNFTADDRESS import TopShot from 0xTOPSHOTADDRESS +import TopShotMarketV3 from 0xMARKETV3ADDRESS // This transaction destroys a number of moments owned by a user @@ -9,15 +10,26 @@ import TopShot from 0xTOPSHOTADDRESS transaction(momentIDs: [UInt64]) { - let tokens: @NonFungibleToken.Collection + let collectionRef: &TopShot.Collection prepare(acct: AuthAccount) { + // delist any of the moments that are listed (this delists for both MarketV1 and Marketv3) + if let topshotSaleV3Collection = acct.borrow<&TopShotMarketV3.SaleCollection>(from: TopShotMarketV3.marketStoragePath) { + for id in momentIDs { + if topshotSaleV3Collection.borrowMoment(id: id) != nil{ + // cancel the moment from the sale, thereby de-listing it + topshotSaleV3Collection.cancelSale(tokenID: id) + } + } + } - self.tokens <- acct.borrow<&TopShot.Collection>(from: /storage/MomentCollection)!.batchWithdraw(ids: momentIDs) + self.collectionRef = acct.borrow<&TopShot.Collection>(from: /storage/MomentCollection) + ?? panic("Could not borrow from MomentCollection in storage") } execute { + let tokens <- self.collectionRef.batchWithdraw(ids: momentIDs) // destroy the NFTs - destroy self.tokens + destroy tokens } } \ No newline at end of file