From 5cf39161dbfe6c7c124156a81f40135b02a78ac3 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sun, 17 Nov 2024 10:30:23 +0100 Subject: [PATCH 01/10] C1.0 migration --- cadence/contract.cdc | 247 +++++++++++++++++++++------------------- cadence/transaction.cdc | 22 ++-- index.js | 4 +- 3 files changed, 143 insertions(+), 130 deletions(-) diff --git a/cadence/contract.cdc b/cadence/contract.cdc index 406ca52..e4f4423 100644 --- a/cadence/contract.cdc +++ b/cadence/contract.cdc @@ -1,151 +1,159 @@ -// the below is a series structure that lays out how a series is to be created -// // Variable size dictionary of SeriesData structs access(self) var seriesData: {UInt32: SeriesData} // Variable size dictionary of Series resources access(self) var series: @{UInt32: Series} -.... -pub struct SeriesData { + +// Structure for SeriesData +access(all) +struct SeriesData { // Unique ID for the Series - pub let seriesId: UInt32 + access(all) + let seriesId: UInt32 - // Dictionary of metadata key value pairs - access(self) var metadata: {String: String} + // Dictionary of metadata key-value pairs + access(self) + var metadata: {String: String} init( seriesId: UInt32, - metadata: {String: String}) { + metadata: {String: String} + ) { self.seriesId = seriesId self.metadata = metadata emit SeriesCreated(seriesId: self.seriesId) } - pub fun getMetadata(): {String: String} { + // Retrieves metadata of the series + access(all) + view fun getMetadata(): {String: String} { return self.metadata } } -.... -// -// -// Resource that allows an admin to mint new NFTs - // - pub resource Series { - - // Unique ID for the Series - pub let seriesId: UInt32 - - // Array of NFTSets that belong to this Series - access(self) var setIds: [UInt32] - - // Series sealed state - pub var seriesSealedState: Bool; - - // Set sealed state - access(self) var setSealedState: {UInt32: Bool}; - - // Current number of editions minted per Set - access(self) var numberEditionsMintedPerSet: {UInt32: UInt32} - - init( - seriesId: UInt32, - metadata: {String: String}) { - - self.seriesId = seriesId - self.seriesSealedState = false - self.numberEditionsMintedPerSet = {} - self.setIds = [] - self.setSealedState = {} - - SetAndSeries.seriesData[seriesId] = SeriesData( - seriesId: seriesId, - metadata: metadata - ) - } - pub fun addNftSet( - setId: UInt32, - maxEditions: UInt32, - ipfsMetadataHashes: {UInt32: String}, - metadata: {String: String}) { - pre { - self.setIds.contains(setId) == false: "The Set has already been added to the Series." - } - - // Create the new Set struct - var newNFTSet = NFTSetData( - setId: setId, - seriesId: self.seriesId, - maxEditions: maxEditions, - ipfsMetadataHashes: ipfsMetadataHashes, - metadata: metadata - ) - - // Add the NFTSet to the array of Sets - self.setIds.append(setId) - - // Initialize the NFT edition count to zero - self.numberEditionsMintedPerSet[setId] = 0 - - // Store it in the sets mapping field - SetAndSeries.setData[setId] = newNFTSet - - emit SetCreated(seriesId: self.seriesId, setId: setId) +// Resource that allows an admin to manage and mint new NFTs for a series +access(all) +resource Series { + + // Unique ID for the Series + access(all) + let seriesId: UInt32 + + // Array of NFTSets that belong to this Series + access(self) + var setIds: [UInt32] + + // Series sealed state + access(all) + var seriesSealedState: Bool + + // Set sealed state + access(self) + var setSealedState: {UInt32: Bool} + + // Current number of editions minted per Set + access(self) + var numberEditionsMintedPerSet: {UInt32: UInt32} + + init( + seriesId: UInt32, + metadata: {String: String} + ) { + self.seriesId = seriesId + self.seriesSealedState = false + self.numberEditionsMintedPerSet = {} + self.setIds = [] + self.setSealedState = {} + + SetAndSeries.seriesData[seriesId] = SeriesData( + seriesId: seriesId, + metadata: metadata + ) + } + + // Adds a new NFTSet to this series + access(all) + fun addNftSet( + setId: UInt32, + maxEditions: UInt32, + ipfsMetadataHashes: {UInt32: String}, + metadata: {String: String} + ) { + pre { + self.setIds.contains(setId) == false: "The Set has already been added to the Series." } + // Create the new Set struct + let newNFTSet = NFTSetData( + setId: setId, + seriesId: self.seriesId, + maxEditions: maxEditions, + ipfsMetadataHashes: ipfsMetadataHashes, + metadata: metadata + ) + + // Add the NFTSet to the array of Sets + self.setIds.append(setId) + + // Initialize the NFT edition count to zero + self.numberEditionsMintedPerSet[setId] = 0 -..... - - // mintSetAndSeries - // Mints a new NFT with a new ID - // and deposits it in the recipients collection using their collection reference - // - pub fun mintSetAndSeriesNFT( - recipient: &{NonFungibleToken.CollectionPublic}, - tokenId: UInt64, - setId: UInt32) { - - pre { - self.numberEditionsMintedPerSet[setId] != nil: "The Set does not exist." - self.numberEditionsMintedPerSet[setId]! < SetAndSeries.getSetMaxEditions(setId: setId)!: - "Set has reached maximum NFT edition capacity." - } - - // Gets the number of editions that have been minted so far in - // this set - let editionNum: UInt32 = self.numberEditionsMintedPerSet[setId]! + (1 as UInt32) - - // deposit it in the recipient's account using their reference - recipient.deposit(token: <-create SetAndSeries.NFT( - tokenId: tokenId, - setId: setId, - editionNum: editionNum - )) - - // Increment the count of global NFTs - SetAndSeries.totalSupply = SetAndSeries.totalSupply + (1 as UInt64) - - // Update the count of Editions minted in the set - self.numberEditionsMintedPerSet[setId] = editionNum + // Store it in the sets mapping field + SetAndSeries.setData[setId] = newNFTSet + + emit SetCreated(seriesId: self.seriesId, setId: setId) + } + + // Mints a new NFT with a new ID and deposits it in the recipient's collection + access(all) + fun mintSetAndSeriesNFT( + recipient: &{NonFungibleToken.CollectionPublic}, + tokenId: UInt64, + setId: UInt32 + ) { + pre { + self.numberEditionsMintedPerSet[setId] != nil: "The Set does not exist." + self.numberEditionsMintedPerSet[setId]! < SetAndSeries.getSetMaxEditions(setId: setId)!: + "Set has reached maximum NFT edition capacity." } -.... -// Admin is a special authorization resource that -// allows the owner to perform important NFT -// functions -// -pub resource Admin { + // Gets the number of editions that have been minted so far in this set + let editionNum: UInt32 = self.numberEditionsMintedPerSet[setId]! + (1 as UInt32) - pub fun addSeries(seriesId: UInt32, metadata: {String: String}) { + // Deposit it in the recipient's account using their reference + recipient.deposit(token: <-create SetAndSeries.NFT( + tokenId: tokenId, + setId: setId, + editionNum: editionNum + )) + + // Increment the count of global NFTs + SetAndSeries.totalSupply = SetAndSeries.totalSupply + (1 as UInt64) + + // Update the count of Editions minted in the set + self.numberEditionsMintedPerSet[setId] = editionNum + } +} + +// Admin is a special authorization resource that allows the owner to perform important NFT functions +access(all) +resource Admin { + + // Adds a new series + access(all) + fun addSeries( + seriesId: UInt32, + metadata: {String: String} + ) { pre { SetAndSeries.series[seriesId] == nil: "Cannot add Series: The Series already exists" } // Create the new Series - var newSeries <- create Series( + let newSeries <- create Series( seriesId: seriesId, metadata: metadata ) @@ -154,7 +162,9 @@ pub resource Admin { SetAndSeries.series[seriesId] <-! newSeries } - pub fun borrowSeries(seriesId: UInt32): &Series { + // Borrows a reference to an existing series + access(all) + fun borrowSeries(seriesId: UInt32): &Series { pre { SetAndSeries.series[seriesId] != nil: "Cannot borrow Series: The Series does not exist" @@ -164,8 +174,9 @@ pub resource Admin { return &SetAndSeries.series[seriesId] as &Series } - pub fun createNewAdmin(): @Admin { + // Creates a new Admin resource + access(all) + fun createNewAdmin(): @Admin { return <-create Admin() } - } diff --git a/cadence/transaction.cdc b/cadence/transaction.cdc index e9fd903..5f5f669 100644 --- a/cadence/transaction.cdc +++ b/cadence/transaction.cdc @@ -2,16 +2,18 @@ import SetAndSeries from 0x01 transaction { -let adminCheck: &SetAndSeries.Admin + let adminCheck: auth(AdminEntitlement) &SetAndSeries.Admin - prepare(acct: AuthAccount) { - self.adminCheck = acct.borrow<&SetAndSeries.Admin>(from: SetAndSeries.AdminStoragePath) - ?? panic("could not borrow admin reference") - } + prepare(acct: auth(Storage, Capabilities) &Account) { + // Borrow the admin reference with entitlement-based access + self.adminCheck = acct.capabilities.storage.borrow<&SetAndSeries.Admin>( + from: SetAndSeries.AdminStoragePath + ) ?? panic("Could not borrow admin reference") + } - execute { - self.adminCheck.addSeries(seriesId: 1, metadata: {"Series": "1"}) - log("series added") - } + execute { + // Add a new series using the borrowed admin reference + self.adminCheck.addSeries(seriesId: 1, metadata: {"Series": "1"}) + log("Series added") + } } - diff --git a/index.js b/index.js index fbcc0a3..ee3e3c8 100644 --- a/index.js +++ b/index.js @@ -23,6 +23,6 @@ export const implementingSeriesForNFTs = { transactionCode: transactionPath, transactionExplanation: transactionExplanationPath, filters: { - difficulty: "intermediate" - } + difficulty: "intermediate", + }, }; From 147612004ebeec5f467090f7570407a42a846782 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sun, 17 Nov 2024 10:32:10 +0100 Subject: [PATCH 02/10] C1.0 migration --- cadence/contract.cdc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cadence/contract.cdc b/cadence/contract.cdc index e4f4423..d45d31d 100644 --- a/cadence/contract.cdc +++ b/cadence/contract.cdc @@ -1,3 +1,5 @@ +// The below is a series structure that lays out how a series is to be created + // Variable size dictionary of SeriesData structs access(self) var seriesData: {UInt32: SeriesData} From 190e93191acd24c4e8bc60c490cba243a5edd526 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Fri, 29 Nov 2024 14:27:28 +0000 Subject: [PATCH 03/10] Flow config + GH actions --- .github/workflows/cadence_lint.yml | 30 +++++++++++++++++++++++++ .github/workflows/cadence_tests.yml | 34 +++++++++++++++++++++++++++++ flow.json | 16 ++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 .github/workflows/cadence_lint.yml create mode 100644 .github/workflows/cadence_tests.yml create mode 100644 flow.json diff --git a/.github/workflows/cadence_lint.yml b/.github/workflows/cadence_lint.yml new file mode 100644 index 0000000..58565d0 --- /dev/null +++ b/.github/workflows/cadence_lint.yml @@ -0,0 +1,30 @@ +name: Run Cadence Lint +on: push + +jobs: + run-cadence-lint: + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: 'true' + + - name: Install Flow CLI + run: | + brew update + brew install flow-cli + + - name: Initialize Flow + run: | + if [ ! -f flow.json ]; then + echo "Initializing Flow project..." + flow init + else + echo "Flow project already initialized." + fi + + - name: Run Cadence Lint + run: | + echo "Running Cadence linter on all .cdc files in the current repository" + flow cadence lint **/*.cdc diff --git a/.github/workflows/cadence_tests.yml b/.github/workflows/cadence_tests.yml new file mode 100644 index 0000000..9a51f78 --- /dev/null +++ b/.github/workflows/cadence_tests.yml @@ -0,0 +1,34 @@ +name: Run Cadence Tests +on: push + +jobs: + run-cadence-tests: + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: 'true' + + - name: Install Flow CLI + run: | + brew update + brew install flow-cli + + - name: Initialize Flow + run: | + if [ ! -f flow.json ]; then + echo "Initializing Flow project..." + flow init + else + echo "Flow project already initialized." + fi + + - name: Run Cadence Tests + run: | + if test -f "cadence/tests.cdc"; then + echo "Running Cadence tests in the current repository" + flow test cadence/tests.cdc + else + echo "No Cadence tests found. Skipping tests." + fi diff --git a/flow.json b/flow.json new file mode 100644 index 0000000..e81ec35 --- /dev/null +++ b/flow.json @@ -0,0 +1,16 @@ +{ + "contracts": { + "Counter": { + "source": "cadence/contracts/Counter.cdc", + "aliases": { + "testing": "0000000000000007" + } + } + }, + "networks": { + "emulator": "127.0.0.1:3569", + "mainnet": "access.mainnet.nodes.onflow.org:9000", + "testing": "127.0.0.1:3569", + "testnet": "access.devnet.nodes.onflow.org:9000" + } +} \ No newline at end of file From 72f26a6f69a1bf3c810ad7ecd4ec5095672aa06c Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Tue, 10 Dec 2024 21:51:16 +0400 Subject: [PATCH 04/10] Working on repo structure --- .github/workflows/cadence_lint.yml | 29 +++++- .gitignore | 4 +- cadence/contracts/Recipe.cdc | 0 cadence/tests/Recipe_test.cdc | 6 ++ .../add_series.cdc} | 2 +- emulator-account.pkey | 1 + flow.json | 96 ++++++++++++++++++- 7 files changed, 129 insertions(+), 9 deletions(-) create mode 100644 cadence/contracts/Recipe.cdc create mode 100644 cadence/tests/Recipe_test.cdc rename cadence/{transaction.cdc => transactions/add_series.cdc} (95%) create mode 100644 emulator-account.pkey diff --git a/.github/workflows/cadence_lint.yml b/.github/workflows/cadence_lint.yml index 58565d0..1100626 100644 --- a/.github/workflows/cadence_lint.yml +++ b/.github/workflows/cadence_lint.yml @@ -1,4 +1,4 @@ -name: Run Cadence Lint +name: Run Cadence Contract Compilation, Deployment, Transaction Execution, and Lint on: push jobs: @@ -9,7 +9,7 @@ jobs: uses: actions/checkout@v3 with: submodules: 'true' - + - name: Install Flow CLI run: | brew update @@ -23,8 +23,29 @@ jobs: else echo "Flow project already initialized." fi + flow dependencies install + + - name: Start Flow Emulator + run: | + echo "Starting Flow emulator in the background..." + nohup flow emulator start > emulator.log 2>&1 & + sleep 5 # Wait for the emulator to start + flow project deploy --network=emulator # Deploy the recipe contracts indicated in flow.json + + - name: Run All Transactions + run: | + echo "Running all transactions in the transactions folder..." + for file in ./cadence/transactions/*.cdc; do + echo "Running transaction: $file" + TRANSACTION_OUTPUT=$(flow transactions send "$file" --signer emulator-account) + echo "$TRANSACTION_OUTPUT" + if echo "$TRANSACTION_OUTPUT" | grep -q "Transaction Error"; then + echo "Transaction Error detected in $file, failing the action..." + exit 1 + fi + done - name: Run Cadence Lint run: | - echo "Running Cadence linter on all .cdc files in the current repository" - flow cadence lint **/*.cdc + echo "Running Cadence linter on .cdc files in the current repository" + flow cadence lint ./cadence/**/*.cdc diff --git a/.gitignore b/.gitignore index 496ee2c..b1d92af 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -.DS_Store \ No newline at end of file +.DS_Store +/imports/ +/.idea/ \ No newline at end of file diff --git a/cadence/contracts/Recipe.cdc b/cadence/contracts/Recipe.cdc new file mode 100644 index 0000000..e69de29 diff --git a/cadence/tests/Recipe_test.cdc b/cadence/tests/Recipe_test.cdc new file mode 100644 index 0000000..986e8fe --- /dev/null +++ b/cadence/tests/Recipe_test.cdc @@ -0,0 +1,6 @@ +import Test + +access(all) fun testExample() { + let array = [1, 2, 3] + Test.expect(array.length, Test.equal(3)) +} diff --git a/cadence/transaction.cdc b/cadence/transactions/add_series.cdc similarity index 95% rename from cadence/transaction.cdc rename to cadence/transactions/add_series.cdc index 5f5f669..35e670d 100644 --- a/cadence/transaction.cdc +++ b/cadence/transactions/add_series.cdc @@ -1,4 +1,4 @@ -import SetAndSeries from 0x01 +import "SetAndSeries" transaction { diff --git a/emulator-account.pkey b/emulator-account.pkey new file mode 100644 index 0000000..75611bd --- /dev/null +++ b/emulator-account.pkey @@ -0,0 +1 @@ +0xdc07d83a937644ff362b279501b7f7a3735ac91a0f3647147acf649dda804e28 \ No newline at end of file diff --git a/flow.json b/flow.json index e81ec35..3d1d7cb 100644 --- a/flow.json +++ b/flow.json @@ -1,9 +1,83 @@ { "contracts": { - "Counter": { - "source": "cadence/contracts/Counter.cdc", + "ExampleToken": { + "source": "./cadence/contracts/Recipe.cdc", "aliases": { - "testing": "0000000000000007" + "emulator": "f8d6e0586b0a20c7" + } + } + }, + "dependencies": { + "Burner": { + "source": "mainnet://f233dcee88fe0abe.Burner", + "hash": "71af18e227984cd434a3ad00bb2f3618b76482842bae920ee55662c37c8bf331", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "mainnet": "f233dcee88fe0abe", + "testnet": "9a0766d93b6608b7" + } + }, + "FlowToken": { + "source": "mainnet://1654653399040a61.FlowToken", + "hash": "cefb25fd19d9fc80ce02896267eb6157a6b0df7b1935caa8641421fe34c0e67a", + "aliases": { + "emulator": "0ae53cb6e3f42a79", + "mainnet": "1654653399040a61", + "testnet": "7e60df042a9c0868" + } + }, + "FungibleToken": { + "source": "mainnet://f233dcee88fe0abe.FungibleToken", + "hash": "050328d01c6cde307fbe14960632666848d9b7ea4fef03ca8c0bbfb0f2884068", + "aliases": { + "emulator": "ee82856bf20e2aa6", + "mainnet": "f233dcee88fe0abe", + "testnet": "9a0766d93b6608b7" + } + }, + "FungibleTokenMetadataViews": { + "source": "mainnet://f233dcee88fe0abe.FungibleTokenMetadataViews", + "hash": "dff704a6e3da83997ed48bcd244aaa3eac0733156759a37c76a58ab08863016a", + "aliases": { + "emulator": "ee82856bf20e2aa6", + "mainnet": "f233dcee88fe0abe", + "testnet": "9a0766d93b6608b7" + } + }, + "FungibleTokenSwitchboard": { + "source": "mainnet://f233dcee88fe0abe.FungibleTokenSwitchboard", + "hash": "10f94fe8803bd1c2878f2323bf26c311fb4fb2beadba9f431efdb1c7fa46c695", + "aliases": { + "emulator": "ee82856bf20e2aa6", + "mainnet": "f233dcee88fe0abe", + "testnet": "9a0766d93b6608b7" + } + }, + "MetadataViews": { + "source": "mainnet://1d7e57aa55817448.MetadataViews", + "hash": "10a239cc26e825077de6c8b424409ae173e78e8391df62750b6ba19ffd048f51", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "mainnet": "1d7e57aa55817448", + "testnet": "631e88ae7f1d7c20" + } + }, + "NonFungibleToken": { + "source": "mainnet://1d7e57aa55817448.NonFungibleToken", + "hash": "b63f10e00d1a814492822652dac7c0574428a200e4c26cb3c832c4829e2778f0", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "mainnet": "1d7e57aa55817448", + "testnet": "631e88ae7f1d7c20" + } + }, + "ViewResolver": { + "source": "mainnet://1d7e57aa55817448.ViewResolver", + "hash": "374a1994046bac9f6228b4843cb32393ef40554df9bd9907a702d098a2987bde", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "mainnet": "1d7e57aa55817448", + "testnet": "631e88ae7f1d7c20" } } }, @@ -12,5 +86,21 @@ "mainnet": "access.mainnet.nodes.onflow.org:9000", "testing": "127.0.0.1:3569", "testnet": "access.devnet.nodes.onflow.org:9000" + }, + "accounts": { + "emulator-account": { + "address": "f8d6e0586b0a20c7", + "key": { + "type": "file", + "location": "emulator-account.pkey" + } + } + }, + "deployments": { + "emulator": { + "emulator-account": [ + "ExampleToken" + ] + } } } \ No newline at end of file From ae2bdb3aa9d3433c028a20117d1f0f200a057bb9 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Tue, 10 Dec 2024 21:53:04 +0400 Subject: [PATCH 05/10] Working on repo structure --- cadence/contracts/Recipe.cdc | 188 +++++++++++++++++++++++++++++++++++ flow.json | 4 +- 2 files changed, 190 insertions(+), 2 deletions(-) diff --git a/cadence/contracts/Recipe.cdc b/cadence/contracts/Recipe.cdc index e69de29..87eea65 100644 --- a/cadence/contracts/Recipe.cdc +++ b/cadence/contracts/Recipe.cdc @@ -0,0 +1,188 @@ +import "SetAndSeries" + +access(all) contract Recipe { + // The below is a series structure that lays out how a series is to be created + + // Variable size dictionary of SeriesData structs + access(self) var seriesData: {UInt32: SeriesData} + + // Variable size dictionary of Series resources + access(self) var series: @{UInt32: Series} + + // Structure for SeriesData + access(all) + struct SeriesData { + + // Unique ID for the Series + access(all) + let seriesId: UInt32 + + // Dictionary of metadata key-value pairs + access(self) + var metadata: {String: String} + + init( + seriesId: UInt32, + metadata: {String: String} + ) { + self.seriesId = seriesId + self.metadata = metadata + + emit SeriesCreated(seriesId: self.seriesId) + } + + // Retrieves metadata of the series + access(all) + view fun getMetadata(): {String: String} { + return self.metadata + } + } + + // Resource that allows an admin to manage and mint new NFTs for a series + access(all) + resource Series { + + // Unique ID for the Series + access(all) + let seriesId: UInt32 + + // Array of NFTSets that belong to this Series + access(self) + var setIds: [UInt32] + + // Series sealed state + access(all) + var seriesSealedState: Bool + + // Set sealed state + access(self) + var setSealedState: {UInt32: Bool} + + // Current number of editions minted per Set + access(self) + var numberEditionsMintedPerSet: {UInt32: UInt32} + + init( + seriesId: UInt32, + metadata: {String: String} + ) { + self.seriesId = seriesId + self.seriesSealedState = false + self.numberEditionsMintedPerSet = {} + self.setIds = [] + self.setSealedState = {} + + SetAndSeries.seriesData[seriesId] = SeriesData( + seriesId: seriesId, + metadata: metadata + ) + } + + // Adds a new NFTSet to this series + access(all) + fun addNftSet( + setId: UInt32, + maxEditions: UInt32, + ipfsMetadataHashes: {UInt32: String}, + metadata: {String: String} + ) { + pre { + self.setIds.contains(setId) == false: "The Set has already been added to the Series." + } + + // Create the new Set struct + let newNFTSet = NFTSetData( + setId: setId, + seriesId: self.seriesId, + maxEditions: maxEditions, + ipfsMetadataHashes: ipfsMetadataHashes, + metadata: metadata + ) + + // Add the NFTSet to the array of Sets + self.setIds.append(setId) + + // Initialize the NFT edition count to zero + self.numberEditionsMintedPerSet[setId] = 0 + + // Store it in the sets mapping field + SetAndSeries.setData[setId] = newNFTSet + + emit SetCreated(seriesId: self.seriesId, setId: setId) + } + + // Mints a new NFT with a new ID and deposits it in the recipient's collection + access(all) + fun mintSetAndSeriesNFT( + recipient: &{NonFungibleToken.CollectionPublic}, + tokenId: UInt64, + setId: UInt32 + ) { + pre { + self.numberEditionsMintedPerSet[setId] != nil: "The Set does not exist." + self.numberEditionsMintedPerSet[setId]! < SetAndSeries.getSetMaxEditions(setId: setId)!: + "Set has reached maximum NFT edition capacity." + } + + // Gets the number of editions that have been minted so far in this set + let editionNum: UInt32 = self.numberEditionsMintedPerSet[setId]! + (1 as UInt32) + + // Deposit it in the recipient's account using their reference + recipient.deposit(token: <-create SetAndSeries.NFT( + tokenId: tokenId, + setId: setId, + editionNum: editionNum + )) + + // Increment the count of global NFTs + SetAndSeries.totalSupply = SetAndSeries.totalSupply + (1 as UInt64) + + // Update the count of Editions minted in the set + self.numberEditionsMintedPerSet[setId] = editionNum + } + } + + // Admin is a special authorization resource that allows the owner to perform important NFT functions + access(all) + resource Admin { + + // Adds a new series + access(all) + fun addSeries( + seriesId: UInt32, + metadata: {String: String} + ) { + pre { + SetAndSeries.series[seriesId] == nil: + "Cannot add Series: The Series already exists" + } + + // Create the new Series + let newSeries <- create Series( + seriesId: seriesId, + metadata: metadata + ) + + // Add the new Series resource to the Series dictionary in the contract + SetAndSeries.series[seriesId] <-! newSeries + } + + // Borrows a reference to an existing series + access(all) + fun borrowSeries(seriesId: UInt32): &Series { + pre { + SetAndSeries.series[seriesId] != nil: + "Cannot borrow Series: The Series does not exist" + } + + // Get a reference to the Series and return it + return &SetAndSeries.series[seriesId] as &Series + } + + // Creates a new Admin resource + access(all) + fun createNewAdmin(): @Admin { + return <-create Admin() + } + } +} \ No newline at end of file diff --git a/flow.json b/flow.json index 3d1d7cb..b549154 100644 --- a/flow.json +++ b/flow.json @@ -1,6 +1,6 @@ { "contracts": { - "ExampleToken": { + "Recipe": { "source": "./cadence/contracts/Recipe.cdc", "aliases": { "emulator": "f8d6e0586b0a20c7" @@ -99,7 +99,7 @@ "deployments": { "emulator": { "emulator-account": [ - "ExampleToken" + "Recipe" ] } } From eb3e818d6f6faaf111c2463013f0b23e23d2a6d6 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Tue, 10 Dec 2024 22:00:50 +0400 Subject: [PATCH 06/10] Add symlinks --- cadence/contract.cdc | 185 +--------------------------------------- cadence/transaction.cdc | 1 + 2 files changed, 2 insertions(+), 184 deletions(-) mode change 100644 => 120000 cadence/contract.cdc create mode 120000 cadence/transaction.cdc diff --git a/cadence/contract.cdc b/cadence/contract.cdc deleted file mode 100644 index d45d31d..0000000 --- a/cadence/contract.cdc +++ /dev/null @@ -1,184 +0,0 @@ -// The below is a series structure that lays out how a series is to be created - -// Variable size dictionary of SeriesData structs -access(self) var seriesData: {UInt32: SeriesData} - -// Variable size dictionary of Series resources -access(self) var series: @{UInt32: Series} - -// Structure for SeriesData -access(all) -struct SeriesData { - - // Unique ID for the Series - access(all) - let seriesId: UInt32 - - // Dictionary of metadata key-value pairs - access(self) - var metadata: {String: String} - - init( - seriesId: UInt32, - metadata: {String: String} - ) { - self.seriesId = seriesId - self.metadata = metadata - - emit SeriesCreated(seriesId: self.seriesId) - } - - // Retrieves metadata of the series - access(all) - view fun getMetadata(): {String: String} { - return self.metadata - } -} - -// Resource that allows an admin to manage and mint new NFTs for a series -access(all) -resource Series { - - // Unique ID for the Series - access(all) - let seriesId: UInt32 - - // Array of NFTSets that belong to this Series - access(self) - var setIds: [UInt32] - - // Series sealed state - access(all) - var seriesSealedState: Bool - - // Set sealed state - access(self) - var setSealedState: {UInt32: Bool} - - // Current number of editions minted per Set - access(self) - var numberEditionsMintedPerSet: {UInt32: UInt32} - - init( - seriesId: UInt32, - metadata: {String: String} - ) { - self.seriesId = seriesId - self.seriesSealedState = false - self.numberEditionsMintedPerSet = {} - self.setIds = [] - self.setSealedState = {} - - SetAndSeries.seriesData[seriesId] = SeriesData( - seriesId: seriesId, - metadata: metadata - ) - } - - // Adds a new NFTSet to this series - access(all) - fun addNftSet( - setId: UInt32, - maxEditions: UInt32, - ipfsMetadataHashes: {UInt32: String}, - metadata: {String: String} - ) { - pre { - self.setIds.contains(setId) == false: "The Set has already been added to the Series." - } - - // Create the new Set struct - let newNFTSet = NFTSetData( - setId: setId, - seriesId: self.seriesId, - maxEditions: maxEditions, - ipfsMetadataHashes: ipfsMetadataHashes, - metadata: metadata - ) - - // Add the NFTSet to the array of Sets - self.setIds.append(setId) - - // Initialize the NFT edition count to zero - self.numberEditionsMintedPerSet[setId] = 0 - - // Store it in the sets mapping field - SetAndSeries.setData[setId] = newNFTSet - - emit SetCreated(seriesId: self.seriesId, setId: setId) - } - - // Mints a new NFT with a new ID and deposits it in the recipient's collection - access(all) - fun mintSetAndSeriesNFT( - recipient: &{NonFungibleToken.CollectionPublic}, - tokenId: UInt64, - setId: UInt32 - ) { - pre { - self.numberEditionsMintedPerSet[setId] != nil: "The Set does not exist." - self.numberEditionsMintedPerSet[setId]! < SetAndSeries.getSetMaxEditions(setId: setId)!: - "Set has reached maximum NFT edition capacity." - } - - // Gets the number of editions that have been minted so far in this set - let editionNum: UInt32 = self.numberEditionsMintedPerSet[setId]! + (1 as UInt32) - - // Deposit it in the recipient's account using their reference - recipient.deposit(token: <-create SetAndSeries.NFT( - tokenId: tokenId, - setId: setId, - editionNum: editionNum - )) - - // Increment the count of global NFTs - SetAndSeries.totalSupply = SetAndSeries.totalSupply + (1 as UInt64) - - // Update the count of Editions minted in the set - self.numberEditionsMintedPerSet[setId] = editionNum - } -} - -// Admin is a special authorization resource that allows the owner to perform important NFT functions -access(all) -resource Admin { - - // Adds a new series - access(all) - fun addSeries( - seriesId: UInt32, - metadata: {String: String} - ) { - pre { - SetAndSeries.series[seriesId] == nil: - "Cannot add Series: The Series already exists" - } - - // Create the new Series - let newSeries <- create Series( - seriesId: seriesId, - metadata: metadata - ) - - // Add the new Series resource to the Series dictionary in the contract - SetAndSeries.series[seriesId] <-! newSeries - } - - // Borrows a reference to an existing series - access(all) - fun borrowSeries(seriesId: UInt32): &Series { - pre { - SetAndSeries.series[seriesId] != nil: - "Cannot borrow Series: The Series does not exist" - } - - // Get a reference to the Series and return it - return &SetAndSeries.series[seriesId] as &Series - } - - // Creates a new Admin resource - access(all) - fun createNewAdmin(): @Admin { - return <-create Admin() - } -} diff --git a/cadence/contract.cdc b/cadence/contract.cdc new file mode 120000 index 0000000..b64184f --- /dev/null +++ b/cadence/contract.cdc @@ -0,0 +1 @@ +./cadence/contracts/Recipe.cdc \ No newline at end of file diff --git a/cadence/transaction.cdc b/cadence/transaction.cdc new file mode 120000 index 0000000..4ef538d --- /dev/null +++ b/cadence/transaction.cdc @@ -0,0 +1 @@ +./cadence/transactions/add_series.cdc \ No newline at end of file From 2162d0f7d64885fe32ea6421cf4df6a1d4528d60 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Wed, 11 Dec 2024 01:59:12 +0400 Subject: [PATCH 07/10] Update readme --- README.md | 61 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index efd2bff..6acee9f 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ This cadence code will help you being to understand how to implement series and - [Description](#description) - [What is included in this repository?](#what-is-included-in-this-repository) - [Supported Recipe Data](#recipe-data) +- [Deploying Recipe Contracts and Running Transactions Locally (Flow Emulator)](#deploying-recipe-contracts-and-running-transactions-locally-flow-emulator) - [License](#license) ## Description @@ -19,7 +20,6 @@ The Cadence Cookbook is a collection of code examples, recipes, and tutorials de Each recipe in the Cadence Cookbook is a practical coding example that showcases a specific aspect of Cadence or use-case on Flow, including smart contract development, interaction, and best practices. By following these recipes, you can gain hands-on experience and learn how to leverage Cadence for your blockchain projects. - ### Contributing to the Cadence Cookbook Learn more about the contribution process [here](https://github.com/onflow/cadence-cookbook/blob/main/contribute.md). @@ -34,17 +34,17 @@ Recipe metadata, such as title, author, and category labels, is stored in `index ``` recipe-name/ -├── cadence/ # Cadence files for recipe examples -│ ├── contract.cdc # Contract code -│ ├── transaction.cdc # Transaction code -│ ├── tests.cdc # Tests code -├── explanations/ # Explanation files for recipe examples -│ ├── contract.txt # Contract code explanation -│ ├── transaction.txt # Transaction code explanation -│ ├── tests.txt # Tests code explanation -├── index.js # Root file for storing recipe metadata -├── README.md # This README file -└── LICENSE # License information +├── cadence/ # Cadence files for recipe examples +│ ├── contracts/Recipe.cdc # Contract code +│ ├── transactions/add_series.cdc # Transaction code +│ ├── tests/Recipe_test.cdc # Tests code +├── explanations/ # Explanation files for recipe examples +│ ├── contract.txt # Contract code explanation +│ ├── transaction.txt # Transaction code explanation +│ ├── tests.txt # Tests code explanation +├── index.js # Root file for storing recipe metadata +├── README.md # This README file +└── LICENSE # License information ``` ## Supported Recipe Data @@ -95,6 +95,43 @@ export const sampleRecipe= { transactionExplanation: transactionExplanationPath, }; ``` +## Deploying Recipe Contracts and Running Transactions Locally (Flow Emulator) + +This section explains how to deploy the recipe's contracts to the Flow emulator, run the associated transaction with sample arguments, and verify the results. + +### Prerequisites + +Before deploying and running the recipe: + +1. Install the Flow CLI. You can find installation instructions [here](https://docs.onflow.org/flow-cli/install/). +2. Ensure the Flow emulator is installed and ready to use with `flow version`. + +### Step 1: Start the Flow Emulator + +Start the Flow emulator to simulate the blockchain environment locally + +```bash +flow emulator start +``` + +### Step 2: Install Dependencies and Deploy Project Contracts + +Deploy contracts to the emulator. This will deploy all the contracts specified in the _deployments_ section of `flow.json` whether project contracts or dependencies. + +```bash +flow dependencies install +flow project deploy --network=emulator +``` + +### Step 3: Run the Transaction + +Transactions associated with the recipe are located in `./cadence/transactions`. To run a transaction, execute the following command: + +```bash +flow transactions send cadence/transactions/TRANSACTION_NAME.cdc --signer emulator-account +``` + +To verify the transaction's execution, check the emulator logs printed during the transaction for confirmation messages. You can add the `--log-level debug` flag to your Flow CLI command for more detailed output during contract deployment or transaction execution. ## License From 617ff9bce4f7cfee0cea8c89aec7f698bda6cbc4 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sun, 15 Dec 2024 16:36:02 +0400 Subject: [PATCH 08/10] Update tests --- cadence/tests/Recipe_test.cdc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cadence/tests/Recipe_test.cdc b/cadence/tests/Recipe_test.cdc index 986e8fe..42ea50e 100644 --- a/cadence/tests/Recipe_test.cdc +++ b/cadence/tests/Recipe_test.cdc @@ -4,3 +4,14 @@ access(all) fun testExample() { let array = [1, 2, 3] Test.expect(array.length, Test.equal(3)) } + +access(all) +fun setup() { + let err = Test.deployContract( + name: "ExampleToken", + path: "../contracts/Recipe.cdc", + arguments: [], + ) + + Test.expect(err, Test.beNil()) +} From f7ba0f44b3a3e0ba791810bede2ec870b6de8824 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Mon, 16 Dec 2024 02:17:44 +0400 Subject: [PATCH 09/10] Add recipe test --- cadence/tests/Recipe_test.cdc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadence/tests/Recipe_test.cdc b/cadence/tests/Recipe_test.cdc index 42ea50e..f572e4c 100644 --- a/cadence/tests/Recipe_test.cdc +++ b/cadence/tests/Recipe_test.cdc @@ -8,7 +8,7 @@ access(all) fun testExample() { access(all) fun setup() { let err = Test.deployContract( - name: "ExampleToken", + name: "Recipe", path: "../contracts/Recipe.cdc", arguments: [], ) From 158f8bb18d90786f43bf1329bfcd2e876c905867 Mon Sep 17 00:00:00 2001 From: Jerome P Date: Mon, 16 Dec 2024 10:48:57 -0800 Subject: [PATCH 10/10] Fix so that test will run --- flow.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flow.json b/flow.json index b549154..5166243 100644 --- a/flow.json +++ b/flow.json @@ -3,7 +3,8 @@ "Recipe": { "source": "./cadence/contracts/Recipe.cdc", "aliases": { - "emulator": "f8d6e0586b0a20c7" + "emulator": "f8d6e0586b0a20c7", + "testing": "0000000000000007" } } },