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" 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/Cargo.toml b/Cargo.toml index abb342a1..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" @@ -26,16 +26,16 @@ 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 -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/README.md b/README.md index de4b3d81..d0ea632e 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.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 | 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)) 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"), + } + } +} diff --git a/dependencies/provenance b/dependencies/provenance index d1119ab0..f7e5177a 160000 --- a/dependencies/provenance +++ b/dependencies/provenance @@ -1 +1 @@ -Subproject commit d1119ab02c423d86a0f485a8f124e73511ec1b9b +Subproject commit f7e5177a16130d930bcda7c1125cb04e935e84a7 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. diff --git a/packages/proto-build/src/main.rs b/packages/proto-build/src/main.rs index adac91ec..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.19.1"; +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/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 } diff --git a/packages/provwasm-std/src/types/PROVENANCE_COMMIT b/packages/provwasm-std/src/types/PROVENANCE_COMMIT index 8e63bed0..b8cceb94 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 \ No newline at end of file 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( diff --git a/scripts/update-and-rebuild.sh b/scripts/update-and-rebuild.sh index efbbd8e4..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.19.1" +LATEST_PROVENANCE_VERSION="v1.20.0" PROVENANCE_REV=${1:-$LATEST_PROVENANCE_VERSION} COMMIT=${2:-"skip"}