From 4e7389b82587fece13abacc6e8281c3d243f711c Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Thu, 14 Nov 2024 17:24:35 +0900 Subject: [PATCH 01/13] C1.0 migration --- cadence/contract.cdc | 34 +++++++++++++++++----------------- cadence/transaction.cdc | 25 +++++++++++-------------- index.js | 7 +++---- 3 files changed, 31 insertions(+), 35 deletions(-) diff --git a/cadence/contract.cdc b/cadence/contract.cdc index b28820b..35c8d65 100644 --- a/cadence/contract.cdc +++ b/cadence/contract.cdc @@ -1,9 +1,9 @@ //TopShot Contract Code Above -... +..... -access(self) var playDatas: {UInt32: Play} +access(all) var playDatas: {UInt32: Play} -pub var nextPlayID: UInt32 +access(all) var nextPlayID: UInt32 ..... @@ -16,16 +16,15 @@ pub var nextPlayID: UInt32 // its metadata. The plays are publicly accessible, so anyone can // read the metadata associated with a specific play ID // -pub struct Play { +access(all) +struct Play { // The unique ID for the Play - pub let playID: UInt32 + access(all) let playID: UInt32 // Stores all the metadata about the play as a string mapping - // This is not the long term way NFT metadata will be stored. It's a temporary - // construct while we figure out a better way to do metadata. - // - pub let metadata: {String: String} + // This is not the long-term way NFT metadata will be stored. + access(all) let metadata: {String: String} init(metadata: {String: String}) { pre { @@ -36,22 +35,21 @@ pub struct Play { } } + ..... -pub resource Admin { +access(all) +resource Admin { // createPlay creates a new Play struct // and stores it in the Plays dictionary in the TopShot smart contract // // Parameters: metadata: A dictionary mapping metadata titles to their data - // example: {"Player Name": "Kevin Durant", "Height": "7 feet"} - // (because we all know Kevin Durant is not 6'9") - // // Returns: the ID of the new Play object - // - pub fun createPlay(metadata: {String: String}): UInt32 { + access(all) + fun createPlay(metadata: {String: String}): UInt32 { // Create the new Play - var newPlay = Play(metadata: metadata) + let newPlay = Play(metadata: metadata) let newID = newPlay.playID // Increment the ID so that it isn't used again @@ -64,6 +62,8 @@ pub resource Admin { return newID } -.... } + +.... + //rest of TopShot contract below \ No newline at end of file diff --git a/cadence/transaction.cdc b/cadence/transaction.cdc index 4243b02..1a346a6 100644 --- a/cadence/transaction.cdc +++ b/cadence/transaction.cdc @@ -1,21 +1,18 @@ import TopShot from 0x01 - transaction { + let admin: auth(Admin) &TopShot.Admin - let admin: &TopShot.Admin - - prepare(acct: AuthAccount) { - - self.admin = acct.borrow<&TopShot.Admin>(from: /storage/TopShotAdmin) - ?? panic("Cant borrow admin resource") - + prepare(acct: auth(Storage) &Account) { + // Borrow the Admin resource from the specified storage path + self.admin = acct.storage.borrow<&TopShot.Admin>(from: /storage/TopShotAdmin) + ?? panic("Cannot borrow admin resource") } - - execute { - self.admin.createPlay(metadata: {"Rookie": "2004", "Player Name": "Dwight Howard"}) - self.admin.createPlay(metadata: {"Rookie": "2003", "Player Name": "Dwayne Wade"}) - log("play created") - } + execute { + // Create plays using the admin resource + self.admin.createPlay(metadata: {"Rookie": "2004", "Player Name": "Dwight Howard"}) + self.admin.createPlay(metadata: {"Rookie": "2003", "Player Name": "Dwayne Wade"}) + log("Play created") + } } diff --git a/index.js b/index.js index 46fe180..a91e239 100644 --- a/index.js +++ b/index.js @@ -9,7 +9,7 @@ const transactionPath = `${recipe}/cadence/transaction.cdc`; const smartContractExplanationPath = `${recipe}/explanations/contract.txt`; const transactionExplanationPath = `${recipe}/explanations/transaction.txt`; -export const createATopShotPlay= { +export const createATopShotPlay = { slug: recipe, title: "Create a TopShot Play", createdAt: new Date(2022, 3, 1), @@ -23,7 +23,6 @@ export const createATopShotPlay= { transactionCode: transactionPath, transactionExplanation: transactionExplanationPath, filters: { - difficulty: "intermediate" - } + difficulty: "intermediate", + }, }; - From e709c1fa605aa7a5c2d9485804506b952f37aa63 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Fri, 29 Nov 2024 14:33:36 +0000 Subject: [PATCH 02/13] GH config --- .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 33252c21f35e359a22afe0dee4d72ccc79b1b08a Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sun, 8 Dec 2024 12:29:50 +0400 Subject: [PATCH 03/13] Update repo structure --- .github/workflows/cadence_lint.yml | 29 ++++++- .gitignore | 4 +- cadence/contract.cdc | 70 +--------------- cadence/contracts/Recipe.cdc | 67 ++++++++++++++++ cadence/tests/Recipe_test.cdc | 6 ++ cadence/transaction.cdc | 19 +---- cadence/transactions/create_play.cdc | 18 +++++ flow.json | 116 ++++++++++++++++++++++++++- 8 files changed, 234 insertions(+), 95 deletions(-) mode change 100644 => 120000 cadence/contract.cdc create mode 100644 cadence/contracts/Recipe.cdc create mode 100644 cadence/tests/Recipe_test.cdc mode change 100644 => 120000 cadence/transaction.cdc create mode 100644 cadence/transactions/create_play.cdc 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/contract.cdc b/cadence/contract.cdc deleted file mode 100644 index 35c8d65..0000000 --- a/cadence/contract.cdc +++ /dev/null @@ -1,69 +0,0 @@ -//TopShot Contract Code Above -..... - -access(all) var playDatas: {UInt32: Play} - -access(all) var nextPlayID: UInt32 - -..... - -// Play is a Struct that holds metadata associated -// with a specific NBA play, like the legendary moment when -// Ray Allen hit the 3 to tie the Heat and Spurs in the 2013 finals game 6 -// or when Lance Stephenson blew in the ear of Lebron James. -// -// Moment NFTs will all reference a single play as the owner of -// its metadata. The plays are publicly accessible, so anyone can -// read the metadata associated with a specific play ID -// -access(all) -struct Play { - - // The unique ID for the Play - access(all) let playID: UInt32 - - // Stores all the metadata about the play as a string mapping - // This is not the long-term way NFT metadata will be stored. - access(all) let metadata: {String: String} - - init(metadata: {String: String}) { - pre { - metadata.length != 0: "New Play metadata cannot be empty" - } - self.playID = TopShot.nextPlayID - self.metadata = metadata - } -} - - -..... - -access(all) -resource Admin { - - // createPlay creates a new Play struct - // and stores it in the Plays dictionary in the TopShot smart contract - // - // Parameters: metadata: A dictionary mapping metadata titles to their data - // Returns: the ID of the new Play object - access(all) - fun createPlay(metadata: {String: String}): UInt32 { - // Create the new Play - let newPlay = Play(metadata: metadata) - let newID = newPlay.playID - - // Increment the ID so that it isn't used again - TopShot.nextPlayID = TopShot.nextPlayID + UInt32(1) - - emit PlayCreated(id: newPlay.playID, metadata: metadata) - - // Store it in the contract storage - TopShot.playDatas[newID] = newPlay - - return newID - } -} - -.... - -//rest of TopShot contract below \ No newline at end of file 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/contracts/Recipe.cdc b/cadence/contracts/Recipe.cdc new file mode 100644 index 0000000..c591567 --- /dev/null +++ b/cadence/contracts/Recipe.cdc @@ -0,0 +1,67 @@ +import "TopShot" + +access(all) contract Recipe { + // This is a snippet extracting the relevant logic from the TopShot contract for demonstration purposes + // TopShot Contract Code Above + + access(all) var playDatas: {UInt32: Play} + + access(all) var nextPlayID: UInt32 + + // Play is a Struct that holds metadata associated + // with a specific NBA play, like the legendary moment when + // Ray Allen hit the 3 to tie the Heat and Spurs in the 2013 finals game 6 + // or when Lance Stephenson blew in the ear of Lebron James. + // + // Moment NFTs will all reference a single play as the owner of + // its metadata. The plays are publicly accessible, so anyone can + // read the metadata associated with a specific play ID + // + access(all) struct Play { + // The unique ID for the Play + access(all) let playID: UInt32 + + // Stores all the metadata about the play as a string mapping + // This is not the long-term way NFT metadata will be stored. + access(all) let metadata: {String: String} + + init(metadata: {String: String}) { + pre { + metadata.length != 0: "New Play metadata cannot be empty" + } + self.playID = TopShot.nextPlayID + self.metadata = metadata + } + } + + + + access(all) + resource Admin { + + // createPlay creates a new Play struct + // and stores it in the Plays dictionary in the TopShot smart contract + // + // Parameters: metadata: A dictionary mapping metadata titles to their data + // Returns: the ID of the new Play object + access(all) + fun createPlay(metadata: {String: String}): UInt32 { + // Create the new Play + let newPlay = Play(metadata: metadata) + let newID = newPlay.playID + + // Increment the ID so that it isn't used again + TopShot.nextPlayID = TopShot.nextPlayID + UInt32(1) + + emit PlayCreated(id: newPlay.playID, metadata: metadata) + + // Store it in the contract storage + TopShot.playDatas[newID] = newPlay + + return newID + } + } + + // Rest of TopShot contract below + +} \ No newline at end of file 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/transaction.cdc deleted file mode 100644 index 1a346a6..0000000 --- a/cadence/transaction.cdc +++ /dev/null @@ -1,18 +0,0 @@ -import TopShot from 0x01 - -transaction { - let admin: auth(Admin) &TopShot.Admin - - prepare(acct: auth(Storage) &Account) { - // Borrow the Admin resource from the specified storage path - self.admin = acct.storage.borrow<&TopShot.Admin>(from: /storage/TopShotAdmin) - ?? panic("Cannot borrow admin resource") - } - - execute { - // Create plays using the admin resource - self.admin.createPlay(metadata: {"Rookie": "2004", "Player Name": "Dwight Howard"}) - self.admin.createPlay(metadata: {"Rookie": "2003", "Player Name": "Dwayne Wade"}) - log("Play created") - } -} diff --git a/cadence/transaction.cdc b/cadence/transaction.cdc new file mode 120000 index 0000000..ae35051 --- /dev/null +++ b/cadence/transaction.cdc @@ -0,0 +1 @@ +./cadence/transactions/create_play.cdc \ No newline at end of file diff --git a/cadence/transactions/create_play.cdc b/cadence/transactions/create_play.cdc new file mode 100644 index 0000000..8a71aa6 --- /dev/null +++ b/cadence/transactions/create_play.cdc @@ -0,0 +1,18 @@ +import "TopShot" + +transaction { + let admin: auth(Admin) &TopShot.Admin + + prepare(acct: auth(Storage) &Account) { + // Borrow the Admin resource from the specified storage path + self.admin = acct.storage.borrow<&TopShot.Admin>(from: /storage/TopShotAdmin) + ?? panic("Cannot borrow admin resource") + } + + execute { + // Create plays using the admin resource + self.admin.createPlay(metadata: {"Rookie": "2004", "Player Name": "Dwight Howard"}) + self.admin.createPlay(metadata: {"Rookie": "2003", "Player Name": "Dwayne Wade"}) + log("Play created") + } +} diff --git a/flow.json b/flow.json index e81ec35..65e216e 100644 --- a/flow.json +++ b/flow.json @@ -1,9 +1,101 @@ { "contracts": { - "Counter": { - "source": "cadence/contracts/Counter.cdc", + "Recipe": { + "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" + } + }, + "TopShot": { + "source": "mainnet://0b2a3299cc857e29.TopShot", + "hash": "804d7381441bea4ed1a0c74e91e0c7c54322b353d236af911f67783263f177f9", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "mainnet": "0b2a3299cc857e29", + "testnet": "877931736ee77cff" + } + }, + "TopShotLocking": { + "source": "mainnet://0b2a3299cc857e29.TopShotLocking", + "hash": "f9b527269a947bbbf5e120ae05ecdb38b8e5f9a6be704e73f5a2e36d33b687b1", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "mainnet": "0b2a3299cc857e29", + "testnet": "877931736ee77cff" + } + }, + "ViewResolver": { + "source": "mainnet://1d7e57aa55817448.ViewResolver", + "hash": "374a1994046bac9f6228b4843cb32393ef40554df9bd9907a702d098a2987bde", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "mainnet": "1d7e57aa55817448", + "testnet": "631e88ae7f1d7c20" } } }, @@ -12,5 +104,23 @@ "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": [ + "Recipe", + "TopShot", + "TopShotLocking" + ] + } } } \ No newline at end of file From 5944b1365e0155a8bda8e870d0400f3c2ac46c2f Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sun, 8 Dec 2024 12:31:13 +0400 Subject: [PATCH 04/13] Emulator config --- emulator-account.pkey | 1 + 1 file changed, 1 insertion(+) create mode 100644 emulator-account.pkey 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 From e2e7e34176fbc2054fd67f159c6792977a1d601a Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sun, 8 Dec 2024 12:35:35 +0400 Subject: [PATCH 05/13] Adjusting deployment errors --- cadence/contracts/Recipe.cdc | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cadence/contracts/Recipe.cdc b/cadence/contracts/Recipe.cdc index c591567..3154288 100644 --- a/cadence/contracts/Recipe.cdc +++ b/cadence/contracts/Recipe.cdc @@ -4,10 +4,16 @@ access(all) contract Recipe { // This is a snippet extracting the relevant logic from the TopShot contract for demonstration purposes // TopShot Contract Code Above - access(all) var playDatas: {UInt32: Play} - + access(all) var playDatas: {UInt32: TopShot.Play} access(all) var nextPlayID: UInt32 + access(all) event PlayCreated(id: UInt32, metadata: {String: String}) + + init() { + self.playDatas = {} + self.nextPlayID = 0 + } + // Play is a Struct that holds metadata associated // with a specific NBA play, like the legendary moment when // Ray Allen hit the 3 to tie the Heat and Spurs in the 2013 finals game 6 From b9e8ba1bee804cef0522b02bfe297ffe6c2fa7d7 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sun, 8 Dec 2024 14:39:58 +0400 Subject: [PATCH 06/13] Debugging contract linter --- cadence/contracts/Recipe.cdc | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cadence/contracts/Recipe.cdc b/cadence/contracts/Recipe.cdc index 3154288..a0e560c 100644 --- a/cadence/contracts/Recipe.cdc +++ b/cadence/contracts/Recipe.cdc @@ -4,7 +4,7 @@ access(all) contract Recipe { // This is a snippet extracting the relevant logic from the TopShot contract for demonstration purposes // TopShot Contract Code Above - access(all) var playDatas: {UInt32: TopShot.Play} + access(all) var playDatas: {UInt32: Recipe.Play} access(all) var nextPlayID: UInt32 access(all) event PlayCreated(id: UInt32, metadata: {String: String}) @@ -40,8 +40,6 @@ access(all) contract Recipe { } } - - access(all) resource Admin { @@ -57,12 +55,12 @@ access(all) contract Recipe { let newID = newPlay.playID // Increment the ID so that it isn't used again - TopShot.nextPlayID = TopShot.nextPlayID + UInt32(1) + Recipe.nextPlayID = Recipe.nextPlayID + UInt32(1) emit PlayCreated(id: newPlay.playID, metadata: metadata) // Store it in the contract storage - TopShot.playDatas[newID] = newPlay + Recipe.playDatas[newID] = newPlay return newID } From 59ea45ac4f66477af57684463583e6a1f3f29e33 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sun, 8 Dec 2024 14:42:06 +0400 Subject: [PATCH 07/13] Debugging contract linter --- cadence/transactions/create_play.cdc | 6 +++--- flow.json | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cadence/transactions/create_play.cdc b/cadence/transactions/create_play.cdc index 8a71aa6..d9cc348 100644 --- a/cadence/transactions/create_play.cdc +++ b/cadence/transactions/create_play.cdc @@ -1,11 +1,11 @@ import "TopShot" transaction { - let admin: auth(Admin) &TopShot.Admin + let admin: &TopShot.Admin - prepare(acct: auth(Storage) &Account) { + prepare(acct: AuthAccount) { // Borrow the Admin resource from the specified storage path - self.admin = acct.storage.borrow<&TopShot.Admin>(from: /storage/TopShotAdmin) + self.admin = acct.borrow<&TopShot.Admin>(from: /storage/TopShotAdmin) ?? panic("Cannot borrow admin resource") } diff --git a/flow.json b/flow.json index 65e216e..2eb5041 100644 --- a/flow.json +++ b/flow.json @@ -118,8 +118,7 @@ "emulator": { "emulator-account": [ "Recipe", - "TopShot", - "TopShotLocking" + "TopShot" ] } } From 5bed50a03d7b67dc6ec03137214fbbb7c7037e14 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sun, 8 Dec 2024 14:49:42 +0400 Subject: [PATCH 08/13] Debug tx error --- cadence/transactions/create_play.cdc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cadence/transactions/create_play.cdc b/cadence/transactions/create_play.cdc index d9cc348..c0bce3a 100644 --- a/cadence/transactions/create_play.cdc +++ b/cadence/transactions/create_play.cdc @@ -3,9 +3,9 @@ import "TopShot" transaction { let admin: &TopShot.Admin - prepare(acct: AuthAccount) { + prepare(signer: auth(Storage) &Account) { // Borrow the Admin resource from the specified storage path - self.admin = acct.borrow<&TopShot.Admin>(from: /storage/TopShotAdmin) + self.admin = signer.storage.borrow<&TopShot.Admin>(from: /storage/TopShotAdmin) ?? panic("Cannot borrow admin resource") } From 121180bef8fcfdf2ae47355dd18d7143432414a0 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sun, 8 Dec 2024 14:56:42 +0400 Subject: [PATCH 09/13] Improve explanation content --- cadence/contracts/Recipe.cdc | 1 - explanations/contract.txt | 11 ++++------- explanations/transaction.txt | 4 ++-- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/cadence/contracts/Recipe.cdc b/cadence/contracts/Recipe.cdc index a0e560c..b097edb 100644 --- a/cadence/contracts/Recipe.cdc +++ b/cadence/contracts/Recipe.cdc @@ -67,5 +67,4 @@ access(all) contract Recipe { } // Rest of TopShot contract below - } \ No newline at end of file diff --git a/explanations/contract.txt b/explanations/contract.txt index 1ea94f9..99df85e 100644 --- a/explanations/contract.txt +++ b/explanations/contract.txt @@ -1,10 +1,7 @@ -In the TopShot contract when you are creating a Play that will be included in sets as a moment you first have to start your contract off by creating a dictionary that stores all of the plays you create. Also you would create a variable called nextPlayID to make sure you aren't overlapping on ID's. +In the TopShot contract, creating a Play to be included in sets as a Moment starts with setting up a dictionary, playDatas, to store all Plays. You also define a nextPlayID variable to ensure each Play gets a unique ID without overlaps. -Then you would create a structure that would be stored in the playDatas dictionary. Here, all that is needed for the Play struct is to input a parameter for metadata which is an object containing strings. +A Play struct is then created, which stores a unique ID and a metadata object containing descriptive key-value pairs about the Play. This struct will be stored in the playDatas dictionary, ensuring organized and efficient data management. -Once these are established, you would have an admin resource that would hold the ability to create a new play. For more information on Admin resources check out the Admin resource example. - -Here you would call the createPlay function that takes in a metadata argument. You would then create the new play by passing in the metadata. You would also take the newPlay's ID so that you can use it to assign the struct to the dictionary. - -Then you would increment the ID so that it can't be used again, emit a PlayCreated event, and store the newPlay in the dictionary. +Next, an Admin resource is introduced to control the creation of new Plays securely. The createPlay function within this resource takes metadata as input, generates a new Play using the metadata, and assigns it a unique ID from nextPlayID. After storing the Play in the playDatas dictionary, the function increments nextPlayID to avoid reuse, emits a PlayCreated event to log the creation, and returns the new Play's ID. +This streamlined process ensures that Plays are uniquely identified, securely created, and properly logged, forming the foundation for Moments that reference them. \ No newline at end of file diff --git a/explanations/transaction.txt b/explanations/transaction.txt index 0c70af2..9af9da9 100644 --- a/explanations/transaction.txt +++ b/explanations/transaction.txt @@ -1,3 +1,3 @@ -To create a play, you first need to get a reference to the admin resource from the AuthAccount. +To create a Play in the TopShot contract, you first need to obtain a reference to the Admin resource. This is done by borrowing it from the signer's storage using the authorized account's storage.borrow method and specifying the path where the Admin resource is stored. If the resource is not found, the transaction will panic to prevent further execution. -Once you receive that reference you can then create a play that gets stored in the playDatas dictionary. +Once you have the Admin resource reference, you can use it to create Plays. In this transaction, the createPlay function is called twice with different metadata for each Play. Each Play is stored in the playDatas dictionary, ensuring it is properly recorded in the contract. This approach ensures secure access to the Admin resource and allows Plays to be created efficiently and logged for future reference. \ No newline at end of file From 2d2f512c644cfddb5180f17da8f905934525d6a4 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Wed, 11 Dec 2024 01:54:07 +0400 Subject: [PATCH 10/13] Update readme --- README.md | 61 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index d5eca42..930df70 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Using the TopShot contract, this is how you would create a Play to start adding - [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/create_play.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 a542844146044c28c426bb2fe443be7e0921ade4 Mon Sep 17 00:00:00 2001 From: Jerome P Date: Thu, 12 Dec 2024 16:11:10 -0800 Subject: [PATCH 11/13] Added setup method to pull in Recipe into test execution Updated flow.json to denote the deployed address for testing context --- cadence/tests/Recipe_test.cdc | 12 ++++++++++++ flow.json | 8 +++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/cadence/tests/Recipe_test.cdc b/cadence/tests/Recipe_test.cdc index 986e8fe..071ce98 100644 --- a/cadence/tests/Recipe_test.cdc +++ b/cadence/tests/Recipe_test.cdc @@ -1,4 +1,16 @@ import Test +import "test_helpers.cdc" + +access(all) +fun setup() { + let err = Test.deployContract( + name: "Recipe", + path: "../contracts/Recipe.cdc", + arguments: [], + ) + + Test.expect(err, Test.beNil()) +} access(all) fun testExample() { let array = [1, 2, 3] diff --git a/flow.json b/flow.json index 2eb5041..7841f79 100644 --- a/flow.json +++ b/flow.json @@ -1,9 +1,10 @@ { "contracts": { "Recipe": { - "source": "./cadence/contracts/Recipe.cdc", + "source": "cadence/contracts/Recipe.cdc", "aliases": { - "emulator": "f8d6e0586b0a20c7" + "emulator": "f8d6e0586b0a20c7", + "testing": "0000000000000007" } } }, @@ -77,7 +78,8 @@ "aliases": { "emulator": "f8d6e0586b0a20c7", "mainnet": "0b2a3299cc857e29", - "testnet": "877931736ee77cff" + "testnet": "877931736ee77cff", + "testing": "0000000000000007" } }, "TopShotLocking": { From 6e4af8f034755d87211c104b4433efd943e6eeb3 Mon Sep 17 00:00:00 2001 From: Jerome P Date: Thu, 12 Dec 2024 16:24:11 -0800 Subject: [PATCH 12/13] Add test setup for recipe --- cadence/tests/Recipe_test.cdc | 1 + 1 file changed, 1 insertion(+) diff --git a/cadence/tests/Recipe_test.cdc b/cadence/tests/Recipe_test.cdc index 071ce98..88ccdb4 100644 --- a/cadence/tests/Recipe_test.cdc +++ b/cadence/tests/Recipe_test.cdc @@ -3,6 +3,7 @@ import "test_helpers.cdc" access(all) fun setup() { + let err = Test.deployContract( name: "Recipe", path: "../contracts/Recipe.cdc", From e55b845f94a993445a7efe118675d910b6ad4411 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Fri, 13 Dec 2024 22:34:11 +0400 Subject: [PATCH 13/13] Debug lint --- cadence/tests/Recipe_test.cdc | 1 - 1 file changed, 1 deletion(-) diff --git a/cadence/tests/Recipe_test.cdc b/cadence/tests/Recipe_test.cdc index 88ccdb4..dfcdb44 100644 --- a/cadence/tests/Recipe_test.cdc +++ b/cadence/tests/Recipe_test.cdc @@ -1,5 +1,4 @@ import Test -import "test_helpers.cdc" access(all) fun setup() {