From 934a30c47e9fb9f4f62e7b803dbeb160d2abab94 Mon Sep 17 00:00:00 2001 From: kwt <4344285+kwtalley@users.noreply.github.com> Date: Mon, 21 Oct 2024 16:28:55 -0500 Subject: [PATCH 01/14] set provenance version to 1.20.0-rc3 --- packages/proto-build/src/main.rs | 2 +- scripts/update-and-rebuild.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/proto-build/src/main.rs b/packages/proto-build/src/main.rs index adac91ec..21a63d1c 100644 --- a/packages/proto-build/src/main.rs +++ b/packages/proto-build/src/main.rs @@ -10,7 +10,7 @@ use proto_build::{ }; /// The provenance commit or tag to be cloned and used to build the proto files -const PROVENANCE_REV: &str = "v1.19.1"; +const PROVENANCE_REV: &str = "v1.20.0-rc3"; // All paths must end with a / and either be absolute or include a ./ to reference the current // working directory. diff --git a/scripts/update-and-rebuild.sh b/scripts/update-and-rebuild.sh index efbbd8e4..5b04c3fb 100755 --- a/scripts/update-and-rebuild.sh +++ b/scripts/update-and-rebuild.sh @@ -3,7 +3,7 @@ set -euxo pipefail SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -LATEST_PROVENANCE_VERSION="v1.19.1" +LATEST_PROVENANCE_VERSION="v1.20.0-rc3" PROVENANCE_REV=${1:-$LATEST_PROVENANCE_VERSION} COMMIT=${2:-"skip"} From 1f57c4b784b232921d7ef6d22e4d246e74f93882 Mon Sep 17 00:00:00 2001 From: kwt <4344285+kwtalley@users.noreply.github.com> Date: Mon, 21 Oct 2024 16:39:13 -0500 Subject: [PATCH 02/14] update provenance git submodule --- dependencies/provenance | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies/provenance b/dependencies/provenance index d1119ab0..500d5bad 160000 --- a/dependencies/provenance +++ b/dependencies/provenance @@ -1 +1 @@ -Subproject commit d1119ab02c423d86a0f485a8f124e73511ec1b9b +Subproject commit 500d5badc776c5f2e085dc837a6c44bf40689909 From 590c71f74db6cca538b90e795e28251daf9d4ba4 Mon Sep 17 00:00:00 2001 From: kwt <4344285+kwtalley@users.noreply.github.com> Date: Tue, 22 Oct 2024 11:42:39 -0500 Subject: [PATCH 03/14] update provenance commit tag --- packages/provwasm-std/src/types/PROVENANCE_COMMIT | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/provwasm-std/src/types/PROVENANCE_COMMIT b/packages/provwasm-std/src/types/PROVENANCE_COMMIT index 8e63bed0..57abfa32 100644 --- a/packages/provwasm-std/src/types/PROVENANCE_COMMIT +++ b/packages/provwasm-std/src/types/PROVENANCE_COMMIT @@ -1 +1 @@ -v1.19.1 \ No newline at end of file +v1.20.0-rc3 \ No newline at end of file From a8ee32e7317db71f46bf8d398feab629a8ec648d Mon Sep 17 00:00:00 2001 From: kwt <4344285+kwtalley@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:02:24 -0500 Subject: [PATCH 04/14] add new generated types --- .../provwasm-std/src/types/cosmos/ics23/v1.rs | 16 ++++++++++--- .../src/types/provenance/metadata/v1/mod.rs | 23 +++++++++++++++++-- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/packages/provwasm-std/src/types/cosmos/ics23/v1.rs b/packages/provwasm-std/src/types/cosmos/ics23/v1.rs index d259ddc1..51f694d5 100644 --- a/packages/provwasm-std/src/types/cosmos/ics23/v1.rs +++ b/packages/provwasm-std/src/types/cosmos/ics23/v1.rs @@ -154,6 +154,7 @@ pub struct ProofSpec { #[prost(message, optional, tag = "2")] pub inner_spec: ::core::option::Option, /// max_depth (if > 0) is the maximum number of InnerOps allowed (mainly for fixed-depth tries) + /// the max_depth is interpreted as 128 if set to 0 #[prost(int32, tag = "3")] pub max_depth: i32, /// min_depth (if > 0) is the minimum number of InnerOps allowed (mainly for fixed-depth tries) @@ -298,11 +299,14 @@ pub enum HashOp { NoHash = 0, Sha256 = 1, Sha512 = 2, - Keccak = 3, + Keccak256 = 3, Ripemd160 = 4, /// ripemd160(sha256(x)) Bitcoin = 5, Sha512256 = 6, + Blake2b512 = 7, + Blake2s256 = 8, + Blake3 = 9, } impl HashOp { /// String value of the enum field names used in the ProtoBuf definition. @@ -314,10 +318,13 @@ impl HashOp { HashOp::NoHash => "NO_HASH", HashOp::Sha256 => "SHA256", HashOp::Sha512 => "SHA512", - HashOp::Keccak => "KECCAK", + HashOp::Keccak256 => "KECCAK256", HashOp::Ripemd160 => "RIPEMD160", HashOp::Bitcoin => "BITCOIN", HashOp::Sha512256 => "SHA512_256", + HashOp::Blake2b512 => "BLAKE2B_512", + HashOp::Blake2s256 => "BLAKE2S_256", + HashOp::Blake3 => "BLAKE3", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -326,10 +333,13 @@ impl HashOp { "NO_HASH" => Some(Self::NoHash), "SHA256" => Some(Self::Sha256), "SHA512" => Some(Self::Sha512), - "KECCAK" => Some(Self::Keccak), + "KECCAK256" => Some(Self::Keccak256), "RIPEMD160" => Some(Self::Ripemd160), "BITCOIN" => Some(Self::Bitcoin), "SHA512_256" => Some(Self::Sha512256), + "BLAKE2B_512" => Some(Self::Blake2b512), + "BLAKE2S_256" => Some(Self::Blake2s256), + "BLAKE3" => Some(Self::Blake3), _ => None, } } diff --git a/packages/provwasm-std/src/types/provenance/metadata/v1/mod.rs b/packages/provwasm-std/src/types/provenance/metadata/v1/mod.rs index 87448476..51b90b94 100644 --- a/packages/provwasm-std/src/types/provenance/metadata/v1/mod.rs +++ b/packages/provwasm-std/src/types/provenance/metadata/v1/mod.rs @@ -260,6 +260,8 @@ pub struct EventSetNetAssetValue { pub price: ::prost::alloc::string::String, #[prost(string, tag = "3")] pub source: ::prost::alloc::string::String, + #[prost(string, tag = "4")] + pub volume: ::prost::alloc::string::String, } /// Params defines the set of params for the metadata module. #[allow(clippy::derive_partial_eq_without_eq)] @@ -682,8 +684,20 @@ pub struct Scope { /// Addresses in this list are authorized to receive off-chain data associated with this scope. #[prost(string, repeated, tag = "4")] pub data_access: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, - /// An address that controls the value associated with this scope. Standard blockchain accounts and marker accounts - /// are supported for this value. This attribute may only be changed by the entity indicated once it is set. + /// The address that controls the value associated with this scope. + /// + /// The value owner is actually tracked by the bank module using a coin with the denom "nft/". + /// The value owner can be changed using WriteScope or anything that transfers funds, e.g. MsgSend. + /// + /// During WriteScope: + /// - If this field is empty, it indicates that there should not be a change to the value owner. + /// I.e. Once a scope has a value owner, it will always have one (until it's deleted). + /// - If this field has a value, the existing value owner will be looked up, and + /// - If there's already an existing value owner, they must be a signer, + /// and the coin will be transferred to the new value owner. + /// - If there isn't yet a value owner, the coin will be minted and sent to the new value owner. + /// If the scope already exists, the owners must be signers (just like changing other fields). + /// If it's a new scope, there's no special signer limitations related to the value owner. #[prost(string, tag = "5")] pub value_owner_address: ::prost::alloc::string::String, /// Whether all parties in this scope and its sessions must be present in this scope's owners field. @@ -866,6 +880,11 @@ pub struct NetAssetValue { /// updated_block_height is the block height of last update #[prost(uint64, tag = "2")] pub updated_block_height: u64, + /// volume is the number of scope instances that were purchased for the price + /// Typically this will be null (equivalent to one) or one. The only reason this would be more than + /// one is for cases where the precision of the price denom is insufficient to represent the actual price + #[prost(uint64, tag = "3")] + pub volume: u64, } /// A set of types for inputs on a record (of fact) #[derive( From 5e2dd4049a00b81f1b7f9105982d3fe23b218a19 Mon Sep 17 00:00:00 2001 From: kwt <4344285+kwtalley@users.noreply.github.com> Date: Tue, 22 Oct 2024 15:43:27 -0500 Subject: [PATCH 05/14] update readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index de4b3d81..ffe6596c 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ The following table shows provwasm version compatibility for smart contract deve | provwasm | wasmd | cosmos | provenance | module support | |----------|---------|---------|-------------------|---------------------------------------------------------------------| +| v2.4.1 | v0.52.X | v0.50.X | v1.20.X | all Provenance and third-party | | v2.4.0 | v0.51.X | v0.50.X | v1.19.X | all Provenance and third-party | | v2.3.0 | v0.51.X | v0.50.X | v1.19.X | all Provenance and most built-in third-party | | v2.2.0 | v0.30.X | v0.46.X | v1.18.X | attribute,exchange,hold,marker,metadata,msgfees,name,reward,trigger | From 16c6fd3000fe4beaa59339e44489c0d5aed0b556 Mon Sep 17 00:00:00 2001 From: kwt <4344285+kwtalley@users.noreply.github.com> Date: Mon, 28 Oct 2024 14:23:08 -0500 Subject: [PATCH 06/14] bump provenance to 1.20.0 --- packages/proto-build/src/main.rs | 2 +- scripts/update-and-rebuild.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/proto-build/src/main.rs b/packages/proto-build/src/main.rs index 21a63d1c..a50725f2 100644 --- a/packages/proto-build/src/main.rs +++ b/packages/proto-build/src/main.rs @@ -10,7 +10,7 @@ use proto_build::{ }; /// The provenance commit or tag to be cloned and used to build the proto files -const PROVENANCE_REV: &str = "v1.20.0-rc3"; +const PROVENANCE_REV: &str = "v1.20.0"; // All paths must end with a / and either be absolute or include a ./ to reference the current // working directory. diff --git a/scripts/update-and-rebuild.sh b/scripts/update-and-rebuild.sh index 5b04c3fb..9335a014 100755 --- a/scripts/update-and-rebuild.sh +++ b/scripts/update-and-rebuild.sh @@ -3,7 +3,7 @@ set -euxo pipefail SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -LATEST_PROVENANCE_VERSION="v1.20.0-rc3" +LATEST_PROVENANCE_VERSION="v1.20.0" PROVENANCE_REV=${1:-$LATEST_PROVENANCE_VERSION} COMMIT=${2:-"skip"} From feb6baabbcb257dee9954cbc1ed5ed043372b689 Mon Sep 17 00:00:00 2001 From: kwt <4344285+kwtalley@users.noreply.github.com> Date: Mon, 28 Oct 2024 14:51:33 -0500 Subject: [PATCH 07/14] update provenance submodule and commit hash --- dependencies/provenance | 2 +- packages/provwasm-std/src/types/PROVENANCE_COMMIT | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dependencies/provenance b/dependencies/provenance index 500d5bad..f7e5177a 160000 --- a/dependencies/provenance +++ b/dependencies/provenance @@ -1 +1 @@ -Subproject commit 500d5badc776c5f2e085dc837a6c44bf40689909 +Subproject commit f7e5177a16130d930bcda7c1125cb04e935e84a7 diff --git a/packages/provwasm-std/src/types/PROVENANCE_COMMIT b/packages/provwasm-std/src/types/PROVENANCE_COMMIT index 57abfa32..b8cceb94 100644 --- a/packages/provwasm-std/src/types/PROVENANCE_COMMIT +++ b/packages/provwasm-std/src/types/PROVENANCE_COMMIT @@ -1 +1 @@ -v1.20.0-rc3 \ No newline at end of file +v1.20.0 \ No newline at end of file From 047ee2bed899a5bca97cf13a8dccf072adb64fa9 Mon Sep 17 00:00:00 2001 From: kwt <4344285+kwtalley@users.noreply.github.com> Date: Mon, 28 Oct 2024 15:03:12 -0500 Subject: [PATCH 08/14] bump cosmwasm to 2.1.4 --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index abb342a1..51bbc9af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,8 +26,8 @@ license = "Apache-2.0" [workspace.dependencies] ### CosmWasm -cosmwasm-schema = { version = "2.0.6" } -cosmwasm-std = { version = "2.1.3", default-features = false, features = ["cosmwasm_2_1", "stargate", "std"] } +cosmwasm-schema = { version = "2.1.4" } +cosmwasm-std = { version = "2.1.4", default-features = false, features = ["cosmwasm_2_1", "stargate", "std"] } cw-storage-plus = { version = "2.0.0" } ### ProvWasm From 847fdb5b2e7457e8d82146e49bc0939014827075 Mon Sep 17 00:00:00 2001 From: kwt <4344285+kwtalley@users.noreply.github.com> Date: Mon, 28 Oct 2024 15:26:45 -0500 Subject: [PATCH 09/14] set project versions --- Cargo.toml | 8 ++++---- packages/provwasm-common/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 51bbc9af..06497f18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ members = [ ] [workspace.package] -version = "2.4.0" +version = "2.5.0" repository = "https://github.com/provenance-io/provwasm" edition = "2021" license = "Apache-2.0" @@ -31,11 +31,11 @@ cosmwasm-std = { version = "2.1.4", default-features = false, features = ["cosmw cw-storage-plus = { version = "2.0.0" } ### ProvWasm -provwasm-common = { version = "0.1.1", path = "packages/provwasm-common" } -provwasm-mocks = { version = "2.4.0", path = "packages/provwasm-mocks" } +provwasm-common = { version = "0.2.0", path = "packages/provwasm-common" } +provwasm-mocks = { version = "2.5.0", path = "packages/provwasm-mocks" } provwasm-proc-macro = { version = "0.2.0", path = "packages/provwasm-proc-macro" } provwasm-proto-build = { version = "0.1.0", path = "packages/proto-build" } -provwasm-std = { version = "2.4.0", path = "packages/provwasm-std" } +provwasm-std = { version = "2.5.0", path = "packages/provwasm-std" } base64 = "0.22.0" chrono = { version = "0.4.37", default-features = false } diff --git a/packages/provwasm-common/Cargo.toml b/packages/provwasm-common/Cargo.toml index 3a60309a..54383b2a 100644 --- a/packages/provwasm-common/Cargo.toml +++ b/packages/provwasm-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "provwasm-common" -version = "0.1.1" +version = "0.2.0" repository = { workspace = true } edition = { workspace = true } license = { workspace = true } From 4ec8c0b8a3f0ff820de5631ed4f6cbfd851cf525 Mon Sep 17 00:00:00 2001 From: kwt <4344285+kwtalley@users.noreply.github.com> Date: Mon, 28 Oct 2024 15:35:19 -0500 Subject: [PATCH 10/14] fix readme version table --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ffe6596c..d0ea632e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The following table shows provwasm version compatibility for smart contract deve | provwasm | wasmd | cosmos | provenance | module support | |----------|---------|---------|-------------------|---------------------------------------------------------------------| -| v2.4.1 | v0.52.X | v0.50.X | v1.20.X | all Provenance and third-party | +| v2.5.0 | v0.52.X | v0.50.X | v1.20.X | all Provenance and third-party | | v2.4.0 | v0.51.X | v0.50.X | v1.19.X | all Provenance and third-party | | v2.3.0 | v0.51.X | v0.50.X | v1.19.X | all Provenance and most built-in third-party | | v2.2.0 | v0.30.X | v0.46.X | v1.18.X | attribute,exchange,hold,marker,metadata,msgfees,name,reward,trigger | From 2e5e4aa9e5fa58829b0daa24c48bc0ed0a6a4cc8 Mon Sep 17 00:00:00 2001 From: kwt <4344285+kwtalley@users.noreply.github.com> Date: Mon, 28 Oct 2024 15:41:24 -0500 Subject: [PATCH 11/14] update changelogs --- CHANGELOG.md | 9 +++++++-- RELEASE_CHANGELOG.md | 7 +++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b59d38c3..27565b0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,17 @@ ## Unreleased changes +* Update to Provenance 1.20.0 ([#163](https://github.com/provenance-io/provwasm/issues/163)) +* Update to cosmwasm 2.1.4 ([#163](https://github.com/provenance-io/provwasm/issues/163)) + +## Releases + +### [v2.4.0](https://github.com/provenance-io/provwasm/tree/v2.4.0) + * migrate to GRPC queries ([#157](https://github.com/provenance-io/provwasm/issues/157)) * add `MetadataAddress` type for encoding/decoding metadata addresses ([#161](https://github.com/provenance-io/provwasm/pull/161)) -## Releases - ### [v2.3.0](https://github.com/provenance-io/provwasm/tree/v2.3.0) * test tube integration tests ([#150](https://github.com/provenance-io/provwasm/pull/150)) diff --git a/RELEASE_CHANGELOG.md b/RELEASE_CHANGELOG.md index 64a8718f..741f40fd 100644 --- a/RELEASE_CHANGELOG.md +++ b/RELEASE_CHANGELOG.md @@ -1,5 +1,4 @@ -### [v2.4.0](https://github.com/provenance-io/provwasm/tree/v2.4.0) +### [v2.5.0](https://github.com/provenance-io/provwasm/tree/v2.5.0) -* migrate to GRPC queries ([#157](https://github.com/provenance-io/provwasm/issues/157)) -* add `MetadataAddress` type for encoding/decoding metadata - addresses ([#161](https://github.com/provenance-io/provwasm/pull/161)) \ No newline at end of file +* Update to Provenance 1.20.0 ([#163](https://github.com/provenance-io/provwasm/issues/163)) +* Update to cosmwasm 2.1.4 ([#163](https://github.com/provenance-io/provwasm/issues/163)) From a30c6aaa18710f8cc241f378097ceaa78e9e7d1a Mon Sep 17 00:00:00 2001 From: kwt <4344285+kwtalley@users.noreply.github.com> Date: Mon, 28 Oct 2024 16:13:31 -0500 Subject: [PATCH 12/14] set github workflows provenance version to 1.20.0 --- .github/workflows/release.yml | 14 +++++++------- .github/workflows/test.yml | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 55b7744d..328810bb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,7 +37,7 @@ jobs: uses: provenance-io/provenance-testing-action@v1.3.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} - provenance_version: "v1.19.0-rc5" + provenance_version: "v1.20.0" test_script: "./scripts/gh-action-test/tutorial_test.sh" test_attrs_smart_contract: @@ -57,7 +57,7 @@ jobs: uses: provenance-io/provenance-testing-action@v1.3.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} - provenance_version: "v1.19.0-rc5" + provenance_version: "v1.20.0" smart_contract_action_version: "latest" test_script: "./scripts/gh-action-test/attrs_test.sh" @@ -78,7 +78,7 @@ jobs: uses: provenance-io/provenance-testing-action@v1.3.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} - provenance_version: "v1.19.0-rc5" + provenance_version: "v1.20.0" smart_contract_action_version: "latest" test_script: "./scripts/gh-action-test/marker_test.sh" @@ -99,7 +99,7 @@ jobs: uses: provenance-io/provenance-testing-action@v1.3.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} - provenance_version: "v1.19.0-rc5" + provenance_version: "v1.20.0" smart_contract_action_version: "latest" test_script: "./scripts/gh-action-test/msgfees_test.sh" @@ -120,7 +120,7 @@ jobs: uses: provenance-io/provenance-testing-action@v1.3.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} - provenance_version: "v1.19.0-rc5" + provenance_version: "v1.20.0" smart_contract_action_version: "latest" test_script: "./scripts/gh-action-test/name_test.sh" @@ -141,7 +141,7 @@ jobs: # uses: provenance-io/provenance-testing-action@v1.3.0 # with: # github_token: ${{ secrets.GITHUB_TOKEN }} - # provenance_version: "v1.19.0-rc5" + # provenance_version: "v1.20.0" # smart_contract_action_version: "latest" # test_script: "./scripts/gh-action-test/scope_test.sh" @@ -162,7 +162,7 @@ jobs: uses: provenance-io/provenance-testing-action@v1.3.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} - provenance_version: "v1.19.0-rc5" + provenance_version: "v1.20.0" smart_contract_action_version: "latest" test_script: "./scripts/gh-action-test/trigger_test.sh" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 139664ce..14d4bdd1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,7 +35,7 @@ jobs: uses: provenance-io/provenance-testing-action@v1.3.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} - provenance_version: "v1.19.1" + provenance_version: "v1.20.0" test_script: "./scripts/gh-action-test/tutorial_test.sh" test_attrs_smart_contract: @@ -55,7 +55,7 @@ jobs: uses: provenance-io/provenance-testing-action@v1.3.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} - provenance_version: "v1.19.1" + provenance_version: "v1.20.0" smart_contract_action_version: "latest" test_script: "./scripts/gh-action-test/attrs_test.sh" @@ -76,7 +76,7 @@ jobs: uses: provenance-io/provenance-testing-action@v1.3.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} - provenance_version: "v1.19.1" + provenance_version: "v1.20.0" smart_contract_action_version: "latest" test_script: "./scripts/gh-action-test/marker_test.sh" @@ -97,7 +97,7 @@ jobs: uses: provenance-io/provenance-testing-action@v1.3.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} - provenance_version: "v1.19.1" + provenance_version: "v1.20.0" smart_contract_action_version: "latest" test_script: "./scripts/gh-action-test/msgfees_test.sh" @@ -118,7 +118,7 @@ jobs: uses: provenance-io/provenance-testing-action@v1.3.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} - provenance_version: "v1.19.1" + provenance_version: "v1.20.0" smart_contract_action_version: "latest" test_script: "./scripts/gh-action-test/name_test.sh" @@ -139,7 +139,7 @@ jobs: uses: provenance-io/provenance-testing-action@v1.3.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} - provenance_version: "v1.19.1" + provenance_version: "v1.20.0" smart_contract_action_version: "latest" test_script: "./scripts/gh-action-test/scope_test.sh" @@ -160,6 +160,6 @@ jobs: uses: provenance-io/provenance-testing-action@v1.3.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} - provenance_version: "v1.19.1" + provenance_version: "v1.20.0" smart_contract_action_version: "latest" test_script: "./scripts/gh-action-test/trigger_test.sh" From 82d6cf467e0e7023dcb4c34a7456abd163ef8770 Mon Sep 17 00:00:00 2001 From: kwt <4344285+kwtalley@users.noreply.github.com> Date: Tue, 29 Oct 2024 09:24:59 -0500 Subject: [PATCH 13/14] fix tutorial contract unit tests --- contracts/tutorial/src/contract.rs | 554 +++++++++++++++-------------- 1 file changed, 287 insertions(+), 267 deletions(-) diff --git a/contracts/tutorial/src/contract.rs b/contracts/tutorial/src/contract.rs index 9d73f57f..33cb8167 100644 --- a/contracts/tutorial/src/contract.rs +++ b/contracts/tutorial/src/contract.rs @@ -172,270 +172,290 @@ fn try_purchase( .add_attribute("purchase_id", id) .add_attribute("purchase_time", env.block.time.to_string())) } -// -// #[cfg(test)] -// mod tests { -// use super::*; -// use cosmwasm_std::testing::{mock_env, mock_info}; -// use cosmwasm_std::{from_json, Addr}; -// use provwasm_mocks::mock_dependencies; -// use provwasm_std::{NameMsgParams, ProvenanceMsgParams}; -// -// #[test] -// fn valid_init() { -// // Create mocks -// let mut deps = mock_dependencies(&[]); -// -// // Create valid config state -// let res = instantiate( -// deps.as_mut(), -// mock_env(), -// mock_info("feebucket", &[]), -// InitMsg { -// contract_name: "tutorial.sc.pb".into(), -// purchase_denom: "purchasecoin".into(), -// merchant_address: "merchant".into(), -// fee_percent: Decimal::percent(10), -// }, -// ) -// .unwrap(); -// -// // Ensure a message was created to bind the name to the contract address. -// assert_eq!(res.messages.len(), 1); -// match &res.messages[0].msg { -// CosmosMsg::Custom(msg) => match &msg.params { -// ProvenanceMsgParams::Name(p) => match &p { -// NameMsgParams::BindName { name, .. } => assert_eq!(name, "tutorial.sc.pb"), -// _ => panic!("unexpected name params"), -// }, -// _ => panic!("unexpected provenance params"), -// }, -// _ => panic!("unexpected cosmos message"), -// } -// } -// -// #[test] -// fn invalid_merchant_init() { -// // Create mocks -// let mut deps = mock_dependencies(&[]); -// -// // Create an invalid init message -// let err = instantiate( -// deps.as_mut(), -// mock_env(), -// mock_info("merchant", &[]), -// InitMsg { -// contract_name: "tutorial.sc.pb".into(), -// purchase_denom: "purchasecoin".into(), -// merchant_address: "merchant".into(), -// fee_percent: Decimal::percent(10), -// }, -// ) -// .unwrap_err(); -// -// // Ensure the expected error was returned. -// match err { -// StdError::GenericErr { msg, .. } => { -// assert_eq!(msg, "merchant address can't be the fee collection address") -// } -// _ => panic!("unexpected init error"), -// } -// } -// -// #[test] -// fn invalid_fee_percent_init() { -// // Create mocks -// let mut deps = mock_dependencies(&[]); -// -// // Create an invalid init message. -// let err = instantiate( -// deps.as_mut(), -// mock_env(), -// mock_info("feebucket", &[]), -// InitMsg { -// contract_name: "tutorial.sc.pb".into(), -// purchase_denom: "purchasecoin".into(), -// merchant_address: "merchant".into(), -// fee_percent: Decimal::percent(37), // error: > 25% -// }, -// ) -// .unwrap_err(); -// -// // Ensure the expected error was returned -// match err { -// StdError::GenericErr { msg, .. } => { -// assert_eq!(msg, "fee percent must be > 0.0 and <= 0.25") -// } -// _ => panic!("unexpected init error"), -// } -// } -// -// #[test] -// fn query_test() { -// // Create mocks -// let mut deps = mock_dependencies(&[]); -// -// // Create config state -// instantiate( -// deps.as_mut(), -// mock_env(), -// mock_info("feebucket", &[]), -// InitMsg { -// contract_name: "tutorial.sc.pb".into(), -// purchase_denom: "purchasecoin".into(), -// merchant_address: "merchant".into(), -// fee_percent: Decimal::percent(10), -// }, -// ) -// .unwrap(); // Panics on error -// -// // Call the smart contract query function to get stored state. -// let bin = query(deps.as_ref(), mock_env(), QueryMsg::QueryRequest {}).unwrap(); -// let resp: State = from_json(&bin).unwrap(); -// -// // Ensure the expected init fields were properly stored. -// assert_eq!(resp.merchant_address, Addr::unchecked("merchant")); -// assert_eq!(resp.purchase_denom, "purchasecoin"); -// assert_eq!(resp.fee_collection_address, Addr::unchecked("feebucket")); -// assert_eq!(resp.fee_percent, Decimal::percent(10)); -// } -// -// #[test] -// fn handle_valid_purchase() { -// // Create mocks -// let mut deps = mock_dependencies(&[]); -// -// // Create config state -// instantiate( -// deps.as_mut(), -// mock_env(), -// mock_info("feebucket", &[]), -// InitMsg { -// contract_name: "tutorial.sc.pb".into(), -// purchase_denom: "purchasecoin".into(), -// merchant_address: "merchant".into(), -// fee_percent: Decimal::percent(10), -// }, -// ) -// .unwrap(); -// -// // Send a valid purchase message of 100purchasecoin -// let res = execute( -// deps.as_mut(), -// mock_env(), -// mock_info("consumer", &[coin(100, "purchasecoin")]), -// ExecuteMsg::Purchase { -// id: "a7918172-ac09-43f6-bc4b-7ac2fbad17e9".into(), -// }, -// ) -// .unwrap(); -// -// // Ensure we have the merchant transfer and fee collection bank messages -// assert_eq!(res.messages.len(), 2); -// -// // Ensure we got the proper bank transfer values. -// // 10% fees on 100 purchasecoin => 90 purchasecoin for the merchant and 10 purchasecoin for the fee bucket. -// let expected_transfer = coin(90, "purchasecoin"); -// let expected_fees = coin(10, "purchasecoin"); -// res.messages.into_iter().for_each(|msg| match msg.msg { -// CosmosMsg::Bank(BankMsg::Send { -// amount, to_address, .. -// }) => { -// assert_eq!(amount.len(), 1); -// if to_address == "merchant" { -// assert_eq!(amount[0], expected_transfer) -// } else if to_address == "feebucket" { -// assert_eq!(amount[0], expected_fees) -// } else { -// panic!("unexpected to_address in bank message") -// } -// } -// _ => panic!("unexpected message type"), -// }); -// -// // Ensure we got the purchase ID event attribute value -// let expected_purchase_id = "a7918172-ac09-43f6-bc4b-7ac2fbad17e9"; -// res.attributes.into_iter().for_each(|atr| { -// if atr.key == "purchase_id" { -// assert_eq!(atr.value, expected_purchase_id) -// } -// }) -// } -// -// #[test] -// fn handle_invalid_funds() { -// // Create mocks -// let mut deps = mock_dependencies(&[]); -// -// // Create config state -// instantiate( -// deps.as_mut(), -// mock_env(), -// mock_info("feebucket", &[]), -// InitMsg { -// contract_name: "tutorial.sc.pb".into(), -// purchase_denom: "purchasecoin".into(), -// merchant_address: "merchant".into(), -// fee_percent: Decimal::percent(10), -// }, -// ) -// .unwrap(); -// -// // Don't send any funds -// let err = execute( -// deps.as_mut(), -// mock_env(), -// mock_info("consumer", &[]), -// ExecuteMsg::Purchase { -// id: "a7918172-ac09-43f6-bc4b-7ac2fbad17e9".into(), -// }, -// ) -// .unwrap_err(); -// -// // Ensure the expected error was returned. -// match err { -// ContractError::Std(StdError::GenericErr { msg, .. }) => { -// assert_eq!(msg, "no purchase funds sent") -// } -// _ => panic!("unexpected handle error"), -// } -// -// // Send zero amount for a valid denom -// let err = execute( -// deps.as_mut(), -// mock_env(), -// mock_info("consumer", &[coin(0, "purchasecoin")]), -// ExecuteMsg::Purchase { -// id: "a7918172-ac09-43f6-bc4b-7ac2fbad17e9".into(), -// }, -// ) -// .unwrap_err(); -// -// // Ensure the expected error was returned. -// match err { -// ContractError::Std(StdError::GenericErr { msg, .. }) => { -// assert_eq!(msg, "invalid purchase funds: 0purchasecoin") -// } -// _ => panic!("unexpected handle error"), -// } -// -// // Send invalid denom -// let err = execute( -// deps.as_mut(), -// mock_env(), -// mock_info("consumer", &[coin(100, "fakecoin")]), -// ExecuteMsg::Purchase { -// id: "a7918172-ac09-43f6-bc4b-7ac2fbad17e9".into(), -// }, -// ) -// .unwrap_err(); -// -// // Ensure the expected error was returned. -// match err { -// ContractError::Std(StdError::GenericErr { msg, .. }) => { -// assert_eq!(msg, "invalid purchase funds: 100fakecoin") -// } -// _ => panic!("unexpected handle error"), -// } -// } -// } + +#[cfg(test)] +mod tests { + use super::*; + use cosmwasm_std::testing::{message_info, mock_env}; + use cosmwasm_std::{from_json, Addr, AnyMsg}; + use provwasm_mocks::mock_provenance_dependencies; + + #[test] + fn valid_init() { + // Create mocks + let mut deps = mock_provenance_dependencies(); + let env = mock_env(); + let contract_address = env.contract.address.to_string(); + let feebucket_addr = deps.api.addr_make("feebucket"); + let merchant_addr = deps.api.addr_make("merchant"); + + // Create valid config state + let res = instantiate( + deps.as_mut(), + env, + message_info(&feebucket_addr, &[]), + InitMsg { + contract_name: "tutorial.sc.pb".into(), + purchase_denom: "purchasecoin".into(), + merchant_address: merchant_addr.to_string(), + fee_percent: Decimal::percent(10), + }, + ) + .unwrap(); + + // Ensure a message was created to bind the name to the contract address. + assert_eq!(res.messages.len(), 1); + match &res.messages[0].msg { + CosmosMsg::Any(AnyMsg { type_url, value }) => { + let expected: Binary = MsgBindNameRequest { + parent: Some(NameRecord { + name: "sc.pb".to_string(), + address: contract_address.clone(), + restricted: true, + }), + record: Some(NameRecord { + name: "tutorial".to_string(), + address: contract_address, + restricted: true, + }), + } + .into(); + + assert_eq!(type_url, "/provenance.name.v1.MsgBindNameRequest"); + assert_eq!(value, &expected) + } + _ => panic!("unexpected cosmos message"), + } + } + + #[test] + fn invalid_merchant_init() { + // Create mocks + let mut deps = mock_provenance_dependencies(); + + // Create an invalid init message + let err = instantiate( + deps.as_mut(), + mock_env(), + message_info(&Addr::unchecked("merchant"), &[]), + InitMsg { + contract_name: "tutorial.sc.pb".into(), + purchase_denom: "purchasecoin".into(), + merchant_address: "merchant".into(), + fee_percent: Decimal::percent(10), + }, + ) + .unwrap_err(); + + // Ensure the expected error was returned. + match err { + StdError::GenericErr { msg, .. } => { + assert_eq!(msg, "merchant address can't be the fee collection address") + } + _ => panic!("unexpected init error"), + } + } + + #[test] + fn invalid_fee_percent_init() { + // Create mocks + let mut deps = mock_provenance_dependencies(); + + // Create an invalid init message. + let err = instantiate( + deps.as_mut(), + mock_env(), + message_info(&Addr::unchecked("feebucket"), &[]), + InitMsg { + contract_name: "tutorial.sc.pb".into(), + purchase_denom: "purchasecoin".into(), + merchant_address: "merchant".into(), + fee_percent: Decimal::percent(37), // error: > 25% + }, + ) + .unwrap_err(); + + // Ensure the expected error was returned + match err { + StdError::GenericErr { msg, .. } => { + assert_eq!(msg, "fee percent must be > 0.0 and <= 0.25") + } + _ => panic!("unexpected init error"), + } + } + + #[test] + fn query_test() { + // Create mocks + let mut deps = mock_provenance_dependencies(); + let feebucket_addr = deps.api.addr_make("feebucket"); + let merchant_addr = deps.api.addr_make("merchant"); + + // Create config state + instantiate( + deps.as_mut(), + mock_env(), + message_info(&feebucket_addr, &[]), + InitMsg { + contract_name: "tutorial.sc.pb".into(), + purchase_denom: "purchasecoin".into(), + merchant_address: merchant_addr.to_string(), + fee_percent: Decimal::percent(10), + }, + ) + .unwrap(); // Panics on error + + // Call the smart contract query function to get stored state. + let bin = query(deps.as_ref(), mock_env(), QueryMsg::QueryRequest {}).unwrap(); + let resp: State = from_json(bin).unwrap(); + + // Ensure the expected init fields were properly stored. + assert_eq!(resp.merchant_address, merchant_addr); + assert_eq!(resp.purchase_denom, "purchasecoin"); + assert_eq!(resp.fee_collection_address, feebucket_addr); + assert_eq!(resp.fee_percent, Decimal::percent(10)); + } + + #[test] + fn handle_valid_purchase() { + // Create mocks + let mut deps = mock_provenance_dependencies(); + let feebucket_addr = deps.api.addr_make("feebucket"); + let merchant_addr = deps.api.addr_make("merchant"); + + // Create config state + instantiate( + deps.as_mut(), + mock_env(), + message_info(&feebucket_addr, &[]), + InitMsg { + contract_name: "tutorial.sc.pb".into(), + purchase_denom: "purchasecoin".into(), + merchant_address: merchant_addr.to_string(), + fee_percent: Decimal::percent(10), + }, + ) + .unwrap(); + + // Send a valid purchase message of 100purchasecoin + let res = execute( + deps.as_mut(), + mock_env(), + message_info(&Addr::unchecked("consumer"), &[coin(100, "purchasecoin")]), + ExecuteMsg::Purchase { + id: "a7918172-ac09-43f6-bc4b-7ac2fbad17e9".into(), + }, + ) + .unwrap(); + + // Ensure we have the merchant transfer and fee collection bank messages + assert_eq!(res.messages.len(), 2); + + // Ensure we got the proper bank transfer values. + // 10% fees on 100 purchasecoin => 90 purchasecoin for the merchant and 10 purchasecoin for the fee bucket. + let expected_transfer = coin(90, "purchasecoin"); + let expected_fees = coin(10, "purchasecoin"); + res.messages.into_iter().for_each(|msg| match msg.msg { + CosmosMsg::Bank(BankMsg::Send { + amount, to_address, .. + }) => { + assert_eq!(amount.len(), 1); + if to_address == merchant_addr.to_string() { + assert_eq!(amount[0], expected_transfer) + } else if to_address == feebucket_addr.to_string() { + assert_eq!(amount[0], expected_fees) + } else { + panic!("unexpected to_address in bank message") + } + } + _ => panic!("unexpected message type"), + }); + + // Ensure we got the purchase ID event attribute value + let expected_purchase_id = "a7918172-ac09-43f6-bc4b-7ac2fbad17e9"; + res.attributes.into_iter().for_each(|atr| { + if atr.key == "purchase_id" { + assert_eq!(atr.value, expected_purchase_id) + } + }) + } + + #[test] + fn handle_invalid_funds() { + // Create mocks + let mut deps = mock_provenance_dependencies(); + let feebucket_addr = deps.api.addr_make("feebucket"); + let merchant_addr = deps.api.addr_make("merchant"); + + // Create config state + instantiate( + deps.as_mut(), + mock_env(), + message_info(&feebucket_addr, &[]), + InitMsg { + contract_name: "tutorial.sc.pb".into(), + purchase_denom: "purchasecoin".into(), + merchant_address: merchant_addr.to_string(), + fee_percent: Decimal::percent(10), + }, + ) + .unwrap(); + + // Don't send any funds + let err = execute( + deps.as_mut(), + mock_env(), + message_info(&Addr::unchecked("consumer"), &[]), + ExecuteMsg::Purchase { + id: "a7918172-ac09-43f6-bc4b-7ac2fbad17e9".into(), + }, + ) + .unwrap_err(); + + // Ensure the expected error was returned. + match err { + ContractError::Std(StdError::GenericErr { msg, .. }) => { + assert_eq!(msg, "no purchase funds sent") + } + _ => panic!("unexpected handle error"), + } + + // Send zero amount for a valid denom + let err = execute( + deps.as_mut(), + mock_env(), + message_info(&Addr::unchecked("consumer"), &[coin(0, "purchasecoin")]), + ExecuteMsg::Purchase { + id: "a7918172-ac09-43f6-bc4b-7ac2fbad17e9".into(), + }, + ) + .unwrap_err(); + + // Ensure the expected error was returned. + match err { + ContractError::Std(StdError::GenericErr { msg, .. }) => { + assert_eq!(msg, "invalid purchase funds: 0purchasecoin") + } + _ => panic!("unexpected handle error"), + } + + // Send invalid denom + let err = execute( + deps.as_mut(), + mock_env(), + message_info(&Addr::unchecked("consumer"), &[coin(100, "fakecoin")]), + ExecuteMsg::Purchase { + id: "a7918172-ac09-43f6-bc4b-7ac2fbad17e9".into(), + }, + ) + .unwrap_err(); + + // Ensure the expected error was returned. + match err { + ContractError::Std(StdError::GenericErr { msg, .. }) => { + assert_eq!(msg, "invalid purchase funds: 100fakecoin") + } + _ => panic!("unexpected handle error"), + } + } +} From 36483efc65aca92f04ebd87241090eab5cb73cd2 Mon Sep 17 00:00:00 2001 From: kwt <4344285+kwtalley@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:42:14 -0500 Subject: [PATCH 14/14] update tutorial documentation --- docs/tutorial/04-project.md | 18 +- docs/tutorial/05-requirements.md | 14 +- docs/tutorial/06-develop.md | 1013 +++++++++++++++--------------- docs/tutorial/07-optimize.md | 17 +- docs/tutorial/08-setup.md | 10 +- docs/tutorial/09-store.md | 4 +- docs/tutorial/10-instantiate.md | 1 - docs/tutorial/12-execute.md | 1 - docs/tutorial/13-migrate.md | 9 +- 9 files changed, 553 insertions(+), 534 deletions(-) diff --git a/docs/tutorial/04-project.md b/docs/tutorial/04-project.md index 641299ce..2e3d9ccd 100644 --- a/docs/tutorial/04-project.md +++ b/docs/tutorial/04-project.md @@ -21,19 +21,17 @@ Edit Cargo.toml to have the following contract dependencies ```toml [dependencies] -cosmwasm-schema = "1.1.9" -cosmwasm-std = "1.1.9" -cosmwasm-storage = "1.1.9" -cw-storage-plus = "1.0.1" +cosmwasm-schema = "2.1.4" +cosmwasm-std = "2.1.4" +cw-storage-plus = "2.0.0" cw2 = "1.0.1" -provwasm-std = "1.1.2" -schemars = "0.8.10" -serde = { version = "1.0.145", default-features = false, features = ["derive"] } -thiserror = { version = "1.0.31" } +provwasm-std = "2.5.0" +schemars = "0.8.16" +serde = { version = "1.0.197", default-features = false, features = ["derive"] } +thiserror = { version = "1.0.58" } [dev-dependencies] -cw-multi-test = "0.16.2" -provwasm-mocks = "1.1.2" +provwasm-mocks = "2.5.0" ``` Reset the README and clear out the current JSON schema artifacts. diff --git a/docs/tutorial/05-requirements.md b/docs/tutorial/05-requirements.md index 5aba796c..f1d262e0 100644 --- a/docs/tutorial/05-requirements.md +++ b/docs/tutorial/05-requirements.md @@ -28,9 +28,9 @@ The tutorial contract requires the following handler functions and messages. ## Instantiate ```rust -#[cfg_attr(not(feature = "library"), entry_point)] +#[entry_point] pub fn instantiate( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, msg: InitMsg, @@ -62,9 +62,9 @@ The following validations must be performed during initialization. ## Execute ```rust -#[cfg_attr(not(feature = "library"), entry_point)] +#[entry_point] pub fn execute( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, @@ -97,10 +97,10 @@ action when a purchase has been completed. ## Query ```rust -#[cfg_attr(not(feature = "library"), entry_point)] +#[entry_point] pub fn query( - deps: Deps, - env: Env, + deps: Deps, + _env: Env, // NOTE: A '_' prefix indicates a variable is unused (suppress linter warnings) msg: QueryMsg, ) -> ... ``` diff --git a/docs/tutorial/06-develop.md b/docs/tutorial/06-develop.md index b45bd2ea..c74b962e 100644 --- a/docs/tutorial/06-develop.md +++ b/docs/tutorial/06-develop.md @@ -3,7 +3,9 @@ ## Development In this section we will build and test the functionality of the smart contract defined in the -[Requirements](05-requirements.md) section. Replace the contents of the files generated from template with the the code listed in this section. The best way to learn is to type out the code. But, it is completely acceptable to copy and paste as well. +[Requirements](05-requirements.md) section. Replace the contents of the files generated from template with the the code +listed in this section. The best way to learn is to type out the code. But, it is completely acceptable to copy and +paste as well. ### Setup @@ -15,13 +17,18 @@ File: `Makefile` .PHONY: all all: fmt build test lint schema optimize +.PHONY: pre-optimize +pre-optimize: fmt build test lint schema + +UNAME_M := $(shell uname -m) + .PHONY: fmt fmt: @cargo fmt --all -- --check .PHONY: build build: - @cargo wasm + @cargo build .PHONY: test test: @@ -29,11 +36,16 @@ test: .PHONY: lint lint: - @cargo clippy -- -D warnings + @cargo clippy .PHONY: schema schema: @cargo schema + +.PHONY: clean +clean: + @cargo clean + @cargo clean --target-dir artifacts ``` NOTE: A few of these cargo commands are aliases. The full commands can be seen in the @@ -41,9 +53,9 @@ NOTE: A few of these cargo commands are aliases. The full commands can be seen i ```toml [alias] -wasm = "build --release --target wasm32-unknown-unknown" +wasm = "build --release --target wasm32-unknown-unknown" unit-test = "test --lib" -schema = "run --bin schema" +schema = "run --example schema" ``` ### Library @@ -58,8 +70,6 @@ pub mod contract; mod error; pub mod msg; pub mod state; - -pub use crate::error::ContractError; ``` ### Errors @@ -69,16 +79,16 @@ File: `src/error.rs` Adds customizable errors to the smart contract. ```rust -use cosmwasm_std::StdError; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("Unauthorized")] - Unauthorized {}, +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized {}, } ``` @@ -89,25 +99,25 @@ File: `src/state.rs` Defines a singleton (one key, one value) configuration state for the smart contract. ```rust -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::{Addr, Decimal}; -use cw_storage_plus::Item; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct Config { - // The required purchase denomination - pub purchase_denom: String, - // The merchant account - pub merchant_address: Addr, - // The fee collection account - pub fee_collection_address: Addr, - // The percentage to collect on transfers - pub fee_percent: Decimal, -} - -pub const CONFIG: Item = Item::new("config"); +use cosmwasm_std::{Addr, Decimal}; +use cw_storage_plus::Item; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +pub const CONFIG: Item = Item::new("config"); + +/// Fields that comprise the smart contract state +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema)] +pub struct State { + // The required purchase denomination + pub purchase_denom: String, + // The merchant account + pub merchant_address: Addr, + // The fee collection account + pub fee_collection_address: Addr, + // The percentage to collect on transfers + pub fee_percent: Decimal, +} ``` ### Messages @@ -117,41 +127,34 @@ File: `src/msg.rs` Define message types for the smart contract. ```rust -use crate::state::Config; -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::Decimal; - -/// A message sent to initialize the contract state. -#[cw_serde] -pub struct InstantiateMsg { - pub contract_name: String, - pub purchase_denom: String, - pub merchant_address: String, - pub fee_percent: Decimal, -} - -/// A message sent to transfer funds and collect fees for a purchase. -#[cw_serde] -pub enum ExecuteMsg { - Purchase { id: String }, -} - -/// A message sent to migrate the contract to a new code id. -#[cw_serde] -pub enum MigrateMsg {} - -/// A message sent to query contract config state. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - #[returns(ConfigResponse)] - QueryRequest {}, -} - -/// A response . -#[cw_serde] -pub struct ConfigResponse { - pub config: Config, +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::Decimal; + +/// A message sent to initialize the contract state. +#[cw_serde] +pub struct InitMsg { + pub contract_name: String, + pub purchase_denom: String, + pub merchant_address: String, + pub fee_percent: Decimal, +} + +/// A message sent to transfer funds and collect fees for a purchase. +#[cw_serde] +pub enum ExecuteMsg { + Purchase { id: String }, +} + +/// Migrate the contract. +#[cw_serde] +pub struct MigrateMsg {} + +/// A message sent to query contract config state. +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(crate::state::State)] + QueryRequest {}, } ``` @@ -164,79 +167,91 @@ File: `src/contract.rs` The following imports are required for the init, query and handle functions. ```rust -use cosmwasm_std::{ - coin, entry_point, to_binary, BankMsg, Binary, CosmosMsg, Decimal, Deps, DepsMut, Env, - MessageInfo, Response, StdError, StdResult, -}; -use cw2::set_contract_version; -use provwasm_std::{bind_name, NameBinding, ProvenanceMsg, ProvenanceQuery}; -use std::ops::Mul; - -use crate::error::ContractError; -use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; -use crate::state::{Config, CONFIG}; +use cosmwasm_std::{ + coin, entry_point, to_json_binary, BankMsg, Binary, CosmosMsg, Decimal, Deps, DepsMut, Env, + MessageInfo, Response, StdError, StdResult, +}; +use provwasm_std::types::provenance::name::v1::{MsgBindNameRequest, NameRecord}; + +use crate::error::ContractError; +use crate::msg::{ExecuteMsg, InitMsg, MigrateMsg, QueryMsg}; +use crate::state::{State, CONFIG}; ``` #### Instantiate Handler code for contract instantiation. -```rust -// version info for migration info -const CONTRACT_NAME: &str = "crates.io:tutorial"; -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -#[entry_point] -pub fn instantiate( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> Result, ContractError> { - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - - // Ensure no funds were sent with the message - if !info.funds.is_empty() { - let err = "purchase funds are not allowed to be sent during init"; - return Err(ContractError::Std(StdError::generic_err(err))); - } - - // Ensure there are limits on fees. - if msg.fee_percent.is_zero() || msg.fee_percent > Decimal::percent(25) { - return Err(ContractError::Std(StdError::generic_err( - "fee percent must be > 0.0 and <= 0.25", - ))); - } - - // Ensure the merchant address is not also the fee collection address - if msg.merchant_address == info.sender { - return Err(ContractError::Std(StdError::generic_err( - "merchant address can't be the fee collection address", - ))); - } - - // Create and save contract config state. The fee collection address represents the network - // (ie they get paid fees), thus they must be the message sender. let merchant_address = deps.api.addr_validate(&msg.merchant_address)?; - CONFIG.save( - deps.storage, - &Config { - purchase_denom: msg.purchase_denom, - merchant_address, - fee_collection_address: info.sender, - fee_percent: msg.fee_percent, - }, - )?; - - // Create a message that will bind a restricted name to the contract address. - let msg = bind_name( - &msg.contract_name, - env.contract.address, - NameBinding::Restricted, - )?; - - Ok(Response::new() - .add_message(msg) - .add_attribute("action", "init")) +```rust +#[entry_point] +pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InitMsg, +) -> Result { + // Ensure no funds were sent with the message + if !info.funds.is_empty() { + let err = "purchase funds are not allowed to be sent during init"; + return Err(StdError::generic_err(err)); + } + + // Ensure there are limits on fees. + if msg.fee_percent.is_zero() || msg.fee_percent > Decimal::percent(25) { + return Err(StdError::generic_err( + "fee percent must be > 0.0 and <= 0.25", + )); + } + + // Ensure the merchant address is not also the fee collection address + if msg.merchant_address.eq(&info.sender.to_string()) { + return Err(StdError::generic_err( + "merchant address can't be the fee collection address", + )); + } + + // Create and save contract config state. The fee collection address represents the network + // (ie they get paid fees), thus they must be the message sender. + let merchant_address = deps.api.addr_validate(&msg.merchant_address)?; + CONFIG.save( + deps.storage, + &State { + purchase_denom: msg.purchase_denom, + merchant_address, + fee_collection_address: info.sender, + fee_percent: msg.fee_percent, + }, + )?; + + // Create a message that will bind a restricted name to the contract address. + let split: Vec<&str> = msg.contract_name.splitn(2, '.').collect(); + let record = split.first(); + let parent = split.last(); + + match (parent, record) { + (Some(parent), Some(record)) => { + // Create a bind name message + let bind_name_msg = MsgBindNameRequest { + parent: Some(NameRecord { + name: parent.to_string(), + address: env.contract.address.to_string(), + restricted: true, + }), + record: Some(NameRecord { + name: record.to_string(), + address: env.contract.address.to_string(), + restricted: true, + }), + }; + + // Dispatch bind name message and add event attributes. + let res = Response::new() + .add_message(bind_name_msg) + .add_attribute("action", "init"); + Ok(res) + } + (_, _) => Err(StdError::generic_err("Invalid contract name")), + } } ``` @@ -245,92 +260,93 @@ pub fn instantiate( Query code for accessing contract state. ```rust -#[entry_point] -pub fn query( - deps: Deps, - _env: Env, // NOTE: A '_' prefix indicates a variable is unused (suppress linter warnings) - msg: QueryMsg, -) -> StdResult { - match msg { - QueryMsg::QueryRequest {} => { - let state = CONFIG.load(deps.storage)?; - let json = to_binary(&state)?; - Ok(json) - } - } +#[entry_point] +pub fn query( + deps: Deps, + _env: Env, // NOTE: A '_' prefix indicates a variable is unused (suppress linter warnings) + msg: QueryMsg, +) -> StdResult { + match msg { + QueryMsg::QueryRequest {} => { + let state = CONFIG.load(deps.storage)?; + let json = to_json_binary(&state)?; + Ok(json) + } + } } ``` #### Execute ```rust -#[entry_point] -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result, ContractError> { - match msg { - ExecuteMsg::Purchase { id } => try_purchase(deps, env, info, id), - } -} - -// Calculates transfers and fees, then dispatches messages to the bank module. -fn try_purchase( - deps: DepsMut, - env: Env, - info: MessageInfo, - id: String, -) -> Result, ContractError> { - // Ensure funds were sent with the message - if info.funds.is_empty() { - let err = "no purchase funds sent"; - return Err(ContractError::Std(StdError::generic_err(err))); - } - - // Load state - let state = CONFIG.load(deps.storage)?; - let fee_pct = state.fee_percent; - - // Ensure the funds have the required amount and denomination - for funds in info.funds.iter() { - if funds.amount.is_zero() || funds.denom != state.purchase_denom { - let err = format!("invalid purchase funds: {}{}", funds.amount, funds.denom); - return Err(ContractError::Std(StdError::generic_err(err))); - } - } - - // Calculate amounts and create bank transfers to the merchant account - let transfers = CosmosMsg::Bank(BankMsg::Send { - to_address: state.merchant_address.to_string(), - amount: info - .funds - .iter() - .map(|sent| { - let fees = sent.amount.mul(fee_pct).u128(); - coin(sent.amount.u128() - fees, sent.denom.clone()) - }) - .collect(), - }); - - // Calculate fees and create bank transfers to the fee collection account - let fees = CosmosMsg::Bank(BankMsg::Send { - to_address: state.fee_collection_address.to_string(), - amount: info - .funds - .iter() - .map(|sent| coin(sent.amount.mul(fee_pct).u128(), sent.denom.clone())) - .collect(), - }); +#[entry_point] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + // BankMsg + match msg { + ExecuteMsg::Purchase { id } => try_purchase(deps, env, info, id), + } +} - // Return a response that will dispatch the transfers to the bank module and emit events. - Ok(Response::new() - .add_message(transfers) - .add_message(fees) - .add_attribute("action", "purchase") - .add_attribute("purchase_id", id) - .add_attribute("purchase_time", env.block.time.to_string())) +// Calculates transfers and fees, then dispatches messages to the bank module. +fn try_purchase( + deps: DepsMut, + env: Env, + info: MessageInfo, + id: String, +) -> Result { + // Ensure funds were sent with the message + if info.funds.is_empty() { + let err = "no purchase funds sent"; + return Err(ContractError::Std(StdError::generic_err(err))); + } + + // Load state + let state = CONFIG.load(deps.storage)?; + let fee_pct = state.fee_percent; + + // Ensure the funds have the required amount and denomination + for funds in info.funds.iter() { + if funds.amount.is_zero() || funds.denom != state.purchase_denom { + let err = format!("invalid purchase funds: {}{}", funds.amount, funds.denom); + return Err(ContractError::Std(StdError::generic_err(err))); + } + } + + // Calculate amounts and create bank transfers to the merchant account + let transfers = CosmosMsg::Bank(BankMsg::Send { + to_address: state.merchant_address.to_string(), + amount: info + .funds + .iter() + .map(|sent| { + let fees = sent.amount.mul_floor(fee_pct).u128(); + coin(sent.amount.u128() - fees, sent.denom.clone()) + }) + .collect(), + }); + + // Calculate fees and create bank transfers to the fee collection account + let fees = CosmosMsg::Bank(BankMsg::Send { + to_address: state.fee_collection_address.to_string(), + amount: info + .funds + .iter() + .map(|sent| coin(sent.amount.mul_floor(fee_pct).u128(), sent.denom.clone())) + .collect(), + }); + + // Return a response that will dispatch the transfers to the bank module and emit events. + Ok(Response::new() + .add_message(transfers) + .add_message(fees) + .add_attribute("action", "purchase") + .add_attribute("purchase_id", id) + .add_attribute("purchase_time", env.block.time.to_string())) } ``` @@ -355,272 +371,283 @@ File: `src/contract.rs` Add an inner module with imports for contract unit tests ```rust -#[cfg(test)] -mod tests { - use super::*; - use crate::state::Config; - use cosmwasm_std::testing::{mock_env, mock_info}; - use cosmwasm_std::{from_binary, Addr}; - use provwasm_mocks::mock_dependencies; - use provwasm_std::{NameMsgParams, ProvenanceMsgParams}; - - #[test] - fn valid_init() { - // Create mocks - let mut deps = mock_dependencies(&[]); - - // Create valid config state - let res = instantiate( - deps.as_mut(), - mock_env(), - mock_info("feebucket", &[]), - InstantiateMsg { - contract_name: "tutorial.sc.pb".into(), - purchase_denom: "purchasecoin".into(), - merchant_address: Addr::unchecked("merchant"), - fee_percent: Decimal::percent(10), - }, - ) - .unwrap(); - - // Ensure a message was created to bind the name to the contract address. - assert_eq!(res.messages.len(), 1); - match &res.messages[0].msg { - CosmosMsg::Custom(msg) => match &msg.params { - ProvenanceMsgParams::Name(p) => match &p { - NameMsgParams::BindName { name, .. } => assert_eq!(name, "tutorial.sc.pb"), - _ => panic!("unexpected name params"), - }, - _ => panic!("unexpected provenance params"), - }, - _ => panic!("unexpected cosmos message"), - } - } - - #[test] - fn invalid_merchant_init() { - // Create mocks - let mut deps = mock_dependencies(&[]); - - // Create an invalid init message - let err = instantiate( - deps.as_mut(), - mock_env(), - mock_info("merchant", &[]), - InstantiateMsg { - contract_name: "tutorial.sc.pb".into(), - purchase_denom: "purchasecoin".into(), - merchant_address: Addr::unchecked("merchant"), - fee_percent: Decimal::percent(10), - }, - ) - .unwrap_err(); - - // Ensure the expected error was returned. - match err { - ContractError::Std(StdError::GenericErr { msg, .. }) => { - assert_eq!(msg, "merchant address can't be the fee collection address") - } - _ => panic!("unexpected init error"), - } - } - - #[test] - fn invalid_fee_percent_init() { - // Create mocks - let mut deps = mock_dependencies(&[]); - - // Create an invalid init message. - let err = instantiate( - deps.as_mut(), - mock_env(), - mock_info("feebucket", &[]), - InstantiateMsg { - contract_name: "tutorial.sc.pb".into(), - purchase_denom: "purchasecoin".into(), - merchant_address: Addr::unchecked("merchant"), - fee_percent: Decimal::percent(37), // error: > 25% - }, - ) - .unwrap_err(); - - // Ensure the expected error was returned - match err { - ContractError::Std(StdError::GenericErr { msg, .. }) => { - assert_eq!(msg, "fee percent must be > 0.0 and <= 0.25") - } - _ => panic!("unexpected init error"), - } - } - - #[test] - fn query_test() { - // Create mocks - let mut deps = mock_dependencies(&[]); - - // Create config state - instantiate( - deps.as_mut(), - mock_env(), - mock_info("feebucket", &[]), - InstantiateMsg { - contract_name: "tutorial.sc.pb".into(), - purchase_denom: "purchasecoin".into(), - merchant_address: Addr::unchecked("merchant"), - fee_percent: Decimal::percent(10), - }, - ) - .unwrap(); // Panics on error - - // Call the smart contract query function to get stored state. - let bin = query(deps.as_ref(), mock_env(), QueryMsg::QueryRequest {}).unwrap(); - let resp: Config = from_binary(&bin).unwrap(); - - // Ensure the expected init fields were properly stored. - assert_eq!(resp.merchant_address, Addr::unchecked("merchant")); - assert_eq!(resp.purchase_denom, "purchasecoin"); - assert_eq!(resp.fee_collection_address, Addr::unchecked("feebucket")); - assert_eq!(resp.fee_percent, Decimal::percent(10)); - } - - #[test] - fn handle_valid_purchase() { - // Create mocks - let mut deps = mock_dependencies(&[]); - - // Create config state - instantiate( - deps.as_mut(), - mock_env(), - mock_info("feebucket", &[]), - InstantiateMsg { - contract_name: "tutorial.sc.pb".into(), - purchase_denom: "purchasecoin".into(), - merchant_address: Addr::unchecked("merchant"), - fee_percent: Decimal::percent(10), - }, - ) - .unwrap(); - - // Send a valid purchase message of 100purchasecoin - let res = execute( - deps.as_mut(), - mock_env(), - mock_info("consumer", &[coin(100, "purchasecoin")]), - ExecuteMsg::Purchase { - id: "a7918172-ac09-43f6-bc4b-7ac2fbad17e9".into(), - }, - ) - .unwrap(); - - // Ensure we have the merchant transfer and fee collection bank messages - assert_eq!(res.messages.len(), 2); - - // Ensure we got the proper bank transfer values. - // 10% fees on 100 purchasecoin => 90 purchasecoin for the merchant and 10 purchasecoin for the fee bucket. - let expected_transfer = coin(90, "purchasecoin"); - let expected_fees = coin(10, "purchasecoin"); - res.messages.into_iter().for_each(|msg| match msg.msg { - CosmosMsg::Bank(BankMsg::Send { - amount, to_address, .. - }) => { - assert_eq!(amount.len(), 1); - if to_address == "merchant" { - assert_eq!(amount[0], expected_transfer) - } else if to_address == "feebucket" { - assert_eq!(amount[0], expected_fees) - } else { - panic!("unexpected to_address in bank message") - } - } - _ => panic!("unexpected message type"), - }); - - // Ensure we got the purchase ID event attribute value - let expected_purchase_id = "a7918172-ac09-43f6-bc4b-7ac2fbad17e9"; - res.attributes.into_iter().for_each(|atr| { - if atr.key == "purchase_id" { - assert_eq!(atr.value, expected_purchase_id) - } - }) - } - - #[test] - fn handle_invalid_funds() { - // Create mocks - let mut deps = mock_dependencies(&[]); - - // Create config state - instantiate( - deps.as_mut(), - mock_env(), - mock_info("feebucket", &[]), - InstantiateMsg { - contract_name: "tutorial.sc.pb".into(), - purchase_denom: "purchasecoin".into(), - merchant_address: Addr::unchecked("merchant"), - fee_percent: Decimal::percent(10), - }, - ) - .unwrap(); - - // Don't send any funds - let err = execute( - deps.as_mut(), - mock_env(), - mock_info("consumer", &[]), - ExecuteMsg::Purchase { - id: "a7918172-ac09-43f6-bc4b-7ac2fbad17e9".into(), - }, - ) - .unwrap_err(); - - // Ensure the expected error was returned. - match err { - ContractError::Std(StdError::GenericErr { msg, .. }) => { - assert_eq!(msg, "no purchase funds sent") - } - _ => panic!("unexpected handle error"), - } - - // Send zero amount for a valid denom - let err = execute( - deps.as_mut(), - mock_env(), - mock_info("consumer", &[coin(0, "purchasecoin")]), - ExecuteMsg::Purchase { - id: "a7918172-ac09-43f6-bc4b-7ac2fbad17e9".into(), - }, - ) - .unwrap_err(); - - // Ensure the expected error was returned. - match err { - ContractError::Std(StdError::GenericErr { msg, .. }) => { - assert_eq!(msg, "invalid purchase funds: 0purchasecoin") - } - _ => panic!("unexpected handle error"), - } - - // Send invalid denom - let err = execute( - deps.as_mut(), - mock_env(), - mock_info("consumer", &[coin(100, "fakecoin")]), - ExecuteMsg::Purchase { - id: "a7918172-ac09-43f6-bc4b-7ac2fbad17e9".into(), - }, - ) - .unwrap_err(); - - // Ensure the expected error was returned. - match err { - ContractError::Std(StdError::GenericErr { msg, .. }) => { - assert_eq!(msg, "invalid purchase funds: 100fakecoin") - } - _ => panic!("unexpected handle error"), - } - } +#[cfg(test)] +mod tests { + use super::*; + use cosmwasm_std::testing::{message_info, mock_env}; + use cosmwasm_std::{from_json, Addr, AnyMsg, Binary, CosmosMsg}; + use provwasm_mocks::mock_provenance_dependencies; + use provwasm_std::types::provenance::name::v1::{ + QueryResolveRequest, QueryResolveResponse, QueryReverseLookupRequest, + QueryReverseLookupResponse, + }; + + #[test] + fn init_test() { + // Create default provenance mocks. + let mut deps = mock_provenance_dependencies(); + let env = mock_env(); + let info = message_info(&Addr::unchecked("sender"), &[]); + + // Give the contract a name + let msg = InitMsg { + name: "contract.pb".into(), + }; + + let contract_address = env.contract.address.to_string(); + + // Ensure a message was created to bind the name to the contract address. + let res = instantiate(deps.as_mut(), env, info, msg).unwrap(); + assert_eq!(1, res.messages.len()); + + match &res.messages[0].msg { + CosmosMsg::Any(AnyMsg { type_url, value }) => { + let expected: Binary = MsgBindNameRequest { + parent: Some(NameRecord { + name: "pb".to_string(), + address: contract_address.clone(), + restricted: true, + }), + record: Some(NameRecord { + name: "contract".to_string(), + address: contract_address, + restricted: true, + }), + } + .into(); + + assert_eq!(type_url, "/provenance.name.v1.MsgBindNameRequest"); + assert_eq!(value, &expected) + } + _ => panic!("unexpected cosmos message"), + } + } + + #[test] + fn bind_name_success() { + // Init state + let mut deps = mock_provenance_dependencies(); + let env = mock_env(); + let info = message_info(&Addr::unchecked("sender"), &[]); + let msg = InitMsg { + name: "contract.pb".into(), + }; + let _ = instantiate(deps.as_mut(), env, info, msg).unwrap(); // Panics on error + + // Bind a name + let env = mock_env(); + let info = message_info(&Addr::unchecked("sender"), &[]); + let msg = ExecuteMsg::BindPrefix { + prefix: "test".into(), + }; + + let contract_address = env.contract.address.to_string(); + + let res = execute(deps.as_mut(), env, info, msg).unwrap(); + + // Assert the correct message was created + match &res.messages[0].msg { + CosmosMsg::Any(AnyMsg { type_url, value }) => { + let expected: Binary = MsgBindNameRequest { + parent: Some(NameRecord { + name: "contract.pb".to_string(), + address: contract_address.clone(), + restricted: true, + }), + record: Some(NameRecord { + name: "test".to_string(), + address: contract_address, + restricted: true, + }), + } + .into(); + + assert_eq!(type_url, "/provenance.name.v1.MsgBindNameRequest"); + assert_eq!(value, &expected) + } + _ => panic!("unexpected cosmos message"), + } + } + + #[test] + fn unbind_name_success() { + // Init state + let mut deps = mock_provenance_dependencies(); + let env = mock_env(); + let info = message_info(&Addr::unchecked("sender"), &[]); + let msg = InitMsg { + name: "contract.pb".into(), + }; + let _ = instantiate(deps.as_mut(), env, info, msg).unwrap(); // Panics on error + + // Bind a name + let env = mock_env(); + let info = message_info(&Addr::unchecked("sender"), &[]); + let msg = ExecuteMsg::UnbindPrefix { + prefix: "test".into(), + }; + + let contract_address = env.contract.address.to_string(); + + let res = execute(deps.as_mut(), env, info, msg).unwrap(); + + // Assert the correct message was created + assert_eq!(1, res.messages.len()); + match &res.messages[0].msg { + CosmosMsg::Any(AnyMsg { type_url, value }) => { + let expected: Binary = MsgDeleteNameRequest { + record: Some(NameRecord { + name: "test.contract.pb".to_string(), + address: contract_address, + restricted: true, + }), + } + .into(); + + assert_eq!(type_url, "/provenance.name.v1.MsgDeleteNameRequest"); + assert_eq!(value, &expected) + } + _ => panic!("unexpected cosmos message"), + } + } + + #[test] + fn bind_name_unauthorized() { + // Init state + let mut deps = mock_provenance_dependencies(); + let env = mock_env(); + let info = message_info(&Addr::unchecked("sender"), &[]); + let msg = InitMsg { + name: "contract.pb".into(), + }; + let _ = instantiate(deps.as_mut(), env, info, msg).unwrap(); // Panics on error + + // Try to bind a name with some other sender address + let env = mock_env(); + let info = message_info(&Addr::unchecked("other"), &[]); // error: not 'sender' + let msg = ExecuteMsg::BindPrefix { + prefix: "test".into(), + }; + let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); + + // Assert an unauthorized error was returned + match err { + ContractError::Unauthorized {} => {} + e => panic!("unexpected error: {:?}", e), + } + } + + #[test] + fn unbind_name_unauthorized() { + // Init state + let mut deps = mock_provenance_dependencies(); + let env = mock_env(); + let info = message_info(&Addr::unchecked("sender"), &[]); + let msg = InitMsg { + name: "contract.pb".into(), + }; + let _ = instantiate(deps.as_mut(), env, info, msg).unwrap(); // Panics on error + + // Try to bind a name with some other sender address + let env = mock_env(); + let info = message_info(&Addr::unchecked("other"), &[]); // error: not 'sender' + let msg = ExecuteMsg::UnbindPrefix { + prefix: "test".into(), + }; + let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); + + // Assert an unauthorized error was returned + match err { + ContractError::Unauthorized {} => {} + e => panic!("unexpected error: {:?}", e), + } + } + + #[test] + fn query_resolve() { + // Create provenance mock deps with a single bound name. + + let mut deps = mock_provenance_dependencies(); + + let mock_response = QueryResolveResponse { + address: "tp1y0txdp3sqmxjvfdaa8hfvwcljl8ugcfv26uync".to_string(), + restricted: false, + }; + + QueryResolveRequest::mock_response(&mut deps.querier, mock_response); + + // Call the smart contract query function to resolve the address for our test name. + let bin = query( + deps.as_ref(), + mock_env(), + QueryMsg::Resolve { + name: "a.pb".into(), + }, + ) + .unwrap(); + + // Ensure that we got the expected address. + let rep: String = from_json(bin).unwrap(); + assert_eq!(rep, "tp1y0txdp3sqmxjvfdaa8hfvwcljl8ugcfv26uync") + } + + #[test] + fn query_lookup() { + // Create provenance mock deps with two bound names. + let mut deps = mock_provenance_dependencies(); + + let mock_response = QueryReverseLookupResponse { + name: vec!["b.pb".to_string(), "a.pb".to_string()], + pagination: None, + }; + + QueryReverseLookupRequest::mock_response(&mut deps.querier, mock_response.clone()); + + // Call the smart contract query function to lookup names bound to an address. + let bin = query( + deps.as_ref(), + mock_env(), + QueryMsg::Lookup { + address: deps.api.addr_make("address").into(), + }, + ) + .unwrap(); + + // Ensure that we got the expected number of records. + let rep: LookupResponse = from_json(bin).unwrap(); + assert_eq!( + rep, + LookupResponse { + name: vec!["b.pb".to_string(), "a.pb".to_string()] + } + ); + } + + #[test] + fn query_lookup_empty() { + // Create provenance mock deps with a bound name. + let mut deps = mock_provenance_dependencies(); + let mock_response = QueryReverseLookupResponse { + name: vec![], + pagination: None, + }; + + QueryReverseLookupRequest::mock_response(&mut deps.querier, mock_response.clone()); + + // Call the smart contract query function to lookup names bound to an address. + let bin = query( + deps.as_ref(), + mock_env(), + QueryMsg::Lookup { + address: deps.api.addr_make("address2").into(), + }, + ) + .unwrap(); + + // Ensure that we got zero records. + let rep: LookupResponse = from_json(bin).unwrap(); + assert_eq!(rep, LookupResponse { name: vec![] }); + } } ``` @@ -631,22 +658,22 @@ File: `src/bin/schema.rs` Ensure a JSON schema is generated for the smart contract types. ```rust -use cosmwasm_schema::write_api; - -use tutorial::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; - -fn main() { - write_api! { - instantiate: InstantiateMsg, - execute: ExecuteMsg, - query: QueryMsg, - } +use cosmwasm_schema::write_api; +use name::msg::{ExecuteMsg, InitMsg, QueryMsg}; + +fn main() { + write_api! { + execute: ExecuteMsg, + instantiate: InitMsg, + query: QueryMsg, + } } ``` ## Code Format Before building make sure that everything is formatted correctly using: + ```bash cargo fmt ``` diff --git a/docs/tutorial/07-optimize.md b/docs/tutorial/07-optimize.md index 100ceeed..4fe321d8 100644 --- a/docs/tutorial/07-optimize.md +++ b/docs/tutorial/07-optimize.md @@ -4,15 +4,21 @@ In this section we will optimize the compiled smart contract Wasm to a deployable file. -A rust optimization tool was developed by the CosmWasm team to reduce the size of smart contract Wasm. It is packaged as a docker image. To use this image, add the following to the end of the tutorial `Makefile`. +A rust optimization tool was developed by the CosmWasm team to reduce the size of smart contract Wasm. It is packaged as +a docker image. To use this image, add the following to the end of the tutorial `Makefile`. ```Makefile .PHONY: optimize optimize: - @docker run --rm -v $(CURDIR):/code:Z \ - --mount type=volume,source=tutorial_cache,target=/code/target \ + @if [ "$(UNAME_M)" = "arm64" ]; then \ + image="cosmwasm/optimizer-arm64"; \ + else \ + image="cosmwasm/optimizer"; \ + fi; \ + docker run --rm -v $(CURDIR)/../../:/code:Z --workdir /code/contracts/name \ + --mount type=volume,source=name_cache,target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/rust-optimizer:0.12.10 + $$image:0.16.0 ``` Then build the optimized Wasm @@ -39,7 +45,8 @@ ls -lh target/wasm32-unknown-unknown/release/tutorial.wasm NOTE: Optimized smart contract size must be smaller than `600K` -This concludes Part 1 of the tutorial. The optimized smart contract Wasm is ready to deploy to the Provenance Blockchain. +This concludes Part 1 of the tutorial. The optimized smart contract Wasm is ready to deploy to the Provenance +Blockchain. ## Up Next diff --git a/docs/tutorial/08-setup.md b/docs/tutorial/08-setup.md index 665aed34..33f47598 100644 --- a/docs/tutorial/08-setup.md +++ b/docs/tutorial/08-setup.md @@ -56,6 +56,7 @@ provenanced keys add consumer --home build/run/provenanced --keyring-backend tes ``` Create alias for the keys + ```bash export validator=$(provenanced keys show -a validator --home build/run/provenanced --keyring-backend test -t) export merchant=$(provenanced keys show -a merchant --home build/run/provenanced --keyring-backend test -t) @@ -77,7 +78,6 @@ provenanced tx bank send \ --gas=auto \ --gas-prices="1905nhash" \ --gas-adjustment=1.5 \ - --broadcast-mode=block \ --yes \ --testnet \ --output json | jq @@ -97,7 +97,6 @@ provenanced tx bank send \ --gas=auto \ --gas-prices="1905nhash" \ --gas-adjustment=1.5 \ - --broadcast-mode=block \ --yes \ --testnet \ --output json | jq @@ -117,7 +116,6 @@ provenanced tx bank send \ --gas auto \ --gas-prices="1905nhash" \ --gas-adjustment=1.5 \ - --broadcast-mode block \ --yes \ --testnet \ --output json | jq @@ -140,7 +138,6 @@ provenanced tx name bind \ --chain-id testing \ --gas-prices="100000nhash" \ --gas-adjustment=1.5 \ - --broadcast-mode block \ --yes \ --testnet \ --output json | jq @@ -160,7 +157,6 @@ provenanced tx marker new 1000000000purchasecoin \ --gas auto \ --gas-prices="1000000nhash" \ --gas-adjustment=1.5 \ - --broadcast-mode block \ --yes \ --testnet \ --output json | jq @@ -180,7 +176,6 @@ provenanced tx marker grant \ --gas auto \ --gas-prices="1905nhash" \ --gas-adjustment=1.5 \ - --broadcast-mode block \ --yes \ --testnet \ --output json | jq @@ -197,7 +192,6 @@ provenanced tx marker finalize purchasecoin \ --gas auto \ --gas-prices="1905nhash" \ --gas-adjustment=1.5 \ - --broadcast-mode block \ --yes \ --testnet \ --output json | jq @@ -214,7 +208,6 @@ provenanced tx marker activate purchasecoin \ --gas auto \ --gas-prices="1905nhash" \ --gas-adjustment=1.5 \ - --broadcast-mode block \ --yes \ --testnet \ --output json | jq @@ -235,7 +228,6 @@ provenanced tx marker withdraw purchasecoin \ --gas auto \ --gas-prices="1905nhash" \ --gas-adjustment=1.5 \ - --broadcast-mode block \ --yes \ --testnet \ --output json | jq diff --git a/docs/tutorial/09-store.md b/docs/tutorial/09-store.md index 242cd7e9..1011f106 100644 --- a/docs/tutorial/09-store.md +++ b/docs/tutorial/09-store.md @@ -26,7 +26,6 @@ provenanced tx wasm store tutorial.wasm \ --gas auto \ --gas-prices="1905nhash" \ --gas-adjustment=1.5 \ - --broadcast-mode block \ --yes \ --testnet \ --output json | jq @@ -63,7 +62,8 @@ Should produce output that resembles (field values may differ) the following. } ``` -The `--instantiate-anyof-addresses` flag is important when it is necessary to limit instance creation to specified accounts. +The `--instantiate-anyof-addresses` flag is important when it is necessary to limit instance creation to specified +accounts. Copy the value of the `id` field. It is required to instantiate the contract in the next section. diff --git a/docs/tutorial/10-instantiate.md b/docs/tutorial/10-instantiate.md index 6558ce7e..7afbce86 100644 --- a/docs/tutorial/10-instantiate.md +++ b/docs/tutorial/10-instantiate.md @@ -43,7 +43,6 @@ provenanced tx wasm instantiate 1 \ --gas auto \ --gas-prices="100000nhash" \ --gas-adjustment=1.5 \ - --broadcast-mode block \ --yes \ --testnet \ --output json | jq diff --git a/docs/tutorial/12-execute.md b/docs/tutorial/12-execute.md index 486dc4c9..9ffb4a9a 100644 --- a/docs/tutorial/12-execute.md +++ b/docs/tutorial/12-execute.md @@ -19,7 +19,6 @@ provenanced tx wasm execute \ --gas auto \ --gas-prices="1905nhash" \ --gas-adjustment=1.5 \ - --broadcast-mode block \ --yes \ --testnet \ --output json | jq diff --git a/docs/tutorial/13-migrate.md b/docs/tutorial/13-migrate.md index 31b8919e..ef644b6d 100644 --- a/docs/tutorial/13-migrate.md +++ b/docs/tutorial/13-migrate.md @@ -39,11 +39,7 @@ File: `src/contract.rs` ```rust /// Called when migrating a contract instance to a new code ID. -pub fn migrate( - deps: DepsMut, - env: Env, - msg: MigrateMsg, -) -> Result { +pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { // 1) Ensure the new fee percent is within the updated range. // 2) Get mutable ref to the contract state // 3) Set new fee percent in the state @@ -71,7 +67,7 @@ File: `examples/schema.rs` ```rust use cosmwasm_schema::write_api; -use tutorial::msg::{ExecuteMsg, InitMsg, MigrateMsg, QueryMsg}; +use name::msg::{ExecuteMsg, InitMsg, QueryMsg}; fn main() { write_api! { @@ -81,6 +77,7 @@ fn main() { query: QueryMsg, } } + ``` When complete, use the CLI commands below to migrate the smart contract instance to a new code ID.