From 2b1ba1bd377e932dd60febf9f57c8d20cfe16c14 Mon Sep 17 00:00:00 2001 From: ALPAC-4 Date: Wed, 17 Aug 2022 18:51:15 +0900 Subject: [PATCH 01/57] feat: add cw1155-base --- Cargo.lock | 26 + contracts/cw1155-base/.cargo/config | 5 + contracts/cw1155-base/Cargo.toml | 39 + contracts/cw1155-base/src/contract_tests.rs | 874 ++++++++++++++++++++ contracts/cw1155-base/src/error.rs | 17 + contracts/cw1155-base/src/execute.rs | 417 ++++++++++ contracts/cw1155-base/src/lib.rs | 52 ++ contracts/cw1155-base/src/msg.rs | 79 ++ contracts/cw1155-base/src/query.rs | 217 +++++ contracts/cw1155-base/src/state.rs | 112 +++ packages/cw1155/.cargo/config | 4 + packages/cw1155/Cargo.toml | 20 + packages/cw1155/README.md | 104 +++ packages/cw1155/src/event.rs | 41 + packages/cw1155/src/lib.rs | 17 + packages/cw1155/src/msg.rs | 51 ++ packages/cw1155/src/query.rs | 131 +++ packages/cw1155/src/receiver.rs | 73 ++ 18 files changed, 2279 insertions(+) create mode 100644 contracts/cw1155-base/.cargo/config create mode 100644 contracts/cw1155-base/Cargo.toml create mode 100644 contracts/cw1155-base/src/contract_tests.rs create mode 100644 contracts/cw1155-base/src/error.rs create mode 100644 contracts/cw1155-base/src/execute.rs create mode 100644 contracts/cw1155-base/src/lib.rs create mode 100644 contracts/cw1155-base/src/msg.rs create mode 100644 contracts/cw1155-base/src/query.rs create mode 100644 contracts/cw1155-base/src/state.rs create mode 100644 packages/cw1155/.cargo/config create mode 100644 packages/cw1155/Cargo.toml create mode 100644 packages/cw1155/README.md create mode 100644 packages/cw1155/src/event.rs create mode 100644 packages/cw1155/src/lib.rs create mode 100644 packages/cw1155/src/msg.rs create mode 100644 packages/cw1155/src/query.rs create mode 100644 packages/cw1155/src/receiver.rs diff --git a/Cargo.lock b/Cargo.lock index 8216b9c15..4ad106b64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -311,6 +311,32 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw1155" +version = "0.13.4" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils", + "schemars", + "serde", +] + +[[package]] +name = "cw1155-base" +version = "0.13.4" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "cw-utils", + "cw1155", + "cw2", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw2" version = "0.16.0" diff --git a/contracts/cw1155-base/.cargo/config b/contracts/cw1155-base/.cargo/config new file mode 100644 index 000000000..7d1a066c8 --- /dev/null +++ b/contracts/cw1155-base/.cargo/config @@ -0,0 +1,5 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +wasm-debug = "build --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --example schema" diff --git a/contracts/cw1155-base/Cargo.toml b/contracts/cw1155-base/Cargo.toml new file mode 100644 index 000000000..d2f475ed7 --- /dev/null +++ b/contracts/cw1155-base/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "cw1155-base" +version = "0.13.4" +authors = [ +] +edition = "2018" +description = "Basic implementation cw1155" +license = "Apache-2.0" +repository = "https://github.com/CosmWasm/cw-nfts" +homepage = "https://cosmwasm.com" +documentation = "https://docs.cosmwasm.com" + +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "artifacts/*", +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +cw-utils = "0.13.4" +cw2 = "0.13.4" +cw1155 = { path = "../../packages/cw1155", version = "0.13.4" } +cw-storage-plus = "0.13.4" +cosmwasm-std = { version = "1.0.0" } +schemars = "0.8.10" +serde = { version = "1.0.140", default-features = false, features = ["derive"] } +thiserror = { version = "1.0.31" } + +[dev-dependencies] +cosmwasm-schema = { version = "1.0.0" } diff --git a/contracts/cw1155-base/src/contract_tests.rs b/contracts/cw1155-base/src/contract_tests.rs new file mode 100644 index 000000000..87b9db05c --- /dev/null +++ b/contracts/cw1155-base/src/contract_tests.rs @@ -0,0 +1,874 @@ +#[cfg(test)] +mod tests { + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::{ + to_binary, Addr, Binary, Empty, OverflowError, Response, StdError, Uint128, + }; + + use crate::{ContractError, Cw1155Contract, ExecuteMsg, InstantiateMsg, MintMsg}; + use cw1155::{ + AllBalancesResponse, ApprovedForAllResponse, Balance, BalanceResponse, + BatchBalanceResponse, Cw1155BatchReceiveMsg, Cw1155QueryMsg, Expiration, + IsApprovedForAllResponse, NumTokensResponse, TokenInfoResponse, TokensResponse, + }; + + #[test] + fn check_transfers() { + let contract = Cw1155Contract::default(); + // A long test case that try to cover as many cases as possible. + // Summary of what it does: + // - try mint without permission, fail + // - mint with permission, success + // - query balance of receipant, success + // - try transfer without approval, fail + // - approve + // - transfer again, success + // - query balance of transfer participants + // - try batch transfer without approval, fail + // - approve and try batch transfer again, success + // - batch query balances + // - user1 revoke approval to minter + // - query approval status + // - minter try to transfer, fail + // - user1 burn token1 + // - user1 batch burn token2 and token3 + let token1 = "token1".to_owned(); + let token2 = "token2".to_owned(); + let token3 = "token3".to_owned(); + let minter = String::from("minter"); + let user1 = String::from("user1"); + let user2 = String::from("user2"); + + let mut deps = mock_dependencies(); + let msg = InstantiateMsg { + minter: minter.clone(), + }; + let res = contract + .instantiate(deps.as_mut(), mock_env(), mock_info("operator", &[]), msg) + .unwrap(); + assert_eq!(0, res.messages.len()); + + // invalid mint, user1 don't mint permission + let mint_msg = ExecuteMsg::Mint(MintMsg:: { + to: user1.clone(), + token_id: token1.clone(), + value: 1u64.into(), + token_uri: None, + extension: None, + }); + assert!(matches!( + contract.execute( + deps.as_mut(), + mock_env(), + mock_info(user1.as_ref(), &[]), + mint_msg.clone(), + ), + Err(ContractError::Unauthorized {}) + )); + + // valid mint + assert_eq!( + contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(minter.as_ref(), &[]), + mint_msg, + ) + .unwrap(), + Response::new() + .add_attribute("action", "transfer") + .add_attribute("token_id", &token1) + .add_attribute("amount", 1u64.to_string()) + .add_attribute("to", &user1) + ); + + // query balance + assert_eq!( + to_binary(&BalanceResponse { + balance: 1u64.into() + }), + contract.query( + deps.as_ref(), + mock_env(), + Cw1155QueryMsg::Balance { + owner: user1.clone(), + token_id: token1.clone(), + } + ), + ); + + let transfer_msg = ExecuteMsg::SendFrom { + from: user1.clone(), + to: user2.clone(), + token_id: token1.clone(), + value: 1u64.into(), + msg: None, + }; + + // not approved yet + assert!(matches!( + contract.execute( + deps.as_mut(), + mock_env(), + mock_info(minter.as_ref(), &[]), + transfer_msg.clone(), + ), + Err(ContractError::Unauthorized {}) + )); + + // approve + contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(user1.as_ref(), &[]), + ExecuteMsg::ApproveAll { + operator: minter.clone(), + expires: None, + }, + ) + .unwrap(); + + // transfer + assert_eq!( + contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(minter.as_ref(), &[]), + transfer_msg, + ) + .unwrap(), + Response::new() + .add_attribute("action", "transfer") + .add_attribute("token_id", &token1) + .add_attribute("amount", 1u64.to_string()) + .add_attribute("from", &user1) + .add_attribute("to", &user2) + ); + + // query balance + assert_eq!( + contract.query( + deps.as_ref(), + mock_env(), + Cw1155QueryMsg::Balance { + owner: user2.clone(), + token_id: token1.clone(), + } + ), + to_binary(&BalanceResponse { + balance: 1u64.into() + }), + ); + assert_eq!( + contract.query( + deps.as_ref(), + mock_env(), + Cw1155QueryMsg::Balance { + owner: user1.clone(), + token_id: token1.clone(), + } + ), + to_binary(&BalanceResponse { + balance: 0u64.into() + }), + ); + + // mint token2 and token3 + contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(minter.as_ref(), &[]), + ExecuteMsg::Mint(MintMsg:: { + to: user2.clone(), + token_id: token2.clone(), + value: 1u64.into(), + token_uri: None, + extension: None, + }), + ) + .unwrap(); + + contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(minter.as_ref(), &[]), + ExecuteMsg::Mint(MintMsg:: { + to: user2.clone(), + token_id: token3.clone(), + value: 1u64.into(), + token_uri: None, + extension: None, + }), + ) + .unwrap(); + + // invalid batch transfer, (user2 not approved yet) + let batch_transfer_msg = ExecuteMsg::BatchSendFrom { + from: user2.clone(), + to: user1.clone(), + batch: vec![ + (token1.clone(), 1u64.into()), + (token2.clone(), 1u64.into()), + (token3.clone(), 1u64.into()), + ], + msg: None, + }; + assert!(matches!( + contract.execute( + deps.as_mut(), + mock_env(), + mock_info(minter.as_ref(), &[]), + batch_transfer_msg.clone(), + ), + Err(ContractError::Unauthorized {}), + )); + + // user2 approve + contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(user2.as_ref(), &[]), + ExecuteMsg::ApproveAll { + operator: minter.clone(), + expires: None, + }, + ) + .unwrap(); + + // valid batch transfer + assert_eq!( + contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(minter.as_ref(), &[]), + batch_transfer_msg, + ) + .unwrap(), + Response::new() + .add_attribute("action", "transfer") + .add_attribute("token_id", &token1) + .add_attribute("amount", 1u64.to_string()) + .add_attribute("from", &user2) + .add_attribute("to", &user1) + .add_attribute("action", "transfer") + .add_attribute("token_id", &token2) + .add_attribute("amount", 1u64.to_string()) + .add_attribute("from", &user2) + .add_attribute("to", &user1) + .add_attribute("action", "transfer") + .add_attribute("token_id", &token3) + .add_attribute("amount", 1u64.to_string()) + .add_attribute("from", &user2) + .add_attribute("to", &user1) + ); + + // batch query + assert_eq!( + contract.query( + deps.as_ref(), + mock_env(), + Cw1155QueryMsg::BatchBalance { + owner: user1.clone(), + token_ids: vec![token1.clone(), token2.clone(), token3.clone()], + } + ), + to_binary(&BatchBalanceResponse { + balances: vec![1u64.into(), 1u64.into(), 1u64.into()] + }), + ); + + // user1 revoke approval + contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(user1.as_ref(), &[]), + ExecuteMsg::RevokeAll { + operator: minter.clone(), + }, + ) + .unwrap(); + + // query approval status + assert_eq!( + contract.query( + deps.as_ref(), + mock_env(), + Cw1155QueryMsg::IsApprovedForAll { + owner: user1.clone(), + operator: minter.clone(), + } + ), + to_binary(&IsApprovedForAllResponse { approved: false }), + ); + + // tranfer without approval + assert!(matches!( + contract.execute( + deps.as_mut(), + mock_env(), + mock_info(minter.as_ref(), &[]), + ExecuteMsg::SendFrom { + from: user1.clone(), + to: user2, + token_id: token1.clone(), + value: 1u64.into(), + msg: None, + }, + ), + Err(ContractError::Unauthorized {}) + )); + + // burn token1 + assert_eq!( + contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(user1.as_ref(), &[]), + ExecuteMsg::Burn { + from: user1.clone(), + token_id: token1.clone(), + value: 1u64.into(), + } + ) + .unwrap(), + Response::new() + .add_attribute("action", "transfer") + .add_attribute("token_id", &token1) + .add_attribute("amount", 1u64.to_string()) + .add_attribute("from", &user1) + ); + + // burn them all + assert_eq!( + contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(user1.as_ref(), &[]), + ExecuteMsg::BatchBurn { + from: user1.clone(), + batch: vec![(token2.clone(), 1u64.into()), (token3.clone(), 1u64.into())] + } + ) + .unwrap(), + Response::new() + .add_attribute("action", "transfer") + .add_attribute("token_id", &token2) + .add_attribute("amount", 1u64.to_string()) + .add_attribute("from", &user1) + .add_attribute("action", "transfer") + .add_attribute("token_id", &token3) + .add_attribute("amount", 1u64.to_string()) + .add_attribute("from", &user1) + ); + } + + #[test] + fn check_send_contract() { + let contract = Cw1155Contract::default(); + let receiver = String::from("receive_contract"); + let minter = String::from("minter"); + let user1 = String::from("user1"); + let token2 = "token2".to_owned(); + let dummy_msg = Binary::default(); + + let mut deps = mock_dependencies(); + let msg = InstantiateMsg { + minter: minter.clone(), + }; + let res = contract + .instantiate(deps.as_mut(), mock_env(), mock_info("operator", &[]), msg) + .unwrap(); + assert_eq!(0, res.messages.len()); + + contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(minter.as_ref(), &[]), + ExecuteMsg::Mint(MintMsg:: { + to: user1.clone(), + token_id: token2.clone(), + value: 1u64.into(), + token_uri: None, + extension: None, + }), + ) + .unwrap(); + + // BatchSendFrom + assert_eq!( + contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(user1.as_ref(), &[]), + ExecuteMsg::BatchSendFrom { + from: user1.clone(), + to: receiver.clone(), + batch: vec![(token2.clone(), 1u64.into())], + msg: Some(dummy_msg.clone()), + }, + ) + .unwrap(), + Response::new() + .add_message( + Cw1155BatchReceiveMsg { + operator: user1.clone(), + from: Some(user1.clone()), + batch: vec![(token2.clone(), 1u64.into())], + msg: dummy_msg, + } + .into_cosmos_msg(receiver.clone()) + .unwrap() + ) + .add_attribute("action", "transfer") + .add_attribute("token_id", &token2) + .add_attribute("amount", 1u64.to_string()) + .add_attribute("from", &user1) + .add_attribute("to", &receiver) + ); + } + + #[test] + fn check_queries() { + let contract = Cw1155Contract::default(); + // mint multiple types of tokens, and query them + // grant approval to multiple operators, and query them + let tokens = (0..10).map(|i| format!("token{}", i)).collect::>(); + let users = (0..10).map(|i| format!("user{}", i)).collect::>(); + let minter = String::from("minter"); + + let mut deps = mock_dependencies(); + let msg = InstantiateMsg { + minter: minter.clone(), + }; + let res = contract + .instantiate(deps.as_mut(), mock_env(), mock_info("operator", &[]), msg) + .unwrap(); + assert_eq!(0, res.messages.len()); + + for token_id in tokens.clone() { + contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(minter.as_ref(), &[]), + ExecuteMsg::Mint(MintMsg:: { + to: users[0].clone(), + token_id: token_id.clone(), + value: 1u64.into(), + token_uri: None, + extension: None, + }), + ) + .unwrap(); + } + + for user in users[1..].iter() { + contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(minter.as_ref(), &[]), + ExecuteMsg::Mint(MintMsg:: { + to: user.clone(), + token_id: tokens[9].clone(), + value: 1u64.into(), + token_uri: None, + extension: None, + }), + ) + .unwrap(); + } + + assert_eq!( + contract.query( + deps.as_ref(), + mock_env(), + Cw1155QueryMsg::NumTokens { + token_id: tokens[0].clone(), + }, + ), + to_binary(&NumTokensResponse { + count: Uint128::new(1), + }) + ); + + assert_eq!( + contract.query( + deps.as_ref(), + mock_env(), + Cw1155QueryMsg::NumTokens { + token_id: tokens[0].clone(), + }, + ), + to_binary(&NumTokensResponse { + count: Uint128::new(1), + }) + ); + + assert_eq!( + contract.query( + deps.as_ref(), + mock_env(), + Cw1155QueryMsg::AllBalances { + token_id: tokens[9].clone(), + start_after: None, + limit: Some(5) + }, + ), + to_binary(&AllBalancesResponse { + balances: users[..5] + .iter() + .map(|user| { + Balance { + owner: Addr::unchecked(user), + amount: Uint128::new(1), + token_id: tokens[9].clone(), + } + }) + .collect(), + }) + ); + + assert_eq!( + contract.query( + deps.as_ref(), + mock_env(), + Cw1155QueryMsg::AllBalances { + token_id: tokens[9].clone(), + start_after: Some("user5".to_owned()), + limit: Some(5) + }, + ), + to_binary(&AllBalancesResponse { + balances: users[6..] + .iter() + .map(|user| { + Balance { + owner: Addr::unchecked(user), + amount: Uint128::new(1), + token_id: tokens[9].clone(), + } + }) + .collect(), + }) + ); + + assert_eq!( + contract.query( + deps.as_ref(), + mock_env(), + Cw1155QueryMsg::Tokens { + owner: users[0].clone(), + start_after: None, + limit: Some(5), + }, + ), + to_binary(&TokensResponse { + tokens: tokens[..5].to_owned() + }) + ); + + assert_eq!( + contract.query( + deps.as_ref(), + mock_env(), + Cw1155QueryMsg::Tokens { + owner: users[0].clone(), + start_after: Some("token5".to_owned()), + limit: Some(5), + }, + ), + to_binary(&TokensResponse { + tokens: tokens[6..].to_owned() + }) + ); + + assert_eq!( + contract.query( + deps.as_ref(), + mock_env(), + Cw1155QueryMsg::AllTokens { + start_after: Some("token5".to_owned()), + limit: Some(5), + }, + ), + to_binary(&TokensResponse { + tokens: tokens[6..].to_owned() + }) + ); + + assert_eq!( + contract.query( + deps.as_ref(), + mock_env(), + Cw1155QueryMsg::TokenInfo { + token_id: "token5".to_owned() + }, + ), + to_binary(&TokenInfoResponse:: { + token_uri: None, + extension: None, + }), + ); + + for user in users[1..].iter() { + contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(users[0].as_ref(), &[]), + ExecuteMsg::ApproveAll { + operator: user.clone(), + expires: None, + }, + ) + .unwrap(); + } + + assert_eq!( + contract.query( + deps.as_ref(), + mock_env(), + Cw1155QueryMsg::ApprovedForAll { + owner: users[0].clone(), + include_expired: None, + start_after: Some(String::from("user2")), + limit: Some(1), + }, + ), + to_binary(&ApprovedForAllResponse { + operators: vec![cw1155::Approval { + spender: users[3].clone(), + expires: Expiration::Never {} + }], + }) + ); + } + + #[test] + fn approval_expires() { + let contract = Cw1155Contract::default(); + let mut deps = mock_dependencies(); + let token1 = "token1".to_owned(); + let minter = String::from("minter"); + let user1 = String::from("user1"); + let user2 = String::from("user2"); + + let env = { + let mut env = mock_env(); + env.block.height = 10; + env + }; + + let msg = InstantiateMsg { + minter: minter.clone(), + }; + let res = contract + .instantiate(deps.as_mut(), env.clone(), mock_info("operator", &[]), msg) + .unwrap(); + assert_eq!(0, res.messages.len()); + + contract + .execute( + deps.as_mut(), + env.clone(), + mock_info(minter.as_ref(), &[]), + ExecuteMsg::Mint(MintMsg:: { + to: user1.clone(), + token_id: token1, + value: 1u64.into(), + token_uri: None, + extension: None, + }), + ) + .unwrap(); + + // invalid expires should be rejected + assert!(matches!( + contract.execute( + deps.as_mut(), + env.clone(), + mock_info(user1.as_ref(), &[]), + ExecuteMsg::ApproveAll { + operator: user2.clone(), + expires: Some(Expiration::AtHeight(5)), + }, + ), + Err(_) + )); + + contract + .execute( + deps.as_mut(), + env.clone(), + mock_info(user1.as_ref(), &[]), + ExecuteMsg::ApproveAll { + operator: user2.clone(), + expires: Some(Expiration::AtHeight(100)), + }, + ) + .unwrap(); + + let query_msg = Cw1155QueryMsg::IsApprovedForAll { + owner: user1, + operator: user2, + }; + assert_eq!( + contract.query(deps.as_ref(), env, query_msg.clone()), + to_binary(&IsApprovedForAllResponse { approved: true }) + ); + + let env = { + let mut env = mock_env(); + env.block.height = 100; + env + }; + + assert_eq!( + contract.query(deps.as_ref(), env, query_msg,), + to_binary(&IsApprovedForAllResponse { approved: false }) + ); + } + + #[test] + fn mint_overflow() { + let contract = Cw1155Contract::default(); + let mut deps = mock_dependencies(); + let token1 = "token1".to_owned(); + let minter = String::from("minter"); + let user1 = String::from("user1"); + + let env = mock_env(); + let msg = InstantiateMsg { + minter: minter.clone(), + }; + let res = contract + .instantiate(deps.as_mut(), env.clone(), mock_info("operator", &[]), msg) + .unwrap(); + assert_eq!(0, res.messages.len()); + + contract + .execute( + deps.as_mut(), + env.clone(), + mock_info(minter.as_ref(), &[]), + ExecuteMsg::Mint(MintMsg:: { + to: user1.clone(), + token_id: token1.clone(), + value: u128::MAX.into(), + token_uri: None, + extension: None, + }), + ) + .unwrap(); + + assert!(matches!( + contract.execute( + deps.as_mut(), + env, + mock_info(minter.as_ref(), &[]), + ExecuteMsg::Mint(MintMsg:: { + to: user1, + token_id: token1, + value: 1u64.into(), + token_uri: None, + extension: None, + }), + ), + Err(ContractError::Std(StdError::Overflow { + source: OverflowError { .. }, + .. + })) + )); + } + + #[test] + fn token_uri() { + let contract = Cw1155Contract::default(); + let minter = String::from("minter"); + let user1 = String::from("user1"); + let token1 = "token1".to_owned(); + let url1 = "url1".to_owned(); + let url2 = "url2".to_owned(); + + let mut deps = mock_dependencies(); + let msg = InstantiateMsg { + minter: minter.clone(), + }; + let res = contract + .instantiate(deps.as_mut(), mock_env(), mock_info("operator", &[]), msg) + .unwrap(); + assert_eq!(0, res.messages.len()); + + // first mint + contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(minter.as_ref(), &[]), + ExecuteMsg::Mint(MintMsg:: { + to: user1.clone(), + token_id: token1.clone(), + value: 1u64.into(), + token_uri: Some(url1.clone()), + extension: None, + }), + ) + .unwrap(); + + assert_eq!( + contract.query( + deps.as_ref(), + mock_env(), + Cw1155QueryMsg::TokenInfo { + token_id: token1.clone() + } + ), + to_binary(&TokenInfoResponse:: { + token_uri: Some(url1.clone()), + extension: None, + }) + ); + + // mint after the first mint + contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(minter.as_ref(), &[]), + ExecuteMsg::Mint(MintMsg:: { + to: user1, + token_id: token1.clone(), + value: 1u64.into(), + token_uri: Some(url2), + extension: None, + }), + ) + .unwrap(); + + // url doesn't changed + assert_eq!( + contract.query( + deps.as_ref(), + mock_env(), + Cw1155QueryMsg::TokenInfo { token_id: token1 } + ), + to_binary(&TokenInfoResponse:: { + token_uri: Some(url1), + extension: None, + }) + ); + } +} diff --git a/contracts/cw1155-base/src/error.rs b/contracts/cw1155-base/src/error.rs new file mode 100644 index 000000000..950c88f7f --- /dev/null +++ b/contracts/cw1155-base/src/error.rs @@ -0,0 +1,17 @@ +use cosmwasm_std::{OverflowError, StdError}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + OverflowError(#[from] OverflowError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("Expired")] + Expired {}, +} diff --git a/contracts/cw1155-base/src/execute.rs b/contracts/cw1155-base/src/execute.rs new file mode 100644 index 000000000..607815806 --- /dev/null +++ b/contracts/cw1155-base/src/execute.rs @@ -0,0 +1,417 @@ +use serde::de::DeserializeOwned; +use serde::Serialize; + +use cosmwasm_std::{ + Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, SubMsg, Uint128, +}; + +use cw1155::{ + ApproveAllEvent, Balance, Cw1155BatchReceiveMsg, Cw1155ReceiveMsg, Expiration, TransferEvent, +}; +use cw2::set_contract_version; +use cw_utils::Event; + +use crate::error::ContractError; +use crate::msg::{ExecuteMsg, InstantiateMsg, MintMsg}; +use crate::state::{Cw1155Contract, TokenInfo}; + +// Version info for migration +const CONTRACT_NAME: &str = "crates.io:cw721-base"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +impl<'a, T> Cw1155Contract<'a, T> +where + T: Serialize + DeserializeOwned + Clone, +{ + pub fn instantiate( + &self, + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, + ) -> StdResult { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + let minter = deps.api.addr_validate(&msg.minter)?; + self.minter.save(deps.storage, &minter)?; + Ok(Response::default()) + } + + pub fn execute( + &self, + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, + ) -> Result { + let env = ExecuteEnv { deps, env, info }; + match msg { + ExecuteMsg::Mint(msg) => self.mint(env, msg), + ExecuteMsg::SendFrom { + from, + to, + token_id, + value, + msg, + } => self.send_from(env, from, to, token_id, value, msg), + ExecuteMsg::BatchSendFrom { + from, + to, + batch, + msg, + } => self.batch_send_from(env, from, to, batch, msg), + ExecuteMsg::Burn { + from, + token_id, + value, + } => self.burn(env, from, token_id, value), + ExecuteMsg::BatchBurn { from, batch } => self.batch_burn(env, from, batch), + ExecuteMsg::ApproveAll { operator, expires } => { + self.approve_all(env, operator, expires) + } + ExecuteMsg::RevokeAll { operator } => self.revoke_all(env, operator), + } + } +} + +/// To mitigate clippy::too_many_arguments warning +pub struct ExecuteEnv<'a> { + deps: DepsMut<'a>, + env: Env, + info: MessageInfo, +} + +// helper +impl<'a, T> Cw1155Contract<'a, T> +where + T: Serialize + DeserializeOwned + Clone, +{ + pub fn mint(&self, env: ExecuteEnv, msg: MintMsg) -> Result { + let ExecuteEnv { mut deps, info, .. } = env; + let to_addr = deps.api.addr_validate(&msg.to)?; + + if info.sender != self.minter.load(deps.storage)? { + return Err(ContractError::Unauthorized {}); + } + + let mut rsp = Response::default(); + + let event = self.execute_transfer_inner( + &mut deps, + None, + Some(to_addr), + msg.token_id.clone(), + msg.value, + )?; + event.add_attributes(&mut rsp); + + // insert if not exist (if it is the first mint) + if !self.tokens.has(deps.storage, &msg.token_id) { + // Add token info + let token_info = TokenInfo { + token_uri: msg.token_uri, + extension: msg.extension, + }; + + self.tokens.save(deps.storage, &msg.token_id, &token_info)?; + + // Increase num token + self.increment_tokens(deps.storage, &msg.token_id, &msg.value)?; + } + + Ok(rsp) + } + + pub fn send_from( + &self, + env: ExecuteEnv, + from: String, + to: String, + token_id: String, + amount: Uint128, + msg: Option, + ) -> Result { + let from_addr = env.deps.api.addr_validate(&from)?; + let to_addr = env.deps.api.addr_validate(&to)?; + + let ExecuteEnv { + mut deps, + env, + info, + } = env; + + self.guard_can_approve(deps.as_ref(), &env, &from_addr, &info.sender)?; + + let mut rsp = Response::default(); + + let event = self.execute_transfer_inner( + &mut deps, + Some(from_addr), + Some(to_addr), + token_id.clone(), + amount, + )?; + event.add_attributes(&mut rsp); + + if let Some(msg) = msg { + rsp.messages = vec![SubMsg::new( + Cw1155ReceiveMsg { + operator: info.sender.to_string(), + from: Some(from), + amount, + token_id, + msg, + } + .into_cosmos_msg(to)?, + )] + } + + Ok(rsp) + } + + pub fn batch_send_from( + &self, + env: ExecuteEnv, + from: String, + to: String, + batch: Vec<(String, Uint128)>, + msg: Option, + ) -> Result { + let ExecuteEnv { + mut deps, + env, + info, + } = env; + + let from_addr = deps.api.addr_validate(&from)?; + let to_addr = deps.api.addr_validate(&to)?; + + self.guard_can_approve(deps.as_ref(), &env, &from_addr, &info.sender)?; + + let mut rsp = Response::default(); + for (token_id, amount) in batch.iter() { + let event = self.execute_transfer_inner( + &mut deps, + Some(from_addr.clone()), + Some(to_addr.clone()), + token_id.clone(), + *amount, + )?; + event.add_attributes(&mut rsp); + } + + if let Some(msg) = msg { + rsp.messages = vec![SubMsg::new( + Cw1155BatchReceiveMsg { + operator: info.sender.to_string(), + from: Some(from), + batch, + msg, + } + .into_cosmos_msg(to)?, + )] + }; + + Ok(rsp) + } + + pub fn burn( + &self, + env: ExecuteEnv, + from: String, + token_id: String, + amount: Uint128, + ) -> Result { + let ExecuteEnv { + mut deps, + info, + env, + } = env; + + let from_addr = deps.api.addr_validate(&from)?; + + // whoever can transfer these tokens can burn + self.guard_can_approve(deps.as_ref(), &env, &from_addr, &info.sender)?; + + let mut rsp = Response::default(); + let event = self.execute_transfer_inner( + &mut deps, + Some(from_addr), + None, + token_id.clone(), + amount, + )?; + + self.decrement_tokens(deps.storage, &token_id, &amount)?; + + event.add_attributes(&mut rsp); + Ok(rsp) + } + + pub fn batch_burn( + &self, + env: ExecuteEnv, + from: String, + batch: Vec<(String, Uint128)>, + ) -> Result { + let ExecuteEnv { + mut deps, + info, + env, + } = env; + + let from_addr = deps.api.addr_validate(&from)?; + + self.guard_can_approve(deps.as_ref(), &env, &from_addr, &info.sender)?; + + let mut rsp = Response::default(); + for (token_id, amount) in batch.into_iter() { + let event = self.execute_transfer_inner( + &mut deps, + Some(from_addr.clone()), + None, + token_id.clone(), + amount, + )?; + + self.decrement_tokens(deps.storage, &token_id, &amount)?; + + event.add_attributes(&mut rsp); + } + + Ok(rsp) + } + + pub fn approve_all( + &self, + env: ExecuteEnv, + operator: String, + expires: Option, + ) -> Result { + let ExecuteEnv { deps, info, env } = env; + + // reject expired data as invalid + let expires = expires.unwrap_or_default(); + if expires.is_expired(&env.block) { + return Err(ContractError::Expired {}); + } + + // set the operator for us + let operator_addr = deps.api.addr_validate(&operator)?; + self.approves + .save(deps.storage, (&info.sender, &operator_addr), &expires)?; + + let mut rsp = Response::default(); + ApproveAllEvent { + sender: &info.sender.to_string(), + operator: &operator, + approved: true, + } + .add_attributes(&mut rsp); + Ok(rsp) + } + + pub fn revoke_all(&self, env: ExecuteEnv, operator: String) -> Result { + let ExecuteEnv { deps, info, .. } = env; + let operator_addr = deps.api.addr_validate(&operator)?; + self.approves + .remove(deps.storage, (&info.sender, &operator_addr)); + + let mut rsp = Response::default(); + ApproveAllEvent { + sender: &info.sender.to_string(), + operator: &operator, + approved: false, + } + .add_attributes(&mut rsp); + Ok(rsp) + } + + /// When from is None: mint new coins + /// When to is None: burn coins + /// When both are None: no token balance is changed, pointless but valid + /// + /// Make sure permissions are checked before calling this. + fn execute_transfer_inner( + &self, + deps: &mut DepsMut, + from: Option, + to: Option, + token_id: String, + amount: Uint128, + ) -> Result { + if let Some(from_addr) = from.clone() { + self.balances.update( + deps.storage, + (from_addr, token_id.clone()), + |balance: Option| -> StdResult<_> { + let mut new_balance = balance.unwrap(); + new_balance.amount = new_balance.amount.checked_sub(amount)?; + Ok(new_balance) + }, + )?; + } + + if let Some(to_addr) = to.clone() { + self.balances.update( + deps.storage, + (to_addr.clone(), token_id.clone()), + |balance: Option| -> StdResult<_> { + let mut new_balance: Balance = if let Some(balance) = balance { + balance + } else { + Balance { + owner: to_addr.clone(), + amount: Uint128::new(0), + token_id: token_id.clone(), + } + }; + + new_balance.amount = new_balance.amount.checked_add(amount)?; + Ok(new_balance) + }, + )?; + } + + Ok(TransferEvent { + from: from.map(|x| x.to_string()), + to: to.map(|x| x.to_string()), + token_id: token_id.clone(), + amount, + }) + } + + /// returns true if the sender can execute approve or reject on the contract + pub fn check_can_approve( + &self, + deps: Deps, + env: &Env, + owner: &Addr, + operator: &Addr, + ) -> StdResult { + // owner can approve + if owner == operator { + return Ok(true); + } + // operator can approve + let op = self.approves.may_load(deps.storage, (owner, operator))?; + Ok(match op { + Some(ex) => !ex.is_expired(&env.block), + None => false, + }) + } + + fn guard_can_approve( + &self, + deps: Deps, + env: &Env, + owner: &Addr, + operator: &Addr, + ) -> Result<(), ContractError> { + if !self.check_can_approve(deps, env, owner, operator)? { + Err(ContractError::Unauthorized {}) + } else { + Ok(()) + } + } +} diff --git a/contracts/cw1155-base/src/lib.rs b/contracts/cw1155-base/src/lib.rs new file mode 100644 index 000000000..a80681288 --- /dev/null +++ b/contracts/cw1155-base/src/lib.rs @@ -0,0 +1,52 @@ +mod contract_tests; +mod error; +mod execute; +mod msg; +mod query; +mod state; + +pub use crate::error::ContractError; +pub use crate::msg::{ExecuteMsg, InstantiateMsg, MintMsg}; +pub use crate::state::Cw1155Contract; +use cosmwasm_std::Empty; +use cw1155::Cw1155QueryMsg; + +// This is a simple type to let us handle empty extensions +pub type Extension = Option; + +pub mod entry { + use super::*; + + #[cfg(not(feature = "library"))] + use cosmwasm_std::entry_point; + use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; + + // This makes a conscious choice on the various generics used by the contract + #[cfg_attr(not(feature = "library"), entry_point)] + pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, + ) -> StdResult { + let tract = Cw1155Contract::::default(); + tract.instantiate(deps, env, info, msg) + } + + #[cfg_attr(not(feature = "library"), entry_point)] + pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, + ) -> Result { + let tract = Cw1155Contract::::default(); + tract.execute(deps, env, info, msg) + } + + #[cfg_attr(not(feature = "library"), entry_point)] + pub fn query(deps: Deps, env: Env, msg: Cw1155QueryMsg) -> StdResult { + let tract = Cw1155Contract::::default(); + tract.query(deps, env, msg) + } +} diff --git a/contracts/cw1155-base/src/msg.rs b/contracts/cw1155-base/src/msg.rs new file mode 100644 index 000000000..8b53fef36 --- /dev/null +++ b/contracts/cw1155-base/src/msg.rs @@ -0,0 +1,79 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{Binary, Uint128}; +use cw1155::Expiration; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct InstantiateMsg { + /// The minter is the only one who can create new tokens. + /// This is designed for a base token platform that is controlled by an external program or + /// contract. + pub minter: String, +} + +/// This is like Cw1155ExecuteMsg but we add a Mint command for a minter +/// to make this stand-alone. You will likely want to remove mint and +/// use other control logic in any contract that inherits this. +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + /// SendFrom is a base message to move tokens, + /// if `env.sender` is the owner or has sufficient pre-approval. + SendFrom { + from: String, + /// If `to` is not contract, `msg` should be `None` + to: String, + token_id: String, + value: Uint128, + /// `None` means don't call the receiver interface + msg: Option, + }, + /// BatchSendFrom is a base message to move multiple types of tokens in batch, + /// if `env.sender` is the owner or has sufficient pre-approval. + BatchSendFrom { + from: String, + /// if `to` is not contract, `msg` should be `None` + to: String, + batch: Vec<(String, Uint128)>, + /// `None` means don't call the receiver interface + msg: Option, + }, + /// Burn is a base message to burn tokens. + Burn { + from: String, + token_id: String, + value: Uint128, + }, + /// BatchBurn is a base message to burn multiple types of tokens in batch. + BatchBurn { + from: String, + batch: Vec<(String, Uint128)>, + }, + /// Allows operator to transfer / send any token from the owner's account. + /// If expiration is set, then this allowance has a time/height limit + ApproveAll { + operator: String, + expires: Option, + }, + /// Remove previously granted ApproveAll permission + RevokeAll { operator: String }, + + /// Mint a new NFT, can only be called by the contract minter + Mint(MintMsg), +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct MintMsg { + pub token_id: String, + /// The owner of the newly minted tokens + pub to: String, + /// The amount of the newly minted tokens + pub value: Uint128, + + /// Only first mint can set `token_uri` and `extension` + /// Metadata JSON Schema + pub token_uri: Option, + /// Any custom extension used by this contract + pub extension: Option, +} diff --git a/contracts/cw1155-base/src/query.rs b/contracts/cw1155-base/src/query.rs new file mode 100644 index 000000000..6041b55e9 --- /dev/null +++ b/contracts/cw1155-base/src/query.rs @@ -0,0 +1,217 @@ +use serde::de::DeserializeOwned; +use serde::Serialize; + +use cosmwasm_std::{to_binary, Addr, Binary, Deps, Env, Order, StdResult, Uint128}; + +use cw1155::{ + AllBalancesResponse, Approval, ApprovedForAllResponse, Balance, BalanceResponse, + BatchBalanceResponse, Cw1155QueryMsg, Expiration, IsApprovedForAllResponse, MinterResponse, + NumTokensResponse, TokenInfoResponse, TokensResponse, +}; +use cw_storage_plus::Bound; +use cw_utils::maybe_addr; + +use crate::state::Cw1155Contract; + +const DEFAULT_LIMIT: u32 = 10; +const MAX_LIMIT: u32 = 100; + +impl<'a, T> Cw1155Contract<'a, T> +where + T: Serialize + DeserializeOwned + Clone, +{ + pub fn query(&self, deps: Deps, env: Env, msg: Cw1155QueryMsg) -> StdResult { + match msg { + Cw1155QueryMsg::Minter {} => { + let minter = self.minter.load(deps.storage)?.to_string(); + to_binary(&MinterResponse { minter }) + } + Cw1155QueryMsg::Balance { owner, token_id } => { + let owner_addr = deps.api.addr_validate(&owner)?; + let balance = self + .balances + .may_load(deps.storage, (owner_addr.clone(), token_id.clone()))? + .unwrap_or(Balance { + owner: owner_addr, + token_id, + amount: Uint128::new(0), + }); + to_binary(&BalanceResponse { + balance: balance.amount, + }) + } + Cw1155QueryMsg::AllBalances { + token_id, + start_after, + limit, + } => to_binary(&self.query_all_balances(deps, token_id, start_after, limit)?), + Cw1155QueryMsg::BatchBalance { owner, token_ids } => { + let owner_addr = deps.api.addr_validate(&owner)?; + let balances = token_ids + .into_iter() + .map(|token_id| -> StdResult<_> { + Ok(self + .balances + .may_load(deps.storage, (owner_addr.clone(), token_id.clone()))? + .unwrap_or(Balance { + owner: owner_addr.clone(), + token_id, + amount: Uint128::new(0), + }) + .amount) + }) + .collect::>()?; + to_binary(&BatchBalanceResponse { balances }) + } + Cw1155QueryMsg::NumTokens { token_id } => { + let count = self.token_count(deps.storage, &token_id)?; + to_binary(&NumTokensResponse { count }) + } + Cw1155QueryMsg::IsApprovedForAll { owner, operator } => { + let owner_addr = deps.api.addr_validate(&owner)?; + let operator_addr = deps.api.addr_validate(&operator)?; + let approved = self.check_can_approve(deps, &env, &owner_addr, &operator_addr)?; + to_binary(&IsApprovedForAllResponse { approved }) + } + Cw1155QueryMsg::ApprovedForAll { + owner, + include_expired, + start_after, + limit, + } => { + let owner_addr = deps.api.addr_validate(&owner)?; + let start_addr = maybe_addr(deps.api, start_after)?; + to_binary(&self.query_all_approvals( + deps, + env, + owner_addr, + include_expired.unwrap_or(false), + start_addr, + limit, + )?) + } + Cw1155QueryMsg::TokenInfo { token_id } => { + let token_info = self.tokens.load(deps.storage, &token_id)?; + to_binary(&TokenInfoResponse:: { + token_uri: token_info.token_uri, + extension: token_info.extension, + }) + } + Cw1155QueryMsg::Tokens { + owner, + start_after, + limit, + } => { + let owner_addr = deps.api.addr_validate(&owner)?; + to_binary(&self.query_tokens(deps, owner_addr, start_after, limit)?) + } + Cw1155QueryMsg::AllTokens { start_after, limit } => { + to_binary(&self.query_all_tokens(deps, start_after, limit)?) + } + } + } +} + +impl<'a, T> Cw1155Contract<'a, T> +where + T: Serialize + DeserializeOwned + Clone, +{ + fn query_all_approvals( + &self, + deps: Deps, + env: Env, + owner: Addr, + include_expired: bool, + start_after: Option, + limit: Option, + ) -> StdResult { + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + let start = start_after.as_ref().map(Bound::exclusive); + + let operators = self + .approves + .prefix(&owner) + .range(deps.storage, start, None, Order::Ascending) + .filter(|r| { + include_expired || r.is_err() || !r.as_ref().unwrap().1.is_expired(&env.block) + }) + .take(limit) + .map(build_approval) + .collect::>()?; + Ok(ApprovedForAllResponse { operators }) + } + + fn query_tokens( + &self, + deps: Deps, + owner: Addr, + start_after: Option, + limit: Option, + ) -> StdResult { + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + let start = start_after.as_ref().map(|s| Bound::exclusive(s.as_str())); + + let tokens = self + .balances + .prefix(owner) + .keys(deps.storage, start, None, Order::Ascending) + .take(limit) + .collect::>()?; + Ok(TokensResponse { tokens }) + } + + fn query_all_tokens( + &self, + deps: Deps, + start_after: Option, + limit: Option, + ) -> StdResult { + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + let start = start_after.as_ref().map(|s| Bound::exclusive(s.as_str())); + let tokens = self + .tokens + .keys(deps.storage, start, None, Order::Ascending) + .take(limit) + .collect::>()?; + Ok(TokensResponse { tokens }) + } + + fn query_all_balances( + &self, + deps: Deps, + token_id: String, + start_after: Option, + limit: Option, + ) -> StdResult { + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + + let start = if let Some(start_after) = start_after { + let start_key = (Addr::unchecked(start_after), token_id.clone()); + Some(Bound::exclusive::<(Addr, String)>(start_key)) + } else { + None + }; + + let balances: Vec = self + .balances + .idx + .token_id + .prefix(token_id) + .range(deps.storage, start, None, Order::Ascending) + .take(limit) + .map(|item| { + let (_, v) = item.unwrap(); + v + }) + .collect(); + + Ok(AllBalancesResponse { balances }) + } +} + +fn build_approval(item: StdResult<(Addr, Expiration)>) -> StdResult { + item.map(|(addr, expires)| Approval { + spender: addr.into(), + expires, + }) +} diff --git a/contracts/cw1155-base/src/state.rs b/contracts/cw1155-base/src/state.rs new file mode 100644 index 000000000..7ba9c71b6 --- /dev/null +++ b/contracts/cw1155-base/src/state.rs @@ -0,0 +1,112 @@ +use schemars::JsonSchema; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{Addr, StdResult, Storage, Uint128}; + +use cw1155::{Balance, Expiration}; +use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, MultiIndex}; + +pub struct Cw1155Contract<'a, T> +where + T: Serialize + DeserializeOwned + Clone, +{ + pub minter: Item<'a, Addr>, + pub token_count: Map<'a, &'a str, Uint128>, + pub balances: IndexedMap<'a, (Addr, String), Balance, BalanceIndexes<'a>>, + pub approves: Map<'a, (&'a Addr, &'a Addr), Expiration>, + pub tokens: Map<'a, &'a str, TokenInfo>, +} + +impl<'a, T> Default for Cw1155Contract<'static, T> +where + T: Serialize + DeserializeOwned + Clone, +{ + fn default() -> Self { + Self::new( + "minter", + "token_count", + "balances", + "approves", + "tokens", + "balances__token_id", + ) + } +} + +impl<'a, T> Cw1155Contract<'a, T> +where + T: Serialize + DeserializeOwned + Clone, +{ + fn new( + minter_key: &'a str, + token_count_key: &'a str, + balances_key: &'a str, + approves_key: &'a str, + tokens_key: &'a str, + balances_token_id_key: &'a str, + ) -> Self { + let indexes = BalanceIndexes { + token_id: MultiIndex::new(balances_token_id_idx, balances_key, balances_token_id_key), + }; + Self { + minter: Item::new(minter_key), + token_count: Map::new(token_count_key), + balances: IndexedMap::new(balances_key, indexes), + approves: Map::new(approves_key), + tokens: Map::new(tokens_key), + } + } + + pub fn token_count(&self, storage: &dyn Storage, token_id: &'a str) -> StdResult { + Ok(self + .token_count + .may_load(storage, token_id)? + .unwrap_or_default()) + } + + pub fn increment_tokens( + &self, + storage: &mut dyn Storage, + token_id: &'a str, + amount: &Uint128, + ) -> StdResult { + let val = self.token_count(storage, token_id)? + amount; + self.token_count.save(storage, token_id, &val)?; + Ok(val) + } + + pub fn decrement_tokens( + &self, + storage: &mut dyn Storage, + token_id: &'a str, + amount: &Uint128, + ) -> StdResult { + let val = self.token_count(storage, token_id)?.checked_sub(*amount)?; + self.token_count.save(storage, token_id, &val)?; + Ok(val) + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct TokenInfo { + /// Metadata JSON Schema + pub token_uri: Option, + /// You can add any custom metadata here when you extend cw1155-base + pub extension: Option, +} + +pub struct BalanceIndexes<'a> { + pub token_id: MultiIndex<'a, String, Balance, (Addr, String)>, +} + +impl<'a> IndexList for BalanceIndexes<'a> { + fn get_indexes(&'_ self) -> Box> + '_> { + let v: Vec<&dyn Index> = vec![&self.token_id]; + Box::new(v.into_iter()) + } +} + +pub fn balances_token_id_idx(d: &Balance) -> String { + d.token_id.clone() +} diff --git a/packages/cw1155/.cargo/config b/packages/cw1155/.cargo/config new file mode 100644 index 000000000..b613a59f1 --- /dev/null +++ b/packages/cw1155/.cargo/config @@ -0,0 +1,4 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +wasm-debug = "build --target wasm32-unknown-unknown" +schema = "run --example schema" diff --git a/packages/cw1155/Cargo.toml b/packages/cw1155/Cargo.toml new file mode 100644 index 000000000..d7f517c17 --- /dev/null +++ b/packages/cw1155/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "cw1155" +version = "0.13.4" +authors = [ +] +edition = "2018" +description = "Definition and types for the CosmWasm-1155 interface" +license = "Apache-2.0" +repository = "https://github.com/CosmWasm/cw-nfts" +homepage = "https://cosmwasm.com" +documentation = "https://docs.cosmwasm.com" + +[dependencies] +cw-utils = "0.13.4" +cosmwasm-std = { version = "1.0.0" } +schemars = "0.8.10" +serde = { version = "1.0.140", default-features = false, features = ["derive"] } + +[dev-dependencies] +cosmwasm-schema = { version = "1.0.0" } diff --git a/packages/cw1155/README.md b/packages/cw1155/README.md new file mode 100644 index 000000000..c4851929e --- /dev/null +++ b/packages/cw1155/README.md @@ -0,0 +1,104 @@ +# CW1155 Spec: Multiple Tokens + +CW1155 is a specification for managing multiple tokens based on CosmWasm. +The name and design is based on Ethereum's ERC1155 standard. + +The specification is split into multiple sections, a contract may only +implement some of this functionality, but must implement the base. + +Fungible tokens and non-fungible tokens are treated equally, non-fungible tokens just have one max supply. + +Approval is set or unset to some operator over entire set of tokens. (More nuanced control is defined in +[ERC1761](https://eips.ethereum.org/EIPS/eip-1761)) + +## Base + +### Messages + +`SendFrom{from, to, token_id, value, msg}` - This transfers some amount of tokens between two accounts. If `to` is an +address controlled by a smart contract, it must implement the `CW1155Receiver` interface, `msg` will be passed to it +along with other fields, otherwise, `msg` should be `None`. The operator should either be the `from` account or have +approval from it. + +`BatchSendFrom{from, to, batch: Vec<(token_id, value)>, msg}` - Batched version of `SendFrom` which can handle multiple +types of tokens at once. + +`Burn {from, token_id, value}` - This burns some tokens from `from` account. + +`BatchBurn {from, batch: Vec<(token_id, value)>}` - Batched version of `Burn`. + +`ApproveAll{operator, expires}` - Allows operator to transfer / send any token from the owner's account. If expiration +is set, then this allowance has a time/height limit. + +`RevokeAll {operator}` - Remove previously granted ApproveAll permission + +### Queries + +`Minter {}` - Query Minter. + +`Balance {owner, token_id}` - Query the balance of `owner` on particular type of token, default to `0` when record not +exist. + +`AllBalances {token_id, start_after, limit}` - Query all balances of the given `token_id`. + +`BatchBalance {owner, token_ids}` - Query the balance of `owner` on multiple types of tokens, batched version of +`Balance`. + +`NumTokens {token_id}` - Total number of tokens of `token_id` issued. + +`ApprovedForAll{owner, include_expired, start_after, limit}` - List all operators that can access all of the owner's +tokens. Return type is `ApprovedForAllResponse`. If `include_expired` is set, show expired owners in the results, +otherwise, ignore them. + +`IsApprovedForAll{owner, operator}` - Query approved status `owner` granted to `operator`. Return type is +`IsApprovedForAllResponse`. + +### Receiver + +Any contract wish to receive CW1155 tokens must implement `Cw1155ReceiveMsg` and `Cw1155BatchReceiveMsg`. + +`Cw1155ReceiveMsg {operator, from, token_id, amount, msg}` - + +`Cw1155BatchReceiveMsg {operator, from, batch, msg}` - + +### Events + +- `transfer(from, to, token_id, value)` + + `from`/`to` are optional, no `from` attribute means minting, no `to` attribute means burning, but they mustn't be +neglected at the same time. + + +## Metadata + +### Queries + +`TokenInfo{token_id}` - Query metadata and token url of `token_id`. + +### Events + +`token_info(url, token_id)` + +Metadata url of `token_id` is changed, `url` should point to a json file. + +## Enumerable + +### Queries + +Pagination is acheived via `start_after` and `limit`. Limit is a request +set by the client, if unset, the contract will automatically set it to +`DefaultLimit` (suggested 10). If set, it will be used up to a `MaxLimit` +value (suggested 30). Contracts can define other `DefaultLimit` and `MaxLimit` +values without violating the CW1155 spec, and clients should not rely on +any particular values. + +If `start_after` is unset, the query returns the first results, ordered by +lexogaphically by `token_id`. If `start_after` is set, then it returns the +first `limit` tokens *after* the given one. This allows straight-forward +pagination by taking the last result returned (a `token_id`) and using it +as the `start_after` value in a future query. + +`Tokens{owner, start_after, limit}` - List all token_ids that belong to a given owner. +Return type is `TokensResponse{tokens: Vec}`. + +`AllTokens{start_after, limit}` - Requires pagination. Lists all token_ids controlled by the contract. \ No newline at end of file diff --git a/packages/cw1155/src/event.rs b/packages/cw1155/src/event.rs new file mode 100644 index 000000000..c4541b945 --- /dev/null +++ b/packages/cw1155/src/event.rs @@ -0,0 +1,41 @@ +use cosmwasm_std::{attr, Response, Uint128}; +use cw_utils::Event; + +/// Tracks token transfer/mint/burn actions +pub struct TransferEvent { + pub from: Option, + pub to: Option, + pub token_id: String, + pub amount: Uint128, +} + +impl Event for TransferEvent { + fn add_attributes(&self, rsp: &mut Response) { + rsp.attributes.push(attr("action", "transfer")); + rsp.attributes.push(attr("token_id", self.token_id.clone())); + rsp.attributes.push(attr("amount", self.amount)); + if let Some(from) = self.from.clone() { + rsp.attributes.push(attr("from", from)); + } + if let Some(to) = self.to.clone() { + rsp.attributes.push(attr("to", to)); + } + } +} + +/// Tracks approve_all status changes +pub struct ApproveAllEvent<'a> { + pub sender: &'a String, + pub operator: &'a String, + pub approved: bool, +} + +impl<'a> Event for ApproveAllEvent<'a> { + fn add_attributes(&self, rsp: &mut Response) { + rsp.attributes.push(attr("action", "approve_all")); + rsp.attributes.push(attr("sender", self.sender)); + rsp.attributes.push(attr("operator", self.operator)); + rsp.attributes + .push(attr("approved", (self.approved as u32).to_string())); + } +} diff --git a/packages/cw1155/src/lib.rs b/packages/cw1155/src/lib.rs new file mode 100644 index 000000000..ededd825c --- /dev/null +++ b/packages/cw1155/src/lib.rs @@ -0,0 +1,17 @@ +mod event; +mod msg; +mod query; +mod receiver; + +pub use cw_utils::Expiration; + +pub use crate::receiver::{Cw1155BatchReceiveMsg, Cw1155ReceiveMsg}; + +pub use crate::msg::Cw1155ExecuteMsg; +pub use crate::query::{ + AllBalancesResponse, Approval, ApprovedForAllResponse, Balance, BalanceResponse, + BatchBalanceResponse, Cw1155QueryMsg, IsApprovedForAllResponse, MinterResponse, + NumTokensResponse, TokenInfoResponse, TokensResponse, +}; + +pub use crate::event::{ApproveAllEvent, TransferEvent}; diff --git a/packages/cw1155/src/msg.rs b/packages/cw1155/src/msg.rs new file mode 100644 index 000000000..21d6a63b8 --- /dev/null +++ b/packages/cw1155/src/msg.rs @@ -0,0 +1,51 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{Binary, Uint128}; +use cw_utils::Expiration; + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub enum Cw1155ExecuteMsg { + /// SendFrom is a base message to move tokens, + /// if `env.sender` is the owner or has sufficient pre-approval. + SendFrom { + from: String, + /// If `to` is not contract, `msg` should be `None` + to: String, + token_id: String, + value: Uint128, + /// `None` means don't call the receiver interface + msg: Option, + }, + /// BatchSendFrom is a base message to move multiple types of tokens in batch, + /// if `env.sender` is the owner or has sufficient pre-approval. + BatchSendFrom { + from: String, + /// if `to` is not contract, `msg` should be `None` + to: String, + batch: Vec<(String, Uint128)>, + /// `None` means don't call the receiver interface + msg: Option, + }, + /// Burn is a base message to burn tokens. + Burn { + from: String, + token_id: String, + value: Uint128, + }, + /// BatchBurn is a base message to burn multiple types of tokens in batch. + BatchBurn { + from: String, + batch: Vec<(String, Uint128)>, + }, + /// Allows operator to transfer / send any token from the owner's account. + /// If expiration is set, then this allowance has a time/height limit + ApproveAll { + operator: String, + expires: Option, + }, + + /// Remove previously granted ApproveAll permission + RevokeAll { operator: String }, +} diff --git a/packages/cw1155/src/query.rs b/packages/cw1155/src/query.rs new file mode 100644 index 000000000..f120b1936 --- /dev/null +++ b/packages/cw1155/src/query.rs @@ -0,0 +1,131 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{Addr, Uint128}; +use cw_utils::Expiration; + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub enum Cw1155QueryMsg { + /// Query Minter. + /// Return type: MinterResponse. + Minter {}, + /// Returns the current balance of the given address, 0 if unset. + /// Return type: BalanceResponse. + Balance { owner: String, token_id: String }, + /// Returns all current balances of the given token id. Supports pagination + /// Return type: AllBalancesResponse. + AllBalances { + token_id: String, + start_after: Option, + limit: Option, + }, + /// Returns the current balance of the given address for a batch of tokens, 0 if unset. + /// Return type: BatchBalanceResponse. + BatchBalance { + owner: String, + token_ids: Vec, + }, + /// Total number of tokens issued for the token id + NumTokens { token_id: String }, + /// List all operators that can access all of the owner's tokens. + /// Return type: ApprovedForAllResponse. + ApprovedForAll { + owner: String, + /// unset or false will filter out expired approvals, you must set to true to see them + include_expired: Option, + start_after: Option, + limit: Option, + }, + /// Query approved status `owner` granted to `operator`. + /// Return type: IsApprovedForAllResponse + IsApprovedForAll { owner: String, operator: String }, + + /// With MetaData Extension. + /// Query metadata of token + /// Return type: TokenInfoResponse. + TokenInfo { token_id: String }, + + /// With Enumerable extension. + /// Returns all tokens owned by the given address, [] if unset. + /// Return type: TokensResponse. + Tokens { + owner: String, + start_after: Option, + limit: Option, + }, + /// With Enumerable extension. + /// Requires pagination. Lists all token_ids controlled by the contract. + /// Return type: TokensResponse. + AllTokens { + start_after: Option, + limit: Option, + }, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct BalanceResponse { + pub balance: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct AllBalancesResponse { + pub balances: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct Balance { + pub token_id: String, + pub owner: Addr, + pub amount: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct BatchBalanceResponse { + pub balances: Vec, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct NumTokensResponse { + pub count: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct Approval { + /// Account that can transfer/send the token + pub spender: String, + /// When the Approval expires (maybe Expiration::never) + pub expires: Expiration, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct ApprovedForAllResponse { + pub operators: Vec, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct IsApprovedForAllResponse { + pub approved: bool, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct TokenInfoResponse { + /// Should be a url point to a json file + pub token_uri: Option, + /// You can add any custom metadata here when you extend cw1155-base + pub extension: Option, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct TokensResponse { + /// Contains all token_ids in lexicographical ordering + /// If there are more than `limit`, use `start_from` in future queries + /// to achieve pagination. + pub tokens: Vec, +} + +/// Shows who can mint these tokens +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct MinterResponse { + pub minter: String, +} diff --git a/packages/cw1155/src/receiver.rs b/packages/cw1155/src/receiver.rs new file mode 100644 index 000000000..409c0df77 --- /dev/null +++ b/packages/cw1155/src/receiver.rs @@ -0,0 +1,73 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{to_binary, Binary, CosmosMsg, StdResult, Uint128, WasmMsg}; + +/// Cw1155ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub struct Cw1155ReceiveMsg { + /// The account that executed the send message + pub operator: String, + /// The account that the token transfered from + pub from: Option, + pub token_id: String, + pub amount: Uint128, + pub msg: Binary, +} + +impl Cw1155ReceiveMsg { + /// serializes the message + pub fn into_binary(self) -> StdResult { + let msg = ReceiverExecuteMsg::Receive(self); + to_binary(&msg) + } + + /// creates a cosmos_msg sending this struct to the named contract + pub fn into_cosmos_msg>(self, contract_addr: T) -> StdResult { + let msg = self.into_binary()?; + let execute = WasmMsg::Execute { + contract_addr: contract_addr.into(), + msg, + funds: vec![], + }; + Ok(execute.into()) + } +} + +/// Cw1155BatchReceiveMsg should be de/serialized under `BatchReceive()` variant in a ExecuteMsg +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +pub struct Cw1155BatchReceiveMsg { + pub operator: String, + pub from: Option, + pub batch: Vec<(String, Uint128)>, + pub msg: Binary, +} + +impl Cw1155BatchReceiveMsg { + /// serializes the message + pub fn into_binary(self) -> StdResult { + let msg = ReceiverExecuteMsg::BatchReceive(self); + to_binary(&msg) + } + + /// creates a cosmos_msg sending this struct to the named contract + pub fn into_cosmos_msg>(self, contract_addr: T) -> StdResult { + let msg = self.into_binary()?; + let execute = WasmMsg::Execute { + contract_addr: contract_addr.into(), + msg, + funds: vec![], + }; + Ok(execute.into()) + } +} + +// This is just a helper to properly serialize the above message +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[serde(rename_all = "snake_case")] +enum ReceiverExecuteMsg { + Receive(Cw1155ReceiveMsg), + BatchReceive(Cw1155BatchReceiveMsg), +} From 26122c8c5b103e0d1b4ce38aae13991d41724cd6 Mon Sep 17 00:00:00 2001 From: ALPAC-4 Date: Thu, 18 Aug 2022 12:03:18 +0900 Subject: [PATCH 02/57] feat: add cw1155-metadata-onchain --- Cargo.lock | 14 ++ .../cw1155-metadata-onchain/.cargo/config | 5 + contracts/cw1155-metadata-onchain/Cargo.toml | 40 ++++++ contracts/cw1155-metadata-onchain/README.md | 64 +++++++++ contracts/cw1155-metadata-onchain/src/lib.rs | 130 ++++++++++++++++++ 5 files changed, 253 insertions(+) create mode 100644 contracts/cw1155-metadata-onchain/.cargo/config create mode 100644 contracts/cw1155-metadata-onchain/Cargo.toml create mode 100644 contracts/cw1155-metadata-onchain/README.md create mode 100644 contracts/cw1155-metadata-onchain/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 4ad106b64..40fffb22a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -337,6 +337,20 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw1155-metadata-onchain" +version = "0.13.4" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw1155", + "cw1155-base", + "cw2", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw2" version = "0.16.0" diff --git a/contracts/cw1155-metadata-onchain/.cargo/config b/contracts/cw1155-metadata-onchain/.cargo/config new file mode 100644 index 000000000..7d1a066c8 --- /dev/null +++ b/contracts/cw1155-metadata-onchain/.cargo/config @@ -0,0 +1,5 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +wasm-debug = "build --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --example schema" diff --git a/contracts/cw1155-metadata-onchain/Cargo.toml b/contracts/cw1155-metadata-onchain/Cargo.toml new file mode 100644 index 000000000..32ea77100 --- /dev/null +++ b/contracts/cw1155-metadata-onchain/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "cw1155-metadata-onchain" +version = "0.13.4" +authors = [ +] +edition = "2018" +description = "Example extending CW1155 Token to store metadata on chain" +license = "Apache-2.0" +repository = "https://github.com/CosmWasm/cw-nfts" +homepage = "https://cosmwasm.com" +documentation = "https://docs.cosmwasm.com" + +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "artifacts/*", +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +cw2 = "0.13.4" +cw1155 = { path = "../../packages/cw1155", version = "0.13.4" } +cw1155-base = { path = "../cw1155-base", version = "0.13.4", features = [ + "library", +] } +cosmwasm-std = { version = "1.0.0" } +schemars = "0.8.10" +serde = { version = "1.0.140", default-features = false, features = ["derive"] } +thiserror = { version = "1.0.31" } + +[dev-dependencies] +cosmwasm-schema = { version = "1.0.0" } diff --git a/contracts/cw1155-metadata-onchain/README.md b/contracts/cw1155-metadata-onchain/README.md new file mode 100644 index 000000000..fcc7d2e7a --- /dev/null +++ b/contracts/cw1155-metadata-onchain/README.md @@ -0,0 +1,64 @@ +# CW1155 Metadata Onchain + +Token creators may want to store their token metadata on-chain so other contracts are able to interact with it. +With CW1155-Base in CosmWasm, we allow you to store any data on chain you wish, using a generic `extension: T`. + +In order to support on-chain metadata, and to demonstrate how to use the extension ability, we have created this simple contract. +There is no business logic here, but looking at `lib.rs` will show you how do define custom data that is included when minting and +available in all queries. + +In particular, here we define: + +```rust +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct Trait { + pub display_type: Option, + pub trait_type: String, + pub value: String, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct Metadata { + pub image: Option, + pub image_data: Option, + pub external_url: Option, + pub description: Option, + pub name: Option, + pub attributes: Option>, + pub background_color: Option, + pub animation_url: Option, + pub youtube_url: Option, +} + +pub type Extension = Option; +``` + +In particular, the fields defined conform to the properties supported in the [OpenSea Metadata Standard](https://docs.opensea.io/docs/metadata-standards). + + +This means when you query `NftInfo{name: "Enterprise"}`, you will get something like: + +```json +{ + "name": "Enterprise", + "token_uri": "https://starships.example.com/Starship/Enterprise.json", + "extension": { + "image": null, + "image_data": null, + "external_url": null, + "description": "Spaceship with Warp Drive", + "name": "Starship USS Enterprise", + "attributes": null, + "background_color": null, + "animation_url": null, + "youtube_url": null + } +} +``` + +Please look at the test code for an example usage in Rust. + +## Notice + +Feel free to use this contract out of the box, or as inspiration for further customization of cw1155-base. +We will not be adding new features or business logic here. diff --git a/contracts/cw1155-metadata-onchain/src/lib.rs b/contracts/cw1155-metadata-onchain/src/lib.rs new file mode 100644 index 000000000..b920681a2 --- /dev/null +++ b/contracts/cw1155-metadata-onchain/src/lib.rs @@ -0,0 +1,130 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +pub use cw1155_base::{ContractError, InstantiateMsg, MintMsg}; +use cw2::set_contract_version; + +// Version info for migration +const CONTRACT_NAME: &str = "crates.io:cw1155-metadata-onchain"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug, Default)] +pub struct Trait { + pub display_type: Option, + pub trait_type: String, + pub value: String, +} + +// see: https://docs.opensea.io/docs/metadata-standards +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug, Default)] +pub struct Metadata { + pub image: Option, + pub image_data: Option, + pub external_url: Option, + pub description: Option, + pub name: Option, + pub attributes: Option>, + pub background_color: Option, + pub animation_url: Option, + pub youtube_url: Option, +} + +pub type Extension = Metadata; + +pub type Cw1155MetadataContract<'a> = cw1155_base::Cw1155Contract<'a, Extension>; +pub type ExecuteMsg = cw1155_base::ExecuteMsg; + +#[cfg(not(feature = "library"))] +pub mod entry { + use super::*; + + use cosmwasm_std::entry_point; + use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; + + // This makes a conscious choice on the various generics used by the contract + #[entry_point] + pub fn instantiate( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, + ) -> Result { + let res = Cw1155MetadataContract::default().instantiate(deps.branch(), env, info, msg)?; + // Explicitly set contract name and version, otherwise set to cw1155-base info + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION) + .map_err(ContractError::Std)?; + Ok(res) + } + + #[entry_point] + pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, + ) -> Result { + Cw1155MetadataContract::default().execute(deps, env, info, msg) + } + + #[entry_point] + pub fn query(deps: Deps, env: Env, msg: cw1155::Cw1155QueryMsg) -> StdResult { + Cw1155MetadataContract::default().query(deps, env, msg) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::{from_binary, Uint128}; + + const CREATOR: &str = "creator"; + + #[test] + fn use_metadata_extension() { + let mut deps = mock_dependencies(); + let contract = Cw1155MetadataContract::default(); + + let info = mock_info(CREATOR, &[]); + let init_msg = InstantiateMsg { + minter: CREATOR.to_string(), + }; + contract + .instantiate(deps.as_mut(), mock_env(), info.clone(), init_msg) + .unwrap(); + + let token_id = "Enterprise"; + let mint_msg = MintMsg { + token_id: token_id.to_string(), + to: "john".to_string(), + value: Uint128::new(1), + token_uri: Some("https://starships.example.com/Starship/Enterprise.json".into()), + extension: Some(Metadata { + description: Some("Spaceship with Warp Drive".into()), + name: Some("Starship USS Enterprise".to_string()), + ..Metadata::default() + }), + }; + let exec_msg = ExecuteMsg::Mint(mint_msg.clone()); + contract + .execute(deps.as_mut(), mock_env(), info, exec_msg) + .unwrap(); + + let res: cw1155::TokenInfoResponse = from_binary( + &contract + .query( + deps.as_ref(), + mock_env(), + cw1155::Cw1155QueryMsg::TokenInfo { + token_id: token_id.to_string(), + }, + ) + .unwrap(), + ) + .unwrap(); + + assert_eq!(res.token_uri, mint_msg.token_uri); + assert_eq!(res.extension, mint_msg.extension); + } +} From 78746b0fadad5ccf7a0a497114689f30c092e9d5 Mon Sep 17 00:00:00 2001 From: ALPAC-4 Date: Thu, 18 Aug 2022 12:17:52 +0900 Subject: [PATCH 03/57] feat: add jobs for cw1155 --- .circleci/config.yml | 72 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index cee9ad8f9..7dd91019c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -19,6 +19,78 @@ workflows: ignore: /.*/ jobs: + contract_cw1155_base: + docker: + - image: rust:1.58.1 + working_directory: ~/project/contracts/cw1155-base + steps: + - checkout: + path: ~/project + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - cargocache-cw1155-base-rust:1.58.1-{{ checksum "~/project/Cargo.lock" }} + - run: + name: Unit Tests + environment: + RUST_BACKTRACE: 1 + command: cargo unit-test --locked + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target + key: cargocache-cw1155-base-rust:1.58.1-{{ checksum "~/project/Cargo.lock" }} + +contract_cw1155_metadata_onchain: + docker: + - image: rust:1.58.1 + working_directory: ~/project/contracts/cw1155-metadata-onchain + steps: + - checkout: + path: ~/project + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - cargocache-cw1155-metadata-onchain-rust:1.58.1-{{ checksum "~/project/Cargo.lock" }} + - run: + name: Unit Tests + environment: + RUST_BACKTRACE: 1 + command: cargo unit-test --locked + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target + key: cargocache-cw1155-metadata-onchain-rust:1.58.1-{{ checksum "~/project/Cargo.lock" }} + contract_cw721_base: docker: - image: rust:1.65.0 From 8fe5edb10142b3c54eb318c382713783ccb24bd2 Mon Sep 17 00:00:00 2001 From: ALPAC-4 Date: Thu, 18 Aug 2022 12:24:14 +0900 Subject: [PATCH 04/57] fix: fix spaceing --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7dd91019c..643a6ad51 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -55,7 +55,7 @@ jobs: - target key: cargocache-cw1155-base-rust:1.58.1-{{ checksum "~/project/Cargo.lock" }} -contract_cw1155_metadata_onchain: + contract_cw1155_metadata_onchain: docker: - image: rust:1.58.1 working_directory: ~/project/contracts/cw1155-metadata-onchain From b70c8dc80aad97d4c7165887b3c96f9fc5a7606f Mon Sep 17 00:00:00 2001 From: ALPAC-4 Date: Thu, 18 Aug 2022 12:26:47 +0900 Subject: [PATCH 05/57] fix: add jobs to test and add package_cw1155 --- .circleci/config.yml | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 643a6ad51..51ecc1c92 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,10 +3,13 @@ workflows: version: 2 test: jobs: + - contract_cw1155_base + - contract_cw1155_metadata_onchain - contract_cw721_base - contract_cw721_metadata_onchain - contract_cw721_fixed_price - package_cw721 + - package_cw1155 - lint - wasm-build deploy: @@ -236,6 +239,43 @@ jobs: - target key: cargocache-v2-cw721:1.64.0-{{ checksum "~/project/Cargo.lock" }} + package_cw1155: + docker: + - image: rust:1.58.1 + working_directory: ~/project/packages/cw1155 + steps: + - checkout: + path: ~/project + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version; rustup target list --installed + - restore_cache: + keys: + - cargocache-v2-cw1155:1.58.1-{{ checksum "~/project/Cargo.lock" }} + - run: + name: Build library for native target + command: cargo build --locked + - run: + name: Run unit tests + command: cargo test --locked + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target + key: cargocache-v2-cw1155:1.58.1-{{ checksum "~/project/Cargo.lock" }} + lint: docker: - image: rust:1.65.0 From 5e3fd482294d3606be61fd946f38a9649d0dd040 Mon Sep 17 00:00:00 2001 From: ALPAC-4 Date: Thu, 18 Aug 2022 12:59:35 +0900 Subject: [PATCH 06/57] add schema --- contracts/cw1155-base/examples/schema.rs | 35 ++ .../schema/all_balances_response.json | 45 ++ .../schema/approved_for_all_response.json | 97 ++++ .../cw1155-base/schema/balance_response.json | 19 + .../schema/batch_balance_response.json | 22 + .../cw1155-base/schema/cw1155_query_msg.json | 280 ++++++++++++ contracts/cw1155-base/schema/execute_msg.json | 360 +++++++++++++++ .../cw1155-base/schema/instantiate_msg.json | 14 + .../schema/is_approved_for_all_response.json | 13 + .../cw1155-base/schema/minter_response.json | 14 + .../cw1155-base/schema/nft_info_response.json | 38 ++ .../schema/num_tokens_response.json | 19 + .../cw1155-base/schema/tokens_response.json | 17 + .../examples/schema.rs | 36 ++ .../schema/all_balances_response.json | 45 ++ .../schema/approved_for_all_response.json | 97 ++++ .../schema/balance_response.json | 19 + .../schema/batch_balance_response.json | 22 + .../schema/cw1155_query_msg.json | 280 ++++++++++++ .../schema/execute_msg.json | 432 ++++++++++++++++++ .../schema/instantiate_msg.json | 14 + .../schema/is_approved_for_all_response.json | 13 + .../schema/minter_response.json | 14 + .../schema/nft_info_response.json | 110 +++++ .../schema/num_tokens_response.json | 19 + .../schema/tokens_response.json | 17 + 26 files changed, 2091 insertions(+) create mode 100644 contracts/cw1155-base/examples/schema.rs create mode 100644 contracts/cw1155-base/schema/all_balances_response.json create mode 100644 contracts/cw1155-base/schema/approved_for_all_response.json create mode 100644 contracts/cw1155-base/schema/balance_response.json create mode 100644 contracts/cw1155-base/schema/batch_balance_response.json create mode 100644 contracts/cw1155-base/schema/cw1155_query_msg.json create mode 100644 contracts/cw1155-base/schema/execute_msg.json create mode 100644 contracts/cw1155-base/schema/instantiate_msg.json create mode 100644 contracts/cw1155-base/schema/is_approved_for_all_response.json create mode 100644 contracts/cw1155-base/schema/minter_response.json create mode 100644 contracts/cw1155-base/schema/nft_info_response.json create mode 100644 contracts/cw1155-base/schema/num_tokens_response.json create mode 100644 contracts/cw1155-base/schema/tokens_response.json create mode 100644 contracts/cw1155-metadata-onchain/examples/schema.rs create mode 100644 contracts/cw1155-metadata-onchain/schema/all_balances_response.json create mode 100644 contracts/cw1155-metadata-onchain/schema/approved_for_all_response.json create mode 100644 contracts/cw1155-metadata-onchain/schema/balance_response.json create mode 100644 contracts/cw1155-metadata-onchain/schema/batch_balance_response.json create mode 100644 contracts/cw1155-metadata-onchain/schema/cw1155_query_msg.json create mode 100644 contracts/cw1155-metadata-onchain/schema/execute_msg.json create mode 100644 contracts/cw1155-metadata-onchain/schema/instantiate_msg.json create mode 100644 contracts/cw1155-metadata-onchain/schema/is_approved_for_all_response.json create mode 100644 contracts/cw1155-metadata-onchain/schema/minter_response.json create mode 100644 contracts/cw1155-metadata-onchain/schema/nft_info_response.json create mode 100644 contracts/cw1155-metadata-onchain/schema/num_tokens_response.json create mode 100644 contracts/cw1155-metadata-onchain/schema/tokens_response.json diff --git a/contracts/cw1155-base/examples/schema.rs b/contracts/cw1155-base/examples/schema.rs new file mode 100644 index 000000000..02717dd38 --- /dev/null +++ b/contracts/cw1155-base/examples/schema.rs @@ -0,0 +1,35 @@ +use std::env::current_dir; +use std::fs::create_dir_all; + +use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for}; + +use cw1155::{ + AllBalancesResponse, ApprovedForAllResponse, BalanceResponse, BatchBalanceResponse, + Cw1155QueryMsg, IsApprovedForAllResponse, MinterResponse, NumTokensResponse, TokenInfoResponse, + TokensResponse, +}; +use cw1155_base::{ExecuteMsg, Extension, InstantiateMsg}; + +fn main() { + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + export_schema(&schema_for!(InstantiateMsg), &out_dir); + export_schema_with_title(&schema_for!(ExecuteMsg), &out_dir, "ExecuteMsg"); + export_schema(&schema_for!(Cw1155QueryMsg), &out_dir); + export_schema(&schema_for!(BalanceResponse), &out_dir); + export_schema(&schema_for!(AllBalancesResponse), &out_dir); + export_schema(&schema_for!(BatchBalanceResponse), &out_dir); + export_schema(&schema_for!(NumTokensResponse), &out_dir); + export_schema(&schema_for!(ApprovedForAllResponse), &out_dir); + export_schema(&schema_for!(IsApprovedForAllResponse), &out_dir); + export_schema(&schema_for!(TokensResponse), &out_dir); + export_schema(&schema_for!(MinterResponse), &out_dir); + export_schema_with_title( + &schema_for!(TokenInfoResponse), + &out_dir, + "NftInfoResponse", + ); +} diff --git a/contracts/cw1155-base/schema/all_balances_response.json b/contracts/cw1155-base/schema/all_balances_response.json new file mode 100644 index 000000000..06cc4f9d3 --- /dev/null +++ b/contracts/cw1155-base/schema/all_balances_response.json @@ -0,0 +1,45 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AllBalancesResponse", + "type": "object", + "required": [ + "balances" + ], + "properties": { + "balances": { + "type": "array", + "items": { + "$ref": "#/definitions/Balance" + } + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Balance": { + "type": "object", + "required": [ + "amount", + "owner", + "token_id" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "owner": { + "$ref": "#/definitions/Addr" + }, + "token_id": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cw1155-base/schema/approved_for_all_response.json b/contracts/cw1155-base/schema/approved_for_all_response.json new file mode 100644 index 000000000..453f17b7c --- /dev/null +++ b/contracts/cw1155-base/schema/approved_for_all_response.json @@ -0,0 +1,97 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ApprovedForAllResponse", + "type": "object", + "required": [ + "operators" + ], + "properties": { + "operators": { + "type": "array", + "items": { + "$ref": "#/definitions/Approval" + } + } + }, + "definitions": { + "Approval": { + "type": "object", + "required": [ + "expires", + "spender" + ], + "properties": { + "expires": { + "description": "When the Approval expires (maybe Expiration::never)", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] + }, + "spender": { + "description": "Account that can transfer/send the token", + "type": "string" + } + } + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cw1155-base/schema/balance_response.json b/contracts/cw1155-base/schema/balance_response.json new file mode 100644 index 000000000..4e1a0be2b --- /dev/null +++ b/contracts/cw1155-base/schema/balance_response.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "BalanceResponse", + "type": "object", + "required": [ + "balance" + ], + "properties": { + "balance": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cw1155-base/schema/batch_balance_response.json b/contracts/cw1155-base/schema/batch_balance_response.json new file mode 100644 index 000000000..39c8bd040 --- /dev/null +++ b/contracts/cw1155-base/schema/batch_balance_response.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "BatchBalanceResponse", + "type": "object", + "required": [ + "balances" + ], + "properties": { + "balances": { + "type": "array", + "items": { + "$ref": "#/definitions/Uint128" + } + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cw1155-base/schema/cw1155_query_msg.json b/contracts/cw1155-base/schema/cw1155_query_msg.json new file mode 100644 index 000000000..8bc2fe9c4 --- /dev/null +++ b/contracts/cw1155-base/schema/cw1155_query_msg.json @@ -0,0 +1,280 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw1155QueryMsg", + "oneOf": [ + { + "description": "Query Minter. Return type: MinterResponse.", + "type": "object", + "required": [ + "minter" + ], + "properties": { + "minter": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Returns the current balance of the given address, 0 if unset. Return type: BalanceResponse.", + "type": "object", + "required": [ + "balance" + ], + "properties": { + "balance": { + "type": "object", + "required": [ + "owner", + "token_id" + ], + "properties": { + "owner": { + "type": "string" + }, + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Returns all current balances of the given token id. Supports pagination Return type: AllBalancesResponse.", + "type": "object", + "required": [ + "all_balances" + ], + "properties": { + "all_balances": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + }, + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Returns the current balance of the given address for a batch of tokens, 0 if unset. Return type: BatchBalanceResponse.", + "type": "object", + "required": [ + "batch_balance" + ], + "properties": { + "batch_balance": { + "type": "object", + "required": [ + "owner", + "token_ids" + ], + "properties": { + "owner": { + "type": "string" + }, + "token_ids": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Total number of tokens issued for the token id", + "type": "object", + "required": [ + "num_tokens" + ], + "properties": { + "num_tokens": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "List all operators that can access all of the owner's tokens. Return type: ApprovedForAllResponse.", + "type": "object", + "required": [ + "approved_for_all" + ], + "properties": { + "approved_for_all": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "include_expired": { + "description": "unset or false will filter out expired approvals, you must set to true to see them", + "type": [ + "boolean", + "null" + ] + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Query approved status `owner` granted to `operator`. Return type: IsApprovedForAllResponse", + "type": "object", + "required": [ + "is_approved_for_all" + ], + "properties": { + "is_approved_for_all": { + "type": "object", + "required": [ + "operator", + "owner" + ], + "properties": { + "operator": { + "type": "string" + }, + "owner": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "With MetaData Extension. Query metadata of token Return type: TokenInfoResponse.", + "type": "object", + "required": [ + "token_info" + ], + "properties": { + "token_info": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "With Enumerable extension. Returns all tokens owned by the given address, [] if unset. Return type: TokensResponse.", + "type": "object", + "required": [ + "tokens" + ], + "properties": { + "tokens": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "With Enumerable extension. Requires pagination. Lists all token_ids controlled by the contract. Return type: TokensResponse.", + "type": "object", + "required": [ + "all_tokens" + ], + "properties": { + "all_tokens": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/cw1155-base/schema/execute_msg.json b/contracts/cw1155-base/schema/execute_msg.json new file mode 100644 index 000000000..871b83025 --- /dev/null +++ b/contracts/cw1155-base/schema/execute_msg.json @@ -0,0 +1,360 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "description": "This is like Cw1155ExecuteMsg but we add a Mint command for a minter to make this stand-alone. You will likely want to remove mint and use other control logic in any contract that inherits this.", + "oneOf": [ + { + "description": "SendFrom is a base message to move tokens, if `env.sender` is the owner or has sufficient pre-approval.", + "type": "object", + "required": [ + "send_from" + ], + "properties": { + "send_from": { + "type": "object", + "required": [ + "from", + "to", + "token_id", + "value" + ], + "properties": { + "from": { + "type": "string" + }, + "msg": { + "description": "`None` means don't call the receiver interface", + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "to": { + "description": "If `to` is not contract, `msg` should be `None`", + "type": "string" + }, + "token_id": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Uint128" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "BatchSendFrom is a base message to move multiple types of tokens in batch, if `env.sender` is the owner or has sufficient pre-approval.", + "type": "object", + "required": [ + "batch_send_from" + ], + "properties": { + "batch_send_from": { + "type": "object", + "required": [ + "batch", + "from", + "to" + ], + "properties": { + "batch": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/Uint128" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "from": { + "type": "string" + }, + "msg": { + "description": "`None` means don't call the receiver interface", + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "to": { + "description": "if `to` is not contract, `msg` should be `None`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Burn is a base message to burn tokens.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "from", + "token_id", + "value" + ], + "properties": { + "from": { + "type": "string" + }, + "token_id": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Uint128" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "BatchBurn is a base message to burn multiple types of tokens in batch.", + "type": "object", + "required": [ + "batch_burn" + ], + "properties": { + "batch_burn": { + "type": "object", + "required": [ + "batch", + "from" + ], + "properties": { + "batch": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/Uint128" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "from": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Allows operator to transfer / send any token from the owner's account. If expiration is set, then this allowance has a time/height limit", + "type": "object", + "required": [ + "approve_all" + ], + "properties": { + "approve_all": { + "type": "object", + "required": [ + "operator" + ], + "properties": { + "expires": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "operator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Remove previously granted ApproveAll permission", + "type": "object", + "required": [ + "revoke_all" + ], + "properties": { + "revoke_all": { + "type": "object", + "required": [ + "operator" + ], + "properties": { + "operator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Mint a new NFT, can only be called by the contract minter", + "type": "object", + "required": [ + "mint" + ], + "properties": { + "mint": { + "$ref": "#/definitions/MintMsg_for_Nullable_Empty" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "MintMsg_for_Nullable_Empty": { + "type": "object", + "required": [ + "to", + "token_id", + "value" + ], + "properties": { + "extension": { + "description": "Any custom extension used by this contract", + "anyOf": [ + { + "anyOf": [ + { + "$ref": "#/definitions/Empty" + }, + { + "type": "null" + } + ] + }, + { + "type": "null" + } + ] + }, + "to": { + "description": "The owner of the newly minted tokens", + "type": "string" + }, + "token_id": { + "type": "string" + }, + "token_uri": { + "description": "Only first mint can set `token_uri` and `extension` Metadata JSON Schema", + "type": [ + "string", + "null" + ] + }, + "value": { + "description": "The amount of the newly minted tokens", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + } + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cw1155-base/schema/instantiate_msg.json b/contracts/cw1155-base/schema/instantiate_msg.json new file mode 100644 index 000000000..3f5eaf0ce --- /dev/null +++ b/contracts/cw1155-base/schema/instantiate_msg.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "minter" + ], + "properties": { + "minter": { + "description": "The minter is the only one who can create new tokens. This is designed for a base token platform that is controlled by an external program or contract.", + "type": "string" + } + } +} diff --git a/contracts/cw1155-base/schema/is_approved_for_all_response.json b/contracts/cw1155-base/schema/is_approved_for_all_response.json new file mode 100644 index 000000000..e3af7a983 --- /dev/null +++ b/contracts/cw1155-base/schema/is_approved_for_all_response.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IsApprovedForAllResponse", + "type": "object", + "required": [ + "approved" + ], + "properties": { + "approved": { + "type": "boolean" + } + } +} diff --git a/contracts/cw1155-base/schema/minter_response.json b/contracts/cw1155-base/schema/minter_response.json new file mode 100644 index 000000000..a20e0d767 --- /dev/null +++ b/contracts/cw1155-base/schema/minter_response.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MinterResponse", + "description": "Shows who can mint these tokens", + "type": "object", + "required": [ + "minter" + ], + "properties": { + "minter": { + "type": "string" + } + } +} diff --git a/contracts/cw1155-base/schema/nft_info_response.json b/contracts/cw1155-base/schema/nft_info_response.json new file mode 100644 index 000000000..131329810 --- /dev/null +++ b/contracts/cw1155-base/schema/nft_info_response.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "NftInfoResponse", + "type": "object", + "properties": { + "extension": { + "description": "You can add any custom metadata here when you extend cw1155-base", + "anyOf": [ + { + "anyOf": [ + { + "$ref": "#/definitions/Empty" + }, + { + "type": "null" + } + ] + }, + { + "type": "null" + } + ] + }, + "token_uri": { + "description": "Should be a url point to a json file", + "type": [ + "string", + "null" + ] + } + }, + "definitions": { + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + } + } +} diff --git a/contracts/cw1155-base/schema/num_tokens_response.json b/contracts/cw1155-base/schema/num_tokens_response.json new file mode 100644 index 000000000..b19bc807e --- /dev/null +++ b/contracts/cw1155-base/schema/num_tokens_response.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "NumTokensResponse", + "type": "object", + "required": [ + "count" + ], + "properties": { + "count": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cw1155-base/schema/tokens_response.json b/contracts/cw1155-base/schema/tokens_response.json new file mode 100644 index 000000000..b8e3d75b5 --- /dev/null +++ b/contracts/cw1155-base/schema/tokens_response.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TokensResponse", + "type": "object", + "required": [ + "tokens" + ], + "properties": { + "tokens": { + "description": "Contains all token_ids in lexicographical ordering If there are more than `limit`, use `start_from` in future queries to achieve pagination.", + "type": "array", + "items": { + "type": "string" + } + } + } +} diff --git a/contracts/cw1155-metadata-onchain/examples/schema.rs b/contracts/cw1155-metadata-onchain/examples/schema.rs new file mode 100644 index 000000000..040aa1b0e --- /dev/null +++ b/contracts/cw1155-metadata-onchain/examples/schema.rs @@ -0,0 +1,36 @@ +use std::env::current_dir; +use std::fs::create_dir_all; + +use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for}; + +use cw1155::{ + AllBalancesResponse, ApprovedForAllResponse, BalanceResponse, BatchBalanceResponse, + Cw1155QueryMsg, IsApprovedForAllResponse, MinterResponse, NumTokensResponse, TokenInfoResponse, + TokensResponse, +}; + +use cw1155_metadata_onchain::{ExecuteMsg, Extension, InstantiateMsg}; + +fn main() { + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + export_schema(&schema_for!(InstantiateMsg), &out_dir); + export_schema_with_title(&schema_for!(ExecuteMsg), &out_dir, "ExecuteMsg"); + export_schema(&schema_for!(Cw1155QueryMsg), &out_dir); + export_schema(&schema_for!(BalanceResponse), &out_dir); + export_schema(&schema_for!(AllBalancesResponse), &out_dir); + export_schema(&schema_for!(BatchBalanceResponse), &out_dir); + export_schema(&schema_for!(NumTokensResponse), &out_dir); + export_schema(&schema_for!(ApprovedForAllResponse), &out_dir); + export_schema(&schema_for!(IsApprovedForAllResponse), &out_dir); + export_schema(&schema_for!(TokensResponse), &out_dir); + export_schema(&schema_for!(MinterResponse), &out_dir); + export_schema_with_title( + &schema_for!(TokenInfoResponse), + &out_dir, + "NftInfoResponse", + ); +} diff --git a/contracts/cw1155-metadata-onchain/schema/all_balances_response.json b/contracts/cw1155-metadata-onchain/schema/all_balances_response.json new file mode 100644 index 000000000..06cc4f9d3 --- /dev/null +++ b/contracts/cw1155-metadata-onchain/schema/all_balances_response.json @@ -0,0 +1,45 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AllBalancesResponse", + "type": "object", + "required": [ + "balances" + ], + "properties": { + "balances": { + "type": "array", + "items": { + "$ref": "#/definitions/Balance" + } + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Balance": { + "type": "object", + "required": [ + "amount", + "owner", + "token_id" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "owner": { + "$ref": "#/definitions/Addr" + }, + "token_id": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cw1155-metadata-onchain/schema/approved_for_all_response.json b/contracts/cw1155-metadata-onchain/schema/approved_for_all_response.json new file mode 100644 index 000000000..453f17b7c --- /dev/null +++ b/contracts/cw1155-metadata-onchain/schema/approved_for_all_response.json @@ -0,0 +1,97 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ApprovedForAllResponse", + "type": "object", + "required": [ + "operators" + ], + "properties": { + "operators": { + "type": "array", + "items": { + "$ref": "#/definitions/Approval" + } + } + }, + "definitions": { + "Approval": { + "type": "object", + "required": [ + "expires", + "spender" + ], + "properties": { + "expires": { + "description": "When the Approval expires (maybe Expiration::never)", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] + }, + "spender": { + "description": "Account that can transfer/send the token", + "type": "string" + } + } + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cw1155-metadata-onchain/schema/balance_response.json b/contracts/cw1155-metadata-onchain/schema/balance_response.json new file mode 100644 index 000000000..4e1a0be2b --- /dev/null +++ b/contracts/cw1155-metadata-onchain/schema/balance_response.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "BalanceResponse", + "type": "object", + "required": [ + "balance" + ], + "properties": { + "balance": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cw1155-metadata-onchain/schema/batch_balance_response.json b/contracts/cw1155-metadata-onchain/schema/batch_balance_response.json new file mode 100644 index 000000000..39c8bd040 --- /dev/null +++ b/contracts/cw1155-metadata-onchain/schema/batch_balance_response.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "BatchBalanceResponse", + "type": "object", + "required": [ + "balances" + ], + "properties": { + "balances": { + "type": "array", + "items": { + "$ref": "#/definitions/Uint128" + } + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cw1155-metadata-onchain/schema/cw1155_query_msg.json b/contracts/cw1155-metadata-onchain/schema/cw1155_query_msg.json new file mode 100644 index 000000000..8bc2fe9c4 --- /dev/null +++ b/contracts/cw1155-metadata-onchain/schema/cw1155_query_msg.json @@ -0,0 +1,280 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw1155QueryMsg", + "oneOf": [ + { + "description": "Query Minter. Return type: MinterResponse.", + "type": "object", + "required": [ + "minter" + ], + "properties": { + "minter": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Returns the current balance of the given address, 0 if unset. Return type: BalanceResponse.", + "type": "object", + "required": [ + "balance" + ], + "properties": { + "balance": { + "type": "object", + "required": [ + "owner", + "token_id" + ], + "properties": { + "owner": { + "type": "string" + }, + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Returns all current balances of the given token id. Supports pagination Return type: AllBalancesResponse.", + "type": "object", + "required": [ + "all_balances" + ], + "properties": { + "all_balances": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + }, + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Returns the current balance of the given address for a batch of tokens, 0 if unset. Return type: BatchBalanceResponse.", + "type": "object", + "required": [ + "batch_balance" + ], + "properties": { + "batch_balance": { + "type": "object", + "required": [ + "owner", + "token_ids" + ], + "properties": { + "owner": { + "type": "string" + }, + "token_ids": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Total number of tokens issued for the token id", + "type": "object", + "required": [ + "num_tokens" + ], + "properties": { + "num_tokens": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "List all operators that can access all of the owner's tokens. Return type: ApprovedForAllResponse.", + "type": "object", + "required": [ + "approved_for_all" + ], + "properties": { + "approved_for_all": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "include_expired": { + "description": "unset or false will filter out expired approvals, you must set to true to see them", + "type": [ + "boolean", + "null" + ] + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Query approved status `owner` granted to `operator`. Return type: IsApprovedForAllResponse", + "type": "object", + "required": [ + "is_approved_for_all" + ], + "properties": { + "is_approved_for_all": { + "type": "object", + "required": [ + "operator", + "owner" + ], + "properties": { + "operator": { + "type": "string" + }, + "owner": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "With MetaData Extension. Query metadata of token Return type: TokenInfoResponse.", + "type": "object", + "required": [ + "token_info" + ], + "properties": { + "token_info": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "With Enumerable extension. Returns all tokens owned by the given address, [] if unset. Return type: TokensResponse.", + "type": "object", + "required": [ + "tokens" + ], + "properties": { + "tokens": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "With Enumerable extension. Requires pagination. Lists all token_ids controlled by the contract. Return type: TokensResponse.", + "type": "object", + "required": [ + "all_tokens" + ], + "properties": { + "all_tokens": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/cw1155-metadata-onchain/schema/execute_msg.json b/contracts/cw1155-metadata-onchain/schema/execute_msg.json new file mode 100644 index 000000000..d2d3f6cd7 --- /dev/null +++ b/contracts/cw1155-metadata-onchain/schema/execute_msg.json @@ -0,0 +1,432 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "description": "This is like Cw1155ExecuteMsg but we add a Mint command for a minter to make this stand-alone. You will likely want to remove mint and use other control logic in any contract that inherits this.", + "oneOf": [ + { + "description": "SendFrom is a base message to move tokens, if `env.sender` is the owner or has sufficient pre-approval.", + "type": "object", + "required": [ + "send_from" + ], + "properties": { + "send_from": { + "type": "object", + "required": [ + "from", + "to", + "token_id", + "value" + ], + "properties": { + "from": { + "type": "string" + }, + "msg": { + "description": "`None` means don't call the receiver interface", + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "to": { + "description": "If `to` is not contract, `msg` should be `None`", + "type": "string" + }, + "token_id": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Uint128" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "BatchSendFrom is a base message to move multiple types of tokens in batch, if `env.sender` is the owner or has sufficient pre-approval.", + "type": "object", + "required": [ + "batch_send_from" + ], + "properties": { + "batch_send_from": { + "type": "object", + "required": [ + "batch", + "from", + "to" + ], + "properties": { + "batch": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/Uint128" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "from": { + "type": "string" + }, + "msg": { + "description": "`None` means don't call the receiver interface", + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "to": { + "description": "if `to` is not contract, `msg` should be `None`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Burn is a base message to burn tokens.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "from", + "token_id", + "value" + ], + "properties": { + "from": { + "type": "string" + }, + "token_id": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Uint128" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "BatchBurn is a base message to burn multiple types of tokens in batch.", + "type": "object", + "required": [ + "batch_burn" + ], + "properties": { + "batch_burn": { + "type": "object", + "required": [ + "batch", + "from" + ], + "properties": { + "batch": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/Uint128" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "from": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Allows operator to transfer / send any token from the owner's account. If expiration is set, then this allowance has a time/height limit", + "type": "object", + "required": [ + "approve_all" + ], + "properties": { + "approve_all": { + "type": "object", + "required": [ + "operator" + ], + "properties": { + "expires": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "operator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Remove previously granted ApproveAll permission", + "type": "object", + "required": [ + "revoke_all" + ], + "properties": { + "revoke_all": { + "type": "object", + "required": [ + "operator" + ], + "properties": { + "operator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Mint a new NFT, can only be called by the contract minter", + "type": "object", + "required": [ + "mint" + ], + "properties": { + "mint": { + "$ref": "#/definitions/MintMsg_for_Metadata" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "Metadata": { + "type": "object", + "properties": { + "animation_url": { + "type": [ + "string", + "null" + ] + }, + "attributes": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Trait" + } + }, + "background_color": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "external_url": { + "type": [ + "string", + "null" + ] + }, + "image": { + "type": [ + "string", + "null" + ] + }, + "image_data": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "youtube_url": { + "type": [ + "string", + "null" + ] + } + } + }, + "MintMsg_for_Metadata": { + "type": "object", + "required": [ + "to", + "token_id", + "value" + ], + "properties": { + "extension": { + "description": "Any custom extension used by this contract", + "anyOf": [ + { + "$ref": "#/definitions/Metadata" + }, + { + "type": "null" + } + ] + }, + "to": { + "description": "The owner of the newly minted tokens", + "type": "string" + }, + "token_id": { + "type": "string" + }, + "token_uri": { + "description": "Only first mint can set `token_uri` and `extension` Metadata JSON Schema", + "type": [ + "string", + "null" + ] + }, + "value": { + "description": "The amount of the newly minted tokens", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + } + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Trait": { + "type": "object", + "required": [ + "trait_type", + "value" + ], + "properties": { + "display_type": { + "type": [ + "string", + "null" + ] + }, + "trait_type": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cw1155-metadata-onchain/schema/instantiate_msg.json b/contracts/cw1155-metadata-onchain/schema/instantiate_msg.json new file mode 100644 index 000000000..3f5eaf0ce --- /dev/null +++ b/contracts/cw1155-metadata-onchain/schema/instantiate_msg.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "minter" + ], + "properties": { + "minter": { + "description": "The minter is the only one who can create new tokens. This is designed for a base token platform that is controlled by an external program or contract.", + "type": "string" + } + } +} diff --git a/contracts/cw1155-metadata-onchain/schema/is_approved_for_all_response.json b/contracts/cw1155-metadata-onchain/schema/is_approved_for_all_response.json new file mode 100644 index 000000000..e3af7a983 --- /dev/null +++ b/contracts/cw1155-metadata-onchain/schema/is_approved_for_all_response.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IsApprovedForAllResponse", + "type": "object", + "required": [ + "approved" + ], + "properties": { + "approved": { + "type": "boolean" + } + } +} diff --git a/contracts/cw1155-metadata-onchain/schema/minter_response.json b/contracts/cw1155-metadata-onchain/schema/minter_response.json new file mode 100644 index 000000000..a20e0d767 --- /dev/null +++ b/contracts/cw1155-metadata-onchain/schema/minter_response.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MinterResponse", + "description": "Shows who can mint these tokens", + "type": "object", + "required": [ + "minter" + ], + "properties": { + "minter": { + "type": "string" + } + } +} diff --git a/contracts/cw1155-metadata-onchain/schema/nft_info_response.json b/contracts/cw1155-metadata-onchain/schema/nft_info_response.json new file mode 100644 index 000000000..af2899e19 --- /dev/null +++ b/contracts/cw1155-metadata-onchain/schema/nft_info_response.json @@ -0,0 +1,110 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "NftInfoResponse", + "type": "object", + "properties": { + "extension": { + "description": "You can add any custom metadata here when you extend cw1155-base", + "anyOf": [ + { + "$ref": "#/definitions/Metadata" + }, + { + "type": "null" + } + ] + }, + "token_uri": { + "description": "Should be a url point to a json file", + "type": [ + "string", + "null" + ] + } + }, + "definitions": { + "Metadata": { + "type": "object", + "properties": { + "animation_url": { + "type": [ + "string", + "null" + ] + }, + "attributes": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Trait" + } + }, + "background_color": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "external_url": { + "type": [ + "string", + "null" + ] + }, + "image": { + "type": [ + "string", + "null" + ] + }, + "image_data": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "youtube_url": { + "type": [ + "string", + "null" + ] + } + } + }, + "Trait": { + "type": "object", + "required": [ + "trait_type", + "value" + ], + "properties": { + "display_type": { + "type": [ + "string", + "null" + ] + }, + "trait_type": { + "type": "string" + }, + "value": { + "type": "string" + } + } + } + } +} diff --git a/contracts/cw1155-metadata-onchain/schema/num_tokens_response.json b/contracts/cw1155-metadata-onchain/schema/num_tokens_response.json new file mode 100644 index 000000000..b19bc807e --- /dev/null +++ b/contracts/cw1155-metadata-onchain/schema/num_tokens_response.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "NumTokensResponse", + "type": "object", + "required": [ + "count" + ], + "properties": { + "count": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cw1155-metadata-onchain/schema/tokens_response.json b/contracts/cw1155-metadata-onchain/schema/tokens_response.json new file mode 100644 index 000000000..b8e3d75b5 --- /dev/null +++ b/contracts/cw1155-metadata-onchain/schema/tokens_response.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TokensResponse", + "type": "object", + "required": [ + "tokens" + ], + "properties": { + "tokens": { + "description": "Contains all token_ids in lexicographical ordering If there are more than `limit`, use `start_from` in future queries to achieve pagination.", + "type": "array", + "items": { + "type": "string" + } + } + } +} From 753cb6be7ba1216e2cb2a805566616e6f09a93c5 Mon Sep 17 00:00:00 2001 From: ALPAC-4 Date: Thu, 18 Aug 2022 13:07:36 +0900 Subject: [PATCH 07/57] fix: add package schema, fix typo --- contracts/cw1155-base/examples/schema.rs | 2 +- ...response.json => token_info_response.json} | 2 +- .../examples/schema.rs | 2 +- ...response.json => token_info_response.json} | 2 +- packages/cw1155/examples/schema.rs | 36 +++ .../cw1155/schema/all_balances_response.json | 45 +++ .../schema/approved_for_all_response.json | 97 ++++++ packages/cw1155/schema/balance_response.json | 19 ++ .../cw1155/schema/batch_balance_response.json | 22 ++ packages/cw1155/schema/cw1155_query_msg.json | 280 +++++++++++++++++ .../cw1155/schema/cw1155_receive_msg.json | 44 +++ packages/cw1155/schema/execute_msg.json | 292 ++++++++++++++++++ .../schema/is_approved_for_all_response.json | 13 + packages/cw1155/schema/minter_response.json | 14 + .../cw1155/schema/num_tokens_response.json | 19 ++ .../cw1155/schema/token_info_response.json | 31 ++ packages/cw1155/schema/tokens_response.json | 17 + 17 files changed, 933 insertions(+), 4 deletions(-) rename contracts/cw1155-base/schema/{nft_info_response.json => token_info_response.json} (97%) rename contracts/cw1155-metadata-onchain/schema/{nft_info_response.json => token_info_response.json} (98%) create mode 100644 packages/cw1155/examples/schema.rs create mode 100644 packages/cw1155/schema/all_balances_response.json create mode 100644 packages/cw1155/schema/approved_for_all_response.json create mode 100644 packages/cw1155/schema/balance_response.json create mode 100644 packages/cw1155/schema/batch_balance_response.json create mode 100644 packages/cw1155/schema/cw1155_query_msg.json create mode 100644 packages/cw1155/schema/cw1155_receive_msg.json create mode 100644 packages/cw1155/schema/execute_msg.json create mode 100644 packages/cw1155/schema/is_approved_for_all_response.json create mode 100644 packages/cw1155/schema/minter_response.json create mode 100644 packages/cw1155/schema/num_tokens_response.json create mode 100644 packages/cw1155/schema/token_info_response.json create mode 100644 packages/cw1155/schema/tokens_response.json diff --git a/contracts/cw1155-base/examples/schema.rs b/contracts/cw1155-base/examples/schema.rs index 02717dd38..adf56a58d 100644 --- a/contracts/cw1155-base/examples/schema.rs +++ b/contracts/cw1155-base/examples/schema.rs @@ -30,6 +30,6 @@ fn main() { export_schema_with_title( &schema_for!(TokenInfoResponse), &out_dir, - "NftInfoResponse", + "TokenInfoResponse", ); } diff --git a/contracts/cw1155-base/schema/nft_info_response.json b/contracts/cw1155-base/schema/token_info_response.json similarity index 97% rename from contracts/cw1155-base/schema/nft_info_response.json rename to contracts/cw1155-base/schema/token_info_response.json index 131329810..3426e872d 100644 --- a/contracts/cw1155-base/schema/nft_info_response.json +++ b/contracts/cw1155-base/schema/token_info_response.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "NftInfoResponse", + "title": "TokenInfoResponse", "type": "object", "properties": { "extension": { diff --git a/contracts/cw1155-metadata-onchain/examples/schema.rs b/contracts/cw1155-metadata-onchain/examples/schema.rs index 040aa1b0e..96635c996 100644 --- a/contracts/cw1155-metadata-onchain/examples/schema.rs +++ b/contracts/cw1155-metadata-onchain/examples/schema.rs @@ -31,6 +31,6 @@ fn main() { export_schema_with_title( &schema_for!(TokenInfoResponse), &out_dir, - "NftInfoResponse", + "TokenInfoResponse", ); } diff --git a/contracts/cw1155-metadata-onchain/schema/nft_info_response.json b/contracts/cw1155-metadata-onchain/schema/token_info_response.json similarity index 98% rename from contracts/cw1155-metadata-onchain/schema/nft_info_response.json rename to contracts/cw1155-metadata-onchain/schema/token_info_response.json index af2899e19..a305674e8 100644 --- a/contracts/cw1155-metadata-onchain/schema/nft_info_response.json +++ b/contracts/cw1155-metadata-onchain/schema/token_info_response.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "NftInfoResponse", + "title": "TokenInfoResponse", "type": "object", "properties": { "extension": { diff --git a/packages/cw1155/examples/schema.rs b/packages/cw1155/examples/schema.rs new file mode 100644 index 000000000..4ca9ed1e8 --- /dev/null +++ b/packages/cw1155/examples/schema.rs @@ -0,0 +1,36 @@ +use std::env::current_dir; +use std::fs::create_dir_all; + +use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for}; +use cosmwasm_std::Empty; + +use cw1155::{ + AllBalancesResponse, ApprovedForAllResponse, BalanceResponse, BatchBalanceResponse, + Cw1155ExecuteMsg, Cw1155QueryMsg, Cw1155ReceiveMsg, IsApprovedForAllResponse, MinterResponse, + NumTokensResponse, TokenInfoResponse, TokensResponse, +}; + +type Extension = Empty; + +fn main() { + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + export_schema_with_title(&schema_for!(Cw1155ExecuteMsg), &out_dir, "ExecuteMsg"); + export_schema(&schema_for!(Cw1155QueryMsg), &out_dir); + export_schema(&schema_for!(Cw1155ReceiveMsg), &out_dir); + export_schema(&schema_for!(BalanceResponse), &out_dir); + export_schema(&schema_for!(AllBalancesResponse), &out_dir); + export_schema(&schema_for!(BatchBalanceResponse), &out_dir); + export_schema(&schema_for!(NumTokensResponse), &out_dir); + export_schema(&schema_for!(ApprovedForAllResponse), &out_dir); + export_schema(&schema_for!(IsApprovedForAllResponse), &out_dir); + export_schema(&schema_for!(TokensResponse), &out_dir); + export_schema(&schema_for!(MinterResponse), &out_dir); + export_schema_with_title( + &schema_for!(TokenInfoResponse), + &out_dir, + "TokenInfoResponse", + ); +} diff --git a/packages/cw1155/schema/all_balances_response.json b/packages/cw1155/schema/all_balances_response.json new file mode 100644 index 000000000..06cc4f9d3 --- /dev/null +++ b/packages/cw1155/schema/all_balances_response.json @@ -0,0 +1,45 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AllBalancesResponse", + "type": "object", + "required": [ + "balances" + ], + "properties": { + "balances": { + "type": "array", + "items": { + "$ref": "#/definitions/Balance" + } + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Balance": { + "type": "object", + "required": [ + "amount", + "owner", + "token_id" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "owner": { + "$ref": "#/definitions/Addr" + }, + "token_id": { + "type": "string" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/packages/cw1155/schema/approved_for_all_response.json b/packages/cw1155/schema/approved_for_all_response.json new file mode 100644 index 000000000..453f17b7c --- /dev/null +++ b/packages/cw1155/schema/approved_for_all_response.json @@ -0,0 +1,97 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ApprovedForAllResponse", + "type": "object", + "required": [ + "operators" + ], + "properties": { + "operators": { + "type": "array", + "items": { + "$ref": "#/definitions/Approval" + } + } + }, + "definitions": { + "Approval": { + "type": "object", + "required": [ + "expires", + "spender" + ], + "properties": { + "expires": { + "description": "When the Approval expires (maybe Expiration::never)", + "allOf": [ + { + "$ref": "#/definitions/Expiration" + } + ] + }, + "spender": { + "description": "Account that can transfer/send the token", + "type": "string" + } + } + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/packages/cw1155/schema/balance_response.json b/packages/cw1155/schema/balance_response.json new file mode 100644 index 000000000..4e1a0be2b --- /dev/null +++ b/packages/cw1155/schema/balance_response.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "BalanceResponse", + "type": "object", + "required": [ + "balance" + ], + "properties": { + "balance": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/packages/cw1155/schema/batch_balance_response.json b/packages/cw1155/schema/batch_balance_response.json new file mode 100644 index 000000000..39c8bd040 --- /dev/null +++ b/packages/cw1155/schema/batch_balance_response.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "BatchBalanceResponse", + "type": "object", + "required": [ + "balances" + ], + "properties": { + "balances": { + "type": "array", + "items": { + "$ref": "#/definitions/Uint128" + } + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/packages/cw1155/schema/cw1155_query_msg.json b/packages/cw1155/schema/cw1155_query_msg.json new file mode 100644 index 000000000..8bc2fe9c4 --- /dev/null +++ b/packages/cw1155/schema/cw1155_query_msg.json @@ -0,0 +1,280 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw1155QueryMsg", + "oneOf": [ + { + "description": "Query Minter. Return type: MinterResponse.", + "type": "object", + "required": [ + "minter" + ], + "properties": { + "minter": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Returns the current balance of the given address, 0 if unset. Return type: BalanceResponse.", + "type": "object", + "required": [ + "balance" + ], + "properties": { + "balance": { + "type": "object", + "required": [ + "owner", + "token_id" + ], + "properties": { + "owner": { + "type": "string" + }, + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Returns all current balances of the given token id. Supports pagination Return type: AllBalancesResponse.", + "type": "object", + "required": [ + "all_balances" + ], + "properties": { + "all_balances": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + }, + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Returns the current balance of the given address for a batch of tokens, 0 if unset. Return type: BatchBalanceResponse.", + "type": "object", + "required": [ + "batch_balance" + ], + "properties": { + "batch_balance": { + "type": "object", + "required": [ + "owner", + "token_ids" + ], + "properties": { + "owner": { + "type": "string" + }, + "token_ids": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Total number of tokens issued for the token id", + "type": "object", + "required": [ + "num_tokens" + ], + "properties": { + "num_tokens": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "List all operators that can access all of the owner's tokens. Return type: ApprovedForAllResponse.", + "type": "object", + "required": [ + "approved_for_all" + ], + "properties": { + "approved_for_all": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "include_expired": { + "description": "unset or false will filter out expired approvals, you must set to true to see them", + "type": [ + "boolean", + "null" + ] + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Query approved status `owner` granted to `operator`. Return type: IsApprovedForAllResponse", + "type": "object", + "required": [ + "is_approved_for_all" + ], + "properties": { + "is_approved_for_all": { + "type": "object", + "required": [ + "operator", + "owner" + ], + "properties": { + "operator": { + "type": "string" + }, + "owner": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "With MetaData Extension. Query metadata of token Return type: TokenInfoResponse.", + "type": "object", + "required": [ + "token_info" + ], + "properties": { + "token_info": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "With Enumerable extension. Returns all tokens owned by the given address, [] if unset. Return type: TokensResponse.", + "type": "object", + "required": [ + "tokens" + ], + "properties": { + "tokens": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "With Enumerable extension. Requires pagination. Lists all token_ids controlled by the contract. Return type: TokensResponse.", + "type": "object", + "required": [ + "all_tokens" + ], + "properties": { + "all_tokens": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + } + ] +} diff --git a/packages/cw1155/schema/cw1155_receive_msg.json b/packages/cw1155/schema/cw1155_receive_msg.json new file mode 100644 index 000000000..1bf693cec --- /dev/null +++ b/packages/cw1155/schema/cw1155_receive_msg.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw1155ReceiveMsg", + "description": "Cw1155ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", + "type": "object", + "required": [ + "amount", + "msg", + "operator", + "token_id" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "from": { + "description": "The account that the token transfered from", + "type": [ + "string", + "null" + ] + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "operator": { + "description": "The account that executed the send message", + "type": "string" + }, + "token_id": { + "type": "string" + } + }, + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/packages/cw1155/schema/execute_msg.json b/packages/cw1155/schema/execute_msg.json new file mode 100644 index 000000000..30f43d62f --- /dev/null +++ b/packages/cw1155/schema/execute_msg.json @@ -0,0 +1,292 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "SendFrom is a base message to move tokens, if `env.sender` is the owner or has sufficient pre-approval.", + "type": "object", + "required": [ + "send_from" + ], + "properties": { + "send_from": { + "type": "object", + "required": [ + "from", + "to", + "token_id", + "value" + ], + "properties": { + "from": { + "type": "string" + }, + "msg": { + "description": "`None` means don't call the receiver interface", + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "to": { + "description": "If `to` is not contract, `msg` should be `None`", + "type": "string" + }, + "token_id": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Uint128" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "BatchSendFrom is a base message to move multiple types of tokens in batch, if `env.sender` is the owner or has sufficient pre-approval.", + "type": "object", + "required": [ + "batch_send_from" + ], + "properties": { + "batch_send_from": { + "type": "object", + "required": [ + "batch", + "from", + "to" + ], + "properties": { + "batch": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/Uint128" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "from": { + "type": "string" + }, + "msg": { + "description": "`None` means don't call the receiver interface", + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "to": { + "description": "if `to` is not contract, `msg` should be `None`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Burn is a base message to burn tokens.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "from", + "token_id", + "value" + ], + "properties": { + "from": { + "type": "string" + }, + "token_id": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Uint128" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "BatchBurn is a base message to burn multiple types of tokens in batch.", + "type": "object", + "required": [ + "batch_burn" + ], + "properties": { + "batch_burn": { + "type": "object", + "required": [ + "batch", + "from" + ], + "properties": { + "batch": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/Uint128" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "from": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Allows operator to transfer / send any token from the owner's account. If expiration is set, then this allowance has a time/height limit", + "type": "object", + "required": [ + "approve_all" + ], + "properties": { + "approve_all": { + "type": "object", + "required": [ + "operator" + ], + "properties": { + "expires": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "operator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Remove previously granted ApproveAll permission", + "type": "object", + "required": [ + "revoke_all" + ], + "properties": { + "revoke_all": { + "type": "object", + "required": [ + "operator" + ], + "properties": { + "operator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object" + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/packages/cw1155/schema/is_approved_for_all_response.json b/packages/cw1155/schema/is_approved_for_all_response.json new file mode 100644 index 000000000..e3af7a983 --- /dev/null +++ b/packages/cw1155/schema/is_approved_for_all_response.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IsApprovedForAllResponse", + "type": "object", + "required": [ + "approved" + ], + "properties": { + "approved": { + "type": "boolean" + } + } +} diff --git a/packages/cw1155/schema/minter_response.json b/packages/cw1155/schema/minter_response.json new file mode 100644 index 000000000..a20e0d767 --- /dev/null +++ b/packages/cw1155/schema/minter_response.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MinterResponse", + "description": "Shows who can mint these tokens", + "type": "object", + "required": [ + "minter" + ], + "properties": { + "minter": { + "type": "string" + } + } +} diff --git a/packages/cw1155/schema/num_tokens_response.json b/packages/cw1155/schema/num_tokens_response.json new file mode 100644 index 000000000..b19bc807e --- /dev/null +++ b/packages/cw1155/schema/num_tokens_response.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "NumTokensResponse", + "type": "object", + "required": [ + "count" + ], + "properties": { + "count": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/packages/cw1155/schema/token_info_response.json b/packages/cw1155/schema/token_info_response.json new file mode 100644 index 000000000..a55515102 --- /dev/null +++ b/packages/cw1155/schema/token_info_response.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TokenInfoResponse", + "type": "object", + "properties": { + "extension": { + "description": "You can add any custom metadata here when you extend cw1155-base", + "anyOf": [ + { + "$ref": "#/definitions/Empty" + }, + { + "type": "null" + } + ] + }, + "token_uri": { + "description": "Should be a url point to a json file", + "type": [ + "string", + "null" + ] + } + }, + "definitions": { + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + } + } +} diff --git a/packages/cw1155/schema/tokens_response.json b/packages/cw1155/schema/tokens_response.json new file mode 100644 index 000000000..b8e3d75b5 --- /dev/null +++ b/packages/cw1155/schema/tokens_response.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TokensResponse", + "type": "object", + "required": [ + "tokens" + ], + "properties": { + "tokens": { + "description": "Contains all token_ids in lexicographical ordering If there are more than `limit`, use `start_from` in future queries to achieve pagination.", + "type": "array", + "items": { + "type": "string" + } + } + } +} From adeb7cf554118d0b9b5fe56a02c7882a82715a4c Mon Sep 17 00:00:00 2001 From: shab Date: Tue, 30 Apr 2024 15:36:02 -0400 Subject: [PATCH 08/57] trivial --- Cargo.lock | 45 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 40fffb22a..75921ba9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -259,6 +259,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "cw-storage-plus" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "648b1507290bbc03a8d88463d7cd9b04b1fa0155e5eef366c4fa052b9caaac7a" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + [[package]] name = "cw-storage-plus" version = "0.16.0" @@ -281,6 +292,18 @@ dependencies = [ "serde", ] +[[package]] +name = "cw-utils" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dbaecb78c8e8abfd6b4258c7f4fbeb5c49a5e45ee4d910d3240ee8e1d714e1b" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw-utils" version = "0.16.0" @@ -317,7 +340,7 @@ version = "0.13.4" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-utils", + "cw-utils 0.13.4", "schemars", "serde", ] @@ -328,10 +351,10 @@ version = "0.13.4" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus", - "cw-utils", + "cw-storage-plus 0.13.4", + "cw-utils 0.13.4", "cw1155", - "cw2", + "cw2 0.13.4", "schemars", "serde", "thiserror", @@ -345,12 +368,24 @@ dependencies = [ "cosmwasm-std", "cw1155", "cw1155-base", - "cw2", + "cw2 0.13.4", "schemars", "serde", "thiserror", ] +[[package]] +name = "cw2" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cf4639517490dd36b333bbd6c4fbd92e325fd0acf4683b41753bc5eb63bfc1" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus 0.13.4", + "schemars", + "serde", +] + [[package]] name = "cw2" version = "0.16.0" From d822a4fb7e0a0e1097e255cd081e1bce17763507 Mon Sep 17 00:00:00 2001 From: shab Date: Wed, 1 May 2024 18:29:35 -0400 Subject: [PATCH 09/57] fix: split up transfer event into relevant action events. fix: batch events that involve multiple tokens will emit 1 event instead of multiple, with a comma delimited list of '{token_id}:{amount}'. fix: remove query IsApprovedForAll - too gas intensive, use query 'ApprovalsFor' instead. fix: verify_approval/s functions. chore: rename 'ApprovedForAll' to 'ApprovalsFor'. chore: TokenAmount struct instead of (token_id, amount) tuple vec. chore: use workspace deps in cargo. --- Cargo.lock | 47 +-- Cargo.toml | 2 + contracts/cw1155-base/Cargo.toml | 22 +- contracts/cw1155-base/examples/schema.rs | 8 +- contracts/cw1155-base/src/contract_tests.rs | 327 +++++++++-------- contracts/cw1155-base/src/execute.rs | 339 ++++++++++-------- contracts/cw1155-base/src/lib.rs | 5 +- contracts/cw1155-base/src/msg.rs | 14 +- contracts/cw1155-base/src/query.rs | 44 +-- contracts/cw1155-base/src/state.rs | 20 +- contracts/cw1155-metadata-onchain/Cargo.toml | 21 +- .../examples/schema.rs | 8 +- contracts/cw1155-metadata-onchain/src/lib.rs | 4 +- packages/cw1155/Cargo.toml | 16 +- packages/cw1155/examples/schema.rs | 9 +- .../cw1155}/src/error.rs | 2 +- packages/cw1155/src/event.rs | 157 ++++++-- packages/cw1155/src/lib.rs | 10 +- packages/cw1155/src/msg.rs | 14 + packages/cw1155/src/query.rs | 16 +- packages/cw1155/src/receiver.rs | 9 +- 21 files changed, 592 insertions(+), 502 deletions(-) rename {contracts/cw1155-base => packages/cw1155}/src/error.rs (90%) diff --git a/Cargo.lock b/Cargo.lock index 75921ba9c..6253f0ab1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -259,17 +259,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "cw-storage-plus" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648b1507290bbc03a8d88463d7cd9b04b1fa0155e5eef366c4fa052b9caaac7a" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", -] - [[package]] name = "cw-storage-plus" version = "0.16.0" @@ -292,18 +281,6 @@ dependencies = [ "serde", ] -[[package]] -name = "cw-utils" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dbaecb78c8e8abfd6b4258c7f4fbeb5c49a5e45ee4d910d3240ee8e1d714e1b" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", - "thiserror", -] - [[package]] name = "cw-utils" version = "0.16.0" @@ -340,9 +317,10 @@ version = "0.13.4" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-utils 0.13.4", + "cw-utils 1.0.3", "schemars", "serde", + "thiserror", ] [[package]] @@ -351,13 +329,12 @@ version = "0.13.4" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.13.4", - "cw-utils 0.13.4", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", "cw1155", - "cw2 0.13.4", + "cw2 1.1.2", "schemars", "serde", - "thiserror", ] [[package]] @@ -368,24 +345,12 @@ dependencies = [ "cosmwasm-std", "cw1155", "cw1155-base", - "cw2 0.13.4", + "cw2 1.1.2", "schemars", "serde", "thiserror", ] -[[package]] -name = "cw2" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cf4639517490dd36b333bbd6c4fbd92e325fd0acf4683b41753bc5eb63bfc1" -dependencies = [ - "cosmwasm-std", - "cw-storage-plus 0.13.4", - "schemars", - "serde", -] - [[package]] name = "cw2" version = "0.16.0" diff --git a/Cargo.toml b/Cargo.toml index 60963487c..94031ef51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,8 @@ cw20 = "^1.1" cw721 = { version = "*", path = "./packages/cw721" } cw721-base = { version = "*", path = "./contracts/cw721-base" } cw721-base-016 = { version = "0.16.0", package = "cw721-base" } +cw1155 = { path = "./packages/cw1155", version = "*" } +cw1155-base = { path = "./contracts/cw1155-base", version = "*" } cw-multi-test = "^0.20" cw-ownable = "^0.5" cw-storage-plus = "^1.1" diff --git a/contracts/cw1155-base/Cargo.toml b/contracts/cw1155-base/Cargo.toml index d2f475ed7..2630146cf 100644 --- a/contracts/cw1155-base/Cargo.toml +++ b/contracts/cw1155-base/Cargo.toml @@ -1,8 +1,7 @@ [package] name = "cw1155-base" version = "0.13.4" -authors = [ -] +authors = ["shab "] edition = "2018" description = "Basic implementation cw1155" license = "Apache-2.0" @@ -26,14 +25,11 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cw-utils = "0.13.4" -cw2 = "0.13.4" -cw1155 = { path = "../../packages/cw1155", version = "0.13.4" } -cw-storage-plus = "0.13.4" -cosmwasm-std = { version = "1.0.0" } -schemars = "0.8.10" -serde = { version = "1.0.140", default-features = false, features = ["derive"] } -thiserror = { version = "1.0.31" } - -[dev-dependencies] -cosmwasm-schema = { version = "1.0.0" } +cw-utils = { workspace = true } +cw2 = { workspace = true } +cw1155 = { workspace = true } +cw-storage-plus = { workspace = true } +cosmwasm-std = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +cosmwasm-schema = { workspace = true } diff --git a/contracts/cw1155-base/examples/schema.rs b/contracts/cw1155-base/examples/schema.rs index adf56a58d..6b2785d70 100644 --- a/contracts/cw1155-base/examples/schema.rs +++ b/contracts/cw1155-base/examples/schema.rs @@ -4,9 +4,8 @@ use std::fs::create_dir_all; use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for}; use cw1155::{ - AllBalancesResponse, ApprovedForAllResponse, BalanceResponse, BatchBalanceResponse, - Cw1155QueryMsg, IsApprovedForAllResponse, MinterResponse, NumTokensResponse, TokenInfoResponse, - TokensResponse, + AllBalancesResponse, ApprovalsForResponse, BalanceResponse, BatchBalanceResponse, + Cw1155QueryMsg, MinterResponse, NumTokensResponse, TokenInfoResponse, TokensResponse, }; use cw1155_base::{ExecuteMsg, Extension, InstantiateMsg}; @@ -23,8 +22,7 @@ fn main() { export_schema(&schema_for!(AllBalancesResponse), &out_dir); export_schema(&schema_for!(BatchBalanceResponse), &out_dir); export_schema(&schema_for!(NumTokensResponse), &out_dir); - export_schema(&schema_for!(ApprovedForAllResponse), &out_dir); - export_schema(&schema_for!(IsApprovedForAllResponse), &out_dir); + export_schema(&schema_for!(ApprovalsForResponse), &out_dir); export_schema(&schema_for!(TokensResponse), &out_dir); export_schema(&schema_for!(MinterResponse), &out_dir); export_schema_with_title( diff --git a/contracts/cw1155-base/src/contract_tests.rs b/contracts/cw1155-base/src/contract_tests.rs index 87b9db05c..8245652b2 100644 --- a/contracts/cw1155-base/src/contract_tests.rs +++ b/contracts/cw1155-base/src/contract_tests.rs @@ -2,14 +2,15 @@ mod tests { use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; use cosmwasm_std::{ - to_binary, Addr, Binary, Empty, OverflowError, Response, StdError, Uint128, + from_json, to_json_binary, Addr, Binary, Empty, Event, OverflowError, Response, StdError, + Uint128, }; - use crate::{ContractError, Cw1155Contract, ExecuteMsg, InstantiateMsg, MintMsg}; + use crate::{Cw1155Contract, ExecuteMsg, InstantiateMsg, MintMsg}; use cw1155::{ - AllBalancesResponse, ApprovedForAllResponse, Balance, BalanceResponse, - BatchBalanceResponse, Cw1155BatchReceiveMsg, Cw1155QueryMsg, Expiration, - IsApprovedForAllResponse, NumTokensResponse, TokenInfoResponse, TokensResponse, + AllBalancesResponse, ApprovalsForResponse, Balance, BalanceResponse, BatchBalanceResponse, + Cw1155BatchReceiveMsg, Cw1155ContractError, Cw1155QueryMsg, Expiration, NumTokensResponse, + TokenAmount, TokenInfoResponse, TokensResponse, }; #[test] @@ -19,7 +20,7 @@ mod tests { // Summary of what it does: // - try mint without permission, fail // - mint with permission, success - // - query balance of receipant, success + // - query balance of recipient, success // - try transfer without approval, fail // - approve // - transfer again, success @@ -52,7 +53,7 @@ mod tests { let mint_msg = ExecuteMsg::Mint(MintMsg:: { to: user1.clone(), token_id: token1.clone(), - value: 1u64.into(), + amount: 1u64.into(), token_uri: None, extension: None, }); @@ -63,7 +64,7 @@ mod tests { mock_info(user1.as_ref(), &[]), mint_msg.clone(), ), - Err(ContractError::Unauthorized {}) + Err(Cw1155ContractError::Unauthorized {}) )); // valid mint @@ -76,16 +77,15 @@ mod tests { mint_msg, ) .unwrap(), - Response::new() - .add_attribute("action", "transfer") - .add_attribute("token_id", &token1) - .add_attribute("amount", 1u64.to_string()) - .add_attribute("to", &user1) + Response::new().add_event(Event::new("mint_tokens").add_attributes(vec![ + ("recipient", user1.as_str()), + ("tokens", &format!("{}:1", token1)), + ])) ); // query balance assert_eq!( - to_binary(&BalanceResponse { + to_json_binary(&BalanceResponse { balance: 1u64.into() }), contract.query( @@ -102,7 +102,7 @@ mod tests { from: user1.clone(), to: user2.clone(), token_id: token1.clone(), - value: 1u64.into(), + amount: 1u64.into(), msg: None, }; @@ -114,7 +114,7 @@ mod tests { mock_info(minter.as_ref(), &[]), transfer_msg.clone(), ), - Err(ContractError::Unauthorized {}) + Err(Cw1155ContractError::Unauthorized {}) )); // approve @@ -140,12 +140,11 @@ mod tests { transfer_msg, ) .unwrap(), - Response::new() - .add_attribute("action", "transfer") - .add_attribute("token_id", &token1) - .add_attribute("amount", 1u64.to_string()) - .add_attribute("from", &user1) - .add_attribute("to", &user2) + Response::new().add_event(Event::new("transfer_tokens").add_attributes(vec![ + ("sender", user1.as_str()), + ("recipient", user2.as_str()), + ("tokens", &format!("{}:1", token1)), + ])) ); // query balance @@ -158,7 +157,7 @@ mod tests { token_id: token1.clone(), } ), - to_binary(&BalanceResponse { + to_json_binary(&BalanceResponse { balance: 1u64.into() }), ); @@ -171,7 +170,7 @@ mod tests { token_id: token1.clone(), } ), - to_binary(&BalanceResponse { + to_json_binary(&BalanceResponse { balance: 0u64.into() }), ); @@ -185,7 +184,7 @@ mod tests { ExecuteMsg::Mint(MintMsg:: { to: user2.clone(), token_id: token2.clone(), - value: 1u64.into(), + amount: 1u64.into(), token_uri: None, extension: None, }), @@ -200,7 +199,7 @@ mod tests { ExecuteMsg::Mint(MintMsg:: { to: user2.clone(), token_id: token3.clone(), - value: 1u64.into(), + amount: 1u64.into(), token_uri: None, extension: None, }), @@ -212,9 +211,18 @@ mod tests { from: user2.clone(), to: user1.clone(), batch: vec![ - (token1.clone(), 1u64.into()), - (token2.clone(), 1u64.into()), - (token3.clone(), 1u64.into()), + TokenAmount { + token_id: token1.to_string(), + amount: 1u64.into(), + }, + TokenAmount { + token_id: token2.to_string(), + amount: 1u64.into(), + }, + TokenAmount { + token_id: token3.to_string(), + amount: 1u64.into(), + }, ], msg: None, }; @@ -225,7 +233,7 @@ mod tests { mock_info(minter.as_ref(), &[]), batch_transfer_msg.clone(), ), - Err(ContractError::Unauthorized {}), + Err(Cw1155ContractError::Unauthorized {}), )); // user2 approve @@ -251,22 +259,11 @@ mod tests { batch_transfer_msg, ) .unwrap(), - Response::new() - .add_attribute("action", "transfer") - .add_attribute("token_id", &token1) - .add_attribute("amount", 1u64.to_string()) - .add_attribute("from", &user2) - .add_attribute("to", &user1) - .add_attribute("action", "transfer") - .add_attribute("token_id", &token2) - .add_attribute("amount", 1u64.to_string()) - .add_attribute("from", &user2) - .add_attribute("to", &user1) - .add_attribute("action", "transfer") - .add_attribute("token_id", &token3) - .add_attribute("amount", 1u64.to_string()) - .add_attribute("from", &user2) - .add_attribute("to", &user1) + Response::new().add_event(Event::new("transfer_tokens").add_attributes(vec![ + ("sender", user2.as_str()), + ("recipient", user1.as_str()), + ("tokens", &format!("{}:1,{}:1,{}:1", token1, token2, token3)), + ])) ); // batch query @@ -279,7 +276,7 @@ mod tests { token_ids: vec![token1.clone(), token2.clone(), token3.clone()], } ), - to_binary(&BatchBalanceResponse { + to_json_binary(&BatchBalanceResponse { balances: vec![1u64.into(), 1u64.into(), 1u64.into()] }), ); @@ -297,19 +294,27 @@ mod tests { .unwrap(); // query approval status - assert_eq!( - contract.query( - deps.as_ref(), - mock_env(), - Cw1155QueryMsg::IsApprovedForAll { - owner: user1.clone(), - operator: minter.clone(), - } - ), - to_binary(&IsApprovedForAllResponse { approved: false }), - ); - - // tranfer without approval + let approvals: ApprovalsForResponse = from_json( + contract + .query( + deps.as_ref(), + mock_env(), + Cw1155QueryMsg::ApprovalsFor { + owner: user1.clone(), + include_expired: None, + start_after: None, + limit: None, + }, + ) + .unwrap(), + ) + .unwrap(); + assert!(!approvals + .operators + .iter() + .all(|approval| approval.spender == minter)); + + // transfer without approval assert!(matches!( contract.execute( deps.as_mut(), @@ -319,11 +324,11 @@ mod tests { from: user1.clone(), to: user2, token_id: token1.clone(), - value: 1u64.into(), + amount: 1u64.into(), msg: None, }, ), - Err(ContractError::Unauthorized {}) + Err(Cw1155ContractError::Unauthorized {}) )); // burn token1 @@ -334,17 +339,15 @@ mod tests { mock_env(), mock_info(user1.as_ref(), &[]), ExecuteMsg::Burn { - from: user1.clone(), token_id: token1.clone(), - value: 1u64.into(), - } + amount: 1u64.into(), + }, ) .unwrap(), - Response::new() - .add_attribute("action", "transfer") - .add_attribute("token_id", &token1) - .add_attribute("amount", 1u64.to_string()) - .add_attribute("from", &user1) + Response::new().add_event(Event::new("burn_tokens").add_attributes(vec![ + ("owner", user1.as_str()), + ("tokens", &format!("{}:1", token1)), + ])) ); // burn them all @@ -355,20 +358,23 @@ mod tests { mock_env(), mock_info(user1.as_ref(), &[]), ExecuteMsg::BatchBurn { - from: user1.clone(), - batch: vec![(token2.clone(), 1u64.into()), (token3.clone(), 1u64.into())] - } + batch: vec![ + TokenAmount { + token_id: token2.to_string(), + amount: 1u64.into(), + }, + TokenAmount { + token_id: token3.to_string(), + amount: 1u64.into(), + }, + ], + }, ) .unwrap(), - Response::new() - .add_attribute("action", "transfer") - .add_attribute("token_id", &token2) - .add_attribute("amount", 1u64.to_string()) - .add_attribute("from", &user1) - .add_attribute("action", "transfer") - .add_attribute("token_id", &token3) - .add_attribute("amount", 1u64.to_string()) - .add_attribute("from", &user1) + Response::new().add_event(Event::new("burn_tokens").add_attributes(vec![ + ("owner", user1.as_str()), + ("tokens", &format!("{}:1,{}:1", token2, token3)), + ])) ); } @@ -398,7 +404,7 @@ mod tests { ExecuteMsg::Mint(MintMsg:: { to: user1.clone(), token_id: token2.clone(), - value: 1u64.into(), + amount: 1u64.into(), token_uri: None, extension: None, }), @@ -415,7 +421,10 @@ mod tests { ExecuteMsg::BatchSendFrom { from: user1.clone(), to: receiver.clone(), - batch: vec![(token2.clone(), 1u64.into())], + batch: vec![TokenAmount { + token_id: token2.to_string(), + amount: 1u64.into(), + },], msg: Some(dummy_msg.clone()), }, ) @@ -425,17 +434,20 @@ mod tests { Cw1155BatchReceiveMsg { operator: user1.clone(), from: Some(user1.clone()), - batch: vec![(token2.clone(), 1u64.into())], + batch: vec![TokenAmount { + token_id: token2.to_string(), + amount: 1u64.into(), + }], msg: dummy_msg, } .into_cosmos_msg(receiver.clone()) .unwrap() ) - .add_attribute("action", "transfer") - .add_attribute("token_id", &token2) - .add_attribute("amount", 1u64.to_string()) - .add_attribute("from", &user1) - .add_attribute("to", &receiver) + .add_event(Event::new("transfer_tokens").add_attributes(vec![ + ("sender", user1.as_str()), + ("recipient", receiver.as_str()), + ("tokens", &format!("{}:1", token2)), + ])) ); } @@ -466,7 +478,7 @@ mod tests { ExecuteMsg::Mint(MintMsg:: { to: users[0].clone(), token_id: token_id.clone(), - value: 1u64.into(), + amount: 1u64.into(), token_uri: None, extension: None, }), @@ -483,7 +495,7 @@ mod tests { ExecuteMsg::Mint(MintMsg:: { to: user.clone(), token_id: tokens[9].clone(), - value: 1u64.into(), + amount: 1u64.into(), token_uri: None, extension: None, }), @@ -499,7 +511,7 @@ mod tests { token_id: tokens[0].clone(), }, ), - to_binary(&NumTokensResponse { + to_json_binary(&NumTokensResponse { count: Uint128::new(1), }) ); @@ -512,7 +524,7 @@ mod tests { token_id: tokens[0].clone(), }, ), - to_binary(&NumTokensResponse { + to_json_binary(&NumTokensResponse { count: Uint128::new(1), }) ); @@ -524,10 +536,10 @@ mod tests { Cw1155QueryMsg::AllBalances { token_id: tokens[9].clone(), start_after: None, - limit: Some(5) + limit: Some(5), }, ), - to_binary(&AllBalancesResponse { + to_json_binary(&AllBalancesResponse { balances: users[..5] .iter() .map(|user| { @@ -548,10 +560,10 @@ mod tests { Cw1155QueryMsg::AllBalances { token_id: tokens[9].clone(), start_after: Some("user5".to_owned()), - limit: Some(5) + limit: Some(5), }, ), - to_binary(&AllBalancesResponse { + to_json_binary(&AllBalancesResponse { balances: users[6..] .iter() .map(|user| { @@ -575,7 +587,7 @@ mod tests { limit: Some(5), }, ), - to_binary(&TokensResponse { + to_json_binary(&TokensResponse { tokens: tokens[..5].to_owned() }) ); @@ -590,7 +602,7 @@ mod tests { limit: Some(5), }, ), - to_binary(&TokensResponse { + to_json_binary(&TokensResponse { tokens: tokens[6..].to_owned() }) ); @@ -604,7 +616,7 @@ mod tests { limit: Some(5), }, ), - to_binary(&TokensResponse { + to_json_binary(&TokensResponse { tokens: tokens[6..].to_owned() }) ); @@ -617,7 +629,7 @@ mod tests { token_id: "token5".to_owned() }, ), - to_binary(&TokenInfoResponse:: { + to_json_binary(&TokenInfoResponse:: { token_uri: None, extension: None, }), @@ -641,17 +653,17 @@ mod tests { contract.query( deps.as_ref(), mock_env(), - Cw1155QueryMsg::ApprovedForAll { + Cw1155QueryMsg::ApprovalsFor { owner: users[0].clone(), include_expired: None, start_after: Some(String::from("user2")), limit: Some(1), }, ), - to_binary(&ApprovedForAllResponse { + to_json_binary(&ApprovalsForResponse { operators: vec![cw1155::Approval { spender: users[3].clone(), - expires: Expiration::Never {} + expires: Expiration::Never {}, }], }) ); @@ -688,7 +700,7 @@ mod tests { ExecuteMsg::Mint(MintMsg:: { to: user1.clone(), token_id: token1, - value: 1u64.into(), + amount: 1u64.into(), token_uri: None, extension: None, }), @@ -721,14 +733,25 @@ mod tests { ) .unwrap(); - let query_msg = Cw1155QueryMsg::IsApprovedForAll { - owner: user1, - operator: user2, - }; - assert_eq!( - contract.query(deps.as_ref(), env, query_msg.clone()), - to_binary(&IsApprovedForAllResponse { approved: true }) - ); + let approvals: ApprovalsForResponse = from_json( + contract + .query( + deps.as_ref(), + mock_env(), + Cw1155QueryMsg::ApprovalsFor { + owner: user1.to_string(), + include_expired: None, + start_after: None, + limit: None, + }, + ) + .unwrap(), + ) + .unwrap(); + assert!(approvals + .operators + .iter() + .all(|approval| approval.spender == user2)); let env = { let mut env = mock_env(); @@ -736,10 +759,25 @@ mod tests { env }; - assert_eq!( - contract.query(deps.as_ref(), env, query_msg,), - to_binary(&IsApprovedForAllResponse { approved: false }) - ); + let approvals: ApprovalsForResponse = from_json( + contract + .query( + deps.as_ref(), + env, + Cw1155QueryMsg::ApprovalsFor { + owner: user1.to_string(), + include_expired: None, + start_after: None, + limit: None, + }, + ) + .unwrap(), + ) + .unwrap(); + assert!(!approvals + .operators + .iter() + .all(|approval| approval.spender == user2)); } #[test] @@ -759,35 +797,22 @@ mod tests { .unwrap(); assert_eq!(0, res.messages.len()); - contract - .execute( - deps.as_mut(), - env.clone(), - mock_info(minter.as_ref(), &[]), - ExecuteMsg::Mint(MintMsg:: { - to: user1.clone(), - token_id: token1.clone(), - value: u128::MAX.into(), - token_uri: None, - extension: None, - }), - ) - .unwrap(); + let res = contract.execute( + deps.as_mut(), + env.clone(), + mock_info(minter.as_ref(), &[]), + ExecuteMsg::Mint(MintMsg:: { + to: user1.clone(), + token_id: token1.clone(), + amount: u128::MAX.into(), + token_uri: None, + extension: None, + }), + ); assert!(matches!( - contract.execute( - deps.as_mut(), - env, - mock_info(minter.as_ref(), &[]), - ExecuteMsg::Mint(MintMsg:: { - to: user1, - token_id: token1, - value: 1u64.into(), - token_uri: None, - extension: None, - }), - ), - Err(ContractError::Std(StdError::Overflow { + res, + Err(Cw1155ContractError::Std(StdError::Overflow { source: OverflowError { .. }, .. })) @@ -821,7 +846,7 @@ mod tests { ExecuteMsg::Mint(MintMsg:: { to: user1.clone(), token_id: token1.clone(), - value: 1u64.into(), + amount: 1u64.into(), token_uri: Some(url1.clone()), extension: None, }), @@ -834,9 +859,9 @@ mod tests { mock_env(), Cw1155QueryMsg::TokenInfo { token_id: token1.clone() - } + }, ), - to_binary(&TokenInfoResponse:: { + to_json_binary(&TokenInfoResponse:: { token_uri: Some(url1.clone()), extension: None, }) @@ -851,7 +876,7 @@ mod tests { ExecuteMsg::Mint(MintMsg:: { to: user1, token_id: token1.clone(), - value: 1u64.into(), + amount: 1u64.into(), token_uri: Some(url2), extension: None, }), @@ -863,9 +888,9 @@ mod tests { contract.query( deps.as_ref(), mock_env(), - Cw1155QueryMsg::TokenInfo { token_id: token1 } + Cw1155QueryMsg::TokenInfo { token_id: token1 }, ), - to_binary(&TokenInfoResponse:: { + to_json_binary(&TokenInfoResponse:: { token_uri: Some(url1), extension: None, }) diff --git a/contracts/cw1155-base/src/execute.rs b/contracts/cw1155-base/src/execute.rs index 607815806..5761e17d2 100644 --- a/contracts/cw1155-base/src/execute.rs +++ b/contracts/cw1155-base/src/execute.rs @@ -2,16 +2,15 @@ use serde::de::DeserializeOwned; use serde::Serialize; use cosmwasm_std::{ - Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, SubMsg, Uint128, + Addr, Binary, DepsMut, Env, Event, MessageInfo, Response, StdResult, Storage, SubMsg, Uint128, }; use cw1155::{ - ApproveAllEvent, Balance, Cw1155BatchReceiveMsg, Cw1155ReceiveMsg, Expiration, TransferEvent, + ApproveAllEvent, Balance, BurnEvent, Cw1155BatchReceiveMsg, Cw1155ContractError, + Cw1155ReceiveMsg, Expiration, MintEvent, RevokeAllEvent, TokenAmount, TransferEvent, }; use cw2::set_contract_version; -use cw_utils::Event; -use crate::error::ContractError; use crate::msg::{ExecuteMsg, InstantiateMsg, MintMsg}; use crate::state::{Cw1155Contract, TokenInfo}; @@ -43,7 +42,7 @@ where env: Env, info: MessageInfo, msg: ExecuteMsg, - ) -> Result { + ) -> Result { let env = ExecuteEnv { deps, env, info }; match msg { ExecuteMsg::Mint(msg) => self.mint(env, msg), @@ -51,21 +50,17 @@ where from, to, token_id, - value, + amount, msg, - } => self.send_from(env, from, to, token_id, value, msg), + } => self.send_from(env, from, to, token_id, amount, msg), ExecuteMsg::BatchSendFrom { from, to, batch, msg, } => self.batch_send_from(env, from, to, batch, msg), - ExecuteMsg::Burn { - from, - token_id, - value, - } => self.burn(env, from, token_id, value), - ExecuteMsg::BatchBurn { from, batch } => self.batch_burn(env, from, batch), + ExecuteMsg::Burn { token_id, amount } => self.burn(env, token_id, amount), + ExecuteMsg::BatchBurn { batch } => self.batch_burn(env, batch), ExecuteMsg::ApproveAll { operator, expires } => { self.approve_all(env, operator, expires) } @@ -86,24 +81,26 @@ impl<'a, T> Cw1155Contract<'a, T> where T: Serialize + DeserializeOwned + Clone, { - pub fn mint(&self, env: ExecuteEnv, msg: MintMsg) -> Result { + pub fn mint(&self, env: ExecuteEnv, msg: MintMsg) -> Result { let ExecuteEnv { mut deps, info, .. } = env; let to_addr = deps.api.addr_validate(&msg.to)?; if info.sender != self.minter.load(deps.storage)? { - return Err(ContractError::Unauthorized {}); + return Err(Cw1155ContractError::Unauthorized {}); } let mut rsp = Response::default(); - let event = self.execute_transfer_inner( + let event = self.update_transfer_state( &mut deps, None, Some(to_addr), - msg.token_id.clone(), - msg.value, + vec![TokenAmount { + token_id: msg.token_id.to_string(), + amount: msg.amount, + }], )?; - event.add_attributes(&mut rsp); + rsp = rsp.add_event(event); // insert if not exist (if it is the first mint) if !self.tokens.has(deps.storage, &msg.token_id) { @@ -116,7 +113,7 @@ where self.tokens.save(deps.storage, &msg.token_id, &token_info)?; // Increase num token - self.increment_tokens(deps.storage, &msg.token_id, &msg.value)?; + self.increment_tokens(deps.storage, &msg.token_id, &msg.amount)?; } Ok(rsp) @@ -130,34 +127,37 @@ where token_id: String, amount: Uint128, msg: Option, - ) -> Result { - let from_addr = env.deps.api.addr_validate(&from)?; - let to_addr = env.deps.api.addr_validate(&to)?; - + ) -> Result { let ExecuteEnv { mut deps, env, info, } = env; - self.guard_can_approve(deps.as_ref(), &env, &from_addr, &info.sender)?; + let from = deps.api.addr_validate(&from)?; + let to = deps.api.addr_validate(&to)?; + + let balance_update = + self.verify_approval(deps.storage, &env, &info, &from, &token_id, amount)?; let mut rsp = Response::default(); - let event = self.execute_transfer_inner( + let event = self.update_transfer_state( &mut deps, - Some(from_addr), - Some(to_addr), - token_id.clone(), - amount, + Some(from.clone()), + Some(to.clone()), + vec![TokenAmount { + token_id: token_id.to_string(), + amount: balance_update.amount, + }], )?; - event.add_attributes(&mut rsp); + rsp = rsp.add_event(event); if let Some(msg) = msg { rsp.messages = vec![SubMsg::new( Cw1155ReceiveMsg { operator: info.sender.to_string(), - from: Some(from), + from: Some(from.to_string()), amount, token_id, msg, @@ -174,37 +174,34 @@ where env: ExecuteEnv, from: String, to: String, - batch: Vec<(String, Uint128)>, + batch: Vec, msg: Option, - ) -> Result { + ) -> Result { let ExecuteEnv { mut deps, env, info, } = env; - let from_addr = deps.api.addr_validate(&from)?; - let to_addr = deps.api.addr_validate(&to)?; + let from = deps.api.addr_validate(&from)?; + let to = deps.api.addr_validate(&to)?; - self.guard_can_approve(deps.as_ref(), &env, &from_addr, &info.sender)?; + let batch = self.verify_approvals(deps.storage, &env, &info, &from, batch)?; let mut rsp = Response::default(); - for (token_id, amount) in batch.iter() { - let event = self.execute_transfer_inner( - &mut deps, - Some(from_addr.clone()), - Some(to_addr.clone()), - token_id.clone(), - *amount, - )?; - event.add_attributes(&mut rsp); - } + let event = self.update_transfer_state( + &mut deps, + Some(from.clone()), + Some(to.clone()), + batch.to_vec(), + )?; + rsp = rsp.add_event(event); if let Some(msg) = msg { rsp.messages = vec![SubMsg::new( Cw1155BatchReceiveMsg { operator: info.sender.to_string(), - from: Some(from), + from: Some(from.to_string()), batch, msg, } @@ -218,66 +215,55 @@ where pub fn burn( &self, env: ExecuteEnv, - from: String, token_id: String, amount: Uint128, - ) -> Result { + ) -> Result { let ExecuteEnv { mut deps, info, env, } = env; - let from_addr = deps.api.addr_validate(&from)?; + let from = &info.sender; // whoever can transfer these tokens can burn - self.guard_can_approve(deps.as_ref(), &env, &from_addr, &info.sender)?; + let balance_update = + self.verify_approval(deps.storage, &env, &info, from, &token_id, amount)?; let mut rsp = Response::default(); - let event = self.execute_transfer_inner( + + let event = self.update_transfer_state( &mut deps, - Some(from_addr), + Some(from.clone()), None, - token_id.clone(), - amount, + vec![TokenAmount { + token_id: token_id.to_string(), + amount: balance_update.amount, + }], )?; + rsp = rsp.add_event(event); - self.decrement_tokens(deps.storage, &token_id, &amount)?; - - event.add_attributes(&mut rsp); Ok(rsp) } pub fn batch_burn( &self, env: ExecuteEnv, - from: String, - batch: Vec<(String, Uint128)>, - ) -> Result { + batch: Vec, + ) -> Result { let ExecuteEnv { mut deps, info, env, } = env; - let from_addr = deps.api.addr_validate(&from)?; + let from = &info.sender; - self.guard_can_approve(deps.as_ref(), &env, &from_addr, &info.sender)?; + let batch = self.verify_approvals(deps.storage, &env, &info, from, batch)?; let mut rsp = Response::default(); - for (token_id, amount) in batch.into_iter() { - let event = self.execute_transfer_inner( - &mut deps, - Some(from_addr.clone()), - None, - token_id.clone(), - amount, - )?; - - self.decrement_tokens(deps.storage, &token_id, &amount)?; - - event.add_attributes(&mut rsp); - } + let event = self.update_transfer_state(&mut deps, Some(from.clone()), None, batch)?; + rsp = rsp.add_event(event); Ok(rsp) } @@ -287,131 +273,166 @@ where env: ExecuteEnv, operator: String, expires: Option, - ) -> Result { + ) -> Result { let ExecuteEnv { deps, info, env } = env; // reject expired data as invalid let expires = expires.unwrap_or_default(); if expires.is_expired(&env.block) { - return Err(ContractError::Expired {}); + return Err(Cw1155ContractError::Expired {}); } // set the operator for us - let operator_addr = deps.api.addr_validate(&operator)?; + let operator = deps.api.addr_validate(&operator)?; self.approves - .save(deps.storage, (&info.sender, &operator_addr), &expires)?; + .save(deps.storage, (&info.sender, &operator), &expires)?; let mut rsp = Response::default(); - ApproveAllEvent { - sender: &info.sender.to_string(), - operator: &operator, - approved: true, - } - .add_attributes(&mut rsp); + + let event = ApproveAllEvent::new(&info.sender, &operator).into(); + rsp = rsp.add_event(event); + Ok(rsp) } - pub fn revoke_all(&self, env: ExecuteEnv, operator: String) -> Result { + pub fn revoke_all( + &self, + env: ExecuteEnv, + operator: String, + ) -> Result { let ExecuteEnv { deps, info, .. } = env; let operator_addr = deps.api.addr_validate(&operator)?; self.approves .remove(deps.storage, (&info.sender, &operator_addr)); let mut rsp = Response::default(); - ApproveAllEvent { - sender: &info.sender.to_string(), - operator: &operator, - approved: false, - } - .add_attributes(&mut rsp); + + let event = RevokeAllEvent::new(&info.sender, &operator_addr).into(); + rsp = rsp.add_event(event); + Ok(rsp) } - /// When from is None: mint new coins - /// When to is None: burn coins - /// When both are None: no token balance is changed, pointless but valid + /// When from is None: mint new tokens + /// When to is None: burn tokens + /// When both are Some: transfer tokens /// /// Make sure permissions are checked before calling this. - fn execute_transfer_inner( + fn update_transfer_state( &self, deps: &mut DepsMut, from: Option, to: Option, - token_id: String, - amount: Uint128, - ) -> Result { - if let Some(from_addr) = from.clone() { - self.balances.update( - deps.storage, - (from_addr, token_id.clone()), - |balance: Option| -> StdResult<_> { - let mut new_balance = balance.unwrap(); - new_balance.amount = new_balance.amount.checked_sub(amount)?; - Ok(new_balance) - }, - )?; + tokens: Vec, + ) -> Result { + if let Some(from) = &from { + for TokenAmount { token_id, amount } in tokens.iter() { + self.balances.update( + deps.storage, + (from.clone(), token_id.clone()), + |balance: Option| -> StdResult<_> { + let mut new_balance = balance.unwrap(); + new_balance.amount = new_balance.amount.checked_sub(*amount)?; + Ok(new_balance) + }, + )?; + } } - if let Some(to_addr) = to.clone() { - self.balances.update( - deps.storage, - (to_addr.clone(), token_id.clone()), - |balance: Option| -> StdResult<_> { - let mut new_balance: Balance = if let Some(balance) = balance { - balance - } else { - Balance { - owner: to_addr.clone(), - amount: Uint128::new(0), - token_id: token_id.clone(), - } - }; - - new_balance.amount = new_balance.amount.checked_add(amount)?; - Ok(new_balance) - }, - )?; + if let Some(to) = &to { + for TokenAmount { token_id, amount } in tokens.iter() { + self.balances.update( + deps.storage, + (to.clone(), token_id.clone()), + |balance: Option| -> StdResult<_> { + let mut new_balance: Balance = if let Some(balance) = balance { + balance + } else { + Balance { + owner: to.clone(), + amount: Uint128::zero(), + token_id: token_id.to_string(), + } + }; + + new_balance.amount = new_balance.amount.checked_add(*amount)?; + Ok(new_balance) + }, + )?; + } } - Ok(TransferEvent { - from: from.map(|x| x.to_string()), - to: to.map(|x| x.to_string()), - token_id: token_id.clone(), - amount, - }) + let event = if let Some(from) = &from { + if let Some(to) = &to { + // transfer + TransferEvent::new(from, to, tokens).into() + } else { + // burn + for TokenAmount { token_id, amount } in &tokens { + self.decrement_tokens(deps.storage, token_id, amount)?; + } + BurnEvent::new(from, tokens).into() + } + } else if let Some(to) = &to { + // mint + for TokenAmount { token_id, amount } in &tokens { + self.increment_tokens(deps.storage, token_id, amount)?; + } + MintEvent::new(to, tokens).into() + } else { + panic!("Invalid transfer: from and to cannot both be None") + }; + + Ok(event) } - /// returns true if the sender can execute approve or reject on the contract - pub fn check_can_approve( + /// returns valid token amount if the sender can execute or is approved to execute + pub fn verify_approval( &self, - deps: Deps, + storage: &dyn Storage, env: &Env, + info: &MessageInfo, owner: &Addr, - operator: &Addr, - ) -> StdResult { - // owner can approve - if owner == operator { - return Ok(true); + token_id: &str, + amount: Uint128, + ) -> Result { + let operator = &info.sender; + + let owner_balance = self + .balances + .load(storage, (owner.clone(), token_id.to_string()))?; + let balance_update = TokenAmount { + token_id: token_id.to_string(), + amount: owner_balance.amount.min(amount), + }; + + // owner or operator can approve + if owner == operator + || match self.approves.may_load(storage, (owner, operator))? { + Some(ex) => !ex.is_expired(&env.block), + None => false, + } + { + Ok(balance_update) + } else { + Err(Cw1155ContractError::Unauthorized {}) } - // operator can approve - let op = self.approves.may_load(deps.storage, (owner, operator))?; - Ok(match op { - Some(ex) => !ex.is_expired(&env.block), - None => false, - }) } - fn guard_can_approve( + /// returns valid token amounts if the sender can execute or is approved to execute on all provided tokens + pub fn verify_approvals( &self, - deps: Deps, + storage: &dyn Storage, env: &Env, + info: &MessageInfo, owner: &Addr, - operator: &Addr, - ) -> Result<(), ContractError> { - if !self.check_can_approve(deps, env, owner, operator)? { - Err(ContractError::Unauthorized {}) - } else { - Ok(()) - } + tokens: Vec, + ) -> Result, Cw1155ContractError> { + tokens + .iter() + .map(|TokenAmount { token_id, amount }| { + self.verify_approval(storage, &env, info, owner, token_id, *amount) + }) + .collect() } } diff --git a/contracts/cw1155-base/src/lib.rs b/contracts/cw1155-base/src/lib.rs index a80681288..4d1609c37 100644 --- a/contracts/cw1155-base/src/lib.rs +++ b/contracts/cw1155-base/src/lib.rs @@ -1,11 +1,9 @@ mod contract_tests; -mod error; mod execute; mod msg; mod query; mod state; -pub use crate::error::ContractError; pub use crate::msg::{ExecuteMsg, InstantiateMsg, MintMsg}; pub use crate::state::Cw1155Contract; use cosmwasm_std::Empty; @@ -20,6 +18,7 @@ pub mod entry { #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; + use cw1155::Cw1155ContractError; // This makes a conscious choice on the various generics used by the contract #[cfg_attr(not(feature = "library"), entry_point)] @@ -39,7 +38,7 @@ pub mod entry { env: Env, info: MessageInfo, msg: ExecuteMsg, - ) -> Result { + ) -> Result { let tract = Cw1155Contract::::default(); tract.execute(deps, env, info, msg) } diff --git a/contracts/cw1155-base/src/msg.rs b/contracts/cw1155-base/src/msg.rs index 8b53fef36..d32173a9c 100644 --- a/contracts/cw1155-base/src/msg.rs +++ b/contracts/cw1155-base/src/msg.rs @@ -2,7 +2,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use cosmwasm_std::{Binary, Uint128}; -use cw1155::Expiration; +use cw1155::{Expiration, TokenAmount}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct InstantiateMsg { @@ -25,7 +25,7 @@ pub enum ExecuteMsg { /// If `to` is not contract, `msg` should be `None` to: String, token_id: String, - value: Uint128, + amount: Uint128, /// `None` means don't call the receiver interface msg: Option, }, @@ -35,20 +35,18 @@ pub enum ExecuteMsg { from: String, /// if `to` is not contract, `msg` should be `None` to: String, - batch: Vec<(String, Uint128)>, + batch: Vec, /// `None` means don't call the receiver interface msg: Option, }, /// Burn is a base message to burn tokens. Burn { - from: String, token_id: String, - value: Uint128, + amount: Uint128, }, /// BatchBurn is a base message to burn multiple types of tokens in batch. BatchBurn { - from: String, - batch: Vec<(String, Uint128)>, + batch: Vec, }, /// Allows operator to transfer / send any token from the owner's account. /// If expiration is set, then this allowance has a time/height limit @@ -69,7 +67,7 @@ pub struct MintMsg { /// The owner of the newly minted tokens pub to: String, /// The amount of the newly minted tokens - pub value: Uint128, + pub amount: Uint128, /// Only first mint can set `token_uri` and `extension` /// Metadata JSON Schema diff --git a/contracts/cw1155-base/src/query.rs b/contracts/cw1155-base/src/query.rs index 6041b55e9..f55ba2e0e 100644 --- a/contracts/cw1155-base/src/query.rs +++ b/contracts/cw1155-base/src/query.rs @@ -1,11 +1,11 @@ use serde::de::DeserializeOwned; use serde::Serialize; -use cosmwasm_std::{to_binary, Addr, Binary, Deps, Env, Order, StdResult, Uint128}; +use cosmwasm_std::{to_json_binary, Addr, Binary, Deps, Env, Order, StdResult, Uint128}; use cw1155::{ - AllBalancesResponse, Approval, ApprovedForAllResponse, Balance, BalanceResponse, - BatchBalanceResponse, Cw1155QueryMsg, Expiration, IsApprovedForAllResponse, MinterResponse, + AllBalancesResponse, Approval, ApprovalsForResponse, Balance, BalanceResponse, + BatchBalanceResponse, Cw1155QueryMsg, Expiration, MinterResponse, NumTokensResponse, TokenInfoResponse, TokensResponse, }; use cw_storage_plus::Bound; @@ -17,14 +17,14 @@ const DEFAULT_LIMIT: u32 = 10; const MAX_LIMIT: u32 = 100; impl<'a, T> Cw1155Contract<'a, T> -where - T: Serialize + DeserializeOwned + Clone, + where + T: Serialize + DeserializeOwned + Clone, { pub fn query(&self, deps: Deps, env: Env, msg: Cw1155QueryMsg) -> StdResult { match msg { Cw1155QueryMsg::Minter {} => { let minter = self.minter.load(deps.storage)?.to_string(); - to_binary(&MinterResponse { minter }) + to_json_binary(&MinterResponse { minter }) } Cw1155QueryMsg::Balance { owner, token_id } => { let owner_addr = deps.api.addr_validate(&owner)?; @@ -36,7 +36,7 @@ where token_id, amount: Uint128::new(0), }); - to_binary(&BalanceResponse { + to_json_binary(&BalanceResponse { balance: balance.amount, }) } @@ -44,7 +44,7 @@ where token_id, start_after, limit, - } => to_binary(&self.query_all_balances(deps, token_id, start_after, limit)?), + } => to_json_binary(&self.query_all_balances(deps, token_id, start_after, limit)?), Cw1155QueryMsg::BatchBalance { owner, token_ids } => { let owner_addr = deps.api.addr_validate(&owner)?; let balances = token_ids @@ -61,19 +61,13 @@ where .amount) }) .collect::>()?; - to_binary(&BatchBalanceResponse { balances }) + to_json_binary(&BatchBalanceResponse { balances }) } Cw1155QueryMsg::NumTokens { token_id } => { let count = self.token_count(deps.storage, &token_id)?; - to_binary(&NumTokensResponse { count }) + to_json_binary(&NumTokensResponse { count }) } - Cw1155QueryMsg::IsApprovedForAll { owner, operator } => { - let owner_addr = deps.api.addr_validate(&owner)?; - let operator_addr = deps.api.addr_validate(&operator)?; - let approved = self.check_can_approve(deps, &env, &owner_addr, &operator_addr)?; - to_binary(&IsApprovedForAllResponse { approved }) - } - Cw1155QueryMsg::ApprovedForAll { + Cw1155QueryMsg::ApprovalsFor { owner, include_expired, start_after, @@ -81,7 +75,7 @@ where } => { let owner_addr = deps.api.addr_validate(&owner)?; let start_addr = maybe_addr(deps.api, start_after)?; - to_binary(&self.query_all_approvals( + to_json_binary(&self.query_all_approvals( deps, env, owner_addr, @@ -92,7 +86,7 @@ where } Cw1155QueryMsg::TokenInfo { token_id } => { let token_info = self.tokens.load(deps.storage, &token_id)?; - to_binary(&TokenInfoResponse:: { + to_json_binary(&TokenInfoResponse:: { token_uri: token_info.token_uri, extension: token_info.extension, }) @@ -103,18 +97,18 @@ where limit, } => { let owner_addr = deps.api.addr_validate(&owner)?; - to_binary(&self.query_tokens(deps, owner_addr, start_after, limit)?) + to_json_binary(&self.query_tokens(deps, owner_addr, start_after, limit)?) } Cw1155QueryMsg::AllTokens { start_after, limit } => { - to_binary(&self.query_all_tokens(deps, start_after, limit)?) + to_json_binary(&self.query_all_tokens(deps, start_after, limit)?) } } } } impl<'a, T> Cw1155Contract<'a, T> -where - T: Serialize + DeserializeOwned + Clone, + where + T: Serialize + DeserializeOwned + Clone, { fn query_all_approvals( &self, @@ -124,7 +118,7 @@ where include_expired: bool, start_after: Option, limit: Option, - ) -> StdResult { + ) -> StdResult { let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; let start = start_after.as_ref().map(Bound::exclusive); @@ -138,7 +132,7 @@ where .take(limit) .map(build_approval) .collect::>()?; - Ok(ApprovedForAllResponse { operators }) + Ok(ApprovalsForResponse { operators }) } fn query_tokens( diff --git a/contracts/cw1155-base/src/state.rs b/contracts/cw1155-base/src/state.rs index 7ba9c71b6..6b7c7e562 100644 --- a/contracts/cw1155-base/src/state.rs +++ b/contracts/cw1155-base/src/state.rs @@ -8,8 +8,8 @@ use cw1155::{Balance, Expiration}; use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, MultiIndex}; pub struct Cw1155Contract<'a, T> -where - T: Serialize + DeserializeOwned + Clone, + where + T: Serialize + DeserializeOwned + Clone, { pub minter: Item<'a, Addr>, pub token_count: Map<'a, &'a str, Uint128>, @@ -19,8 +19,8 @@ where } impl<'a, T> Default for Cw1155Contract<'static, T> -where - T: Serialize + DeserializeOwned + Clone, + where + T: Serialize + DeserializeOwned + Clone, { fn default() -> Self { Self::new( @@ -35,8 +35,8 @@ where } impl<'a, T> Cw1155Contract<'a, T> -where - T: Serialize + DeserializeOwned + Clone, + where + T: Serialize + DeserializeOwned + Clone, { fn new( minter_key: &'a str, @@ -47,7 +47,7 @@ where balances_token_id_key: &'a str, ) -> Self { let indexes = BalanceIndexes { - token_id: MultiIndex::new(balances_token_id_idx, balances_key, balances_token_id_key), + token_id: MultiIndex::new(|_, b| b.token_id.to_string(), balances_key, balances_token_id_key), }; Self { minter: Item::new(minter_key), @@ -101,12 +101,8 @@ pub struct BalanceIndexes<'a> { } impl<'a> IndexList for BalanceIndexes<'a> { - fn get_indexes(&'_ self) -> Box> + '_> { + fn get_indexes(&'_ self) -> Box> + '_> { let v: Vec<&dyn Index> = vec![&self.token_id]; Box::new(v.into_iter()) } } - -pub fn balances_token_id_idx(d: &Balance) -> String { - d.token_id.clone() -} diff --git a/contracts/cw1155-metadata-onchain/Cargo.toml b/contracts/cw1155-metadata-onchain/Cargo.toml index 32ea77100..9a028bc64 100644 --- a/contracts/cw1155-metadata-onchain/Cargo.toml +++ b/contracts/cw1155-metadata-onchain/Cargo.toml @@ -1,8 +1,7 @@ [package] name = "cw1155-metadata-onchain" version = "0.13.4" -authors = [ -] +authors = ["shab "] edition = "2018" description = "Example extending CW1155 Token to store metadata on chain" license = "Apache-2.0" @@ -26,15 +25,13 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cw2 = "0.13.4" -cw1155 = { path = "../../packages/cw1155", version = "0.13.4" } -cw1155-base = { path = "../cw1155-base", version = "0.13.4", features = [ - "library", -] } -cosmwasm-std = { version = "1.0.0" } -schemars = "0.8.10" -serde = { version = "1.0.140", default-features = false, features = ["derive"] } -thiserror = { version = "1.0.31" } +cw2 = { workspace = true } +cw1155 = { workspace = true } +cw1155-base = { workspace = true, features = ["library"] } +cosmwasm-std = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] -cosmwasm-schema = { version = "1.0.0" } +cosmwasm-schema = { workspace = true } diff --git a/contracts/cw1155-metadata-onchain/examples/schema.rs b/contracts/cw1155-metadata-onchain/examples/schema.rs index 96635c996..5e2644a56 100644 --- a/contracts/cw1155-metadata-onchain/examples/schema.rs +++ b/contracts/cw1155-metadata-onchain/examples/schema.rs @@ -4,9 +4,8 @@ use std::fs::create_dir_all; use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for}; use cw1155::{ - AllBalancesResponse, ApprovedForAllResponse, BalanceResponse, BatchBalanceResponse, - Cw1155QueryMsg, IsApprovedForAllResponse, MinterResponse, NumTokensResponse, TokenInfoResponse, - TokensResponse, + AllBalancesResponse, ApprovalsForResponse, BalanceResponse, BatchBalanceResponse, + Cw1155QueryMsg, MinterResponse, NumTokensResponse, TokenInfoResponse, TokensResponse, }; use cw1155_metadata_onchain::{ExecuteMsg, Extension, InstantiateMsg}; @@ -24,8 +23,7 @@ fn main() { export_schema(&schema_for!(AllBalancesResponse), &out_dir); export_schema(&schema_for!(BatchBalanceResponse), &out_dir); export_schema(&schema_for!(NumTokensResponse), &out_dir); - export_schema(&schema_for!(ApprovedForAllResponse), &out_dir); - export_schema(&schema_for!(IsApprovedForAllResponse), &out_dir); + export_schema(&schema_for!(ApprovalsForResponse), &out_dir); export_schema(&schema_for!(TokensResponse), &out_dir); export_schema(&schema_for!(MinterResponse), &out_dir); export_schema_with_title( diff --git a/contracts/cw1155-metadata-onchain/src/lib.rs b/contracts/cw1155-metadata-onchain/src/lib.rs index b920681a2..3262f1975 100644 --- a/contracts/cw1155-metadata-onchain/src/lib.rs +++ b/contracts/cw1155-metadata-onchain/src/lib.rs @@ -98,7 +98,7 @@ mod tests { let mint_msg = MintMsg { token_id: token_id.to_string(), to: "john".to_string(), - value: Uint128::new(1), + amount: Uint128::new(1), token_uri: Some("https://starships.example.com/Starship/Enterprise.json".into()), extension: Some(Metadata { description: Some("Spaceship with Warp Drive".into()), @@ -122,7 +122,7 @@ mod tests { ) .unwrap(), ) - .unwrap(); + .unwrap(); assert_eq!(res.token_uri, mint_msg.token_uri); assert_eq!(res.extension, mint_msg.extension); diff --git a/packages/cw1155/Cargo.toml b/packages/cw1155/Cargo.toml index d7f517c17..11974f5dc 100644 --- a/packages/cw1155/Cargo.toml +++ b/packages/cw1155/Cargo.toml @@ -1,8 +1,7 @@ [package] name = "cw1155" version = "0.13.4" -authors = [ -] +authors = ["shab "] edition = "2018" description = "Definition and types for the CosmWasm-1155 interface" license = "Apache-2.0" @@ -11,10 +10,9 @@ homepage = "https://cosmwasm.com" documentation = "https://docs.cosmwasm.com" [dependencies] -cw-utils = "0.13.4" -cosmwasm-std = { version = "1.0.0" } -schemars = "0.8.10" -serde = { version = "1.0.140", default-features = false, features = ["derive"] } - -[dev-dependencies] -cosmwasm-schema = { version = "1.0.0" } +cw-utils = { workspace = true } +cosmwasm-std = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +cosmwasm-schema = { workspace = true } diff --git a/packages/cw1155/examples/schema.rs b/packages/cw1155/examples/schema.rs index 4ca9ed1e8..f773ba706 100644 --- a/packages/cw1155/examples/schema.rs +++ b/packages/cw1155/examples/schema.rs @@ -5,9 +5,9 @@ use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, s use cosmwasm_std::Empty; use cw1155::{ - AllBalancesResponse, ApprovedForAllResponse, BalanceResponse, BatchBalanceResponse, - Cw1155ExecuteMsg, Cw1155QueryMsg, Cw1155ReceiveMsg, IsApprovedForAllResponse, MinterResponse, - NumTokensResponse, TokenInfoResponse, TokensResponse, + AllBalancesResponse, ApprovalsForResponse, BalanceResponse, BatchBalanceResponse, + Cw1155ExecuteMsg, Cw1155QueryMsg, Cw1155ReceiveMsg, MinterResponse, NumTokensResponse, + TokenInfoResponse, TokensResponse, }; type Extension = Empty; @@ -24,8 +24,7 @@ fn main() { export_schema(&schema_for!(AllBalancesResponse), &out_dir); export_schema(&schema_for!(BatchBalanceResponse), &out_dir); export_schema(&schema_for!(NumTokensResponse), &out_dir); - export_schema(&schema_for!(ApprovedForAllResponse), &out_dir); - export_schema(&schema_for!(IsApprovedForAllResponse), &out_dir); + export_schema(&schema_for!(ApprovalsForResponse), &out_dir); export_schema(&schema_for!(TokensResponse), &out_dir); export_schema(&schema_for!(MinterResponse), &out_dir); export_schema_with_title( diff --git a/contracts/cw1155-base/src/error.rs b/packages/cw1155/src/error.rs similarity index 90% rename from contracts/cw1155-base/src/error.rs rename to packages/cw1155/src/error.rs index 950c88f7f..e6f4e6115 100644 --- a/contracts/cw1155-base/src/error.rs +++ b/packages/cw1155/src/error.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{OverflowError, StdError}; use thiserror::Error; #[derive(Error, Debug)] -pub enum ContractError { +pub enum Cw1155ContractError { #[error("{0}")] Std(#[from] StdError), diff --git a/packages/cw1155/src/event.rs b/packages/cw1155/src/event.rs index c4541b945..c330e7d86 100644 --- a/packages/cw1155/src/event.rs +++ b/packages/cw1155/src/event.rs @@ -1,41 +1,136 @@ -use cosmwasm_std::{attr, Response, Uint128}; -use cw_utils::Event; +use cosmwasm_std::{Addr, attr, Event}; +use crate::TokenAmount; -/// Tracks token transfer/mint/burn actions + +/// Tracks token transfer actions pub struct TransferEvent { - pub from: Option, - pub to: Option, - pub token_id: String, - pub amount: Uint128, -} - -impl Event for TransferEvent { - fn add_attributes(&self, rsp: &mut Response) { - rsp.attributes.push(attr("action", "transfer")); - rsp.attributes.push(attr("token_id", self.token_id.clone())); - rsp.attributes.push(attr("amount", self.amount)); - if let Some(from) = self.from.clone() { - rsp.attributes.push(attr("from", from)); + pub sender: Addr, + pub recipient: Addr, + pub tokens: Vec, +} + +impl TransferEvent { + pub fn new(sender: &Addr, recipient: &Addr, tokens: Vec) -> Self { + Self { + sender: sender.clone(), + recipient: recipient.clone(), + tokens, } - if let Some(to) = self.to.clone() { - rsp.attributes.push(attr("to", to)); + } +} + +impl From for Event { + fn from(event: TransferEvent) -> Self { + Event::new("transfer_tokens").add_attributes( + vec![ + attr("sender", event.sender.as_str()), + attr("recipient", event.recipient.as_str()), + attr("tokens", event.tokens.iter().map(|t| t.to_string()).collect::>().join(",")), + ] + ) + } +} + +/// Tracks token mint actions +pub struct MintEvent { + pub recipient: Addr, + pub tokens: Vec, +} + +impl MintEvent { + pub fn new(recipient: &Addr, tokens: Vec) -> Self { + Self { + recipient: recipient.clone(), + tokens, + } + } +} + +impl From for Event { + fn from(event: MintEvent) -> Self { + Event::new("mint_tokens").add_attributes( + vec![ + attr("recipient", event.recipient.as_str()), + attr("tokens", event.tokens.iter().map(|t| t.to_string()).collect::>().join(",")), + ] + ) + } +} + +/// Tracks token burn actions +pub struct BurnEvent { + pub sender: Addr, + pub tokens: Vec, +} + +impl BurnEvent { + pub fn new(sender: &Addr, tokens: Vec) -> Self { + Self { + sender: sender.clone(), + tokens, } } } +impl From for Event { + fn from(event: BurnEvent) -> Self { + Event::new("burn_tokens").add_attributes( + vec![ + attr("sender", event.sender.as_str()), + attr("tokens", event.tokens.iter().map(|t| t.to_string()).collect::>().join(",")), + ] + ) + } +} + /// Tracks approve_all status changes -pub struct ApproveAllEvent<'a> { - pub sender: &'a String, - pub operator: &'a String, - pub approved: bool, -} - -impl<'a> Event for ApproveAllEvent<'a> { - fn add_attributes(&self, rsp: &mut Response) { - rsp.attributes.push(attr("action", "approve_all")); - rsp.attributes.push(attr("sender", self.sender)); - rsp.attributes.push(attr("operator", self.operator)); - rsp.attributes - .push(attr("approved", (self.approved as u32).to_string())); +pub struct ApproveAllEvent { + pub sender: Addr, + pub operator: Addr, +} + +impl ApproveAllEvent { + pub fn new(sender: &Addr, operator: &Addr) -> Self { + Self { + sender: sender.clone(), + operator: operator.clone(), + } + } +} + +impl From for Event { + fn from(event: ApproveAllEvent) -> Self { + Event::new("approve_all").add_attributes( + vec![ + attr("sender", event.sender.as_str()), + attr("operator", event.operator.as_str()), + ] + ) + } +} + +/// Tracks revoke_all status changes +pub struct RevokeAllEvent { + pub sender: Addr, + pub operator: Addr, +} + +impl RevokeAllEvent { + pub fn new(sender: &Addr, operator: &Addr) -> Self { + Self { + sender: sender.clone(), + operator: operator.clone(), + } + } +} + +impl From for Event { + fn from(event: RevokeAllEvent) -> Self { + Event::new("revoke_all").add_attributes( + vec![ + attr("sender", event.sender.as_str()), + attr("operator", event.operator.as_str()), + ] + ) } } diff --git a/packages/cw1155/src/lib.rs b/packages/cw1155/src/lib.rs index ededd825c..5658f254b 100644 --- a/packages/cw1155/src/lib.rs +++ b/packages/cw1155/src/lib.rs @@ -2,16 +2,18 @@ mod event; mod msg; mod query; mod receiver; +mod error; pub use cw_utils::Expiration; pub use crate::receiver::{Cw1155BatchReceiveMsg, Cw1155ReceiveMsg}; -pub use crate::msg::Cw1155ExecuteMsg; +pub use crate::msg::{Cw1155ExecuteMsg, TokenAmount}; pub use crate::query::{ - AllBalancesResponse, Approval, ApprovedForAllResponse, Balance, BalanceResponse, - BatchBalanceResponse, Cw1155QueryMsg, IsApprovedForAllResponse, MinterResponse, + AllBalancesResponse, Approval, ApprovalsForResponse, Balance, BalanceResponse, + BatchBalanceResponse, Cw1155QueryMsg, MinterResponse, NumTokensResponse, TokenInfoResponse, TokensResponse, }; -pub use crate::event::{ApproveAllEvent, TransferEvent}; +pub use crate::event::*; +pub use crate::error::Cw1155ContractError; diff --git a/packages/cw1155/src/msg.rs b/packages/cw1155/src/msg.rs index 21d6a63b8..74258b879 100644 --- a/packages/cw1155/src/msg.rs +++ b/packages/cw1155/src/msg.rs @@ -1,3 +1,5 @@ +use std::fmt::{Display, Formatter}; +use cosmwasm_schema::cw_serde; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -49,3 +51,15 @@ pub enum Cw1155ExecuteMsg { /// Remove previously granted ApproveAll permission RevokeAll { operator: String }, } + +#[cw_serde] +pub struct TokenAmount { + pub token_id: String, + pub amount: Uint128, +} + +impl Display for TokenAmount { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", self.token_id, self.amount) + } +} \ No newline at end of file diff --git a/packages/cw1155/src/query.rs b/packages/cw1155/src/query.rs index f120b1936..9d794a15f 100644 --- a/packages/cw1155/src/query.rs +++ b/packages/cw1155/src/query.rs @@ -28,18 +28,15 @@ pub enum Cw1155QueryMsg { }, /// Total number of tokens issued for the token id NumTokens { token_id: String }, - /// List all operators that can access all of the owner's tokens. - /// Return type: ApprovedForAllResponse. - ApprovedForAll { + /// List all operators that can access the owner's tokens. + /// Return type: ApprovalsForResponse. + ApprovalsFor { owner: String, /// unset or false will filter out expired approvals, you must set to true to see them include_expired: Option, start_after: Option, limit: Option, }, - /// Query approved status `owner` granted to `operator`. - /// Return type: IsApprovedForAllResponse - IsApprovedForAll { owner: String, operator: String }, /// With MetaData Extension. /// Query metadata of token @@ -99,15 +96,10 @@ pub struct Approval { } #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] -pub struct ApprovedForAllResponse { +pub struct ApprovalsForResponse { pub operators: Vec, } -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] -pub struct IsApprovedForAllResponse { - pub approved: bool, -} - #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] pub struct TokenInfoResponse { /// Should be a url point to a json file diff --git a/packages/cw1155/src/receiver.rs b/packages/cw1155/src/receiver.rs index 409c0df77..f90469a73 100644 --- a/packages/cw1155/src/receiver.rs +++ b/packages/cw1155/src/receiver.rs @@ -1,7 +1,8 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{to_binary, Binary, CosmosMsg, StdResult, Uint128, WasmMsg}; +use cosmwasm_std::{Binary, CosmosMsg, StdResult, Uint128, WasmMsg, to_json_binary}; +use crate::TokenAmount; /// Cw1155ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] @@ -20,7 +21,7 @@ impl Cw1155ReceiveMsg { /// serializes the message pub fn into_binary(self) -> StdResult { let msg = ReceiverExecuteMsg::Receive(self); - to_binary(&msg) + to_json_binary(&msg) } /// creates a cosmos_msg sending this struct to the named contract @@ -41,7 +42,7 @@ impl Cw1155ReceiveMsg { pub struct Cw1155BatchReceiveMsg { pub operator: String, pub from: Option, - pub batch: Vec<(String, Uint128)>, + pub batch: Vec, pub msg: Binary, } @@ -49,7 +50,7 @@ impl Cw1155BatchReceiveMsg { /// serializes the message pub fn into_binary(self) -> StdResult { let msg = ReceiverExecuteMsg::BatchReceive(self); - to_binary(&msg) + to_json_binary(&msg) } /// creates a cosmos_msg sending this struct to the named contract From 42a4499627e862c4ca9744bcb5a386647a1f729f Mon Sep 17 00:00:00 2001 From: shab Date: Thu, 2 May 2024 11:58:05 -0400 Subject: [PATCH 10/57] fix: bring back ApprovedForAll and IsApprovedForAll queries (part of the standard, was incorrectly removed) --- contracts/cw1155-base/src/execute.rs | 21 +++++++++++++++------ contracts/cw1155-base/src/query.rs | 25 ++++++++++++++++--------- packages/cw1155/src/lib.rs | 8 ++++---- packages/cw1155/src/query.rs | 16 ++++++++++++---- 4 files changed, 47 insertions(+), 23 deletions(-) diff --git a/contracts/cw1155-base/src/execute.rs b/contracts/cw1155-base/src/execute.rs index 5761e17d2..ace0d8ba6 100644 --- a/contracts/cw1155-base/src/execute.rs +++ b/contracts/cw1155-base/src/execute.rs @@ -406,13 +406,9 @@ where amount: owner_balance.amount.min(amount), }; + // todo - logic for checking approval on specific token // owner or operator can approve - if owner == operator - || match self.approves.may_load(storage, (owner, operator))? { - Some(ex) => !ex.is_expired(&env.block), - None => false, - } - { + if owner == operator || self.verify_all_approval(storage, env, owner, operator) { Ok(balance_update) } else { Err(Cw1155ContractError::Unauthorized {}) @@ -435,4 +431,17 @@ where }) .collect() } + + pub fn verify_all_approval( + &self, + storage: &dyn Storage, + env: &Env, + owner: &Addr, + operator: &Addr, + ) -> bool { + match self.approves.load(storage, (owner, operator)) { + Ok(ex) => !ex.is_expired(&env.block), + Err(_) => false, + } + } } diff --git a/contracts/cw1155-base/src/query.rs b/contracts/cw1155-base/src/query.rs index f55ba2e0e..ea7a4efdf 100644 --- a/contracts/cw1155-base/src/query.rs +++ b/contracts/cw1155-base/src/query.rs @@ -4,8 +4,8 @@ use serde::Serialize; use cosmwasm_std::{to_json_binary, Addr, Binary, Deps, Env, Order, StdResult, Uint128}; use cw1155::{ - AllBalancesResponse, Approval, ApprovalsForResponse, Balance, BalanceResponse, - BatchBalanceResponse, Cw1155QueryMsg, Expiration, MinterResponse, + AllBalancesResponse, Approval, ApprovedForAllResponse, Balance, BalanceResponse, + BatchBalanceResponse, Cw1155QueryMsg, Expiration, IsApprovedForAllResponse, MinterResponse, NumTokensResponse, TokenInfoResponse, TokensResponse, }; use cw_storage_plus::Bound; @@ -17,8 +17,8 @@ const DEFAULT_LIMIT: u32 = 10; const MAX_LIMIT: u32 = 100; impl<'a, T> Cw1155Contract<'a, T> - where - T: Serialize + DeserializeOwned + Clone, +where + T: Serialize + DeserializeOwned + Clone, { pub fn query(&self, deps: Deps, env: Env, msg: Cw1155QueryMsg) -> StdResult { match msg { @@ -67,7 +67,7 @@ impl<'a, T> Cw1155Contract<'a, T> let count = self.token_count(deps.storage, &token_id)?; to_json_binary(&NumTokensResponse { count }) } - Cw1155QueryMsg::ApprovalsFor { + Cw1155QueryMsg::ApprovedForAll { owner, include_expired, start_after, @@ -84,6 +84,13 @@ impl<'a, T> Cw1155Contract<'a, T> limit, )?) } + Cw1155QueryMsg::IsApprovedForAll { owner, operator } => { + let owner_addr = deps.api.addr_validate(&owner)?; + let operator_addr = deps.api.addr_validate(&operator)?; + let approved = + self.verify_all_approval(deps.storage, &env, &owner_addr, &operator_addr); + to_json_binary(&IsApprovedForAllResponse { approved }) + } Cw1155QueryMsg::TokenInfo { token_id } => { let token_info = self.tokens.load(deps.storage, &token_id)?; to_json_binary(&TokenInfoResponse:: { @@ -107,8 +114,8 @@ impl<'a, T> Cw1155Contract<'a, T> } impl<'a, T> Cw1155Contract<'a, T> - where - T: Serialize + DeserializeOwned + Clone, +where + T: Serialize + DeserializeOwned + Clone, { fn query_all_approvals( &self, @@ -118,7 +125,7 @@ impl<'a, T> Cw1155Contract<'a, T> include_expired: bool, start_after: Option, limit: Option, - ) -> StdResult { + ) -> StdResult { let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; let start = start_after.as_ref().map(Bound::exclusive); @@ -132,7 +139,7 @@ impl<'a, T> Cw1155Contract<'a, T> .take(limit) .map(build_approval) .collect::>()?; - Ok(ApprovalsForResponse { operators }) + Ok(ApprovedForAllResponse { operators }) } fn query_tokens( diff --git a/packages/cw1155/src/lib.rs b/packages/cw1155/src/lib.rs index 5658f254b..f9637766a 100644 --- a/packages/cw1155/src/lib.rs +++ b/packages/cw1155/src/lib.rs @@ -1,8 +1,8 @@ +mod error; mod event; mod msg; mod query; mod receiver; -mod error; pub use cw_utils::Expiration; @@ -10,10 +10,10 @@ pub use crate::receiver::{Cw1155BatchReceiveMsg, Cw1155ReceiveMsg}; pub use crate::msg::{Cw1155ExecuteMsg, TokenAmount}; pub use crate::query::{ - AllBalancesResponse, Approval, ApprovalsForResponse, Balance, BalanceResponse, - BatchBalanceResponse, Cw1155QueryMsg, MinterResponse, + AllBalancesResponse, Approval, ApprovedForAllResponse, Balance, BalanceResponse, + BatchBalanceResponse, Cw1155QueryMsg, IsApprovedForAllResponse, MinterResponse, NumTokensResponse, TokenInfoResponse, TokensResponse, }; -pub use crate::event::*; pub use crate::error::Cw1155ContractError; +pub use crate::event::*; diff --git a/packages/cw1155/src/query.rs b/packages/cw1155/src/query.rs index 9d794a15f..f120b1936 100644 --- a/packages/cw1155/src/query.rs +++ b/packages/cw1155/src/query.rs @@ -28,15 +28,18 @@ pub enum Cw1155QueryMsg { }, /// Total number of tokens issued for the token id NumTokens { token_id: String }, - /// List all operators that can access the owner's tokens. - /// Return type: ApprovalsForResponse. - ApprovalsFor { + /// List all operators that can access all of the owner's tokens. + /// Return type: ApprovedForAllResponse. + ApprovedForAll { owner: String, /// unset or false will filter out expired approvals, you must set to true to see them include_expired: Option, start_after: Option, limit: Option, }, + /// Query approved status `owner` granted to `operator`. + /// Return type: IsApprovedForAllResponse + IsApprovedForAll { owner: String, operator: String }, /// With MetaData Extension. /// Query metadata of token @@ -96,10 +99,15 @@ pub struct Approval { } #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] -pub struct ApprovalsForResponse { +pub struct ApprovedForAllResponse { pub operators: Vec, } +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct IsApprovedForAllResponse { + pub approved: bool, +} + #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] pub struct TokenInfoResponse { /// Should be a url point to a json file From 159c79e1e4b17fb77c2c43143edde50635d7ec95 Mon Sep 17 00:00:00 2001 From: Shab | Apollo DAO Date: Fri, 3 May 2024 12:49:35 -0400 Subject: [PATCH 11/57] Cw1155 Approve/Revoke single (#3) * feat: token_approvals map for approvals on specific tokens on a set amount. feat: removes previously set token approvals when transferring/burning * fix: decrement token approvals instead of removing if not expired and amount transferred is less than approved amount * feat: execute Approve/Revoke single methods * feat: Approvals query for approvals on a token owner --- contracts/cw1155-base/src/execute.rs | 190 ++++++++++++++++++++++++--- contracts/cw1155-base/src/lib.rs | 3 + contracts/cw1155-base/src/msg.rs | 21 ++- contracts/cw1155-base/src/query.rs | 21 +++ contracts/cw1155-base/src/state.rs | 56 +++++--- packages/cw1155/src/event.rs | 139 +++++++++++++++----- packages/cw1155/src/msg.rs | 4 +- packages/cw1155/src/query.rs | 6 + 8 files changed, 370 insertions(+), 70 deletions(-) diff --git a/contracts/cw1155-base/src/execute.rs b/contracts/cw1155-base/src/execute.rs index ace0d8ba6..f7f000182 100644 --- a/contracts/cw1155-base/src/execute.rs +++ b/contracts/cw1155-base/src/execute.rs @@ -2,17 +2,19 @@ use serde::de::DeserializeOwned; use serde::Serialize; use cosmwasm_std::{ - Addr, Binary, DepsMut, Env, Event, MessageInfo, Response, StdResult, Storage, SubMsg, Uint128, + Addr, Binary, DepsMut, Env, Event, MessageInfo, Order, Response, StdResult, Storage, SubMsg, + Uint128, }; use cw1155::{ - ApproveAllEvent, Balance, BurnEvent, Cw1155BatchReceiveMsg, Cw1155ContractError, - Cw1155ReceiveMsg, Expiration, MintEvent, RevokeAllEvent, TokenAmount, TransferEvent, + ApproveAllEvent, ApproveEvent, Balance, BurnEvent, Cw1155BatchReceiveMsg, Cw1155ContractError, + Cw1155ReceiveMsg, Expiration, MintEvent, RevokeAllEvent, RevokeEvent, TokenAmount, + TransferEvent, }; use cw2::set_contract_version; use crate::msg::{ExecuteMsg, InstantiateMsg, MintMsg}; -use crate::state::{Cw1155Contract, TokenInfo}; +use crate::state::{Cw1155Contract, TokenApproval, TokenInfo}; // Version info for migration const CONTRACT_NAME: &str = "crates.io:cw721-base"; @@ -61,9 +63,20 @@ where } => self.batch_send_from(env, from, to, batch, msg), ExecuteMsg::Burn { token_id, amount } => self.burn(env, token_id, amount), ExecuteMsg::BatchBurn { batch } => self.batch_burn(env, batch), + ExecuteMsg::Approve { + spender, + token_id, + amount, + expires, + } => self.approve_token(env, spender, token_id, amount, expires), ExecuteMsg::ApproveAll { operator, expires } => { self.approve_all(env, operator, expires) } + ExecuteMsg::Revoke { + spender, + token_id, + amount, + } => self.revoke_token(env, spender, token_id, amount), ExecuteMsg::RevokeAll { operator } => self.revoke_all(env, operator), } } @@ -82,7 +95,11 @@ where T: Serialize + DeserializeOwned + Clone, { pub fn mint(&self, env: ExecuteEnv, msg: MintMsg) -> Result { - let ExecuteEnv { mut deps, info, .. } = env; + let ExecuteEnv { + mut deps, + info, + env, + } = env; let to_addr = deps.api.addr_validate(&msg.to)?; if info.sender != self.minter.load(deps.storage)? { @@ -93,6 +110,7 @@ where let event = self.update_transfer_state( &mut deps, + &env, None, Some(to_addr), vec![TokenAmount { @@ -144,6 +162,7 @@ where let event = self.update_transfer_state( &mut deps, + &env, Some(from.clone()), Some(to.clone()), vec![TokenAmount { @@ -191,6 +210,7 @@ where let mut rsp = Response::default(); let event = self.update_transfer_state( &mut deps, + &env, Some(from.clone()), Some(to.clone()), batch.to_vec(), @@ -234,6 +254,7 @@ where let event = self.update_transfer_state( &mut deps, + &env, Some(from.clone()), None, vec![TokenAmount { @@ -262,7 +283,48 @@ where let batch = self.verify_approvals(deps.storage, &env, &info, from, batch)?; let mut rsp = Response::default(); - let event = self.update_transfer_state(&mut deps, Some(from.clone()), None, batch)?; + let event = self.update_transfer_state(&mut deps, &env, Some(from.clone()), None, batch)?; + rsp = rsp.add_event(event); + + Ok(rsp) + } + + pub fn approve_token( + &self, + env: ExecuteEnv, + operator: String, + token_id: String, + amount: Option, + expiration: Option, + ) -> Result { + let ExecuteEnv { deps, info, env } = env; + + // reject expired data as invalid + let expiration = expiration.unwrap_or_default(); + if expiration.is_expired(&env.block) { + return Err(Cw1155ContractError::Expired {}); + } + + // get sender's token balance to get valid approval amount + let balance = self + .balances + .load(deps.storage, (info.sender.clone(), token_id.to_string()))?; + let approval_amount = amount.unwrap_or(Uint128::MAX).min(balance.amount); + + // store the approval + let operator = deps.api.addr_validate(&operator)?; + self.token_approves.save( + deps.storage, + (&token_id, &info.sender, &operator), + &TokenApproval { + amount: approval_amount, + expiration, + }, + )?; + + let mut rsp = Response::default(); + + let event = ApproveEvent::new(&info.sender, &operator, &token_id, approval_amount).into(); rsp = rsp.add_event(event); Ok(rsp) @@ -295,6 +357,46 @@ where Ok(rsp) } + pub fn revoke_token( + &self, + env: ExecuteEnv, + operator: String, + token_id: String, + amount: Option, + ) -> Result { + let ExecuteEnv { deps, info, .. } = env; + let operator = deps.api.addr_validate(&operator)?; + + // get prev approval amount to get valid revoke amount + let prev_approval = self + .token_approves + .load(deps.storage, (&token_id, &info.sender, &operator))?; + let revoke_amount = amount.unwrap_or(Uint128::MAX).min(prev_approval.amount); + + // remove or update approval + if revoke_amount == prev_approval.amount { + self.token_approves + .remove(deps.storage, (&token_id, &info.sender, &operator)); + } else { + self.token_approves.update( + deps.storage, + (&token_id, &info.sender, &operator), + |prev| -> StdResult<_> { + let mut new_approval = prev.unwrap(); + new_approval.amount = new_approval.amount.checked_sub(revoke_amount)?; + Ok(new_approval) + }, + )?; + } + + let mut rsp = Response::default(); + + let event = RevokeEvent::new(&info.sender, &operator, &token_id, revoke_amount).into(); + rsp = rsp.add_event(event); + + Ok(rsp) + } + pub fn revoke_all( &self, env: ExecuteEnv, @@ -321,6 +423,7 @@ where fn update_transfer_state( &self, deps: &mut DepsMut, + env: &Env, from: Option, to: Option, tokens: Vec, @@ -363,14 +466,41 @@ where } let event = if let Some(from) = &from { + for TokenAmount { token_id, amount } in &tokens { + // remove token approvals + for (operator, approval) in self + .token_approves + .prefix((&token_id, from)) + .range(deps.storage, None, None, Order::Ascending) + .collect::>>()? + { + if approval.is_expired(&env) || approval.amount <= *amount { + self.token_approves + .remove(deps.storage, (&token_id, &from, &operator)); + } else { + self.token_approves.update( + deps.storage, + (&token_id, &from, &operator), + |prev| -> StdResult<_> { + let mut new_approval = prev.unwrap(); + new_approval.amount = new_approval.amount.checked_sub(*amount)?; + Ok(new_approval) + }, + )?; + } + } + + // decrement tokens if burning + if to.is_none() { + self.decrement_tokens(deps.storage, token_id, amount)?; + } + } + if let Some(to) = &to { // transfer TransferEvent::new(from, to, tokens).into() } else { // burn - for TokenAmount { token_id, amount } in &tokens { - self.decrement_tokens(deps.storage, token_id, amount)?; - } BurnEvent::new(from, tokens).into() } } else if let Some(to) = &to { @@ -401,18 +531,25 @@ where let owner_balance = self .balances .load(storage, (owner.clone(), token_id.to_string()))?; - let balance_update = TokenAmount { + let mut balance_update = TokenAmount { token_id: token_id.to_string(), amount: owner_balance.amount.min(amount), }; - // todo - logic for checking approval on specific token - // owner or operator can approve + // owner or all operator can execute if owner == operator || self.verify_all_approval(storage, env, owner, operator) { - Ok(balance_update) - } else { - Err(Cw1155ContractError::Unauthorized {}) + return Ok(balance_update); } + + // token operator can execute up to approved amount + if let Some(token_approval) = + self.get_active_token_approval(storage, env, owner, operator, token_id) + { + balance_update.amount = balance_update.amount.min(token_approval.amount); + return Ok(balance_update); + } + + Err(Cw1155ContractError::Unauthorized {}) } /// returns valid token amounts if the sender can execute or is approved to execute on all provided tokens @@ -444,4 +581,27 @@ where Err(_) => false, } } + + pub fn get_active_token_approval( + &self, + storage: &dyn Storage, + env: &Env, + owner: &Addr, + operator: &Addr, + token_id: &str, + ) -> Option { + match self + .token_approves + .load(storage, (&token_id, owner, operator)) + { + Ok(approval) => { + if !approval.is_expired(&env) { + Some(approval) + } else { + None + } + } + Err(_) => None, + } + } } diff --git a/contracts/cw1155-base/src/lib.rs b/contracts/cw1155-base/src/lib.rs index 4d1609c37..e7a4bf83c 100644 --- a/contracts/cw1155-base/src/lib.rs +++ b/contracts/cw1155-base/src/lib.rs @@ -9,6 +9,9 @@ pub use crate::state::Cw1155Contract; use cosmwasm_std::Empty; use cw1155::Cw1155QueryMsg; +// todo - is this standard? +pub const CW_ADDRESS_LENGTH: usize = 62; + // This is a simple type to let us handle empty extensions pub type Extension = Option; diff --git a/contracts/cw1155-base/src/msg.rs b/contracts/cw1155-base/src/msg.rs index d32173a9c..27c09a5b4 100644 --- a/contracts/cw1155-base/src/msg.rs +++ b/contracts/cw1155-base/src/msg.rs @@ -40,13 +40,24 @@ pub enum ExecuteMsg { msg: Option, }, /// Burn is a base message to burn tokens. - Burn { + Burn { token_id: String, amount: Uint128 }, + /// BatchBurn is a base message to burn multiple types of tokens in batch. + BatchBurn { batch: Vec }, + /// Allows operator to transfer / send the token from the owner's account. + /// If expiration is set, then this allowance has a time/height limit + Approve { + spender: String, token_id: String, - amount: Uint128, + /// Optional amount to approve. If None, approve entire balance. + amount: Option, + expires: Option, }, - /// BatchBurn is a base message to burn multiple types of tokens in batch. - BatchBurn { - batch: Vec, + /// Remove previously granted Approval + Revoke { + spender: String, + token_id: String, + /// Optional amount to revoke. If None, revoke entire amount. + amount: Option, }, /// Allows operator to transfer / send any token from the owner's account. /// If expiration is set, then this allowance has a time/height limit diff --git a/contracts/cw1155-base/src/query.rs b/contracts/cw1155-base/src/query.rs index ea7a4efdf..6f7a1d6fb 100644 --- a/contracts/cw1155-base/src/query.rs +++ b/contracts/cw1155-base/src/query.rs @@ -67,6 +67,27 @@ where let count = self.token_count(deps.storage, &token_id)?; to_json_binary(&NumTokensResponse { count }) } + Cw1155QueryMsg::Approvals { + owner, + token_id, + include_expired, + } => { + let owner = deps.api.addr_validate(&owner)?; + let approvals = self + .token_approves + .prefix((&token_id, &owner)) + .range(deps.storage, None, None, Order::Ascending) + .filter_map(|approval| { + let (_, approval) = approval.unwrap(); + if include_expired.unwrap_or(false) || !approval.is_expired(&env) { + Some(approval) + } else { + None + } + }) + .collect::>(); + to_json_binary(&approvals) + } Cw1155QueryMsg::ApprovedForAll { owner, include_expired, diff --git a/contracts/cw1155-base/src/state.rs b/contracts/cw1155-base/src/state.rs index 6b7c7e562..48cac2fda 100644 --- a/contracts/cw1155-base/src/state.rs +++ b/contracts/cw1155-base/src/state.rs @@ -1,59 +1,73 @@ +use cosmwasm_schema::cw_serde; use schemars::JsonSchema; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{Addr, StdResult, Storage, Uint128}; +use cosmwasm_std::{Addr, Env, StdResult, Storage, Uint128}; use cw1155::{Balance, Expiration}; use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, MultiIndex}; pub struct Cw1155Contract<'a, T> - where - T: Serialize + DeserializeOwned + Clone, +where + T: Serialize + DeserializeOwned + Clone, { pub minter: Item<'a, Addr>, + // key: token id pub token_count: Map<'a, &'a str, Uint128>, + // key: (owner, token id) pub balances: IndexedMap<'a, (Addr, String), Balance, BalanceIndexes<'a>>, + // key: (owner, spender) pub approves: Map<'a, (&'a Addr, &'a Addr), Expiration>, + // key: (token id, owner, spender) + pub token_approves: Map<'a, (&'a str, &'a Addr, &'a Addr), TokenApproval>, + // key: token id pub tokens: Map<'a, &'a str, TokenInfo>, } impl<'a, T> Default for Cw1155Contract<'static, T> - where - T: Serialize + DeserializeOwned + Clone, +where + T: Serialize + DeserializeOwned + Clone, { fn default() -> Self { Self::new( "minter", + "tokens", "token_count", "balances", - "approves", - "tokens", "balances__token_id", + "approves", + "token_approves", ) } } impl<'a, T> Cw1155Contract<'a, T> - where - T: Serialize + DeserializeOwned + Clone, +where + T: Serialize + DeserializeOwned + Clone, { fn new( minter_key: &'a str, + tokens_key: &'a str, token_count_key: &'a str, balances_key: &'a str, - approves_key: &'a str, - tokens_key: &'a str, balances_token_id_key: &'a str, + approves_key: &'a str, + token_approves_key: &'a str, ) -> Self { - let indexes = BalanceIndexes { - token_id: MultiIndex::new(|_, b| b.token_id.to_string(), balances_key, balances_token_id_key), + let balances_indexes = BalanceIndexes { + token_id: MultiIndex::new( + |_, b| b.token_id.to_string(), + balances_key, + balances_token_id_key, + ), }; Self { minter: Item::new(minter_key), token_count: Map::new(token_count_key), - balances: IndexedMap::new(balances_key, indexes), + balances: IndexedMap::new(balances_key, balances_indexes), approves: Map::new(approves_key), + token_approves: Map::new(token_approves_key), tokens: Map::new(tokens_key), } } @@ -101,8 +115,20 @@ pub struct BalanceIndexes<'a> { } impl<'a> IndexList for BalanceIndexes<'a> { - fn get_indexes(&'_ self) -> Box> + '_> { + fn get_indexes(&'_ self) -> Box> + '_> { let v: Vec<&dyn Index> = vec![&self.token_id]; Box::new(v.into_iter()) } } + +#[cw_serde] +pub struct TokenApproval { + pub amount: Uint128, + pub expiration: Expiration, +} + +impl TokenApproval { + pub fn is_expired(&self, env: &Env) -> bool { + self.expiration.is_expired(&env.block) + } +} diff --git a/packages/cw1155/src/event.rs b/packages/cw1155/src/event.rs index c330e7d86..35263dfb1 100644 --- a/packages/cw1155/src/event.rs +++ b/packages/cw1155/src/event.rs @@ -1,6 +1,5 @@ -use cosmwasm_std::{Addr, attr, Event}; use crate::TokenAmount; - +use cosmwasm_std::{attr, Addr, Event, Uint128}; /// Tracks token transfer actions pub struct TransferEvent { @@ -21,13 +20,19 @@ impl TransferEvent { impl From for Event { fn from(event: TransferEvent) -> Self { - Event::new("transfer_tokens").add_attributes( - vec![ - attr("sender", event.sender.as_str()), - attr("recipient", event.recipient.as_str()), - attr("tokens", event.tokens.iter().map(|t| t.to_string()).collect::>().join(",")), - ] - ) + Event::new("transfer_tokens").add_attributes(vec![ + attr("sender", event.sender.as_str()), + attr("recipient", event.recipient.as_str()), + attr( + "tokens", + event + .tokens + .iter() + .map(|t| t.to_string()) + .collect::>() + .join(","), + ), + ]) } } @@ -48,12 +53,18 @@ impl MintEvent { impl From for Event { fn from(event: MintEvent) -> Self { - Event::new("mint_tokens").add_attributes( - vec![ - attr("recipient", event.recipient.as_str()), - attr("tokens", event.tokens.iter().map(|t| t.to_string()).collect::>().join(",")), - ] - ) + Event::new("mint_tokens").add_attributes(vec![ + attr("recipient", event.recipient.as_str()), + attr( + "tokens", + event + .tokens + .iter() + .map(|t| t.to_string()) + .collect::>() + .join(","), + ), + ]) } } @@ -74,12 +85,78 @@ impl BurnEvent { impl From for Event { fn from(event: BurnEvent) -> Self { - Event::new("burn_tokens").add_attributes( - vec![ - attr("sender", event.sender.as_str()), - attr("tokens", event.tokens.iter().map(|t| t.to_string()).collect::>().join(",")), - ] - ) + Event::new("burn_tokens").add_attributes(vec![ + attr("sender", event.sender.as_str()), + attr( + "tokens", + event + .tokens + .iter() + .map(|t| t.to_string()) + .collect::>() + .join(","), + ), + ]) + } +} + +/// Tracks approve status changes +pub struct ApproveEvent { + pub sender: Addr, + pub operator: Addr, + pub token_id: String, + pub amount: Uint128, +} + +impl ApproveEvent { + pub fn new(sender: &Addr, operator: &Addr, token_id: &str, amount: Uint128) -> Self { + Self { + sender: sender.clone(), + operator: operator.clone(), + token_id: token_id.to_string(), + amount, + } + } +} + +impl From for Event { + fn from(event: ApproveEvent) -> Self { + Event::new("approve_single").add_attributes(vec![ + attr("sender", event.sender.as_str()), + attr("operator", event.operator.as_str()), + attr("token_id", event.token_id), + attr("amount", event.amount.to_string()), + ]) + } +} + +/// Tracks revoke status changes +pub struct RevokeEvent { + pub sender: Addr, + pub operator: Addr, + pub token_id: String, + pub amount: Uint128, +} + +impl RevokeEvent { + pub fn new(sender: &Addr, operator: &Addr, token_id: &str, amount: Uint128) -> Self { + Self { + sender: sender.clone(), + operator: operator.clone(), + token_id: token_id.to_string(), + amount, + } + } +} + +impl From for Event { + fn from(event: RevokeEvent) -> Self { + Event::new("revoke_single").add_attributes(vec![ + attr("sender", event.sender.as_str()), + attr("operator", event.operator.as_str()), + attr("token_id", event.token_id), + attr("amount", event.amount.to_string()), + ]) } } @@ -100,12 +177,10 @@ impl ApproveAllEvent { impl From for Event { fn from(event: ApproveAllEvent) -> Self { - Event::new("approve_all").add_attributes( - vec![ - attr("sender", event.sender.as_str()), - attr("operator", event.operator.as_str()), - ] - ) + Event::new("approve_all").add_attributes(vec![ + attr("sender", event.sender.as_str()), + attr("operator", event.operator.as_str()), + ]) } } @@ -126,11 +201,9 @@ impl RevokeAllEvent { impl From for Event { fn from(event: RevokeAllEvent) -> Self { - Event::new("revoke_all").add_attributes( - vec![ - attr("sender", event.sender.as_str()), - attr("operator", event.operator.as_str()), - ] - ) + Event::new("revoke_all").add_attributes(vec![ + attr("sender", event.sender.as_str()), + attr("operator", event.operator.as_str()), + ]) } } diff --git a/packages/cw1155/src/msg.rs b/packages/cw1155/src/msg.rs index 74258b879..adceb2e97 100644 --- a/packages/cw1155/src/msg.rs +++ b/packages/cw1155/src/msg.rs @@ -1,7 +1,7 @@ -use std::fmt::{Display, Formatter}; use cosmwasm_schema::cw_serde; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use std::fmt::{Display, Formatter}; use cosmwasm_std::{Binary, Uint128}; use cw_utils::Expiration; @@ -62,4 +62,4 @@ impl Display for TokenAmount { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}:{}", self.token_id, self.amount) } -} \ No newline at end of file +} diff --git a/packages/cw1155/src/query.rs b/packages/cw1155/src/query.rs index f120b1936..c970e29d3 100644 --- a/packages/cw1155/src/query.rs +++ b/packages/cw1155/src/query.rs @@ -28,6 +28,12 @@ pub enum Cw1155QueryMsg { }, /// Total number of tokens issued for the token id NumTokens { token_id: String }, + /// Return approvals that a token owner has + Approvals { + owner: String, + token_id: String, + include_expired: Option, + }, /// List all operators that can access all of the owner's tokens. /// Return type: ApprovedForAllResponse. ApprovedForAll { From ad05092904aaf3d328ddac1ebd3b73a8c18d6374 Mon Sep 17 00:00:00 2001 From: shab Date: Fri, 3 May 2024 14:31:34 -0400 Subject: [PATCH 12/57] refactor: moves cw1155 messages to package, renames messages to closer resemble standard, adds missing messages (todo - implement) --- Cargo.lock | 2 + contracts/cw1155-base/src/contract_tests.rs | 10 +- contracts/cw1155-base/src/execute.rs | 115 +++++++++++-------- contracts/cw1155-base/src/lib.rs | 38 ++++-- contracts/cw1155-base/src/msg.rs | 88 -------------- contracts/cw1155-base/src/query.rs | 33 ++++-- contracts/cw1155-base/src/state.rs | 30 ++--- contracts/cw1155-metadata-onchain/src/lib.rs | 25 ++-- packages/cw1155/Cargo.toml | 2 + packages/cw1155/examples/schema.rs | 33 ++---- packages/cw1155/src/error.rs | 10 +- packages/cw1155/src/lib.rs | 2 +- packages/cw1155/src/msg.rs | 114 ++++++++++++++---- packages/cw1155/src/query.rs | 80 ++++++++----- 14 files changed, 309 insertions(+), 273 deletions(-) delete mode 100644 contracts/cw1155-base/src/msg.rs diff --git a/Cargo.lock b/Cargo.lock index 6253f0ab1..9a42ecc6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -318,6 +318,8 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-utils 1.0.3", + "cw2 1.1.2", + "cw721 0.18.0", "schemars", "serde", "thiserror", diff --git a/contracts/cw1155-base/src/contract_tests.rs b/contracts/cw1155-base/src/contract_tests.rs index 8245652b2..129a79306 100644 --- a/contracts/cw1155-base/src/contract_tests.rs +++ b/contracts/cw1155-base/src/contract_tests.rs @@ -91,7 +91,7 @@ mod tests { contract.query( deps.as_ref(), mock_env(), - Cw1155QueryMsg::Balance { + Cw1155QueryMsg::BalanceOf { owner: user1.clone(), token_id: token1.clone(), } @@ -152,7 +152,7 @@ mod tests { contract.query( deps.as_ref(), mock_env(), - Cw1155QueryMsg::Balance { + Cw1155QueryMsg::BalanceOf { owner: user2.clone(), token_id: token1.clone(), } @@ -165,7 +165,7 @@ mod tests { contract.query( deps.as_ref(), mock_env(), - Cw1155QueryMsg::Balance { + Cw1155QueryMsg::BalanceOf { owner: user1.clone(), token_id: token1.clone(), } @@ -271,7 +271,7 @@ mod tests { contract.query( deps.as_ref(), mock_env(), - Cw1155QueryMsg::BatchBalance { + Cw1155QueryMsg::BalanceOfBatch { owner: user1.clone(), token_ids: vec![token1.clone(), token2.clone(), token3.clone()], } @@ -611,7 +611,7 @@ mod tests { contract.query( deps.as_ref(), mock_env(), - Cw1155QueryMsg::AllTokens { + Cw1155QueryMsg::AllTokenInfo { start_after: Some("token5".to_owned()), limit: Some(5), }, diff --git a/contracts/cw1155-base/src/execute.rs b/contracts/cw1155-base/src/execute.rs index f7f000182..7aac6b870 100644 --- a/contracts/cw1155-base/src/execute.rs +++ b/contracts/cw1155-base/src/execute.rs @@ -1,35 +1,29 @@ -use serde::de::DeserializeOwned; -use serde::Serialize; - +use crate::state::{Cw1155Contract, TokenInfo}; +use crate::{CONTRACT_NAME, CONTRACT_VERSION}; use cosmwasm_std::{ - Addr, Binary, DepsMut, Env, Event, MessageInfo, Order, Response, StdResult, Storage, SubMsg, - Uint128, + Addr, Binary, CustomMsg, DepsMut, Env, Event, MessageInfo, Order, Response, StdResult, Storage, + SubMsg, Uint128, }; - use cw1155::{ ApproveAllEvent, ApproveEvent, Balance, BurnEvent, Cw1155BatchReceiveMsg, Cw1155ContractError, - Cw1155ReceiveMsg, Expiration, MintEvent, RevokeAllEvent, RevokeEvent, TokenAmount, - TransferEvent, + Cw1155ExecuteMsg, Cw1155InstantiateMsg, Cw1155ReceiveMsg, Expiration, MintEvent, MintMsg, + RevokeAllEvent, RevokeEvent, TokenAmount, TokenApproval, TransferEvent, }; use cw2::set_contract_version; +use serde::de::DeserializeOwned; +use serde::Serialize; -use crate::msg::{ExecuteMsg, InstantiateMsg, MintMsg}; -use crate::state::{Cw1155Contract, TokenApproval, TokenInfo}; - -// Version info for migration -const CONTRACT_NAME: &str = "crates.io:cw721-base"; -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -impl<'a, T> Cw1155Contract<'a, T> +impl<'a, T, Q> Cw1155Contract<'a, T, Q> where T: Serialize + DeserializeOwned + Clone, + Q: CustomMsg, { pub fn instantiate( &self, deps: DepsMut, _env: Env, _info: MessageInfo, - msg: InstantiateMsg, + msg: Cw1155InstantiateMsg, ) -> StdResult { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; @@ -43,41 +37,51 @@ where deps: DepsMut, env: Env, info: MessageInfo, - msg: ExecuteMsg, + msg: Cw1155ExecuteMsg, ) -> Result { let env = ExecuteEnv { deps, env, info }; match msg { - ExecuteMsg::Mint(msg) => self.mint(env, msg), - ExecuteMsg::SendFrom { + // cw1155 + Cw1155ExecuteMsg::SendBatch { from, to, - token_id, - amount, + batch, msg, - } => self.send_from(env, from, to, token_id, amount, msg), - ExecuteMsg::BatchSendFrom { + } => self.send_batch(env, from, to, batch, msg), + Cw1155ExecuteMsg::MintBatch(_) => { + todo!() + } + Cw1155ExecuteMsg::BurnBatch { from, batch } => self.burn_batch(env, from, batch), + Cw1155ExecuteMsg::ApproveAll { operator, expires } => { + self.approve_all(env, operator, expires) + } + Cw1155ExecuteMsg::RevokeAll { operator } => self.revoke_all(env, operator), + + // cw721 + Cw1155ExecuteMsg::Send { from, to, - batch, + token_id, + amount, msg, - } => self.batch_send_from(env, from, to, batch, msg), - ExecuteMsg::Burn { token_id, amount } => self.burn(env, token_id, amount), - ExecuteMsg::BatchBurn { batch } => self.batch_burn(env, batch), - ExecuteMsg::Approve { + } => self.send(env, from, to, token_id, amount, msg), + Cw1155ExecuteMsg::Mint(msg) => self.mint(env, msg), + Cw1155ExecuteMsg::Burn { + from, + token_id, + amount, + } => self.burn(env, from, token_id, amount), + Cw1155ExecuteMsg::Approve { spender, token_id, amount, expires, } => self.approve_token(env, spender, token_id, amount, expires), - ExecuteMsg::ApproveAll { operator, expires } => { - self.approve_all(env, operator, expires) - } - ExecuteMsg::Revoke { + Cw1155ExecuteMsg::Revoke { spender, token_id, amount, } => self.revoke_token(env, spender, token_id, amount), - ExecuteMsg::RevokeAll { operator } => self.revoke_all(env, operator), } } } @@ -90,9 +94,10 @@ pub struct ExecuteEnv<'a> { } // helper -impl<'a, T> Cw1155Contract<'a, T> +impl<'a, T, Q> Cw1155Contract<'a, T, Q> where T: Serialize + DeserializeOwned + Clone, + Q: CustomMsg, { pub fn mint(&self, env: ExecuteEnv, msg: MintMsg) -> Result { let ExecuteEnv { @@ -137,10 +142,10 @@ where Ok(rsp) } - pub fn send_from( + pub fn send( &self, env: ExecuteEnv, - from: String, + from: Option, to: String, token_id: String, amount: Uint128, @@ -152,11 +157,15 @@ where info, } = env; - let from = deps.api.addr_validate(&from)?; + let from = &if let Some(from) = from { + deps.api.addr_validate(&from)? + } else { + info.sender.clone() + }; let to = deps.api.addr_validate(&to)?; let balance_update = - self.verify_approval(deps.storage, &env, &info, &from, &token_id, amount)?; + self.verify_approval(deps.storage, &env, &info, from, &token_id, amount)?; let mut rsp = Response::default(); @@ -188,10 +197,10 @@ where Ok(rsp) } - pub fn batch_send_from( + pub fn send_batch( &self, env: ExecuteEnv, - from: String, + from: Option, to: String, batch: Vec, msg: Option, @@ -202,10 +211,14 @@ where info, } = env; - let from = deps.api.addr_validate(&from)?; + let from = &if let Some(from) = from { + deps.api.addr_validate(&from)? + } else { + info.sender.clone() + }; let to = deps.api.addr_validate(&to)?; - let batch = self.verify_approvals(deps.storage, &env, &info, &from, batch)?; + let batch = self.verify_approvals(deps.storage, &env, &info, from, batch)?; let mut rsp = Response::default(); let event = self.update_transfer_state( @@ -235,6 +248,7 @@ where pub fn burn( &self, env: ExecuteEnv, + from: Option, token_id: String, amount: Uint128, ) -> Result { @@ -244,7 +258,11 @@ where env, } = env; - let from = &info.sender; + let from = &if let Some(from) = from { + deps.api.addr_validate(&from)? + } else { + info.sender.clone() + }; // whoever can transfer these tokens can burn let balance_update = @@ -267,9 +285,10 @@ where Ok(rsp) } - pub fn batch_burn( + pub fn burn_batch( &self, env: ExecuteEnv, + from: Option, batch: Vec, ) -> Result { let ExecuteEnv { @@ -278,7 +297,11 @@ where env, } = env; - let from = &info.sender; + let from = &if let Some(from) = from { + deps.api.addr_validate(&from)? + } else { + info.sender.clone() + }; let batch = self.verify_approvals(deps.storage, &env, &info, from, batch)?; diff --git a/contracts/cw1155-base/src/lib.rs b/contracts/cw1155-base/src/lib.rs index e7a4bf83c..f8407d4ff 100644 --- a/contracts/cw1155-base/src/lib.rs +++ b/contracts/cw1155-base/src/lib.rs @@ -1,27 +1,27 @@ mod contract_tests; mod execute; -mod msg; mod query; mod state; -pub use crate::msg::{ExecuteMsg, InstantiateMsg, MintMsg}; pub use crate::state::Cw1155Contract; use cosmwasm_std::Empty; use cw1155::Cw1155QueryMsg; -// todo - is this standard? -pub const CW_ADDRESS_LENGTH: usize = 62; - // This is a simple type to let us handle empty extensions pub type Extension = Option; +// Version info for migration +pub const CONTRACT_NAME: &str = "crates.io:cw1155-base"; +pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const EXPECTED_FROM_VERSION: &str = CONTRACT_VERSION; + pub mod entry { use super::*; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; - use cw1155::Cw1155ContractError; + use cw1155::{Cw1155ContractError, Cw1155ExecuteMsg, Cw1155InstantiateMsg}; // This makes a conscious choice on the various generics used by the contract #[cfg_attr(not(feature = "library"), entry_point)] @@ -29,9 +29,11 @@ pub mod entry { deps: DepsMut, env: Env, info: MessageInfo, - msg: InstantiateMsg, + msg: Cw1155InstantiateMsg, ) -> StdResult { - let tract = Cw1155Contract::::default(); + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + let tract = Cw1155Contract::::default(); tract.instantiate(deps, env, info, msg) } @@ -40,15 +42,27 @@ pub mod entry { deps: DepsMut, env: Env, info: MessageInfo, - msg: ExecuteMsg, + msg: Cw1155ExecuteMsg, ) -> Result { - let tract = Cw1155Contract::::default(); + let tract = Cw1155Contract::::default(); tract.execute(deps, env, info, msg) } #[cfg_attr(not(feature = "library"), entry_point)] - pub fn query(deps: Deps, env: Env, msg: Cw1155QueryMsg) -> StdResult { - let tract = Cw1155Contract::::default(); + pub fn query(deps: Deps, env: Env, msg: Cw1155QueryMsg) -> StdResult { + let tract = Cw1155Contract::::default(); tract.query(deps, env, msg) } + + #[cfg_attr(not(feature = "library"), entry_point)] + pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> Result { + // make sure the correct contract is being upgraded, and it's being + // upgraded from the correct version. + cw2::assert_contract_version(deps.as_ref().storage, CONTRACT_NAME, EXPECTED_FROM_VERSION)?; + + // update contract version + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + Ok(Response::default()) + } } diff --git a/contracts/cw1155-base/src/msg.rs b/contracts/cw1155-base/src/msg.rs deleted file mode 100644 index 27c09a5b4..000000000 --- a/contracts/cw1155-base/src/msg.rs +++ /dev/null @@ -1,88 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::{Binary, Uint128}; -use cw1155::{Expiration, TokenAmount}; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct InstantiateMsg { - /// The minter is the only one who can create new tokens. - /// This is designed for a base token platform that is controlled by an external program or - /// contract. - pub minter: String, -} - -/// This is like Cw1155ExecuteMsg but we add a Mint command for a minter -/// to make this stand-alone. You will likely want to remove mint and -/// use other control logic in any contract that inherits this. -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] -#[serde(rename_all = "snake_case")] -pub enum ExecuteMsg { - /// SendFrom is a base message to move tokens, - /// if `env.sender` is the owner or has sufficient pre-approval. - SendFrom { - from: String, - /// If `to` is not contract, `msg` should be `None` - to: String, - token_id: String, - amount: Uint128, - /// `None` means don't call the receiver interface - msg: Option, - }, - /// BatchSendFrom is a base message to move multiple types of tokens in batch, - /// if `env.sender` is the owner or has sufficient pre-approval. - BatchSendFrom { - from: String, - /// if `to` is not contract, `msg` should be `None` - to: String, - batch: Vec, - /// `None` means don't call the receiver interface - msg: Option, - }, - /// Burn is a base message to burn tokens. - Burn { token_id: String, amount: Uint128 }, - /// BatchBurn is a base message to burn multiple types of tokens in batch. - BatchBurn { batch: Vec }, - /// Allows operator to transfer / send the token from the owner's account. - /// If expiration is set, then this allowance has a time/height limit - Approve { - spender: String, - token_id: String, - /// Optional amount to approve. If None, approve entire balance. - amount: Option, - expires: Option, - }, - /// Remove previously granted Approval - Revoke { - spender: String, - token_id: String, - /// Optional amount to revoke. If None, revoke entire amount. - amount: Option, - }, - /// Allows operator to transfer / send any token from the owner's account. - /// If expiration is set, then this allowance has a time/height limit - ApproveAll { - operator: String, - expires: Option, - }, - /// Remove previously granted ApproveAll permission - RevokeAll { operator: String }, - - /// Mint a new NFT, can only be called by the contract minter - Mint(MintMsg), -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct MintMsg { - pub token_id: String, - /// The owner of the newly minted tokens - pub to: String, - /// The amount of the newly minted tokens - pub amount: Uint128, - - /// Only first mint can set `token_uri` and `extension` - /// Metadata JSON Schema - pub token_uri: Option, - /// Any custom extension used by this contract - pub extension: Option, -} diff --git a/contracts/cw1155-base/src/query.rs b/contracts/cw1155-base/src/query.rs index 6f7a1d6fb..3377ee77a 100644 --- a/contracts/cw1155-base/src/query.rs +++ b/contracts/cw1155-base/src/query.rs @@ -1,7 +1,7 @@ use serde::de::DeserializeOwned; use serde::Serialize; -use cosmwasm_std::{to_json_binary, Addr, Binary, Deps, Env, Order, StdResult, Uint128}; +use cosmwasm_std::{to_json_binary, Addr, Binary, CustomMsg, Deps, Env, Order, StdResult, Uint128}; use cw1155::{ AllBalancesResponse, Approval, ApprovedForAllResponse, Balance, BalanceResponse, @@ -16,17 +16,18 @@ use crate::state::Cw1155Contract; const DEFAULT_LIMIT: u32 = 10; const MAX_LIMIT: u32 = 100; -impl<'a, T> Cw1155Contract<'a, T> +impl<'a, T, Q> Cw1155Contract<'a, T, Q> where T: Serialize + DeserializeOwned + Clone, + Q: CustomMsg, { - pub fn query(&self, deps: Deps, env: Env, msg: Cw1155QueryMsg) -> StdResult { + pub fn query(&self, deps: Deps, env: Env, msg: Cw1155QueryMsg) -> StdResult { match msg { Cw1155QueryMsg::Minter {} => { let minter = self.minter.load(deps.storage)?.to_string(); to_json_binary(&MinterResponse { minter }) } - Cw1155QueryMsg::Balance { owner, token_id } => { + Cw1155QueryMsg::BalanceOf { owner, token_id } => { let owner_addr = deps.api.addr_validate(&owner)?; let balance = self .balances @@ -45,7 +46,7 @@ where start_after, limit, } => to_json_binary(&self.query_all_balances(deps, token_id, start_after, limit)?), - Cw1155QueryMsg::BatchBalance { owner, token_ids } => { + Cw1155QueryMsg::BalanceOfBatch { owner, token_ids } => { let owner_addr = deps.api.addr_validate(&owner)?; let balances = token_ids .into_iter() @@ -67,7 +68,7 @@ where let count = self.token_count(deps.storage, &token_id)?; to_json_binary(&NumTokensResponse { count }) } - Cw1155QueryMsg::Approvals { + Cw1155QueryMsg::TokenApprovals { owner, token_id, include_expired, @@ -88,7 +89,7 @@ where .collect::>(); to_json_binary(&approvals) } - Cw1155QueryMsg::ApprovedForAll { + Cw1155QueryMsg::ApprovalsForAll { owner, include_expired, start_after, @@ -127,16 +128,30 @@ where let owner_addr = deps.api.addr_validate(&owner)?; to_json_binary(&self.query_tokens(deps, owner_addr, start_after, limit)?) } - Cw1155QueryMsg::AllTokens { start_after, limit } => { + Cw1155QueryMsg::AllTokenInfo { start_after, limit } => { to_json_binary(&self.query_all_tokens(deps, start_after, limit)?) } + Cw1155QueryMsg::ContractInfo {} => { + todo!() + // to_json_binary(&self.contract_info(deps)?) + } + Cw1155QueryMsg::Supply { .. } => { + todo!() + } + Cw1155QueryMsg::AllTokens { .. } => { + todo!() + } + Cw1155QueryMsg::Extension { .. } => { + todo!() + } } } } -impl<'a, T> Cw1155Contract<'a, T> +impl<'a, T, Q> Cw1155Contract<'a, T, Q> where T: Serialize + DeserializeOwned + Clone, + Q: CustomMsg, { fn query_all_approvals( &self, diff --git a/contracts/cw1155-base/src/state.rs b/contracts/cw1155-base/src/state.rs index 48cac2fda..d85d3a528 100644 --- a/contracts/cw1155-base/src/state.rs +++ b/contracts/cw1155-base/src/state.rs @@ -1,16 +1,17 @@ -use cosmwasm_schema::cw_serde; use schemars::JsonSchema; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; +use std::marker::PhantomData; -use cosmwasm_std::{Addr, Env, StdResult, Storage, Uint128}; +use cosmwasm_std::{Addr, CustomMsg, StdResult, Storage, Uint128}; -use cw1155::{Balance, Expiration}; +use cw1155::{Balance, Expiration, TokenApproval}; use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, MultiIndex}; -pub struct Cw1155Contract<'a, T> +pub struct Cw1155Contract<'a, T, Q> where T: Serialize + DeserializeOwned + Clone, + Q: CustomMsg, { pub minter: Item<'a, Addr>, // key: token id @@ -23,11 +24,14 @@ where pub token_approves: Map<'a, (&'a str, &'a Addr, &'a Addr), TokenApproval>, // key: token id pub tokens: Map<'a, &'a str, TokenInfo>, + + pub(crate) _custom_query: PhantomData, } -impl<'a, T> Default for Cw1155Contract<'static, T> +impl<'a, T, Q> Default for Cw1155Contract<'static, T, Q> where T: Serialize + DeserializeOwned + Clone, + Q: CustomMsg, { fn default() -> Self { Self::new( @@ -42,9 +46,10 @@ where } } -impl<'a, T> Cw1155Contract<'a, T> +impl<'a, T, Q> Cw1155Contract<'a, T, Q> where T: Serialize + DeserializeOwned + Clone, + Q: CustomMsg, { fn new( minter_key: &'a str, @@ -69,6 +74,7 @@ where approves: Map::new(approves_key), token_approves: Map::new(token_approves_key), tokens: Map::new(tokens_key), + _custom_query: PhantomData, } } @@ -120,15 +126,3 @@ impl<'a> IndexList for BalanceIndexes<'a> { Box::new(v.into_iter()) } } - -#[cw_serde] -pub struct TokenApproval { - pub amount: Uint128, - pub expiration: Expiration, -} - -impl TokenApproval { - pub fn is_expired(&self, env: &Env) -> bool { - self.expiration.is_expired(&env.block) - } -} diff --git a/contracts/cw1155-metadata-onchain/src/lib.rs b/contracts/cw1155-metadata-onchain/src/lib.rs index 3262f1975..8085ec45a 100644 --- a/contracts/cw1155-metadata-onchain/src/lib.rs +++ b/contracts/cw1155-metadata-onchain/src/lib.rs @@ -1,7 +1,9 @@ +use cosmwasm_std::Empty; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -pub use cw1155_base::{ContractError, InstantiateMsg, MintMsg}; +use cw1155::Cw1155ExecuteMsg; +pub use cw1155::{Cw1155ContractError, Cw1155InstantiateMsg, MintMsg}; use cw2::set_contract_version; // Version info for migration @@ -31,15 +33,16 @@ pub struct Metadata { pub type Extension = Metadata; -pub type Cw1155MetadataContract<'a> = cw1155_base::Cw1155Contract<'a, Extension>; -pub type ExecuteMsg = cw1155_base::ExecuteMsg; +pub type Cw1155MetadataContract<'a> = cw1155_base::Cw1155Contract<'a, Extension, Empty>; +pub type ExecuteMsg = Cw1155ExecuteMsg; -#[cfg(not(feature = "library"))] +// #[cfg(not(feature = "library"))] pub mod entry { use super::*; use cosmwasm_std::entry_point; use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; + use cw1155::Cw1155QueryMsg; // This makes a conscious choice on the various generics used by the contract #[entry_point] @@ -47,12 +50,12 @@ pub mod entry { mut deps: DepsMut, env: Env, info: MessageInfo, - msg: InstantiateMsg, - ) -> Result { + msg: Cw1155InstantiateMsg, + ) -> Result { let res = Cw1155MetadataContract::default().instantiate(deps.branch(), env, info, msg)?; // Explicitly set contract name and version, otherwise set to cw1155-base info set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION) - .map_err(ContractError::Std)?; + .map_err(Cw1155ContractError::Std)?; Ok(res) } @@ -62,12 +65,12 @@ pub mod entry { env: Env, info: MessageInfo, msg: ExecuteMsg, - ) -> Result { + ) -> Result { Cw1155MetadataContract::default().execute(deps, env, info, msg) } #[entry_point] - pub fn query(deps: Deps, env: Env, msg: cw1155::Cw1155QueryMsg) -> StdResult { + pub fn query(deps: Deps, env: Env, msg: Cw1155QueryMsg) -> StdResult { Cw1155MetadataContract::default().query(deps, env, msg) } } @@ -87,7 +90,7 @@ mod tests { let contract = Cw1155MetadataContract::default(); let info = mock_info(CREATOR, &[]); - let init_msg = InstantiateMsg { + let init_msg = Cw1155InstantiateMsg { minter: CREATOR.to_string(), }; contract @@ -122,7 +125,7 @@ mod tests { ) .unwrap(), ) - .unwrap(); + .unwrap(); assert_eq!(res.token_uri, mint_msg.token_uri); assert_eq!(res.extension, mint_msg.extension); diff --git a/packages/cw1155/Cargo.toml b/packages/cw1155/Cargo.toml index 11974f5dc..21d0a4579 100644 --- a/packages/cw1155/Cargo.toml +++ b/packages/cw1155/Cargo.toml @@ -10,6 +10,8 @@ homepage = "https://cosmwasm.com" documentation = "https://docs.cosmwasm.com" [dependencies] +cw2 = { workspace = true } +cw721 = { workspace = true } cw-utils = { workspace = true } cosmwasm-std = { workspace = true } schemars = { workspace = true } diff --git a/packages/cw1155/examples/schema.rs b/packages/cw1155/examples/schema.rs index f773ba706..da6deb476 100644 --- a/packages/cw1155/examples/schema.rs +++ b/packages/cw1155/examples/schema.rs @@ -1,35 +1,16 @@ -use std::env::current_dir; use std::fs::create_dir_all; -use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for}; +use cosmwasm_schema::{remove_schemas, write_api}; use cosmwasm_std::Empty; -use cw1155::{ - AllBalancesResponse, ApprovalsForResponse, BalanceResponse, BatchBalanceResponse, - Cw1155ExecuteMsg, Cw1155QueryMsg, Cw1155ReceiveMsg, MinterResponse, NumTokensResponse, - TokenInfoResponse, TokensResponse, -}; +use cw1155::{Cw1155ExecuteMsg, Cw1155QueryMsg}; type Extension = Empty; fn main() { - let mut out_dir = current_dir().unwrap(); - out_dir.push("schema"); - create_dir_all(&out_dir).unwrap(); - remove_schemas(&out_dir).unwrap(); - export_schema_with_title(&schema_for!(Cw1155ExecuteMsg), &out_dir, "ExecuteMsg"); - export_schema(&schema_for!(Cw1155QueryMsg), &out_dir); - export_schema(&schema_for!(Cw1155ReceiveMsg), &out_dir); - export_schema(&schema_for!(BalanceResponse), &out_dir); - export_schema(&schema_for!(AllBalancesResponse), &out_dir); - export_schema(&schema_for!(BatchBalanceResponse), &out_dir); - export_schema(&schema_for!(NumTokensResponse), &out_dir); - export_schema(&schema_for!(ApprovalsForResponse), &out_dir); - export_schema(&schema_for!(TokensResponse), &out_dir); - export_schema(&schema_for!(MinterResponse), &out_dir); - export_schema_with_title( - &schema_for!(TokenInfoResponse), - &out_dir, - "TokenInfoResponse", - ); + write_api! { + instantiate: InstantiateMsg, + execute: Cw1155ExecuteMsg, + query: Cw1155QueryMsg, + } } diff --git a/packages/cw1155/src/error.rs b/packages/cw1155/src/error.rs index e6f4e6115..db80659b9 100644 --- a/packages/cw1155/src/error.rs +++ b/packages/cw1155/src/error.rs @@ -1,13 +1,17 @@ use cosmwasm_std::{OverflowError, StdError}; +use cw2::VersionError; use thiserror::Error; #[derive(Error, Debug)] pub enum Cw1155ContractError { - #[error("{0}")] + #[error("StdError: {0}")] Std(#[from] StdError), - #[error("{0}")] - OverflowError(#[from] OverflowError), + #[error("OverflowError: {0}")] + Overflow(#[from] OverflowError), + + #[error("Version: {0}")] + Version(#[from] VersionError), #[error("Unauthorized")] Unauthorized {}, diff --git a/packages/cw1155/src/lib.rs b/packages/cw1155/src/lib.rs index f9637766a..9b6803e9e 100644 --- a/packages/cw1155/src/lib.rs +++ b/packages/cw1155/src/lib.rs @@ -8,7 +8,7 @@ pub use cw_utils::Expiration; pub use crate::receiver::{Cw1155BatchReceiveMsg, Cw1155ReceiveMsg}; -pub use crate::msg::{Cw1155ExecuteMsg, TokenAmount}; +pub use crate::msg::{Cw1155ExecuteMsg, Cw1155InstantiateMsg, MintMsg, TokenAmount, TokenApproval}; pub use crate::query::{ AllBalancesResponse, Approval, ApprovedForAllResponse, Balance, BalanceResponse, BatchBalanceResponse, Cw1155QueryMsg, IsApprovedForAllResponse, MinterResponse, diff --git a/packages/cw1155/src/msg.rs b/packages/cw1155/src/msg.rs index adceb2e97..876543ad1 100644 --- a/packages/cw1155/src/msg.rs +++ b/packages/cw1155/src/msg.rs @@ -3,53 +3,105 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; -use cosmwasm_std::{Binary, Uint128}; +use cosmwasm_std::{Binary, Env, Uint128}; use cw_utils::Expiration; +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct Cw1155InstantiateMsg { + /// The minter is the only one who can create new tokens. + /// This is designed for a base token platform that is controlled by an external program or + /// contract. + pub minter: String, +} + +/// This is like Cw1155ExecuteMsg but we add a Mint command for a minter +/// to make this stand-alone. You will likely want to remove mint and +/// use other control logic in any contract that inherits this. #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] #[serde(rename_all = "snake_case")] -pub enum Cw1155ExecuteMsg { - /// SendFrom is a base message to move tokens, +pub enum Cw1155ExecuteMsg { + // cw1155 + /// BatchSendFrom is a base message to move multiple types of tokens in batch, /// if `env.sender` is the owner or has sufficient pre-approval. - SendFrom { - from: String, - /// If `to` is not contract, `msg` should be `None` + SendBatch { + /// check approval if from is Some, otherwise assume sender is owner + from: Option, + /// if `to` is not contract, `msg` should be `None` to: String, - token_id: String, - value: Uint128, + batch: Vec, /// `None` means don't call the receiver interface msg: Option, }, - /// BatchSendFrom is a base message to move multiple types of tokens in batch, + /// Mint a batch of tokens, can only be called by the contract minter + MintBatch(Vec>), + /// BatchBurn is a base message to burn multiple types of tokens in batch. + BurnBatch { + /// check approval if from is Some, otherwise assume sender is owner + from: Option, + batch: Vec, + }, + /// Allows operator to transfer / send any token from the owner's account. + /// If expiration is set, then this allowance has a time/height limit + ApproveAll { + operator: String, + expires: Option, + }, + /// Remove previously granted ApproveAll permission + RevokeAll { operator: String }, + + // cw721 + /// SendFrom is a base message to move tokens, /// if `env.sender` is the owner or has sufficient pre-approval. - BatchSendFrom { - from: String, - /// if `to` is not contract, `msg` should be `None` + Send { + /// check approval if from is Some, otherwise assume sender is owner + from: Option, + /// If `to` is not contract, `msg` should be `None` to: String, - batch: Vec<(String, Uint128)>, + token_id: String, + amount: Uint128, /// `None` means don't call the receiver interface msg: Option, }, + /// Mint a new NFT, can only be called by the contract minter + Mint(MintMsg), /// Burn is a base message to burn tokens. Burn { - from: String, + /// check approval if from is Some, otherwise assume sender is owner + from: Option, token_id: String, - value: Uint128, - }, - /// BatchBurn is a base message to burn multiple types of tokens in batch. - BatchBurn { - from: String, - batch: Vec<(String, Uint128)>, + amount: Uint128, }, - /// Allows operator to transfer / send any token from the owner's account. + /// Allows operator to transfer / send the token from the owner's account. /// If expiration is set, then this allowance has a time/height limit - ApproveAll { - operator: String, + Approve { + spender: String, + token_id: String, + /// Optional amount to approve. If None, approve entire balance. + amount: Option, expires: Option, }, + /// Remove previously granted Approval + Revoke { + spender: String, + token_id: String, + /// Optional amount to revoke. If None, revoke entire amount. + amount: Option, + }, +} - /// Remove previously granted ApproveAll permission - RevokeAll { operator: String }, +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct MintMsg { + pub token_id: String, + /// The owner of the newly minted tokens + pub to: String, + /// The amount of the newly minted tokens + pub amount: Uint128, + + /// Only first mint can set `token_uri` and `extension` + /// Metadata JSON Schema + pub token_uri: Option, + /// Any custom extension used by this contract + pub extension: Option, } #[cw_serde] @@ -63,3 +115,15 @@ impl Display for TokenAmount { write!(f, "{}:{}", self.token_id, self.amount) } } + +#[cw_serde] +pub struct TokenApproval { + pub amount: Uint128, + pub expiration: Expiration, +} + +impl TokenApproval { + pub fn is_expired(&self, env: &Env) -> bool { + self.expiration.is_expired(&env.block) + } +} diff --git a/packages/cw1155/src/query.rs b/packages/cw1155/src/query.rs index c970e29d3..dbe7d18f0 100644 --- a/packages/cw1155/src/query.rs +++ b/packages/cw1155/src/query.rs @@ -1,60 +1,78 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use cosmwasm_std::{Addr, Uint128}; use cw_utils::Expiration; -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] -#[serde(rename_all = "snake_case")] -pub enum Cw1155QueryMsg { - /// Query Minter. - /// Return type: MinterResponse. - Minter {}, +#[cw_serde] +#[derive(QueryResponses)] +pub enum Cw1155QueryMsg { + // cw1155 /// Returns the current balance of the given address, 0 if unset. - /// Return type: BalanceResponse. - Balance { owner: String, token_id: String }, - /// Returns all current balances of the given token id. Supports pagination - /// Return type: AllBalancesResponse. - AllBalances { - token_id: String, - start_after: Option, - limit: Option, - }, + #[returns(BalanceResponse)] + BalanceOf { owner: String, token_id: String }, /// Returns the current balance of the given address for a batch of tokens, 0 if unset. - /// Return type: BatchBalanceResponse. - BatchBalance { + #[returns(BatchBalanceResponse)] + BalanceOfBatch { owner: String, token_ids: Vec, }, - /// Total number of tokens issued for the token id - NumTokens { token_id: String }, + /// Query approved status `owner` granted to `operator`. + #[returns(IsApprovedForAllResponse)] + IsApprovedForAll { owner: String, operator: String }, /// Return approvals that a token owner has - Approvals { + #[returns(Vec)] + TokenApprovals { owner: String, token_id: String, include_expired: Option, }, /// List all operators that can access all of the owner's tokens. - /// Return type: ApprovedForAllResponse. - ApprovedForAll { + #[returns(ApprovedForAllResponse)] + ApprovalsForAll { owner: String, /// unset or false will filter out expired approvals, you must set to true to see them include_expired: Option, start_after: Option, limit: Option, }, - /// Query approved status `owner` granted to `operator`. - /// Return type: IsApprovedForAllResponse - IsApprovedForAll { owner: String, operator: String }, + /// Returns all current balances of the given token id. Supports pagination + #[returns(AllBalancesResponse)] + AllBalances { + token_id: String, + start_after: Option, + limit: Option, + }, + /// Total number of tokens issued + #[returns(cw721::NumTokensResponse)] + Supply {}, + /// Total number of tokens issued for the token id + #[returns(cw721::NumTokensResponse)] + NumTokens { token_id: String }, + // cw721 + /// With MetaData Extension. + /// Returns top-level metadata about the contract. + #[returns(cw721::ContractInfoResponse)] + ContractInfo {}, + /// Query Minter. + #[returns(MinterResponse)] + Minter {}, /// With MetaData Extension. /// Query metadata of token - /// Return type: TokenInfoResponse. + #[returns(TokenInfoResponse)] TokenInfo { token_id: String }, - + /// With Enumerable extension. + /// Requires pagination. Lists all token_ids controlled by the contract. + #[returns(TokensResponse)] + AllTokenInfo { + start_after: Option, + limit: Option, + }, /// With Enumerable extension. /// Returns all tokens owned by the given address, [] if unset. - /// Return type: TokensResponse. + #[returns(TokensResponse)] Tokens { owner: String, start_after: Option, @@ -62,11 +80,15 @@ pub enum Cw1155QueryMsg { }, /// With Enumerable extension. /// Requires pagination. Lists all token_ids controlled by the contract. - /// Return type: TokensResponse. + #[returns(TokensResponse)] AllTokens { start_after: Option, limit: Option, }, + + /// Extension query + #[returns(())] + Extension { msg: Q }, } #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] From 823639f9a6c853f85a32911af12b295b8763de13 Mon Sep 17 00:00:00 2001 From: shab Date: Fri, 3 May 2024 14:40:15 -0400 Subject: [PATCH 13/57] fix: mint logic --- contracts/cw1155-base/src/execute.rs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/contracts/cw1155-base/src/execute.rs b/contracts/cw1155-base/src/execute.rs index 7aac6b870..d118a3658 100644 --- a/contracts/cw1155-base/src/execute.rs +++ b/contracts/cw1155-base/src/execute.rs @@ -27,6 +27,8 @@ where ) -> StdResult { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + // todo - cw ownership + let minter = deps.api.addr_validate(&msg.minter)?; self.minter.save(deps.storage, &minter)?; Ok(Response::default()) @@ -105,7 +107,7 @@ where info, env, } = env; - let to_addr = deps.api.addr_validate(&msg.to)?; + let to = deps.api.addr_validate(&msg.to)?; if info.sender != self.minter.load(deps.storage)? { return Err(Cw1155ContractError::Unauthorized {}); @@ -113,11 +115,11 @@ where let mut rsp = Response::default(); - let event = self.update_transfer_state( + let event = self.update_balances( &mut deps, &env, None, - Some(to_addr), + Some(to), vec![TokenAmount { token_id: msg.token_id.to_string(), amount: msg.amount, @@ -127,16 +129,11 @@ where // insert if not exist (if it is the first mint) if !self.tokens.has(deps.storage, &msg.token_id) { - // Add token info let token_info = TokenInfo { token_uri: msg.token_uri, extension: msg.extension, }; - self.tokens.save(deps.storage, &msg.token_id, &token_info)?; - - // Increase num token - self.increment_tokens(deps.storage, &msg.token_id, &msg.amount)?; } Ok(rsp) @@ -169,7 +166,7 @@ where let mut rsp = Response::default(); - let event = self.update_transfer_state( + let event = self.update_balances( &mut deps, &env, Some(from.clone()), @@ -221,7 +218,7 @@ where let batch = self.verify_approvals(deps.storage, &env, &info, from, batch)?; let mut rsp = Response::default(); - let event = self.update_transfer_state( + let event = self.update_balances( &mut deps, &env, Some(from.clone()), @@ -270,7 +267,7 @@ where let mut rsp = Response::default(); - let event = self.update_transfer_state( + let event = self.update_balances( &mut deps, &env, Some(from.clone()), @@ -306,7 +303,7 @@ where let batch = self.verify_approvals(deps.storage, &env, &info, from, batch)?; let mut rsp = Response::default(); - let event = self.update_transfer_state(&mut deps, &env, Some(from.clone()), None, batch)?; + let event = self.update_balances(&mut deps, &env, Some(from.clone()), None, batch)?; rsp = rsp.add_event(event); Ok(rsp) @@ -443,7 +440,7 @@ where /// When both are Some: transfer tokens /// /// Make sure permissions are checked before calling this. - fn update_transfer_state( + fn update_balances( &self, deps: &mut DepsMut, env: &Env, From 71a1f2a69557eb4e204d910dd83151f393122b25 Mon Sep 17 00:00:00 2001 From: shab Date: Fri, 3 May 2024 14:52:35 -0400 Subject: [PATCH 14/57] fix: rename events to be in format *_single and *_batch --- packages/cw1155/src/event.rs | 87 +++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/packages/cw1155/src/event.rs b/packages/cw1155/src/event.rs index 35263dfb1..d5252a722 100644 --- a/packages/cw1155/src/event.rs +++ b/packages/cw1155/src/event.rs @@ -1,5 +1,5 @@ use crate::TokenAmount; -use cosmwasm_std::{attr, Addr, Event, Uint128}; +use cosmwasm_std::{attr, Addr, Attribute, Event, Uint128}; /// Tracks token transfer actions pub struct TransferEvent { @@ -20,19 +20,19 @@ impl TransferEvent { impl From for Event { fn from(event: TransferEvent) -> Self { - Event::new("transfer_tokens").add_attributes(vec![ + Event::new(format!( + "transfer_{}", + if event.tokens.len() == 1 { + "single" + } else { + "batch" + } + )) + .add_attributes(vec![ attr("sender", event.sender.as_str()), attr("recipient", event.recipient.as_str()), - attr( - "tokens", - event - .tokens - .iter() - .map(|t| t.to_string()) - .collect::>() - .join(","), - ), ]) + .add_attributes(token_attributes(event.tokens)) } } @@ -53,18 +53,16 @@ impl MintEvent { impl From for Event { fn from(event: MintEvent) -> Self { - Event::new("mint_tokens").add_attributes(vec![ - attr("recipient", event.recipient.as_str()), - attr( - "tokens", - event - .tokens - .iter() - .map(|t| t.to_string()) - .collect::>() - .join(","), - ), - ]) + Event::new(format!( + "mint_{}", + if event.tokens.len() == 1 { + "single" + } else { + "batch" + } + )) + .add_attribute("recipient", event.recipient.as_str()) + .add_attributes(token_attributes(event.tokens)) } } @@ -85,18 +83,16 @@ impl BurnEvent { impl From for Event { fn from(event: BurnEvent) -> Self { - Event::new("burn_tokens").add_attributes(vec![ - attr("sender", event.sender.as_str()), - attr( - "tokens", - event - .tokens - .iter() - .map(|t| t.to_string()) - .collect::>() - .join(","), - ), - ]) + Event::new(format!( + "burn_{}", + if event.tokens.len() == 1 { + "single" + } else { + "batch" + } + )) + .add_attribute("sender", event.sender.as_str()) + .add_attributes(token_attributes(event.tokens)) } } @@ -207,3 +203,24 @@ impl From for Event { ]) } } + +pub fn token_attributes(tokens: Vec) -> Vec { + vec![ + attr( + format!("token_id{}", if tokens.len() == 1 { "" } else { "s" }), + tokens + .iter() + .map(|t| t.token_id.to_string()) + .collect::>() + .join(","), + ), + attr( + format!("amount{}", if tokens.len() == 1 { "" } else { "s" }), + tokens + .iter() + .map(|t| t.amount.to_string()) + .collect::>() + .join(","), + ), + ] +} From 5875ad0ee864b028501f9d1528fd98f90eff354e Mon Sep 17 00:00:00 2001 From: shab Date: Fri, 3 May 2024 15:19:38 -0400 Subject: [PATCH 15/57] fix: schema files --- .gitignore | 4 +- contracts/cw1155-base/examples/schema.rs | 36 +- .../schema/all_balances_response.json | 45 -- .../schema/approved_for_all_response.json | 97 ---- .../cw1155-base/schema/balance_response.json | 19 - .../schema/batch_balance_response.json | 22 - .../cw1155-base/schema/cw1155_query_msg.json | 280 ------------ contracts/cw1155-base/schema/execute_msg.json | 360 --------------- .../cw1155-base/schema/instantiate_msg.json | 14 - .../schema/is_approved_for_all_response.json | 13 - .../cw1155-base/schema/minter_response.json | 14 - .../schema/num_tokens_response.json | 19 - .../schema/token_info_response.json | 38 -- .../cw1155-base/schema/tokens_response.json | 17 - .../examples/schema.rs | 37 +- .../schema/all_balances_response.json | 45 -- .../schema/approved_for_all_response.json | 97 ---- .../schema/balance_response.json | 19 - .../schema/batch_balance_response.json | 22 - .../schema/cw1155_query_msg.json | 280 ------------ .../schema/execute_msg.json | 432 ------------------ .../schema/instantiate_msg.json | 14 - .../schema/is_approved_for_all_response.json | 13 - .../schema/minter_response.json | 14 - .../schema/num_tokens_response.json | 19 - .../schema/token_info_response.json | 110 ----- .../schema/tokens_response.json | 17 - packages/cw1155/examples/schema.rs | 8 +- .../cw1155/schema/all_balances_response.json | 45 -- .../schema/approved_for_all_response.json | 97 ---- packages/cw1155/schema/balance_response.json | 19 - .../cw1155/schema/batch_balance_response.json | 22 - packages/cw1155/schema/cw1155_query_msg.json | 280 ------------ .../cw1155/schema/cw1155_receive_msg.json | 44 -- packages/cw1155/schema/execute_msg.json | 292 ------------ .../schema/is_approved_for_all_response.json | 13 - packages/cw1155/schema/minter_response.json | 14 - .../cw1155/schema/num_tokens_response.json | 19 - .../cw1155/schema/token_info_response.json | 31 -- packages/cw1155/schema/tokens_response.json | 17 - 40 files changed, 24 insertions(+), 2974 deletions(-) delete mode 100644 contracts/cw1155-base/schema/all_balances_response.json delete mode 100644 contracts/cw1155-base/schema/approved_for_all_response.json delete mode 100644 contracts/cw1155-base/schema/balance_response.json delete mode 100644 contracts/cw1155-base/schema/batch_balance_response.json delete mode 100644 contracts/cw1155-base/schema/cw1155_query_msg.json delete mode 100644 contracts/cw1155-base/schema/execute_msg.json delete mode 100644 contracts/cw1155-base/schema/instantiate_msg.json delete mode 100644 contracts/cw1155-base/schema/is_approved_for_all_response.json delete mode 100644 contracts/cw1155-base/schema/minter_response.json delete mode 100644 contracts/cw1155-base/schema/num_tokens_response.json delete mode 100644 contracts/cw1155-base/schema/token_info_response.json delete mode 100644 contracts/cw1155-base/schema/tokens_response.json delete mode 100644 contracts/cw1155-metadata-onchain/schema/all_balances_response.json delete mode 100644 contracts/cw1155-metadata-onchain/schema/approved_for_all_response.json delete mode 100644 contracts/cw1155-metadata-onchain/schema/balance_response.json delete mode 100644 contracts/cw1155-metadata-onchain/schema/batch_balance_response.json delete mode 100644 contracts/cw1155-metadata-onchain/schema/cw1155_query_msg.json delete mode 100644 contracts/cw1155-metadata-onchain/schema/execute_msg.json delete mode 100644 contracts/cw1155-metadata-onchain/schema/instantiate_msg.json delete mode 100644 contracts/cw1155-metadata-onchain/schema/is_approved_for_all_response.json delete mode 100644 contracts/cw1155-metadata-onchain/schema/minter_response.json delete mode 100644 contracts/cw1155-metadata-onchain/schema/num_tokens_response.json delete mode 100644 contracts/cw1155-metadata-onchain/schema/token_info_response.json delete mode 100644 contracts/cw1155-metadata-onchain/schema/tokens_response.json delete mode 100644 packages/cw1155/schema/all_balances_response.json delete mode 100644 packages/cw1155/schema/approved_for_all_response.json delete mode 100644 packages/cw1155/schema/balance_response.json delete mode 100644 packages/cw1155/schema/batch_balance_response.json delete mode 100644 packages/cw1155/schema/cw1155_query_msg.json delete mode 100644 packages/cw1155/schema/cw1155_receive_msg.json delete mode 100644 packages/cw1155/schema/execute_msg.json delete mode 100644 packages/cw1155/schema/is_approved_for_all_response.json delete mode 100644 packages/cw1155/schema/minter_response.json delete mode 100644 packages/cw1155/schema/num_tokens_response.json delete mode 100644 packages/cw1155/schema/token_info_response.json delete mode 100644 packages/cw1155/schema/tokens_response.json diff --git a/.gitignore b/.gitignore index ad53e6f59..9424941ec 100644 --- a/.gitignore +++ b/.gitignore @@ -26,5 +26,5 @@ artifacts/ # code coverage tarpaulin-report.* -# raw json schema files -**/raw/*.json \ No newline at end of file +# json schema files +**/schema/**/*.json diff --git a/contracts/cw1155-base/examples/schema.rs b/contracts/cw1155-base/examples/schema.rs index 6b2785d70..01ed7589c 100644 --- a/contracts/cw1155-base/examples/schema.rs +++ b/contracts/cw1155-base/examples/schema.rs @@ -1,33 +1,15 @@ -use std::env::current_dir; use std::fs::create_dir_all; -use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for}; +use cosmwasm_schema::{remove_schemas, write_api}; +use cosmwasm_std::Empty; -use cw1155::{ - AllBalancesResponse, ApprovalsForResponse, BalanceResponse, BatchBalanceResponse, - Cw1155QueryMsg, MinterResponse, NumTokensResponse, TokenInfoResponse, TokensResponse, -}; -use cw1155_base::{ExecuteMsg, Extension, InstantiateMsg}; +use cw1155::{Cw1155ExecuteMsg, Cw1155InstantiateMsg, Cw1155QueryMsg}; +use cw1155_base::Extension; fn main() { - let mut out_dir = current_dir().unwrap(); - out_dir.push("schema"); - create_dir_all(&out_dir).unwrap(); - remove_schemas(&out_dir).unwrap(); - - export_schema(&schema_for!(InstantiateMsg), &out_dir); - export_schema_with_title(&schema_for!(ExecuteMsg), &out_dir, "ExecuteMsg"); - export_schema(&schema_for!(Cw1155QueryMsg), &out_dir); - export_schema(&schema_for!(BalanceResponse), &out_dir); - export_schema(&schema_for!(AllBalancesResponse), &out_dir); - export_schema(&schema_for!(BatchBalanceResponse), &out_dir); - export_schema(&schema_for!(NumTokensResponse), &out_dir); - export_schema(&schema_for!(ApprovalsForResponse), &out_dir); - export_schema(&schema_for!(TokensResponse), &out_dir); - export_schema(&schema_for!(MinterResponse), &out_dir); - export_schema_with_title( - &schema_for!(TokenInfoResponse), - &out_dir, - "TokenInfoResponse", - ); + write_api! { + instantiate: Cw1155InstantiateMsg, + execute: Cw1155ExecuteMsg, + query: Cw1155QueryMsg, + } } diff --git a/contracts/cw1155-base/schema/all_balances_response.json b/contracts/cw1155-base/schema/all_balances_response.json deleted file mode 100644 index 06cc4f9d3..000000000 --- a/contracts/cw1155-base/schema/all_balances_response.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AllBalancesResponse", - "type": "object", - "required": [ - "balances" - ], - "properties": { - "balances": { - "type": "array", - "items": { - "$ref": "#/definitions/Balance" - } - } - }, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Balance": { - "type": "object", - "required": [ - "amount", - "owner", - "token_id" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "owner": { - "$ref": "#/definitions/Addr" - }, - "token_id": { - "type": "string" - } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/cw1155-base/schema/approved_for_all_response.json b/contracts/cw1155-base/schema/approved_for_all_response.json deleted file mode 100644 index 453f17b7c..000000000 --- a/contracts/cw1155-base/schema/approved_for_all_response.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ApprovedForAllResponse", - "type": "object", - "required": [ - "operators" - ], - "properties": { - "operators": { - "type": "array", - "items": { - "$ref": "#/definitions/Approval" - } - } - }, - "definitions": { - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - } - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object" - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/cw1155-base/schema/balance_response.json b/contracts/cw1155-base/schema/balance_response.json deleted file mode 100644 index 4e1a0be2b..000000000 --- a/contracts/cw1155-base/schema/balance_response.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "BalanceResponse", - "type": "object", - "required": [ - "balance" - ], - "properties": { - "balance": { - "$ref": "#/definitions/Uint128" - } - }, - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/cw1155-base/schema/batch_balance_response.json b/contracts/cw1155-base/schema/batch_balance_response.json deleted file mode 100644 index 39c8bd040..000000000 --- a/contracts/cw1155-base/schema/batch_balance_response.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "BatchBalanceResponse", - "type": "object", - "required": [ - "balances" - ], - "properties": { - "balances": { - "type": "array", - "items": { - "$ref": "#/definitions/Uint128" - } - } - }, - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/cw1155-base/schema/cw1155_query_msg.json b/contracts/cw1155-base/schema/cw1155_query_msg.json deleted file mode 100644 index 8bc2fe9c4..000000000 --- a/contracts/cw1155-base/schema/cw1155_query_msg.json +++ /dev/null @@ -1,280 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Cw1155QueryMsg", - "oneOf": [ - { - "description": "Query Minter. Return type: MinterResponse.", - "type": "object", - "required": [ - "minter" - ], - "properties": { - "minter": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "description": "Returns the current balance of the given address, 0 if unset. Return type: BalanceResponse.", - "type": "object", - "required": [ - "balance" - ], - "properties": { - "balance": { - "type": "object", - "required": [ - "owner", - "token_id" - ], - "properties": { - "owner": { - "type": "string" - }, - "token_id": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Returns all current balances of the given token id. Supports pagination Return type: AllBalancesResponse.", - "type": "object", - "required": [ - "all_balances" - ], - "properties": { - "all_balances": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "string", - "null" - ] - }, - "token_id": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Returns the current balance of the given address for a batch of tokens, 0 if unset. Return type: BatchBalanceResponse.", - "type": "object", - "required": [ - "batch_balance" - ], - "properties": { - "batch_balance": { - "type": "object", - "required": [ - "owner", - "token_ids" - ], - "properties": { - "owner": { - "type": "string" - }, - "token_ids": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Total number of tokens issued for the token id", - "type": "object", - "required": [ - "num_tokens" - ], - "properties": { - "num_tokens": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "token_id": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "List all operators that can access all of the owner's tokens. Return type: ApprovedForAllResponse.", - "type": "object", - "required": [ - "approved_for_all" - ], - "properties": { - "approved_for_all": { - "type": "object", - "required": [ - "owner" - ], - "properties": { - "include_expired": { - "description": "unset or false will filter out expired approvals, you must set to true to see them", - "type": [ - "boolean", - "null" - ] - }, - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "owner": { - "type": "string" - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Query approved status `owner` granted to `operator`. Return type: IsApprovedForAllResponse", - "type": "object", - "required": [ - "is_approved_for_all" - ], - "properties": { - "is_approved_for_all": { - "type": "object", - "required": [ - "operator", - "owner" - ], - "properties": { - "operator": { - "type": "string" - }, - "owner": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "With MetaData Extension. Query metadata of token Return type: TokenInfoResponse.", - "type": "object", - "required": [ - "token_info" - ], - "properties": { - "token_info": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "token_id": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "With Enumerable extension. Returns all tokens owned by the given address, [] if unset. Return type: TokensResponse.", - "type": "object", - "required": [ - "tokens" - ], - "properties": { - "tokens": { - "type": "object", - "required": [ - "owner" - ], - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "owner": { - "type": "string" - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "With Enumerable extension. Requires pagination. Lists all token_ids controlled by the contract. Return type: TokensResponse.", - "type": "object", - "required": [ - "all_tokens" - ], - "properties": { - "all_tokens": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - } - } - }, - "additionalProperties": false - } - ] -} diff --git a/contracts/cw1155-base/schema/execute_msg.json b/contracts/cw1155-base/schema/execute_msg.json deleted file mode 100644 index 871b83025..000000000 --- a/contracts/cw1155-base/schema/execute_msg.json +++ /dev/null @@ -1,360 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "description": "This is like Cw1155ExecuteMsg but we add a Mint command for a minter to make this stand-alone. You will likely want to remove mint and use other control logic in any contract that inherits this.", - "oneOf": [ - { - "description": "SendFrom is a base message to move tokens, if `env.sender` is the owner or has sufficient pre-approval.", - "type": "object", - "required": [ - "send_from" - ], - "properties": { - "send_from": { - "type": "object", - "required": [ - "from", - "to", - "token_id", - "value" - ], - "properties": { - "from": { - "type": "string" - }, - "msg": { - "description": "`None` means don't call the receiver interface", - "anyOf": [ - { - "$ref": "#/definitions/Binary" - }, - { - "type": "null" - } - ] - }, - "to": { - "description": "If `to` is not contract, `msg` should be `None`", - "type": "string" - }, - "token_id": { - "type": "string" - }, - "value": { - "$ref": "#/definitions/Uint128" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "BatchSendFrom is a base message to move multiple types of tokens in batch, if `env.sender` is the owner or has sufficient pre-approval.", - "type": "object", - "required": [ - "batch_send_from" - ], - "properties": { - "batch_send_from": { - "type": "object", - "required": [ - "batch", - "from", - "to" - ], - "properties": { - "batch": { - "type": "array", - "items": { - "type": "array", - "items": [ - { - "type": "string" - }, - { - "$ref": "#/definitions/Uint128" - } - ], - "maxItems": 2, - "minItems": 2 - } - }, - "from": { - "type": "string" - }, - "msg": { - "description": "`None` means don't call the receiver interface", - "anyOf": [ - { - "$ref": "#/definitions/Binary" - }, - { - "type": "null" - } - ] - }, - "to": { - "description": "if `to` is not contract, `msg` should be `None`", - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Burn is a base message to burn tokens.", - "type": "object", - "required": [ - "burn" - ], - "properties": { - "burn": { - "type": "object", - "required": [ - "from", - "token_id", - "value" - ], - "properties": { - "from": { - "type": "string" - }, - "token_id": { - "type": "string" - }, - "value": { - "$ref": "#/definitions/Uint128" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "BatchBurn is a base message to burn multiple types of tokens in batch.", - "type": "object", - "required": [ - "batch_burn" - ], - "properties": { - "batch_burn": { - "type": "object", - "required": [ - "batch", - "from" - ], - "properties": { - "batch": { - "type": "array", - "items": { - "type": "array", - "items": [ - { - "type": "string" - }, - { - "$ref": "#/definitions/Uint128" - } - ], - "maxItems": 2, - "minItems": 2 - } - }, - "from": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Allows operator to transfer / send any token from the owner's account. If expiration is set, then this allowance has a time/height limit", - "type": "object", - "required": [ - "approve_all" - ], - "properties": { - "approve_all": { - "type": "object", - "required": [ - "operator" - ], - "properties": { - "expires": { - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "operator": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Remove previously granted ApproveAll permission", - "type": "object", - "required": [ - "revoke_all" - ], - "properties": { - "revoke_all": { - "type": "object", - "required": [ - "operator" - ], - "properties": { - "operator": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Mint a new NFT, can only be called by the contract minter", - "type": "object", - "required": [ - "mint" - ], - "properties": { - "mint": { - "$ref": "#/definitions/MintMsg_for_Nullable_Empty" - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - }, - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object" - } - }, - "additionalProperties": false - } - ] - }, - "MintMsg_for_Nullable_Empty": { - "type": "object", - "required": [ - "to", - "token_id", - "value" - ], - "properties": { - "extension": { - "description": "Any custom extension used by this contract", - "anyOf": [ - { - "anyOf": [ - { - "$ref": "#/definitions/Empty" - }, - { - "type": "null" - } - ] - }, - { - "type": "null" - } - ] - }, - "to": { - "description": "The owner of the newly minted tokens", - "type": "string" - }, - "token_id": { - "type": "string" - }, - "token_uri": { - "description": "Only first mint can set `token_uri` and `extension` Metadata JSON Schema", - "type": [ - "string", - "null" - ] - }, - "value": { - "description": "The amount of the newly minted tokens", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - } - } - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/cw1155-base/schema/instantiate_msg.json b/contracts/cw1155-base/schema/instantiate_msg.json deleted file mode 100644 index 3f5eaf0ce..000000000 --- a/contracts/cw1155-base/schema/instantiate_msg.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "minter" - ], - "properties": { - "minter": { - "description": "The minter is the only one who can create new tokens. This is designed for a base token platform that is controlled by an external program or contract.", - "type": "string" - } - } -} diff --git a/contracts/cw1155-base/schema/is_approved_for_all_response.json b/contracts/cw1155-base/schema/is_approved_for_all_response.json deleted file mode 100644 index e3af7a983..000000000 --- a/contracts/cw1155-base/schema/is_approved_for_all_response.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "IsApprovedForAllResponse", - "type": "object", - "required": [ - "approved" - ], - "properties": { - "approved": { - "type": "boolean" - } - } -} diff --git a/contracts/cw1155-base/schema/minter_response.json b/contracts/cw1155-base/schema/minter_response.json deleted file mode 100644 index a20e0d767..000000000 --- a/contracts/cw1155-base/schema/minter_response.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MinterResponse", - "description": "Shows who can mint these tokens", - "type": "object", - "required": [ - "minter" - ], - "properties": { - "minter": { - "type": "string" - } - } -} diff --git a/contracts/cw1155-base/schema/num_tokens_response.json b/contracts/cw1155-base/schema/num_tokens_response.json deleted file mode 100644 index b19bc807e..000000000 --- a/contracts/cw1155-base/schema/num_tokens_response.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "NumTokensResponse", - "type": "object", - "required": [ - "count" - ], - "properties": { - "count": { - "$ref": "#/definitions/Uint128" - } - }, - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/cw1155-base/schema/token_info_response.json b/contracts/cw1155-base/schema/token_info_response.json deleted file mode 100644 index 3426e872d..000000000 --- a/contracts/cw1155-base/schema/token_info_response.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "TokenInfoResponse", - "type": "object", - "properties": { - "extension": { - "description": "You can add any custom metadata here when you extend cw1155-base", - "anyOf": [ - { - "anyOf": [ - { - "$ref": "#/definitions/Empty" - }, - { - "type": "null" - } - ] - }, - { - "type": "null" - } - ] - }, - "token_uri": { - "description": "Should be a url point to a json file", - "type": [ - "string", - "null" - ] - } - }, - "definitions": { - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - } - } -} diff --git a/contracts/cw1155-base/schema/tokens_response.json b/contracts/cw1155-base/schema/tokens_response.json deleted file mode 100644 index b8e3d75b5..000000000 --- a/contracts/cw1155-base/schema/tokens_response.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "TokensResponse", - "type": "object", - "required": [ - "tokens" - ], - "properties": { - "tokens": { - "description": "Contains all token_ids in lexicographical ordering If there are more than `limit`, use `start_from` in future queries to achieve pagination.", - "type": "array", - "items": { - "type": "string" - } - } - } -} diff --git a/contracts/cw1155-metadata-onchain/examples/schema.rs b/contracts/cw1155-metadata-onchain/examples/schema.rs index 5e2644a56..6f3de8b7a 100644 --- a/contracts/cw1155-metadata-onchain/examples/schema.rs +++ b/contracts/cw1155-metadata-onchain/examples/schema.rs @@ -1,34 +1,15 @@ -use std::env::current_dir; use std::fs::create_dir_all; -use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for}; +use cosmwasm_schema::{remove_schemas, write_api}; +use cosmwasm_std::Empty; -use cw1155::{ - AllBalancesResponse, ApprovalsForResponse, BalanceResponse, BatchBalanceResponse, - Cw1155QueryMsg, MinterResponse, NumTokensResponse, TokenInfoResponse, TokensResponse, -}; - -use cw1155_metadata_onchain::{ExecuteMsg, Extension, InstantiateMsg}; +use cw1155::{Cw1155ExecuteMsg, Cw1155InstantiateMsg, Cw1155QueryMsg}; +use cw1155_metadata_onchain::Extension; fn main() { - let mut out_dir = current_dir().unwrap(); - out_dir.push("schema"); - create_dir_all(&out_dir).unwrap(); - remove_schemas(&out_dir).unwrap(); - - export_schema(&schema_for!(InstantiateMsg), &out_dir); - export_schema_with_title(&schema_for!(ExecuteMsg), &out_dir, "ExecuteMsg"); - export_schema(&schema_for!(Cw1155QueryMsg), &out_dir); - export_schema(&schema_for!(BalanceResponse), &out_dir); - export_schema(&schema_for!(AllBalancesResponse), &out_dir); - export_schema(&schema_for!(BatchBalanceResponse), &out_dir); - export_schema(&schema_for!(NumTokensResponse), &out_dir); - export_schema(&schema_for!(ApprovalsForResponse), &out_dir); - export_schema(&schema_for!(TokensResponse), &out_dir); - export_schema(&schema_for!(MinterResponse), &out_dir); - export_schema_with_title( - &schema_for!(TokenInfoResponse), - &out_dir, - "TokenInfoResponse", - ); + write_api! { + instantiate: Cw1155InstantiateMsg, + execute: Cw1155ExecuteMsg, + query: Cw1155QueryMsg, + } } diff --git a/contracts/cw1155-metadata-onchain/schema/all_balances_response.json b/contracts/cw1155-metadata-onchain/schema/all_balances_response.json deleted file mode 100644 index 06cc4f9d3..000000000 --- a/contracts/cw1155-metadata-onchain/schema/all_balances_response.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AllBalancesResponse", - "type": "object", - "required": [ - "balances" - ], - "properties": { - "balances": { - "type": "array", - "items": { - "$ref": "#/definitions/Balance" - } - } - }, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Balance": { - "type": "object", - "required": [ - "amount", - "owner", - "token_id" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "owner": { - "$ref": "#/definitions/Addr" - }, - "token_id": { - "type": "string" - } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/cw1155-metadata-onchain/schema/approved_for_all_response.json b/contracts/cw1155-metadata-onchain/schema/approved_for_all_response.json deleted file mode 100644 index 453f17b7c..000000000 --- a/contracts/cw1155-metadata-onchain/schema/approved_for_all_response.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ApprovedForAllResponse", - "type": "object", - "required": [ - "operators" - ], - "properties": { - "operators": { - "type": "array", - "items": { - "$ref": "#/definitions/Approval" - } - } - }, - "definitions": { - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - } - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object" - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/cw1155-metadata-onchain/schema/balance_response.json b/contracts/cw1155-metadata-onchain/schema/balance_response.json deleted file mode 100644 index 4e1a0be2b..000000000 --- a/contracts/cw1155-metadata-onchain/schema/balance_response.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "BalanceResponse", - "type": "object", - "required": [ - "balance" - ], - "properties": { - "balance": { - "$ref": "#/definitions/Uint128" - } - }, - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/cw1155-metadata-onchain/schema/batch_balance_response.json b/contracts/cw1155-metadata-onchain/schema/batch_balance_response.json deleted file mode 100644 index 39c8bd040..000000000 --- a/contracts/cw1155-metadata-onchain/schema/batch_balance_response.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "BatchBalanceResponse", - "type": "object", - "required": [ - "balances" - ], - "properties": { - "balances": { - "type": "array", - "items": { - "$ref": "#/definitions/Uint128" - } - } - }, - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/cw1155-metadata-onchain/schema/cw1155_query_msg.json b/contracts/cw1155-metadata-onchain/schema/cw1155_query_msg.json deleted file mode 100644 index 8bc2fe9c4..000000000 --- a/contracts/cw1155-metadata-onchain/schema/cw1155_query_msg.json +++ /dev/null @@ -1,280 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Cw1155QueryMsg", - "oneOf": [ - { - "description": "Query Minter. Return type: MinterResponse.", - "type": "object", - "required": [ - "minter" - ], - "properties": { - "minter": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "description": "Returns the current balance of the given address, 0 if unset. Return type: BalanceResponse.", - "type": "object", - "required": [ - "balance" - ], - "properties": { - "balance": { - "type": "object", - "required": [ - "owner", - "token_id" - ], - "properties": { - "owner": { - "type": "string" - }, - "token_id": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Returns all current balances of the given token id. Supports pagination Return type: AllBalancesResponse.", - "type": "object", - "required": [ - "all_balances" - ], - "properties": { - "all_balances": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "string", - "null" - ] - }, - "token_id": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Returns the current balance of the given address for a batch of tokens, 0 if unset. Return type: BatchBalanceResponse.", - "type": "object", - "required": [ - "batch_balance" - ], - "properties": { - "batch_balance": { - "type": "object", - "required": [ - "owner", - "token_ids" - ], - "properties": { - "owner": { - "type": "string" - }, - "token_ids": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Total number of tokens issued for the token id", - "type": "object", - "required": [ - "num_tokens" - ], - "properties": { - "num_tokens": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "token_id": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "List all operators that can access all of the owner's tokens. Return type: ApprovedForAllResponse.", - "type": "object", - "required": [ - "approved_for_all" - ], - "properties": { - "approved_for_all": { - "type": "object", - "required": [ - "owner" - ], - "properties": { - "include_expired": { - "description": "unset or false will filter out expired approvals, you must set to true to see them", - "type": [ - "boolean", - "null" - ] - }, - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "owner": { - "type": "string" - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Query approved status `owner` granted to `operator`. Return type: IsApprovedForAllResponse", - "type": "object", - "required": [ - "is_approved_for_all" - ], - "properties": { - "is_approved_for_all": { - "type": "object", - "required": [ - "operator", - "owner" - ], - "properties": { - "operator": { - "type": "string" - }, - "owner": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "With MetaData Extension. Query metadata of token Return type: TokenInfoResponse.", - "type": "object", - "required": [ - "token_info" - ], - "properties": { - "token_info": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "token_id": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "With Enumerable extension. Returns all tokens owned by the given address, [] if unset. Return type: TokensResponse.", - "type": "object", - "required": [ - "tokens" - ], - "properties": { - "tokens": { - "type": "object", - "required": [ - "owner" - ], - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "owner": { - "type": "string" - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "With Enumerable extension. Requires pagination. Lists all token_ids controlled by the contract. Return type: TokensResponse.", - "type": "object", - "required": [ - "all_tokens" - ], - "properties": { - "all_tokens": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - } - } - }, - "additionalProperties": false - } - ] -} diff --git a/contracts/cw1155-metadata-onchain/schema/execute_msg.json b/contracts/cw1155-metadata-onchain/schema/execute_msg.json deleted file mode 100644 index d2d3f6cd7..000000000 --- a/contracts/cw1155-metadata-onchain/schema/execute_msg.json +++ /dev/null @@ -1,432 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "description": "This is like Cw1155ExecuteMsg but we add a Mint command for a minter to make this stand-alone. You will likely want to remove mint and use other control logic in any contract that inherits this.", - "oneOf": [ - { - "description": "SendFrom is a base message to move tokens, if `env.sender` is the owner or has sufficient pre-approval.", - "type": "object", - "required": [ - "send_from" - ], - "properties": { - "send_from": { - "type": "object", - "required": [ - "from", - "to", - "token_id", - "value" - ], - "properties": { - "from": { - "type": "string" - }, - "msg": { - "description": "`None` means don't call the receiver interface", - "anyOf": [ - { - "$ref": "#/definitions/Binary" - }, - { - "type": "null" - } - ] - }, - "to": { - "description": "If `to` is not contract, `msg` should be `None`", - "type": "string" - }, - "token_id": { - "type": "string" - }, - "value": { - "$ref": "#/definitions/Uint128" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "BatchSendFrom is a base message to move multiple types of tokens in batch, if `env.sender` is the owner or has sufficient pre-approval.", - "type": "object", - "required": [ - "batch_send_from" - ], - "properties": { - "batch_send_from": { - "type": "object", - "required": [ - "batch", - "from", - "to" - ], - "properties": { - "batch": { - "type": "array", - "items": { - "type": "array", - "items": [ - { - "type": "string" - }, - { - "$ref": "#/definitions/Uint128" - } - ], - "maxItems": 2, - "minItems": 2 - } - }, - "from": { - "type": "string" - }, - "msg": { - "description": "`None` means don't call the receiver interface", - "anyOf": [ - { - "$ref": "#/definitions/Binary" - }, - { - "type": "null" - } - ] - }, - "to": { - "description": "if `to` is not contract, `msg` should be `None`", - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Burn is a base message to burn tokens.", - "type": "object", - "required": [ - "burn" - ], - "properties": { - "burn": { - "type": "object", - "required": [ - "from", - "token_id", - "value" - ], - "properties": { - "from": { - "type": "string" - }, - "token_id": { - "type": "string" - }, - "value": { - "$ref": "#/definitions/Uint128" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "BatchBurn is a base message to burn multiple types of tokens in batch.", - "type": "object", - "required": [ - "batch_burn" - ], - "properties": { - "batch_burn": { - "type": "object", - "required": [ - "batch", - "from" - ], - "properties": { - "batch": { - "type": "array", - "items": { - "type": "array", - "items": [ - { - "type": "string" - }, - { - "$ref": "#/definitions/Uint128" - } - ], - "maxItems": 2, - "minItems": 2 - } - }, - "from": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Allows operator to transfer / send any token from the owner's account. If expiration is set, then this allowance has a time/height limit", - "type": "object", - "required": [ - "approve_all" - ], - "properties": { - "approve_all": { - "type": "object", - "required": [ - "operator" - ], - "properties": { - "expires": { - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "operator": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Remove previously granted ApproveAll permission", - "type": "object", - "required": [ - "revoke_all" - ], - "properties": { - "revoke_all": { - "type": "object", - "required": [ - "operator" - ], - "properties": { - "operator": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Mint a new NFT, can only be called by the contract minter", - "type": "object", - "required": [ - "mint" - ], - "properties": { - "mint": { - "$ref": "#/definitions/MintMsg_for_Metadata" - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object" - } - }, - "additionalProperties": false - } - ] - }, - "Metadata": { - "type": "object", - "properties": { - "animation_url": { - "type": [ - "string", - "null" - ] - }, - "attributes": { - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Trait" - } - }, - "background_color": { - "type": [ - "string", - "null" - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "external_url": { - "type": [ - "string", - "null" - ] - }, - "image": { - "type": [ - "string", - "null" - ] - }, - "image_data": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": [ - "string", - "null" - ] - }, - "youtube_url": { - "type": [ - "string", - "null" - ] - } - } - }, - "MintMsg_for_Metadata": { - "type": "object", - "required": [ - "to", - "token_id", - "value" - ], - "properties": { - "extension": { - "description": "Any custom extension used by this contract", - "anyOf": [ - { - "$ref": "#/definitions/Metadata" - }, - { - "type": "null" - } - ] - }, - "to": { - "description": "The owner of the newly minted tokens", - "type": "string" - }, - "token_id": { - "type": "string" - }, - "token_uri": { - "description": "Only first mint can set `token_uri` and `extension` Metadata JSON Schema", - "type": [ - "string", - "null" - ] - }, - "value": { - "description": "The amount of the newly minted tokens", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - } - } - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Trait": { - "type": "object", - "required": [ - "trait_type", - "value" - ], - "properties": { - "display_type": { - "type": [ - "string", - "null" - ] - }, - "trait_type": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/cw1155-metadata-onchain/schema/instantiate_msg.json b/contracts/cw1155-metadata-onchain/schema/instantiate_msg.json deleted file mode 100644 index 3f5eaf0ce..000000000 --- a/contracts/cw1155-metadata-onchain/schema/instantiate_msg.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "minter" - ], - "properties": { - "minter": { - "description": "The minter is the only one who can create new tokens. This is designed for a base token platform that is controlled by an external program or contract.", - "type": "string" - } - } -} diff --git a/contracts/cw1155-metadata-onchain/schema/is_approved_for_all_response.json b/contracts/cw1155-metadata-onchain/schema/is_approved_for_all_response.json deleted file mode 100644 index e3af7a983..000000000 --- a/contracts/cw1155-metadata-onchain/schema/is_approved_for_all_response.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "IsApprovedForAllResponse", - "type": "object", - "required": [ - "approved" - ], - "properties": { - "approved": { - "type": "boolean" - } - } -} diff --git a/contracts/cw1155-metadata-onchain/schema/minter_response.json b/contracts/cw1155-metadata-onchain/schema/minter_response.json deleted file mode 100644 index a20e0d767..000000000 --- a/contracts/cw1155-metadata-onchain/schema/minter_response.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MinterResponse", - "description": "Shows who can mint these tokens", - "type": "object", - "required": [ - "minter" - ], - "properties": { - "minter": { - "type": "string" - } - } -} diff --git a/contracts/cw1155-metadata-onchain/schema/num_tokens_response.json b/contracts/cw1155-metadata-onchain/schema/num_tokens_response.json deleted file mode 100644 index b19bc807e..000000000 --- a/contracts/cw1155-metadata-onchain/schema/num_tokens_response.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "NumTokensResponse", - "type": "object", - "required": [ - "count" - ], - "properties": { - "count": { - "$ref": "#/definitions/Uint128" - } - }, - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/cw1155-metadata-onchain/schema/token_info_response.json b/contracts/cw1155-metadata-onchain/schema/token_info_response.json deleted file mode 100644 index a305674e8..000000000 --- a/contracts/cw1155-metadata-onchain/schema/token_info_response.json +++ /dev/null @@ -1,110 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "TokenInfoResponse", - "type": "object", - "properties": { - "extension": { - "description": "You can add any custom metadata here when you extend cw1155-base", - "anyOf": [ - { - "$ref": "#/definitions/Metadata" - }, - { - "type": "null" - } - ] - }, - "token_uri": { - "description": "Should be a url point to a json file", - "type": [ - "string", - "null" - ] - } - }, - "definitions": { - "Metadata": { - "type": "object", - "properties": { - "animation_url": { - "type": [ - "string", - "null" - ] - }, - "attributes": { - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Trait" - } - }, - "background_color": { - "type": [ - "string", - "null" - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "external_url": { - "type": [ - "string", - "null" - ] - }, - "image": { - "type": [ - "string", - "null" - ] - }, - "image_data": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": [ - "string", - "null" - ] - }, - "youtube_url": { - "type": [ - "string", - "null" - ] - } - } - }, - "Trait": { - "type": "object", - "required": [ - "trait_type", - "value" - ], - "properties": { - "display_type": { - "type": [ - "string", - "null" - ] - }, - "trait_type": { - "type": "string" - }, - "value": { - "type": "string" - } - } - } - } -} diff --git a/contracts/cw1155-metadata-onchain/schema/tokens_response.json b/contracts/cw1155-metadata-onchain/schema/tokens_response.json deleted file mode 100644 index b8e3d75b5..000000000 --- a/contracts/cw1155-metadata-onchain/schema/tokens_response.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "TokensResponse", - "type": "object", - "required": [ - "tokens" - ], - "properties": { - "tokens": { - "description": "Contains all token_ids in lexicographical ordering If there are more than `limit`, use `start_from` in future queries to achieve pagination.", - "type": "array", - "items": { - "type": "string" - } - } - } -} diff --git a/packages/cw1155/examples/schema.rs b/packages/cw1155/examples/schema.rs index da6deb476..cb7ee2d5c 100644 --- a/packages/cw1155/examples/schema.rs +++ b/packages/cw1155/examples/schema.rs @@ -3,14 +3,14 @@ use std::fs::create_dir_all; use cosmwasm_schema::{remove_schemas, write_api}; use cosmwasm_std::Empty; -use cw1155::{Cw1155ExecuteMsg, Cw1155QueryMsg}; +use cw1155::{Cw1155ExecuteMsg, Cw1155InstantiateMsg, Cw1155QueryMsg}; type Extension = Empty; fn main() { write_api! { - instantiate: InstantiateMsg, - execute: Cw1155ExecuteMsg, - query: Cw1155QueryMsg, + instantiate: Cw1155InstantiateMsg, + execute: Cw1155ExecuteMsg, + query: Cw1155QueryMsg, } } diff --git a/packages/cw1155/schema/all_balances_response.json b/packages/cw1155/schema/all_balances_response.json deleted file mode 100644 index 06cc4f9d3..000000000 --- a/packages/cw1155/schema/all_balances_response.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AllBalancesResponse", - "type": "object", - "required": [ - "balances" - ], - "properties": { - "balances": { - "type": "array", - "items": { - "$ref": "#/definitions/Balance" - } - } - }, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Balance": { - "type": "object", - "required": [ - "amount", - "owner", - "token_id" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "owner": { - "$ref": "#/definitions/Addr" - }, - "token_id": { - "type": "string" - } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/packages/cw1155/schema/approved_for_all_response.json b/packages/cw1155/schema/approved_for_all_response.json deleted file mode 100644 index 453f17b7c..000000000 --- a/packages/cw1155/schema/approved_for_all_response.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ApprovedForAllResponse", - "type": "object", - "required": [ - "operators" - ], - "properties": { - "operators": { - "type": "array", - "items": { - "$ref": "#/definitions/Approval" - } - } - }, - "definitions": { - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - } - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object" - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } -} diff --git a/packages/cw1155/schema/balance_response.json b/packages/cw1155/schema/balance_response.json deleted file mode 100644 index 4e1a0be2b..000000000 --- a/packages/cw1155/schema/balance_response.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "BalanceResponse", - "type": "object", - "required": [ - "balance" - ], - "properties": { - "balance": { - "$ref": "#/definitions/Uint128" - } - }, - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/packages/cw1155/schema/batch_balance_response.json b/packages/cw1155/schema/batch_balance_response.json deleted file mode 100644 index 39c8bd040..000000000 --- a/packages/cw1155/schema/batch_balance_response.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "BatchBalanceResponse", - "type": "object", - "required": [ - "balances" - ], - "properties": { - "balances": { - "type": "array", - "items": { - "$ref": "#/definitions/Uint128" - } - } - }, - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/packages/cw1155/schema/cw1155_query_msg.json b/packages/cw1155/schema/cw1155_query_msg.json deleted file mode 100644 index 8bc2fe9c4..000000000 --- a/packages/cw1155/schema/cw1155_query_msg.json +++ /dev/null @@ -1,280 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Cw1155QueryMsg", - "oneOf": [ - { - "description": "Query Minter. Return type: MinterResponse.", - "type": "object", - "required": [ - "minter" - ], - "properties": { - "minter": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "description": "Returns the current balance of the given address, 0 if unset. Return type: BalanceResponse.", - "type": "object", - "required": [ - "balance" - ], - "properties": { - "balance": { - "type": "object", - "required": [ - "owner", - "token_id" - ], - "properties": { - "owner": { - "type": "string" - }, - "token_id": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Returns all current balances of the given token id. Supports pagination Return type: AllBalancesResponse.", - "type": "object", - "required": [ - "all_balances" - ], - "properties": { - "all_balances": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "string", - "null" - ] - }, - "token_id": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Returns the current balance of the given address for a batch of tokens, 0 if unset. Return type: BatchBalanceResponse.", - "type": "object", - "required": [ - "batch_balance" - ], - "properties": { - "batch_balance": { - "type": "object", - "required": [ - "owner", - "token_ids" - ], - "properties": { - "owner": { - "type": "string" - }, - "token_ids": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Total number of tokens issued for the token id", - "type": "object", - "required": [ - "num_tokens" - ], - "properties": { - "num_tokens": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "token_id": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "List all operators that can access all of the owner's tokens. Return type: ApprovedForAllResponse.", - "type": "object", - "required": [ - "approved_for_all" - ], - "properties": { - "approved_for_all": { - "type": "object", - "required": [ - "owner" - ], - "properties": { - "include_expired": { - "description": "unset or false will filter out expired approvals, you must set to true to see them", - "type": [ - "boolean", - "null" - ] - }, - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "owner": { - "type": "string" - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Query approved status `owner` granted to `operator`. Return type: IsApprovedForAllResponse", - "type": "object", - "required": [ - "is_approved_for_all" - ], - "properties": { - "is_approved_for_all": { - "type": "object", - "required": [ - "operator", - "owner" - ], - "properties": { - "operator": { - "type": "string" - }, - "owner": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "With MetaData Extension. Query metadata of token Return type: TokenInfoResponse.", - "type": "object", - "required": [ - "token_info" - ], - "properties": { - "token_info": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "token_id": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "With Enumerable extension. Returns all tokens owned by the given address, [] if unset. Return type: TokensResponse.", - "type": "object", - "required": [ - "tokens" - ], - "properties": { - "tokens": { - "type": "object", - "required": [ - "owner" - ], - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "owner": { - "type": "string" - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - } - } - }, - "additionalProperties": false - }, - { - "description": "With Enumerable extension. Requires pagination. Lists all token_ids controlled by the contract. Return type: TokensResponse.", - "type": "object", - "required": [ - "all_tokens" - ], - "properties": { - "all_tokens": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - } - } - }, - "additionalProperties": false - } - ] -} diff --git a/packages/cw1155/schema/cw1155_receive_msg.json b/packages/cw1155/schema/cw1155_receive_msg.json deleted file mode 100644 index 1bf693cec..000000000 --- a/packages/cw1155/schema/cw1155_receive_msg.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Cw1155ReceiveMsg", - "description": "Cw1155ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", - "type": "object", - "required": [ - "amount", - "msg", - "operator", - "token_id" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "from": { - "description": "The account that the token transfered from", - "type": [ - "string", - "null" - ] - }, - "msg": { - "$ref": "#/definitions/Binary" - }, - "operator": { - "description": "The account that executed the send message", - "type": "string" - }, - "token_id": { - "type": "string" - } - }, - "definitions": { - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/packages/cw1155/schema/execute_msg.json b/packages/cw1155/schema/execute_msg.json deleted file mode 100644 index 30f43d62f..000000000 --- a/packages/cw1155/schema/execute_msg.json +++ /dev/null @@ -1,292 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "oneOf": [ - { - "description": "SendFrom is a base message to move tokens, if `env.sender` is the owner or has sufficient pre-approval.", - "type": "object", - "required": [ - "send_from" - ], - "properties": { - "send_from": { - "type": "object", - "required": [ - "from", - "to", - "token_id", - "value" - ], - "properties": { - "from": { - "type": "string" - }, - "msg": { - "description": "`None` means don't call the receiver interface", - "anyOf": [ - { - "$ref": "#/definitions/Binary" - }, - { - "type": "null" - } - ] - }, - "to": { - "description": "If `to` is not contract, `msg` should be `None`", - "type": "string" - }, - "token_id": { - "type": "string" - }, - "value": { - "$ref": "#/definitions/Uint128" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "BatchSendFrom is a base message to move multiple types of tokens in batch, if `env.sender` is the owner or has sufficient pre-approval.", - "type": "object", - "required": [ - "batch_send_from" - ], - "properties": { - "batch_send_from": { - "type": "object", - "required": [ - "batch", - "from", - "to" - ], - "properties": { - "batch": { - "type": "array", - "items": { - "type": "array", - "items": [ - { - "type": "string" - }, - { - "$ref": "#/definitions/Uint128" - } - ], - "maxItems": 2, - "minItems": 2 - } - }, - "from": { - "type": "string" - }, - "msg": { - "description": "`None` means don't call the receiver interface", - "anyOf": [ - { - "$ref": "#/definitions/Binary" - }, - { - "type": "null" - } - ] - }, - "to": { - "description": "if `to` is not contract, `msg` should be `None`", - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Burn is a base message to burn tokens.", - "type": "object", - "required": [ - "burn" - ], - "properties": { - "burn": { - "type": "object", - "required": [ - "from", - "token_id", - "value" - ], - "properties": { - "from": { - "type": "string" - }, - "token_id": { - "type": "string" - }, - "value": { - "$ref": "#/definitions/Uint128" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "BatchBurn is a base message to burn multiple types of tokens in batch.", - "type": "object", - "required": [ - "batch_burn" - ], - "properties": { - "batch_burn": { - "type": "object", - "required": [ - "batch", - "from" - ], - "properties": { - "batch": { - "type": "array", - "items": { - "type": "array", - "items": [ - { - "type": "string" - }, - { - "$ref": "#/definitions/Uint128" - } - ], - "maxItems": 2, - "minItems": 2 - } - }, - "from": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Allows operator to transfer / send any token from the owner's account. If expiration is set, then this allowance has a time/height limit", - "type": "object", - "required": [ - "approve_all" - ], - "properties": { - "approve_all": { - "type": "object", - "required": [ - "operator" - ], - "properties": { - "expires": { - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "operator": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Remove previously granted ApproveAll permission", - "type": "object", - "required": [ - "revoke_all" - ], - "properties": { - "revoke_all": { - "type": "object", - "required": [ - "operator" - ], - "properties": { - "operator": { - "type": "string" - } - } - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", - "type": "string" - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object" - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } -} diff --git a/packages/cw1155/schema/is_approved_for_all_response.json b/packages/cw1155/schema/is_approved_for_all_response.json deleted file mode 100644 index e3af7a983..000000000 --- a/packages/cw1155/schema/is_approved_for_all_response.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "IsApprovedForAllResponse", - "type": "object", - "required": [ - "approved" - ], - "properties": { - "approved": { - "type": "boolean" - } - } -} diff --git a/packages/cw1155/schema/minter_response.json b/packages/cw1155/schema/minter_response.json deleted file mode 100644 index a20e0d767..000000000 --- a/packages/cw1155/schema/minter_response.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MinterResponse", - "description": "Shows who can mint these tokens", - "type": "object", - "required": [ - "minter" - ], - "properties": { - "minter": { - "type": "string" - } - } -} diff --git a/packages/cw1155/schema/num_tokens_response.json b/packages/cw1155/schema/num_tokens_response.json deleted file mode 100644 index b19bc807e..000000000 --- a/packages/cw1155/schema/num_tokens_response.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "NumTokensResponse", - "type": "object", - "required": [ - "count" - ], - "properties": { - "count": { - "$ref": "#/definitions/Uint128" - } - }, - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/packages/cw1155/schema/token_info_response.json b/packages/cw1155/schema/token_info_response.json deleted file mode 100644 index a55515102..000000000 --- a/packages/cw1155/schema/token_info_response.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "TokenInfoResponse", - "type": "object", - "properties": { - "extension": { - "description": "You can add any custom metadata here when you extend cw1155-base", - "anyOf": [ - { - "$ref": "#/definitions/Empty" - }, - { - "type": "null" - } - ] - }, - "token_uri": { - "description": "Should be a url point to a json file", - "type": [ - "string", - "null" - ] - } - }, - "definitions": { - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - } - } -} diff --git a/packages/cw1155/schema/tokens_response.json b/packages/cw1155/schema/tokens_response.json deleted file mode 100644 index b8e3d75b5..000000000 --- a/packages/cw1155/schema/tokens_response.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "TokensResponse", - "type": "object", - "required": [ - "tokens" - ], - "properties": { - "tokens": { - "description": "Contains all token_ids in lexicographical ordering If there are more than `limit`, use `start_from` in future queries to achieve pagination.", - "type": "array", - "items": { - "type": "string" - } - } - } -} From e107869d5142a6918bc92a5ba41a4fb6cdf44e64 Mon Sep 17 00:00:00 2001 From: shab Date: Fri, 3 May 2024 15:38:42 -0400 Subject: [PATCH 16/57] feat: implement mint_batch --- contracts/cw1155-base/src/execute.rs | 61 +++++++++++++++++++++++++--- packages/cw1155/src/msg.rs | 9 ++-- 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/contracts/cw1155-base/src/execute.rs b/contracts/cw1155-base/src/execute.rs index d118a3658..8f2cf1ee6 100644 --- a/contracts/cw1155-base/src/execute.rs +++ b/contracts/cw1155-base/src/execute.rs @@ -50,8 +50,8 @@ where batch, msg, } => self.send_batch(env, from, to, batch, msg), - Cw1155ExecuteMsg::MintBatch(_) => { - todo!() + Cw1155ExecuteMsg::MintBatch { recipient, msgs } => { + self.mint_batch(env, recipient, msgs) } Cw1155ExecuteMsg::BurnBatch { from, batch } => self.burn_batch(env, from, batch), Cw1155ExecuteMsg::ApproveAll { operator, expires } => { @@ -67,7 +67,7 @@ where amount, msg, } => self.send(env, from, to, token_id, amount, msg), - Cw1155ExecuteMsg::Mint(msg) => self.mint(env, msg), + Cw1155ExecuteMsg::Mint { recipient, msg } => self.mint(env, recipient, msg), Cw1155ExecuteMsg::Burn { from, token_id, @@ -101,18 +101,24 @@ where T: Serialize + DeserializeOwned + Clone, Q: CustomMsg, { - pub fn mint(&self, env: ExecuteEnv, msg: MintMsg) -> Result { + pub fn mint( + &self, + env: ExecuteEnv, + recipient: String, + msg: MintMsg, + ) -> Result { let ExecuteEnv { mut deps, info, env, } = env; - let to = deps.api.addr_validate(&msg.to)?; if info.sender != self.minter.load(deps.storage)? { return Err(Cw1155ContractError::Unauthorized {}); } + let to = deps.api.addr_validate(&recipient)?; + let mut rsp = Response::default(); let event = self.update_balances( @@ -127,7 +133,7 @@ where )?; rsp = rsp.add_event(event); - // insert if not exist (if it is the first mint) + // store token info if not exist (if it is the first mint) if !self.tokens.has(deps.storage, &msg.token_id) { let token_info = TokenInfo { token_uri: msg.token_uri, @@ -139,6 +145,49 @@ where Ok(rsp) } + pub fn mint_batch( + &self, + env: ExecuteEnv, + recipient: String, + msgs: Vec>, + ) -> Result { + let ExecuteEnv { + mut deps, + info, + env, + } = env; + + if info.sender != self.minter.load(deps.storage)? { + return Err(Cw1155ContractError::Unauthorized {}); + } + + let to = deps.api.addr_validate(&recipient)?; + + let batch = msgs + .iter() + .map(|msg| { + // store token info if not exist (if it is the first mint) + if !self.tokens.has(deps.storage, &msg.token_id) { + let token_info = TokenInfo { + token_uri: msg.token_uri.clone(), + extension: msg.extension.clone(), + }; + self.tokens.save(deps.storage, &msg.token_id, &token_info)?; + } + Ok(TokenAmount { + token_id: msg.token_id.to_string(), + amount: msg.amount, + }) + }) + .collect::>>()?; + + let mut rsp = Response::default(); + let event = self.update_balances(&mut deps, &env, None, Some(to), batch)?; + rsp = rsp.add_event(event); + + Ok(rsp) + } + pub fn send( &self, env: ExecuteEnv, diff --git a/packages/cw1155/src/msg.rs b/packages/cw1155/src/msg.rs index 876543ad1..5d71f0279 100644 --- a/packages/cw1155/src/msg.rs +++ b/packages/cw1155/src/msg.rs @@ -33,7 +33,10 @@ pub enum Cw1155ExecuteMsg { msg: Option, }, /// Mint a batch of tokens, can only be called by the contract minter - MintBatch(Vec>), + MintBatch { + recipient: String, + msgs: Vec>, + }, /// BatchBurn is a base message to burn multiple types of tokens in batch. BurnBatch { /// check approval if from is Some, otherwise assume sender is owner @@ -63,7 +66,7 @@ pub enum Cw1155ExecuteMsg { msg: Option, }, /// Mint a new NFT, can only be called by the contract minter - Mint(MintMsg), + Mint { recipient: String, msg: MintMsg }, /// Burn is a base message to burn tokens. Burn { /// check approval if from is Some, otherwise assume sender is owner @@ -92,8 +95,6 @@ pub enum Cw1155ExecuteMsg { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct MintMsg { pub token_id: String, - /// The owner of the newly minted tokens - pub to: String, /// The amount of the newly minted tokens pub amount: Uint128, From ed56d0a53ad3390552f4a7e05422a4b184ab0da9 Mon Sep 17 00:00:00 2001 From: shab Date: Fri, 3 May 2024 19:57:03 -0400 Subject: [PATCH 17/57] feat: implements contract_info, minter, and ownership queries. feat: uses cw_ownable for minter storage and auth. --- Cargo.lock | 4 ++++ contracts/cw1155-base/Cargo.toml | 3 +++ contracts/cw1155-base/src/execute.rs | 24 ++++++++++++++---------- contracts/cw1155-base/src/query.rs | 22 ++++++++++++++-------- contracts/cw1155-base/src/state.rs | 17 +++++++++++------ contracts/cw721-base/src/lib.rs | 2 +- contracts/cw721-base/src/msg.rs | 8 +------- contracts/cw721-base/src/query.rs | 6 +++--- packages/cw1155/Cargo.toml | 1 + packages/cw1155/src/error.rs | 6 +++++- packages/cw1155/src/lib.rs | 4 ++-- packages/cw1155/src/msg.rs | 8 +++++++- packages/cw1155/src/query.rs | 10 +++------- packages/cw721/src/lib.rs | 4 ++-- packages/cw721/src/query.rs | 6 ++++++ 15 files changed, 77 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9a42ecc6c..47ea5f83e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -317,6 +317,7 @@ version = "0.13.4" dependencies = [ "cosmwasm-schema", "cosmwasm-std", + "cw-ownable", "cw-utils 1.0.3", "cw2 1.1.2", "cw721 0.18.0", @@ -331,10 +332,13 @@ version = "0.13.4" dependencies = [ "cosmwasm-schema", "cosmwasm-std", + "cw-ownable", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw1155", "cw2 1.1.2", + "cw721 0.18.0", + "cw721-base 0.18.0", "schemars", "serde", ] diff --git a/contracts/cw1155-base/Cargo.toml b/contracts/cw1155-base/Cargo.toml index 2630146cf..895a939a9 100644 --- a/contracts/cw1155-base/Cargo.toml +++ b/contracts/cw1155-base/Cargo.toml @@ -28,6 +28,9 @@ library = [] cw-utils = { workspace = true } cw2 = { workspace = true } cw1155 = { workspace = true } +cw721 = { workspace = true } +cw721-base = { workspace = true } +cw-ownable = { workspace = true } cw-storage-plus = { workspace = true } cosmwasm-std = { workspace = true } schemars = { workspace = true } diff --git a/contracts/cw1155-base/src/execute.rs b/contracts/cw1155-base/src/execute.rs index 8f2cf1ee6..a67a0cd36 100644 --- a/contracts/cw1155-base/src/execute.rs +++ b/contracts/cw1155-base/src/execute.rs @@ -22,15 +22,23 @@ where &self, deps: DepsMut, _env: Env, - _info: MessageInfo, + info: MessageInfo, msg: Cw1155InstantiateMsg, ) -> StdResult { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - // todo - cw ownership + let contract_info = cw721::ContractInfoResponse { + name: msg.name, + symbol: msg.symbol, + }; + self.contract_info.save(deps.storage, &contract_info)?; + + let owner = match msg.minter { + Some(owner) => deps.api.addr_validate(&owner)?, + None => info.sender, + }; + cw_ownable::initialize_owner(deps.storage, deps.api, Some(owner.as_ref()))?; - let minter = deps.api.addr_validate(&msg.minter)?; - self.minter.save(deps.storage, &minter)?; Ok(Response::default()) } @@ -113,9 +121,7 @@ where env, } = env; - if info.sender != self.minter.load(deps.storage)? { - return Err(Cw1155ContractError::Unauthorized {}); - } + cw_ownable::assert_owner(deps.storage, &info.sender)?; let to = deps.api.addr_validate(&recipient)?; @@ -157,9 +163,7 @@ where env, } = env; - if info.sender != self.minter.load(deps.storage)? { - return Err(Cw1155ContractError::Unauthorized {}); - } + cw_ownable::assert_owner(deps.storage, &info.sender)?; let to = deps.api.addr_validate(&recipient)?; diff --git a/contracts/cw1155-base/src/query.rs b/contracts/cw1155-base/src/query.rs index 3377ee77a..367fb2169 100644 --- a/contracts/cw1155-base/src/query.rs +++ b/contracts/cw1155-base/src/query.rs @@ -1,13 +1,16 @@ use serde::de::DeserializeOwned; use serde::Serialize; -use cosmwasm_std::{to_json_binary, Addr, Binary, CustomMsg, Deps, Env, Order, StdResult, Uint128}; +use cosmwasm_std::{ + to_json_binary, Addr, Binary, CustomMsg, Deps, Empty, Env, Order, StdResult, Uint128, +}; use cw1155::{ AllBalancesResponse, Approval, ApprovedForAllResponse, Balance, BalanceResponse, - BatchBalanceResponse, Cw1155QueryMsg, Expiration, IsApprovedForAllResponse, MinterResponse, - NumTokensResponse, TokenInfoResponse, TokensResponse, + BatchBalanceResponse, Cw1155QueryMsg, Expiration, IsApprovedForAllResponse, NumTokensResponse, + TokenInfoResponse, TokensResponse, }; +use cw721_base::{Cw721Contract, Extension}; use cw_storage_plus::Bound; use cw_utils::maybe_addr; @@ -24,8 +27,8 @@ where pub fn query(&self, deps: Deps, env: Env, msg: Cw1155QueryMsg) -> StdResult { match msg { Cw1155QueryMsg::Minter {} => { - let minter = self.minter.load(deps.storage)?.to_string(); - to_json_binary(&MinterResponse { minter }) + let tract = Cw721Contract::::default(); + to_json_binary(&tract.minter(deps)?) } Cw1155QueryMsg::BalanceOf { owner, token_id } => { let owner_addr = deps.api.addr_validate(&owner)?; @@ -132,8 +135,7 @@ where to_json_binary(&self.query_all_tokens(deps, start_after, limit)?) } Cw1155QueryMsg::ContractInfo {} => { - todo!() - // to_json_binary(&self.contract_info(deps)?) + to_json_binary(&self.contract_info.load(deps.storage)?) } Cw1155QueryMsg::Supply { .. } => { todo!() @@ -141,8 +143,12 @@ where Cw1155QueryMsg::AllTokens { .. } => { todo!() } + Cw1155QueryMsg::Ownership {} => { + to_json_binary(&cw_ownable::get_ownership(deps.storage)?) + } + Cw1155QueryMsg::Extension { .. } => { - todo!() + unimplemented!() } } } diff --git a/contracts/cw1155-base/src/state.rs b/contracts/cw1155-base/src/state.rs index d85d3a528..cc16b34c5 100644 --- a/contracts/cw1155-base/src/state.rs +++ b/contracts/cw1155-base/src/state.rs @@ -6,6 +6,7 @@ use std::marker::PhantomData; use cosmwasm_std::{Addr, CustomMsg, StdResult, Storage, Uint128}; use cw1155::{Balance, Expiration, TokenApproval}; +use cw721::ContractInfoResponse; use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, MultiIndex}; pub struct Cw1155Contract<'a, T, Q> @@ -13,9 +14,10 @@ where T: Serialize + DeserializeOwned + Clone, Q: CustomMsg, { - pub minter: Item<'a, Addr>, + pub contract_info: Item<'a, ContractInfoResponse>, + pub supply: Item<'a, Uint128>, // total supply of all tokens // key: token id - pub token_count: Map<'a, &'a str, Uint128>, + pub token_count: Map<'a, &'a str, Uint128>, // total supply of a specific token // key: (owner, token id) pub balances: IndexedMap<'a, (Addr, String), Balance, BalanceIndexes<'a>>, // key: (owner, spender) @@ -35,9 +37,10 @@ where { fn default() -> Self { Self::new( - "minter", + "cw1155_contract_info", "tokens", "token_count", + "supply", "balances", "balances__token_id", "approves", @@ -52,9 +55,10 @@ where Q: CustomMsg, { fn new( - minter_key: &'a str, + contract_info_key: &'a str, tokens_key: &'a str, token_count_key: &'a str, + supply_key: &'a str, balances_key: &'a str, balances_token_id_key: &'a str, approves_key: &'a str, @@ -68,12 +72,13 @@ where ), }; Self { - minter: Item::new(minter_key), + contract_info: Item::new(contract_info_key), + tokens: Map::new(tokens_key), token_count: Map::new(token_count_key), + supply: Item::new(supply_key), balances: IndexedMap::new(balances_key, balances_indexes), approves: Map::new(approves_key), token_approves: Map::new(token_approves_key), - tokens: Map::new(tokens_key), _custom_query: PhantomData, } } diff --git a/contracts/cw721-base/src/lib.rs b/contracts/cw721-base/src/lib.rs index b7b567ed8..40f88a8a0 100644 --- a/contracts/cw721-base/src/lib.rs +++ b/contracts/cw721-base/src/lib.rs @@ -12,7 +12,7 @@ mod contract_tests; mod multi_tests; pub use crate::error::ContractError; -pub use crate::msg::{ExecuteMsg, InstantiateMsg, MinterResponse, QueryMsg}; +pub use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; pub use crate::state::Cw721Contract; // These types are re-exported so that contracts interacting with this diff --git a/contracts/cw721-base/src/msg.rs b/contracts/cw721-base/src/msg.rs index 6af543714..0ef443376 100644 --- a/contracts/cw721-base/src/msg.rs +++ b/contracts/cw721-base/src/msg.rs @@ -161,7 +161,7 @@ pub enum QueryMsg { }, /// Return the minter - #[returns(MinterResponse)] + #[returns(cw721::MinterResponse)] Minter {}, /// Extension query @@ -171,9 +171,3 @@ pub enum QueryMsg { #[returns(Option)] GetWithdrawAddress {}, } - -/// Shows who can mint these tokens -#[cw_serde] -pub struct MinterResponse { - pub minter: Option, -} diff --git a/contracts/cw721-base/src/query.rs b/contracts/cw721-base/src/query.rs index 52db6a7ca..8ebc2f4b8 100644 --- a/contracts/cw721-base/src/query.rs +++ b/contracts/cw721-base/src/query.rs @@ -7,13 +7,13 @@ use cosmwasm_std::{ use cw721::{ AllNftInfoResponse, ApprovalResponse, ApprovalsResponse, ContractInfoResponse, Cw721Query, - Expiration, NftInfoResponse, NumTokensResponse, OperatorResponse, OperatorsResponse, - OwnerOfResponse, TokensResponse, + Expiration, MinterResponse, NftInfoResponse, NumTokensResponse, OperatorResponse, + OperatorsResponse, OwnerOfResponse, TokensResponse, }; use cw_storage_plus::Bound; use cw_utils::maybe_addr; -use crate::msg::{MinterResponse, QueryMsg}; +use crate::msg::QueryMsg; use crate::state::{Approval, Cw721Contract, TokenInfo}; const DEFAULT_LIMIT: u32 = 10; diff --git a/packages/cw1155/Cargo.toml b/packages/cw1155/Cargo.toml index 21d0a4579..1d849707b 100644 --- a/packages/cw1155/Cargo.toml +++ b/packages/cw1155/Cargo.toml @@ -13,6 +13,7 @@ documentation = "https://docs.cosmwasm.com" cw2 = { workspace = true } cw721 = { workspace = true } cw-utils = { workspace = true } +cw-ownable = { workspace = true } cosmwasm-std = { workspace = true } schemars = { workspace = true } serde = { workspace = true } diff --git a/packages/cw1155/src/error.rs b/packages/cw1155/src/error.rs index db80659b9..c81ca95f9 100644 --- a/packages/cw1155/src/error.rs +++ b/packages/cw1155/src/error.rs @@ -1,5 +1,6 @@ use cosmwasm_std::{OverflowError, StdError}; use cw2::VersionError; +use cw_ownable::OwnershipError; use thiserror::Error; #[derive(Error, Debug)] @@ -10,9 +11,12 @@ pub enum Cw1155ContractError { #[error("OverflowError: {0}")] Overflow(#[from] OverflowError), - #[error("Version: {0}")] + #[error("VersionError: {0}")] Version(#[from] VersionError), + #[error("OwnershipError: {0}")] + Ownership(#[from] OwnershipError), + #[error("Unauthorized")] Unauthorized {}, diff --git a/packages/cw1155/src/lib.rs b/packages/cw1155/src/lib.rs index 9b6803e9e..ab5efd51b 100644 --- a/packages/cw1155/src/lib.rs +++ b/packages/cw1155/src/lib.rs @@ -11,8 +11,8 @@ pub use crate::receiver::{Cw1155BatchReceiveMsg, Cw1155ReceiveMsg}; pub use crate::msg::{Cw1155ExecuteMsg, Cw1155InstantiateMsg, MintMsg, TokenAmount, TokenApproval}; pub use crate::query::{ AllBalancesResponse, Approval, ApprovedForAllResponse, Balance, BalanceResponse, - BatchBalanceResponse, Cw1155QueryMsg, IsApprovedForAllResponse, MinterResponse, - NumTokensResponse, TokenInfoResponse, TokensResponse, + BatchBalanceResponse, Cw1155QueryMsg, IsApprovedForAllResponse, NumTokensResponse, + TokenInfoResponse, TokensResponse, }; pub use crate::error::Cw1155ContractError; diff --git a/packages/cw1155/src/msg.rs b/packages/cw1155/src/msg.rs index 5d71f0279..972c8a520 100644 --- a/packages/cw1155/src/msg.rs +++ b/packages/cw1155/src/msg.rs @@ -8,10 +8,16 @@ use cw_utils::Expiration; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct Cw1155InstantiateMsg { + /// Name of the token contract + pub name: String, + /// Symbol of the token contract + pub symbol: String, + /// The minter is the only one who can create new tokens. /// This is designed for a base token platform that is controlled by an external program or /// contract. - pub minter: String, + /// If None, sender is the minter. + pub minter: Option, } /// This is like Cw1155ExecuteMsg but we add a Mint command for a minter diff --git a/packages/cw1155/src/query.rs b/packages/cw1155/src/query.rs index dbe7d18f0..6112d18ae 100644 --- a/packages/cw1155/src/query.rs +++ b/packages/cw1155/src/query.rs @@ -3,8 +3,10 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use cosmwasm_std::{Addr, Uint128}; +use cw_ownable::cw_ownable_query; use cw_utils::Expiration; +#[cw_ownable_query] #[cw_serde] #[derive(QueryResponses)] pub enum Cw1155QueryMsg { @@ -57,7 +59,7 @@ pub enum Cw1155QueryMsg { #[returns(cw721::ContractInfoResponse)] ContractInfo {}, /// Query Minter. - #[returns(MinterResponse)] + #[returns(cw721::MinterResponse)] Minter {}, /// With MetaData Extension. /// Query metadata of token @@ -151,9 +153,3 @@ pub struct TokensResponse { /// to achieve pagination. pub tokens: Vec, } - -/// Shows who can mint these tokens -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] -pub struct MinterResponse { - pub minter: String, -} diff --git a/packages/cw721/src/lib.rs b/packages/cw721/src/lib.rs index 312189b64..0784c6272 100644 --- a/packages/cw721/src/lib.rs +++ b/packages/cw721/src/lib.rs @@ -8,8 +8,8 @@ pub use cw_utils::Expiration; pub use crate::msg::Cw721ExecuteMsg; pub use crate::query::{ AllNftInfoResponse, Approval, ApprovalResponse, ApprovalsResponse, ContractInfoResponse, - Cw721QueryMsg, NftInfoResponse, NumTokensResponse, OperatorResponse, OperatorsResponse, - OwnerOfResponse, TokensResponse, + Cw721QueryMsg, MinterResponse, NftInfoResponse, NumTokensResponse, OperatorResponse, + OperatorsResponse, OwnerOfResponse, TokensResponse, }; pub use crate::receiver::Cw721ReceiveMsg; pub use crate::traits::{Cw721, Cw721Execute, Cw721Query}; diff --git a/packages/cw721/src/query.rs b/packages/cw721/src/query.rs index f336c7530..0c921217d 100644 --- a/packages/cw721/src/query.rs +++ b/packages/cw721/src/query.rs @@ -147,3 +147,9 @@ pub struct TokensResponse { /// to achieve pagination. pub tokens: Vec, } + +/// Shows who can mint these tokens +#[cw_serde] +pub struct MinterResponse { + pub minter: Option, +} From d8e7f7630c7354e43e91ce2a7701c83b323f175c Mon Sep 17 00:00:00 2001 From: shab Date: Fri, 3 May 2024 20:07:20 -0400 Subject: [PATCH 18/57] feat: query all_token_infos --- contracts/cw1155-base/src/query.rs | 50 ++++++++++++++++++++++++------ packages/cw1155/src/lib.rs | 6 ++-- packages/cw1155/src/query.rs | 8 ++++- 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/contracts/cw1155-base/src/query.rs b/contracts/cw1155-base/src/query.rs index 367fb2169..3f05ac0af 100644 --- a/contracts/cw1155-base/src/query.rs +++ b/contracts/cw1155-base/src/query.rs @@ -6,15 +6,15 @@ use cosmwasm_std::{ }; use cw1155::{ - AllBalancesResponse, Approval, ApprovedForAllResponse, Balance, BalanceResponse, - BatchBalanceResponse, Cw1155QueryMsg, Expiration, IsApprovedForAllResponse, NumTokensResponse, - TokenInfoResponse, TokensResponse, + AllBalancesResponse, AllTokenInfoResponse, Approval, ApprovedForAllResponse, Balance, + BalanceResponse, BatchBalanceResponse, Cw1155QueryMsg, Expiration, IsApprovedForAllResponse, + NumTokensResponse, TokenInfoResponse, TokensResponse, }; use cw721_base::{Cw721Contract, Extension}; use cw_storage_plus::Bound; use cw_utils::maybe_addr; -use crate::state::Cw1155Contract; +use crate::state::{Cw1155Contract, TokenInfo}; const DEFAULT_LIMIT: u32 = 10; const MAX_LIMIT: u32 = 100; @@ -129,10 +129,10 @@ where limit, } => { let owner_addr = deps.api.addr_validate(&owner)?; - to_json_binary(&self.query_tokens(deps, owner_addr, start_after, limit)?) + to_json_binary(&self.query_owner_tokens(deps, owner_addr, start_after, limit)?) } Cw1155QueryMsg::AllTokenInfo { start_after, limit } => { - to_json_binary(&self.query_all_tokens(deps, start_after, limit)?) + to_json_binary(&self.query_all_token_infos(deps, start_after, limit)?) } Cw1155QueryMsg::ContractInfo {} => { to_json_binary(&self.contract_info.load(deps.storage)?) @@ -140,8 +140,8 @@ where Cw1155QueryMsg::Supply { .. } => { todo!() } - Cw1155QueryMsg::AllTokens { .. } => { - todo!() + Cw1155QueryMsg::AllTokens { start_after, limit } => { + to_json_binary(&self.query_all_tokens(deps, start_after, limit)?) } Cw1155QueryMsg::Ownership {} => { to_json_binary(&cw_ownable::get_ownership(deps.storage)?) @@ -184,7 +184,7 @@ where Ok(ApprovedForAllResponse { operators }) } - fn query_tokens( + fn query_owner_tokens( &self, deps: Deps, owner: Addr, @@ -219,6 +219,38 @@ where Ok(TokensResponse { tokens }) } + fn query_all_token_infos( + &self, + deps: Deps, + start_after: Option, + limit: Option, + ) -> StdResult>> { + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + let start = start_after.as_ref().map(|s| Bound::exclusive(s.as_str())); + let tokens = self + .tokens + .range(deps.storage, start, None, Order::Ascending) + .take(limit) + .map(|item| { + let ( + token_id, + TokenInfo { + token_uri, + extension, + }, + ) = item.unwrap(); + AllTokenInfoResponse { + token_id, + info: TokenInfoResponse { + token_uri, + extension, + }, + } + }) + .collect::>(); + Ok(tokens) + } + fn query_all_balances( &self, deps: Deps, diff --git a/packages/cw1155/src/lib.rs b/packages/cw1155/src/lib.rs index ab5efd51b..9fe3f1eb0 100644 --- a/packages/cw1155/src/lib.rs +++ b/packages/cw1155/src/lib.rs @@ -10,9 +10,9 @@ pub use crate::receiver::{Cw1155BatchReceiveMsg, Cw1155ReceiveMsg}; pub use crate::msg::{Cw1155ExecuteMsg, Cw1155InstantiateMsg, MintMsg, TokenAmount, TokenApproval}; pub use crate::query::{ - AllBalancesResponse, Approval, ApprovedForAllResponse, Balance, BalanceResponse, - BatchBalanceResponse, Cw1155QueryMsg, IsApprovedForAllResponse, NumTokensResponse, - TokenInfoResponse, TokensResponse, + AllBalancesResponse, AllTokenInfoResponse, Approval, ApprovedForAllResponse, Balance, + BalanceResponse, BatchBalanceResponse, Cw1155QueryMsg, IsApprovedForAllResponse, + NumTokensResponse, TokenInfoResponse, TokensResponse, }; pub use crate::error::Cw1155ContractError; diff --git a/packages/cw1155/src/query.rs b/packages/cw1155/src/query.rs index 6112d18ae..78f203daf 100644 --- a/packages/cw1155/src/query.rs +++ b/packages/cw1155/src/query.rs @@ -67,7 +67,7 @@ pub enum Cw1155QueryMsg { TokenInfo { token_id: String }, /// With Enumerable extension. /// Requires pagination. Lists all token_ids controlled by the contract. - #[returns(TokensResponse)] + #[returns(TokenInfoResponse)] AllTokenInfo { start_after: Option, limit: Option, @@ -138,6 +138,12 @@ pub struct IsApprovedForAllResponse { pub approved: bool, } +#[cw_serde] +pub struct AllTokenInfoResponse { + pub token_id: String, + pub info: TokenInfoResponse, +} + #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] pub struct TokenInfoResponse { /// Should be a url point to a json file From 0f4967492ee76d6b2157d10ffa3887692a71cce4 Mon Sep 17 00:00:00 2001 From: shab Date: Fri, 3 May 2024 20:15:45 -0400 Subject: [PATCH 19/57] feat: store and query total supply (sum of all token balances) --- contracts/cw1155-base/src/query.rs | 5 +++-- contracts/cw1155-base/src/state.rs | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/contracts/cw1155-base/src/query.rs b/contracts/cw1155-base/src/query.rs index 3f05ac0af..77c0bdbcf 100644 --- a/contracts/cw1155-base/src/query.rs +++ b/contracts/cw1155-base/src/query.rs @@ -137,8 +137,9 @@ where Cw1155QueryMsg::ContractInfo {} => { to_json_binary(&self.contract_info.load(deps.storage)?) } - Cw1155QueryMsg::Supply { .. } => { - todo!() + Cw1155QueryMsg::Supply {} => { + let supply = self.supply.load(deps.storage)?; + to_json_binary(&NumTokensResponse { count: supply }) } Cw1155QueryMsg::AllTokens { start_after, limit } => { to_json_binary(&self.query_all_tokens(deps, start_after, limit)?) diff --git a/contracts/cw1155-base/src/state.rs b/contracts/cw1155-base/src/state.rs index cc16b34c5..e388ca7d1 100644 --- a/contracts/cw1155-base/src/state.rs +++ b/contracts/cw1155-base/src/state.rs @@ -3,7 +3,7 @@ use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use std::marker::PhantomData; -use cosmwasm_std::{Addr, CustomMsg, StdResult, Storage, Uint128}; +use cosmwasm_std::{Addr, CustomMsg, StdError, StdResult, Storage, Uint128}; use cw1155::{Balance, Expiration, TokenApproval}; use cw721::ContractInfoResponse; @@ -96,8 +96,15 @@ where token_id: &'a str, amount: &Uint128, ) -> StdResult { + // increment token count let val = self.token_count(storage, token_id)? + amount; self.token_count.save(storage, token_id, &val)?; + + // increment total supply + self.supply.update(storage, |prev| { + Ok::(prev.checked_add(*amount)?) + })?; + Ok(val) } @@ -107,8 +114,15 @@ where token_id: &'a str, amount: &Uint128, ) -> StdResult { + // decrement token count let val = self.token_count(storage, token_id)?.checked_sub(*amount)?; self.token_count.save(storage, token_id, &val)?; + + // decrement total supply + self.supply.update(storage, |prev| { + Ok::(prev.checked_sub(*amount)?) + })?; + Ok(val) } } From e42a81101a69db355d48ef88f7d5999402279d87 Mon Sep 17 00:00:00 2001 From: shab Date: Mon, 6 May 2024 11:15:59 -0400 Subject: [PATCH 20/57] fix: use library feature in cw721-base import --- contracts/cw1155-base/Cargo.toml | 2 +- contracts/cw1155-base/src/contract_tests.rs | 2 +- contracts/cw1155-base/src/lib.rs | 10 ++++++---- contracts/cw1155-metadata-onchain/src/lib.rs | 6 +++--- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/contracts/cw1155-base/Cargo.toml b/contracts/cw1155-base/Cargo.toml index 895a939a9..d64e5a22e 100644 --- a/contracts/cw1155-base/Cargo.toml +++ b/contracts/cw1155-base/Cargo.toml @@ -29,7 +29,7 @@ cw-utils = { workspace = true } cw2 = { workspace = true } cw1155 = { workspace = true } cw721 = { workspace = true } -cw721-base = { workspace = true } +cw721-base = { workspace = true, features = ["library"] } cw-ownable = { workspace = true } cw-storage-plus = { workspace = true } cosmwasm-std = { workspace = true } diff --git a/contracts/cw1155-base/src/contract_tests.rs b/contracts/cw1155-base/src/contract_tests.rs index 129a79306..19e169af6 100644 --- a/contracts/cw1155-base/src/contract_tests.rs +++ b/contracts/cw1155-base/src/contract_tests.rs @@ -6,7 +6,7 @@ mod tests { Uint128, }; - use crate::{Cw1155Contract, ExecuteMsg, InstantiateMsg, MintMsg}; + use crate::{Cw1155BaseExecuteMsg, Cw1155Contract, InstantiateMsg, MintMsg}; use cw1155::{ AllBalancesResponse, ApprovalsForResponse, Balance, BalanceResponse, BatchBalanceResponse, Cw1155BatchReceiveMsg, Cw1155ContractError, Cw1155QueryMsg, Expiration, NumTokensResponse, diff --git a/contracts/cw1155-base/src/lib.rs b/contracts/cw1155-base/src/lib.rs index f8407d4ff..d63f8fb15 100644 --- a/contracts/cw1155-base/src/lib.rs +++ b/contracts/cw1155-base/src/lib.rs @@ -5,16 +5,18 @@ mod state; pub use crate::state::Cw1155Contract; use cosmwasm_std::Empty; -use cw1155::Cw1155QueryMsg; - -// This is a simple type to let us handle empty extensions -pub type Extension = Option; +use cw1155::{Cw1155ExecuteMsg, Cw1155QueryMsg}; // Version info for migration pub const CONTRACT_NAME: &str = "crates.io:cw1155-base"; pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); pub const EXPECTED_FROM_VERSION: &str = CONTRACT_VERSION; +// This is a simple type to let us handle empty extensions +pub type Extension = Option; +pub type Cw1155BaseExecuteMsg = Cw1155ExecuteMsg; +pub type Cw1155BaseQueryMsg = Cw1155QueryMsg; + pub mod entry { use super::*; diff --git a/contracts/cw1155-metadata-onchain/src/lib.rs b/contracts/cw1155-metadata-onchain/src/lib.rs index 8085ec45a..a2109e5be 100644 --- a/contracts/cw1155-metadata-onchain/src/lib.rs +++ b/contracts/cw1155-metadata-onchain/src/lib.rs @@ -34,7 +34,7 @@ pub struct Metadata { pub type Extension = Metadata; pub type Cw1155MetadataContract<'a> = cw1155_base::Cw1155Contract<'a, Extension, Empty>; -pub type ExecuteMsg = Cw1155ExecuteMsg; +pub type Cw1155MetadataExecuteMsg = Cw1155ExecuteMsg; // #[cfg(not(feature = "library"))] pub mod entry { @@ -64,7 +64,7 @@ pub mod entry { deps: DepsMut, env: Env, info: MessageInfo, - msg: ExecuteMsg, + msg: Cw1155MetadataExecuteMsg, ) -> Result { Cw1155MetadataContract::default().execute(deps, env, info, msg) } @@ -109,7 +109,7 @@ mod tests { ..Metadata::default() }), }; - let exec_msg = ExecuteMsg::Mint(mint_msg.clone()); + let exec_msg = Cw1155MetadataExecuteMsg::Mint(mint_msg.clone()); contract .execute(deps.as_mut(), mock_env(), info, exec_msg) .unwrap(); From b2c060c8c059f0c35fb2b69b200961656ad4db71 Mon Sep 17 00:00:00 2001 From: shab Date: Mon, 6 May 2024 12:39:30 -0400 Subject: [PATCH 21/57] fix: store total supply in base instantiate. fix: prev base tests. --- contracts/cw1155-base/src/contract_tests.rs | 374 ++++++++++++------- contracts/cw1155-base/src/execute.rs | 16 +- contracts/cw1155-base/src/lib.rs | 8 +- contracts/cw1155-metadata-onchain/src/lib.rs | 4 +- packages/cw1155/src/lib.rs | 4 +- packages/cw1155/src/msg.rs | 9 +- 6 files changed, 260 insertions(+), 155 deletions(-) diff --git a/contracts/cw1155-base/src/contract_tests.rs b/contracts/cw1155-base/src/contract_tests.rs index 19e169af6..472253bc4 100644 --- a/contracts/cw1155-base/src/contract_tests.rs +++ b/contracts/cw1155-base/src/contract_tests.rs @@ -5,17 +5,19 @@ mod tests { from_json, to_json_binary, Addr, Binary, Empty, Event, OverflowError, Response, StdError, Uint128, }; + use cw_ownable::OwnershipError; - use crate::{Cw1155BaseExecuteMsg, Cw1155Contract, InstantiateMsg, MintMsg}; + use crate::{Cw1155BaseContract, Cw1155BaseExecuteMsg, Cw1155BaseQueryMsg}; use cw1155::{ - AllBalancesResponse, ApprovalsForResponse, Balance, BalanceResponse, BatchBalanceResponse, - Cw1155BatchReceiveMsg, Cw1155ContractError, Cw1155QueryMsg, Expiration, NumTokensResponse, - TokenAmount, TokenInfoResponse, TokensResponse, + AllBalancesResponse, ApprovedForAllResponse, Balance, BalanceResponse, + BatchBalanceResponse, Cw1155BatchReceiveMsg, Cw1155ContractError, Cw1155InstantiateMsg, + Cw1155MintMsg, Cw1155QueryMsg, Expiration, NumTokensResponse, TokenAmount, + TokenInfoResponse, TokensResponse, }; #[test] fn check_transfers() { - let contract = Cw1155Contract::default(); + let contract = Cw1155BaseContract::default(); // A long test case that try to cover as many cases as possible. // Summary of what it does: // - try mint without permission, fail @@ -41,8 +43,10 @@ mod tests { let user2 = String::from("user2"); let mut deps = mock_dependencies(); - let msg = InstantiateMsg { - minter: minter.clone(), + let msg = Cw1155InstantiateMsg { + name: "name".to_string(), + symbol: "symbol".to_string(), + minter: Some(minter.to_string()), }; let res = contract .instantiate(deps.as_mut(), mock_env(), mock_info("operator", &[]), msg) @@ -50,13 +54,15 @@ mod tests { assert_eq!(0, res.messages.len()); // invalid mint, user1 don't mint permission - let mint_msg = ExecuteMsg::Mint(MintMsg:: { - to: user1.clone(), - token_id: token1.clone(), - amount: 1u64.into(), - token_uri: None, - extension: None, - }); + let mint_msg = Cw1155BaseExecuteMsg::Mint { + recipient: user1.to_string(), + msg: Cw1155MintMsg { + token_id: token1.to_string(), + amount: 1u64.into(), + token_uri: None, + extension: None, + }, + }; assert!(matches!( contract.execute( deps.as_mut(), @@ -64,7 +70,7 @@ mod tests { mock_info(user1.as_ref(), &[]), mint_msg.clone(), ), - Err(Cw1155ContractError::Unauthorized {}) + Err(Cw1155ContractError::Ownership(OwnershipError::NotOwner)) )); // valid mint @@ -77,9 +83,10 @@ mod tests { mint_msg, ) .unwrap(), - Response::new().add_event(Event::new("mint_tokens").add_attributes(vec![ + Response::new().add_event(Event::new("mint_single").add_attributes(vec![ ("recipient", user1.as_str()), - ("tokens", &format!("{}:1", token1)), + ("token_id", token1.as_str()), + ("amount", "1"), ])) ); @@ -91,15 +98,15 @@ mod tests { contract.query( deps.as_ref(), mock_env(), - Cw1155QueryMsg::BalanceOf { + Cw1155BaseQueryMsg::BalanceOf { owner: user1.clone(), token_id: token1.clone(), } ), ); - let transfer_msg = ExecuteMsg::SendFrom { - from: user1.clone(), + let transfer_msg = Cw1155BaseExecuteMsg::Send { + from: Some(user1.to_string()), to: user2.clone(), token_id: token1.clone(), amount: 1u64.into(), @@ -123,7 +130,7 @@ mod tests { deps.as_mut(), mock_env(), mock_info(user1.as_ref(), &[]), - ExecuteMsg::ApproveAll { + Cw1155BaseExecuteMsg::ApproveAll { operator: minter.clone(), expires: None, }, @@ -140,10 +147,11 @@ mod tests { transfer_msg, ) .unwrap(), - Response::new().add_event(Event::new("transfer_tokens").add_attributes(vec![ + Response::new().add_event(Event::new("transfer_single").add_attributes(vec![ ("sender", user1.as_str()), ("recipient", user2.as_str()), - ("tokens", &format!("{}:1", token1)), + ("token_id", token1.as_str()), + ("amount", "1"), ])) ); @@ -181,13 +189,15 @@ mod tests { deps.as_mut(), mock_env(), mock_info(minter.as_ref(), &[]), - ExecuteMsg::Mint(MintMsg:: { - to: user2.clone(), - token_id: token2.clone(), - amount: 1u64.into(), - token_uri: None, - extension: None, - }), + Cw1155BaseExecuteMsg::Mint { + recipient: user2.clone(), + msg: Cw1155MintMsg { + token_id: token2.clone(), + amount: 1u64.into(), + token_uri: None, + extension: None, + }, + }, ) .unwrap(); @@ -196,19 +206,21 @@ mod tests { deps.as_mut(), mock_env(), mock_info(minter.as_ref(), &[]), - ExecuteMsg::Mint(MintMsg:: { - to: user2.clone(), - token_id: token3.clone(), - amount: 1u64.into(), - token_uri: None, - extension: None, - }), + Cw1155BaseExecuteMsg::Mint { + recipient: user2.clone(), + msg: Cw1155MintMsg { + token_id: token3.clone(), + amount: 1u64.into(), + token_uri: None, + extension: None, + }, + }, ) .unwrap(); // invalid batch transfer, (user2 not approved yet) - let batch_transfer_msg = ExecuteMsg::BatchSendFrom { - from: user2.clone(), + let batch_transfer_msg = Cw1155BaseExecuteMsg::SendBatch { + from: Some(user2.clone()), to: user1.clone(), batch: vec![ TokenAmount { @@ -242,7 +254,7 @@ mod tests { deps.as_mut(), mock_env(), mock_info(user2.as_ref(), &[]), - ExecuteMsg::ApproveAll { + Cw1155BaseExecuteMsg::ApproveAll { operator: minter.clone(), expires: None, }, @@ -259,10 +271,11 @@ mod tests { batch_transfer_msg, ) .unwrap(), - Response::new().add_event(Event::new("transfer_tokens").add_attributes(vec![ + Response::new().add_event(Event::new("transfer_batch").add_attributes(vec![ ("sender", user2.as_str()), ("recipient", user1.as_str()), - ("tokens", &format!("{}:1,{}:1,{}:1", token1, token2, token3)), + ("token_ids", &format!("{},{},{}", token1, token2, token3)), + ("amounts", "1,1,1"), ])) ); @@ -287,19 +300,19 @@ mod tests { deps.as_mut(), mock_env(), mock_info(user1.as_ref(), &[]), - ExecuteMsg::RevokeAll { + Cw1155BaseExecuteMsg::RevokeAll { operator: minter.clone(), }, ) .unwrap(); // query approval status - let approvals: ApprovalsForResponse = from_json( + let approvals: ApprovedForAllResponse = from_json( contract .query( deps.as_ref(), mock_env(), - Cw1155QueryMsg::ApprovalsFor { + Cw1155QueryMsg::ApprovalsForAll { owner: user1.clone(), include_expired: None, start_after: None, @@ -309,10 +322,13 @@ mod tests { .unwrap(), ) .unwrap(); - assert!(!approvals - .operators - .iter() - .all(|approval| approval.spender == minter)); + assert!( + approvals.operators.len() == 0 + || !approvals + .operators + .iter() + .all(|approval| approval.spender == minter) + ); // transfer without approval assert!(matches!( @@ -320,8 +336,8 @@ mod tests { deps.as_mut(), mock_env(), mock_info(minter.as_ref(), &[]), - ExecuteMsg::SendFrom { - from: user1.clone(), + Cw1155BaseExecuteMsg::Send { + from: Some(user1.clone()), to: user2, token_id: token1.clone(), amount: 1u64.into(), @@ -338,15 +354,17 @@ mod tests { deps.as_mut(), mock_env(), mock_info(user1.as_ref(), &[]), - ExecuteMsg::Burn { + Cw1155BaseExecuteMsg::Burn { + from: None, token_id: token1.clone(), amount: 1u64.into(), }, ) .unwrap(), - Response::new().add_event(Event::new("burn_tokens").add_attributes(vec![ - ("owner", user1.as_str()), - ("tokens", &format!("{}:1", token1)), + Response::new().add_event(Event::new("burn_single").add_attributes(vec![ + ("sender", user1.as_str()), + ("token_id", &token1), + ("amount", "1"), ])) ); @@ -357,7 +375,8 @@ mod tests { deps.as_mut(), mock_env(), mock_info(user1.as_ref(), &[]), - ExecuteMsg::BatchBurn { + Cw1155BaseExecuteMsg::BurnBatch { + from: None, batch: vec![ TokenAmount { token_id: token2.to_string(), @@ -371,16 +390,17 @@ mod tests { }, ) .unwrap(), - Response::new().add_event(Event::new("burn_tokens").add_attributes(vec![ - ("owner", user1.as_str()), - ("tokens", &format!("{}:1,{}:1", token2, token3)), + Response::new().add_event(Event::new("burn_batch").add_attributes(vec![ + ("sender", user1.as_str()), + ("token_ids", &format!("{},{}", token2, token3)), + ("amounts", "1,1"), ])) ); } #[test] fn check_send_contract() { - let contract = Cw1155Contract::default(); + let contract = Cw1155BaseContract::default(); let receiver = String::from("receive_contract"); let minter = String::from("minter"); let user1 = String::from("user1"); @@ -388,8 +408,10 @@ mod tests { let dummy_msg = Binary::default(); let mut deps = mock_dependencies(); - let msg = InstantiateMsg { - minter: minter.clone(), + let msg = Cw1155InstantiateMsg { + name: "name".to_string(), + symbol: "symbol".to_string(), + minter: Some(minter.to_string()), }; let res = contract .instantiate(deps.as_mut(), mock_env(), mock_info("operator", &[]), msg) @@ -401,13 +423,15 @@ mod tests { deps.as_mut(), mock_env(), mock_info(minter.as_ref(), &[]), - ExecuteMsg::Mint(MintMsg:: { - to: user1.clone(), - token_id: token2.clone(), - amount: 1u64.into(), - token_uri: None, - extension: None, - }), + Cw1155BaseExecuteMsg::Mint { + recipient: user1.clone(), + msg: Cw1155MintMsg { + token_id: token2.clone(), + amount: 1u64.into(), + token_uri: None, + extension: None, + }, + }, ) .unwrap(); @@ -418,8 +442,8 @@ mod tests { deps.as_mut(), mock_env(), mock_info(user1.as_ref(), &[]), - ExecuteMsg::BatchSendFrom { - from: user1.clone(), + Cw1155BaseExecuteMsg::SendBatch { + from: Some(user1.clone()), to: receiver.clone(), batch: vec![TokenAmount { token_id: token2.to_string(), @@ -443,17 +467,18 @@ mod tests { .into_cosmos_msg(receiver.clone()) .unwrap() ) - .add_event(Event::new("transfer_tokens").add_attributes(vec![ + .add_event(Event::new("transfer_single").add_attributes(vec![ ("sender", user1.as_str()), ("recipient", receiver.as_str()), - ("tokens", &format!("{}:1", token2)), + ("token_id", token2.as_str()), + ("amount", "1"), ])) ); } #[test] fn check_queries() { - let contract = Cw1155Contract::default(); + let contract = Cw1155BaseContract::default(); // mint multiple types of tokens, and query them // grant approval to multiple operators, and query them let tokens = (0..10).map(|i| format!("token{}", i)).collect::>(); @@ -461,8 +486,10 @@ mod tests { let minter = String::from("minter"); let mut deps = mock_dependencies(); - let msg = InstantiateMsg { - minter: minter.clone(), + let msg = Cw1155InstantiateMsg { + name: "name".to_string(), + symbol: "symbol".to_string(), + minter: Some(minter.to_string()), }; let res = contract .instantiate(deps.as_mut(), mock_env(), mock_info("operator", &[]), msg) @@ -475,13 +502,15 @@ mod tests { deps.as_mut(), mock_env(), mock_info(minter.as_ref(), &[]), - ExecuteMsg::Mint(MintMsg:: { - to: users[0].clone(), - token_id: token_id.clone(), - amount: 1u64.into(), - token_uri: None, - extension: None, - }), + Cw1155BaseExecuteMsg::Mint { + recipient: users[0].clone(), + msg: Cw1155MintMsg { + token_id: token_id.clone(), + amount: 1u64.into(), + token_uri: None, + extension: None, + }, + }, ) .unwrap(); } @@ -492,13 +521,15 @@ mod tests { deps.as_mut(), mock_env(), mock_info(minter.as_ref(), &[]), - ExecuteMsg::Mint(MintMsg:: { - to: user.clone(), - token_id: tokens[9].clone(), - amount: 1u64.into(), - token_uri: None, - extension: None, - }), + Cw1155BaseExecuteMsg::Mint { + recipient: user.clone(), + msg: Cw1155MintMsg { + token_id: tokens[9].clone(), + amount: 1u64.into(), + token_uri: None, + extension: None, + }, + }, ) .unwrap(); } @@ -611,7 +642,7 @@ mod tests { contract.query( deps.as_ref(), mock_env(), - Cw1155QueryMsg::AllTokenInfo { + Cw1155QueryMsg::AllTokens { start_after: Some("token5".to_owned()), limit: Some(5), }, @@ -641,7 +672,7 @@ mod tests { deps.as_mut(), mock_env(), mock_info(users[0].as_ref(), &[]), - ExecuteMsg::ApproveAll { + Cw1155BaseExecuteMsg::ApproveAll { operator: user.clone(), expires: None, }, @@ -653,14 +684,14 @@ mod tests { contract.query( deps.as_ref(), mock_env(), - Cw1155QueryMsg::ApprovalsFor { + Cw1155QueryMsg::ApprovalsForAll { owner: users[0].clone(), include_expired: None, start_after: Some(String::from("user2")), limit: Some(1), }, ), - to_json_binary(&ApprovalsForResponse { + to_json_binary(&ApprovedForAllResponse { operators: vec![cw1155::Approval { spender: users[3].clone(), expires: Expiration::Never {}, @@ -671,7 +702,7 @@ mod tests { #[test] fn approval_expires() { - let contract = Cw1155Contract::default(); + let contract = Cw1155BaseContract::default(); let mut deps = mock_dependencies(); let token1 = "token1".to_owned(); let minter = String::from("minter"); @@ -684,8 +715,10 @@ mod tests { env }; - let msg = InstantiateMsg { - minter: minter.clone(), + let msg = Cw1155InstantiateMsg { + name: "name".to_string(), + symbol: "symbol".to_string(), + minter: Some(minter.to_string()), }; let res = contract .instantiate(deps.as_mut(), env.clone(), mock_info("operator", &[]), msg) @@ -697,13 +730,15 @@ mod tests { deps.as_mut(), env.clone(), mock_info(minter.as_ref(), &[]), - ExecuteMsg::Mint(MintMsg:: { - to: user1.clone(), - token_id: token1, - amount: 1u64.into(), - token_uri: None, - extension: None, - }), + Cw1155BaseExecuteMsg::Mint { + recipient: user1.clone(), + msg: Cw1155MintMsg { + token_id: token1, + amount: 1u64.into(), + token_uri: None, + extension: None, + }, + }, ) .unwrap(); @@ -713,7 +748,7 @@ mod tests { deps.as_mut(), env.clone(), mock_info(user1.as_ref(), &[]), - ExecuteMsg::ApproveAll { + Cw1155BaseExecuteMsg::ApproveAll { operator: user2.clone(), expires: Some(Expiration::AtHeight(5)), }, @@ -726,19 +761,19 @@ mod tests { deps.as_mut(), env.clone(), mock_info(user1.as_ref(), &[]), - ExecuteMsg::ApproveAll { + Cw1155BaseExecuteMsg::ApproveAll { operator: user2.clone(), expires: Some(Expiration::AtHeight(100)), }, ) .unwrap(); - let approvals: ApprovalsForResponse = from_json( + let approvals: ApprovedForAllResponse = from_json( contract .query( deps.as_ref(), mock_env(), - Cw1155QueryMsg::ApprovalsFor { + Cw1155QueryMsg::ApprovalsForAll { owner: user1.to_string(), include_expired: None, start_after: None, @@ -759,12 +794,12 @@ mod tests { env }; - let approvals: ApprovalsForResponse = from_json( + let approvals: ApprovedForAllResponse = from_json( contract .query( deps.as_ref(), env, - Cw1155QueryMsg::ApprovalsFor { + Cw1155QueryMsg::ApprovalsForAll { owner: user1.to_string(), include_expired: None, start_after: None, @@ -774,40 +809,91 @@ mod tests { .unwrap(), ) .unwrap(); - assert!(!approvals - .operators - .iter() - .all(|approval| approval.spender == user2)); + assert!( + approvals.operators.len() == 0 + || !approvals + .operators + .iter() + .all(|approval| approval.spender == user2) + ); } #[test] fn mint_overflow() { - let contract = Cw1155Contract::default(); + let contract = Cw1155BaseContract::default(); let mut deps = mock_dependencies(); let token1 = "token1".to_owned(); + let token2 = "token2".to_owned(); let minter = String::from("minter"); let user1 = String::from("user1"); let env = mock_env(); - let msg = InstantiateMsg { - minter: minter.clone(), + let msg = Cw1155InstantiateMsg { + name: "name".to_string(), + symbol: "symbol".to_string(), + minter: Some(minter.to_string()), }; let res = contract .instantiate(deps.as_mut(), env.clone(), mock_info("operator", &[]), msg) .unwrap(); assert_eq!(0, res.messages.len()); + // minting up to max u128 should pass let res = contract.execute( deps.as_mut(), env.clone(), mock_info(minter.as_ref(), &[]), - ExecuteMsg::Mint(MintMsg:: { - to: user1.clone(), - token_id: token1.clone(), - amount: u128::MAX.into(), - token_uri: None, - extension: None, - }), + Cw1155BaseExecuteMsg::Mint { + recipient: user1.clone(), + msg: Cw1155MintMsg { + token_id: token1.clone(), + amount: u128::MAX.into(), + token_uri: None, + extension: None, + }, + }, + ); + + assert!(res.is_ok()); + + // minting one more should fail + let res = contract.execute( + deps.as_mut(), + env.clone(), + mock_info(minter.as_ref(), &[]), + Cw1155BaseExecuteMsg::Mint { + recipient: user1.clone(), + msg: Cw1155MintMsg { + token_id: token1.clone(), + amount: 1u128.into(), + token_uri: None, + extension: None, + }, + }, + ); + + assert!(matches!( + res, + Err(Cw1155ContractError::Std(StdError::Overflow { + source: OverflowError { .. }, + .. + })) + )); + + // minting one more of a different token id should fail + let res = contract.execute( + deps.as_mut(), + env.clone(), + mock_info(minter.as_ref(), &[]), + Cw1155BaseExecuteMsg::Mint { + recipient: user1.clone(), + msg: Cw1155MintMsg { + token_id: token2.clone(), + amount: 1u128.into(), + token_uri: None, + extension: None, + }, + }, ); assert!(matches!( @@ -821,7 +907,7 @@ mod tests { #[test] fn token_uri() { - let contract = Cw1155Contract::default(); + let contract = Cw1155BaseContract::default(); let minter = String::from("minter"); let user1 = String::from("user1"); let token1 = "token1".to_owned(); @@ -829,8 +915,10 @@ mod tests { let url2 = "url2".to_owned(); let mut deps = mock_dependencies(); - let msg = InstantiateMsg { - minter: minter.clone(), + let msg = Cw1155InstantiateMsg { + name: "name".to_string(), + symbol: "symbol".to_string(), + minter: Some(minter.to_string()), }; let res = contract .instantiate(deps.as_mut(), mock_env(), mock_info("operator", &[]), msg) @@ -843,13 +931,15 @@ mod tests { deps.as_mut(), mock_env(), mock_info(minter.as_ref(), &[]), - ExecuteMsg::Mint(MintMsg:: { - to: user1.clone(), - token_id: token1.clone(), - amount: 1u64.into(), - token_uri: Some(url1.clone()), - extension: None, - }), + Cw1155BaseExecuteMsg::Mint { + recipient: user1.clone(), + msg: Cw1155MintMsg { + token_id: token1.clone(), + amount: 1u64.into(), + token_uri: Some(url1.clone()), + extension: None, + }, + }, ) .unwrap(); @@ -873,13 +963,15 @@ mod tests { deps.as_mut(), mock_env(), mock_info(minter.as_ref(), &[]), - ExecuteMsg::Mint(MintMsg:: { - to: user1, - token_id: token1.clone(), - amount: 1u64.into(), - token_uri: Some(url2), - extension: None, - }), + Cw1155BaseExecuteMsg::Mint { + recipient: user1, + msg: Cw1155MintMsg { + token_id: token1.clone(), + amount: 1u64.into(), + token_uri: Some(url2), + extension: None, + }, + }, ) .unwrap(); diff --git a/contracts/cw1155-base/src/execute.rs b/contracts/cw1155-base/src/execute.rs index a67a0cd36..4e0e66a83 100644 --- a/contracts/cw1155-base/src/execute.rs +++ b/contracts/cw1155-base/src/execute.rs @@ -6,7 +6,7 @@ use cosmwasm_std::{ }; use cw1155::{ ApproveAllEvent, ApproveEvent, Balance, BurnEvent, Cw1155BatchReceiveMsg, Cw1155ContractError, - Cw1155ExecuteMsg, Cw1155InstantiateMsg, Cw1155ReceiveMsg, Expiration, MintEvent, MintMsg, + Cw1155ExecuteMsg, Cw1155InstantiateMsg, Cw1155MintMsg, Cw1155ReceiveMsg, Expiration, MintEvent, RevokeAllEvent, RevokeEvent, TokenAmount, TokenApproval, TransferEvent, }; use cw2::set_contract_version; @@ -25,20 +25,26 @@ where info: MessageInfo, msg: Cw1155InstantiateMsg, ) -> StdResult { + // store contract version set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + // store contract info let contract_info = cw721::ContractInfoResponse { name: msg.name, symbol: msg.symbol, }; self.contract_info.save(deps.storage, &contract_info)?; + // store minter let owner = match msg.minter { Some(owner) => deps.api.addr_validate(&owner)?, None => info.sender, }; cw_ownable::initialize_owner(deps.storage, deps.api, Some(owner.as_ref()))?; + // store total supply + self.supply.save(deps.storage, &Uint128::zero())?; + Ok(Response::default()) } @@ -113,7 +119,7 @@ where &self, env: ExecuteEnv, recipient: String, - msg: MintMsg, + msg: Cw1155MintMsg, ) -> Result { let ExecuteEnv { mut deps, @@ -155,7 +161,7 @@ where &self, env: ExecuteEnv, recipient: String, - msgs: Vec>, + msgs: Vec>, ) -> Result { let ExecuteEnv { mut deps, @@ -505,7 +511,7 @@ where for TokenAmount { token_id, amount } in tokens.iter() { self.balances.update( deps.storage, - (from.clone(), token_id.clone()), + (from.clone(), token_id.to_string()), |balance: Option| -> StdResult<_> { let mut new_balance = balance.unwrap(); new_balance.amount = new_balance.amount.checked_sub(*amount)?; @@ -519,7 +525,7 @@ where for TokenAmount { token_id, amount } in tokens.iter() { self.balances.update( deps.storage, - (to.clone(), token_id.clone()), + (to.clone(), token_id.to_string()), |balance: Option| -> StdResult<_> { let mut new_balance: Balance = if let Some(balance) = balance { balance diff --git a/contracts/cw1155-base/src/lib.rs b/contracts/cw1155-base/src/lib.rs index d63f8fb15..1b2968b04 100644 --- a/contracts/cw1155-base/src/lib.rs +++ b/contracts/cw1155-base/src/lib.rs @@ -17,6 +17,8 @@ pub type Extension = Option; pub type Cw1155BaseExecuteMsg = Cw1155ExecuteMsg; pub type Cw1155BaseQueryMsg = Cw1155QueryMsg; +pub type Cw1155BaseContract<'a> = Cw1155Contract<'a, Extension, Empty>; + pub mod entry { use super::*; @@ -35,7 +37,7 @@ pub mod entry { ) -> StdResult { cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - let tract = Cw1155Contract::::default(); + let tract = Cw1155BaseContract::default(); tract.instantiate(deps, env, info, msg) } @@ -46,13 +48,13 @@ pub mod entry { info: MessageInfo, msg: Cw1155ExecuteMsg, ) -> Result { - let tract = Cw1155Contract::::default(); + let tract = Cw1155BaseContract::default(); tract.execute(deps, env, info, msg) } #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, env: Env, msg: Cw1155QueryMsg) -> StdResult { - let tract = Cw1155Contract::::default(); + let tract = Cw1155BaseContract::default(); tract.query(deps, env, msg) } diff --git a/contracts/cw1155-metadata-onchain/src/lib.rs b/contracts/cw1155-metadata-onchain/src/lib.rs index a2109e5be..586e725ca 100644 --- a/contracts/cw1155-metadata-onchain/src/lib.rs +++ b/contracts/cw1155-metadata-onchain/src/lib.rs @@ -3,7 +3,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use cw1155::Cw1155ExecuteMsg; -pub use cw1155::{Cw1155ContractError, Cw1155InstantiateMsg, MintMsg}; +pub use cw1155::{Cw1155ContractError, Cw1155InstantiateMsg, Cw1155MintMsg}; use cw2::set_contract_version; // Version info for migration @@ -98,7 +98,7 @@ mod tests { .unwrap(); let token_id = "Enterprise"; - let mint_msg = MintMsg { + let mint_msg = Cw1155MintMsg { token_id: token_id.to_string(), to: "john".to_string(), amount: Uint128::new(1), diff --git a/packages/cw1155/src/lib.rs b/packages/cw1155/src/lib.rs index 9fe3f1eb0..827b2147c 100644 --- a/packages/cw1155/src/lib.rs +++ b/packages/cw1155/src/lib.rs @@ -8,7 +8,9 @@ pub use cw_utils::Expiration; pub use crate::receiver::{Cw1155BatchReceiveMsg, Cw1155ReceiveMsg}; -pub use crate::msg::{Cw1155ExecuteMsg, Cw1155InstantiateMsg, MintMsg, TokenAmount, TokenApproval}; +pub use crate::msg::{ + Cw1155ExecuteMsg, Cw1155InstantiateMsg, Cw1155MintMsg, TokenAmount, TokenApproval, +}; pub use crate::query::{ AllBalancesResponse, AllTokenInfoResponse, Approval, ApprovedForAllResponse, Balance, BalanceResponse, BatchBalanceResponse, Cw1155QueryMsg, IsApprovedForAllResponse, diff --git a/packages/cw1155/src/msg.rs b/packages/cw1155/src/msg.rs index 972c8a520..d74e4119f 100644 --- a/packages/cw1155/src/msg.rs +++ b/packages/cw1155/src/msg.rs @@ -41,7 +41,7 @@ pub enum Cw1155ExecuteMsg { /// Mint a batch of tokens, can only be called by the contract minter MintBatch { recipient: String, - msgs: Vec>, + msgs: Vec>, }, /// BatchBurn is a base message to burn multiple types of tokens in batch. BurnBatch { @@ -72,7 +72,10 @@ pub enum Cw1155ExecuteMsg { msg: Option, }, /// Mint a new NFT, can only be called by the contract minter - Mint { recipient: String, msg: MintMsg }, + Mint { + recipient: String, + msg: Cw1155MintMsg, + }, /// Burn is a base message to burn tokens. Burn { /// check approval if from is Some, otherwise assume sender is owner @@ -99,7 +102,7 @@ pub enum Cw1155ExecuteMsg { } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct MintMsg { +pub struct Cw1155MintMsg { pub token_id: String, /// The amount of the newly minted tokens pub amount: Uint128, From 56e4bc37f54fa4ff528ba66fc61e595ae9226439 Mon Sep 17 00:00:00 2001 From: shab Date: Mon, 6 May 2024 12:43:33 -0400 Subject: [PATCH 22/57] fix: cw1155-metadata tests --- contracts/cw1155-metadata-onchain/src/lib.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/contracts/cw1155-metadata-onchain/src/lib.rs b/contracts/cw1155-metadata-onchain/src/lib.rs index 586e725ca..30e5864ea 100644 --- a/contracts/cw1155-metadata-onchain/src/lib.rs +++ b/contracts/cw1155-metadata-onchain/src/lib.rs @@ -80,7 +80,7 @@ mod tests { use super::*; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; - use cosmwasm_std::{from_binary, Uint128}; + use cosmwasm_std::{from_json, Uint128}; const CREATOR: &str = "creator"; @@ -91,7 +91,9 @@ mod tests { let info = mock_info(CREATOR, &[]); let init_msg = Cw1155InstantiateMsg { - minter: CREATOR.to_string(), + name: "name".to_string(), + symbol: "symbol".to_string(), + minter: Some(CREATOR.to_string()), }; contract .instantiate(deps.as_mut(), mock_env(), info.clone(), init_msg) @@ -100,7 +102,6 @@ mod tests { let token_id = "Enterprise"; let mint_msg = Cw1155MintMsg { token_id: token_id.to_string(), - to: "john".to_string(), amount: Uint128::new(1), token_uri: Some("https://starships.example.com/Starship/Enterprise.json".into()), extension: Some(Metadata { @@ -109,12 +110,15 @@ mod tests { ..Metadata::default() }), }; - let exec_msg = Cw1155MetadataExecuteMsg::Mint(mint_msg.clone()); + let exec_msg = Cw1155MetadataExecuteMsg::Mint { + recipient: "john".to_string(), + msg: mint_msg.clone(), + }; contract .execute(deps.as_mut(), mock_env(), info, exec_msg) .unwrap(); - let res: cw1155::TokenInfoResponse = from_binary( + let res: cw1155::TokenInfoResponse = from_json( &contract .query( deps.as_ref(), From 6635fa334392fb6041e9132d49527e70bfe704df Mon Sep 17 00:00:00 2001 From: shab Date: Mon, 6 May 2024 14:47:29 -0400 Subject: [PATCH 23/57] feat: cw1155 on-chain royalties extension --- Cargo.lock | 15 + contracts/cw1155-base/src/state.rs | 2 +- contracts/cw1155-royalties/.cargo/config | 5 + contracts/cw1155-royalties/Cargo.toml | 38 +++ contracts/cw1155-royalties/README.md | 63 ++++ contracts/cw1155-royalties/examples/schema.rs | 14 + contracts/cw1155-royalties/src/error.rs | 18 + contracts/cw1155-royalties/src/lib.rs | 309 ++++++++++++++++++ contracts/cw1155-royalties/src/query.rs | 37 +++ contracts/cw2981-royalties/src/lib.rs | 2 +- packages/cw1155/src/error.rs | 2 +- packages/cw1155/src/msg.rs | 2 +- packages/cw1155/src/query.rs | 2 +- 13 files changed, 504 insertions(+), 5 deletions(-) create mode 100644 contracts/cw1155-royalties/.cargo/config create mode 100644 contracts/cw1155-royalties/Cargo.toml create mode 100644 contracts/cw1155-royalties/README.md create mode 100644 contracts/cw1155-royalties/examples/schema.rs create mode 100644 contracts/cw1155-royalties/src/error.rs create mode 100644 contracts/cw1155-royalties/src/lib.rs create mode 100644 contracts/cw1155-royalties/src/query.rs diff --git a/Cargo.lock b/Cargo.lock index 47ea5f83e..725a0f2ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -357,6 +357,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw1155-royalties" +version = "0.13.4" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw1155", + "cw1155-base", + "cw2 1.1.2", + "cw2981-royalties", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw2" version = "0.16.0" diff --git a/contracts/cw1155-base/src/state.rs b/contracts/cw1155-base/src/state.rs index e388ca7d1..128fcb437 100644 --- a/contracts/cw1155-base/src/state.rs +++ b/contracts/cw1155-base/src/state.rs @@ -132,7 +132,7 @@ pub struct TokenInfo { /// Metadata JSON Schema pub token_uri: Option, /// You can add any custom metadata here when you extend cw1155-base - pub extension: Option, + pub extension: T, } pub struct BalanceIndexes<'a> { diff --git a/contracts/cw1155-royalties/.cargo/config b/contracts/cw1155-royalties/.cargo/config new file mode 100644 index 000000000..7d1a066c8 --- /dev/null +++ b/contracts/cw1155-royalties/.cargo/config @@ -0,0 +1,5 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +wasm-debug = "build --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --example schema" diff --git a/contracts/cw1155-royalties/Cargo.toml b/contracts/cw1155-royalties/Cargo.toml new file mode 100644 index 000000000..55ce1561c --- /dev/null +++ b/contracts/cw1155-royalties/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "cw1155-royalties" +version = "0.13.4" +authors = ["shab "] +edition = "2018" +description = "Example extending CW1155 Token to store royalties on chain" +license = "Apache-2.0" +repository = "https://github.com/CosmWasm/cw-nfts" +homepage = "https://cosmwasm.com" +documentation = "https://docs.cosmwasm.com" + +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "artifacts/*", +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +cw2 = { workspace = true } +cw1155 = { workspace = true } +cw1155-base = { workspace = true, features = ["library"] } +cw2981-royalties = { path = "../cw2981-royalties", features = ["library"] } +cosmwasm-std = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } + +[dev-dependencies] +cosmwasm-schema = { workspace = true } diff --git a/contracts/cw1155-royalties/README.md b/contracts/cw1155-royalties/README.md new file mode 100644 index 000000000..b4b1c7830 --- /dev/null +++ b/contracts/cw1155-royalties/README.md @@ -0,0 +1,63 @@ +# CW-1155 Token-level Royalties + +An example of porting EIP-2981 to implement royalties at a token mint level. + +Builds on top of the metadata pattern in `cw1155-metadata-onchain`. + +All of the CW-1155 logic and behaviour you would expect for a token is implemented as normal, but additionally at mint time, royalty information can be attached to a token. + +Exposes two new query message types that can be called: + +```rust +// Should be called on sale to see if royalties are owed +// by the marketplace selling the tokens. +// See https://eips.ethereum.org/EIPS/eip-2981 +RoyaltyInfo { + token_id: String, + // the denom of this sale must also be the denom returned by RoyaltiesInfoResponse + sale_price: Uint128, +}, +// Called against the contract to signal that CW-2981 is implemented +CheckRoyalties {}, +``` + +The responses are: + +```rust +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct RoyaltiesInfoResponse { + pub address: String, + // Note that this must be the same denom as that passed in to RoyaltyInfo + // rounding up or down is at the discretion of the implementer + pub royalty_amount: Uint128, +} + +/// Shows if the contract implements royalties +/// if royalty_payments is true, marketplaces should pay them +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct CheckRoyaltiesResponse { + pub royalty_payments: bool, +} +``` + + +To set this information, new meta fields are available on mint: + +```rust + /// This is how much the minter takes as a cut when sold + pub royalty_percentage: Option, + /// The payment address, may be different to or the same + /// as the minter addr + /// question: how do we validate this? + pub royalty_payment_address: Option, +``` + +Note that the `royalty_payment_address` could of course be a single address, a multisig, or a DAO. + +## A note on CheckRoyalties + +For this contract, there's nothing to check. This hook is expected to be present to check if the contract does implement CW2981 and signal that on sale royalties should be checked. With the implementation at token level it should always return true because it's up to the token. + +Of course contracts that extend this can determine their own behaviour and replace this function if they have more complex behaviour (for example, you could maintain a secondary index of which tokens actually have royalties). + +In this super simple case that isn't necessary. diff --git a/contracts/cw1155-royalties/examples/schema.rs b/contracts/cw1155-royalties/examples/schema.rs new file mode 100644 index 000000000..03df3442f --- /dev/null +++ b/contracts/cw1155-royalties/examples/schema.rs @@ -0,0 +1,14 @@ +use std::fs::create_dir_all; + +use cosmwasm_schema::{remove_schemas, write_api}; + +use cw1155::Cw1155InstantiateMsg; +use cw1155_royalties::{Cw1155RoyaltiesExecuteMsg, Cw1155RoyaltiesQueryMsg}; + +fn main() { + write_api! { + instantiate: Cw1155InstantiateMsg, + execute: Cw1155RoyaltiesExecuteMsg, + query: Cw1155RoyaltiesQueryMsg, + } +} diff --git a/contracts/cw1155-royalties/src/error.rs b/contracts/cw1155-royalties/src/error.rs new file mode 100644 index 000000000..9099c9771 --- /dev/null +++ b/contracts/cw1155-royalties/src/error.rs @@ -0,0 +1,18 @@ +use cosmwasm_std::StdError; +use cw1155::Cw1155ContractError; +use thiserror::Error; + +#[derive(Debug, Error, PartialEq)] +pub enum Cw1155RoyaltiesContractError { + #[error(transparent)] + Std(#[from] StdError), + + #[error(transparent)] + Base(#[from] Cw1155ContractError), + + #[error("Royalty percentage must be between 0 and 100")] + InvalidRoyaltyPercentage, + + #[error("Invalid royalty payment address")] + InvalidRoyaltyPaymentAddress, +} diff --git a/contracts/cw1155-royalties/src/lib.rs b/contracts/cw1155-royalties/src/lib.rs new file mode 100644 index 000000000..a92616f2c --- /dev/null +++ b/contracts/cw1155-royalties/src/lib.rs @@ -0,0 +1,309 @@ +use cw1155::{Cw1155ExecuteMsg, Cw1155QueryMsg}; +pub use cw1155::{Cw1155InstantiateMsg, Cw1155MintMsg}; +use cw1155_base::Cw1155Contract; +use cw2::set_contract_version; +use cw2981_royalties::msg::Cw2981QueryMsg; +use cw2981_royalties::Extension; + +mod query; +pub use query::query_royalties_info; + +mod error; +pub use error::Cw1155RoyaltiesContractError; + +// Version info for migration +const CONTRACT_NAME: &str = "crates.io:cw1155-royalties"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub type Cw1155RoyaltiesContract<'a> = Cw1155Contract<'a, Extension, Cw2981QueryMsg>; +pub type Cw1155RoyaltiesExecuteMsg = Cw1155ExecuteMsg; +pub type Cw1155RoyaltiesQueryMsg = Cw1155QueryMsg; + +#[cfg(not(feature = "library"))] +pub mod entry { + use super::*; + + use cosmwasm_std::{entry_point, to_json_binary}; + use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; + use cw2981_royalties::msg::Cw2981QueryMsg; + use cw2981_royalties::{check_royalties, Metadata}; + + // This makes a conscious choice on the various generics used by the contract + #[entry_point] + pub fn instantiate( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + msg: Cw1155InstantiateMsg, + ) -> Result { + let res = Cw1155RoyaltiesContract::default().instantiate(deps.branch(), env, info, msg)?; + // Explicitly set contract name and version, otherwise set to cw1155-base info + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION) + .map_err(Cw1155RoyaltiesContractError::Std)?; + Ok(res) + } + + #[entry_point] + pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: Cw1155RoyaltiesExecuteMsg, + ) -> Result { + if let Cw1155RoyaltiesExecuteMsg::Mint { + msg: + Cw1155MintMsg { + extension: + Some(Metadata { + royalty_percentage: Some(royalty_percentage), + royalty_payment_address, + .. + }), + .. + }, + .. + } = &msg + { + // validate royalty_percentage to be between 0 and 100 + // no need to check < 0 because royalty_percentage is u64 + if *royalty_percentage > 100 { + return Err(Cw1155RoyaltiesContractError::InvalidRoyaltyPercentage); + } + + // validate royalty_payment_address to be a valid address + if let Some(royalty_payment_address) = royalty_payment_address { + deps.api.addr_validate(royalty_payment_address)?; + } else { + return Err(Cw1155RoyaltiesContractError::InvalidRoyaltyPaymentAddress); + } + } + Ok(Cw1155RoyaltiesContract::default().execute(deps, env, info, msg)?) + } + + #[entry_point] + pub fn query(deps: Deps, env: Env, msg: Cw1155RoyaltiesQueryMsg) -> StdResult { + match msg { + Cw1155RoyaltiesQueryMsg::Extension { msg } => match msg { + Cw2981QueryMsg::RoyaltyInfo { + token_id, + sale_price, + } => to_json_binary(&query_royalties_info(deps, token_id, sale_price)?), + Cw2981QueryMsg::CheckRoyalties {} => to_json_binary(&check_royalties(deps)?), + }, + _ => Cw1155RoyaltiesContract::default().query(deps, env, msg), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use cosmwasm_std::{from_json, Uint128}; + + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cw2981_royalties::msg::{CheckRoyaltiesResponse, RoyaltiesInfoResponse}; + use cw2981_royalties::{check_royalties, Metadata}; + + const CREATOR: &str = "creator"; + + #[test] + fn use_metadata_extension() { + let mut deps = mock_dependencies(); + let contract = Cw1155RoyaltiesContract::default(); + + let info = mock_info(CREATOR, &[]); + let init_msg = Cw1155InstantiateMsg { + name: "SpaceShips".to_string(), + symbol: "SPACE".to_string(), + minter: None, + }; + entry::instantiate(deps.as_mut(), mock_env(), info.clone(), init_msg).unwrap(); + + let token_id = "Enterprise"; + let token_uri = Some("https://starships.example.com/Starship/Enterprise.json".into()); + let extension = Some(Metadata { + description: Some("Spaceship with Warp Drive".into()), + name: Some("Starship USS Enterprise".to_string()), + ..Metadata::default() + }); + let exec_msg = Cw1155RoyaltiesExecuteMsg::Mint { + recipient: "john".to_string(), + msg: Cw1155MintMsg { + token_id: token_id.to_string(), + token_uri: token_uri.clone(), + extension: extension.clone(), + amount: Uint128::one(), + }, + }; + entry::execute(deps.as_mut(), mock_env(), info, exec_msg).unwrap(); + + let res = contract.tokens.load(&deps.storage, token_id).unwrap(); + assert_eq!(res.token_uri, token_uri); + assert_eq!(res.extension, extension); + } + + #[test] + fn validate_royalty_information() { + let mut deps = mock_dependencies(); + let _contract = Cw1155RoyaltiesContract::default(); + + let info = mock_info(CREATOR, &[]); + let init_msg = Cw1155InstantiateMsg { + name: "SpaceShips".to_string(), + symbol: "SPACE".to_string(), + minter: None, + }; + entry::instantiate(deps.as_mut(), mock_env(), info.clone(), init_msg).unwrap(); + + let token_id = "Enterprise"; + let exec_msg = Cw1155RoyaltiesExecuteMsg::Mint { + recipient: "john".to_string(), + msg: Cw1155MintMsg { + token_id: token_id.to_string(), + amount: Uint128::one(), + token_uri: Some("https://starships.example.com/Starship/Enterprise.json".into()), + extension: Some(Metadata { + description: Some("Spaceship with Warp Drive".into()), + name: Some("Starship USS Enterprise".to_string()), + royalty_percentage: Some(101), + ..Metadata::default() + }), + }, + }; + // mint will return StdError + let err = entry::execute(deps.as_mut(), mock_env(), info, exec_msg).unwrap_err(); + assert_eq!(err, Cw1155RoyaltiesContractError::InvalidRoyaltyPercentage); + } + + #[test] + fn check_royalties_response() { + let mut deps = mock_dependencies(); + let _contract = Cw1155RoyaltiesContract::default(); + + let info = mock_info(CREATOR, &[]); + let init_msg = Cw1155InstantiateMsg { + name: "SpaceShips".to_string(), + symbol: "SPACE".to_string(), + minter: None, + }; + entry::instantiate(deps.as_mut(), mock_env(), info.clone(), init_msg).unwrap(); + + let token_id = "Enterprise"; + let exec_msg = Cw1155RoyaltiesExecuteMsg::Mint { + recipient: "john".to_string(), + msg: Cw1155MintMsg { + token_id: token_id.to_string(), + amount: Uint128::one(), + token_uri: Some("https://starships.example.com/Starship/Enterprise.json".into()), + extension: Some(Metadata { + description: Some("Spaceship with Warp Drive".into()), + name: Some("Starship USS Enterprise".to_string()), + ..Metadata::default() + }), + }, + }; + entry::execute(deps.as_mut(), mock_env(), info, exec_msg).unwrap(); + + let expected = CheckRoyaltiesResponse { + royalty_payments: true, + }; + let res = check_royalties(deps.as_ref()).unwrap(); + assert_eq!(res, expected); + + // also check the longhand way + let query_msg = Cw1155RoyaltiesQueryMsg::Extension { + msg: Cw2981QueryMsg::CheckRoyalties {}, + }; + let query_res: CheckRoyaltiesResponse = + from_json(entry::query(deps.as_ref(), mock_env(), query_msg).unwrap()).unwrap(); + assert_eq!(query_res, expected); + } + + #[test] + fn check_token_royalties() { + let mut deps = mock_dependencies(); + + let info = mock_info(CREATOR, &[]); + let init_msg = Cw1155InstantiateMsg { + name: "SpaceShips".to_string(), + symbol: "SPACE".to_string(), + minter: None, + }; + entry::instantiate(deps.as_mut(), mock_env(), info.clone(), init_msg).unwrap(); + + let token_id = "Enterprise"; + let owner = "jeanluc"; + let exec_msg = Cw1155RoyaltiesExecuteMsg::Mint { + recipient: owner.into(), + msg: Cw1155MintMsg { + token_id: token_id.to_string(), + amount: Uint128::one(), + token_uri: Some("https://starships.example.com/Starship/Enterprise.json".into()), + extension: Some(Metadata { + description: Some("Spaceship with Warp Drive".into()), + name: Some("Starship USS Enterprise".to_string()), + royalty_payment_address: Some("jeanluc".to_string()), + royalty_percentage: Some(10), + ..Metadata::default() + }), + }, + }; + entry::execute(deps.as_mut(), mock_env(), info.clone(), exec_msg).unwrap(); + + let expected = RoyaltiesInfoResponse { + address: owner.into(), + royalty_amount: Uint128::new(10), + }; + let res = + query_royalties_info(deps.as_ref(), token_id.to_string(), Uint128::new(100)).unwrap(); + assert_eq!(res, expected); + + // also check the longhand way + let query_msg = Cw1155RoyaltiesQueryMsg::Extension { + msg: Cw2981QueryMsg::RoyaltyInfo { + token_id: token_id.to_string(), + sale_price: Uint128::new(100), + }, + }; + let query_res: RoyaltiesInfoResponse = + from_json(entry::query(deps.as_ref(), mock_env(), query_msg).unwrap()).unwrap(); + assert_eq!(query_res, expected); + + // check for rounding down + // which is the default behaviour + let voyager_token_id = "Voyager"; + let owner = "janeway"; + let voyager_exec_msg = Cw1155RoyaltiesExecuteMsg::Mint { + recipient: owner.into(), + msg: Cw1155MintMsg { + token_id: voyager_token_id.to_string(), + amount: Uint128::one(), + token_uri: Some("https://starships.example.com/Starship/Voyager.json".into()), + extension: Some(Metadata { + description: Some("Spaceship with Warp Drive".into()), + name: Some("Starship USS Voyager".to_string()), + royalty_payment_address: Some("janeway".to_string()), + royalty_percentage: Some(4), + ..Metadata::default() + }), + }, + }; + entry::execute(deps.as_mut(), mock_env(), info, voyager_exec_msg).unwrap(); + + // 43 x 0.04 (i.e., 4%) should be 1.72 + // we expect this to be rounded down to 1 + let voyager_expected = RoyaltiesInfoResponse { + address: owner.into(), + royalty_amount: Uint128::new(1), + }; + + let res = query_royalties_info( + deps.as_ref(), + voyager_token_id.to_string(), + Uint128::new(43), + ) + .unwrap(); + assert_eq!(res, voyager_expected); + } +} diff --git a/contracts/cw1155-royalties/src/query.rs b/contracts/cw1155-royalties/src/query.rs new file mode 100644 index 000000000..dfd90a300 --- /dev/null +++ b/contracts/cw1155-royalties/src/query.rs @@ -0,0 +1,37 @@ +use crate::Cw1155RoyaltiesContract; +use cosmwasm_std::{Decimal, Deps, StdResult, Uint128}; +use cw2981_royalties::msg::RoyaltiesInfoResponse; + +/// NOTE: default behaviour here is to round down +/// EIP2981 specifies that the rounding behaviour is at the discretion of the implementer +/// NOTE: This implementation is copied from the cw2981-royalties contract, only difference is the TokenInfo struct (no owner field in cw1155) +pub fn query_royalties_info( + deps: Deps, + token_id: String, + sale_price: Uint128, +) -> StdResult { + let contract = Cw1155RoyaltiesContract::default(); + let token_info = contract.tokens.load(deps.storage, &token_id)?; + + let royalty_percentage = match token_info.extension { + Some(ref ext) => match ext.royalty_percentage { + Some(percentage) => Decimal::percent(percentage), + None => Decimal::percent(0), + }, + None => Decimal::percent(0), + }; + let royalty_from_sale_price = sale_price * royalty_percentage; + + let royalty_address = match token_info.extension { + Some(ext) => match ext.royalty_payment_address { + Some(addr) => addr, + None => String::from(""), + }, + None => String::from(""), + }; + + Ok(RoyaltiesInfoResponse { + address: royalty_address, + royalty_amount: royalty_from_sale_price, + }) +} diff --git a/contracts/cw2981-royalties/src/lib.rs b/contracts/cw2981-royalties/src/lib.rs index 194412495..6142fccb5 100644 --- a/contracts/cw2981-royalties/src/lib.rs +++ b/contracts/cw2981-royalties/src/lib.rs @@ -7,7 +7,7 @@ pub use query::{check_royalties, query_royalties_info}; use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_json_binary, Empty}; use cw721_base::Cw721Contract; -pub use cw721_base::{InstantiateMsg, MinterResponse}; +pub use cw721_base::InstantiateMsg; use crate::error::ContractError; use crate::msg::Cw2981QueryMsg; diff --git a/packages/cw1155/src/error.rs b/packages/cw1155/src/error.rs index c81ca95f9..3a70aafd2 100644 --- a/packages/cw1155/src/error.rs +++ b/packages/cw1155/src/error.rs @@ -3,7 +3,7 @@ use cw2::VersionError; use cw_ownable::OwnershipError; use thiserror::Error; -#[derive(Error, Debug)] +#[derive(Error, Debug, PartialEq)] pub enum Cw1155ContractError { #[error("StdError: {0}")] Std(#[from] StdError), diff --git a/packages/cw1155/src/msg.rs b/packages/cw1155/src/msg.rs index d74e4119f..bb330e447 100644 --- a/packages/cw1155/src/msg.rs +++ b/packages/cw1155/src/msg.rs @@ -111,7 +111,7 @@ pub struct Cw1155MintMsg { /// Metadata JSON Schema pub token_uri: Option, /// Any custom extension used by this contract - pub extension: Option, + pub extension: T, } #[cw_serde] diff --git a/packages/cw1155/src/query.rs b/packages/cw1155/src/query.rs index 78f203daf..7578f18fd 100644 --- a/packages/cw1155/src/query.rs +++ b/packages/cw1155/src/query.rs @@ -149,7 +149,7 @@ pub struct TokenInfoResponse { /// Should be a url point to a json file pub token_uri: Option, /// You can add any custom metadata here when you extend cw1155-base - pub extension: Option, + pub extension: T, } #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] From 00c7b195a72c3f42cc10bb8668e09a1d2e49bf60 Mon Sep 17 00:00:00 2001 From: shab Date: Mon, 6 May 2024 15:06:41 -0400 Subject: [PATCH 24/57] fix: existing tests/imports --- contracts/cw1155-base/src/contract_tests.rs | 6 +++--- contracts/cw1155-metadata-onchain/examples/schema.rs | 4 +--- contracts/cw1155-metadata-onchain/src/lib.rs | 2 +- contracts/cw1155-royalties/examples/schema.rs | 4 +--- contracts/cw721-base/src/contract_tests.rs | 6 ++---- contracts/cw721-base/src/multi_tests.rs | 4 +--- contracts/cw721-expiration/src/lib.rs | 2 +- contracts/cw721-expiration/src/query.rs | 5 ++--- contracts/cw721-metadata-onchain/src/lib.rs | 2 +- contracts/cw721-non-transferable/examples/schema.rs | 5 +++-- contracts/cw721-non-transferable/src/lib.rs | 1 - 11 files changed, 16 insertions(+), 25 deletions(-) diff --git a/contracts/cw1155-base/src/contract_tests.rs b/contracts/cw1155-base/src/contract_tests.rs index 472253bc4..1fbeda822 100644 --- a/contracts/cw1155-base/src/contract_tests.rs +++ b/contracts/cw1155-base/src/contract_tests.rs @@ -660,7 +660,7 @@ mod tests { token_id: "token5".to_owned() }, ), - to_json_binary(&TokenInfoResponse:: { + to_json_binary(&TokenInfoResponse::> { token_uri: None, extension: None, }), @@ -951,7 +951,7 @@ mod tests { token_id: token1.clone() }, ), - to_json_binary(&TokenInfoResponse:: { + to_json_binary(&TokenInfoResponse::> { token_uri: Some(url1.clone()), extension: None, }) @@ -982,7 +982,7 @@ mod tests { mock_env(), Cw1155QueryMsg::TokenInfo { token_id: token1 }, ), - to_json_binary(&TokenInfoResponse:: { + to_json_binary(&TokenInfoResponse::> { token_uri: Some(url1), extension: None, }) diff --git a/contracts/cw1155-metadata-onchain/examples/schema.rs b/contracts/cw1155-metadata-onchain/examples/schema.rs index 6f3de8b7a..445695e6c 100644 --- a/contracts/cw1155-metadata-onchain/examples/schema.rs +++ b/contracts/cw1155-metadata-onchain/examples/schema.rs @@ -1,6 +1,4 @@ -use std::fs::create_dir_all; - -use cosmwasm_schema::{remove_schemas, write_api}; +use cosmwasm_schema::write_api; use cosmwasm_std::Empty; use cw1155::{Cw1155ExecuteMsg, Cw1155InstantiateMsg, Cw1155QueryMsg}; diff --git a/contracts/cw1155-metadata-onchain/src/lib.rs b/contracts/cw1155-metadata-onchain/src/lib.rs index 30e5864ea..0d5770cc7 100644 --- a/contracts/cw1155-metadata-onchain/src/lib.rs +++ b/contracts/cw1155-metadata-onchain/src/lib.rs @@ -31,7 +31,7 @@ pub struct Metadata { pub youtube_url: Option, } -pub type Extension = Metadata; +pub type Extension = Option; pub type Cw1155MetadataContract<'a> = cw1155_base::Cw1155Contract<'a, Extension, Empty>; pub type Cw1155MetadataExecuteMsg = Cw1155ExecuteMsg; diff --git a/contracts/cw1155-royalties/examples/schema.rs b/contracts/cw1155-royalties/examples/schema.rs index 03df3442f..087a49ebb 100644 --- a/contracts/cw1155-royalties/examples/schema.rs +++ b/contracts/cw1155-royalties/examples/schema.rs @@ -1,6 +1,4 @@ -use std::fs::create_dir_all; - -use cosmwasm_schema::{remove_schemas, write_api}; +use cosmwasm_schema::write_api; use cw1155::Cw1155InstantiateMsg; use cw1155_royalties::{Cw1155RoyaltiesExecuteMsg, Cw1155RoyaltiesQueryMsg}; diff --git a/contracts/cw721-base/src/contract_tests.rs b/contracts/cw721-base/src/contract_tests.rs index a128a1939..b789748a8 100644 --- a/contracts/cw721-base/src/contract_tests.rs +++ b/contracts/cw721-base/src/contract_tests.rs @@ -7,13 +7,11 @@ use cosmwasm_std::{ use cw721::{ Approval, ApprovalResponse, ContractInfoResponse, Cw721Query, Cw721ReceiveMsg, Expiration, - NftInfoResponse, OperatorResponse, OperatorsResponse, OwnerOfResponse, + MinterResponse, NftInfoResponse, OperatorResponse, OperatorsResponse, OwnerOfResponse, }; use cw_ownable::OwnershipError; -use crate::{ - ContractError, Cw721Contract, ExecuteMsg, Extension, InstantiateMsg, MinterResponse, QueryMsg, -}; +use crate::{ContractError, Cw721Contract, ExecuteMsg, Extension, InstantiateMsg, QueryMsg}; const MINTER: &str = "merlin"; const CONTRACT_NAME: &str = "Magic Power"; diff --git a/contracts/cw721-base/src/multi_tests.rs b/contracts/cw721-base/src/multi_tests.rs index d691d8a0a..345750907 100644 --- a/contracts/cw721-base/src/multi_tests.rs +++ b/contracts/cw721-base/src/multi_tests.rs @@ -1,9 +1,7 @@ use cosmwasm_std::{to_json_binary, Addr, Empty, QuerierWrapper, WasmMsg}; -use cw721::OwnerOfResponse; +use cw721::{MinterResponse, OwnerOfResponse}; use cw_multi_test::{App, Contract, ContractWrapper, Executor}; -use crate::MinterResponse; - fn cw721_base_latest_contract() -> Box> { let contract = ContractWrapper::new( crate::entry::execute, diff --git a/contracts/cw721-expiration/src/lib.rs b/contracts/cw721-expiration/src/lib.rs index 29b0027f9..c841de969 100644 --- a/contracts/cw721-expiration/src/lib.rs +++ b/contracts/cw721-expiration/src/lib.rs @@ -13,7 +13,7 @@ use cosmwasm_std::Empty; const CONTRACT_NAME: &str = "crates.io:cw721-expiration"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); -pub type MinterResponse = cw721_base::msg::MinterResponse; +pub type MinterResponse = cw721::MinterResponse; pub type Extension = Option; pub type TokenInfo = cw721_base::state::TokenInfo; diff --git a/contracts/cw721-expiration/src/query.rs b/contracts/cw721-expiration/src/query.rs index 208a297eb..a7d7626dc 100644 --- a/contracts/cw721-expiration/src/query.rs +++ b/contracts/cw721-expiration/src/query.rs @@ -1,10 +1,9 @@ use cosmwasm_std::{to_json_binary, Addr, Binary, Deps, Env, StdResult}; use cw721::{ AllNftInfoResponse, ApprovalResponse, ApprovalsResponse, ContractInfoResponse, Cw721Query, - NftInfoResponse, NumTokensResponse, OperatorResponse, OperatorsResponse, OwnerOfResponse, - TokensResponse, + MinterResponse, NftInfoResponse, NumTokensResponse, OperatorResponse, OperatorsResponse, + OwnerOfResponse, TokensResponse, }; -use cw721_base::MinterResponse; use crate::{error::ContractError, msg::QueryMsg, state::Cw721ExpirationContract, Extension}; diff --git a/contracts/cw721-metadata-onchain/src/lib.rs b/contracts/cw721-metadata-onchain/src/lib.rs index 11d053022..e8a133122 100644 --- a/contracts/cw721-metadata-onchain/src/lib.rs +++ b/contracts/cw721-metadata-onchain/src/lib.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::Empty; -pub use cw721_base::{ContractError, InstantiateMsg, MinterResponse}; +pub use cw721_base::{ContractError, InstantiateMsg}; // Version info for migration const CONTRACT_NAME: &str = "crates.io:cw721-metadata-onchain"; diff --git a/contracts/cw721-non-transferable/examples/schema.rs b/contracts/cw721-non-transferable/examples/schema.rs index 5d11328a0..cd1cde28c 100644 --- a/contracts/cw721-non-transferable/examples/schema.rs +++ b/contracts/cw721-non-transferable/examples/schema.rs @@ -5,9 +5,10 @@ use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, s use cw721::{ AllNftInfoResponse, ApprovalResponse, ApprovalsResponse, ContractInfoResponse, Cw721ExecuteMsg, - NftInfoResponse, NumTokensResponse, OperatorsResponse, OwnerOfResponse, TokensResponse, + MinterResponse, NftInfoResponse, NumTokensResponse, OperatorsResponse, OwnerOfResponse, + TokensResponse, }; -use cw721_non_transferable::{Extension, InstantiateMsg, MinterResponse, QueryMsg}; +use cw721_non_transferable::{Extension, InstantiateMsg, QueryMsg}; fn main() { let mut out_dir = current_dir().unwrap(); diff --git a/contracts/cw721-non-transferable/src/lib.rs b/contracts/cw721-non-transferable/src/lib.rs index e40fea0ba..ed3fde750 100644 --- a/contracts/cw721-non-transferable/src/lib.rs +++ b/contracts/cw721-non-transferable/src/lib.rs @@ -3,7 +3,6 @@ use cosmwasm_std::Empty; pub use cw721_base::{ entry::{execute as _execute, query as _query}, ContractError, Cw721Contract, ExecuteMsg, Extension, InstantiateMsg as Cw721BaseInstantiateMsg, - MinterResponse, }; pub mod msg; From 8f9afd292fb89001bcda7c7fab441d85bc183a57 Mon Sep 17 00:00:00 2001 From: shab Date: Mon, 6 May 2024 15:25:08 -0400 Subject: [PATCH 25/57] fix: generic usage --- contracts/cw1155-base/src/query.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/cw1155-base/src/query.rs b/contracts/cw1155-base/src/query.rs index 77c0bdbcf..c04adac54 100644 --- a/contracts/cw1155-base/src/query.rs +++ b/contracts/cw1155-base/src/query.rs @@ -10,7 +10,7 @@ use cw1155::{ BalanceResponse, BatchBalanceResponse, Cw1155QueryMsg, Expiration, IsApprovedForAllResponse, NumTokensResponse, TokenInfoResponse, TokensResponse, }; -use cw721_base::{Cw721Contract, Extension}; +use cw721_base::Cw721Contract; use cw_storage_plus::Bound; use cw_utils::maybe_addr; @@ -27,7 +27,7 @@ where pub fn query(&self, deps: Deps, env: Env, msg: Cw1155QueryMsg) -> StdResult { match msg { Cw1155QueryMsg::Minter {} => { - let tract = Cw721Contract::::default(); + let tract = Cw721Contract::::default(); to_json_binary(&tract.minter(deps)?) } Cw1155QueryMsg::BalanceOf { owner, token_id } => { From c048017264dbfbdd7cc955c0bba2670e952533fd Mon Sep 17 00:00:00 2001 From: shab Date: Mon, 6 May 2024 15:40:22 -0400 Subject: [PATCH 26/57] chore: lint --- packages/cw1155/src/receiver.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cw1155/src/receiver.rs b/packages/cw1155/src/receiver.rs index f90469a73..4994e9665 100644 --- a/packages/cw1155/src/receiver.rs +++ b/packages/cw1155/src/receiver.rs @@ -1,8 +1,8 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use cosmwasm_std::{Binary, CosmosMsg, StdResult, Uint128, WasmMsg, to_json_binary}; use crate::TokenAmount; +use cosmwasm_std::{to_json_binary, Binary, CosmosMsg, StdResult, Uint128, WasmMsg}; /// Cw1155ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] From be21d01a2ba039cccec0d596bf90508baace0fda Mon Sep 17 00:00:00 2001 From: shab Date: Mon, 6 May 2024 16:10:53 -0400 Subject: [PATCH 27/57] chore: clippy --- contracts/cw1155-base/examples/schema.rs | 4 ++-- contracts/cw1155-base/src/contract_tests.rs | 11 ++++------- contracts/cw1155-base/src/execute.rs | 6 +++--- contracts/cw1155-base/src/state.rs | 3 ++- contracts/cw1155-metadata-onchain/src/lib.rs | 2 +- contracts/cw2981-royalties/src/lib.rs | 6 ++++-- packages/cw1155/examples/schema.rs | 4 ++-- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/contracts/cw1155-base/examples/schema.rs b/contracts/cw1155-base/examples/schema.rs index 01ed7589c..087cf9226 100644 --- a/contracts/cw1155-base/examples/schema.rs +++ b/contracts/cw1155-base/examples/schema.rs @@ -1,6 +1,6 @@ -use std::fs::create_dir_all; -use cosmwasm_schema::{remove_schemas, write_api}; + +use cosmwasm_schema::{write_api}; use cosmwasm_std::Empty; use cw1155::{Cw1155ExecuteMsg, Cw1155InstantiateMsg, Cw1155QueryMsg}; diff --git a/contracts/cw1155-base/src/contract_tests.rs b/contracts/cw1155-base/src/contract_tests.rs index 1fbeda822..5a796f67b 100644 --- a/contracts/cw1155-base/src/contract_tests.rs +++ b/contracts/cw1155-base/src/contract_tests.rs @@ -323,7 +323,7 @@ mod tests { ) .unwrap(); assert!( - approvals.operators.len() == 0 + approvals.operators.is_empty() || !approvals .operators .iter() @@ -743,8 +743,7 @@ mod tests { .unwrap(); // invalid expires should be rejected - assert!(matches!( - contract.execute( + assert!(contract.execute( deps.as_mut(), env.clone(), mock_info(user1.as_ref(), &[]), @@ -752,9 +751,7 @@ mod tests { operator: user2.clone(), expires: Some(Expiration::AtHeight(5)), }, - ), - Err(_) - )); + ).is_err()); contract .execute( @@ -810,7 +807,7 @@ mod tests { ) .unwrap(); assert!( - approvals.operators.len() == 0 + approvals.operators.is_empty() || !approvals .operators .iter() diff --git a/contracts/cw1155-base/src/execute.rs b/contracts/cw1155-base/src/execute.rs index 4e0e66a83..72ed2ffab 100644 --- a/contracts/cw1155-base/src/execute.rs +++ b/contracts/cw1155-base/src/execute.rs @@ -553,7 +553,7 @@ where .range(deps.storage, None, None, Order::Ascending) .collect::>>()? { - if approval.is_expired(&env) || approval.amount <= *amount { + if approval.is_expired(env) || approval.amount <= *amount { self.token_approves .remove(deps.storage, (&token_id, &from, &operator)); } else { @@ -643,7 +643,7 @@ where tokens .iter() .map(|TokenAmount { token_id, amount }| { - self.verify_approval(storage, &env, info, owner, token_id, *amount) + self.verify_approval(storage, env, info, owner, token_id, *amount) }) .collect() } @@ -674,7 +674,7 @@ where .load(storage, (&token_id, owner, operator)) { Ok(approval) => { - if !approval.is_expired(&env) { + if !approval.is_expired(env) { Some(approval) } else { None diff --git a/contracts/cw1155-base/src/state.rs b/contracts/cw1155-base/src/state.rs index 128fcb437..0a678ce2f 100644 --- a/contracts/cw1155-base/src/state.rs +++ b/contracts/cw1155-base/src/state.rs @@ -30,7 +30,7 @@ where pub(crate) _custom_query: PhantomData, } -impl<'a, T, Q> Default for Cw1155Contract<'static, T, Q> +impl<'a, T, Q> Default for Cw1155Contract<'a, T, Q> where T: Serialize + DeserializeOwned + Clone, Q: CustomMsg, @@ -54,6 +54,7 @@ where T: Serialize + DeserializeOwned + Clone, Q: CustomMsg, { + #[allow(clippy::too_many_arguments)] fn new( contract_info_key: &'a str, tokens_key: &'a str, diff --git a/contracts/cw1155-metadata-onchain/src/lib.rs b/contracts/cw1155-metadata-onchain/src/lib.rs index 0d5770cc7..eb2f4d432 100644 --- a/contracts/cw1155-metadata-onchain/src/lib.rs +++ b/contracts/cw1155-metadata-onchain/src/lib.rs @@ -119,7 +119,7 @@ mod tests { .unwrap(); let res: cw1155::TokenInfoResponse = from_json( - &contract + contract .query( deps.as_ref(), mock_env(), diff --git a/contracts/cw2981-royalties/src/lib.rs b/contracts/cw2981-royalties/src/lib.rs index 6142fccb5..88e0b8f37 100644 --- a/contracts/cw2981-royalties/src/lib.rs +++ b/contracts/cw2981-royalties/src/lib.rs @@ -5,15 +5,16 @@ pub mod query; pub use query::{check_royalties, query_royalties_info}; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{to_json_binary, Empty}; +use cosmwasm_std::Empty; use cw721_base::Cw721Contract; pub use cw721_base::InstantiateMsg; -use crate::error::ContractError; use crate::msg::Cw2981QueryMsg; // Version info for migration +#[cfg(not(feature = "library"))] const CONTRACT_NAME: &str = "crates.io:cw2981-royalties"; +#[cfg(not(feature = "library"))] const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); #[cw_serde] @@ -122,6 +123,7 @@ mod tests { use cosmwasm_std::{from_json, Uint128}; + use crate::error::ContractError; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; use cw721::Cw721Query; diff --git a/packages/cw1155/examples/schema.rs b/packages/cw1155/examples/schema.rs index cb7ee2d5c..9d5d3939d 100644 --- a/packages/cw1155/examples/schema.rs +++ b/packages/cw1155/examples/schema.rs @@ -1,6 +1,6 @@ -use std::fs::create_dir_all; -use cosmwasm_schema::{remove_schemas, write_api}; + +use cosmwasm_schema::{write_api}; use cosmwasm_std::Empty; use cw1155::{Cw1155ExecuteMsg, Cw1155InstantiateMsg, Cw1155QueryMsg}; From 65ab7d4a4e8eaa6260fb2c29389022eb3ccbc5a9 Mon Sep 17 00:00:00 2001 From: shab Date: Mon, 6 May 2024 16:57:06 -0400 Subject: [PATCH 28/57] fix: lint 2, add circle ci config for cw1155-royalties --- .circleci/config.yml | 37 ++++++++++++++++++++ contracts/cw1155-base/examples/schema.rs | 4 +-- contracts/cw1155-base/src/contract_tests.rs | 6 ++-- contracts/cw1155-metadata-onchain/src/lib.rs | 2 +- packages/cw1155/examples/schema.rs | 4 +-- 5 files changed, 44 insertions(+), 9 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 51ecc1c92..f548768ae 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,6 +5,7 @@ workflows: jobs: - contract_cw1155_base - contract_cw1155_metadata_onchain + - contract_cw1155_royalties - contract_cw721_base - contract_cw721_metadata_onchain - contract_cw721_fixed_price @@ -94,6 +95,42 @@ jobs: - target key: cargocache-cw1155-metadata-onchain-rust:1.58.1-{{ checksum "~/project/Cargo.lock" }} + contract_cw1155_royalties: + docker: + - image: rust:1.58.1 + working_directory: ~/project/contracts/cw1155-royalties + steps: + - checkout: + path: ~/project + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - cargocache-cw1155-royalties-rust:1.58.1-{{ checksum "~/project/Cargo.lock" }} + - run: + name: Unit Tests + environment: + RUST_BACKTRACE: 1 + command: cargo unit-test --locked + - run: + name: Build and run schema generator + command: cargo schema --locked + - run: + name: Ensure checked-in schemas are up-to-date + command: | + CHANGES_IN_REPO=$(git status --porcelain) + if [[ -n "$CHANGES_IN_REPO" ]]; then + echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:" + git status && git --no-pager diff + exit 1 + fi + - save_cache: + paths: + - /usr/local/cargo/registry + - target + key: cargocache-cw1155-royalties-rust:1.58.1-{{ checksum "~/project/Cargo.lock" }} + contract_cw721_base: docker: - image: rust:1.65.0 diff --git a/contracts/cw1155-base/examples/schema.rs b/contracts/cw1155-base/examples/schema.rs index 087cf9226..477f1323d 100644 --- a/contracts/cw1155-base/examples/schema.rs +++ b/contracts/cw1155-base/examples/schema.rs @@ -1,6 +1,4 @@ - - -use cosmwasm_schema::{write_api}; +use cosmwasm_schema::write_api; use cosmwasm_std::Empty; use cw1155::{Cw1155ExecuteMsg, Cw1155InstantiateMsg, Cw1155QueryMsg}; diff --git a/contracts/cw1155-base/src/contract_tests.rs b/contracts/cw1155-base/src/contract_tests.rs index 5a796f67b..8c7559ac9 100644 --- a/contracts/cw1155-base/src/contract_tests.rs +++ b/contracts/cw1155-base/src/contract_tests.rs @@ -743,7 +743,8 @@ mod tests { .unwrap(); // invalid expires should be rejected - assert!(contract.execute( + assert!(contract + .execute( deps.as_mut(), env.clone(), mock_info(user1.as_ref(), &[]), @@ -751,7 +752,8 @@ mod tests { operator: user2.clone(), expires: Some(Expiration::AtHeight(5)), }, - ).is_err()); + ) + .is_err()); contract .execute( diff --git a/contracts/cw1155-metadata-onchain/src/lib.rs b/contracts/cw1155-metadata-onchain/src/lib.rs index eb2f4d432..2adfc5372 100644 --- a/contracts/cw1155-metadata-onchain/src/lib.rs +++ b/contracts/cw1155-metadata-onchain/src/lib.rs @@ -36,7 +36,7 @@ pub type Extension = Option; pub type Cw1155MetadataContract<'a> = cw1155_base::Cw1155Contract<'a, Extension, Empty>; pub type Cw1155MetadataExecuteMsg = Cw1155ExecuteMsg; -// #[cfg(not(feature = "library"))] +#[cfg(not(feature = "library"))] pub mod entry { use super::*; diff --git a/packages/cw1155/examples/schema.rs b/packages/cw1155/examples/schema.rs index 9d5d3939d..b44ea3de9 100644 --- a/packages/cw1155/examples/schema.rs +++ b/packages/cw1155/examples/schema.rs @@ -1,6 +1,4 @@ - - -use cosmwasm_schema::{write_api}; +use cosmwasm_schema::write_api; use cosmwasm_std::Empty; use cw1155::{Cw1155ExecuteMsg, Cw1155InstantiateMsg, Cw1155QueryMsg}; From 09f471db0b9fd11cc8d338cbd34754d31f4ec421 Mon Sep 17 00:00:00 2001 From: shab Date: Mon, 6 May 2024 17:33:55 -0400 Subject: [PATCH 29/57] fix: circle ci --- .circleci/config.yml | 24 ++++++++++++------------ contracts/cw2981-royalties/src/lib.rs | 3 ++- packages/cw1155/src/msg.rs | 1 + packages/cw1155/src/receiver.rs | 6 +++--- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f548768ae..0a4c01546 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -25,7 +25,7 @@ workflows: jobs: contract_cw1155_base: docker: - - image: rust:1.58.1 + - image: rust:1.65.0 working_directory: ~/project/contracts/cw1155-base steps: - checkout: @@ -35,7 +35,7 @@ jobs: command: rustc --version; cargo --version; rustup --version - restore_cache: keys: - - cargocache-cw1155-base-rust:1.58.1-{{ checksum "~/project/Cargo.lock" }} + - cargocache-cw1155-base-rust:1.65.0-{{ checksum "~/project/Cargo.lock" }} - run: name: Unit Tests environment: @@ -57,11 +57,11 @@ jobs: paths: - /usr/local/cargo/registry - target - key: cargocache-cw1155-base-rust:1.58.1-{{ checksum "~/project/Cargo.lock" }} + key: cargocache-cw1155-base-rust:1.65.0-{{ checksum "~/project/Cargo.lock" }} contract_cw1155_metadata_onchain: docker: - - image: rust:1.58.1 + - image: rust:1.65.0 working_directory: ~/project/contracts/cw1155-metadata-onchain steps: - checkout: @@ -71,7 +71,7 @@ jobs: command: rustc --version; cargo --version; rustup --version - restore_cache: keys: - - cargocache-cw1155-metadata-onchain-rust:1.58.1-{{ checksum "~/project/Cargo.lock" }} + - cargocache-cw1155-metadata-onchain-rust:1.65.0-{{ checksum "~/project/Cargo.lock" }} - run: name: Unit Tests environment: @@ -93,11 +93,11 @@ jobs: paths: - /usr/local/cargo/registry - target - key: cargocache-cw1155-metadata-onchain-rust:1.58.1-{{ checksum "~/project/Cargo.lock" }} + key: cargocache-cw1155-metadata-onchain-rust:1.65.0-{{ checksum "~/project/Cargo.lock" }} contract_cw1155_royalties: docker: - - image: rust:1.58.1 + - image: rust:1.65.0 working_directory: ~/project/contracts/cw1155-royalties steps: - checkout: @@ -107,7 +107,7 @@ jobs: command: rustc --version; cargo --version; rustup --version - restore_cache: keys: - - cargocache-cw1155-royalties-rust:1.58.1-{{ checksum "~/project/Cargo.lock" }} + - cargocache-cw1155-royalties-rust:1.65.0-{{ checksum "~/project/Cargo.lock" }} - run: name: Unit Tests environment: @@ -129,7 +129,7 @@ jobs: paths: - /usr/local/cargo/registry - target - key: cargocache-cw1155-royalties-rust:1.58.1-{{ checksum "~/project/Cargo.lock" }} + key: cargocache-cw1155-royalties-rust:1.65.0-{{ checksum "~/project/Cargo.lock" }} contract_cw721_base: docker: @@ -278,7 +278,7 @@ jobs: package_cw1155: docker: - - image: rust:1.58.1 + - image: rust:1.65.0 working_directory: ~/project/packages/cw1155 steps: - checkout: @@ -288,7 +288,7 @@ jobs: command: rustc --version; cargo --version; rustup --version; rustup target list --installed - restore_cache: keys: - - cargocache-v2-cw1155:1.58.1-{{ checksum "~/project/Cargo.lock" }} + - cargocache-v2-cw1155:1.65.0-{{ checksum "~/project/Cargo.lock" }} - run: name: Build library for native target command: cargo build --locked @@ -311,7 +311,7 @@ jobs: paths: - /usr/local/cargo/registry - target - key: cargocache-v2-cw1155:1.58.1-{{ checksum "~/project/Cargo.lock" }} + key: cargocache-v2-cw1155:1.65.0-{{ checksum "~/project/Cargo.lock" }} lint: docker: diff --git a/contracts/cw2981-royalties/src/lib.rs b/contracts/cw2981-royalties/src/lib.rs index 88e0b8f37..7c49e0be0 100644 --- a/contracts/cw2981-royalties/src/lib.rs +++ b/contracts/cw2981-royalties/src/lib.rs @@ -58,7 +58,8 @@ pub type QueryMsg = cw721_base::QueryMsg; pub mod entry { use super::*; - use cosmwasm_std::entry_point; + use crate::error::ContractError; + use cosmwasm_std::{entry_point, to_json_binary}; use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; #[entry_point] diff --git a/packages/cw1155/src/msg.rs b/packages/cw1155/src/msg.rs index bb330e447..0759195f8 100644 --- a/packages/cw1155/src/msg.rs +++ b/packages/cw1155/src/msg.rs @@ -115,6 +115,7 @@ pub struct Cw1155MintMsg { } #[cw_serde] +#[derive(Eq)] pub struct TokenAmount { pub token_id: String, pub amount: Uint128, diff --git a/packages/cw1155/src/receiver.rs b/packages/cw1155/src/receiver.rs index 4994e9665..e5d0171ff 100644 --- a/packages/cw1155/src/receiver.rs +++ b/packages/cw1155/src/receiver.rs @@ -5,7 +5,7 @@ use crate::TokenAmount; use cosmwasm_std::{to_json_binary, Binary, CosmosMsg, StdResult, Uint128, WasmMsg}; /// Cw1155ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] #[serde(rename_all = "snake_case")] pub struct Cw1155ReceiveMsg { /// The account that executed the send message @@ -37,7 +37,7 @@ impl Cw1155ReceiveMsg { } /// Cw1155BatchReceiveMsg should be de/serialized under `BatchReceive()` variant in a ExecuteMsg -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] #[serde(rename_all = "snake_case")] pub struct Cw1155BatchReceiveMsg { pub operator: String, @@ -66,7 +66,7 @@ impl Cw1155BatchReceiveMsg { } // This is just a helper to properly serialize the above message -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] #[serde(rename_all = "snake_case")] enum ReceiverExecuteMsg { Receive(Cw1155ReceiveMsg), From 68960bb4640132885c29b7520b73b5f10e887288 Mon Sep 17 00:00:00 2001 From: shab Date: Tue, 7 May 2024 14:18:52 -0400 Subject: [PATCH 30/57] fix: circle ci lint --- contracts/cw1155-base/src/execute.rs | 30 ++++++++++++++-------------- contracts/cw1155-base/src/state.rs | 6 +++--- packages/cw1155/src/msg.rs | 9 +++------ packages/cw1155/src/query.rs | 21 ++++++++++--------- packages/cw1155/src/receiver.rs | 13 ++++-------- 5 files changed, 35 insertions(+), 44 deletions(-) diff --git a/contracts/cw1155-base/src/execute.rs b/contracts/cw1155-base/src/execute.rs index 72ed2ffab..e566910ba 100644 --- a/contracts/cw1155-base/src/execute.rs +++ b/contracts/cw1155-base/src/execute.rs @@ -213,7 +213,7 @@ where info, } = env; - let from = &if let Some(from) = from { + let from = if let Some(from) = from { deps.api.addr_validate(&from)? } else { info.sender.clone() @@ -221,7 +221,7 @@ where let to = deps.api.addr_validate(&to)?; let balance_update = - self.verify_approval(deps.storage, &env, &info, from, &token_id, amount)?; + self.verify_approval(deps.storage, &env, &info, &from, &token_id, amount)?; let mut rsp = Response::default(); @@ -267,14 +267,14 @@ where info, } = env; - let from = &if let Some(from) = from { + let from = if let Some(from) = from { deps.api.addr_validate(&from)? } else { info.sender.clone() }; let to = deps.api.addr_validate(&to)?; - let batch = self.verify_approvals(deps.storage, &env, &info, from, batch)?; + let batch = self.verify_approvals(deps.storage, &env, &info, &from, batch)?; let mut rsp = Response::default(); let event = self.update_balances( @@ -314,7 +314,7 @@ where env, } = env; - let from = &if let Some(from) = from { + let from = if let Some(from) = from { deps.api.addr_validate(&from)? } else { info.sender.clone() @@ -322,17 +322,17 @@ where // whoever can transfer these tokens can burn let balance_update = - self.verify_approval(deps.storage, &env, &info, from, &token_id, amount)?; + self.verify_approval(deps.storage, &env, &info, &from, &token_id, amount)?; let mut rsp = Response::default(); let event = self.update_balances( &mut deps, &env, - Some(from.clone()), + Some(from), None, vec![TokenAmount { - token_id: token_id.to_string(), + token_id, amount: balance_update.amount, }], )?; @@ -353,16 +353,16 @@ where env, } = env; - let from = &if let Some(from) = from { + let from = if let Some(from) = from { deps.api.addr_validate(&from)? } else { info.sender.clone() }; - let batch = self.verify_approvals(deps.storage, &env, &info, from, batch)?; + let batch = self.verify_approvals(deps.storage, &env, &info, &from, batch)?; let mut rsp = Response::default(); - let event = self.update_balances(&mut deps, &env, Some(from.clone()), None, batch)?; + let event = self.update_balances(&mut deps, &env, Some(from), None, batch)?; rsp = rsp.add_event(event); Ok(rsp) @@ -549,17 +549,17 @@ where // remove token approvals for (operator, approval) in self .token_approves - .prefix((&token_id, from)) + .prefix((token_id, from)) .range(deps.storage, None, None, Order::Ascending) .collect::>>()? { if approval.is_expired(env) || approval.amount <= *amount { self.token_approves - .remove(deps.storage, (&token_id, &from, &operator)); + .remove(deps.storage, (token_id, from, &operator)); } else { self.token_approves.update( deps.storage, - (&token_id, &from, &operator), + (token_id, from, &operator), |prev| -> StdResult<_> { let mut new_approval = prev.unwrap(); new_approval.amount = new_approval.amount.checked_sub(*amount)?; @@ -671,7 +671,7 @@ where ) -> Option { match self .token_approves - .load(storage, (&token_id, owner, operator)) + .load(storage, (token_id, owner, operator)) { Ok(approval) => { if !approval.is_expired(env) { diff --git a/contracts/cw1155-base/src/state.rs b/contracts/cw1155-base/src/state.rs index 0a678ce2f..049c386dc 100644 --- a/contracts/cw1155-base/src/state.rs +++ b/contracts/cw1155-base/src/state.rs @@ -1,6 +1,6 @@ -use schemars::JsonSchema; +use cosmwasm_schema::cw_serde; use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use serde::Serialize; use std::marker::PhantomData; use cosmwasm_std::{Addr, CustomMsg, StdError, StdResult, Storage, Uint128}; @@ -128,7 +128,7 @@ where } } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[cw_serde] pub struct TokenInfo { /// Metadata JSON Schema pub token_uri: Option, diff --git a/packages/cw1155/src/msg.rs b/packages/cw1155/src/msg.rs index 0759195f8..df4d7771b 100644 --- a/packages/cw1155/src/msg.rs +++ b/packages/cw1155/src/msg.rs @@ -1,12 +1,10 @@ use cosmwasm_schema::cw_serde; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; use cosmwasm_std::{Binary, Env, Uint128}; use cw_utils::Expiration; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[cw_serde] pub struct Cw1155InstantiateMsg { /// Name of the token contract pub name: String, @@ -23,8 +21,7 @@ pub struct Cw1155InstantiateMsg { /// This is like Cw1155ExecuteMsg but we add a Mint command for a minter /// to make this stand-alone. You will likely want to remove mint and /// use other control logic in any contract that inherits this. -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub enum Cw1155ExecuteMsg { // cw1155 /// BatchSendFrom is a base message to move multiple types of tokens in batch, @@ -101,7 +98,7 @@ pub enum Cw1155ExecuteMsg { }, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[cw_serde] pub struct Cw1155MintMsg { pub token_id: String, /// The amount of the newly minted tokens diff --git a/packages/cw1155/src/query.rs b/packages/cw1155/src/query.rs index 7578f18fd..eea049112 100644 --- a/packages/cw1155/src/query.rs +++ b/packages/cw1155/src/query.rs @@ -1,6 +1,5 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; use cosmwasm_std::{Addr, Uint128}; use cw_ownable::cw_ownable_query; @@ -93,34 +92,34 @@ pub enum Cw1155QueryMsg { Extension { msg: Q }, } -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[cw_serde] pub struct BalanceResponse { pub balance: Uint128, } -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[cw_serde] pub struct AllBalancesResponse { pub balances: Vec, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[cw_serde] pub struct Balance { pub token_id: String, pub owner: Addr, pub amount: Uint128, } -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[cw_serde] pub struct BatchBalanceResponse { pub balances: Vec, } -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[cw_serde] pub struct NumTokensResponse { pub count: Uint128, } -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[cw_serde] pub struct Approval { /// Account that can transfer/send the token pub spender: String, @@ -128,12 +127,12 @@ pub struct Approval { pub expires: Expiration, } -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[cw_serde] pub struct ApprovedForAllResponse { pub operators: Vec, } -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[cw_serde] pub struct IsApprovedForAllResponse { pub approved: bool, } @@ -144,7 +143,7 @@ pub struct AllTokenInfoResponse { pub info: TokenInfoResponse, } -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[cw_serde] pub struct TokenInfoResponse { /// Should be a url point to a json file pub token_uri: Option, @@ -152,7 +151,7 @@ pub struct TokenInfoResponse { pub extension: T, } -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +#[cw_serde] pub struct TokensResponse { /// Contains all token_ids in lexicographical ordering /// If there are more than `limit`, use `start_from` in future queries diff --git a/packages/cw1155/src/receiver.rs b/packages/cw1155/src/receiver.rs index e5d0171ff..2d49c8cdb 100644 --- a/packages/cw1155/src/receiver.rs +++ b/packages/cw1155/src/receiver.rs @@ -1,12 +1,9 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - use crate::TokenAmount; +use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_json_binary, Binary, CosmosMsg, StdResult, Uint128, WasmMsg}; /// Cw1155ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct Cw1155ReceiveMsg { /// The account that executed the send message pub operator: String, @@ -37,8 +34,7 @@ impl Cw1155ReceiveMsg { } /// Cw1155BatchReceiveMsg should be de/serialized under `BatchReceive()` variant in a ExecuteMsg -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] -#[serde(rename_all = "snake_case")] +#[cw_serde] pub struct Cw1155BatchReceiveMsg { pub operator: String, pub from: Option, @@ -66,8 +62,7 @@ impl Cw1155BatchReceiveMsg { } // This is just a helper to properly serialize the above message -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)] -#[serde(rename_all = "snake_case")] +#[cw_serde] enum ReceiverExecuteMsg { Receive(Cw1155ReceiveMsg), BatchReceive(Cw1155BatchReceiveMsg), From 85e5d47e1db0c0f07693f5aabc2aab0a0d8c891a Mon Sep 17 00:00:00 2001 From: shab Date: Tue, 7 May 2024 14:47:16 -0400 Subject: [PATCH 31/57] fix: move cosmwasm-schema to deps in metadata extension --- contracts/cw1155-metadata-onchain/Cargo.toml | 3 +-- contracts/cw1155-metadata-onchain/src/lib.rs | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/contracts/cw1155-metadata-onchain/Cargo.toml b/contracts/cw1155-metadata-onchain/Cargo.toml index 9a028bc64..497297ceb 100644 --- a/contracts/cw1155-metadata-onchain/Cargo.toml +++ b/contracts/cw1155-metadata-onchain/Cargo.toml @@ -32,6 +32,5 @@ cosmwasm-std = { workspace = true } schemars = { workspace = true } serde = { workspace = true } thiserror = { workspace = true } - -[dev-dependencies] cosmwasm-schema = { workspace = true } + diff --git a/contracts/cw1155-metadata-onchain/src/lib.rs b/contracts/cw1155-metadata-onchain/src/lib.rs index 2adfc5372..f6eef82eb 100644 --- a/contracts/cw1155-metadata-onchain/src/lib.rs +++ b/contracts/cw1155-metadata-onchain/src/lib.rs @@ -1,6 +1,5 @@ +use cosmwasm_schema::cw_serde; use cosmwasm_std::Empty; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; use cw1155::Cw1155ExecuteMsg; pub use cw1155::{Cw1155ContractError, Cw1155InstantiateMsg, Cw1155MintMsg}; @@ -10,7 +9,7 @@ use cw2::set_contract_version; const CONTRACT_NAME: &str = "crates.io:cw1155-metadata-onchain"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug, Default)] +#[cw_serde] pub struct Trait { pub display_type: Option, pub trait_type: String, @@ -18,7 +17,8 @@ pub struct Trait { } // see: https://docs.opensea.io/docs/metadata-standards -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug, Default)] +#[cw_serde] +#[derive(Default)] pub struct Metadata { pub image: Option, pub image_data: Option, From 6f5e9f51462862d26abf9e87b85782b83f527754 Mon Sep 17 00:00:00 2001 From: shab Date: Tue, 7 May 2024 15:28:46 -0400 Subject: [PATCH 32/57] fix: clippy --- Cargo.lock | 8 ++++---- contracts/cw1155-base/Cargo.toml | 12 ++++++------ contracts/cw1155-base/src/contract_tests.rs | 12 ++++++------ contracts/cw1155-metadata-onchain/Cargo.toml | 12 ++++++------ contracts/cw1155-royalties/Cargo.toml | 19 +++++++++++-------- contracts/cw2981-royalties/src/lib.rs | 1 + packages/cw1155/Cargo.toml | 12 ++++++------ 7 files changed, 40 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 725a0f2ec..3e791817b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -313,7 +313,7 @@ dependencies = [ [[package]] name = "cw1155" -version = "0.13.4" +version = "0.18.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -328,7 +328,7 @@ dependencies = [ [[package]] name = "cw1155-base" -version = "0.13.4" +version = "0.18.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -345,7 +345,7 @@ dependencies = [ [[package]] name = "cw1155-metadata-onchain" -version = "0.13.4" +version = "0.18.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -359,7 +359,7 @@ dependencies = [ [[package]] name = "cw1155-royalties" -version = "0.13.4" +version = "0.18.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", diff --git a/contracts/cw1155-base/Cargo.toml b/contracts/cw1155-base/Cargo.toml index d64e5a22e..cd8a63d2c 100644 --- a/contracts/cw1155-base/Cargo.toml +++ b/contracts/cw1155-base/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "cw1155-base" -version = "0.13.4" authors = ["shab "] -edition = "2018" description = "Basic implementation cw1155" -license = "Apache-2.0" -repository = "https://github.com/CosmWasm/cw-nfts" -homepage = "https://cosmwasm.com" -documentation = "https://docs.cosmwasm.com" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } exclude = [ # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. diff --git a/contracts/cw1155-base/src/contract_tests.rs b/contracts/cw1155-base/src/contract_tests.rs index 8c7559ac9..d30048091 100644 --- a/contracts/cw1155-base/src/contract_tests.rs +++ b/contracts/cw1155-base/src/contract_tests.rs @@ -758,7 +758,7 @@ mod tests { contract .execute( deps.as_mut(), - env.clone(), + env, mock_info(user1.as_ref(), &[]), Cw1155BaseExecuteMsg::ApproveAll { operator: user2.clone(), @@ -799,7 +799,7 @@ mod tests { deps.as_ref(), env, Cw1155QueryMsg::ApprovalsForAll { - owner: user1.to_string(), + owner: user1, include_expired: None, start_after: None, limit: None, @@ -863,7 +863,7 @@ mod tests { Cw1155BaseExecuteMsg::Mint { recipient: user1.clone(), msg: Cw1155MintMsg { - token_id: token1.clone(), + token_id: token1, amount: 1u128.into(), token_uri: None, extension: None, @@ -882,12 +882,12 @@ mod tests { // minting one more of a different token id should fail let res = contract.execute( deps.as_mut(), - env.clone(), + env, mock_info(minter.as_ref(), &[]), Cw1155BaseExecuteMsg::Mint { - recipient: user1.clone(), + recipient: user1, msg: Cw1155MintMsg { - token_id: token2.clone(), + token_id: token2, amount: 1u128.into(), token_uri: None, extension: None, diff --git a/contracts/cw1155-metadata-onchain/Cargo.toml b/contracts/cw1155-metadata-onchain/Cargo.toml index 497297ceb..b31f6901b 100644 --- a/contracts/cw1155-metadata-onchain/Cargo.toml +++ b/contracts/cw1155-metadata-onchain/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "cw1155-metadata-onchain" -version = "0.13.4" authors = ["shab "] -edition = "2018" description = "Example extending CW1155 Token to store metadata on chain" -license = "Apache-2.0" -repository = "https://github.com/CosmWasm/cw-nfts" -homepage = "https://cosmwasm.com" -documentation = "https://docs.cosmwasm.com" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } exclude = [ # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. diff --git a/contracts/cw1155-royalties/Cargo.toml b/contracts/cw1155-royalties/Cargo.toml index 55ce1561c..f39e955af 100644 --- a/contracts/cw1155-royalties/Cargo.toml +++ b/contracts/cw1155-royalties/Cargo.toml @@ -1,13 +1,16 @@ [package] name = "cw1155-royalties" -version = "0.13.4" -authors = ["shab "] -edition = "2018" -description = "Example extending CW1155 Token to store royalties on chain" -license = "Apache-2.0" -repository = "https://github.com/CosmWasm/cw-nfts" -homepage = "https://cosmwasm.com" -documentation = "https://docs.cosmwasm.com" +authors = [ + "Alex Lynham ", + "shab " +] +description = "Basic implementation of royalties for cw1155 with token level royalties" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } exclude = [ # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. diff --git a/contracts/cw2981-royalties/src/lib.rs b/contracts/cw2981-royalties/src/lib.rs index 7c49e0be0..42edc0caf 100644 --- a/contracts/cw2981-royalties/src/lib.rs +++ b/contracts/cw2981-royalties/src/lib.rs @@ -118,6 +118,7 @@ pub mod entry { } #[cfg(test)] +#[cfg(not(feature = "library"))] mod tests { use super::*; use crate::msg::{CheckRoyaltiesResponse, RoyaltiesInfoResponse}; diff --git a/packages/cw1155/Cargo.toml b/packages/cw1155/Cargo.toml index 1d849707b..a31669731 100644 --- a/packages/cw1155/Cargo.toml +++ b/packages/cw1155/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "cw1155" -version = "0.13.4" authors = ["shab "] -edition = "2018" description = "Definition and types for the CosmWasm-1155 interface" -license = "Apache-2.0" -repository = "https://github.com/CosmWasm/cw-nfts" -homepage = "https://cosmwasm.com" -documentation = "https://docs.cosmwasm.com" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } [dependencies] cw2 = { workspace = true } From 4c7600e779871aa932fc3b90f793fbb3100496d3 Mon Sep 17 00:00:00 2001 From: shab Date: Wed, 22 May 2024 18:09:29 -0400 Subject: [PATCH 33/57] adds custom execute extension and response generics to Cw1155Contract (copied from cw721) --- contracts/cw1155-base/src/execute.rs | 11 ++++++++--- contracts/cw1155-base/src/lib.rs | 10 +++++----- contracts/cw1155-base/src/query.rs | 8 ++++++-- contracts/cw1155-base/src/state.rs | 15 ++++++++++++--- contracts/cw1155-metadata-onchain/src/lib.rs | 5 +++-- contracts/cw1155-royalties/src/lib.rs | 7 ++++--- packages/cw1155/src/msg.rs | 5 ++++- 7 files changed, 42 insertions(+), 19 deletions(-) diff --git a/contracts/cw1155-base/src/execute.rs b/contracts/cw1155-base/src/execute.rs index e566910ba..5602d589d 100644 --- a/contracts/cw1155-base/src/execute.rs +++ b/contracts/cw1155-base/src/execute.rs @@ -13,9 +13,11 @@ use cw2::set_contract_version; use serde::de::DeserializeOwned; use serde::Serialize; -impl<'a, T, Q> Cw1155Contract<'a, T, Q> +impl<'a, T, C, E, Q> Cw1155Contract<'a, T, C, E, Q> where T: Serialize + DeserializeOwned + Clone, + C: CustomMsg, + E: CustomMsg, Q: CustomMsg, { pub fn instantiate( @@ -53,7 +55,7 @@ where deps: DepsMut, env: Env, info: MessageInfo, - msg: Cw1155ExecuteMsg, + msg: Cw1155ExecuteMsg, ) -> Result { let env = ExecuteEnv { deps, env, info }; match msg { @@ -98,6 +100,7 @@ where token_id, amount, } => self.revoke_token(env, spender, token_id, amount), + Cw1155ExecuteMsg::Extension { .. } => unimplemented!(), } } } @@ -110,9 +113,11 @@ pub struct ExecuteEnv<'a> { } // helper -impl<'a, T, Q> Cw1155Contract<'a, T, Q> +impl<'a, T, C, E, Q> Cw1155Contract<'a, T, C, E, Q> where T: Serialize + DeserializeOwned + Clone, + C: CustomMsg, + E: CustomMsg, Q: CustomMsg, { pub fn mint( diff --git a/contracts/cw1155-base/src/lib.rs b/contracts/cw1155-base/src/lib.rs index 1b2968b04..6561ef65b 100644 --- a/contracts/cw1155-base/src/lib.rs +++ b/contracts/cw1155-base/src/lib.rs @@ -14,10 +14,10 @@ pub const EXPECTED_FROM_VERSION: &str = CONTRACT_VERSION; // This is a simple type to let us handle empty extensions pub type Extension = Option; -pub type Cw1155BaseExecuteMsg = Cw1155ExecuteMsg; +pub type Cw1155BaseExecuteMsg = Cw1155ExecuteMsg; pub type Cw1155BaseQueryMsg = Cw1155QueryMsg; -pub type Cw1155BaseContract<'a> = Cw1155Contract<'a, Extension, Empty>; +pub type Cw1155BaseContract<'a> = Cw1155Contract<'a, Extension, Empty, Empty, Empty>; pub mod entry { use super::*; @@ -25,7 +25,7 @@ pub mod entry { #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; - use cw1155::{Cw1155ContractError, Cw1155ExecuteMsg, Cw1155InstantiateMsg}; + use cw1155::{Cw1155ContractError, Cw1155InstantiateMsg}; // This makes a conscious choice on the various generics used by the contract #[cfg_attr(not(feature = "library"), entry_point)] @@ -46,14 +46,14 @@ pub mod entry { deps: DepsMut, env: Env, info: MessageInfo, - msg: Cw1155ExecuteMsg, + msg: Cw1155BaseExecuteMsg, ) -> Result { let tract = Cw1155BaseContract::default(); tract.execute(deps, env, info, msg) } #[cfg_attr(not(feature = "library"), entry_point)] - pub fn query(deps: Deps, env: Env, msg: Cw1155QueryMsg) -> StdResult { + pub fn query(deps: Deps, env: Env, msg: Cw1155BaseQueryMsg) -> StdResult { let tract = Cw1155BaseContract::default(); tract.query(deps, env, msg) } diff --git a/contracts/cw1155-base/src/query.rs b/contracts/cw1155-base/src/query.rs index c04adac54..ac02d9504 100644 --- a/contracts/cw1155-base/src/query.rs +++ b/contracts/cw1155-base/src/query.rs @@ -19,9 +19,11 @@ use crate::state::{Cw1155Contract, TokenInfo}; const DEFAULT_LIMIT: u32 = 10; const MAX_LIMIT: u32 = 100; -impl<'a, T, Q> Cw1155Contract<'a, T, Q> +impl<'a, T, C, E, Q> Cw1155Contract<'a, T, C, E, Q> where T: Serialize + DeserializeOwned + Clone, + C: CustomMsg, + E: CustomMsg, Q: CustomMsg, { pub fn query(&self, deps: Deps, env: Env, msg: Cw1155QueryMsg) -> StdResult { @@ -155,9 +157,11 @@ where } } -impl<'a, T, Q> Cw1155Contract<'a, T, Q> +impl<'a, T, C, E, Q> Cw1155Contract<'a, T, C, E, Q> where T: Serialize + DeserializeOwned + Clone, + C: CustomMsg, + E: CustomMsg, Q: CustomMsg, { fn query_all_approvals( diff --git a/contracts/cw1155-base/src/state.rs b/contracts/cw1155-base/src/state.rs index 049c386dc..fca601eae 100644 --- a/contracts/cw1155-base/src/state.rs +++ b/contracts/cw1155-base/src/state.rs @@ -9,9 +9,10 @@ use cw1155::{Balance, Expiration, TokenApproval}; use cw721::ContractInfoResponse; use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, MultiIndex}; -pub struct Cw1155Contract<'a, T, Q> +pub struct Cw1155Contract<'a, T, C, E, Q> where T: Serialize + DeserializeOwned + Clone, + E: CustomMsg, Q: CustomMsg, { pub contract_info: Item<'a, ContractInfoResponse>, @@ -27,12 +28,16 @@ where // key: token id pub tokens: Map<'a, &'a str, TokenInfo>, + pub(crate) _custom_response: PhantomData, pub(crate) _custom_query: PhantomData, + pub(crate) _custom_execute: PhantomData, } -impl<'a, T, Q> Default for Cw1155Contract<'a, T, Q> +impl<'a, T, C, E, Q> Default for Cw1155Contract<'a, T, C, E, Q> where T: Serialize + DeserializeOwned + Clone, + C: CustomMsg, + E: CustomMsg, Q: CustomMsg, { fn default() -> Self { @@ -49,9 +54,11 @@ where } } -impl<'a, T, Q> Cw1155Contract<'a, T, Q> +impl<'a, T, C, E, Q> Cw1155Contract<'a, T, C, E, Q> where T: Serialize + DeserializeOwned + Clone, + C: CustomMsg, + E: CustomMsg, Q: CustomMsg, { #[allow(clippy::too_many_arguments)] @@ -80,7 +87,9 @@ where balances: IndexedMap::new(balances_key, balances_indexes), approves: Map::new(approves_key), token_approves: Map::new(token_approves_key), + _custom_execute: PhantomData, _custom_query: PhantomData, + _custom_response: PhantomData, } } diff --git a/contracts/cw1155-metadata-onchain/src/lib.rs b/contracts/cw1155-metadata-onchain/src/lib.rs index f6eef82eb..7faa47104 100644 --- a/contracts/cw1155-metadata-onchain/src/lib.rs +++ b/contracts/cw1155-metadata-onchain/src/lib.rs @@ -33,8 +33,9 @@ pub struct Metadata { pub type Extension = Option; -pub type Cw1155MetadataContract<'a> = cw1155_base::Cw1155Contract<'a, Extension, Empty>; -pub type Cw1155MetadataExecuteMsg = Cw1155ExecuteMsg; +pub type Cw1155MetadataContract<'a> = + cw1155_base::Cw1155Contract<'a, Extension, Empty, Empty, Empty>; +pub type Cw1155MetadataExecuteMsg = Cw1155ExecuteMsg; #[cfg(not(feature = "library"))] pub mod entry { diff --git a/contracts/cw1155-royalties/src/lib.rs b/contracts/cw1155-royalties/src/lib.rs index a92616f2c..1553c97c3 100644 --- a/contracts/cw1155-royalties/src/lib.rs +++ b/contracts/cw1155-royalties/src/lib.rs @@ -1,3 +1,4 @@ +use cosmwasm_std::Empty; use cw1155::{Cw1155ExecuteMsg, Cw1155QueryMsg}; pub use cw1155::{Cw1155InstantiateMsg, Cw1155MintMsg}; use cw1155_base::Cw1155Contract; @@ -15,8 +16,8 @@ pub use error::Cw1155RoyaltiesContractError; const CONTRACT_NAME: &str = "crates.io:cw1155-royalties"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); -pub type Cw1155RoyaltiesContract<'a> = Cw1155Contract<'a, Extension, Cw2981QueryMsg>; -pub type Cw1155RoyaltiesExecuteMsg = Cw1155ExecuteMsg; +pub type Cw1155RoyaltiesContract<'a> = Cw1155Contract<'a, Extension, Empty, Empty, Cw2981QueryMsg>; +pub type Cw1155RoyaltiesExecuteMsg = Cw1155ExecuteMsg; pub type Cw1155RoyaltiesQueryMsg = Cw1155QueryMsg; #[cfg(not(feature = "library"))] @@ -83,7 +84,7 @@ pub mod entry { #[entry_point] pub fn query(deps: Deps, env: Env, msg: Cw1155RoyaltiesQueryMsg) -> StdResult { match msg { - Cw1155RoyaltiesQueryMsg::Extension { msg } => match msg { + Cw1155RoyaltiesQueryMsg::Extension { msg: ext_msg } => match ext_msg { Cw2981QueryMsg::RoyaltyInfo { token_id, sale_price, diff --git a/packages/cw1155/src/msg.rs b/packages/cw1155/src/msg.rs index df4d7771b..0b74bf143 100644 --- a/packages/cw1155/src/msg.rs +++ b/packages/cw1155/src/msg.rs @@ -22,7 +22,7 @@ pub struct Cw1155InstantiateMsg { /// to make this stand-alone. You will likely want to remove mint and /// use other control logic in any contract that inherits this. #[cw_serde] -pub enum Cw1155ExecuteMsg { +pub enum Cw1155ExecuteMsg { // cw1155 /// BatchSendFrom is a base message to move multiple types of tokens in batch, /// if `env.sender` is the owner or has sufficient pre-approval. @@ -96,6 +96,9 @@ pub enum Cw1155ExecuteMsg { /// Optional amount to revoke. If None, revoke entire amount. amount: Option, }, + + /// Extension msg + Extension { msg: E }, } #[cw_serde] From c00a913254b822a6f32669196ace8e1378c97ad1 Mon Sep 17 00:00:00 2001 From: shab Date: Thu, 23 May 2024 14:28:26 -0400 Subject: [PATCH 34/57] impl cw_ownable_execute on cw1155. fix Response generics. --- contracts/cw1155-base/src/execute.rs | 39 ++++++++++++++++++---------- packages/cw1155/src/msg.rs | 2 ++ packages/cw1155/src/receiver.rs | 11 ++++++-- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/contracts/cw1155-base/src/execute.rs b/contracts/cw1155-base/src/execute.rs index 5602d589d..4cbaafff0 100644 --- a/contracts/cw1155-base/src/execute.rs +++ b/contracts/cw1155-base/src/execute.rs @@ -26,7 +26,7 @@ where _env: Env, info: MessageInfo, msg: Cw1155InstantiateMsg, - ) -> StdResult { + ) -> StdResult> { // store contract version set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; @@ -56,7 +56,7 @@ where env: Env, info: MessageInfo, msg: Cw1155ExecuteMsg, - ) -> Result { + ) -> Result, Cw1155ContractError> { let env = ExecuteEnv { deps, env, info }; match msg { // cw1155 @@ -100,6 +100,8 @@ where token_id, amount, } => self.revoke_token(env, spender, token_id, amount), + Cw1155ExecuteMsg::UpdateOwnership(action) => Self::update_ownership(env, action), + Cw1155ExecuteMsg::Extension { .. } => unimplemented!(), } } @@ -125,7 +127,7 @@ where env: ExecuteEnv, recipient: String, msg: Cw1155MintMsg, - ) -> Result { + ) -> Result, Cw1155ContractError> { let ExecuteEnv { mut deps, info, @@ -167,7 +169,7 @@ where env: ExecuteEnv, recipient: String, msgs: Vec>, - ) -> Result { + ) -> Result, Cw1155ContractError> { let ExecuteEnv { mut deps, info, @@ -211,7 +213,7 @@ where token_id: String, amount: Uint128, msg: Option, - ) -> Result { + ) -> Result, Cw1155ContractError> { let ExecuteEnv { mut deps, env, @@ -228,7 +230,7 @@ where let balance_update = self.verify_approval(deps.storage, &env, &info, &from, &token_id, amount)?; - let mut rsp = Response::default(); + let mut rsp = Response::::default(); let event = self.update_balances( &mut deps, @@ -265,7 +267,7 @@ where to: String, batch: Vec, msg: Option, - ) -> Result { + ) -> Result, Cw1155ContractError> { let ExecuteEnv { mut deps, env, @@ -281,7 +283,7 @@ where let batch = self.verify_approvals(deps.storage, &env, &info, &from, batch)?; - let mut rsp = Response::default(); + let mut rsp = Response::::default(); let event = self.update_balances( &mut deps, &env, @@ -312,7 +314,7 @@ where from: Option, token_id: String, amount: Uint128, - ) -> Result { + ) -> Result, Cw1155ContractError> { let ExecuteEnv { mut deps, info, @@ -351,7 +353,7 @@ where env: ExecuteEnv, from: Option, batch: Vec, - ) -> Result { + ) -> Result, Cw1155ContractError> { let ExecuteEnv { mut deps, info, @@ -380,7 +382,7 @@ where token_id: String, amount: Option, expiration: Option, - ) -> Result { + ) -> Result, Cw1155ContractError> { let ExecuteEnv { deps, info, env } = env; // reject expired data as invalid @@ -419,7 +421,7 @@ where env: ExecuteEnv, operator: String, expires: Option, - ) -> Result { + ) -> Result, Cw1155ContractError> { let ExecuteEnv { deps, info, env } = env; // reject expired data as invalid @@ -447,7 +449,7 @@ where operator: String, token_id: String, amount: Option, - ) -> Result { + ) -> Result, Cw1155ContractError> { let ExecuteEnv { deps, info, .. } = env; let operator = deps.api.addr_validate(&operator)?; @@ -485,7 +487,7 @@ where &self, env: ExecuteEnv, operator: String, - ) -> Result { + ) -> Result, Cw1155ContractError> { let ExecuteEnv { deps, info, .. } = env; let operator_addr = deps.api.addr_validate(&operator)?; self.approves @@ -688,4 +690,13 @@ where Err(_) => None, } } + + pub fn update_ownership( + env: ExecuteEnv, + action: cw_ownable::Action, + ) -> Result, Cw1155ContractError> { + let ExecuteEnv { deps, info, env } = env; + let ownership = cw_ownable::update_ownership(deps, &env.block, &info.sender, action)?; + Ok(Response::new().add_attributes(ownership.into_attributes())) + } } diff --git a/packages/cw1155/src/msg.rs b/packages/cw1155/src/msg.rs index 0b74bf143..5d99011ce 100644 --- a/packages/cw1155/src/msg.rs +++ b/packages/cw1155/src/msg.rs @@ -2,6 +2,7 @@ use cosmwasm_schema::cw_serde; use std::fmt::{Display, Formatter}; use cosmwasm_std::{Binary, Env, Uint128}; +use cw_ownable::cw_ownable_execute; use cw_utils::Expiration; #[cw_serde] @@ -21,6 +22,7 @@ pub struct Cw1155InstantiateMsg { /// This is like Cw1155ExecuteMsg but we add a Mint command for a minter /// to make this stand-alone. You will likely want to remove mint and /// use other control logic in any contract that inherits this. +#[cw_ownable_execute] #[cw_serde] pub enum Cw1155ExecuteMsg { // cw1155 diff --git a/packages/cw1155/src/receiver.rs b/packages/cw1155/src/receiver.rs index 2d49c8cdb..76ac8363b 100644 --- a/packages/cw1155/src/receiver.rs +++ b/packages/cw1155/src/receiver.rs @@ -1,6 +1,7 @@ use crate::TokenAmount; use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_json_binary, Binary, CosmosMsg, StdResult, Uint128, WasmMsg}; +use schemars::JsonSchema; /// Cw1155ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg #[cw_serde] @@ -22,7 +23,10 @@ impl Cw1155ReceiveMsg { } /// creates a cosmos_msg sending this struct to the named contract - pub fn into_cosmos_msg>(self, contract_addr: T) -> StdResult { + pub fn into_cosmos_msg, C>(self, contract_addr: T) -> StdResult> + where + C: Clone + std::fmt::Debug + PartialEq + JsonSchema, + { let msg = self.into_binary()?; let execute = WasmMsg::Execute { contract_addr: contract_addr.into(), @@ -50,7 +54,10 @@ impl Cw1155BatchReceiveMsg { } /// creates a cosmos_msg sending this struct to the named contract - pub fn into_cosmos_msg>(self, contract_addr: T) -> StdResult { + pub fn into_cosmos_msg, C>(self, contract_addr: T) -> StdResult> + where + C: Clone + std::fmt::Debug + PartialEq + JsonSchema, + { let msg = self.into_binary()?; let execute = WasmMsg::Execute { contract_addr: contract_addr.into(), From 1d3b795aab31f75d3a0f4a9a036b484f20f9f0b7 Mon Sep 17 00:00:00 2001 From: shab Date: Thu, 23 May 2024 14:36:53 -0400 Subject: [PATCH 35/57] fix: cargo schema --- contracts/cw1155-base/examples/schema.rs | 9 ++++----- contracts/cw1155-metadata-onchain/examples/schema.rs | 6 +++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/contracts/cw1155-base/examples/schema.rs b/contracts/cw1155-base/examples/schema.rs index 477f1323d..5df5cc3ce 100644 --- a/contracts/cw1155-base/examples/schema.rs +++ b/contracts/cw1155-base/examples/schema.rs @@ -1,13 +1,12 @@ use cosmwasm_schema::write_api; -use cosmwasm_std::Empty; -use cw1155::{Cw1155ExecuteMsg, Cw1155InstantiateMsg, Cw1155QueryMsg}; -use cw1155_base::Extension; +use cw1155::Cw1155InstantiateMsg; +use cw1155_base::{Cw1155BaseExecuteMsg, Cw1155BaseQueryMsg}; fn main() { write_api! { instantiate: Cw1155InstantiateMsg, - execute: Cw1155ExecuteMsg, - query: Cw1155QueryMsg, + execute: Cw1155BaseExecuteMsg, + query: Cw1155BaseQueryMsg, } } diff --git a/contracts/cw1155-metadata-onchain/examples/schema.rs b/contracts/cw1155-metadata-onchain/examples/schema.rs index 445695e6c..5f0fa8ddd 100644 --- a/contracts/cw1155-metadata-onchain/examples/schema.rs +++ b/contracts/cw1155-metadata-onchain/examples/schema.rs @@ -1,13 +1,13 @@ use cosmwasm_schema::write_api; use cosmwasm_std::Empty; -use cw1155::{Cw1155ExecuteMsg, Cw1155InstantiateMsg, Cw1155QueryMsg}; -use cw1155_metadata_onchain::Extension; +use cw1155::{Cw1155InstantiateMsg, Cw1155QueryMsg}; +use cw1155_metadata_onchain::Cw1155MetadataExecuteMsg; fn main() { write_api! { instantiate: Cw1155InstantiateMsg, - execute: Cw1155ExecuteMsg, + execute: Cw1155MetadataExecuteMsg, query: Cw1155QueryMsg, } } From 32c6e419b2ecfa05f50c4b0c72970cc5d1d6a96d Mon Sep 17 00:00:00 2001 From: shab Date: Thu, 23 May 2024 14:56:32 -0400 Subject: [PATCH 36/57] fix: cargo schema package --- packages/cw1155/examples/schema.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cw1155/examples/schema.rs b/packages/cw1155/examples/schema.rs index b44ea3de9..f29cfdbe1 100644 --- a/packages/cw1155/examples/schema.rs +++ b/packages/cw1155/examples/schema.rs @@ -3,12 +3,12 @@ use cosmwasm_std::Empty; use cw1155::{Cw1155ExecuteMsg, Cw1155InstantiateMsg, Cw1155QueryMsg}; -type Extension = Empty; +type Extension = Option; fn main() { write_api! { instantiate: Cw1155InstantiateMsg, - execute: Cw1155ExecuteMsg, + execute: Cw1155ExecuteMsg, query: Cw1155QueryMsg, } } From cfa0c084023c3cef6d9be4ae510a6ae77c963aaf Mon Sep 17 00:00:00 2001 From: shab Date: Thu, 23 May 2024 18:56:57 -0400 Subject: [PATCH 37/57] chore: merge queries num_tokens/supply --- contracts/cw1155-base/src/contract_tests.rs | 4 ++-- contracts/cw1155-base/src/query.rs | 14 +++++++------- packages/cw1155/src/query.rs | 7 +++---- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/contracts/cw1155-base/src/contract_tests.rs b/contracts/cw1155-base/src/contract_tests.rs index d30048091..7b41c22c3 100644 --- a/contracts/cw1155-base/src/contract_tests.rs +++ b/contracts/cw1155-base/src/contract_tests.rs @@ -539,7 +539,7 @@ mod tests { deps.as_ref(), mock_env(), Cw1155QueryMsg::NumTokens { - token_id: tokens[0].clone(), + token_id: Some(tokens[0].clone()), }, ), to_json_binary(&NumTokensResponse { @@ -552,7 +552,7 @@ mod tests { deps.as_ref(), mock_env(), Cw1155QueryMsg::NumTokens { - token_id: tokens[0].clone(), + token_id: Some(tokens[0].clone()), }, ), to_json_binary(&NumTokensResponse { diff --git a/contracts/cw1155-base/src/query.rs b/contracts/cw1155-base/src/query.rs index ac02d9504..7a0d6fc40 100644 --- a/contracts/cw1155-base/src/query.rs +++ b/contracts/cw1155-base/src/query.rs @@ -69,10 +69,6 @@ where .collect::>()?; to_json_binary(&BatchBalanceResponse { balances }) } - Cw1155QueryMsg::NumTokens { token_id } => { - let count = self.token_count(deps.storage, &token_id)?; - to_json_binary(&NumTokensResponse { count }) - } Cw1155QueryMsg::TokenApprovals { owner, token_id, @@ -139,9 +135,13 @@ where Cw1155QueryMsg::ContractInfo {} => { to_json_binary(&self.contract_info.load(deps.storage)?) } - Cw1155QueryMsg::Supply {} => { - let supply = self.supply.load(deps.storage)?; - to_json_binary(&NumTokensResponse { count: supply }) + Cw1155QueryMsg::NumTokens { token_id } => { + let count = if let Some(token_id) = token_id { + self.token_count(deps.storage, &token_id)? + } else { + self.supply.load(deps.storage)? + }; + to_json_binary(&NumTokensResponse { count }) } Cw1155QueryMsg::AllTokens { start_after, limit } => { to_json_binary(&self.query_all_tokens(deps, start_after, limit)?) diff --git a/packages/cw1155/src/query.rs b/packages/cw1155/src/query.rs index eea049112..970c77e35 100644 --- a/packages/cw1155/src/query.rs +++ b/packages/cw1155/src/query.rs @@ -47,10 +47,9 @@ pub enum Cw1155QueryMsg { }, /// Total number of tokens issued #[returns(cw721::NumTokensResponse)] - Supply {}, - /// Total number of tokens issued for the token id - #[returns(cw721::NumTokensResponse)] - NumTokens { token_id: String }, + NumTokens { + token_id: Option, // optional token id to get supply of, otherwise total supply + }, // cw721 /// With MetaData Extension. From 3751bf6b4059ed573f7a1c883ee41ce92c7e07e6 Mon Sep 17 00:00:00 2001 From: shab Date: Wed, 5 Jun 2024 15:26:45 -0400 Subject: [PATCH 38/57] fix query BalanceOfBatch --- contracts/cw1155-base/src/contract_tests.rs | 56 +++++++++++++++------ contracts/cw1155-base/src/query.rs | 26 +++++----- packages/cw1155/src/lib.rs | 8 +-- packages/cw1155/src/msg.rs | 23 ++++++++- packages/cw1155/src/query.rs | 37 +++----------- 5 files changed, 85 insertions(+), 65 deletions(-) diff --git a/contracts/cw1155-base/src/contract_tests.rs b/contracts/cw1155-base/src/contract_tests.rs index 7b41c22c3..d7b4cc452 100644 --- a/contracts/cw1155-base/src/contract_tests.rs +++ b/contracts/cw1155-base/src/contract_tests.rs @@ -10,9 +10,9 @@ mod tests { use crate::{Cw1155BaseContract, Cw1155BaseExecuteMsg, Cw1155BaseQueryMsg}; use cw1155::{ AllBalancesResponse, ApprovedForAllResponse, Balance, BalanceResponse, - BatchBalanceResponse, Cw1155BatchReceiveMsg, Cw1155ContractError, Cw1155InstantiateMsg, - Cw1155MintMsg, Cw1155QueryMsg, Expiration, NumTokensResponse, TokenAmount, - TokenInfoResponse, TokensResponse, + Cw1155BatchReceiveMsg, Cw1155ContractError, Cw1155InstantiateMsg, Cw1155MintMsg, + Cw1155QueryMsg, Expiration, NumTokensResponse, OwnerToken, TokenAmount, TokenInfoResponse, + TokensResponse, }; #[test] @@ -98,10 +98,10 @@ mod tests { contract.query( deps.as_ref(), mock_env(), - Cw1155BaseQueryMsg::BalanceOf { + Cw1155BaseQueryMsg::BalanceOf(OwnerToken { owner: user1.clone(), token_id: token1.clone(), - } + }) ), ); @@ -160,10 +160,10 @@ mod tests { contract.query( deps.as_ref(), mock_env(), - Cw1155QueryMsg::BalanceOf { + Cw1155BaseQueryMsg::BalanceOf(OwnerToken { owner: user2.clone(), token_id: token1.clone(), - } + }) ), to_json_binary(&BalanceResponse { balance: 1u64.into() @@ -173,10 +173,10 @@ mod tests { contract.query( deps.as_ref(), mock_env(), - Cw1155QueryMsg::BalanceOf { + Cw1155BaseQueryMsg::BalanceOf(OwnerToken { owner: user1.clone(), token_id: token1.clone(), - } + }) ), to_json_binary(&BalanceResponse { balance: 0u64.into() @@ -284,14 +284,38 @@ mod tests { contract.query( deps.as_ref(), mock_env(), - Cw1155QueryMsg::BalanceOfBatch { - owner: user1.clone(), - token_ids: vec![token1.clone(), token2.clone(), token3.clone()], - } + Cw1155BaseQueryMsg::BalanceOfBatch(vec![ + OwnerToken { + owner: user1.clone(), + token_id: token1.clone(), + }, + OwnerToken { + owner: user1.clone(), + token_id: token2.clone(), + }, + OwnerToken { + owner: user1.clone(), + token_id: token3.clone(), + } + ]), ), - to_json_binary(&BatchBalanceResponse { - balances: vec![1u64.into(), 1u64.into(), 1u64.into()] - }), + to_json_binary(&vec![ + Balance { + token_id: token1.to_string(), + owner: Addr::unchecked(user1.to_string()), + amount: Uint128::one(), + }, + Balance { + token_id: token2.to_string(), + owner: Addr::unchecked(user1.to_string()), + amount: Uint128::one(), + }, + Balance { + token_id: token3.to_string(), + owner: Addr::unchecked(user1.to_string()), + amount: Uint128::one(), + } + ]), ); // user1 revoke approval diff --git a/contracts/cw1155-base/src/query.rs b/contracts/cw1155-base/src/query.rs index 7a0d6fc40..e86265d7f 100644 --- a/contracts/cw1155-base/src/query.rs +++ b/contracts/cw1155-base/src/query.rs @@ -7,8 +7,8 @@ use cosmwasm_std::{ use cw1155::{ AllBalancesResponse, AllTokenInfoResponse, Approval, ApprovedForAllResponse, Balance, - BalanceResponse, BatchBalanceResponse, Cw1155QueryMsg, Expiration, IsApprovedForAllResponse, - NumTokensResponse, TokenInfoResponse, TokensResponse, + BalanceResponse, Cw1155QueryMsg, Expiration, IsApprovedForAllResponse, NumTokensResponse, + OwnerToken, TokenInfoResponse, TokensResponse, }; use cw721_base::Cw721Contract; use cw_storage_plus::Bound; @@ -32,7 +32,7 @@ where let tract = Cw721Contract::::default(); to_json_binary(&tract.minter(deps)?) } - Cw1155QueryMsg::BalanceOf { owner, token_id } => { + Cw1155QueryMsg::BalanceOf(OwnerToken { owner, token_id }) => { let owner_addr = deps.api.addr_validate(&owner)?; let balance = self .balances @@ -51,23 +51,21 @@ where start_after, limit, } => to_json_binary(&self.query_all_balances(deps, token_id, start_after, limit)?), - Cw1155QueryMsg::BalanceOfBatch { owner, token_ids } => { - let owner_addr = deps.api.addr_validate(&owner)?; - let balances = token_ids + Cw1155QueryMsg::BalanceOfBatch(batch) => { + let balances = batch .into_iter() - .map(|token_id| -> StdResult<_> { - Ok(self - .balances - .may_load(deps.storage, (owner_addr.clone(), token_id.clone()))? + .map(|OwnerToken { owner, token_id }| { + let owner = Addr::unchecked(owner); + self.balances + .load(deps.storage, (owner.clone(), token_id.to_string())) .unwrap_or(Balance { - owner: owner_addr.clone(), + owner, token_id, amount: Uint128::new(0), }) - .amount) }) - .collect::>()?; - to_json_binary(&BatchBalanceResponse { balances }) + .collect::>(); + to_json_binary(&balances) } Cw1155QueryMsg::TokenApprovals { owner, diff --git a/packages/cw1155/src/lib.rs b/packages/cw1155/src/lib.rs index 827b2147c..c304aa95b 100644 --- a/packages/cw1155/src/lib.rs +++ b/packages/cw1155/src/lib.rs @@ -9,12 +9,12 @@ pub use cw_utils::Expiration; pub use crate::receiver::{Cw1155BatchReceiveMsg, Cw1155ReceiveMsg}; pub use crate::msg::{ - Cw1155ExecuteMsg, Cw1155InstantiateMsg, Cw1155MintMsg, TokenAmount, TokenApproval, + Approval, Balance, Cw1155ExecuteMsg, Cw1155InstantiateMsg, Cw1155MintMsg, OwnerToken, + TokenAmount, TokenApproval, }; pub use crate::query::{ - AllBalancesResponse, AllTokenInfoResponse, Approval, ApprovedForAllResponse, Balance, - BalanceResponse, BatchBalanceResponse, Cw1155QueryMsg, IsApprovedForAllResponse, - NumTokensResponse, TokenInfoResponse, TokensResponse, + AllBalancesResponse, AllTokenInfoResponse, ApprovedForAllResponse, BalanceResponse, + Cw1155QueryMsg, IsApprovedForAllResponse, NumTokensResponse, TokenInfoResponse, TokensResponse, }; pub use crate::error::Cw1155ContractError; diff --git a/packages/cw1155/src/msg.rs b/packages/cw1155/src/msg.rs index 5d99011ce..c6a0dd8b3 100644 --- a/packages/cw1155/src/msg.rs +++ b/packages/cw1155/src/msg.rs @@ -1,7 +1,7 @@ use cosmwasm_schema::cw_serde; use std::fmt::{Display, Formatter}; -use cosmwasm_std::{Binary, Env, Uint128}; +use cosmwasm_std::{Addr, Binary, Env, Uint128}; use cw_ownable::cw_ownable_execute; use cw_utils::Expiration; @@ -140,3 +140,24 @@ impl TokenApproval { self.expiration.is_expired(&env.block) } } + +#[cw_serde] +pub struct OwnerToken { + pub owner: String, + pub token_id: String, +} + +#[cw_serde] +pub struct Balance { + pub token_id: String, + pub owner: Addr, + pub amount: Uint128, +} + +#[cw_serde] +pub struct Approval { + /// Account that can transfer/send the token + pub spender: String, + /// When the Approval expires (maybe Expiration::never) + pub expires: Expiration, +} diff --git a/packages/cw1155/src/query.rs b/packages/cw1155/src/query.rs index 970c77e35..ccebe0fb1 100644 --- a/packages/cw1155/src/query.rs +++ b/packages/cw1155/src/query.rs @@ -1,24 +1,21 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use schemars::JsonSchema; -use cosmwasm_std::{Addr, Uint128}; +use crate::{Approval, Balance, OwnerToken}; +use cosmwasm_std::Uint128; use cw_ownable::cw_ownable_query; -use cw_utils::Expiration; #[cw_ownable_query] #[cw_serde] #[derive(QueryResponses)] pub enum Cw1155QueryMsg { // cw1155 - /// Returns the current balance of the given address, 0 if unset. + /// Returns the current balance of the given account, 0 if unset. #[returns(BalanceResponse)] - BalanceOf { owner: String, token_id: String }, - /// Returns the current balance of the given address for a batch of tokens, 0 if unset. - #[returns(BatchBalanceResponse)] - BalanceOfBatch { - owner: String, - token_ids: Vec, - }, + BalanceOf(OwnerToken), + /// Returns the current balance of the given batch of accounts/tokens, 0 if unset. + #[returns(Vec)] + BalanceOfBatch(Vec), /// Query approved status `owner` granted to `operator`. #[returns(IsApprovedForAllResponse)] IsApprovedForAll { owner: String, operator: String }, @@ -101,31 +98,11 @@ pub struct AllBalancesResponse { pub balances: Vec, } -#[cw_serde] -pub struct Balance { - pub token_id: String, - pub owner: Addr, - pub amount: Uint128, -} - -#[cw_serde] -pub struct BatchBalanceResponse { - pub balances: Vec, -} - #[cw_serde] pub struct NumTokensResponse { pub count: Uint128, } -#[cw_serde] -pub struct Approval { - /// Account that can transfer/send the token - pub spender: String, - /// When the Approval expires (maybe Expiration::never) - pub expires: Expiration, -} - #[cw_serde] pub struct ApprovedForAllResponse { pub operators: Vec, From 3609e4a72ccf0ac6507c4555bf6d5038d1f412b2 Mon Sep 17 00:00:00 2001 From: shab Date: Mon, 1 Jul 2024 14:46:30 -0400 Subject: [PATCH 39/57] make state mod public --- contracts/cw1155-base/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/cw1155-base/src/lib.rs b/contracts/cw1155-base/src/lib.rs index 6561ef65b..140aba97f 100644 --- a/contracts/cw1155-base/src/lib.rs +++ b/contracts/cw1155-base/src/lib.rs @@ -1,7 +1,7 @@ mod contract_tests; mod execute; mod query; -mod state; +pub mod state; pub use crate::state::Cw1155Contract; use cosmwasm_std::Empty; From 5acfcbf7c00b97317e67982e49d4dfbade574cc3 Mon Sep 17 00:00:00 2001 From: shab Date: Wed, 3 Jul 2024 11:59:23 -0400 Subject: [PATCH 40/57] rename AllBalancesResponse to BalancesResponse and return as response in query BalanceOfBatch --- contracts/cw1155-base/src/contract_tests.rs | 11 +++++------ contracts/cw1155-base/src/query.rs | 16 ++++++++-------- packages/cw1155/src/lib.rs | 2 +- packages/cw1155/src/query.rs | 6 +++--- 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/contracts/cw1155-base/src/contract_tests.rs b/contracts/cw1155-base/src/contract_tests.rs index d7b4cc452..fb7dd18a7 100644 --- a/contracts/cw1155-base/src/contract_tests.rs +++ b/contracts/cw1155-base/src/contract_tests.rs @@ -9,10 +9,9 @@ mod tests { use crate::{Cw1155BaseContract, Cw1155BaseExecuteMsg, Cw1155BaseQueryMsg}; use cw1155::{ - AllBalancesResponse, ApprovedForAllResponse, Balance, BalanceResponse, - Cw1155BatchReceiveMsg, Cw1155ContractError, Cw1155InstantiateMsg, Cw1155MintMsg, - Cw1155QueryMsg, Expiration, NumTokensResponse, OwnerToken, TokenAmount, TokenInfoResponse, - TokensResponse, + ApprovedForAllResponse, Balance, BalanceResponse, BalancesResponse, Cw1155BatchReceiveMsg, + Cw1155ContractError, Cw1155InstantiateMsg, Cw1155MintMsg, Cw1155QueryMsg, Expiration, + NumTokensResponse, OwnerToken, TokenAmount, TokenInfoResponse, TokensResponse, }; #[test] @@ -594,7 +593,7 @@ mod tests { limit: Some(5), }, ), - to_json_binary(&AllBalancesResponse { + to_json_binary(&BalancesResponse { balances: users[..5] .iter() .map(|user| { @@ -618,7 +617,7 @@ mod tests { limit: Some(5), }, ), - to_json_binary(&AllBalancesResponse { + to_json_binary(&BalancesResponse { balances: users[6..] .iter() .map(|user| { diff --git a/contracts/cw1155-base/src/query.rs b/contracts/cw1155-base/src/query.rs index e86265d7f..072548198 100644 --- a/contracts/cw1155-base/src/query.rs +++ b/contracts/cw1155-base/src/query.rs @@ -6,8 +6,8 @@ use cosmwasm_std::{ }; use cw1155::{ - AllBalancesResponse, AllTokenInfoResponse, Approval, ApprovedForAllResponse, Balance, - BalanceResponse, Cw1155QueryMsg, Expiration, IsApprovedForAllResponse, NumTokensResponse, + AllTokenInfoResponse, Approval, ApprovedForAllResponse, Balance, BalanceResponse, + BalancesResponse, Cw1155QueryMsg, Expiration, IsApprovedForAllResponse, NumTokensResponse, OwnerToken, TokenInfoResponse, TokensResponse, }; use cw721_base::Cw721Contract; @@ -61,11 +61,11 @@ where .unwrap_or(Balance { owner, token_id, - amount: Uint128::new(0), + amount: Uint128::zero(), }) }) .collect::>(); - to_json_binary(&balances) + to_json_binary(&BalancesResponse { balances }) } Cw1155QueryMsg::TokenApprovals { owner, @@ -128,7 +128,7 @@ where to_json_binary(&self.query_owner_tokens(deps, owner_addr, start_after, limit)?) } Cw1155QueryMsg::AllTokenInfo { start_after, limit } => { - to_json_binary(&self.query_all_token_infos(deps, start_after, limit)?) + to_json_binary(&self.query_all_token_info(deps, start_after, limit)?) } Cw1155QueryMsg::ContractInfo {} => { to_json_binary(&self.contract_info.load(deps.storage)?) @@ -222,7 +222,7 @@ where Ok(TokensResponse { tokens }) } - fn query_all_token_infos( + fn query_all_token_info( &self, deps: Deps, start_after: Option, @@ -260,7 +260,7 @@ where token_id: String, start_after: Option, limit: Option, - ) -> StdResult { + ) -> StdResult { let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; let start = if let Some(start_after) = start_after { @@ -283,7 +283,7 @@ where }) .collect(); - Ok(AllBalancesResponse { balances }) + Ok(BalancesResponse { balances }) } } diff --git a/packages/cw1155/src/lib.rs b/packages/cw1155/src/lib.rs index c304aa95b..41a928d3b 100644 --- a/packages/cw1155/src/lib.rs +++ b/packages/cw1155/src/lib.rs @@ -13,7 +13,7 @@ pub use crate::msg::{ TokenAmount, TokenApproval, }; pub use crate::query::{ - AllBalancesResponse, AllTokenInfoResponse, ApprovedForAllResponse, BalanceResponse, + AllTokenInfoResponse, ApprovedForAllResponse, BalanceResponse, BalancesResponse, Cw1155QueryMsg, IsApprovedForAllResponse, NumTokensResponse, TokenInfoResponse, TokensResponse, }; diff --git a/packages/cw1155/src/query.rs b/packages/cw1155/src/query.rs index ccebe0fb1..1f02fef0f 100644 --- a/packages/cw1155/src/query.rs +++ b/packages/cw1155/src/query.rs @@ -14,7 +14,7 @@ pub enum Cw1155QueryMsg { #[returns(BalanceResponse)] BalanceOf(OwnerToken), /// Returns the current balance of the given batch of accounts/tokens, 0 if unset. - #[returns(Vec)] + #[returns(BalancesResponse)] BalanceOfBatch(Vec), /// Query approved status `owner` granted to `operator`. #[returns(IsApprovedForAllResponse)] @@ -36,7 +36,7 @@ pub enum Cw1155QueryMsg { limit: Option, }, /// Returns all current balances of the given token id. Supports pagination - #[returns(AllBalancesResponse)] + #[returns(BalancesResponse)] AllBalances { token_id: String, start_after: Option, @@ -94,7 +94,7 @@ pub struct BalanceResponse { } #[cw_serde] -pub struct AllBalancesResponse { +pub struct BalancesResponse { pub balances: Vec, } From d1e6c9d1d44cbdd8b993d6cca839fd32aa5e0bc7 Mon Sep 17 00:00:00 2001 From: shab Date: Wed, 3 Jul 2024 12:49:32 -0400 Subject: [PATCH 41/57] remove query AllTokenInfo --- contracts/cw1155-base/src/query.rs | 43 +++--------------------------- packages/cw1155/src/query.rs | 7 ----- 2 files changed, 4 insertions(+), 46 deletions(-) diff --git a/contracts/cw1155-base/src/query.rs b/contracts/cw1155-base/src/query.rs index 072548198..b0c27a512 100644 --- a/contracts/cw1155-base/src/query.rs +++ b/contracts/cw1155-base/src/query.rs @@ -6,15 +6,15 @@ use cosmwasm_std::{ }; use cw1155::{ - AllTokenInfoResponse, Approval, ApprovedForAllResponse, Balance, BalanceResponse, - BalancesResponse, Cw1155QueryMsg, Expiration, IsApprovedForAllResponse, NumTokensResponse, - OwnerToken, TokenInfoResponse, TokensResponse, + Approval, ApprovedForAllResponse, Balance, BalanceResponse, BalancesResponse, Cw1155QueryMsg, + Expiration, IsApprovedForAllResponse, NumTokensResponse, OwnerToken, TokenInfoResponse, + TokensResponse, }; use cw721_base::Cw721Contract; use cw_storage_plus::Bound; use cw_utils::maybe_addr; -use crate::state::{Cw1155Contract, TokenInfo}; +use crate::state::Cw1155Contract; const DEFAULT_LIMIT: u32 = 10; const MAX_LIMIT: u32 = 100; @@ -127,9 +127,6 @@ where let owner_addr = deps.api.addr_validate(&owner)?; to_json_binary(&self.query_owner_tokens(deps, owner_addr, start_after, limit)?) } - Cw1155QueryMsg::AllTokenInfo { start_after, limit } => { - to_json_binary(&self.query_all_token_info(deps, start_after, limit)?) - } Cw1155QueryMsg::ContractInfo {} => { to_json_binary(&self.contract_info.load(deps.storage)?) } @@ -222,38 +219,6 @@ where Ok(TokensResponse { tokens }) } - fn query_all_token_info( - &self, - deps: Deps, - start_after: Option, - limit: Option, - ) -> StdResult>> { - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let start = start_after.as_ref().map(|s| Bound::exclusive(s.as_str())); - let tokens = self - .tokens - .range(deps.storage, start, None, Order::Ascending) - .take(limit) - .map(|item| { - let ( - token_id, - TokenInfo { - token_uri, - extension, - }, - ) = item.unwrap(); - AllTokenInfoResponse { - token_id, - info: TokenInfoResponse { - token_uri, - extension, - }, - } - }) - .collect::>(); - Ok(tokens) - } - fn query_all_balances( &self, deps: Deps, diff --git a/packages/cw1155/src/query.rs b/packages/cw1155/src/query.rs index 1f02fef0f..f13ff9db1 100644 --- a/packages/cw1155/src/query.rs +++ b/packages/cw1155/src/query.rs @@ -61,13 +61,6 @@ pub enum Cw1155QueryMsg { #[returns(TokenInfoResponse)] TokenInfo { token_id: String }, /// With Enumerable extension. - /// Requires pagination. Lists all token_ids controlled by the contract. - #[returns(TokenInfoResponse)] - AllTokenInfo { - start_after: Option, - limit: Option, - }, - /// With Enumerable extension. /// Returns all tokens owned by the given address, [] if unset. #[returns(TokensResponse)] Tokens { From 07105d8124755147202ae1b9f625a93a15bfae5d Mon Sep 17 00:00:00 2001 From: shab Date: Wed, 3 Jul 2024 17:38:49 -0400 Subject: [PATCH 42/57] transfer funds along with send/batch_send --- contracts/cw1155-base/src/contract_tests.rs | 75 +++++++++++---------- contracts/cw1155-base/src/execute.rs | 40 ++++++++--- packages/cw1155/src/receiver.rs | 18 +++-- 3 files changed, 83 insertions(+), 50 deletions(-) diff --git a/contracts/cw1155-base/src/contract_tests.rs b/contracts/cw1155-base/src/contract_tests.rs index fb7dd18a7..41a19aa3c 100644 --- a/contracts/cw1155-base/src/contract_tests.rs +++ b/contracts/cw1155-base/src/contract_tests.rs @@ -280,41 +280,47 @@ mod tests { // batch query assert_eq!( - contract.query( - deps.as_ref(), - mock_env(), - Cw1155BaseQueryMsg::BalanceOfBatch(vec![ - OwnerToken { - owner: user1.clone(), - token_id: token1.clone(), + from_json( + contract + .query( + deps.as_ref(), + mock_env(), + Cw1155BaseQueryMsg::BalanceOfBatch(vec![ + OwnerToken { + owner: user1.clone(), + token_id: token1.clone(), + }, + OwnerToken { + owner: user1.clone(), + token_id: token2.clone(), + }, + OwnerToken { + owner: user1.clone(), + token_id: token3.clone(), + } + ]), + ) + .unwrap() + ), + Ok(BalancesResponse { + balances: vec![ + Balance { + token_id: token1.to_string(), + owner: Addr::unchecked(user1.to_string()), + amount: Uint128::one(), }, - OwnerToken { - owner: user1.clone(), - token_id: token2.clone(), + Balance { + token_id: token2.to_string(), + owner: Addr::unchecked(user1.to_string()), + amount: Uint128::one(), }, - OwnerToken { - owner: user1.clone(), - token_id: token3.clone(), + Balance { + token_id: token3.to_string(), + owner: Addr::unchecked(user1.to_string()), + amount: Uint128::one(), } - ]), - ), - to_json_binary(&vec![ - Balance { - token_id: token1.to_string(), - owner: Addr::unchecked(user1.to_string()), - amount: Uint128::one(), - }, - Balance { - token_id: token2.to_string(), - owner: Addr::unchecked(user1.to_string()), - amount: Uint128::one(), - }, - Balance { - token_id: token3.to_string(), - owner: Addr::unchecked(user1.to_string()), - amount: Uint128::one(), - } - ]), + ] + }), ); // user1 revoke approval @@ -428,6 +434,7 @@ mod tests { let minter = String::from("minter"); let user1 = String::from("user1"); let token2 = "token2".to_owned(); + let operator_info = mock_info("operator", &[]); let dummy_msg = Binary::default(); let mut deps = mock_dependencies(); @@ -437,7 +444,7 @@ mod tests { minter: Some(minter.to_string()), }; let res = contract - .instantiate(deps.as_mut(), mock_env(), mock_info("operator", &[]), msg) + .instantiate(deps.as_mut(), mock_env(), operator_info.clone(), msg) .unwrap(); assert_eq!(0, res.messages.len()); @@ -487,7 +494,7 @@ mod tests { }], msg: dummy_msg, } - .into_cosmos_msg(receiver.clone()) + .into_cosmos_msg(&operator_info, receiver.clone()) .unwrap() ) .add_event(Event::new("transfer_single").add_attributes(vec![ diff --git a/contracts/cw1155-base/src/execute.rs b/contracts/cw1155-base/src/execute.rs index 4cbaafff0..8926dcef0 100644 --- a/contracts/cw1155-base/src/execute.rs +++ b/contracts/cw1155-base/src/execute.rs @@ -1,8 +1,8 @@ use crate::state::{Cw1155Contract, TokenInfo}; use crate::{CONTRACT_NAME, CONTRACT_VERSION}; use cosmwasm_std::{ - Addr, Binary, CustomMsg, DepsMut, Env, Event, MessageInfo, Order, Response, StdResult, Storage, - SubMsg, Uint128, + Addr, BankMsg, Binary, CustomMsg, DepsMut, Env, Event, MessageInfo, Order, Response, StdResult, + Storage, SubMsg, Uint128, }; use cw1155::{ ApproveAllEvent, ApproveEvent, Balance, BurnEvent, Cw1155BatchReceiveMsg, Cw1155ContractError, @@ -242,10 +242,10 @@ where amount: balance_update.amount, }], )?; - rsp = rsp.add_event(event); + rsp.events.push(event); if let Some(msg) = msg { - rsp.messages = vec![SubMsg::new( + rsp.messages.push(SubMsg::new( Cw1155ReceiveMsg { operator: info.sender.to_string(), from: Some(from.to_string()), @@ -253,8 +253,17 @@ where token_id, msg, } - .into_cosmos_msg(to)?, - )] + .into_cosmos_msg(&info, to)?, + )); + } else { + // transfer funds along to recipient + if info.funds.len() > 0 { + let transfer_msg = BankMsg::Send { + to_address: to.to_string(), + amount: info.funds.to_vec(), + }; + rsp.messages.push(SubMsg::new(transfer_msg)); + } } Ok(rsp) @@ -291,19 +300,28 @@ where Some(to.clone()), batch.to_vec(), )?; - rsp = rsp.add_event(event); + rsp.events.push(event); if let Some(msg) = msg { - rsp.messages = vec![SubMsg::new( + rsp.messages.push(SubMsg::new( Cw1155BatchReceiveMsg { operator: info.sender.to_string(), from: Some(from.to_string()), batch, msg, } - .into_cosmos_msg(to)?, - )] - }; + .into_cosmos_msg(&info, to)?, + )); + } else { + // transfer funds along to recipient + if info.funds.len() > 0 { + let transfer_msg = BankMsg::Send { + to_address: to.to_string(), + amount: info.funds.to_vec(), + }; + rsp.messages.push(SubMsg::new(transfer_msg)); + } + } Ok(rsp) } diff --git a/packages/cw1155/src/receiver.rs b/packages/cw1155/src/receiver.rs index 76ac8363b..b1efe4e33 100644 --- a/packages/cw1155/src/receiver.rs +++ b/packages/cw1155/src/receiver.rs @@ -1,6 +1,6 @@ use crate::TokenAmount; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{to_json_binary, Binary, CosmosMsg, StdResult, Uint128, WasmMsg}; +use cosmwasm_std::{to_json_binary, Binary, CosmosMsg, MessageInfo, StdResult, Uint128, WasmMsg}; use schemars::JsonSchema; /// Cw1155ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg @@ -23,7 +23,11 @@ impl Cw1155ReceiveMsg { } /// creates a cosmos_msg sending this struct to the named contract - pub fn into_cosmos_msg, C>(self, contract_addr: T) -> StdResult> + pub fn into_cosmos_msg, C>( + self, + info: &MessageInfo, + contract_addr: T, + ) -> StdResult> where C: Clone + std::fmt::Debug + PartialEq + JsonSchema, { @@ -31,7 +35,7 @@ impl Cw1155ReceiveMsg { let execute = WasmMsg::Execute { contract_addr: contract_addr.into(), msg, - funds: vec![], + funds: info.funds.to_vec(), }; Ok(execute.into()) } @@ -54,7 +58,11 @@ impl Cw1155BatchReceiveMsg { } /// creates a cosmos_msg sending this struct to the named contract - pub fn into_cosmos_msg, C>(self, contract_addr: T) -> StdResult> + pub fn into_cosmos_msg, C>( + self, + info: &MessageInfo, + contract_addr: T, + ) -> StdResult> where C: Clone + std::fmt::Debug + PartialEq + JsonSchema, { @@ -62,7 +70,7 @@ impl Cw1155BatchReceiveMsg { let execute = WasmMsg::Execute { contract_addr: contract_addr.into(), msg, - funds: vec![], + funds: info.funds.to_vec(), }; Ok(execute.into()) } From 46136ba2940f5a2becda39b3d64e3bb66082ef5d Mon Sep 17 00:00:00 2001 From: Shab | Apollo DAO Date: Mon, 8 Jul 2024 18:33:23 -0400 Subject: [PATCH 43/57] Merge upstream (#4) * rename states: tokens -> nft_info, contract_info -> collection_info * renamed structs and deprecate: TokenInfo -> NftInfo, ContractInfo -> CollectionInfo, ContractInfoResponse -> CollectionInfoResponse * cargo schema * change storage keys for collection info and nft info, consider in migration and keep legacy data for backward migration * use PA repo for `cw-ownable` * rename generics and make more readable * CollectionInfoResponse -> CollectionInfo * add CollectionInfoExtension with RoyaltyInfo * cargo schema * typo * cleanup * rename * cargo schema * cleanup * creator and owner changes: - new query GetMinterOwnership and GetCreatorOwnership, deprecated Ownership - new execute UpdateMinterOwnership and UpdateCreatorOwnership, deprecate UpdateOwnership - dedicated stores for minter and creator, where creator usess by default cw_ownable singleton! - new migrate msg allowing to reset creator and minter - cleanup migration and split legacy part to dedicated functions - also make sure using decicated CREATOR and MINTER stores and NOT use cw_ownable::... * refactor and move key logic to cw721 package: - move logic from cw721-base to cw721 - merge cw721-base and cw721-metadata-onchain into one, distinction is: `extension: T` where T for base contract is `type DefaultMetadataExtension = Option` - all logic now in default implementation for traits Cw721Execute and Cw721Query * fix ci * cargo fmt * cargo fmt + clippy * cargo fmt * cargo schema * cargo clippy * undo: do not rename token keys * fix unit test * remove useless generic `TMetadataResponse` * remove response structs to msg.rs * cargo schema * move to dedicated fn * docs * rename * cargo clippy * add build script * undo collection info extension and royalty info additions, will be added in next pr * cleanup * generate schemas for entry points, cleanup * update rustc 1.65 -> 1.71 * update cosmwasm version 1.2 -> 1.5, due to rustc compiler issues see also: https://github.com/CosmWasm/cosmwasm/issues/1727 * update optimizer * update rustc 1.71 -> 1.78 * use optimizer * formatting * install rustup first * set $HOME/.cargo/env * install libgcc * install build-base * cleanup * cleanup * Sync cw1155 with upstream (#5) * wip: sync up cw1155 with upstream improvements from cw-awesome * fix build errors/tests --------- Co-authored-by: mr-t --- .circleci/config.yml | 125 +- Cargo.lock | 386 ++-- Cargo.toml | 17 +- Makefile.toml | 2 +- build.sh | 12 + contracts/cw1155-base/examples/schema.rs | 2 +- contracts/cw1155-base/src/contract_tests.rs | 78 +- contracts/cw1155-base/src/execute.rs | 750 +------ contracts/cw1155-base/src/lib.rs | 49 +- contracts/cw1155-base/src/query.rs | 290 +-- contracts/cw1155-base/src/state.rs | 179 +- .../cw1155-metadata-onchain/.cargo/config | 5 - contracts/cw1155-metadata-onchain/Cargo.toml | 36 - contracts/cw1155-metadata-onchain/README.md | 64 - .../examples/schema.rs | 13 - contracts/cw1155-metadata-onchain/src/lib.rs | 138 -- contracts/cw1155-royalties/examples/schema.rs | 3 +- contracts/cw1155-royalties/src/error.rs | 2 +- contracts/cw1155-royalties/src/lib.rs | 43 +- contracts/cw1155-royalties/src/query.rs | 6 +- contracts/cw2981-royalties/Cargo.toml | 1 + contracts/cw2981-royalties/examples/schema.rs | 2 +- .../schema/cw2981-royalties.json | 737 +++++-- contracts/cw2981-royalties/src/error.rs | 2 +- contracts/cw2981-royalties/src/lib.rs | 83 +- contracts/cw2981-royalties/src/msg.rs | 179 +- contracts/cw2981-royalties/src/query.rs | 9 +- contracts/cw721-base/Cargo.toml | 9 +- contracts/cw721-base/README.md | 2 +- contracts/cw721-base/examples/schema.rs | 27 +- contracts/cw721-base/schema/cw721-base.json | 1788 ---------------- contracts/cw721-base/schema/execute_msg.json | 562 +++++ .../cw721-base/schema/instantiate_msg.json | 33 + contracts/cw721-base/schema/migrate_msg.json | 33 + contracts/cw721-base/schema/query_msg.json | 382 ++++ contracts/cw721-base/src/error.rs | 29 +- contracts/cw721-base/src/execute.rs | 500 +---- contracts/cw721-base/src/lib.rs | 145 +- contracts/cw721-base/src/msg.rs | 178 +- contracts/cw721-base/src/multi_tests.rs | 166 -- contracts/cw721-base/src/query.rs | 389 +--- contracts/cw721-base/src/state.rs | 183 +- contracts/cw721-base/src/upgrades/mod.rs | 1 - contracts/cw721-base/src/upgrades/v0_17.rs | 27 - contracts/cw721-expiration/README.md | 2 +- contracts/cw721-expiration/examples/schema.rs | 27 +- .../schema/cw721-expiration.json | 1825 ---------------- .../cw721-expiration/schema/execute_msg.json | 562 +++++ .../schema/instantiate_msg.json | 40 + .../cw721-expiration/schema/migrate_msg.json | 33 + .../cw721-expiration/schema/query_msg.json | 458 ++++ .../cw721-expiration/src/contract_tests.rs | 426 ++-- contracts/cw721-expiration/src/error.rs | 2 +- contracts/cw721-expiration/src/execute.rs | 261 +-- contracts/cw721-expiration/src/lib.rs | 48 +- contracts/cw721-expiration/src/msg.rs | 111 +- contracts/cw721-expiration/src/query.rs | 416 ++-- contracts/cw721-expiration/src/state.rs | 51 +- contracts/cw721-fixed-price/Cargo.toml | 1 + contracts/cw721-fixed-price/README.md | 2 +- .../schema/cw721-fixed-price.json | 180 +- contracts/cw721-fixed-price/src/contract.rs | 22 +- contracts/cw721-fixed-price/src/msg.rs | 6 +- contracts/cw721-fixed-price/src/state.rs | 7 +- .../cw721-metadata-onchain/.cargo/config | 5 - contracts/cw721-metadata-onchain/Cargo.toml | 31 - contracts/cw721-metadata-onchain/NOTICE | 14 - contracts/cw721-metadata-onchain/README.md | 64 - .../cw721-metadata-onchain/examples/schema.rs | 11 - .../schema/cw721-metadata-onchain.json | 1875 ----------------- contracts/cw721-metadata-onchain/src/lib.rs | 143 -- .../cw721-non-transferable/examples/schema.rs | 36 +- .../schema/all_nft_info_response.json | 160 -- .../schema/approval_response.json | 97 - .../schema/approvals_response.json | 100 - .../schema/cw721_execute_msg.json | 265 --- .../schema/execute_msg.json | 562 +++++ .../schema/migrate_msg.json | 33 + .../schema/nft_info_response.json | 32 - .../schema/num_tokens_response.json | 16 - .../schema/operators_response.json | 100 - .../schema/owner_of_response.json | 106 - .../schema/query_msg.json | 13 + .../schema/tokens_response.json | 18 - contracts/cw721-non-transferable/src/lib.rs | 25 +- contracts/cw721-non-transferable/src/msg.rs | 19 +- contracts/cw721-non-transferable/src/state.rs | 3 + .../schema/cw721-receiver-tester.json | 2 +- contracts/cw721-receiver-tester/src/msg.rs | 2 +- packages/cw1155/Cargo.toml | 11 +- packages/cw1155/examples/schema.rs | 10 +- packages/cw1155/src/event.rs | 2 +- packages/cw1155/src/execute.rs | 753 +++++++ packages/cw1155/src/lib.rs | 26 +- packages/cw1155/src/msg.rs | 139 +- packages/cw1155/src/query.rs | 369 +++- packages/cw1155/src/receiver.rs | 2 +- packages/cw1155/src/state.rs | 190 ++ packages/cw721/Cargo.toml | 11 + packages/cw721/README.md | 128 +- packages/cw721/examples/schema.rs | 57 +- .../cw721/schema/all_nft_info_response.json | 105 +- packages/cw721/schema/approval_response.json | 10 +- packages/cw721/schema/approvals_response.json | 10 +- ...nfo_response.json => collection_info.json} | 2 +- .../cw721/schema/collection_info_msg.json | 2 +- packages/cw721/schema/cw721_execute_msg.json | 297 +++ .../cw721/schema/cw721_instantiate_msg.json | 33 + packages/cw721/schema/cw721_migrate_msg.json | 33 + packages/cw721/schema/cw721_query_msg.json | 89 +- .../cw721}/schema/minter_response.json | 2 +- packages/cw721/schema/nft_info_response.json | 89 +- packages/cw721/schema/operator_response.json | 10 +- packages/cw721/schema/operators_response.json | 10 +- packages/cw721/schema/owner_of_response.json | 10 +- packages/cw721/src/error.rs | 27 + packages/cw721/src/execute.rs | 698 ++++++ .../cw721}/src/helpers.rs | 61 +- packages/cw721/src/lib.rs | 22 +- packages/cw721/src/msg.rs | 255 ++- packages/cw721/src/query.rs | 583 +++-- packages/cw721/src/receiver.rs | 7 +- packages/cw721/src/state.rs | 205 ++ packages/cw721/src/testing/contract.rs | 93 + .../cw721/src/testing}/contract_tests.rs | 421 ++-- packages/cw721/src/testing/mod.rs | 4 + packages/cw721/src/testing/multi_tests.rs | 941 +++++++++ packages/cw721/src/testing/unit_tests.rs | 255 +++ packages/cw721/src/traits.rs | 168 -- 129 files changed, 10640 insertions(+), 11379 deletions(-) create mode 100755 build.sh delete mode 100644 contracts/cw1155-metadata-onchain/.cargo/config delete mode 100644 contracts/cw1155-metadata-onchain/Cargo.toml delete mode 100644 contracts/cw1155-metadata-onchain/README.md delete mode 100644 contracts/cw1155-metadata-onchain/examples/schema.rs delete mode 100644 contracts/cw1155-metadata-onchain/src/lib.rs delete mode 100644 contracts/cw721-base/schema/cw721-base.json create mode 100644 contracts/cw721-base/schema/execute_msg.json create mode 100644 contracts/cw721-base/schema/instantiate_msg.json create mode 100644 contracts/cw721-base/schema/migrate_msg.json create mode 100644 contracts/cw721-base/schema/query_msg.json delete mode 100644 contracts/cw721-base/src/multi_tests.rs delete mode 100644 contracts/cw721-base/src/upgrades/mod.rs delete mode 100644 contracts/cw721-base/src/upgrades/v0_17.rs delete mode 100644 contracts/cw721-expiration/schema/cw721-expiration.json create mode 100644 contracts/cw721-expiration/schema/execute_msg.json create mode 100644 contracts/cw721-expiration/schema/instantiate_msg.json create mode 100644 contracts/cw721-expiration/schema/migrate_msg.json create mode 100644 contracts/cw721-expiration/schema/query_msg.json delete mode 100644 contracts/cw721-metadata-onchain/.cargo/config delete mode 100644 contracts/cw721-metadata-onchain/Cargo.toml delete mode 100644 contracts/cw721-metadata-onchain/NOTICE delete mode 100644 contracts/cw721-metadata-onchain/README.md delete mode 100644 contracts/cw721-metadata-onchain/examples/schema.rs delete mode 100644 contracts/cw721-metadata-onchain/schema/cw721-metadata-onchain.json delete mode 100644 contracts/cw721-metadata-onchain/src/lib.rs delete mode 100644 contracts/cw721-non-transferable/schema/all_nft_info_response.json delete mode 100644 contracts/cw721-non-transferable/schema/approval_response.json delete mode 100644 contracts/cw721-non-transferable/schema/approvals_response.json delete mode 100644 contracts/cw721-non-transferable/schema/cw721_execute_msg.json create mode 100644 contracts/cw721-non-transferable/schema/execute_msg.json create mode 100644 contracts/cw721-non-transferable/schema/migrate_msg.json delete mode 100644 contracts/cw721-non-transferable/schema/nft_info_response.json delete mode 100644 contracts/cw721-non-transferable/schema/num_tokens_response.json delete mode 100644 contracts/cw721-non-transferable/schema/operators_response.json delete mode 100644 contracts/cw721-non-transferable/schema/owner_of_response.json delete mode 100644 contracts/cw721-non-transferable/schema/tokens_response.json create mode 100644 packages/cw1155/src/execute.rs create mode 100644 packages/cw1155/src/state.rs rename packages/cw721/schema/{contract_info_response.json => collection_info.json} (88%) rename contracts/cw721-non-transferable/schema/contract_info_response.json => packages/cw721/schema/collection_info_msg.json (88%) create mode 100644 packages/cw721/schema/cw721_instantiate_msg.json create mode 100644 packages/cw721/schema/cw721_migrate_msg.json rename {contracts/cw721-non-transferable => packages/cw721}/schema/minter_response.json (68%) create mode 100644 packages/cw721/src/error.rs create mode 100644 packages/cw721/src/execute.rs rename {contracts/cw721-base => packages/cw721}/src/helpers.rs (72%) create mode 100644 packages/cw721/src/state.rs create mode 100644 packages/cw721/src/testing/contract.rs rename {contracts/cw721-base/src => packages/cw721/src/testing}/contract_tests.rs (67%) create mode 100644 packages/cw721/src/testing/mod.rs create mode 100644 packages/cw721/src/testing/multi_tests.rs create mode 100644 packages/cw721/src/testing/unit_tests.rs delete mode 100644 packages/cw721/src/traits.rs diff --git a/.circleci/config.yml b/.circleci/config.yml index 0a4c01546..513fc597b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,8 +7,9 @@ workflows: - contract_cw1155_metadata_onchain - contract_cw1155_royalties - contract_cw721_base - - contract_cw721_metadata_onchain + - contract_cw721_expiration - contract_cw721_fixed_price + - contract_cw721_receiver_tester - package_cw721 - package_cw1155 - lint @@ -133,7 +134,7 @@ jobs: contract_cw721_base: docker: - - image: rust:1.65.0 + - image: rust:1.78.0 working_directory: ~/project/contracts/cw721-base steps: - checkout: @@ -143,7 +144,31 @@ jobs: command: rustc --version; cargo --version; rustup --version - restore_cache: keys: - - cargocache-cw721-base-rust:1.65.0-{{ checksum "~/project/Cargo.lock" }} + - cargocache-cw721-base-rust:1.78.0-{{ checksum "~/project/Cargo.lock" }} + - run: + name: Unit Tests + environment: + RUST_BACKTRACE: 1 + command: cargo unit-test --locked + - save_cache: + paths: + - /usr/local/cargo/registry + - target + key: cargocache-cw721-base-rust:1.78.0-{{ checksum "~/project/Cargo.lock" }} + + contract_cw721_expiration: + docker: + - image: rust:1.78.0 + working_directory: ~/project/contracts/cw721-expiration + steps: + - checkout: + path: ~/project + - run: + name: Version information + command: rustc --version; cargo --version; rustup --version + - restore_cache: + keys: + - cargocache-cw721-expiration-rust:1.78.0-{{ checksum "~/project/Cargo.lock" }} - run: name: Unit Tests environment: @@ -165,12 +190,12 @@ jobs: paths: - /usr/local/cargo/registry - target - key: cargocache-cw721-base-rust:1.65.0-{{ checksum "~/project/Cargo.lock" }} + key: cargocache-cw721-expiration-rust:1.78.0-{{ checksum "~/project/Cargo.lock" }} - contract_cw721_metadata_onchain: + contract_cw721_fixed_price: docker: - - image: rust:1.65.0 - working_directory: ~/project/contracts/cw721-metadata-onchain + - image: rust:1.78.0 + working_directory: ~/project/contracts/cw721-fixed-price steps: - checkout: path: ~/project @@ -179,7 +204,7 @@ jobs: command: rustc --version; cargo --version; rustup --version - restore_cache: keys: - - cargocache-cw721-metadata-onchain-rust:1.65.0-{{ checksum "~/project/Cargo.lock" }} + - cargocache-cw721-fixed-price-rust:1.78.0-{{ checksum "~/project/Cargo.lock" }} - run: name: Unit Tests environment: @@ -201,12 +226,12 @@ jobs: paths: - /usr/local/cargo/registry - target - key: cargocache-cw721-metadata-onchain-rust:1.65.0-{{ checksum "~/project/Cargo.lock" }} + key: cargocache-cw721-fixed-price-rust:1.78.0-{{ checksum "~/project/Cargo.lock" }} - contract_cw721_fixed_price: + contract_cw721_receiver_tester: docker: - - image: rust:1.65.0 - working_directory: ~/project/contracts/cw721-fixed-price + - image: rust:1.78.0 + working_directory: ~/project/contracts/cw721-receiver-tester steps: - checkout: path: ~/project @@ -215,7 +240,7 @@ jobs: command: rustc --version; cargo --version; rustup --version - restore_cache: keys: - - cargocache-cw721-fixed-price-rust:1.65.0-{{ checksum "~/project/Cargo.lock" }} + - cargocache-cw721-receiver-tester-rust:1.78.0-{{ checksum "~/project/Cargo.lock" }} - run: name: Unit Tests environment: @@ -237,11 +262,11 @@ jobs: paths: - /usr/local/cargo/registry - target - key: cargocache-cw721-fixed-price-rust:1.65.0-{{ checksum "~/project/Cargo.lock" }} + key: cargocache-cw721-receiver-tester-rust:1.78.0-{{ checksum "~/project/Cargo.lock" }} package_cw721: docker: - - image: rust:1.65.0 + - image: rust:1.78.0 working_directory: ~/project/packages/cw721 steps: - checkout: @@ -315,7 +340,7 @@ jobs: lint: docker: - - image: rust:1.65.0 + - image: rust:1.78.0 steps: - checkout - run: @@ -323,7 +348,7 @@ jobs: command: rustc --version; cargo --version; rustup --version; rustup target list --installed - restore_cache: keys: - - cargocache-v2-lint-rust:1.65.0-{{ checksum "Cargo.lock" }} + - cargocache-v2-lint-rust:1.78.0-{{ checksum "Cargo.lock" }} - run: name: Add rustfmt component command: rustup component add rustfmt @@ -342,7 +367,7 @@ jobs: - target/debug/.fingerprint - target/debug/build - target/debug/deps - key: cargocache-v2-lint-rust:1.65.0-{{ checksum "Cargo.lock" }} + key: cargocache-v2-lint-rust:1.78.0-{{ checksum "Cargo.lock" }} # This runs one time on the top level to ensure all contracts compile properly into wasm. # We don't run the wasm build per contract build, and then reuse a lot of the same dependencies, so this speeds up CI time @@ -350,39 +375,53 @@ jobs: # We also sanity-check the resultant wasm files. wasm-build: docker: - - image: rust:1.65.0 + # Image from https://github.com/cibuilds/github, based on alpine + - image: cibuilds/github:0.13 steps: - - checkout: - path: ~/project - run: - name: Version information - command: rustc --version; cargo --version; rustup --version - - restore_cache: - keys: - - cargocache-wasm-rust:1.65.0-{{ checksum "~/project/Cargo.lock" }} + name: Install Docker client, rust/cargo libs (build-base) + command: apk add docker-cli build-base + - setup_remote_docker + - checkout - run: - name: Add wasm32 target - command: rustup target add wasm32-unknown-unknown + # We cannot mount local folders, see https://circleci.com/docs/2.0/building-docker-images/#mounting-folders + name: Prepare volume with source code + command: | + # create a dummy container which will hold a volume with config + docker create -v /code --name with_code alpine /bin/true + # copy a config file into this volume + docker cp Cargo.toml with_code:/code + docker cp Cargo.lock with_code:/code + # copy code into this volume + docker cp ./contracts with_code:/code + docker cp ./packages with_code:/code + - run: + name: Build and optimize contracts + command: | + docker run --volumes-from with_code cosmwasm/workspace-optimizer:0.16.0 + docker cp with_code:/code/artifacts ./artifacts - run: - name: Build Wasm Release + name: List artifacts and checksums command: | - for C in ./contracts/*/ - do - echo "Compiling `basename $C`..." - (cd $C && cargo build --release --target wasm32-unknown-unknown --lib --locked) - done + ls -l artifacts + cat artifacts/checksums.txt - run: - name: Install check_contract + name: Install Rust and Cargo + command: | + curl https://sh.rustup.rs -sSf | sh -s -- -y + source $HOME/.cargo/env + rustup target add wasm32-unknown-unknown + - run: + name: Install cosmwasm-check # Uses --debug for compilation speed - command: cargo install --debug --version 1.1.0 --locked -- cosmwasm-check - - save_cache: - paths: - - /usr/local/cargo/registry - - target - key: cargocache-wasm-rust:1.65.0-{{ checksum "~/project/Cargo.lock" }} + command: | + source $HOME/.cargo/env + cargo install --debug --version 1.1.0 --locked cosmwasm-check - run: name: Check wasm contracts - command: cosmwasm-check ./target/wasm32-unknown-unknown/release/*.wasm + command: | + source $HOME/.cargo/env + cosmwasm-check ./artifacts/*.wasm # This job roughly follows the instructions from https://circleci.com/blog/publishing-to-github-releases-via-circleci/ build_and_upload_contracts: @@ -410,7 +449,7 @@ jobs: - run: name: Build development contracts command: | - docker run --volumes-from with_code cosmwasm/workspace-optimizer:0.12.13 + docker run --volumes-from with_code cosmwasm/workspace-optimizer:0.16.0 docker cp with_code:/code/artifacts ./artifacts - run: name: Show data diff --git a/Cargo.lock b/Cargo.lock index 3e791817b..3f89d2064 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "ahash" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom", "once_cell", @@ -15,9 +15,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.77" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9d19de80eff169429ac1e9f48fffb163916b448a44e8e046186232046d9e1f9" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "base16ct" @@ -27,9 +27,9 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64ct" @@ -63,9 +63,9 @@ dependencies = [ [[package]] name = "bnum" -version = "0.8.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab9008b6bb9fc80b5277f2fe481c09e828743d9151203e804583eb4c9e15b31d" +checksum = "56953345e39537a3e18bdaeba4cb0c58a78c1f61f361dc0fa7c5c7340ae87c5f" [[package]] name = "byteorder" @@ -75,9 +75,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cfg-if" @@ -93,9 +93,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "cosmwasm-crypto" -version = "1.5.0" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8bb3c77c3b7ce472056968c745eb501c440fbc07be5004eba02782c35bfbbe3" +checksum = "dd50718a2b6830ce9eb5d465de5a018a12e71729d66b70807ce97e6dd14f931d" dependencies = [ "digest 0.10.7", "ecdsa", @@ -107,18 +107,18 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.5.0" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea73e9162e6efde00018d55ed0061e93a108b5d6ec4548b4f8ce3c706249687" +checksum = "242e98e7a231c122e08f300d9db3262d1007b51758a8732cd6210b3e9faa4f3a" dependencies = [ "syn 1.0.109", ] [[package]] name = "cosmwasm-schema" -version = "1.5.0" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0df41ea55f2946b6b43579659eec048cc2f66e8c8e2e3652fc5e5e476f673856" +checksum = "7879036156092ad1c22fe0d7316efc5a5eceec2bc3906462a2560215f2a2f929" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -129,9 +129,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.5.0" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43609e92ce1b9368aa951b334dd354a2d0dd4d484931a5f83ae10e12a26c8ba9" +checksum = "0bb57855fbfc83327f8445ae0d413b1a05ac0d68c396ab4d122b2abd7bb82cb6" dependencies = [ "proc-macro2", "quote", @@ -140,9 +140,9 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.5.0" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04d6864742e3a7662d024b51a94ea81c9af21db6faea2f9a6d2232bb97c6e53e" +checksum = "78c1556156fdf892a55cced6115968b961eaaadd6f724a2c2cb7d1e168e32dd3" dependencies = [ "base64", "bech32", @@ -162,9 +162,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] @@ -213,11 +213,19 @@ dependencies = [ "cosmwasm-std", ] +[[package]] +name = "cw-address-like" +version = "1.0.4" +source = "git+https://github.com/public-awesome/cw-plus-plus.git?rev=28c1a09bfc6b4f1942fefe3eb0b50faf9d3b1523#28c1a09bfc6b4f1942fefe3eb0b50faf9d3b1523" +dependencies = [ + "cosmwasm-std", +] + [[package]] name = "cw-multi-test" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fff029689ae89127cf6d7655809a68d712f3edbdb9686c70b018ba438b26ca" +checksum = "cc392a5cb7e778e3f90adbf7faa43c4db7f35b6623224b08886d796718edb875" dependencies = [ "anyhow", "bech32", @@ -225,8 +233,8 @@ dependencies = [ "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "derivative", - "itertools 0.12.0", - "prost 0.12.3", + "itertools 0.12.1", + "prost 0.12.6", "schemars", "serde", "sha2 0.10.8", @@ -241,8 +249,22 @@ checksum = "093dfb4520c48b5848274dd88ea99e280a04bc08729603341c7fb0d758c74321" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-address-like", - "cw-ownable-derive", + "cw-address-like 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cw-ownable-derive 0.5.1", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "thiserror", +] + +[[package]] +name = "cw-ownable" +version = "0.6.0" +source = "git+https://github.com/public-awesome/cw-plus-plus.git?rev=28c1a09bfc6b4f1942fefe3eb0b50faf9d3b1523#28c1a09bfc6b4f1942fefe3eb0b50faf9d3b1523" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-address-like 1.0.4 (git+https://github.com/public-awesome/cw-plus-plus.git?rev=28c1a09bfc6b4f1942fefe3eb0b50faf9d3b1523)", + "cw-ownable-derive 0.6.0", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "thiserror", @@ -259,6 +281,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "cw-ownable-derive" +version = "0.6.0" +source = "git+https://github.com/public-awesome/cw-plus-plus.git?rev=28c1a09bfc6b4f1942fefe3eb0b50faf9d3b1523#28c1a09bfc6b4f1942fefe3eb0b50faf9d3b1523" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "cw-storage-plus" version = "0.16.0" @@ -313,14 +345,15 @@ dependencies = [ [[package]] name = "cw1155" -version = "0.18.0" +version = "0.19.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-ownable", + "cw-ownable 0.6.0", + "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", - "cw721 0.18.0", + "cw721 0.19.0", "schemars", "serde", "thiserror", @@ -328,38 +361,24 @@ dependencies = [ [[package]] name = "cw1155-base" -version = "0.18.0" +version = "0.19.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-ownable", + "cw-ownable 0.6.0", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw1155", "cw2 1.1.2", - "cw721 0.18.0", - "cw721-base 0.18.0", + "cw721 0.19.0", + "cw721-base 0.19.0", "schemars", "serde", ] -[[package]] -name = "cw1155-metadata-onchain" -version = "0.18.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw1155", - "cw1155-base", - "cw2 1.1.2", - "schemars", - "serde", - "thiserror", -] - [[package]] name = "cw1155-royalties" -version = "0.18.0" +version = "0.19.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -415,13 +434,14 @@ dependencies = [ [[package]] name = "cw2981-royalties" -version = "0.18.0" +version = "0.19.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", + "cw-ownable 0.6.0", "cw2 1.1.2", - "cw721 0.18.0", - "cw721-base 0.18.0", + "cw721 0.19.0", + "cw721-base 0.19.0", "schemars", "serde", "thiserror", @@ -440,9 +460,34 @@ dependencies = [ "serde", ] +[[package]] +name = "cw721" +version = "0.16.0" +source = "git+https://github.com/CosmWasm/cw-nfts?tag=v0.16.0#2cad1d3e15e0a34d466a0b51e02c58b82ebe5ecd" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 0.16.0", + "schemars", + "serde", +] + +[[package]] +name = "cw721" +version = "0.17.0" +source = "git+https://github.com/CosmWasm/cw-nfts?tag=v0.17.0#c1ece555dded6cbcebba1d417ed2a18d47ca3c8a" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 1.0.3", + "schemars", + "serde", +] + [[package]] name = "cw721" version = "0.18.0" +source = "git+https://github.com/CosmWasm/cw-nfts?tag=v0.18.0#177a993dfb5a1a3164be1baf274f43b1ca53da53" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -451,6 +496,26 @@ dependencies = [ "serde", ] +[[package]] +name = "cw721" +version = "0.19.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test", + "cw-ownable 0.6.0", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw721 0.16.0 (git+https://github.com/CosmWasm/cw-nfts?tag=v0.16.0)", + "cw721-base 0.16.0 (git+https://github.com/CosmWasm/cw-nfts?tag=v0.16.0)", + "cw721-base 0.17.0", + "cw721-base 0.18.0", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw721-base" version = "0.16.0" @@ -462,7 +527,7 @@ dependencies = [ "cw-storage-plus 0.16.0", "cw-utils 0.16.0", "cw2 0.16.0", - "cw721 0.16.0", + "cw721 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", "schemars", "serde", "thiserror", @@ -470,92 +535,128 @@ dependencies = [ [[package]] name = "cw721-base" -version = "0.18.0" +version = "0.16.0" +source = "git+https://github.com/CosmWasm/cw-nfts?tag=v0.16.0#2cad1d3e15e0a34d466a0b51e02c58b82ebe5ecd" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test", - "cw-ownable", + "cw-storage-plus 0.16.0", + "cw-utils 0.16.0", + "cw2 0.16.0", + "cw721 0.16.0 (git+https://github.com/CosmWasm/cw-nfts?tag=v0.16.0)", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw721-base" +version = "0.17.0" +source = "git+https://github.com/CosmWasm/cw-nfts?tag=v0.17.0#c1ece555dded6cbcebba1d417ed2a18d47ca3c8a" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-ownable 0.5.1", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", - "cw721 0.18.0", - "cw721-base 0.16.0", + "cw721 0.17.0", + "cw721-base 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", "schemars", "serde", "thiserror", ] [[package]] -name = "cw721-expiration" +name = "cw721-base" version = "0.18.0" +source = "git+https://github.com/CosmWasm/cw-nfts?tag=v0.18.0#177a993dfb5a1a3164be1baf274f43b1ca53da53" dependencies = [ "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test", - "cw-ownable", + "cw-ownable 0.5.1", "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", "cw2 1.1.2", "cw721 0.18.0", - "cw721-base 0.18.0", + "cw721-base 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", "schemars", "serde", "thiserror", ] [[package]] -name = "cw721-fixed-price" -version = "0.18.0" +name = "cw721-base" +version = "0.19.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-ownable 0.6.0", + "cw2 1.1.2", + "cw721 0.17.0", + "cw721 0.18.0", + "cw721 0.19.0", + "serde", +] + +[[package]] +name = "cw721-expiration" +version = "0.19.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", + "cw-multi-test", + "cw-ownable 0.6.0", "cw-storage-plus 1.2.0", - "cw-utils 1.0.3", "cw2 1.1.2", - "cw20", - "cw721-base 0.18.0", - "prost 0.10.4", + "cw721 0.19.0", + "cw721-base 0.19.0", "schemars", "serde", "thiserror", ] [[package]] -name = "cw721-metadata-onchain" -version = "0.18.0" +name = "cw721-fixed-price" +version = "0.19.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", "cw2 1.1.2", - "cw721 0.18.0", - "cw721-base 0.18.0", + "cw20", + "cw721 0.19.0", + "cw721-base 0.19.0", + "prost 0.10.4", "schemars", "serde", + "thiserror", ] [[package]] name = "cw721-non-transferable" -version = "0.18.0" +version = "0.19.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", "cw2 1.1.2", - "cw721 0.18.0", - "cw721-base 0.18.0", + "cw721 0.19.0", + "cw721-base 0.19.0", "schemars", "serde", ] [[package]] name = "cw721-receiver-tester" -version = "0.18.0" +version = "0.19.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", - "cw721 0.18.0", - "cw721-base 0.18.0", + "cw721 0.19.0", + "cw721-base 0.19.0", "schemars", "serde", "thiserror", @@ -563,9 +664,9 @@ dependencies = [ [[package]] name = "der" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "zeroize", @@ -605,9 +706,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "ecdsa" @@ -640,9 +741,9 @@ dependencies = [ [[package]] name = "either" -version = "1.9.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" [[package]] name = "elliptic-curve" @@ -692,9 +793,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -747,33 +848,24 @@ dependencies = [ [[package]] name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "k256" -version = "0.13.2" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f01b677d82ef7a676aa37e099defd83a28e15687112cafdd112d60236b6115b" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" dependencies = [ "cfg-if", "ecdsa", @@ -785,9 +877,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.151" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "once_cell" @@ -797,9 +889,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "pkcs8" @@ -813,9 +905,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.71" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" dependencies = [ "unicode-ident", ] @@ -832,12 +924,12 @@ dependencies = [ [[package]] name = "prost" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ "bytes", - "prost-derive 0.12.3", + "prost-derive 0.12.6", ] [[package]] @@ -855,22 +947,22 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.11.0", + "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.66", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -902,15 +994,15 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "schemars" -version = "0.8.16" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" dependencies = [ "dyn-clone", "schemars_derive", @@ -920,14 +1012,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.16" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 1.0.109", + "syn 2.0.66", ] [[package]] @@ -946,55 +1038,55 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde-json-wasm" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a62a1fad1e1828b24acac8f2b468971dade7b8c3c2e672bcadefefb1f8c137" +checksum = "9e9213a07d53faa0b8dd81e767a54a8188a242fdb9be99ab75ec576a774bfdd7" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.66", ] [[package]] name = "serde_derive_internals" -version = "0.26.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.66", ] [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", @@ -1070,9 +1162,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.43" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", @@ -1081,22 +1173,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.52" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.52" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.66", ] [[package]] @@ -1125,6 +1217,6 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 94031ef51..45ec8a234 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,26 +2,31 @@ members = ["packages/*", "contracts/*"] [workspace.package] -version = "0.18.0" +version = "0.19.0" edition = "2021" license = "Apache-2.0" repository = "https://github.com/CosmWasm/cw-nfts" homepage = "https://cosmwasm.com" documentation = "https://docs.cosmwasm.com" -rust-version = "1.65" +rust-version = "1.78" [workspace.dependencies] -cosmwasm-schema = "^1.2" -cosmwasm-std = "^1.2" +cosmwasm-schema = "^1.5" +cosmwasm-std = "^1.5" cw2 = "^1.1" cw20 = "^1.1" cw721 = { version = "*", path = "./packages/cw721" } +cw721-016 = { git = "https://github.com/CosmWasm/cw-nfts", tag = "v0.16.0", package = "cw721" } # needed for backwards compatibility and legacy migration +cw721-017 = { git = "https://github.com/CosmWasm/cw-nfts", tag = "v0.17.0", package = "cw721" } # needed for testing legacy migration +cw721-018 = { git = "https://github.com/CosmWasm/cw-nfts", tag = "v0.18.0", package = "cw721" } # needed for testing legacy migration cw721-base = { version = "*", path = "./contracts/cw721-base" } -cw721-base-016 = { version = "0.16.0", package = "cw721-base" } +cw721-base-016 = { git = "https://github.com/CosmWasm/cw-nfts", tag = "v0.16.0", package = "cw721-base" } # needed for testing legacy migration +cw721-base-017 = { git = "https://github.com/CosmWasm/cw-nfts", tag = "v0.17.0", package = "cw721-base" } # needed for testing legacy migration +cw721-base-018 = { git = "https://github.com/CosmWasm/cw-nfts", tag = "v0.18.0", package = "cw721-base" } # needed for testing legacy migration cw1155 = { path = "./packages/cw1155", version = "*" } cw1155-base = { path = "./contracts/cw1155-base", version = "*" } cw-multi-test = "^0.20" -cw-ownable = "^0.5" +cw-ownable = { git = "https://github.com/public-awesome/cw-plus-plus.git", rev = "28c1a09bfc6b4f1942fefe3eb0b50faf9d3b1523"} # TODO: switch to official https://github.com/larry0x/cw-plus-plus once merged cw-storage-plus = "^1.1" cw-utils = "^1.0" schemars = "^0.8" diff --git a/Makefile.toml b/Makefile.toml index 951218c6c..633e010ed 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -32,7 +32,7 @@ fi docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - ${image}:0.15.0 + ${image}:0.16.0 """ [tasks.schema] diff --git a/build.sh b/build.sh new file mode 100755 index 000000000..df1d9651c --- /dev/null +++ b/build.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# Compiles and optimizes contracts + +set -o errexit -o nounset -o pipefail +command -v shellcheck >/dev/null && shellcheck "$0" + +cd "$(git rev-parse --show-toplevel)" +docker run --rm -v "$(pwd)":/code --platform linux/amd64 \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/workspace-optimizer:0.16.0 # https://hub.docker.com/r/cosmwasm/workspace-optimizer/tags +ls -al ./artifacts/*wasm diff --git a/contracts/cw1155-base/examples/schema.rs b/contracts/cw1155-base/examples/schema.rs index 5df5cc3ce..1666716a7 100644 --- a/contracts/cw1155-base/examples/schema.rs +++ b/contracts/cw1155-base/examples/schema.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::write_api; +use cw1155::msg::Cw1155InstantiateMsg; -use cw1155::Cw1155InstantiateMsg; use cw1155_base::{Cw1155BaseExecuteMsg, Cw1155BaseQueryMsg}; fn main() { diff --git a/contracts/cw1155-base/src/contract_tests.rs b/contracts/cw1155-base/src/contract_tests.rs index 41a19aa3c..1345e6f40 100644 --- a/contracts/cw1155-base/src/contract_tests.rs +++ b/contracts/cw1155-base/src/contract_tests.rs @@ -1,18 +1,24 @@ #[cfg(test)] mod tests { + use crate::{Cw1155BaseContract, Cw1155BaseExecuteMsg, Cw1155BaseQueryMsg}; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; use cosmwasm_std::{ from_json, to_json_binary, Addr, Binary, Empty, Event, OverflowError, Response, StdError, Uint128, }; - use cw_ownable::OwnershipError; - - use crate::{Cw1155BaseContract, Cw1155BaseExecuteMsg, Cw1155BaseQueryMsg}; - use cw1155::{ - ApprovedForAllResponse, Balance, BalanceResponse, BalancesResponse, Cw1155BatchReceiveMsg, - Cw1155ContractError, Cw1155InstantiateMsg, Cw1155MintMsg, Cw1155QueryMsg, Expiration, - NumTokensResponse, OwnerToken, TokenAmount, TokenInfoResponse, TokensResponse, + use cw1155::error::Cw1155ContractError; + use cw1155::execute::Cw1155Execute; + use cw1155::msg::{ + ApprovedForAllResponse, Balance, BalanceResponse, BalancesResponse, Cw1155InstantiateMsg, + Cw1155MintMsg, Cw1155QueryMsg, NumTokensResponse, OwnerToken, TokenAmount, + TokenInfoResponse, }; + use cw1155::query::Cw1155Query; + use cw1155::receiver::Cw1155BatchReceiveMsg; + use cw721::msg::TokensResponse; + use cw721::Approval; + use cw_ownable::OwnershipError; + use cw_utils::Expiration; #[test] fn check_transfers() { @@ -48,7 +54,14 @@ mod tests { minter: Some(minter.to_string()), }; let res = contract - .instantiate(deps.as_mut(), mock_env(), mock_info("operator", &[]), msg) + .instantiate( + deps.as_mut(), + mock_env(), + mock_info("operator", &[]), + msg, + "contract_name", + "contract_version", + ) .unwrap(); assert_eq!(0, res.messages.len()); @@ -444,7 +457,14 @@ mod tests { minter: Some(minter.to_string()), }; let res = contract - .instantiate(deps.as_mut(), mock_env(), operator_info.clone(), msg) + .instantiate( + deps.as_mut(), + mock_env(), + operator_info.clone(), + msg, + "contract_name", + "contract_version", + ) .unwrap(); assert_eq!(0, res.messages.len()); @@ -522,7 +542,14 @@ mod tests { minter: Some(minter.to_string()), }; let res = contract - .instantiate(deps.as_mut(), mock_env(), mock_info("operator", &[]), msg) + .instantiate( + deps.as_mut(), + mock_env(), + mock_info("operator", &[]), + msg, + "contract_name", + "contract_version", + ) .unwrap(); assert_eq!(0, res.messages.len()); @@ -722,8 +749,8 @@ mod tests { }, ), to_json_binary(&ApprovedForAllResponse { - operators: vec![cw1155::Approval { - spender: users[3].clone(), + operators: vec![Approval { + spender: Addr::unchecked(&users[3]), expires: Expiration::Never {}, }], }) @@ -751,7 +778,14 @@ mod tests { minter: Some(minter.to_string()), }; let res = contract - .instantiate(deps.as_mut(), env.clone(), mock_info("operator", &[]), msg) + .instantiate( + deps.as_mut(), + env.clone(), + mock_info("operator", &[]), + msg, + "contract_name", + "contract_version", + ) .unwrap(); assert_eq!(0, res.messages.len()); @@ -863,7 +897,14 @@ mod tests { minter: Some(minter.to_string()), }; let res = contract - .instantiate(deps.as_mut(), env.clone(), mock_info("operator", &[]), msg) + .instantiate( + deps.as_mut(), + env.clone(), + mock_info("operator", &[]), + msg, + "contract_name", + "contract_version", + ) .unwrap(); assert_eq!(0, res.messages.len()); @@ -950,7 +991,14 @@ mod tests { minter: Some(minter.to_string()), }; let res = contract - .instantiate(deps.as_mut(), mock_env(), mock_info("operator", &[]), msg) + .instantiate( + deps.as_mut(), + mock_env(), + mock_info("operator", &[]), + msg, + "contract_name", + "contract_version", + ) .unwrap(); assert_eq!(0, res.messages.len()); diff --git a/contracts/cw1155-base/src/execute.rs b/contracts/cw1155-base/src/execute.rs index 8926dcef0..165181165 100644 --- a/contracts/cw1155-base/src/execute.rs +++ b/contracts/cw1155-base/src/execute.rs @@ -1,720 +1,50 @@ -use crate::state::{Cw1155Contract, TokenInfo}; -use crate::{CONTRACT_NAME, CONTRACT_VERSION}; -use cosmwasm_std::{ - Addr, BankMsg, Binary, CustomMsg, DepsMut, Env, Event, MessageInfo, Order, Response, StdResult, - Storage, SubMsg, Uint128, -}; -use cw1155::{ - ApproveAllEvent, ApproveEvent, Balance, BurnEvent, Cw1155BatchReceiveMsg, Cw1155ContractError, - Cw1155ExecuteMsg, Cw1155InstantiateMsg, Cw1155MintMsg, Cw1155ReceiveMsg, Expiration, MintEvent, - RevokeAllEvent, RevokeEvent, TokenAmount, TokenApproval, TransferEvent, -}; -use cw2::set_contract_version; +use crate::Cw1155Contract; +use cosmwasm_std::CustomMsg; +use cw1155::execute::Cw1155Execute; +use cw721::execute::Cw721Execute; use serde::de::DeserializeOwned; use serde::Serialize; -impl<'a, T, C, E, Q> Cw1155Contract<'a, T, C, E, Q> +impl<'a, TMetadataExtension, TCustomResponseMessage, TMetadataExtensionMsg, TQueryExtensionMsg> + Cw1155Execute< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + > + for Cw1155Contract< + 'a, + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + > where - T: Serialize + DeserializeOwned + Clone, - C: CustomMsg, - E: CustomMsg, - Q: CustomMsg, + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TCustomResponseMessage: CustomMsg, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, { - pub fn instantiate( - &self, - deps: DepsMut, - _env: Env, - info: MessageInfo, - msg: Cw1155InstantiateMsg, - ) -> StdResult> { - // store contract version - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - - // store contract info - let contract_info = cw721::ContractInfoResponse { - name: msg.name, - symbol: msg.symbol, - }; - self.contract_info.save(deps.storage, &contract_info)?; - - // store minter - let owner = match msg.minter { - Some(owner) => deps.api.addr_validate(&owner)?, - None => info.sender, - }; - cw_ownable::initialize_owner(deps.storage, deps.api, Some(owner.as_ref()))?; - - // store total supply - self.supply.save(deps.storage, &Uint128::zero())?; - - Ok(Response::default()) - } - - pub fn execute( - &self, - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: Cw1155ExecuteMsg, - ) -> Result, Cw1155ContractError> { - let env = ExecuteEnv { deps, env, info }; - match msg { - // cw1155 - Cw1155ExecuteMsg::SendBatch { - from, - to, - batch, - msg, - } => self.send_batch(env, from, to, batch, msg), - Cw1155ExecuteMsg::MintBatch { recipient, msgs } => { - self.mint_batch(env, recipient, msgs) - } - Cw1155ExecuteMsg::BurnBatch { from, batch } => self.burn_batch(env, from, batch), - Cw1155ExecuteMsg::ApproveAll { operator, expires } => { - self.approve_all(env, operator, expires) - } - Cw1155ExecuteMsg::RevokeAll { operator } => self.revoke_all(env, operator), - - // cw721 - Cw1155ExecuteMsg::Send { - from, - to, - token_id, - amount, - msg, - } => self.send(env, from, to, token_id, amount, msg), - Cw1155ExecuteMsg::Mint { recipient, msg } => self.mint(env, recipient, msg), - Cw1155ExecuteMsg::Burn { - from, - token_id, - amount, - } => self.burn(env, from, token_id, amount), - Cw1155ExecuteMsg::Approve { - spender, - token_id, - amount, - expires, - } => self.approve_token(env, spender, token_id, amount, expires), - Cw1155ExecuteMsg::Revoke { - spender, - token_id, - amount, - } => self.revoke_token(env, spender, token_id, amount), - Cw1155ExecuteMsg::UpdateOwnership(action) => Self::update_ownership(env, action), - - Cw1155ExecuteMsg::Extension { .. } => unimplemented!(), - } - } -} - -/// To mitigate clippy::too_many_arguments warning -pub struct ExecuteEnv<'a> { - deps: DepsMut<'a>, - env: Env, - info: MessageInfo, } -// helper -impl<'a, T, C, E, Q> Cw1155Contract<'a, T, C, E, Q> +impl<'a, TMetadataExtension, TCustomResponseMessage, TMetadataExtensionMsg, TQueryExtensionMsg> + Cw721Execute< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + > + for Cw1155Contract< + 'a, + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + > where - T: Serialize + DeserializeOwned + Clone, - C: CustomMsg, - E: CustomMsg, - Q: CustomMsg, + TCustomResponseMessage: CustomMsg, + TMetadataExtension: Clone + DeserializeOwned + Serialize, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, { - pub fn mint( - &self, - env: ExecuteEnv, - recipient: String, - msg: Cw1155MintMsg, - ) -> Result, Cw1155ContractError> { - let ExecuteEnv { - mut deps, - info, - env, - } = env; - - cw_ownable::assert_owner(deps.storage, &info.sender)?; - - let to = deps.api.addr_validate(&recipient)?; - - let mut rsp = Response::default(); - - let event = self.update_balances( - &mut deps, - &env, - None, - Some(to), - vec![TokenAmount { - token_id: msg.token_id.to_string(), - amount: msg.amount, - }], - )?; - rsp = rsp.add_event(event); - - // store token info if not exist (if it is the first mint) - if !self.tokens.has(deps.storage, &msg.token_id) { - let token_info = TokenInfo { - token_uri: msg.token_uri, - extension: msg.extension, - }; - self.tokens.save(deps.storage, &msg.token_id, &token_info)?; - } - - Ok(rsp) - } - - pub fn mint_batch( - &self, - env: ExecuteEnv, - recipient: String, - msgs: Vec>, - ) -> Result, Cw1155ContractError> { - let ExecuteEnv { - mut deps, - info, - env, - } = env; - - cw_ownable::assert_owner(deps.storage, &info.sender)?; - - let to = deps.api.addr_validate(&recipient)?; - - let batch = msgs - .iter() - .map(|msg| { - // store token info if not exist (if it is the first mint) - if !self.tokens.has(deps.storage, &msg.token_id) { - let token_info = TokenInfo { - token_uri: msg.token_uri.clone(), - extension: msg.extension.clone(), - }; - self.tokens.save(deps.storage, &msg.token_id, &token_info)?; - } - Ok(TokenAmount { - token_id: msg.token_id.to_string(), - amount: msg.amount, - }) - }) - .collect::>>()?; - - let mut rsp = Response::default(); - let event = self.update_balances(&mut deps, &env, None, Some(to), batch)?; - rsp = rsp.add_event(event); - - Ok(rsp) - } - - pub fn send( - &self, - env: ExecuteEnv, - from: Option, - to: String, - token_id: String, - amount: Uint128, - msg: Option, - ) -> Result, Cw1155ContractError> { - let ExecuteEnv { - mut deps, - env, - info, - } = env; - - let from = if let Some(from) = from { - deps.api.addr_validate(&from)? - } else { - info.sender.clone() - }; - let to = deps.api.addr_validate(&to)?; - - let balance_update = - self.verify_approval(deps.storage, &env, &info, &from, &token_id, amount)?; - - let mut rsp = Response::::default(); - - let event = self.update_balances( - &mut deps, - &env, - Some(from.clone()), - Some(to.clone()), - vec![TokenAmount { - token_id: token_id.to_string(), - amount: balance_update.amount, - }], - )?; - rsp.events.push(event); - - if let Some(msg) = msg { - rsp.messages.push(SubMsg::new( - Cw1155ReceiveMsg { - operator: info.sender.to_string(), - from: Some(from.to_string()), - amount, - token_id, - msg, - } - .into_cosmos_msg(&info, to)?, - )); - } else { - // transfer funds along to recipient - if info.funds.len() > 0 { - let transfer_msg = BankMsg::Send { - to_address: to.to_string(), - amount: info.funds.to_vec(), - }; - rsp.messages.push(SubMsg::new(transfer_msg)); - } - } - - Ok(rsp) - } - - pub fn send_batch( - &self, - env: ExecuteEnv, - from: Option, - to: String, - batch: Vec, - msg: Option, - ) -> Result, Cw1155ContractError> { - let ExecuteEnv { - mut deps, - env, - info, - } = env; - - let from = if let Some(from) = from { - deps.api.addr_validate(&from)? - } else { - info.sender.clone() - }; - let to = deps.api.addr_validate(&to)?; - - let batch = self.verify_approvals(deps.storage, &env, &info, &from, batch)?; - - let mut rsp = Response::::default(); - let event = self.update_balances( - &mut deps, - &env, - Some(from.clone()), - Some(to.clone()), - batch.to_vec(), - )?; - rsp.events.push(event); - - if let Some(msg) = msg { - rsp.messages.push(SubMsg::new( - Cw1155BatchReceiveMsg { - operator: info.sender.to_string(), - from: Some(from.to_string()), - batch, - msg, - } - .into_cosmos_msg(&info, to)?, - )); - } else { - // transfer funds along to recipient - if info.funds.len() > 0 { - let transfer_msg = BankMsg::Send { - to_address: to.to_string(), - amount: info.funds.to_vec(), - }; - rsp.messages.push(SubMsg::new(transfer_msg)); - } - } - - Ok(rsp) - } - - pub fn burn( - &self, - env: ExecuteEnv, - from: Option, - token_id: String, - amount: Uint128, - ) -> Result, Cw1155ContractError> { - let ExecuteEnv { - mut deps, - info, - env, - } = env; - - let from = if let Some(from) = from { - deps.api.addr_validate(&from)? - } else { - info.sender.clone() - }; - - // whoever can transfer these tokens can burn - let balance_update = - self.verify_approval(deps.storage, &env, &info, &from, &token_id, amount)?; - - let mut rsp = Response::default(); - - let event = self.update_balances( - &mut deps, - &env, - Some(from), - None, - vec![TokenAmount { - token_id, - amount: balance_update.amount, - }], - )?; - rsp = rsp.add_event(event); - - Ok(rsp) - } - - pub fn burn_batch( - &self, - env: ExecuteEnv, - from: Option, - batch: Vec, - ) -> Result, Cw1155ContractError> { - let ExecuteEnv { - mut deps, - info, - env, - } = env; - - let from = if let Some(from) = from { - deps.api.addr_validate(&from)? - } else { - info.sender.clone() - }; - - let batch = self.verify_approvals(deps.storage, &env, &info, &from, batch)?; - - let mut rsp = Response::default(); - let event = self.update_balances(&mut deps, &env, Some(from), None, batch)?; - rsp = rsp.add_event(event); - - Ok(rsp) - } - - pub fn approve_token( - &self, - env: ExecuteEnv, - operator: String, - token_id: String, - amount: Option, - expiration: Option, - ) -> Result, Cw1155ContractError> { - let ExecuteEnv { deps, info, env } = env; - - // reject expired data as invalid - let expiration = expiration.unwrap_or_default(); - if expiration.is_expired(&env.block) { - return Err(Cw1155ContractError::Expired {}); - } - - // get sender's token balance to get valid approval amount - let balance = self - .balances - .load(deps.storage, (info.sender.clone(), token_id.to_string()))?; - let approval_amount = amount.unwrap_or(Uint128::MAX).min(balance.amount); - - // store the approval - let operator = deps.api.addr_validate(&operator)?; - self.token_approves.save( - deps.storage, - (&token_id, &info.sender, &operator), - &TokenApproval { - amount: approval_amount, - expiration, - }, - )?; - - let mut rsp = Response::default(); - - let event = ApproveEvent::new(&info.sender, &operator, &token_id, approval_amount).into(); - rsp = rsp.add_event(event); - - Ok(rsp) - } - - pub fn approve_all( - &self, - env: ExecuteEnv, - operator: String, - expires: Option, - ) -> Result, Cw1155ContractError> { - let ExecuteEnv { deps, info, env } = env; - - // reject expired data as invalid - let expires = expires.unwrap_or_default(); - if expires.is_expired(&env.block) { - return Err(Cw1155ContractError::Expired {}); - } - - // set the operator for us - let operator = deps.api.addr_validate(&operator)?; - self.approves - .save(deps.storage, (&info.sender, &operator), &expires)?; - - let mut rsp = Response::default(); - - let event = ApproveAllEvent::new(&info.sender, &operator).into(); - rsp = rsp.add_event(event); - - Ok(rsp) - } - - pub fn revoke_token( - &self, - env: ExecuteEnv, - operator: String, - token_id: String, - amount: Option, - ) -> Result, Cw1155ContractError> { - let ExecuteEnv { deps, info, .. } = env; - let operator = deps.api.addr_validate(&operator)?; - - // get prev approval amount to get valid revoke amount - let prev_approval = self - .token_approves - .load(deps.storage, (&token_id, &info.sender, &operator))?; - let revoke_amount = amount.unwrap_or(Uint128::MAX).min(prev_approval.amount); - - // remove or update approval - if revoke_amount == prev_approval.amount { - self.token_approves - .remove(deps.storage, (&token_id, &info.sender, &operator)); - } else { - self.token_approves.update( - deps.storage, - (&token_id, &info.sender, &operator), - |prev| -> StdResult<_> { - let mut new_approval = prev.unwrap(); - new_approval.amount = new_approval.amount.checked_sub(revoke_amount)?; - Ok(new_approval) - }, - )?; - } - - let mut rsp = Response::default(); - - let event = RevokeEvent::new(&info.sender, &operator, &token_id, revoke_amount).into(); - rsp = rsp.add_event(event); - - Ok(rsp) - } - - pub fn revoke_all( - &self, - env: ExecuteEnv, - operator: String, - ) -> Result, Cw1155ContractError> { - let ExecuteEnv { deps, info, .. } = env; - let operator_addr = deps.api.addr_validate(&operator)?; - self.approves - .remove(deps.storage, (&info.sender, &operator_addr)); - - let mut rsp = Response::default(); - - let event = RevokeAllEvent::new(&info.sender, &operator_addr).into(); - rsp = rsp.add_event(event); - - Ok(rsp) - } - - /// When from is None: mint new tokens - /// When to is None: burn tokens - /// When both are Some: transfer tokens - /// - /// Make sure permissions are checked before calling this. - fn update_balances( - &self, - deps: &mut DepsMut, - env: &Env, - from: Option, - to: Option, - tokens: Vec, - ) -> Result { - if let Some(from) = &from { - for TokenAmount { token_id, amount } in tokens.iter() { - self.balances.update( - deps.storage, - (from.clone(), token_id.to_string()), - |balance: Option| -> StdResult<_> { - let mut new_balance = balance.unwrap(); - new_balance.amount = new_balance.amount.checked_sub(*amount)?; - Ok(new_balance) - }, - )?; - } - } - - if let Some(to) = &to { - for TokenAmount { token_id, amount } in tokens.iter() { - self.balances.update( - deps.storage, - (to.clone(), token_id.to_string()), - |balance: Option| -> StdResult<_> { - let mut new_balance: Balance = if let Some(balance) = balance { - balance - } else { - Balance { - owner: to.clone(), - amount: Uint128::zero(), - token_id: token_id.to_string(), - } - }; - - new_balance.amount = new_balance.amount.checked_add(*amount)?; - Ok(new_balance) - }, - )?; - } - } - - let event = if let Some(from) = &from { - for TokenAmount { token_id, amount } in &tokens { - // remove token approvals - for (operator, approval) in self - .token_approves - .prefix((token_id, from)) - .range(deps.storage, None, None, Order::Ascending) - .collect::>>()? - { - if approval.is_expired(env) || approval.amount <= *amount { - self.token_approves - .remove(deps.storage, (token_id, from, &operator)); - } else { - self.token_approves.update( - deps.storage, - (token_id, from, &operator), - |prev| -> StdResult<_> { - let mut new_approval = prev.unwrap(); - new_approval.amount = new_approval.amount.checked_sub(*amount)?; - Ok(new_approval) - }, - )?; - } - } - - // decrement tokens if burning - if to.is_none() { - self.decrement_tokens(deps.storage, token_id, amount)?; - } - } - - if let Some(to) = &to { - // transfer - TransferEvent::new(from, to, tokens).into() - } else { - // burn - BurnEvent::new(from, tokens).into() - } - } else if let Some(to) = &to { - // mint - for TokenAmount { token_id, amount } in &tokens { - self.increment_tokens(deps.storage, token_id, amount)?; - } - MintEvent::new(to, tokens).into() - } else { - panic!("Invalid transfer: from and to cannot both be None") - }; - - Ok(event) - } - - /// returns valid token amount if the sender can execute or is approved to execute - pub fn verify_approval( - &self, - storage: &dyn Storage, - env: &Env, - info: &MessageInfo, - owner: &Addr, - token_id: &str, - amount: Uint128, - ) -> Result { - let operator = &info.sender; - - let owner_balance = self - .balances - .load(storage, (owner.clone(), token_id.to_string()))?; - let mut balance_update = TokenAmount { - token_id: token_id.to_string(), - amount: owner_balance.amount.min(amount), - }; - - // owner or all operator can execute - if owner == operator || self.verify_all_approval(storage, env, owner, operator) { - return Ok(balance_update); - } - - // token operator can execute up to approved amount - if let Some(token_approval) = - self.get_active_token_approval(storage, env, owner, operator, token_id) - { - balance_update.amount = balance_update.amount.min(token_approval.amount); - return Ok(balance_update); - } - - Err(Cw1155ContractError::Unauthorized {}) - } - - /// returns valid token amounts if the sender can execute or is approved to execute on all provided tokens - pub fn verify_approvals( - &self, - storage: &dyn Storage, - env: &Env, - info: &MessageInfo, - owner: &Addr, - tokens: Vec, - ) -> Result, Cw1155ContractError> { - tokens - .iter() - .map(|TokenAmount { token_id, amount }| { - self.verify_approval(storage, env, info, owner, token_id, *amount) - }) - .collect() - } - - pub fn verify_all_approval( - &self, - storage: &dyn Storage, - env: &Env, - owner: &Addr, - operator: &Addr, - ) -> bool { - match self.approves.load(storage, (owner, operator)) { - Ok(ex) => !ex.is_expired(&env.block), - Err(_) => false, - } - } - - pub fn get_active_token_approval( - &self, - storage: &dyn Storage, - env: &Env, - owner: &Addr, - operator: &Addr, - token_id: &str, - ) -> Option { - match self - .token_approves - .load(storage, (token_id, owner, operator)) - { - Ok(approval) => { - if !approval.is_expired(env) { - Some(approval) - } else { - None - } - } - Err(_) => None, - } - } - - pub fn update_ownership( - env: ExecuteEnv, - action: cw_ownable::Action, - ) -> Result, Cw1155ContractError> { - let ExecuteEnv { deps, info, env } = env; - let ownership = cw_ownable::update_ownership(deps, &env.block, &info.sender, action)?; - Ok(Response::new().add_attributes(ownership.into_attributes())) - } } diff --git a/contracts/cw1155-base/src/lib.rs b/contracts/cw1155-base/src/lib.rs index 140aba97f..c84273ad4 100644 --- a/contracts/cw1155-base/src/lib.rs +++ b/contracts/cw1155-base/src/lib.rs @@ -5,19 +5,20 @@ pub mod state; pub use crate::state::Cw1155Contract; use cosmwasm_std::Empty; -use cw1155::{Cw1155ExecuteMsg, Cw1155QueryMsg}; +use cw1155::msg::{Cw1155ExecuteMsg, Cw1155QueryMsg}; +use cw1155::state::Cw1155Config; +use cw721::state::DefaultOptionMetadataExtension; // Version info for migration pub const CONTRACT_NAME: &str = "crates.io:cw1155-base"; pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); -pub const EXPECTED_FROM_VERSION: &str = CONTRACT_VERSION; -// This is a simple type to let us handle empty extensions -pub type Extension = Option; -pub type Cw1155BaseExecuteMsg = Cw1155ExecuteMsg; -pub type Cw1155BaseQueryMsg = Cw1155QueryMsg; - -pub type Cw1155BaseContract<'a> = Cw1155Contract<'a, Extension, Empty, Empty, Empty>; +pub type Cw1155BaseContract<'a> = + Cw1155Contract<'a, DefaultOptionMetadataExtension, Empty, Empty, Empty>; +pub type Cw1155BaseExecuteMsg = Cw1155ExecuteMsg; +pub type Cw1155BaseQueryMsg = Cw1155QueryMsg; +pub type Cw1155BaseConfig<'a> = + Cw1155Config<'a, DefaultOptionMetadataExtension, Empty, Empty, Empty>; pub mod entry { use super::*; @@ -25,7 +26,11 @@ pub mod entry { #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; - use cw1155::{Cw1155ContractError, Cw1155InstantiateMsg}; + use cw1155::error::Cw1155ContractError; + use cw1155::execute::Cw1155Execute; + use cw1155::msg::{Cw1155ExecuteMsg, Cw1155InstantiateMsg, Cw1155QueryMsg}; + use cw1155::query::Cw1155Query; + use cw721::state::DefaultOptionMetadataExtension; // This makes a conscious choice on the various generics used by the contract #[cfg_attr(not(feature = "library"), entry_point)] @@ -34,11 +39,9 @@ pub mod entry { env: Env, info: MessageInfo, msg: Cw1155InstantiateMsg, - ) -> StdResult { - cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - + ) -> Result { let tract = Cw1155BaseContract::default(); - tract.instantiate(deps, env, info, msg) + tract.instantiate(deps, env, info, msg, CONTRACT_NAME, CONTRACT_VERSION) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -46,27 +49,25 @@ pub mod entry { deps: DepsMut, env: Env, info: MessageInfo, - msg: Cw1155BaseExecuteMsg, + msg: Cw1155ExecuteMsg, ) -> Result { let tract = Cw1155BaseContract::default(); tract.execute(deps, env, info, msg) } #[cfg_attr(not(feature = "library"), entry_point)] - pub fn query(deps: Deps, env: Env, msg: Cw1155BaseQueryMsg) -> StdResult { + pub fn query( + deps: Deps, + env: Env, + msg: Cw1155QueryMsg, + ) -> StdResult { let tract = Cw1155BaseContract::default(); tract.query(deps, env, msg) } #[cfg_attr(not(feature = "library"), entry_point)] - pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> Result { - // make sure the correct contract is being upgraded, and it's being - // upgraded from the correct version. - cw2::assert_contract_version(deps.as_ref().storage, CONTRACT_NAME, EXPECTED_FROM_VERSION)?; - - // update contract version - cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - - Ok(Response::default()) + pub fn migrate(deps: DepsMut, env: Env, msg: Empty) -> Result { + let contract = Cw1155BaseContract::default(); + contract.migrate(deps, env, msg, CONTRACT_NAME, CONTRACT_VERSION) } } diff --git a/contracts/cw1155-base/src/query.rs b/contracts/cw1155-base/src/query.rs index b0c27a512..39e689d28 100644 --- a/contracts/cw1155-base/src/query.rs +++ b/contracts/cw1155-base/src/query.rs @@ -1,260 +1,50 @@ +use crate::Cw1155Contract; +use cosmwasm_std::CustomMsg; +use cw1155::query::Cw1155Query; +use cw721::query::Cw721Query; use serde::de::DeserializeOwned; use serde::Serialize; -use cosmwasm_std::{ - to_json_binary, Addr, Binary, CustomMsg, Deps, Empty, Env, Order, StdResult, Uint128, -}; - -use cw1155::{ - Approval, ApprovedForAllResponse, Balance, BalanceResponse, BalancesResponse, Cw1155QueryMsg, - Expiration, IsApprovedForAllResponse, NumTokensResponse, OwnerToken, TokenInfoResponse, - TokensResponse, -}; -use cw721_base::Cw721Contract; -use cw_storage_plus::Bound; -use cw_utils::maybe_addr; - -use crate::state::Cw1155Contract; - -const DEFAULT_LIMIT: u32 = 10; -const MAX_LIMIT: u32 = 100; - -impl<'a, T, C, E, Q> Cw1155Contract<'a, T, C, E, Q> +impl<'a, TMetadataExtension, TCustomResponseMessage, TMetadataExtensionMsg, TQueryExtensionMsg> + Cw1155Query< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + > + for Cw1155Contract< + 'a, + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + > where - T: Serialize + DeserializeOwned + Clone, - C: CustomMsg, - E: CustomMsg, - Q: CustomMsg, + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TCustomResponseMessage: CustomMsg, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, { - pub fn query(&self, deps: Deps, env: Env, msg: Cw1155QueryMsg) -> StdResult { - match msg { - Cw1155QueryMsg::Minter {} => { - let tract = Cw721Contract::::default(); - to_json_binary(&tract.minter(deps)?) - } - Cw1155QueryMsg::BalanceOf(OwnerToken { owner, token_id }) => { - let owner_addr = deps.api.addr_validate(&owner)?; - let balance = self - .balances - .may_load(deps.storage, (owner_addr.clone(), token_id.clone()))? - .unwrap_or(Balance { - owner: owner_addr, - token_id, - amount: Uint128::new(0), - }); - to_json_binary(&BalanceResponse { - balance: balance.amount, - }) - } - Cw1155QueryMsg::AllBalances { - token_id, - start_after, - limit, - } => to_json_binary(&self.query_all_balances(deps, token_id, start_after, limit)?), - Cw1155QueryMsg::BalanceOfBatch(batch) => { - let balances = batch - .into_iter() - .map(|OwnerToken { owner, token_id }| { - let owner = Addr::unchecked(owner); - self.balances - .load(deps.storage, (owner.clone(), token_id.to_string())) - .unwrap_or(Balance { - owner, - token_id, - amount: Uint128::zero(), - }) - }) - .collect::>(); - to_json_binary(&BalancesResponse { balances }) - } - Cw1155QueryMsg::TokenApprovals { - owner, - token_id, - include_expired, - } => { - let owner = deps.api.addr_validate(&owner)?; - let approvals = self - .token_approves - .prefix((&token_id, &owner)) - .range(deps.storage, None, None, Order::Ascending) - .filter_map(|approval| { - let (_, approval) = approval.unwrap(); - if include_expired.unwrap_or(false) || !approval.is_expired(&env) { - Some(approval) - } else { - None - } - }) - .collect::>(); - to_json_binary(&approvals) - } - Cw1155QueryMsg::ApprovalsForAll { - owner, - include_expired, - start_after, - limit, - } => { - let owner_addr = deps.api.addr_validate(&owner)?; - let start_addr = maybe_addr(deps.api, start_after)?; - to_json_binary(&self.query_all_approvals( - deps, - env, - owner_addr, - include_expired.unwrap_or(false), - start_addr, - limit, - )?) - } - Cw1155QueryMsg::IsApprovedForAll { owner, operator } => { - let owner_addr = deps.api.addr_validate(&owner)?; - let operator_addr = deps.api.addr_validate(&operator)?; - let approved = - self.verify_all_approval(deps.storage, &env, &owner_addr, &operator_addr); - to_json_binary(&IsApprovedForAllResponse { approved }) - } - Cw1155QueryMsg::TokenInfo { token_id } => { - let token_info = self.tokens.load(deps.storage, &token_id)?; - to_json_binary(&TokenInfoResponse:: { - token_uri: token_info.token_uri, - extension: token_info.extension, - }) - } - Cw1155QueryMsg::Tokens { - owner, - start_after, - limit, - } => { - let owner_addr = deps.api.addr_validate(&owner)?; - to_json_binary(&self.query_owner_tokens(deps, owner_addr, start_after, limit)?) - } - Cw1155QueryMsg::ContractInfo {} => { - to_json_binary(&self.contract_info.load(deps.storage)?) - } - Cw1155QueryMsg::NumTokens { token_id } => { - let count = if let Some(token_id) = token_id { - self.token_count(deps.storage, &token_id)? - } else { - self.supply.load(deps.storage)? - }; - to_json_binary(&NumTokensResponse { count }) - } - Cw1155QueryMsg::AllTokens { start_after, limit } => { - to_json_binary(&self.query_all_tokens(deps, start_after, limit)?) - } - Cw1155QueryMsg::Ownership {} => { - to_json_binary(&cw_ownable::get_ownership(deps.storage)?) - } - - Cw1155QueryMsg::Extension { .. } => { - unimplemented!() - } - } - } } -impl<'a, T, C, E, Q> Cw1155Contract<'a, T, C, E, Q> +impl<'a, TMetadataExtension, TCustomResponseMessage, TMetadataExtensionMsg, TQueryExtensionMsg> + Cw721Query< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + > + for Cw1155Contract< + 'a, + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + > where - T: Serialize + DeserializeOwned + Clone, - C: CustomMsg, - E: CustomMsg, - Q: CustomMsg, + TCustomResponseMessage: CustomMsg, + TMetadataExtension: Clone + DeserializeOwned + Serialize, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, { - fn query_all_approvals( - &self, - deps: Deps, - env: Env, - owner: Addr, - include_expired: bool, - start_after: Option, - limit: Option, - ) -> StdResult { - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let start = start_after.as_ref().map(Bound::exclusive); - - let operators = self - .approves - .prefix(&owner) - .range(deps.storage, start, None, Order::Ascending) - .filter(|r| { - include_expired || r.is_err() || !r.as_ref().unwrap().1.is_expired(&env.block) - }) - .take(limit) - .map(build_approval) - .collect::>()?; - Ok(ApprovedForAllResponse { operators }) - } - - fn query_owner_tokens( - &self, - deps: Deps, - owner: Addr, - start_after: Option, - limit: Option, - ) -> StdResult { - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let start = start_after.as_ref().map(|s| Bound::exclusive(s.as_str())); - - let tokens = self - .balances - .prefix(owner) - .keys(deps.storage, start, None, Order::Ascending) - .take(limit) - .collect::>()?; - Ok(TokensResponse { tokens }) - } - - fn query_all_tokens( - &self, - deps: Deps, - start_after: Option, - limit: Option, - ) -> StdResult { - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let start = start_after.as_ref().map(|s| Bound::exclusive(s.as_str())); - let tokens = self - .tokens - .keys(deps.storage, start, None, Order::Ascending) - .take(limit) - .collect::>()?; - Ok(TokensResponse { tokens }) - } - - fn query_all_balances( - &self, - deps: Deps, - token_id: String, - start_after: Option, - limit: Option, - ) -> StdResult { - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - - let start = if let Some(start_after) = start_after { - let start_key = (Addr::unchecked(start_after), token_id.clone()); - Some(Bound::exclusive::<(Addr, String)>(start_key)) - } else { - None - }; - - let balances: Vec = self - .balances - .idx - .token_id - .prefix(token_id) - .range(deps.storage, start, None, Order::Ascending) - .take(limit) - .map(|item| { - let (_, v) = item.unwrap(); - v - }) - .collect(); - - Ok(BalancesResponse { balances }) - } -} - -fn build_approval(item: StdResult<(Addr, Expiration)>) -> StdResult { - item.map(|(addr, expires)| Approval { - spender: addr.into(), - expires, - }) } diff --git a/contracts/cw1155-base/src/state.rs b/contracts/cw1155-base/src/state.rs index fca601eae..89734b62a 100644 --- a/contracts/cw1155-base/src/state.rs +++ b/contracts/cw1155-base/src/state.rs @@ -1,157 +1,48 @@ -use cosmwasm_schema::cw_serde; +use cosmwasm_std::CustomMsg; +use cw1155::state::Cw1155Config; use serde::de::DeserializeOwned; use serde::Serialize; -use std::marker::PhantomData; -use cosmwasm_std::{Addr, CustomMsg, StdError, StdResult, Storage, Uint128}; - -use cw1155::{Balance, Expiration, TokenApproval}; -use cw721::ContractInfoResponse; -use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, MultiIndex}; - -pub struct Cw1155Contract<'a, T, C, E, Q> -where - T: Serialize + DeserializeOwned + Clone, - E: CustomMsg, - Q: CustomMsg, +pub struct Cw1155Contract< + 'a, + // Metadata defined in NftInfo (used for mint). + TMetadataExtension, + // Defines for `CosmosMsg::Custom` in response. Barely used, so `Empty` can be used. + TCustomResponseMessage, + // Message passed for updating metadata. + TMetadataExtensionMsg, + // Extension query message. + TQueryExtensionMsg, +> where + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, { - pub contract_info: Item<'a, ContractInfoResponse>, - pub supply: Item<'a, Uint128>, // total supply of all tokens - // key: token id - pub token_count: Map<'a, &'a str, Uint128>, // total supply of a specific token - // key: (owner, token id) - pub balances: IndexedMap<'a, (Addr, String), Balance, BalanceIndexes<'a>>, - // key: (owner, spender) - pub approves: Map<'a, (&'a Addr, &'a Addr), Expiration>, - // key: (token id, owner, spender) - pub token_approves: Map<'a, (&'a str, &'a Addr, &'a Addr), TokenApproval>, - // key: token id - pub tokens: Map<'a, &'a str, TokenInfo>, - - pub(crate) _custom_response: PhantomData, - pub(crate) _custom_query: PhantomData, - pub(crate) _custom_execute: PhantomData, + pub config: Cw1155Config< + 'a, + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >, } -impl<'a, T, C, E, Q> Default for Cw1155Contract<'a, T, C, E, Q> +impl Default + for Cw1155Contract< + 'static, + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + > where - T: Serialize + DeserializeOwned + Clone, - C: CustomMsg, - E: CustomMsg, - Q: CustomMsg, + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, { fn default() -> Self { - Self::new( - "cw1155_contract_info", - "tokens", - "token_count", - "supply", - "balances", - "balances__token_id", - "approves", - "token_approves", - ) - } -} - -impl<'a, T, C, E, Q> Cw1155Contract<'a, T, C, E, Q> -where - T: Serialize + DeserializeOwned + Clone, - C: CustomMsg, - E: CustomMsg, - Q: CustomMsg, -{ - #[allow(clippy::too_many_arguments)] - fn new( - contract_info_key: &'a str, - tokens_key: &'a str, - token_count_key: &'a str, - supply_key: &'a str, - balances_key: &'a str, - balances_token_id_key: &'a str, - approves_key: &'a str, - token_approves_key: &'a str, - ) -> Self { - let balances_indexes = BalanceIndexes { - token_id: MultiIndex::new( - |_, b| b.token_id.to_string(), - balances_key, - balances_token_id_key, - ), - }; Self { - contract_info: Item::new(contract_info_key), - tokens: Map::new(tokens_key), - token_count: Map::new(token_count_key), - supply: Item::new(supply_key), - balances: IndexedMap::new(balances_key, balances_indexes), - approves: Map::new(approves_key), - token_approves: Map::new(token_approves_key), - _custom_execute: PhantomData, - _custom_query: PhantomData, - _custom_response: PhantomData, + config: Cw1155Config::default(), } } - - pub fn token_count(&self, storage: &dyn Storage, token_id: &'a str) -> StdResult { - Ok(self - .token_count - .may_load(storage, token_id)? - .unwrap_or_default()) - } - - pub fn increment_tokens( - &self, - storage: &mut dyn Storage, - token_id: &'a str, - amount: &Uint128, - ) -> StdResult { - // increment token count - let val = self.token_count(storage, token_id)? + amount; - self.token_count.save(storage, token_id, &val)?; - - // increment total supply - self.supply.update(storage, |prev| { - Ok::(prev.checked_add(*amount)?) - })?; - - Ok(val) - } - - pub fn decrement_tokens( - &self, - storage: &mut dyn Storage, - token_id: &'a str, - amount: &Uint128, - ) -> StdResult { - // decrement token count - let val = self.token_count(storage, token_id)?.checked_sub(*amount)?; - self.token_count.save(storage, token_id, &val)?; - - // decrement total supply - self.supply.update(storage, |prev| { - Ok::(prev.checked_sub(*amount)?) - })?; - - Ok(val) - } -} - -#[cw_serde] -pub struct TokenInfo { - /// Metadata JSON Schema - pub token_uri: Option, - /// You can add any custom metadata here when you extend cw1155-base - pub extension: T, -} - -pub struct BalanceIndexes<'a> { - pub token_id: MultiIndex<'a, String, Balance, (Addr, String)>, -} - -impl<'a> IndexList for BalanceIndexes<'a> { - fn get_indexes(&'_ self) -> Box> + '_> { - let v: Vec<&dyn Index> = vec![&self.token_id]; - Box::new(v.into_iter()) - } } diff --git a/contracts/cw1155-metadata-onchain/.cargo/config b/contracts/cw1155-metadata-onchain/.cargo/config deleted file mode 100644 index 7d1a066c8..000000000 --- a/contracts/cw1155-metadata-onchain/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib" -schema = "run --example schema" diff --git a/contracts/cw1155-metadata-onchain/Cargo.toml b/contracts/cw1155-metadata-onchain/Cargo.toml deleted file mode 100644 index b31f6901b..000000000 --- a/contracts/cw1155-metadata-onchain/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "cw1155-metadata-onchain" -authors = ["shab "] -description = "Example extending CW1155 Token to store metadata on chain" -version = { workspace = true } -edition = { workspace = true } -license = { workspace = true } -repository = { workspace = true } -homepage = { workspace = true } -documentation = { workspace = true } - -exclude = [ - # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. - "artifacts/*", -] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] -# use library feature to disable all instantiate/execute/query exports -library = [] - -[dependencies] -cw2 = { workspace = true } -cw1155 = { workspace = true } -cw1155-base = { workspace = true, features = ["library"] } -cosmwasm-std = { workspace = true } -schemars = { workspace = true } -serde = { workspace = true } -thiserror = { workspace = true } -cosmwasm-schema = { workspace = true } - diff --git a/contracts/cw1155-metadata-onchain/README.md b/contracts/cw1155-metadata-onchain/README.md deleted file mode 100644 index fcc7d2e7a..000000000 --- a/contracts/cw1155-metadata-onchain/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# CW1155 Metadata Onchain - -Token creators may want to store their token metadata on-chain so other contracts are able to interact with it. -With CW1155-Base in CosmWasm, we allow you to store any data on chain you wish, using a generic `extension: T`. - -In order to support on-chain metadata, and to demonstrate how to use the extension ability, we have created this simple contract. -There is no business logic here, but looking at `lib.rs` will show you how do define custom data that is included when minting and -available in all queries. - -In particular, here we define: - -```rust -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] -pub struct Trait { - pub display_type: Option, - pub trait_type: String, - pub value: String, -} - -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] -pub struct Metadata { - pub image: Option, - pub image_data: Option, - pub external_url: Option, - pub description: Option, - pub name: Option, - pub attributes: Option>, - pub background_color: Option, - pub animation_url: Option, - pub youtube_url: Option, -} - -pub type Extension = Option; -``` - -In particular, the fields defined conform to the properties supported in the [OpenSea Metadata Standard](https://docs.opensea.io/docs/metadata-standards). - - -This means when you query `NftInfo{name: "Enterprise"}`, you will get something like: - -```json -{ - "name": "Enterprise", - "token_uri": "https://starships.example.com/Starship/Enterprise.json", - "extension": { - "image": null, - "image_data": null, - "external_url": null, - "description": "Spaceship with Warp Drive", - "name": "Starship USS Enterprise", - "attributes": null, - "background_color": null, - "animation_url": null, - "youtube_url": null - } -} -``` - -Please look at the test code for an example usage in Rust. - -## Notice - -Feel free to use this contract out of the box, or as inspiration for further customization of cw1155-base. -We will not be adding new features or business logic here. diff --git a/contracts/cw1155-metadata-onchain/examples/schema.rs b/contracts/cw1155-metadata-onchain/examples/schema.rs deleted file mode 100644 index 5f0fa8ddd..000000000 --- a/contracts/cw1155-metadata-onchain/examples/schema.rs +++ /dev/null @@ -1,13 +0,0 @@ -use cosmwasm_schema::write_api; -use cosmwasm_std::Empty; - -use cw1155::{Cw1155InstantiateMsg, Cw1155QueryMsg}; -use cw1155_metadata_onchain::Cw1155MetadataExecuteMsg; - -fn main() { - write_api! { - instantiate: Cw1155InstantiateMsg, - execute: Cw1155MetadataExecuteMsg, - query: Cw1155QueryMsg, - } -} diff --git a/contracts/cw1155-metadata-onchain/src/lib.rs b/contracts/cw1155-metadata-onchain/src/lib.rs deleted file mode 100644 index 7faa47104..000000000 --- a/contracts/cw1155-metadata-onchain/src/lib.rs +++ /dev/null @@ -1,138 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::Empty; - -use cw1155::Cw1155ExecuteMsg; -pub use cw1155::{Cw1155ContractError, Cw1155InstantiateMsg, Cw1155MintMsg}; -use cw2::set_contract_version; - -// Version info for migration -const CONTRACT_NAME: &str = "crates.io:cw1155-metadata-onchain"; -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -#[cw_serde] -pub struct Trait { - pub display_type: Option, - pub trait_type: String, - pub value: String, -} - -// see: https://docs.opensea.io/docs/metadata-standards -#[cw_serde] -#[derive(Default)] -pub struct Metadata { - pub image: Option, - pub image_data: Option, - pub external_url: Option, - pub description: Option, - pub name: Option, - pub attributes: Option>, - pub background_color: Option, - pub animation_url: Option, - pub youtube_url: Option, -} - -pub type Extension = Option; - -pub type Cw1155MetadataContract<'a> = - cw1155_base::Cw1155Contract<'a, Extension, Empty, Empty, Empty>; -pub type Cw1155MetadataExecuteMsg = Cw1155ExecuteMsg; - -#[cfg(not(feature = "library"))] -pub mod entry { - use super::*; - - use cosmwasm_std::entry_point; - use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; - use cw1155::Cw1155QueryMsg; - - // This makes a conscious choice on the various generics used by the contract - #[entry_point] - pub fn instantiate( - mut deps: DepsMut, - env: Env, - info: MessageInfo, - msg: Cw1155InstantiateMsg, - ) -> Result { - let res = Cw1155MetadataContract::default().instantiate(deps.branch(), env, info, msg)?; - // Explicitly set contract name and version, otherwise set to cw1155-base info - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION) - .map_err(Cw1155ContractError::Std)?; - Ok(res) - } - - #[entry_point] - pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: Cw1155MetadataExecuteMsg, - ) -> Result { - Cw1155MetadataContract::default().execute(deps, env, info, msg) - } - - #[entry_point] - pub fn query(deps: Deps, env: Env, msg: Cw1155QueryMsg) -> StdResult { - Cw1155MetadataContract::default().query(deps, env, msg) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; - use cosmwasm_std::{from_json, Uint128}; - - const CREATOR: &str = "creator"; - - #[test] - fn use_metadata_extension() { - let mut deps = mock_dependencies(); - let contract = Cw1155MetadataContract::default(); - - let info = mock_info(CREATOR, &[]); - let init_msg = Cw1155InstantiateMsg { - name: "name".to_string(), - symbol: "symbol".to_string(), - minter: Some(CREATOR.to_string()), - }; - contract - .instantiate(deps.as_mut(), mock_env(), info.clone(), init_msg) - .unwrap(); - - let token_id = "Enterprise"; - let mint_msg = Cw1155MintMsg { - token_id: token_id.to_string(), - amount: Uint128::new(1), - token_uri: Some("https://starships.example.com/Starship/Enterprise.json".into()), - extension: Some(Metadata { - description: Some("Spaceship with Warp Drive".into()), - name: Some("Starship USS Enterprise".to_string()), - ..Metadata::default() - }), - }; - let exec_msg = Cw1155MetadataExecuteMsg::Mint { - recipient: "john".to_string(), - msg: mint_msg.clone(), - }; - contract - .execute(deps.as_mut(), mock_env(), info, exec_msg) - .unwrap(); - - let res: cw1155::TokenInfoResponse = from_json( - contract - .query( - deps.as_ref(), - mock_env(), - cw1155::Cw1155QueryMsg::TokenInfo { - token_id: token_id.to_string(), - }, - ) - .unwrap(), - ) - .unwrap(); - - assert_eq!(res.token_uri, mint_msg.token_uri); - assert_eq!(res.extension, mint_msg.extension); - } -} diff --git a/contracts/cw1155-royalties/examples/schema.rs b/contracts/cw1155-royalties/examples/schema.rs index 087a49ebb..6b7a4aa60 100644 --- a/contracts/cw1155-royalties/examples/schema.rs +++ b/contracts/cw1155-royalties/examples/schema.rs @@ -1,6 +1,5 @@ use cosmwasm_schema::write_api; - -use cw1155::Cw1155InstantiateMsg; +use cw1155::msg::Cw1155InstantiateMsg; use cw1155_royalties::{Cw1155RoyaltiesExecuteMsg, Cw1155RoyaltiesQueryMsg}; fn main() { diff --git a/contracts/cw1155-royalties/src/error.rs b/contracts/cw1155-royalties/src/error.rs index 9099c9771..457f46411 100644 --- a/contracts/cw1155-royalties/src/error.rs +++ b/contracts/cw1155-royalties/src/error.rs @@ -1,5 +1,5 @@ use cosmwasm_std::StdError; -use cw1155::Cw1155ContractError; +use cw1155::error::Cw1155ContractError; use thiserror::Error; #[derive(Debug, Error, PartialEq)] diff --git a/contracts/cw1155-royalties/src/lib.rs b/contracts/cw1155-royalties/src/lib.rs index 1553c97c3..9245212bb 100644 --- a/contracts/cw1155-royalties/src/lib.rs +++ b/contracts/cw1155-royalties/src/lib.rs @@ -1,9 +1,8 @@ use cosmwasm_std::Empty; -use cw1155::{Cw1155ExecuteMsg, Cw1155QueryMsg}; -pub use cw1155::{Cw1155InstantiateMsg, Cw1155MintMsg}; +use cw1155::msg::{Cw1155ExecuteMsg, Cw1155QueryMsg}; +use cw1155::state::Cw1155Config; use cw1155_base::Cw1155Contract; -use cw2::set_contract_version; -use cw2981_royalties::msg::Cw2981QueryMsg; +use cw2981_royalties::msg::QueryMsg as Cw2981QueryMsg; use cw2981_royalties::Extension; mod query; @@ -18,7 +17,8 @@ const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); pub type Cw1155RoyaltiesContract<'a> = Cw1155Contract<'a, Extension, Empty, Empty, Cw2981QueryMsg>; pub type Cw1155RoyaltiesExecuteMsg = Cw1155ExecuteMsg; -pub type Cw1155RoyaltiesQueryMsg = Cw1155QueryMsg; +pub type Cw1155RoyaltiesQueryMsg = Cw1155QueryMsg; +pub type Cw1155RoyaltiesConfig<'a> = Cw1155Config<'a, Extension, Empty, Empty, Cw2981QueryMsg>; #[cfg(not(feature = "library"))] pub mod entry { @@ -26,7 +26,9 @@ pub mod entry { use cosmwasm_std::{entry_point, to_json_binary}; use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; - use cw2981_royalties::msg::Cw2981QueryMsg; + use cw1155::execute::Cw1155Execute; + use cw1155::query::Cw1155Query; + use cw2981_royalties::msg::QueryMsg as Cw2981QueryMsg; use cw2981_royalties::{check_royalties, Metadata}; // This makes a conscious choice on the various generics used by the contract @@ -35,13 +37,18 @@ pub mod entry { mut deps: DepsMut, env: Env, info: MessageInfo, - msg: Cw1155InstantiateMsg, + msg: cw1155::msg::Cw1155InstantiateMsg, ) -> Result { - let res = Cw1155RoyaltiesContract::default().instantiate(deps.branch(), env, info, msg)?; - // Explicitly set contract name and version, otherwise set to cw1155-base info - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION) - .map_err(Cw1155RoyaltiesContractError::Std)?; - Ok(res) + Cw1155RoyaltiesContract::default() + .instantiate( + deps.branch(), + env, + info, + msg, + CONTRACT_NAME, + CONTRACT_VERSION, + ) + .map_err(Into::into) } #[entry_point] @@ -53,7 +60,7 @@ pub mod entry { ) -> Result { if let Cw1155RoyaltiesExecuteMsg::Mint { msg: - Cw1155MintMsg { + cw1155::msg::Cw1155MintMsg { extension: Some(Metadata { royalty_percentage: Some(royalty_percentage), @@ -84,12 +91,13 @@ pub mod entry { #[entry_point] pub fn query(deps: Deps, env: Env, msg: Cw1155RoyaltiesQueryMsg) -> StdResult { match msg { - Cw1155RoyaltiesQueryMsg::Extension { msg: ext_msg } => match ext_msg { + Cw1155RoyaltiesQueryMsg::Extension { msg: ext_msg, .. } => match ext_msg { Cw2981QueryMsg::RoyaltyInfo { token_id, sale_price, } => to_json_binary(&query_royalties_info(deps, token_id, sale_price)?), Cw2981QueryMsg::CheckRoyalties {} => to_json_binary(&check_royalties(deps)?), + _ => unimplemented!(), }, _ => Cw1155RoyaltiesContract::default().query(deps, env, msg), } @@ -103,6 +111,7 @@ mod tests { use cosmwasm_std::{from_json, Uint128}; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cw1155::msg::{Cw1155InstantiateMsg, Cw1155MintMsg}; use cw2981_royalties::msg::{CheckRoyaltiesResponse, RoyaltiesInfoResponse}; use cw2981_royalties::{check_royalties, Metadata}; @@ -111,7 +120,7 @@ mod tests { #[test] fn use_metadata_extension() { let mut deps = mock_dependencies(); - let contract = Cw1155RoyaltiesContract::default(); + let config = Cw1155RoyaltiesConfig::default(); let info = mock_info(CREATOR, &[]); let init_msg = Cw1155InstantiateMsg { @@ -139,7 +148,7 @@ mod tests { }; entry::execute(deps.as_mut(), mock_env(), info, exec_msg).unwrap(); - let res = contract.tokens.load(&deps.storage, token_id).unwrap(); + let res = config.tokens.load(&deps.storage, token_id).unwrap(); assert_eq!(res.token_uri, token_uri); assert_eq!(res.extension, extension); } @@ -215,6 +224,7 @@ mod tests { // also check the longhand way let query_msg = Cw1155RoyaltiesQueryMsg::Extension { msg: Cw2981QueryMsg::CheckRoyalties {}, + phantom: None, }; let query_res: CheckRoyaltiesResponse = from_json(entry::query(deps.as_ref(), mock_env(), query_msg).unwrap()).unwrap(); @@ -266,6 +276,7 @@ mod tests { token_id: token_id.to_string(), sale_price: Uint128::new(100), }, + phantom: None, }; let query_res: RoyaltiesInfoResponse = from_json(entry::query(deps.as_ref(), mock_env(), query_msg).unwrap()).unwrap(); diff --git a/contracts/cw1155-royalties/src/query.rs b/contracts/cw1155-royalties/src/query.rs index dfd90a300..17e44e58a 100644 --- a/contracts/cw1155-royalties/src/query.rs +++ b/contracts/cw1155-royalties/src/query.rs @@ -1,4 +1,4 @@ -use crate::Cw1155RoyaltiesContract; +use crate::Cw1155RoyaltiesConfig; use cosmwasm_std::{Decimal, Deps, StdResult, Uint128}; use cw2981_royalties::msg::RoyaltiesInfoResponse; @@ -10,8 +10,8 @@ pub fn query_royalties_info( token_id: String, sale_price: Uint128, ) -> StdResult { - let contract = Cw1155RoyaltiesContract::default(); - let token_info = contract.tokens.load(deps.storage, &token_id)?; + let config = Cw1155RoyaltiesConfig::default(); + let token_info = config.tokens.load(deps.storage, &token_id)?; let royalty_percentage = match token_info.extension { Some(ref ext) => match ext.royalty_percentage { diff --git a/contracts/cw2981-royalties/Cargo.toml b/contracts/cw2981-royalties/Cargo.toml index d9140bc37..e66cb6284 100644 --- a/contracts/cw2981-royalties/Cargo.toml +++ b/contracts/cw2981-royalties/Cargo.toml @@ -21,6 +21,7 @@ library = [] [dependencies] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } +cw-ownable = { workspace = true } cw2 = { workspace = true } cw721 = { workspace = true } cw721-base = { workspace = true, features = ["library"] } diff --git a/contracts/cw2981-royalties/examples/schema.rs b/contracts/cw2981-royalties/examples/schema.rs index f3da971f8..055a210ad 100644 --- a/contracts/cw2981-royalties/examples/schema.rs +++ b/contracts/cw2981-royalties/examples/schema.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::write_api; -use cw2981_royalties::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use cw2981_royalties::{msg::QueryMsg, ExecuteMsg, InstantiateMsg}; fn main() { write_api! { diff --git a/contracts/cw2981-royalties/schema/cw2981-royalties.json b/contracts/cw2981-royalties/schema/cw2981-royalties.json index 5ac8e2923..3040363db 100644 --- a/contracts/cw2981-royalties/schema/cw2981-royalties.json +++ b/contracts/cw2981-royalties/schema/cw2981-royalties.json @@ -1,6 +1,6 @@ { "contract_name": "cw2981-royalties", - "contract_version": "0.18.0", + "contract_version": "0.19.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -38,8 +38,19 @@ "execute": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "ExecuteMsg", - "description": "This is like Cw721ExecuteMsg but we add a Mint command for an owner to make this stand-alone. You will likely want to remove mint and use other control logic in any contract that inherits this.", "oneOf": [ + { + "type": "object", + "required": [ + "update_ownership" + ], + "properties": { + "update_ownership": { + "$ref": "#/definitions/Action" + } + }, + "additionalProperties": false + }, { "description": "Transfer is a base message to move a token to another account without triggering actions", "type": "object", @@ -359,19 +370,6 @@ } }, "additionalProperties": false - }, - { - "description": "Update the contract's ownership. The `action` to be provided can be either to propose transferring ownership to an account, accept a pending ownership transfer, or renounce the ownership permanently.", - "type": "object", - "required": [ - "update_ownership" - ], - "properties": { - "update_ownership": { - "$ref": "#/definitions/Action" - } - }, - "additionalProperties": false } ], "definitions": { @@ -619,6 +617,46 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "QueryMsg", "oneOf": [ + { + "description": "Should be called on sale to see if royalties are owed by the marketplace selling the NFT, if CheckRoyalties returns true See https://eips.ethereum.org/EIPS/eip-2981", + "type": "object", + "required": [ + "royalty_info" + ], + "properties": { + "royalty_info": { + "type": "object", + "required": [ + "sale_price", + "token_id" + ], + "properties": { + "sale_price": { + "$ref": "#/definitions/Uint128" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Called against contract to determine if this NFT implements royalties. Should return a boolean as part of CheckRoyaltiesResponse - default can simply be true if royalties are implemented at token level (i.e. always check on sale)", + "type": "object", + "required": [ + "check_royalties" + ], + "properties": { + "check_royalties": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Return the owner of the given token, error if token does not exist", "type": "object", @@ -798,7 +836,6 @@ "additionalProperties": false }, { - "description": "With MetaData Extension. Returns top-level metadata about the contract", "type": "object", "required": [ "contract_info" @@ -811,6 +848,33 @@ }, "additionalProperties": false }, + { + "description": "With MetaData Extension. Returns top-level metadata about the contract", + "type": "object", + "required": [ + "get_collection_info" + ], + "properties": { + "get_collection_info": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ownership" + ], + "properties": { + "ownership": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "With MetaData Extension. Returns metadata about one particular token, based on *ERC721 Metadata JSON Schema* but directly from the contract", "type": "object", @@ -942,28 +1006,6 @@ }, "additionalProperties": false }, - { - "description": "Extension query", - "type": "object", - "required": [ - "extension" - ], - "properties": { - "extension": { - "type": "object", - "required": [ - "msg" - ], - "properties": { - "msg": { - "$ref": "#/definitions/Cw2981QueryMsg" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, { "type": "object", "required": [ @@ -978,14 +1020,25 @@ "additionalProperties": false }, { - "description": "Query the contract's ownership information", "type": "object", "required": [ - "ownership" + "extension" ], "properties": { - "ownership": { + "extension": { "type": "object", + "properties": { + "msg": { + "anyOf": [ + { + "$ref": "#/definitions/Metadata" + }, + { + "type": "null" + } + ] + } + }, "additionalProperties": false } }, @@ -993,49 +1046,106 @@ } ], "definitions": { - "Cw2981QueryMsg": { - "oneOf": [ - { - "description": "Should be called on sale to see if royalties are owed by the marketplace selling the NFT, if CheckRoyalties returns true See https://eips.ethereum.org/EIPS/eip-2981", - "type": "object", - "required": [ - "royalty_info" + "Metadata": { + "type": "object", + "properties": { + "animation_url": { + "type": [ + "string", + "null" + ] + }, + "attributes": { + "type": [ + "array", + "null" ], - "properties": { - "royalty_info": { - "type": "object", - "required": [ - "sale_price", - "token_id" - ], - "properties": { - "sale_price": { - "$ref": "#/definitions/Uint128" - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false + "items": { + "$ref": "#/definitions/Trait" + } }, - { - "description": "Called against contract to determine if this NFT implements royalties. Should return a boolean as part of CheckRoyaltiesResponse - default can simply be true if royalties are implemented at token level (i.e. always check on sale)", - "type": "object", - "required": [ - "check_royalties" + "background_color": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "external_url": { + "type": [ + "string", + "null" + ] + }, + "image": { + "type": [ + "string", + "null" + ] + }, + "image_data": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "royalty_payment_address": { + "description": "The payment address, may be different to or the same as the minter addr question: how do we validate this?", + "type": [ + "string", + "null" + ] + }, + "royalty_percentage": { + "description": "This is how much the minter takes as a cut when sold royalties are owed on this token if it is Some", + "type": [ + "integer", + "null" ], - "properties": { - "check_royalties": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false + "format": "uint64", + "minimum": 0.0 + }, + "youtube_url": { + "type": [ + "string", + "null" + ] } - ] + }, + "additionalProperties": false + }, + "Trait": { + "type": "object", + "required": [ + "trait_type", + "value" + ], + "properties": { + "display_type": { + "type": [ + "string", + "null" + ] + }, + "trait_type": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", @@ -1048,7 +1158,7 @@ "responses": { "all_nft_info": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AllNftInfoResponse_for_Cw2981QueryMsg", + "title": "AllNftInfoResponse_for_Nullable_Metadata", "type": "object", "required": [ "access", @@ -1067,13 +1177,17 @@ "description": "Data on the token itself,", "allOf": [ { - "$ref": "#/definitions/NftInfoResponse_for_Cw2981QueryMsg" + "$ref": "#/definitions/NftInfoResponse_for_Nullable_Metadata" } ] } }, "additionalProperties": false, "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, "Approval": { "type": "object", "required": [ @@ -1091,63 +1205,23 @@ }, "spender": { "description": "Account that can transfer/send the token", - "type": "string" + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] } }, "additionalProperties": false }, - "Cw2981QueryMsg": { + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", "oneOf": [ { - "description": "Should be called on sale to see if royalties are owed by the marketplace selling the NFT, if CheckRoyalties returns true See https://eips.ethereum.org/EIPS/eip-2981", + "description": "AtHeight will expire when `env.block.height` >= height", "type": "object", "required": [ - "royalty_info" - ], - "properties": { - "royalty_info": { - "type": "object", - "required": [ - "sale_price", - "token_id" - ], - "properties": { - "sale_price": { - "$ref": "#/definitions/Uint128" - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Called against contract to determine if this NFT implements royalties. Should return a boolean as part of CheckRoyaltiesResponse - default can simply be true if royalties are implemented at token level (i.e. always check on sale)", - "type": "object", - "required": [ - "check_royalties" - ], - "properties": { - "check_royalties": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" + "at_height" ], "properties": { "at_height": { @@ -1187,17 +1261,96 @@ } ] }, - "NftInfoResponse_for_Cw2981QueryMsg": { + "Metadata": { + "type": "object", + "properties": { + "animation_url": { + "type": [ + "string", + "null" + ] + }, + "attributes": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Trait" + } + }, + "background_color": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "external_url": { + "type": [ + "string", + "null" + ] + }, + "image": { + "type": [ + "string", + "null" + ] + }, + "image_data": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "royalty_payment_address": { + "description": "The payment address, may be different to or the same as the minter addr question: how do we validate this?", + "type": [ + "string", + "null" + ] + }, + "royalty_percentage": { + "description": "This is how much the minter takes as a cut when sold royalties are owed on this token if it is Some", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "youtube_url": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "NftInfoResponse_for_Nullable_Metadata": { "type": "object", - "required": [ - "extension" - ], "properties": { "extension": { "description": "You can add any custom metadata here when you extend cw721-base", - "allOf": [ + "anyOf": [ + { + "$ref": "#/definitions/Metadata" + }, { - "$ref": "#/definitions/Cw2981QueryMsg" + "type": "null" } ] }, @@ -1240,9 +1393,27 @@ } ] }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" + "Trait": { + "type": "object", + "required": [ + "trait_type", + "value" + ], + "properties": { + "display_type": { + "type": [ + "string", + "null" + ] + }, + "trait_type": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false }, "Uint64": { "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", @@ -1267,6 +1438,10 @@ }, "additionalProperties": false, "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, "Approval": { "type": "object", "required": [ @@ -1284,7 +1459,11 @@ }, "spender": { "description": "Account that can transfer/send the token", - "type": "string" + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] } }, "additionalProperties": false @@ -1382,6 +1561,10 @@ }, "additionalProperties": false, "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, "Approval": { "type": "object", "required": [ @@ -1399,7 +1582,11 @@ }, "spender": { "description": "Account that can transfer/send the token", - "type": "string" + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] } }, "additionalProperties": false @@ -1482,6 +1669,10 @@ }, "additionalProperties": false, "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, "Approval": { "type": "object", "required": [ @@ -1499,7 +1690,11 @@ }, "spender": { "description": "Account that can transfer/send the token", - "type": "string" + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] } }, "additionalProperties": false @@ -1565,9 +1760,24 @@ } } }, + "check_royalties": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CheckRoyaltiesResponse", + "description": "Shows if the contract implements royalties if royalty_payments is true, marketplaces should pay them", + "type": "object", + "required": [ + "royalty_payments" + ], + "properties": { + "royalty_payments": { + "type": "boolean" + } + }, + "additionalProperties": false + }, "contract_info": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ContractInfoResponse", + "title": "CollectionInfo", "type": "object", "required": [ "name", @@ -1588,6 +1798,24 @@ "title": "Null", "type": "null" }, + "get_collection_info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CollectionInfo", + "type": "object", + "required": [ + "name", + "symbol" + ], + "properties": { + "name": { + "type": "string" + }, + "symbol": { + "type": "string" + } + }, + "additionalProperties": false + }, "get_withdraw_address": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Nullable_String", @@ -1599,7 +1827,7 @@ "minter": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "MinterResponse", - "description": "Shows who can mint these tokens", + "description": "Deprecated: use Cw721QueryMsg::GetMinterOwnership instead! Shows who can mint these tokens.", "type": "object", "properties": { "minter": { @@ -1613,17 +1841,17 @@ }, "nft_info": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "NftInfoResponse_for_Cw2981QueryMsg", + "title": "NftInfoResponse_for_Nullable_Metadata", "type": "object", - "required": [ - "extension" - ], "properties": { "extension": { "description": "You can add any custom metadata here when you extend cw721-base", - "allOf": [ + "anyOf": [ { - "$ref": "#/definitions/Cw2981QueryMsg" + "$ref": "#/definitions/Metadata" + }, + { + "type": "null" } ] }, @@ -1637,53 +1865,106 @@ }, "additionalProperties": false, "definitions": { - "Cw2981QueryMsg": { - "oneOf": [ - { - "description": "Should be called on sale to see if royalties are owed by the marketplace selling the NFT, if CheckRoyalties returns true See https://eips.ethereum.org/EIPS/eip-2981", - "type": "object", - "required": [ - "royalty_info" + "Metadata": { + "type": "object", + "properties": { + "animation_url": { + "type": [ + "string", + "null" + ] + }, + "attributes": { + "type": [ + "array", + "null" ], - "properties": { - "royalty_info": { - "type": "object", - "required": [ - "sale_price", - "token_id" - ], - "properties": { - "sale_price": { - "$ref": "#/definitions/Uint128" - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false + "items": { + "$ref": "#/definitions/Trait" + } }, - { - "description": "Called against contract to determine if this NFT implements royalties. Should return a boolean as part of CheckRoyaltiesResponse - default can simply be true if royalties are implemented at token level (i.e. always check on sale)", - "type": "object", - "required": [ - "check_royalties" + "background_color": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "external_url": { + "type": [ + "string", + "null" + ] + }, + "image": { + "type": [ + "string", + "null" + ] + }, + "image_data": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "royalty_payment_address": { + "description": "The payment address, may be different to or the same as the minter addr question: how do we validate this?", + "type": [ + "string", + "null" + ] + }, + "royalty_percentage": { + "description": "This is how much the minter takes as a cut when sold royalties are owed on this token if it is Some", + "type": [ + "integer", + "null" ], - "properties": { - "check_royalties": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false + "format": "uint64", + "minimum": 0.0 + }, + "youtube_url": { + "type": [ + "string", + "null" + ] } - ] + }, + "additionalProperties": false }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" + "Trait": { + "type": "object", + "required": [ + "trait_type", + "value" + ], + "properties": { + "display_type": { + "type": [ + "string", + "null" + ] + }, + "trait_type": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false } } }, @@ -1717,6 +1998,10 @@ }, "additionalProperties": false, "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, "Approval": { "type": "object", "required": [ @@ -1734,7 +2019,11 @@ }, "spender": { "description": "Account that can transfer/send the token", - "type": "string" + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] } }, "additionalProperties": false @@ -1823,6 +2112,10 @@ }, "additionalProperties": false, "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, "Approval": { "type": "object", "required": [ @@ -1840,7 +2133,11 @@ }, "spender": { "description": "Account that can transfer/send the token", - "type": "string" + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] } }, "additionalProperties": false @@ -1908,15 +2205,19 @@ }, "ownership": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Ownership_for_String", + "title": "Ownership_for_Addr", "description": "The contract's ownership info", "type": "object", "properties": { "owner": { "description": "The contract's current owner. `None` if the ownership has been renounced.", - "type": [ - "string", - "null" + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } ] }, "pending_expiry": { @@ -1932,14 +2233,22 @@ }, "pending_owner": { "description": "The account who has been proposed to take over the ownership. `None` if there isn't a pending ownership transfer.", - "type": [ - "string", - "null" + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } ] } }, "additionalProperties": false, "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, "Expiration": { "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", "oneOf": [ @@ -2001,6 +2310,30 @@ } } }, + "royalty_info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "RoyaltiesInfoResponse", + "type": "object", + "required": [ + "address", + "royalty_amount" + ], + "properties": { + "address": { + "type": "string" + }, + "royalty_amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, "tokens": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "TokensResponse", diff --git a/contracts/cw2981-royalties/src/error.rs b/contracts/cw2981-royalties/src/error.rs index 73c0e4bb4..2d1cd6324 100644 --- a/contracts/cw2981-royalties/src/error.rs +++ b/contracts/cw2981-royalties/src/error.rs @@ -7,7 +7,7 @@ pub enum ContractError { Std(#[from] StdError), #[error(transparent)] - Base(#[from] cw721_base::ContractError), + Base(#[from] cw721_base::error::ContractError), #[error("Royalty percentage must be between 0 and 100")] InvalidRoyaltyPercentage, diff --git a/contracts/cw2981-royalties/src/lib.rs b/contracts/cw2981-royalties/src/lib.rs index 42edc0caf..ad229e815 100644 --- a/contracts/cw2981-royalties/src/lib.rs +++ b/contracts/cw2981-royalties/src/lib.rs @@ -6,15 +6,15 @@ pub use query::{check_royalties, query_royalties_info}; use cosmwasm_schema::cw_serde; use cosmwasm_std::Empty; -use cw721_base::Cw721Contract; -pub use cw721_base::InstantiateMsg; +pub use cw721_base::{ + execute::Cw721Execute, msg::InstantiateMsg, query::Cw721Query, Cw721Contract, +}; -use crate::msg::Cw2981QueryMsg; +use crate::error::ContractError; +use crate::msg::QueryMsg; // Version info for migration -#[cfg(not(feature = "library"))] const CONTRACT_NAME: &str = "crates.io:cw2981-royalties"; -#[cfg(not(feature = "library"))] const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); #[cw_serde] @@ -50,16 +50,16 @@ pub type Extension = Option; pub type MintExtension = Option; -pub type Cw2981Contract<'a> = Cw721Contract<'a, Extension, Empty, Empty, Cw2981QueryMsg>; -pub type ExecuteMsg = cw721_base::ExecuteMsg; -pub type QueryMsg = cw721_base::QueryMsg; +pub type Cw2981Contract<'a> = Cw721Contract<'a, Extension, Empty, Empty, QueryMsg>; +pub type ExecuteMsg = cw721_base::msg::ExecuteMsg; #[cfg(not(feature = "library"))] pub mod entry { + use self::msg::QueryMsg; + use super::*; - use crate::error::ContractError; - use cosmwasm_std::{entry_point, to_json_binary}; + use cosmwasm_std::entry_point; use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; #[entry_point] @@ -69,9 +69,14 @@ pub mod entry { info: MessageInfo, msg: InstantiateMsg, ) -> Result { - cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - - Ok(Cw2981Contract::default().instantiate(deps.branch(), env, info, msg)?) + Ok(Cw2981Contract::default().instantiate( + deps.branch(), + env, + info, + msg, + CONTRACT_NAME, + CONTRACT_VERSION, + )?) } #[entry_point] @@ -105,29 +110,25 @@ pub mod entry { #[entry_point] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::Extension { msg } => match msg { - Cw2981QueryMsg::RoyaltyInfo { - token_id, - sale_price, - } => to_json_binary(&query_royalties_info(deps, token_id, sale_price)?), - Cw2981QueryMsg::CheckRoyalties {} => to_json_binary(&check_royalties(deps)?), - }, - _ => Cw2981Contract::default().query(deps, env, msg), + QueryMsg::RoyaltyInfo { + token_id, + sale_price, + } => to_json_binary(&query_royalties_info(deps, env, token_id, sale_price)?), + QueryMsg::CheckRoyalties {} => to_json_binary(&check_royalties(deps)?), + _ => Cw2981Contract::default().query(deps, env, msg.into()), } } } -#[cfg(test)] #[cfg(not(feature = "library"))] +#[cfg(test)] mod tests { use super::*; - use crate::msg::{CheckRoyaltiesResponse, RoyaltiesInfoResponse}; + use crate::msg::{CheckRoyaltiesResponse, QueryMsg, RoyaltiesInfoResponse}; use cosmwasm_std::{from_json, Uint128}; - use crate::error::ContractError; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; - use cw721::Cw721Query; const CREATOR: &str = "creator"; @@ -158,9 +159,12 @@ mod tests { token_uri: token_uri.clone(), extension: extension.clone(), }; - entry::execute(deps.as_mut(), mock_env(), info, exec_msg).unwrap(); + let env = mock_env(); + entry::execute(deps.as_mut(), env.clone(), info, exec_msg).unwrap(); - let res = contract.nft_info(deps.as_ref(), token_id.into()).unwrap(); + let res = contract + .query_nft_info(deps.as_ref(), env, token_id.into()) + .unwrap(); assert_eq!(res.token_uri, token_uri); assert_eq!(res.extension, extension); } @@ -230,9 +234,7 @@ mod tests { assert_eq!(res, expected); // also check the longhand way - let query_msg = QueryMsg::Extension { - msg: Cw2981QueryMsg::CheckRoyalties {}, - }; + let query_msg = QueryMsg::CheckRoyalties {}; let query_res: CheckRoyaltiesResponse = from_json(entry::query(deps.as_ref(), mock_env(), query_msg).unwrap()).unwrap(); assert_eq!(query_res, expected); @@ -249,7 +251,8 @@ mod tests { minter: None, withdraw_address: None, }; - entry::instantiate(deps.as_mut(), mock_env(), info.clone(), init_msg).unwrap(); + let env = mock_env(); + entry::instantiate(deps.as_mut(), env.clone(), info.clone(), init_msg).unwrap(); let token_id = "Enterprise"; let owner = "jeanluc"; @@ -271,16 +274,19 @@ mod tests { address: owner.into(), royalty_amount: Uint128::new(10), }; - let res = - query_royalties_info(deps.as_ref(), token_id.to_string(), Uint128::new(100)).unwrap(); + let res = query_royalties_info( + deps.as_ref(), + env.clone(), + token_id.to_string(), + Uint128::new(100), + ) + .unwrap(); assert_eq!(res, expected); // also check the longhand way - let query_msg = QueryMsg::Extension { - msg: Cw2981QueryMsg::RoyaltyInfo { - token_id: token_id.to_string(), - sale_price: Uint128::new(100), - }, + let query_msg = QueryMsg::RoyaltyInfo { + token_id: token_id.to_string(), + sale_price: Uint128::new(100), }; let query_res: RoyaltiesInfoResponse = from_json(entry::query(deps.as_ref(), mock_env(), query_msg).unwrap()).unwrap(); @@ -313,6 +319,7 @@ mod tests { let res = query_royalties_info( deps.as_ref(), + env, voyager_token_id.to_string(), Uint128::new(43), ) diff --git a/contracts/cw2981-royalties/src/msg.rs b/contracts/cw2981-royalties/src/msg.rs index fa9c7f387..30e3cc702 100644 --- a/contracts/cw2981-royalties/src/msg.rs +++ b/contracts/cw2981-royalties/src/msg.rs @@ -1,9 +1,19 @@ +use crate::Extension; use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{CustomMsg, Uint128}; +use cosmwasm_std::{Addr, Uint128}; +use cw721::msg::Cw721QueryMsg; +use cw721_base::{ + msg::{ + AllNftInfoResponse, ApprovalResponse, ApprovalsResponse, MinterResponse, NftInfoResponse, + NumTokensResponse, OperatorResponse, OperatorsResponse, OwnerOfResponse, TokensResponse, + }, + state::CollectionInfo, +}; +use cw_ownable::Ownership; #[cw_serde] #[derive(QueryResponses)] -pub enum Cw2981QueryMsg { +pub enum QueryMsg { /// Should be called on sale to see if royalties are owed /// by the marketplace selling the NFT, if CheckRoyalties /// returns true @@ -24,16 +34,171 @@ pub enum Cw2981QueryMsg { /// (i.e. always check on sale) #[returns(CheckRoyaltiesResponse)] CheckRoyalties {}, + + // -- below copied from Cw721QueryMsg + /// Return the owner of the given token, error if token does not exist + #[returns(OwnerOfResponse)] + OwnerOf { + token_id: String, + /// unset or false will filter out expired approvals, you must set to true to see them + include_expired: Option, + }, + /// Return operator that can access all of the owner's tokens. + #[returns(ApprovalResponse)] + Approval { + token_id: String, + spender: String, + include_expired: Option, + }, + /// Return approvals that a token has + #[returns(ApprovalsResponse)] + Approvals { + token_id: String, + include_expired: Option, + }, + /// Return approval of a given operator for all tokens of an owner, error if not set + #[returns(OperatorResponse)] + Operator { + owner: String, + operator: String, + include_expired: Option, + }, + /// List all operators that can access all of the owner's tokens + #[returns(OperatorsResponse)] + AllOperators { + owner: String, + /// unset or false will filter out expired items, you must set to true to see them + include_expired: Option, + start_after: Option, + limit: Option, + }, + /// Total number of tokens issued + #[returns(NumTokensResponse)] + NumTokens {}, + + #[returns(CollectionInfo)] + ContractInfo {}, + + /// With MetaData Extension. + /// Returns top-level metadata about the contract + #[returns(CollectionInfo)] + GetCollectionInfo {}, + + #[returns(Ownership)] + Ownership {}, + + /// With MetaData Extension. + /// Returns metadata about one particular token, based on *ERC721 Metadata JSON Schema* + /// but directly from the contract + #[returns(NftInfoResponse)] + NftInfo { token_id: String }, + /// With MetaData Extension. + /// Returns the result of both `NftInfo` and `OwnerOf` as one query as an optimization + /// for clients + #[returns(AllNftInfoResponse)] + AllNftInfo { + token_id: String, + /// unset or false will filter out expired approvals, you must set to true to see them + include_expired: Option, + }, + + /// With Enumerable extension. + /// Returns all tokens owned by the given address, [] if unset. + #[returns(TokensResponse)] + Tokens { + owner: String, + start_after: Option, + limit: Option, + }, + /// With Enumerable extension. + /// Requires pagination. Lists all token_ids controlled by the contract. + #[returns(TokensResponse)] + AllTokens { + start_after: Option, + limit: Option, + }, + + /// Return the minter + #[returns(MinterResponse)] + Minter {}, + + #[returns(Option)] + GetWithdrawAddress {}, + + // -- below queries, Extension and GetCollectionInfoExtension, are just dummies, since type annotations are required for + // -- TMetadataExtension and TCollectionInfoExtension, Error: + // -- "type annotations needed: cannot infer type for type parameter `TMetadataExtension` declared on the enum `Cw721QueryMsg`" + #[returns(())] + Extension { msg: Extension }, } -impl Default for Cw2981QueryMsg { - fn default() -> Self { - Cw2981QueryMsg::CheckRoyalties {} +impl From for Cw721QueryMsg { + fn from(msg: QueryMsg) -> Cw721QueryMsg { + match msg { + QueryMsg::OwnerOf { + token_id, + include_expired, + } => Cw721QueryMsg::OwnerOf { + token_id, + include_expired, + }, + QueryMsg::NumTokens {} => Cw721QueryMsg::NumTokens {}, + QueryMsg::ContractInfo {} => Cw721QueryMsg::ContractInfo {}, + QueryMsg::NftInfo { token_id } => Cw721QueryMsg::NftInfo { token_id }, + QueryMsg::AllNftInfo { + token_id, + include_expired, + } => Cw721QueryMsg::AllNftInfo { + token_id, + include_expired, + }, + QueryMsg::Tokens { + owner, + start_after, + limit, + } => Cw721QueryMsg::Tokens { + owner, + start_after, + limit, + }, + QueryMsg::AllTokens { start_after, limit } => { + Cw721QueryMsg::AllTokens { start_after, limit } + } + #[allow(deprecated)] + QueryMsg::Minter {} => Cw721QueryMsg::Minter {}, + QueryMsg::GetWithdrawAddress {} => Cw721QueryMsg::GetWithdrawAddress {}, + QueryMsg::AllOperators { + owner, + include_expired, + start_after, + limit, + } => Cw721QueryMsg::AllOperators { + owner, + include_expired, + start_after, + limit, + }, + QueryMsg::Approval { + token_id, + spender, + include_expired, + } => Cw721QueryMsg::Approval { + token_id, + spender, + include_expired, + }, + QueryMsg::Approvals { + token_id, + include_expired, + } => Cw721QueryMsg::Approvals { + token_id, + include_expired, + }, + msg => unreachable!("Unsupported query: {:?}", msg), + } } } -impl CustomMsg for Cw2981QueryMsg {} - #[cw_serde] pub struct RoyaltiesInfoResponse { pub address: String, diff --git a/contracts/cw2981-royalties/src/query.rs b/contracts/cw2981-royalties/src/query.rs index 53c20589f..33704e3dc 100644 --- a/contracts/cw2981-royalties/src/query.rs +++ b/contracts/cw2981-royalties/src/query.rs @@ -1,16 +1,19 @@ use crate::msg::{CheckRoyaltiesResponse, RoyaltiesInfoResponse}; -use crate::Cw2981Contract; -use cosmwasm_std::{Decimal, Deps, StdResult, Uint128}; +use crate::{Cw2981Contract, Extension}; +use cosmwasm_std::{Decimal, Deps, Env, StdResult, Uint128}; +use cw721::msg::NftInfoResponse; +use cw721_base::query::Cw721Query; /// NOTE: default behaviour here is to round down /// EIP2981 specifies that the rounding behaviour is at the discretion of the implementer pub fn query_royalties_info( deps: Deps, + env: Env, token_id: String, sale_price: Uint128, ) -> StdResult { let contract = Cw2981Contract::default(); - let token_info = contract.tokens.load(deps.storage, &token_id)?; + let token_info: NftInfoResponse = contract.query_nft_info(deps, env, token_id)?; let royalty_percentage = match token_info.extension { Some(ref ext) => match ext.royalty_percentage { diff --git a/contracts/cw721-base/Cargo.toml b/contracts/cw721-base/Cargo.toml index be71889b1..8b0427498 100644 --- a/contracts/cw721-base/Cargo.toml +++ b/contracts/cw721-base/Cargo.toml @@ -2,6 +2,7 @@ name = "cw721-base" description = "Basic implementation cw721 NFTs" authors = [ + "mr-t ", "Ethan Frey ", "Orkun Külçe ", ] @@ -26,14 +27,10 @@ library = [] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw-ownable = { workspace = true } -cw-storage-plus = { workspace = true } -cw-utils = { workspace = true } cw2 = { workspace = true } cw721 = { workspace = true } -cw721-base-016 = { workspace = true, features = ["library"] } -schemars = { workspace = true } serde = { workspace = true } -thiserror = { workspace = true } [dev-dependencies] -cw-multi-test = { workspace = true } +cw721-017 = { workspace = true } +cw721-018 = { workspace = true } diff --git a/contracts/cw721-base/README.md b/contracts/cw721-base/README.md index d3be359b9..6ab157023 100644 --- a/contracts/cw721-base/README.md +++ b/contracts/cw721-base/README.md @@ -32,7 +32,7 @@ If provided, it is expected that the _token_uri_ points to a JSON file following ## Running this contract -You will need Rust 1.65+ with `wasm32-unknown-unknown` target installed. +You will need Rust 1.78+ with `wasm32-unknown-unknown` target installed. You can run unit tests on this via: diff --git a/contracts/cw721-base/examples/schema.rs b/contracts/cw721-base/examples/schema.rs index 390811894..ab49e8a4b 100644 --- a/contracts/cw721-base/examples/schema.rs +++ b/contracts/cw721-base/examples/schema.rs @@ -1,12 +1,23 @@ -use cosmwasm_schema::write_api; +use cosmwasm_schema::{export_schema_with_title, remove_schemas, schema_for}; use cosmwasm_std::Empty; - -use cw721_base::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use cw721::state::DefaultOptionMetadataExtension; +use cw721_base::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use std::env::current_dir; +use std::fs::create_dir_all; fn main() { - write_api! { - instantiate: InstantiateMsg, - execute: ExecuteMsg, - query: QueryMsg, - } + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + // entry points - generate always with title for avoiding name suffixes like "..._empty_for_..." due to generics + export_schema_with_title(&schema_for!(InstantiateMsg), &out_dir, "InstantiateMsg"); + export_schema_with_title( + &schema_for!(ExecuteMsg::), + &out_dir, + "ExecuteMsg", + ); + export_schema_with_title(&schema_for!(QueryMsg), &out_dir, "QueryMsg"); + export_schema_with_title(&schema_for!(MigrateMsg), &out_dir, "MigrateMsg"); } diff --git a/contracts/cw721-base/schema/cw721-base.json b/contracts/cw721-base/schema/cw721-base.json deleted file mode 100644 index 5b2403b29..000000000 --- a/contracts/cw721-base/schema/cw721-base.json +++ /dev/null @@ -1,1788 +0,0 @@ -{ - "contract_name": "cw721-base", - "contract_version": "0.18.0", - "idl_version": "1.0.0", - "instantiate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "name", - "symbol" - ], - "properties": { - "minter": { - "description": "The minter is the only one who can create new NFTs. This is designed for a base NFT that is controlled by an external program or contract. You will likely replace this with custom logic in custom NFTs", - "type": [ - "string", - "null" - ] - }, - "name": { - "description": "Name of the NFT contract", - "type": "string" - }, - "symbol": { - "description": "Symbol of the NFT contract", - "type": "string" - }, - "withdraw_address": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - }, - "execute": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "description": "This is like Cw721ExecuteMsg but we add a Mint command for an owner to make this stand-alone. You will likely want to remove mint and use other control logic in any contract that inherits this.", - "oneOf": [ - { - "description": "Transfer is a base message to move a token to another account without triggering actions", - "type": "object", - "required": [ - "transfer_nft" - ], - "properties": { - "transfer_nft": { - "type": "object", - "required": [ - "recipient", - "token_id" - ], - "properties": { - "recipient": { - "type": "string" - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Send is a base message to transfer a token to a contract and trigger an action on the receiving contract.", - "type": "object", - "required": [ - "send_nft" - ], - "properties": { - "send_nft": { - "type": "object", - "required": [ - "contract", - "msg", - "token_id" - ], - "properties": { - "contract": { - "type": "string" - }, - "msg": { - "$ref": "#/definitions/Binary" - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Allows operator to transfer / send the token from the owner's account. If expiration is set, then this allowance has a time/height limit", - "type": "object", - "required": [ - "approve" - ], - "properties": { - "approve": { - "type": "object", - "required": [ - "spender", - "token_id" - ], - "properties": { - "expires": { - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "spender": { - "type": "string" - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Remove previously granted Approval", - "type": "object", - "required": [ - "revoke" - ], - "properties": { - "revoke": { - "type": "object", - "required": [ - "spender", - "token_id" - ], - "properties": { - "spender": { - "type": "string" - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Allows operator to transfer / send any token from the owner's account. If expiration is set, then this allowance has a time/height limit", - "type": "object", - "required": [ - "approve_all" - ], - "properties": { - "approve_all": { - "type": "object", - "required": [ - "operator" - ], - "properties": { - "expires": { - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "operator": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Remove previously granted ApproveAll permission", - "type": "object", - "required": [ - "revoke_all" - ], - "properties": { - "revoke_all": { - "type": "object", - "required": [ - "operator" - ], - "properties": { - "operator": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Mint a new NFT, can only be called by the contract minter", - "type": "object", - "required": [ - "mint" - ], - "properties": { - "mint": { - "type": "object", - "required": [ - "extension", - "owner", - "token_id" - ], - "properties": { - "extension": { - "description": "Any custom extension used by this contract", - "allOf": [ - { - "$ref": "#/definitions/Empty" - } - ] - }, - "owner": { - "description": "The owner of the newly minter NFT", - "type": "string" - }, - "token_id": { - "description": "Unique ID of the NFT", - "type": "string" - }, - "token_uri": { - "description": "Universal resource identifier for this NFT Should point to a JSON file that conforms to the ERC721 Metadata JSON Schema", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Burn an NFT the sender has access to", - "type": "object", - "required": [ - "burn" - ], - "properties": { - "burn": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Extension msg", - "type": "object", - "required": [ - "extension" - ], - "properties": { - "extension": { - "type": "object", - "required": [ - "msg" - ], - "properties": { - "msg": { - "$ref": "#/definitions/Empty" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Sets address to send withdrawn fees to. Only owner can call this.", - "type": "object", - "required": [ - "set_withdraw_address" - ], - "properties": { - "set_withdraw_address": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Removes the withdraw address, so fees are sent to the contract. Only owner can call this.", - "type": "object", - "required": [ - "remove_withdraw_address" - ], - "properties": { - "remove_withdraw_address": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Withdraw from the contract to the given address. Anyone can call this, which is okay since withdraw address has been set by owner.", - "type": "object", - "required": [ - "withdraw_funds" - ], - "properties": { - "withdraw_funds": { - "type": "object", - "required": [ - "amount" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Update the contract's ownership. The `action` to be provided can be either to propose transferring ownership to an account, accept a pending ownership transfer, or renounce the ownership permanently.", - "type": "object", - "required": [ - "update_ownership" - ], - "properties": { - "update_ownership": { - "$ref": "#/definitions/Action" - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Action": { - "description": "Actions that can be taken to alter the contract's ownership", - "oneOf": [ - { - "description": "Propose to transfer the contract's ownership to another account, optionally with an expiry time.\n\nCan only be called by the contract's current owner.\n\nAny existing pending ownership transfer is overwritten.", - "type": "object", - "required": [ - "transfer_ownership" - ], - "properties": { - "transfer_ownership": { - "type": "object", - "required": [ - "new_owner" - ], - "properties": { - "expiry": { - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "new_owner": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Accept the pending ownership transfer.\n\nCan only be called by the pending owner.", - "type": "string", - "enum": [ - "accept_ownership" - ] - }, - { - "description": "Give up the contract's ownership and the possibility of appointing a new owner.\n\nCan only be invoked by the contract's current owner.\n\nAny existing pending ownership transfer is canceled.", - "type": "string", - "enum": [ - "renounce_ownership" - ] - } - ] - }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", - "type": "string" - }, - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "query": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "oneOf": [ - { - "description": "Return the owner of the given token, error if token does not exist", - "type": "object", - "required": [ - "owner_of" - ], - "properties": { - "owner_of": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "include_expired": { - "description": "unset or false will filter out expired approvals, you must set to true to see them", - "type": [ - "boolean", - "null" - ] - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Return operator that can access all of the owner's tokens.", - "type": "object", - "required": [ - "approval" - ], - "properties": { - "approval": { - "type": "object", - "required": [ - "spender", - "token_id" - ], - "properties": { - "include_expired": { - "type": [ - "boolean", - "null" - ] - }, - "spender": { - "type": "string" - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Return approvals that a token has", - "type": "object", - "required": [ - "approvals" - ], - "properties": { - "approvals": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "include_expired": { - "type": [ - "boolean", - "null" - ] - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Return approval of a given operator for all tokens of an owner, error if not set", - "type": "object", - "required": [ - "operator" - ], - "properties": { - "operator": { - "type": "object", - "required": [ - "operator", - "owner" - ], - "properties": { - "include_expired": { - "type": [ - "boolean", - "null" - ] - }, - "operator": { - "type": "string" - }, - "owner": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "List all operators that can access all of the owner's tokens", - "type": "object", - "required": [ - "all_operators" - ], - "properties": { - "all_operators": { - "type": "object", - "required": [ - "owner" - ], - "properties": { - "include_expired": { - "description": "unset or false will filter out expired items, you must set to true to see them", - "type": [ - "boolean", - "null" - ] - }, - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "owner": { - "type": "string" - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Total number of tokens issued", - "type": "object", - "required": [ - "num_tokens" - ], - "properties": { - "num_tokens": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "With MetaData Extension. Returns top-level metadata about the contract", - "type": "object", - "required": [ - "contract_info" - ], - "properties": { - "contract_info": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "With MetaData Extension. Returns metadata about one particular token, based on *ERC721 Metadata JSON Schema* but directly from the contract", - "type": "object", - "required": [ - "nft_info" - ], - "properties": { - "nft_info": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "With MetaData Extension. Returns the result of both `NftInfo` and `OwnerOf` as one query as an optimization for clients", - "type": "object", - "required": [ - "all_nft_info" - ], - "properties": { - "all_nft_info": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "include_expired": { - "description": "unset or false will filter out expired approvals, you must set to true to see them", - "type": [ - "boolean", - "null" - ] - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "With Enumerable extension. Returns all tokens owned by the given address, [] if unset.", - "type": "object", - "required": [ - "tokens" - ], - "properties": { - "tokens": { - "type": "object", - "required": [ - "owner" - ], - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "owner": { - "type": "string" - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "With Enumerable extension. Requires pagination. Lists all token_ids controlled by the contract.", - "type": "object", - "required": [ - "all_tokens" - ], - "properties": { - "all_tokens": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Return the minter", - "type": "object", - "required": [ - "minter" - ], - "properties": { - "minter": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Extension query", - "type": "object", - "required": [ - "extension" - ], - "properties": { - "extension": { - "type": "object", - "required": [ - "msg" - ], - "properties": { - "msg": { - "$ref": "#/definitions/Empty" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "get_withdraw_address" - ], - "properties": { - "get_withdraw_address": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Query the contract's ownership information", - "type": "object", - "required": [ - "ownership" - ], - "properties": { - "ownership": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - } - } - }, - "migrate": null, - "sudo": null, - "responses": { - "all_nft_info": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AllNftInfoResponse_for_Empty", - "type": "object", - "required": [ - "access", - "info" - ], - "properties": { - "access": { - "description": "Who can transfer the token", - "allOf": [ - { - "$ref": "#/definitions/OwnerOfResponse" - } - ] - }, - "info": { - "description": "Data on the token itself,", - "allOf": [ - { - "$ref": "#/definitions/NftInfoResponse_for_Empty" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "NftInfoResponse_for_Empty": { - "type": "object", - "required": [ - "extension" - ], - "properties": { - "extension": { - "description": "You can add any custom metadata here when you extend cw721-base", - "allOf": [ - { - "$ref": "#/definitions/Empty" - } - ] - }, - "token_uri": { - "description": "Universal resource identifier for this NFT Should point to a JSON file that conforms to the ERC721 Metadata JSON Schema", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - }, - "OwnerOfResponse": { - "type": "object", - "required": [ - "approvals", - "owner" - ], - "properties": { - "approvals": { - "description": "If set this address is approved to transfer/send the token as well", - "type": "array", - "items": { - "$ref": "#/definitions/Approval" - } - }, - "owner": { - "description": "Owner of the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "all_operators": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "OperatorsResponse", - "type": "object", - "required": [ - "operators" - ], - "properties": { - "operators": { - "type": "array", - "items": { - "$ref": "#/definitions/Approval" - } - } - }, - "additionalProperties": false, - "definitions": { - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "all_tokens": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "TokensResponse", - "type": "object", - "required": [ - "tokens" - ], - "properties": { - "tokens": { - "description": "Contains all token_ids in lexicographical ordering If there are more than `limit`, use `start_after` in future queries to achieve pagination.", - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - }, - "approval": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ApprovalResponse", - "type": "object", - "required": [ - "approval" - ], - "properties": { - "approval": { - "$ref": "#/definitions/Approval" - } - }, - "additionalProperties": false, - "definitions": { - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "approvals": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ApprovalsResponse", - "type": "object", - "required": [ - "approvals" - ], - "properties": { - "approvals": { - "type": "array", - "items": { - "$ref": "#/definitions/Approval" - } - } - }, - "additionalProperties": false, - "definitions": { - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "contract_info": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ContractInfoResponse", - "type": "object", - "required": [ - "name", - "symbol" - ], - "properties": { - "name": { - "type": "string" - }, - "symbol": { - "type": "string" - } - }, - "additionalProperties": false - }, - "extension": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Null", - "type": "null" - }, - "get_withdraw_address": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Nullable_String", - "type": [ - "string", - "null" - ] - }, - "minter": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MinterResponse", - "description": "Shows who can mint these tokens", - "type": "object", - "properties": { - "minter": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - }, - "nft_info": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "NftInfoResponse_for_Empty", - "type": "object", - "required": [ - "extension" - ], - "properties": { - "extension": { - "description": "You can add any custom metadata here when you extend cw721-base", - "allOf": [ - { - "$ref": "#/definitions/Empty" - } - ] - }, - "token_uri": { - "description": "Universal resource identifier for this NFT Should point to a JSON file that conforms to the ERC721 Metadata JSON Schema", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false, - "definitions": { - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - } - } - }, - "num_tokens": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "NumTokensResponse", - "type": "object", - "required": [ - "count" - ], - "properties": { - "count": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - "operator": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "OperatorResponse", - "type": "object", - "required": [ - "approval" - ], - "properties": { - "approval": { - "$ref": "#/definitions/Approval" - } - }, - "additionalProperties": false, - "definitions": { - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "owner_of": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "OwnerOfResponse", - "type": "object", - "required": [ - "approvals", - "owner" - ], - "properties": { - "approvals": { - "description": "If set this address is approved to transfer/send the token as well", - "type": "array", - "items": { - "$ref": "#/definitions/Approval" - } - }, - "owner": { - "description": "Owner of the token", - "type": "string" - } - }, - "additionalProperties": false, - "definitions": { - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "ownership": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Ownership_for_String", - "description": "The contract's ownership info", - "type": "object", - "properties": { - "owner": { - "description": "The contract's current owner. `None` if the ownership has been renounced.", - "type": [ - "string", - "null" - ] - }, - "pending_expiry": { - "description": "The deadline for the pending owner to accept the ownership. `None` if there isn't a pending ownership transfer, or if a transfer exists and it doesn't have a deadline.", - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "pending_owner": { - "description": "The account who has been proposed to take over the ownership. `None` if there isn't a pending ownership transfer.", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false, - "definitions": { - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "tokens": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "TokensResponse", - "type": "object", - "required": [ - "tokens" - ], - "properties": { - "tokens": { - "description": "Contains all token_ids in lexicographical ordering If there are more than `limit`, use `start_after` in future queries to achieve pagination.", - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - } - } -} diff --git a/contracts/cw721-base/schema/execute_msg.json b/contracts/cw721-base/schema/execute_msg.json new file mode 100644 index 000000000..2bcf2fea7 --- /dev/null +++ b/contracts/cw721-base/schema/execute_msg.json @@ -0,0 +1,562 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "update_ownership" + ], + "properties": { + "update_ownership": { + "$ref": "#/definitions/Action" + } + }, + "additionalProperties": false + }, + { + "description": "Transfer is a base message to move a token to another account without triggering actions", + "type": "object", + "required": [ + "transfer_nft" + ], + "properties": { + "transfer_nft": { + "type": "object", + "required": [ + "recipient", + "token_id" + ], + "properties": { + "recipient": { + "type": "string" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Send is a base message to transfer a token to a contract and trigger an action on the receiving contract.", + "type": "object", + "required": [ + "send_nft" + ], + "properties": { + "send_nft": { + "type": "object", + "required": [ + "contract", + "msg", + "token_id" + ], + "properties": { + "contract": { + "type": "string" + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Allows operator to transfer / send the token from the owner's account. If expiration is set, then this allowance has a time/height limit", + "type": "object", + "required": [ + "approve" + ], + "properties": { + "approve": { + "type": "object", + "required": [ + "spender", + "token_id" + ], + "properties": { + "expires": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "spender": { + "type": "string" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Remove previously granted Approval", + "type": "object", + "required": [ + "revoke" + ], + "properties": { + "revoke": { + "type": "object", + "required": [ + "spender", + "token_id" + ], + "properties": { + "spender": { + "type": "string" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Allows operator to transfer / send any token from the owner's account. If expiration is set, then this allowance has a time/height limit", + "type": "object", + "required": [ + "approve_all" + ], + "properties": { + "approve_all": { + "type": "object", + "required": [ + "operator" + ], + "properties": { + "expires": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "operator": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Remove previously granted ApproveAll permission", + "type": "object", + "required": [ + "revoke_all" + ], + "properties": { + "revoke_all": { + "type": "object", + "required": [ + "operator" + ], + "properties": { + "operator": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Mint a new NFT, can only be called by the contract minter", + "type": "object", + "required": [ + "mint" + ], + "properties": { + "mint": { + "type": "object", + "required": [ + "owner", + "token_id" + ], + "properties": { + "extension": { + "description": "Any custom extension used by this contract", + "anyOf": [ + { + "$ref": "#/definitions/Metadata" + }, + { + "type": "null" + } + ] + }, + "owner": { + "description": "The owner of the newly minter NFT", + "type": "string" + }, + "token_id": { + "description": "Unique ID of the NFT", + "type": "string" + }, + "token_uri": { + "description": "Universal resource identifier for this NFT Should point to a JSON file that conforms to the ERC721 Metadata JSON Schema", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Burn an NFT the sender has access to", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Extension msg", + "type": "object", + "required": [ + "extension" + ], + "properties": { + "extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Sets address to send withdrawn fees to. Only owner can call this.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Removes the withdraw address, so fees are sent to the contract. Only owner can call this.", + "type": "object", + "required": [ + "remove_withdraw_address" + ], + "properties": { + "remove_withdraw_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Withdraw from the contract to the given address. Anyone can call this, which is okay since withdraw address has been set by owner.", + "type": "object", + "required": [ + "withdraw_funds" + ], + "properties": { + "withdraw_funds": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Action": { + "description": "Actions that can be taken to alter the contract's ownership", + "oneOf": [ + { + "description": "Propose to transfer the contract's ownership to another account, optionally with an expiry time.\n\nCan only be called by the contract's current owner.\n\nAny existing pending ownership transfer is overwritten.", + "type": "object", + "required": [ + "transfer_ownership" + ], + "properties": { + "transfer_ownership": { + "type": "object", + "required": [ + "new_owner" + ], + "properties": { + "expiry": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "new_owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Accept the pending ownership transfer.\n\nCan only be called by the pending owner.", + "type": "string", + "enum": [ + "accept_ownership" + ] + }, + { + "description": "Give up the contract's ownership and the possibility of appointing a new owner.\n\nCan only be invoked by the contract's current owner.\n\nAny existing pending ownership transfer is canceled.", + "type": "string", + "enum": [ + "renounce_ownership" + ] + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Metadata": { + "type": "object", + "properties": { + "animation_url": { + "type": [ + "string", + "null" + ] + }, + "attributes": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Trait" + } + }, + "background_color": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "external_url": { + "type": [ + "string", + "null" + ] + }, + "image": { + "type": [ + "string", + "null" + ] + }, + "image_data": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "youtube_url": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Trait": { + "type": "object", + "required": [ + "trait_type", + "value" + ], + "properties": { + "display_type": { + "type": [ + "string", + "null" + ] + }, + "trait_type": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cw721-base/schema/instantiate_msg.json b/contracts/cw721-base/schema/instantiate_msg.json new file mode 100644 index 000000000..1a02553e0 --- /dev/null +++ b/contracts/cw721-base/schema/instantiate_msg.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "name", + "symbol" + ], + "properties": { + "minter": { + "description": "The minter is the only one who can create new NFTs. This is designed for a base NFT that is controlled by an external program or contract. You will likely replace this with custom logic in custom NFTs", + "type": [ + "string", + "null" + ] + }, + "name": { + "description": "Name of the NFT contract", + "type": "string" + }, + "symbol": { + "description": "Symbol of the NFT contract", + "type": "string" + }, + "withdraw_address": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false +} diff --git a/contracts/cw721-base/schema/migrate_msg.json b/contracts/cw721-base/schema/migrate_msg.json new file mode 100644 index 000000000..f83fa9508 --- /dev/null +++ b/contracts/cw721-base/schema/migrate_msg.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "with_update" + ], + "properties": { + "with_update": { + "type": "object", + "properties": { + "creator": { + "type": [ + "string", + "null" + ] + }, + "minter": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/cw721-base/schema/query_msg.json b/contracts/cw721-base/schema/query_msg.json new file mode 100644 index 000000000..a56350cc7 --- /dev/null +++ b/contracts/cw721-base/schema/query_msg.json @@ -0,0 +1,382 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "description": "Return the owner of the given token, error if token does not exist", + "type": "object", + "required": [ + "owner_of" + ], + "properties": { + "owner_of": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "include_expired": { + "description": "unset or false will filter out expired approvals, you must set to true to see them", + "type": [ + "boolean", + "null" + ] + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return operator that can access all of the owner's tokens.", + "type": "object", + "required": [ + "approval" + ], + "properties": { + "approval": { + "type": "object", + "required": [ + "spender", + "token_id" + ], + "properties": { + "include_expired": { + "type": [ + "boolean", + "null" + ] + }, + "spender": { + "type": "string" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return approvals that a token has", + "type": "object", + "required": [ + "approvals" + ], + "properties": { + "approvals": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "include_expired": { + "type": [ + "boolean", + "null" + ] + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return approval of a given operator for all tokens of an owner, error if not set", + "type": "object", + "required": [ + "operator" + ], + "properties": { + "operator": { + "type": "object", + "required": [ + "operator", + "owner" + ], + "properties": { + "include_expired": { + "type": [ + "boolean", + "null" + ] + }, + "operator": { + "type": "string" + }, + "owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "List all operators that can access all of the owner's tokens", + "type": "object", + "required": [ + "all_operators" + ], + "properties": { + "all_operators": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "include_expired": { + "description": "unset or false will filter out expired items, you must set to true to see them", + "type": [ + "boolean", + "null" + ] + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Total number of tokens issued", + "type": "object", + "required": [ + "num_tokens" + ], + "properties": { + "num_tokens": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "contract_info" + ], + "properties": { + "contract_info": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ownership" + ], + "properties": { + "ownership": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "With MetaData Extension. Returns metadata about one particular token, based on *ERC721 Metadata JSON Schema* but directly from the contract", + "type": "object", + "required": [ + "nft_info" + ], + "properties": { + "nft_info": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "With MetaData Extension. Returns the result of both `NftInfo` and `OwnerOf` as one query as an optimization for clients", + "type": "object", + "required": [ + "all_nft_info" + ], + "properties": { + "all_nft_info": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "include_expired": { + "description": "unset or false will filter out expired approvals, you must set to true to see them", + "type": [ + "boolean", + "null" + ] + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "With Enumerable extension. Returns all tokens owned by the given address, [] if unset.", + "type": "object", + "required": [ + "tokens" + ], + "properties": { + "tokens": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "With Enumerable extension. Requires pagination. Lists all token_ids controlled by the contract.", + "type": "object", + "required": [ + "all_tokens" + ], + "properties": { + "all_tokens": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return the minter", + "type": "object", + "required": [ + "minter" + ], + "properties": { + "minter": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_withdraw_address" + ], + "properties": { + "get_withdraw_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Do not use - dummy extension query, needed for inferring type parameter during compile", + "type": "object", + "required": [ + "extension" + ], + "properties": { + "extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + } + } +} diff --git a/contracts/cw721-base/src/error.rs b/contracts/cw721-base/src/error.rs index 8ce92f08c..302102d4b 100644 --- a/contracts/cw721-base/src/error.rs +++ b/contracts/cw721-base/src/error.rs @@ -1,27 +1,2 @@ -use cosmwasm_std::StdError; -use cw_ownable::OwnershipError; -use thiserror::Error; - -#[derive(Error, Debug, PartialEq)] -pub enum ContractError { - #[error(transparent)] - Std(#[from] StdError), - - #[error(transparent)] - Ownership(#[from] OwnershipError), - - #[error(transparent)] - Version(#[from] cw2::VersionError), - - #[error("token_id already claimed")] - Claimed {}, - - #[error("Cannot set approval that is already expired")] - Expired {}, - - #[error("Approval not found for: {spender}")] - ApprovalNotFound { spender: String }, - - #[error("No withdraw address set")] - NoWithdrawAddress {}, -} +// expose to all others using contract, so others dont need to import cw721 +pub use cw721::error::{Cw721ContractError as ContractError, *}; diff --git a/contracts/cw721-base/src/execute.rs b/contracts/cw721-base/src/execute.rs index 246919bc4..333dad9e9 100644 --- a/contracts/cw721-base/src/execute.rs +++ b/contracts/cw721-base/src/execute.rs @@ -1,483 +1,29 @@ -use cw_ownable::OwnershipError; +use cosmwasm_std::CustomMsg; +// expose to all others using contract, so others dont need to import cw721 +pub use cw721::execute::*; use serde::de::DeserializeOwned; use serde::Serialize; -use cosmwasm_std::{ - Addr, BankMsg, Binary, Coin, CustomMsg, Deps, DepsMut, Env, MessageInfo, Response, Storage, -}; - -use cw721::{ContractInfoResponse, Cw721Execute, Cw721ReceiveMsg, Expiration}; - -use crate::error::ContractError; -use crate::msg::{ExecuteMsg, InstantiateMsg}; -use crate::state::{Approval, Cw721Contract, TokenInfo}; - -impl<'a, T, C, E, Q> Cw721Contract<'a, T, C, E, Q> -where - T: Serialize + DeserializeOwned + Clone, - C: CustomMsg, - E: CustomMsg, - Q: CustomMsg, -{ - pub fn instantiate( - &self, - deps: DepsMut, - _env: Env, - info: MessageInfo, - msg: InstantiateMsg, - ) -> Result, ContractError> { - let contract_info = ContractInfoResponse { - name: msg.name, - symbol: msg.symbol, - }; - self.contract_info.save(deps.storage, &contract_info)?; - - let owner = match msg.minter { - Some(owner) => deps.api.addr_validate(&owner)?, - None => info.sender, - }; - cw_ownable::initialize_owner(deps.storage, deps.api, Some(owner.as_ref()))?; - - if let Some(address) = msg.withdraw_address { - self.set_withdraw_address(deps, &owner, address)?; - } - - Ok(Response::default()) - } - - pub fn execute( - &self, - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, - ) -> Result, ContractError> { - match msg { - ExecuteMsg::Mint { - token_id, - owner, - token_uri, - extension, - } => self.mint(deps, info, token_id, owner, token_uri, extension), - ExecuteMsg::Approve { - spender, - token_id, - expires, - } => self.approve(deps, env, info, spender, token_id, expires), - ExecuteMsg::Revoke { spender, token_id } => { - self.revoke(deps, env, info, spender, token_id) - } - ExecuteMsg::ApproveAll { operator, expires } => { - self.approve_all(deps, env, info, operator, expires) - } - ExecuteMsg::RevokeAll { operator } => self.revoke_all(deps, env, info, operator), - ExecuteMsg::TransferNft { - recipient, - token_id, - } => self.transfer_nft(deps, env, info, recipient, token_id), - ExecuteMsg::SendNft { - contract, - token_id, - msg, - } => self.send_nft(deps, env, info, contract, token_id, msg), - ExecuteMsg::Burn { token_id } => self.burn(deps, env, info, token_id), - ExecuteMsg::UpdateOwnership(action) => Self::update_ownership(deps, env, info, action), - ExecuteMsg::Extension { msg: _ } => Ok(Response::default()), - ExecuteMsg::SetWithdrawAddress { address } => { - self.set_withdraw_address(deps, &info.sender, address) - } - ExecuteMsg::RemoveWithdrawAddress {} => { - self.remove_withdraw_address(deps.storage, &info.sender) - } - ExecuteMsg::WithdrawFunds { amount } => self.withdraw_funds(deps.storage, &amount), - } - } -} - -// TODO pull this into some sort of trait extension?? -impl<'a, T, C, E, Q> Cw721Contract<'a, T, C, E, Q> -where - T: Serialize + DeserializeOwned + Clone, - C: CustomMsg, - E: CustomMsg, - Q: CustomMsg, -{ - pub fn mint( - &self, - deps: DepsMut, - info: MessageInfo, - token_id: String, - owner: String, - token_uri: Option, - extension: T, - ) -> Result, ContractError> { - cw_ownable::assert_owner(deps.storage, &info.sender)?; - - // create the token - let token = TokenInfo { - owner: deps.api.addr_validate(&owner)?, - approvals: vec![], - token_uri, - extension, - }; - self.tokens - .update(deps.storage, &token_id, |old| match old { - Some(_) => Err(ContractError::Claimed {}), - None => Ok(token), - })?; - - self.increment_tokens(deps.storage)?; - - Ok(Response::new() - .add_attribute("action", "mint") - .add_attribute("minter", info.sender) - .add_attribute("owner", owner) - .add_attribute("token_id", token_id)) - } - - pub fn update_ownership( - deps: DepsMut, - env: Env, - info: MessageInfo, - action: cw_ownable::Action, - ) -> Result, ContractError> { - let ownership = cw_ownable::update_ownership(deps, &env.block, &info.sender, action)?; - Ok(Response::new().add_attributes(ownership.into_attributes())) - } - - pub fn set_withdraw_address( - &self, - deps: DepsMut, - sender: &Addr, - address: String, - ) -> Result, ContractError> { - cw_ownable::assert_owner(deps.storage, sender)?; - deps.api.addr_validate(&address)?; - self.withdraw_address.save(deps.storage, &address)?; - Ok(Response::new() - .add_attribute("action", "set_withdraw_address") - .add_attribute("address", address)) - } - - pub fn remove_withdraw_address( - &self, - storage: &mut dyn Storage, - sender: &Addr, - ) -> Result, ContractError> { - cw_ownable::assert_owner(storage, sender)?; - let address = self.withdraw_address.may_load(storage)?; - match address { - Some(address) => { - self.withdraw_address.remove(storage); - Ok(Response::new() - .add_attribute("action", "remove_withdraw_address") - .add_attribute("address", address)) - } - None => Err(ContractError::NoWithdrawAddress {}), - } - } - - pub fn withdraw_funds( - &self, - storage: &mut dyn Storage, - amount: &Coin, - ) -> Result, ContractError> { - let address = self.withdraw_address.may_load(storage)?; - match address { - Some(address) => { - let msg = BankMsg::Send { - to_address: address, - amount: vec![amount.clone()], - }; - Ok(Response::new() - .add_message(msg) - .add_attribute("action", "withdraw_funds") - .add_attribute("amount", amount.amount.to_string()) - .add_attribute("denom", amount.denom.to_string())) - } - None => Err(ContractError::NoWithdrawAddress {}), - } - } -} - -impl<'a, T, C, E, Q> Cw721Execute for Cw721Contract<'a, T, C, E, Q> -where - T: Serialize + DeserializeOwned + Clone, - C: CustomMsg, - E: CustomMsg, - Q: CustomMsg, -{ - type Err = ContractError; - - fn transfer_nft( - &self, - deps: DepsMut, - env: Env, - info: MessageInfo, - recipient: String, - token_id: String, - ) -> Result, ContractError> { - self._transfer_nft(deps, &env, &info, &recipient, &token_id)?; - - Ok(Response::new() - .add_attribute("action", "transfer_nft") - .add_attribute("sender", info.sender) - .add_attribute("recipient", recipient) - .add_attribute("token_id", token_id)) - } - - fn send_nft( - &self, - deps: DepsMut, - env: Env, - info: MessageInfo, - contract: String, - token_id: String, - msg: Binary, - ) -> Result, ContractError> { - // Transfer token - self._transfer_nft(deps, &env, &info, &contract, &token_id)?; - - let send = Cw721ReceiveMsg { - sender: info.sender.to_string(), - token_id: token_id.clone(), - msg, - }; - - // Send message - Ok(Response::new() - .add_message(send.into_cosmos_msg(contract.clone())?) - .add_attribute("action", "send_nft") - .add_attribute("sender", info.sender) - .add_attribute("recipient", contract) - .add_attribute("token_id", token_id)) - } - - fn approve( - &self, - deps: DepsMut, - env: Env, - info: MessageInfo, - spender: String, - token_id: String, - expires: Option, - ) -> Result, ContractError> { - self._update_approvals(deps, &env, &info, &spender, &token_id, true, expires)?; - - Ok(Response::new() - .add_attribute("action", "approve") - .add_attribute("sender", info.sender) - .add_attribute("spender", spender) - .add_attribute("token_id", token_id)) - } - - fn revoke( - &self, - deps: DepsMut, - env: Env, - info: MessageInfo, - spender: String, - token_id: String, - ) -> Result, ContractError> { - self._update_approvals(deps, &env, &info, &spender, &token_id, false, None)?; - - Ok(Response::new() - .add_attribute("action", "revoke") - .add_attribute("sender", info.sender) - .add_attribute("spender", spender) - .add_attribute("token_id", token_id)) - } - - fn approve_all( - &self, - deps: DepsMut, - env: Env, - info: MessageInfo, - operator: String, - expires: Option, - ) -> Result, ContractError> { - // reject expired data as invalid - let expires = expires.unwrap_or_default(); - if expires.is_expired(&env.block) { - return Err(ContractError::Expired {}); - } - - // set the operator for us - let operator_addr = deps.api.addr_validate(&operator)?; - self.operators - .save(deps.storage, (&info.sender, &operator_addr), &expires)?; - - Ok(Response::new() - .add_attribute("action", "approve_all") - .add_attribute("sender", info.sender) - .add_attribute("operator", operator)) - } - - fn revoke_all( - &self, - deps: DepsMut, - _env: Env, - info: MessageInfo, - operator: String, - ) -> Result, ContractError> { - let operator_addr = deps.api.addr_validate(&operator)?; - self.operators - .remove(deps.storage, (&info.sender, &operator_addr)); - - Ok(Response::new() - .add_attribute("action", "revoke_all") - .add_attribute("sender", info.sender) - .add_attribute("operator", operator)) - } - - fn burn( - &self, - deps: DepsMut, - env: Env, - info: MessageInfo, - token_id: String, - ) -> Result, ContractError> { - let token = self.tokens.load(deps.storage, &token_id)?; - self.check_can_send(deps.as_ref(), &env, &info, &token)?; - - self.tokens.remove(deps.storage, &token_id)?; - self.decrement_tokens(deps.storage)?; - - Ok(Response::new() - .add_attribute("action", "burn") - .add_attribute("sender", info.sender) - .add_attribute("token_id", token_id)) - } -} - -// helpers -impl<'a, T, C, E, Q> Cw721Contract<'a, T, C, E, Q> +use crate::Cw721Contract; + +impl<'a, TMetadataExtension, TCustomResponseMessage, TMetadataExtensionMsg, TQueryExtensionMsg> + Cw721Execute< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + > + for Cw721Contract< + 'a, + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + > where - T: Serialize + DeserializeOwned + Clone, - C: CustomMsg, - E: CustomMsg, - Q: CustomMsg, + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TCustomResponseMessage: CustomMsg, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, { - pub fn _transfer_nft( - &self, - deps: DepsMut, - env: &Env, - info: &MessageInfo, - recipient: &str, - token_id: &str, - ) -> Result, ContractError> { - let mut token = self.tokens.load(deps.storage, token_id)?; - // ensure we have permissions - self.check_can_send(deps.as_ref(), env, info, &token)?; - // set owner and remove existing approvals - token.owner = deps.api.addr_validate(recipient)?; - token.approvals = vec![]; - self.tokens.save(deps.storage, token_id, &token)?; - Ok(token) - } - - #[allow(clippy::too_many_arguments)] - pub fn _update_approvals( - &self, - deps: DepsMut, - env: &Env, - info: &MessageInfo, - spender: &str, - token_id: &str, - // if add == false, remove. if add == true, remove then set with this expiration - add: bool, - expires: Option, - ) -> Result, ContractError> { - let mut token = self.tokens.load(deps.storage, token_id)?; - // ensure we have permissions - self.check_can_approve(deps.as_ref(), env, info, &token)?; - - // update the approval list (remove any for the same spender before adding) - let spender_addr = deps.api.addr_validate(spender)?; - token.approvals.retain(|apr| apr.spender != spender_addr); - - // only difference between approve and revoke - if add { - // reject expired data as invalid - let expires = expires.unwrap_or_default(); - if expires.is_expired(&env.block) { - return Err(ContractError::Expired {}); - } - let approval = Approval { - spender: spender_addr, - expires, - }; - token.approvals.push(approval); - } - - self.tokens.save(deps.storage, token_id, &token)?; - - Ok(token) - } - - /// returns true iff the sender can execute approve or reject on the contract - pub fn check_can_approve( - &self, - deps: Deps, - env: &Env, - info: &MessageInfo, - token: &TokenInfo, - ) -> Result<(), ContractError> { - // owner can approve - if token.owner == info.sender { - return Ok(()); - } - // operator can approve - let op = self - .operators - .may_load(deps.storage, (&token.owner, &info.sender))?; - match op { - Some(ex) => { - if ex.is_expired(&env.block) { - Err(ContractError::Ownership(OwnershipError::NotOwner)) - } else { - Ok(()) - } - } - None => Err(ContractError::Ownership(OwnershipError::NotOwner)), - } - } - - /// returns true iff the sender can transfer ownership of the token - pub fn check_can_send( - &self, - deps: Deps, - env: &Env, - info: &MessageInfo, - token: &TokenInfo, - ) -> Result<(), ContractError> { - // owner can send - if token.owner == info.sender { - return Ok(()); - } - - // any non-expired token approval can send - if token - .approvals - .iter() - .any(|apr| apr.spender == info.sender && !apr.is_expired(&env.block)) - { - return Ok(()); - } - - // operator can send - let op = self - .operators - .may_load(deps.storage, (&token.owner, &info.sender))?; - match op { - Some(ex) => { - if ex.is_expired(&env.block) { - Err(ContractError::Ownership(OwnershipError::NotOwner)) - } else { - Ok(()) - } - } - None => Err(ContractError::Ownership(OwnershipError::NotOwner)), - } - } } diff --git a/contracts/cw721-base/src/lib.rs b/contracts/cw721-base/src/lib.rs index 40f88a8a0..1d0328f29 100644 --- a/contracts/cw721-base/src/lib.rs +++ b/contracts/cw721-base/src/lib.rs @@ -1,18 +1,9 @@ pub mod error; -mod execute; -pub mod helpers; +pub mod execute; pub mod msg; -mod query; +pub mod query; pub mod state; -pub mod upgrades; -#[cfg(test)] -mod contract_tests; -#[cfg(test)] -mod multi_tests; - -pub use crate::error::ContractError; -pub use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; pub use crate::state::Cw721Contract; // These types are re-exported so that contracts interacting with this @@ -25,25 +16,24 @@ pub use cw_ownable::{Action, Ownership, OwnershipError}; use cosmwasm_std::Empty; -// This is a simple type to let us handle empty extensions -pub type Extension = Option; - // Version info for migration pub const CONTRACT_NAME: &str = "crates.io:cw721-base"; pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); -// currently we only support migrating from 0.16.0. this is ok for now because -// we have not released any 0.16.x where x != 0 -// -// TODO: parse semvar so that any version 0.16.x can be migrated from -pub const EXPECTED_FROM_VERSION: &str = "0.16.0"; - pub mod entry { + use super::*; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; + use cw721::{ + error::Cw721ContractError, + execute::Cw721Execute, + msg::{Cw721ExecuteMsg, Cw721InstantiateMsg, Cw721MigrateMsg, Cw721QueryMsg}, + query::Cw721Query, + state::DefaultOptionMetadataExtension, + }; // This makes a conscious choice on the various generics used by the contract #[cfg_attr(not(feature = "library"), entry_point)] @@ -51,12 +41,11 @@ pub mod entry { deps: DepsMut, env: Env, info: MessageInfo, - msg: InstantiateMsg, - ) -> Result { - cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - - let tract = Cw721Contract::::default(); - tract.instantiate(deps, env, info, msg) + msg: Cw721InstantiateMsg, + ) -> Result { + let contract = + Cw721Contract::::default(); + contract.instantiate(deps, env, info, msg, CONTRACT_NAME, CONTRACT_VERSION) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -64,94 +53,32 @@ pub mod entry { deps: DepsMut, env: Env, info: MessageInfo, - msg: ExecuteMsg, - ) -> Result { - let tract = Cw721Contract::::default(); - tract.execute(deps, env, info, msg) + msg: Cw721ExecuteMsg, + ) -> Result { + let contract = + Cw721Contract::::default(); + contract.execute(deps, env, info, msg) } #[cfg_attr(not(feature = "library"), entry_point)] - pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - let tract = Cw721Contract::::default(); - tract.query(deps, env, msg) + pub fn query( + deps: Deps, + env: Env, + msg: Cw721QueryMsg, + ) -> StdResult { + let contract = + Cw721Contract::::default(); + contract.query(deps, env, msg) } #[cfg_attr(not(feature = "library"), entry_point)] - pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> Result { - // make sure the correct contract is being upgraded, and it's being - // upgraded from the correct version. - cw2::assert_contract_version(deps.as_ref().storage, CONTRACT_NAME, EXPECTED_FROM_VERSION)?; - - // update contract version - cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - - // perform the upgrade - upgrades::v0_17::migrate::(deps) - } -} - -#[cfg(test)] -mod tests { - use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; - use cw2::ContractVersion; - - use super::*; - - /// Make sure cw2 version info is properly initialized during instantiation. - #[test] - fn proper_cw2_initialization() { - let mut deps = mock_dependencies(); - - entry::instantiate( - deps.as_mut(), - mock_env(), - mock_info("larry", &[]), - InstantiateMsg { - name: "".into(), - symbol: "".into(), - minter: Some("other".into()), - withdraw_address: None, - }, - ) - .unwrap(); - - let minter = cw_ownable::get_ownership(deps.as_ref().storage) - .unwrap() - .owner - .map(|a| a.into_string()); - assert_eq!(minter, Some("other".to_string())); - - let version = cw2::get_contract_version(deps.as_ref().storage).unwrap(); - assert_eq!( - version, - ContractVersion { - contract: CONTRACT_NAME.into(), - version: CONTRACT_VERSION.into(), - }, - ); - } - - #[test] - fn proper_owner_initialization() { - let mut deps = mock_dependencies(); - - entry::instantiate( - deps.as_mut(), - mock_env(), - mock_info("owner", &[]), - InstantiateMsg { - name: "".into(), - symbol: "".into(), - minter: None, - withdraw_address: None, - }, - ) - .unwrap(); - - let minter = cw_ownable::get_ownership(deps.as_ref().storage) - .unwrap() - .owner - .map(|a| a.into_string()); - assert_eq!(minter, Some("owner".to_string())); + pub fn migrate( + deps: DepsMut, + env: Env, + msg: Cw721MigrateMsg, + ) -> Result { + let contract = + Cw721Contract::::default(); + contract.migrate(deps, env, msg, CONTRACT_NAME, CONTRACT_VERSION) } } diff --git a/contracts/cw721-base/src/msg.rs b/contracts/cw721-base/src/msg.rs index 0ef443376..7cfb7a5c1 100644 --- a/contracts/cw721-base/src/msg.rs +++ b/contracts/cw721-base/src/msg.rs @@ -1,173 +1,5 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Binary, Coin}; -use cw721::Expiration; -use cw_ownable::{cw_ownable_execute, cw_ownable_query}; -use schemars::JsonSchema; - -#[cw_serde] -pub struct InstantiateMsg { - /// Name of the NFT contract - pub name: String, - /// Symbol of the NFT contract - pub symbol: String, - - /// The minter is the only one who can create new NFTs. - /// This is designed for a base NFT that is controlled by an external program - /// or contract. You will likely replace this with custom logic in custom NFTs - pub minter: Option, - - pub withdraw_address: Option, -} - -/// This is like Cw721ExecuteMsg but we add a Mint command for an owner -/// to make this stand-alone. You will likely want to remove mint and -/// use other control logic in any contract that inherits this. -#[cw_ownable_execute] -#[cw_serde] -pub enum ExecuteMsg { - /// Transfer is a base message to move a token to another account without triggering actions - TransferNft { recipient: String, token_id: String }, - /// Send is a base message to transfer a token to a contract and trigger an action - /// on the receiving contract. - SendNft { - contract: String, - token_id: String, - msg: Binary, - }, - /// Allows operator to transfer / send the token from the owner's account. - /// If expiration is set, then this allowance has a time/height limit - Approve { - spender: String, - token_id: String, - expires: Option, - }, - /// Remove previously granted Approval - Revoke { spender: String, token_id: String }, - /// Allows operator to transfer / send any token from the owner's account. - /// If expiration is set, then this allowance has a time/height limit - ApproveAll { - operator: String, - expires: Option, - }, - /// Remove previously granted ApproveAll permission - RevokeAll { operator: String }, - - /// Mint a new NFT, can only be called by the contract minter - Mint { - /// Unique ID of the NFT - token_id: String, - /// The owner of the newly minter NFT - owner: String, - /// Universal resource identifier for this NFT - /// Should point to a JSON file that conforms to the ERC721 - /// Metadata JSON Schema - token_uri: Option, - /// Any custom extension used by this contract - extension: T, - }, - - /// Burn an NFT the sender has access to - Burn { token_id: String }, - - /// Extension msg - Extension { msg: E }, - - /// Sets address to send withdrawn fees to. Only owner can call this. - SetWithdrawAddress { address: String }, - /// Removes the withdraw address, so fees are sent to the contract. Only owner can call this. - RemoveWithdrawAddress {}, - /// Withdraw from the contract to the given address. Anyone can call this, - /// which is okay since withdraw address has been set by owner. - WithdrawFunds { amount: Coin }, -} - -#[cw_ownable_query] -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Return the owner of the given token, error if token does not exist - #[returns(cw721::OwnerOfResponse)] - OwnerOf { - token_id: String, - /// unset or false will filter out expired approvals, you must set to true to see them - include_expired: Option, - }, - /// Return operator that can access all of the owner's tokens. - #[returns(cw721::ApprovalResponse)] - Approval { - token_id: String, - spender: String, - include_expired: Option, - }, - /// Return approvals that a token has - #[returns(cw721::ApprovalsResponse)] - Approvals { - token_id: String, - include_expired: Option, - }, - /// Return approval of a given operator for all tokens of an owner, error if not set - #[returns(cw721::OperatorResponse)] - Operator { - owner: String, - operator: String, - include_expired: Option, - }, - /// List all operators that can access all of the owner's tokens - #[returns(cw721::OperatorsResponse)] - AllOperators { - owner: String, - /// unset or false will filter out expired items, you must set to true to see them - include_expired: Option, - start_after: Option, - limit: Option, - }, - /// Total number of tokens issued - #[returns(cw721::NumTokensResponse)] - NumTokens {}, - - /// With MetaData Extension. - /// Returns top-level metadata about the contract - #[returns(cw721::ContractInfoResponse)] - ContractInfo {}, - /// With MetaData Extension. - /// Returns metadata about one particular token, based on *ERC721 Metadata JSON Schema* - /// but directly from the contract - #[returns(cw721::NftInfoResponse)] - NftInfo { token_id: String }, - /// With MetaData Extension. - /// Returns the result of both `NftInfo` and `OwnerOf` as one query as an optimization - /// for clients - #[returns(cw721::AllNftInfoResponse)] - AllNftInfo { - token_id: String, - /// unset or false will filter out expired approvals, you must set to true to see them - include_expired: Option, - }, - - /// With Enumerable extension. - /// Returns all tokens owned by the given address, [] if unset. - #[returns(cw721::TokensResponse)] - Tokens { - owner: String, - start_after: Option, - limit: Option, - }, - /// With Enumerable extension. - /// Requires pagination. Lists all token_ids controlled by the contract. - #[returns(cw721::TokensResponse)] - AllTokens { - start_after: Option, - limit: Option, - }, - - /// Return the minter - #[returns(cw721::MinterResponse)] - Minter {}, - - /// Extension query - #[returns(())] - Extension { msg: Q }, - - #[returns(Option)] - GetWithdrawAddress {}, -} +// expose to all others using contract, so others dont need to import cw721 +pub use cw721::msg::{ + Cw721ExecuteMsg as ExecuteMsg, Cw721InstantiateMsg as InstantiateMsg, + Cw721MigrateMsg as MigrateMsg, Cw721QueryMsg as QueryMsg, *, +}; diff --git a/contracts/cw721-base/src/multi_tests.rs b/contracts/cw721-base/src/multi_tests.rs deleted file mode 100644 index 345750907..000000000 --- a/contracts/cw721-base/src/multi_tests.rs +++ /dev/null @@ -1,166 +0,0 @@ -use cosmwasm_std::{to_json_binary, Addr, Empty, QuerierWrapper, WasmMsg}; -use cw721::{MinterResponse, OwnerOfResponse}; -use cw_multi_test::{App, Contract, ContractWrapper, Executor}; - -fn cw721_base_latest_contract() -> Box> { - let contract = ContractWrapper::new( - crate::entry::execute, - crate::entry::instantiate, - crate::entry::query, - ) - .with_migrate(crate::entry::migrate); - Box::new(contract) -} - -fn cw721_base_016_contract() -> Box> { - use cw721_base_016 as v16; - let contract = ContractWrapper::new( - v16::entry::execute, - v16::entry::instantiate, - v16::entry::query, - ); - Box::new(contract) -} - -fn query_owner(querier: QuerierWrapper, cw721: &Addr, token_id: String) -> Addr { - let resp: OwnerOfResponse = querier - .query_wasm_smart( - cw721, - &crate::QueryMsg::::OwnerOf { - token_id, - include_expired: None, - }, - ) - .unwrap(); - Addr::unchecked(resp.owner) -} - -fn mint_transfer_and_burn(app: &mut App, cw721: Addr, sender: Addr, token_id: String) { - app.execute_contract( - sender.clone(), - cw721.clone(), - &crate::ExecuteMsg::::Mint { - token_id: token_id.clone(), - owner: sender.to_string(), - token_uri: None, - extension: Empty::default(), - }, - &[], - ) - .unwrap(); - - let owner = query_owner(app.wrap(), &cw721, token_id.clone()); - assert_eq!(owner, sender.to_string()); - - app.execute_contract( - sender, - cw721.clone(), - &crate::ExecuteMsg::::TransferNft { - recipient: "burner".to_string(), - token_id: token_id.clone(), - }, - &[], - ) - .unwrap(); - - let owner = query_owner(app.wrap(), &cw721, token_id.clone()); - assert_eq!(owner, "burner".to_string()); - - app.execute_contract( - Addr::unchecked("burner"), - cw721, - &crate::ExecuteMsg::::Burn { token_id }, - &[], - ) - .unwrap(); -} - -/// Instantiates a 0.16 version of this contract and tests that tokens -/// can be minted, transferred, and burnred after migration. -#[test] -fn test_migration_016_to_latest() { - use cw721_base_016 as v16; - let mut app = App::default(); - let admin = || Addr::unchecked("admin"); - - let code_id_016 = app.store_code(cw721_base_016_contract()); - let code_id_latest = app.store_code(cw721_base_latest_contract()); - - let cw721 = app - .instantiate_contract( - code_id_016, - admin(), - &v16::InstantiateMsg { - name: "collection".to_string(), - symbol: "symbol".to_string(), - minter: admin().into_string(), - }, - &[], - "cw721-base", - Some(admin().into_string()), - ) - .unwrap(); - - mint_transfer_and_burn(&mut app, cw721.clone(), admin(), "1".to_string()); - - app.execute( - admin(), - WasmMsg::Migrate { - contract_addr: cw721.to_string(), - new_code_id: code_id_latest, - msg: to_json_binary(&Empty::default()).unwrap(), - } - .into(), - ) - .unwrap(); - - mint_transfer_and_burn(&mut app, cw721.clone(), admin(), "1".to_string()); - - // check new mint query response works. - let m: MinterResponse = app - .wrap() - .query_wasm_smart(&cw721, &crate::QueryMsg::::Minter {}) - .unwrap(); - assert_eq!(m.minter, Some(admin().to_string())); - - // check that the new response is backwards compatable when minter - // is not None. - let m: v16::MinterResponse = app - .wrap() - .query_wasm_smart(&cw721, &crate::QueryMsg::::Minter {}) - .unwrap(); - assert_eq!(m.minter, admin().to_string()); -} - -/// Test backward compatibility using instantiate msg from a 0.16 version on latest contract. -/// This ensures existing 3rd party contracts doesnt need to updated as well. -#[test] -fn test_instantiate_016_msg() { - use cw721_base_016 as v16; - let mut app = App::default(); - let admin = || Addr::unchecked("admin"); - - let code_id_latest = app.store_code(cw721_base_latest_contract()); - - let cw721 = app - .instantiate_contract( - code_id_latest, - admin(), - &v16::InstantiateMsg { - name: "collection".to_string(), - symbol: "symbol".to_string(), - minter: admin().into_string(), - }, - &[], - "cw721-base", - Some(admin().into_string()), - ) - .unwrap(); - - // assert withdraw address is None - let withdraw_addr: Option = app - .wrap() - .query_wasm_smart(cw721, &crate::QueryMsg::::GetWithdrawAddress {}) - .unwrap(); - assert!(withdraw_addr.is_none()); -} diff --git a/contracts/cw721-base/src/query.rs b/contracts/cw721-base/src/query.rs index 8ebc2f4b8..b0c897d75 100644 --- a/contracts/cw721-base/src/query.rs +++ b/contracts/cw721-base/src/query.rs @@ -1,372 +1,29 @@ +use cosmwasm_std::CustomMsg; +// expose to all others using contract, so others dont need to import cw721 +pub use cw721::query::*; use serde::de::DeserializeOwned; use serde::Serialize; -use cosmwasm_std::{ - to_json_binary, Addr, Binary, BlockInfo, CustomMsg, Deps, Env, Order, StdError, StdResult, -}; - -use cw721::{ - AllNftInfoResponse, ApprovalResponse, ApprovalsResponse, ContractInfoResponse, Cw721Query, - Expiration, MinterResponse, NftInfoResponse, NumTokensResponse, OperatorResponse, - OperatorsResponse, OwnerOfResponse, TokensResponse, -}; -use cw_storage_plus::Bound; -use cw_utils::maybe_addr; - -use crate::msg::QueryMsg; -use crate::state::{Approval, Cw721Contract, TokenInfo}; - -const DEFAULT_LIMIT: u32 = 10; -const MAX_LIMIT: u32 = 1000; - -impl<'a, T, C, E, Q> Cw721Query for Cw721Contract<'a, T, C, E, Q> +use crate::Cw721Contract; + +impl<'a, TMetadataExtension, TCustomResponseMessage, TMetadataExtensionMsg, TQueryExtensionMsg> + Cw721Query< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + > + for Cw721Contract< + 'a, + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + > where - T: Serialize + DeserializeOwned + Clone, - C: CustomMsg, - E: CustomMsg, - Q: CustomMsg, + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TCustomResponseMessage: CustomMsg, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, { - fn contract_info(&self, deps: Deps) -> StdResult { - self.contract_info.load(deps.storage) - } - - fn num_tokens(&self, deps: Deps) -> StdResult { - let count = self.token_count(deps.storage)?; - Ok(NumTokensResponse { count }) - } - - fn nft_info(&self, deps: Deps, token_id: String) -> StdResult> { - let info = self.tokens.load(deps.storage, &token_id)?; - Ok(NftInfoResponse { - token_uri: info.token_uri, - extension: info.extension, - }) - } - - fn owner_of( - &self, - deps: Deps, - env: Env, - token_id: String, - include_expired: bool, - ) -> StdResult { - let info = self.tokens.load(deps.storage, &token_id)?; - Ok(OwnerOfResponse { - owner: info.owner.to_string(), - approvals: humanize_approvals(&env.block, &info, include_expired), - }) - } - - /// operator returns the approval status of an operator for a given owner if exists - fn operator( - &self, - deps: Deps, - env: Env, - owner: String, - operator: String, - include_expired: bool, - ) -> StdResult { - let owner_addr = deps.api.addr_validate(&owner)?; - let operator_addr = deps.api.addr_validate(&operator)?; - - let info = self - .operators - .may_load(deps.storage, (&owner_addr, &operator_addr))?; - - if let Some(expires) = info { - if !include_expired && expires.is_expired(&env.block) { - return Err(StdError::not_found("Approval not found")); - } - - return Ok(OperatorResponse { - approval: cw721::Approval { - spender: operator, - expires, - }, - }); - } - - Err(StdError::not_found("Approval not found")) - } - - /// operators returns all operators owner given access to - fn operators( - &self, - deps: Deps, - env: Env, - owner: String, - include_expired: bool, - start_after: Option, - limit: Option, - ) -> StdResult { - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let start_addr = maybe_addr(deps.api, start_after)?; - let start = start_addr.as_ref().map(Bound::exclusive); - - let owner_addr = deps.api.addr_validate(&owner)?; - let res: StdResult> = self - .operators - .prefix(&owner_addr) - .range(deps.storage, start, None, Order::Ascending) - .filter(|r| { - include_expired || r.is_err() || !r.as_ref().unwrap().1.is_expired(&env.block) - }) - .take(limit) - .map(parse_approval) - .collect(); - Ok(OperatorsResponse { operators: res? }) - } - - fn approval( - &self, - deps: Deps, - env: Env, - token_id: String, - spender: String, - include_expired: bool, - ) -> StdResult { - let token = self.tokens.load(deps.storage, &token_id)?; - - // token owner has absolute approval - if token.owner == spender { - let approval = cw721::Approval { - spender: token.owner.to_string(), - expires: Expiration::Never {}, - }; - return Ok(ApprovalResponse { approval }); - } - - let filtered: Vec<_> = token - .approvals - .into_iter() - .filter(|t| t.spender == spender) - .filter(|t| include_expired || !t.is_expired(&env.block)) - .map(|a| cw721::Approval { - spender: a.spender.into_string(), - expires: a.expires, - }) - .collect(); - - if filtered.is_empty() { - return Err(StdError::not_found("Approval not found")); - } - // we expect only one item - let approval = filtered[0].clone(); - - Ok(ApprovalResponse { approval }) - } - - /// approvals returns all approvals owner given access to - fn approvals( - &self, - deps: Deps, - env: Env, - token_id: String, - include_expired: bool, - ) -> StdResult { - let token = self.tokens.load(deps.storage, &token_id)?; - let approvals: Vec<_> = token - .approvals - .into_iter() - .filter(|t| include_expired || !t.is_expired(&env.block)) - .map(|a| cw721::Approval { - spender: a.spender.into_string(), - expires: a.expires, - }) - .collect(); - - Ok(ApprovalsResponse { approvals }) - } - - fn tokens( - &self, - deps: Deps, - owner: String, - start_after: Option, - limit: Option, - ) -> StdResult { - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let start = start_after.map(|s| Bound::ExclusiveRaw(s.into())); - - let owner_addr = deps.api.addr_validate(&owner)?; - let tokens: Vec = self - .tokens - .idx - .owner - .prefix(owner_addr) - .keys(deps.storage, start, None, Order::Ascending) - .take(limit) - .collect::>>()?; - - Ok(TokensResponse { tokens }) - } - - fn all_tokens( - &self, - deps: Deps, - start_after: Option, - limit: Option, - ) -> StdResult { - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let start = start_after.map(|s| Bound::ExclusiveRaw(s.into())); - - let tokens: StdResult> = self - .tokens - .range(deps.storage, start, None, Order::Ascending) - .take(limit) - .map(|item| item.map(|(k, _)| k)) - .collect(); - - Ok(TokensResponse { tokens: tokens? }) - } - - fn all_nft_info( - &self, - deps: Deps, - env: Env, - token_id: String, - include_expired: bool, - ) -> StdResult> { - let info = self.tokens.load(deps.storage, &token_id)?; - Ok(AllNftInfoResponse { - access: OwnerOfResponse { - owner: info.owner.to_string(), - approvals: humanize_approvals(&env.block, &info, include_expired), - }, - info: NftInfoResponse { - token_uri: info.token_uri, - extension: info.extension, - }, - }) - } -} - -impl<'a, T, C, E, Q> Cw721Contract<'a, T, C, E, Q> -where - T: Serialize + DeserializeOwned + Clone, - C: CustomMsg, - E: CustomMsg, - Q: CustomMsg, -{ - pub fn query(&self, deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Minter {} => to_json_binary(&self.minter(deps)?), - QueryMsg::ContractInfo {} => to_json_binary(&self.contract_info(deps)?), - QueryMsg::NftInfo { token_id } => to_json_binary(&self.nft_info(deps, token_id)?), - QueryMsg::OwnerOf { - token_id, - include_expired, - } => to_json_binary(&self.owner_of( - deps, - env, - token_id, - include_expired.unwrap_or(false), - )?), - QueryMsg::AllNftInfo { - token_id, - include_expired, - } => to_json_binary(&self.all_nft_info( - deps, - env, - token_id, - include_expired.unwrap_or(false), - )?), - QueryMsg::Operator { - owner, - operator, - include_expired, - } => to_json_binary(&self.operator( - deps, - env, - owner, - operator, - include_expired.unwrap_or(false), - )?), - QueryMsg::AllOperators { - owner, - include_expired, - start_after, - limit, - } => to_json_binary(&self.operators( - deps, - env, - owner, - include_expired.unwrap_or(false), - start_after, - limit, - )?), - QueryMsg::NumTokens {} => to_json_binary(&self.num_tokens(deps)?), - QueryMsg::Tokens { - owner, - start_after, - limit, - } => to_json_binary(&self.tokens(deps, owner, start_after, limit)?), - QueryMsg::AllTokens { start_after, limit } => { - to_json_binary(&self.all_tokens(deps, start_after, limit)?) - } - QueryMsg::Approval { - token_id, - spender, - include_expired, - } => to_json_binary(&self.approval( - deps, - env, - token_id, - spender, - include_expired.unwrap_or(false), - )?), - QueryMsg::Approvals { - token_id, - include_expired, - } => to_json_binary(&self.approvals( - deps, - env, - token_id, - include_expired.unwrap_or(false), - )?), - QueryMsg::Ownership {} => to_json_binary(&Self::ownership(deps)?), - QueryMsg::Extension { msg: _ } => Ok(Binary::default()), - QueryMsg::GetWithdrawAddress {} => { - to_json_binary(&self.withdraw_address.may_load(deps.storage)?) - } - } - } - - pub fn minter(&self, deps: Deps) -> StdResult { - let minter = cw_ownable::get_ownership(deps.storage)? - .owner - .map(|a| a.into_string()); - - Ok(MinterResponse { minter }) - } - - pub fn ownership(deps: Deps) -> StdResult> { - cw_ownable::get_ownership(deps.storage) - } -} - -fn parse_approval(item: StdResult<(Addr, Expiration)>) -> StdResult { - item.map(|(spender, expires)| cw721::Approval { - spender: spender.to_string(), - expires, - }) -} - -fn humanize_approvals( - block: &BlockInfo, - info: &TokenInfo, - include_expired: bool, -) -> Vec { - info.approvals - .iter() - .filter(|apr| include_expired || !apr.is_expired(block)) - .map(humanize_approval) - .collect() -} - -fn humanize_approval(approval: &Approval) -> cw721::Approval { - cw721::Approval { - spender: approval.spender.to_string(), - expires: approval.expires, - } } diff --git a/contracts/cw721-base/src/state.rs b/contracts/cw721-base/src/state.rs index 48bca6bf5..b386e0615 100644 --- a/contracts/cw721-base/src/state.rs +++ b/contracts/cw721-base/src/state.rs @@ -1,152 +1,51 @@ -use schemars::JsonSchema; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; -use std::marker::PhantomData; - -use cosmwasm_std::{Addr, BlockInfo, CustomMsg, StdResult, Storage}; - -use cw721::{ContractInfoResponse, Cw721, Expiration}; -use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, MultiIndex}; +use cosmwasm_std::CustomMsg; -pub struct Cw721Contract<'a, T, C, E, Q> -where - T: Serialize + DeserializeOwned + Clone, - Q: CustomMsg, - E: CustomMsg, -{ - pub contract_info: Item<'a, ContractInfoResponse>, - pub token_count: Item<'a, u64>, - /// Stored as (granter, operator) giving operator full control over granter's account - pub operators: Map<'a, (&'a Addr, &'a Addr), Expiration>, - pub tokens: IndexedMap<'a, &'a str, TokenInfo, TokenIndexes<'a, T>>, - pub withdraw_address: Item<'a, String>, +// expose to all others using contract, so others dont need to import cw721 +pub use cw721::state::*; - pub(crate) _custom_response: PhantomData, - pub(crate) _custom_query: PhantomData, - pub(crate) _custom_execute: PhantomData, -} - -// This is a signal, the implementations are in other files -impl<'a, T, C, E, Q> Cw721 for Cw721Contract<'a, T, C, E, Q> -where - T: Serialize + DeserializeOwned + Clone, - C: CustomMsg, - E: CustomMsg, - Q: CustomMsg, +use serde::de::DeserializeOwned; +use serde::Serialize; + +pub struct Cw721Contract< + 'a, + // Metadata defined in NftInfo (used for mint). + TMetadataExtension, + // Defines for `CosmosMsg::Custom` in response. Barely used, so `Empty` can be used. + TCustomResponseMessage, + // Message passed for updating metadata. + TMetadataExtensionMsg, + // Extension query message. + TQueryExtensionMsg, +> where + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, { -} - -impl Default for Cw721Contract<'static, T, C, E, Q> + pub config: Cw721Config< + 'a, + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >, +} + +impl Default + for Cw721Contract< + 'static, + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + > where - T: Serialize + DeserializeOwned + Clone, - E: CustomMsg, - Q: CustomMsg, + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, { fn default() -> Self { - Self::new( - "nft_info", - "num_tokens", - "operators", - "tokens", - "tokens__owner", - "withdraw_address", - ) - } -} - -impl<'a, T, C, E, Q> Cw721Contract<'a, T, C, E, Q> -where - T: Serialize + DeserializeOwned + Clone, - E: CustomMsg, - Q: CustomMsg, -{ - fn new( - contract_key: &'a str, - token_count_key: &'a str, - operator_key: &'a str, - tokens_key: &'a str, - tokens_owner_key: &'a str, - withdraw_address_key: &'a str, - ) -> Self { - let indexes = TokenIndexes { - owner: MultiIndex::new(token_owner_idx, tokens_key, tokens_owner_key), - }; Self { - contract_info: Item::new(contract_key), - token_count: Item::new(token_count_key), - operators: Map::new(operator_key), - tokens: IndexedMap::new(tokens_key, indexes), - withdraw_address: Item::new(withdraw_address_key), - _custom_response: PhantomData, - _custom_execute: PhantomData, - _custom_query: PhantomData, + config: Cw721Config::default(), } } - - pub fn token_count(&self, storage: &dyn Storage) -> StdResult { - Ok(self.token_count.may_load(storage)?.unwrap_or_default()) - } - - pub fn increment_tokens(&self, storage: &mut dyn Storage) -> StdResult { - let val = self.token_count(storage)? + 1; - self.token_count.save(storage, &val)?; - Ok(val) - } - - pub fn decrement_tokens(&self, storage: &mut dyn Storage) -> StdResult { - let val = self.token_count(storage)? - 1; - self.token_count.save(storage, &val)?; - Ok(val) - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct TokenInfo { - /// The owner of the newly minted NFT - pub owner: Addr, - /// Approvals are stored here, as we clear them all upon transfer and cannot accumulate much - pub approvals: Vec, - - /// Universal resource identifier for this NFT - /// Should point to a JSON file that conforms to the ERC721 - /// Metadata JSON Schema - pub token_uri: Option, - - /// You can add any custom metadata here when you extend cw721-base - pub extension: T, -} - -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] -pub struct Approval { - /// Account that can transfer/send the token - pub spender: Addr, - /// When the Approval expires (maybe Expiration::never) - pub expires: Expiration, -} - -impl Approval { - pub fn is_expired(&self, block: &BlockInfo) -> bool { - self.expires.is_expired(block) - } -} - -pub struct TokenIndexes<'a, T> -where - T: Serialize + DeserializeOwned + Clone, -{ - pub owner: MultiIndex<'a, Addr, TokenInfo, String>, -} - -impl<'a, T> IndexList> for TokenIndexes<'a, T> -where - T: Serialize + DeserializeOwned + Clone, -{ - fn get_indexes(&'_ self) -> Box>> + '_> { - let v: Vec<&dyn Index>> = vec![&self.owner]; - Box::new(v.into_iter()) - } -} - -pub fn token_owner_idx(_pk: &[u8], d: &TokenInfo) -> Addr { - d.owner.clone() } diff --git a/contracts/cw721-base/src/upgrades/mod.rs b/contracts/cw721-base/src/upgrades/mod.rs deleted file mode 100644 index bdb01c30d..000000000 --- a/contracts/cw721-base/src/upgrades/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod v0_17; diff --git a/contracts/cw721-base/src/upgrades/v0_17.rs b/contracts/cw721-base/src/upgrades/v0_17.rs deleted file mode 100644 index c0bd63b98..000000000 --- a/contracts/cw721-base/src/upgrades/v0_17.rs +++ /dev/null @@ -1,27 +0,0 @@ -use cosmwasm_std::{CustomMsg, DepsMut, Response}; -use cw721_base_016 as v16; -use serde::{de::DeserializeOwned, Serialize}; - -use crate::ContractError; - -pub fn migrate(deps: DepsMut) -> Result, ContractError> -where - T: Serialize + DeserializeOwned + Clone, - Q: CustomMsg, - E: CustomMsg, -{ - // remove old minter info - let tract16 = v16::Cw721Contract::::default(); - let minter = tract16.minter.load(deps.storage)?; - tract16.minter.remove(deps.storage); - - // save new ownership info - let ownership = cw_ownable::initialize_owner(deps.storage, deps.api, Some(minter.as_str()))?; - - Ok(Response::new() - .add_attribute("action", "migrate") - .add_attribute("from_version", "0.16.0") - .add_attribute("to_version", "0.17.0") - .add_attribute("old_minter", minter) - .add_attributes(ownership.into_attributes())) -} diff --git a/contracts/cw721-expiration/README.md b/contracts/cw721-expiration/README.md index d12c11c8c..43bf5c961 100644 --- a/contracts/cw721-expiration/README.md +++ b/contracts/cw721-expiration/README.md @@ -52,7 +52,7 @@ To generate an optimized build run: docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/rust-optimizer:0.14.0 + cosmwasm/rust-optimizer:0.16.0 ``` ### Testing diff --git a/contracts/cw721-expiration/examples/schema.rs b/contracts/cw721-expiration/examples/schema.rs index aadffcf75..560945fa3 100644 --- a/contracts/cw721-expiration/examples/schema.rs +++ b/contracts/cw721-expiration/examples/schema.rs @@ -1,10 +1,23 @@ -use cosmwasm_schema::write_api; -use cw721_expiration::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use cosmwasm_schema::{export_schema_with_title, remove_schemas, schema_for}; +use cosmwasm_std::Empty; +use cw721::state::DefaultOptionMetadataExtension; +use cw721_expiration::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use std::env::current_dir; +use std::fs::create_dir_all; fn main() { - write_api! { - instantiate: InstantiateMsg, - execute: ExecuteMsg, - query: QueryMsg, - } + let mut out_dir = current_dir().unwrap(); + out_dir.push("schema"); + create_dir_all(&out_dir).unwrap(); + remove_schemas(&out_dir).unwrap(); + + // entry points - generate always with title for avoiding name suffixes like "..._empty_for_..." due to generics + export_schema_with_title(&schema_for!(InstantiateMsg), &out_dir, "InstantiateMsg"); + export_schema_with_title( + &schema_for!(ExecuteMsg::), + &out_dir, + "ExecuteMsg", + ); + export_schema_with_title(&schema_for!(QueryMsg), &out_dir, "QueryMsg"); + export_schema_with_title(&schema_for!(MigrateMsg), &out_dir, "MigrateMsg"); } diff --git a/contracts/cw721-expiration/schema/cw721-expiration.json b/contracts/cw721-expiration/schema/cw721-expiration.json deleted file mode 100644 index 3da3c0405..000000000 --- a/contracts/cw721-expiration/schema/cw721-expiration.json +++ /dev/null @@ -1,1825 +0,0 @@ -{ - "contract_name": "cw721-expiration", - "contract_version": "0.18.0", - "idl_version": "1.0.0", - "instantiate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "expiration_days", - "name", - "symbol" - ], - "properties": { - "expiration_days": { - "description": "max 65535 days", - "type": "integer", - "format": "uint16", - "minimum": 0.0 - }, - "minter": { - "description": "The minter is the only one who can create new NFTs. This is designed for a base NFT that is controlled by an external program or contract. You will likely replace this with custom logic in custom NFTs", - "type": [ - "string", - "null" - ] - }, - "name": { - "description": "Name of the NFT contract", - "type": "string" - }, - "symbol": { - "description": "Symbol of the NFT contract", - "type": "string" - }, - "withdraw_address": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - }, - "execute": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "description": "This is like Cw721ExecuteMsg but we add a Mint command for an owner to make this stand-alone. You will likely want to remove mint and use other control logic in any contract that inherits this.", - "oneOf": [ - { - "description": "Transfer is a base message to move a token to another account without triggering actions", - "type": "object", - "required": [ - "transfer_nft" - ], - "properties": { - "transfer_nft": { - "type": "object", - "required": [ - "recipient", - "token_id" - ], - "properties": { - "recipient": { - "type": "string" - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Send is a base message to transfer a token to a contract and trigger an action on the receiving contract.", - "type": "object", - "required": [ - "send_nft" - ], - "properties": { - "send_nft": { - "type": "object", - "required": [ - "contract", - "msg", - "token_id" - ], - "properties": { - "contract": { - "type": "string" - }, - "msg": { - "$ref": "#/definitions/Binary" - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Allows operator to transfer / send the token from the owner's account. If expiration is set, then this allowance has a time/height limit", - "type": "object", - "required": [ - "approve" - ], - "properties": { - "approve": { - "type": "object", - "required": [ - "spender", - "token_id" - ], - "properties": { - "expires": { - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "spender": { - "type": "string" - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Remove previously granted Approval", - "type": "object", - "required": [ - "revoke" - ], - "properties": { - "revoke": { - "type": "object", - "required": [ - "spender", - "token_id" - ], - "properties": { - "spender": { - "type": "string" - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Allows operator to transfer / send any token from the owner's account. If expiration is set, then this allowance has a time/height limit", - "type": "object", - "required": [ - "approve_all" - ], - "properties": { - "approve_all": { - "type": "object", - "required": [ - "operator" - ], - "properties": { - "expires": { - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "operator": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Remove previously granted ApproveAll permission", - "type": "object", - "required": [ - "revoke_all" - ], - "properties": { - "revoke_all": { - "type": "object", - "required": [ - "operator" - ], - "properties": { - "operator": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Mint a new NFT, can only be called by the contract minter", - "type": "object", - "required": [ - "mint" - ], - "properties": { - "mint": { - "type": "object", - "required": [ - "owner", - "token_id" - ], - "properties": { - "extension": { - "description": "Any custom extension used by this contract", - "anyOf": [ - { - "$ref": "#/definitions/Empty" - }, - { - "type": "null" - } - ] - }, - "owner": { - "description": "The owner of the newly minter NFT", - "type": "string" - }, - "token_id": { - "description": "Unique ID of the NFT", - "type": "string" - }, - "token_uri": { - "description": "Universal resource identifier for this NFT Should point to a JSON file that conforms to the ERC721 Metadata JSON Schema", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Burn an NFT the sender has access to", - "type": "object", - "required": [ - "burn" - ], - "properties": { - "burn": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Extension msg", - "type": "object", - "required": [ - "extension" - ], - "properties": { - "extension": { - "type": "object", - "required": [ - "msg" - ], - "properties": { - "msg": { - "$ref": "#/definitions/Empty" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Sets address to send withdrawn fees to. Only owner can call this.", - "type": "object", - "required": [ - "set_withdraw_address" - ], - "properties": { - "set_withdraw_address": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Removes the withdraw address, so fees are sent to the contract. Only owner can call this.", - "type": "object", - "required": [ - "remove_withdraw_address" - ], - "properties": { - "remove_withdraw_address": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Withdraw from the contract to the given address. Anyone can call this, which is okay since withdraw address has been set by owner.", - "type": "object", - "required": [ - "withdraw_funds" - ], - "properties": { - "withdraw_funds": { - "type": "object", - "required": [ - "amount" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Update the contract's ownership. The `action` to be provided can be either to propose transferring ownership to an account, accept a pending ownership transfer, or renounce the ownership permanently.", - "type": "object", - "required": [ - "update_ownership" - ], - "properties": { - "update_ownership": { - "$ref": "#/definitions/Action" - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Action": { - "description": "Actions that can be taken to alter the contract's ownership", - "oneOf": [ - { - "description": "Propose to transfer the contract's ownership to another account, optionally with an expiry time.\n\nCan only be called by the contract's current owner.\n\nAny existing pending ownership transfer is overwritten.", - "type": "object", - "required": [ - "transfer_ownership" - ], - "properties": { - "transfer_ownership": { - "type": "object", - "required": [ - "new_owner" - ], - "properties": { - "expiry": { - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "new_owner": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Accept the pending ownership transfer.\n\nCan only be called by the pending owner.", - "type": "string", - "enum": [ - "accept_ownership" - ] - }, - { - "description": "Give up the contract's ownership and the possibility of appointing a new owner.\n\nCan only be invoked by the contract's current owner.\n\nAny existing pending ownership transfer is canceled.", - "type": "string", - "enum": [ - "renounce_ownership" - ] - } - ] - }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", - "type": "string" - }, - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "query": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "oneOf": [ - { - "description": "Return the owner of the given token, error if token does not exist", - "type": "object", - "required": [ - "owner_of" - ], - "properties": { - "owner_of": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "include_expired": { - "description": "unset or false will filter out expired approvals, you must set to true to see them", - "type": [ - "boolean", - "null" - ] - }, - "include_invalid": { - "description": "unset or false will filter out expired nfts, you must set to true to see them", - "type": [ - "boolean", - "null" - ] - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Return operator that can access all of the owner's tokens.", - "type": "object", - "required": [ - "approval" - ], - "properties": { - "approval": { - "type": "object", - "required": [ - "spender", - "token_id" - ], - "properties": { - "include_expired": { - "type": [ - "boolean", - "null" - ] - }, - "include_invalid": { - "description": "unset or false will filter out expired nfts, you must set to true to see them", - "type": [ - "boolean", - "null" - ] - }, - "spender": { - "type": "string" - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Return approvals that a token has", - "type": "object", - "required": [ - "approvals" - ], - "properties": { - "approvals": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "include_expired": { - "type": [ - "boolean", - "null" - ] - }, - "include_invalid": { - "description": "unset or false will filter out expired nfts, you must set to true to see them", - "type": [ - "boolean", - "null" - ] - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Return approval of a given operator for all tokens of an owner, error if not set", - "type": "object", - "required": [ - "operator" - ], - "properties": { - "operator": { - "type": "object", - "required": [ - "operator", - "owner" - ], - "properties": { - "include_expired": { - "type": [ - "boolean", - "null" - ] - }, - "operator": { - "type": "string" - }, - "owner": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "List all operators that can access all of the owner's tokens", - "type": "object", - "required": [ - "all_operators" - ], - "properties": { - "all_operators": { - "type": "object", - "required": [ - "owner" - ], - "properties": { - "include_expired": { - "description": "unset or false will filter out expired items, you must set to true to see them", - "type": [ - "boolean", - "null" - ] - }, - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "owner": { - "type": "string" - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Total number of tokens issued, including all expired NFTs", - "type": "object", - "required": [ - "num_tokens" - ], - "properties": { - "num_tokens": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "With MetaData Extension. Returns top-level metadata about the contract", - "type": "object", - "required": [ - "contract_info" - ], - "properties": { - "contract_info": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "With MetaData Extension. Returns metadata about one particular token, based on *ERC721 Metadata JSON Schema* but directly from the contract", - "type": "object", - "required": [ - "nft_info" - ], - "properties": { - "nft_info": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "include_invalid": { - "description": "unset or false will filter out expired nfts, you must set to true to see them", - "type": [ - "boolean", - "null" - ] - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "With MetaData Extension. Returns the result of both `NftInfo` and `OwnerOf` as one query as an optimization for clients", - "type": "object", - "required": [ - "all_nft_info" - ], - "properties": { - "all_nft_info": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "include_expired": { - "description": "unset or false will filter out expired approvals, you must set to true to see them", - "type": [ - "boolean", - "null" - ] - }, - "include_invalid": { - "description": "unset or false will filter out expired nfts, you must set to true to see them", - "type": [ - "boolean", - "null" - ] - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "With Enumerable extension. Returns all tokens owned by the given address, [] if unset.", - "type": "object", - "required": [ - "tokens" - ], - "properties": { - "tokens": { - "type": "object", - "required": [ - "owner" - ], - "properties": { - "include_invalid": { - "description": "unset or false will filter out expired nfts, you must set to true to see them", - "type": [ - "boolean", - "null" - ] - }, - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "owner": { - "type": "string" - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "With Enumerable extension. Requires pagination. Lists all token_ids controlled by the contract.", - "type": "object", - "required": [ - "all_tokens" - ], - "properties": { - "all_tokens": { - "type": "object", - "properties": { - "include_invalid": { - "description": "unset or false will filter out expired nfts, you must set to true to see them", - "type": [ - "boolean", - "null" - ] - }, - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Return the minter", - "type": "object", - "required": [ - "minter" - ], - "properties": { - "minter": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Extension query", - "type": "object", - "required": [ - "extension" - ], - "properties": { - "extension": { - "type": "object", - "required": [ - "msg" - ], - "properties": { - "msg": { - "$ref": "#/definitions/Empty" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Query the contract's ownership information", - "type": "object", - "required": [ - "ownership" - ], - "properties": { - "ownership": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - } - } - }, - "migrate": null, - "sudo": null, - "responses": { - "all_nft_info": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AllNftInfoResponse_for_Nullable_Empty", - "type": "object", - "required": [ - "access", - "info" - ], - "properties": { - "access": { - "description": "Who can transfer the token", - "allOf": [ - { - "$ref": "#/definitions/OwnerOfResponse" - } - ] - }, - "info": { - "description": "Data on the token itself,", - "allOf": [ - { - "$ref": "#/definitions/NftInfoResponse_for_Nullable_Empty" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "NftInfoResponse_for_Nullable_Empty": { - "type": "object", - "properties": { - "extension": { - "description": "You can add any custom metadata here when you extend cw721-base", - "anyOf": [ - { - "$ref": "#/definitions/Empty" - }, - { - "type": "null" - } - ] - }, - "token_uri": { - "description": "Universal resource identifier for this NFT Should point to a JSON file that conforms to the ERC721 Metadata JSON Schema", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - }, - "OwnerOfResponse": { - "type": "object", - "required": [ - "approvals", - "owner" - ], - "properties": { - "approvals": { - "description": "If set this address is approved to transfer/send the token as well", - "type": "array", - "items": { - "$ref": "#/definitions/Approval" - } - }, - "owner": { - "description": "Owner of the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "all_operators": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "OperatorsResponse", - "type": "object", - "required": [ - "operators" - ], - "properties": { - "operators": { - "type": "array", - "items": { - "$ref": "#/definitions/Approval" - } - } - }, - "additionalProperties": false, - "definitions": { - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "all_tokens": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "TokensResponse", - "type": "object", - "required": [ - "tokens" - ], - "properties": { - "tokens": { - "description": "Contains all token_ids in lexicographical ordering If there are more than `limit`, use `start_after` in future queries to achieve pagination.", - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - }, - "approval": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ApprovalResponse", - "type": "object", - "required": [ - "approval" - ], - "properties": { - "approval": { - "$ref": "#/definitions/Approval" - } - }, - "additionalProperties": false, - "definitions": { - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "approvals": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ApprovalsResponse", - "type": "object", - "required": [ - "approvals" - ], - "properties": { - "approvals": { - "type": "array", - "items": { - "$ref": "#/definitions/Approval" - } - } - }, - "additionalProperties": false, - "definitions": { - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "contract_info": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ContractInfoResponse", - "type": "object", - "required": [ - "name", - "symbol" - ], - "properties": { - "name": { - "type": "string" - }, - "symbol": { - "type": "string" - } - }, - "additionalProperties": false - }, - "extension": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Null", - "type": "null" - }, - "minter": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MinterResponse", - "description": "Shows who can mint these tokens", - "type": "object", - "properties": { - "minter": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - }, - "nft_info": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "NftInfoResponse_for_Nullable_Empty", - "type": "object", - "properties": { - "extension": { - "description": "You can add any custom metadata here when you extend cw721-base", - "anyOf": [ - { - "$ref": "#/definitions/Empty" - }, - { - "type": "null" - } - ] - }, - "token_uri": { - "description": "Universal resource identifier for this NFT Should point to a JSON file that conforms to the ERC721 Metadata JSON Schema", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false, - "definitions": { - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - } - } - }, - "num_tokens": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "NumTokensResponse", - "type": "object", - "required": [ - "count" - ], - "properties": { - "count": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - "operator": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "OperatorResponse", - "type": "object", - "required": [ - "approval" - ], - "properties": { - "approval": { - "$ref": "#/definitions/Approval" - } - }, - "additionalProperties": false, - "definitions": { - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "owner_of": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "OwnerOfResponse", - "type": "object", - "required": [ - "approvals", - "owner" - ], - "properties": { - "approvals": { - "description": "If set this address is approved to transfer/send the token as well", - "type": "array", - "items": { - "$ref": "#/definitions/Approval" - } - }, - "owner": { - "description": "Owner of the token", - "type": "string" - } - }, - "additionalProperties": false, - "definitions": { - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "ownership": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Ownership_for_String", - "description": "The contract's ownership info", - "type": "object", - "properties": { - "owner": { - "description": "The contract's current owner. `None` if the ownership has been renounced.", - "type": [ - "string", - "null" - ] - }, - "pending_expiry": { - "description": "The deadline for the pending owner to accept the ownership. `None` if there isn't a pending ownership transfer, or if a transfer exists and it doesn't have a deadline.", - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "pending_owner": { - "description": "The account who has been proposed to take over the ownership. `None` if there isn't a pending ownership transfer.", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false, - "definitions": { - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "tokens": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "TokensResponse", - "type": "object", - "required": [ - "tokens" - ], - "properties": { - "tokens": { - "description": "Contains all token_ids in lexicographical ordering If there are more than `limit`, use `start_after` in future queries to achieve pagination.", - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - } - } -} diff --git a/contracts/cw721-expiration/schema/execute_msg.json b/contracts/cw721-expiration/schema/execute_msg.json new file mode 100644 index 000000000..2bcf2fea7 --- /dev/null +++ b/contracts/cw721-expiration/schema/execute_msg.json @@ -0,0 +1,562 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "update_ownership" + ], + "properties": { + "update_ownership": { + "$ref": "#/definitions/Action" + } + }, + "additionalProperties": false + }, + { + "description": "Transfer is a base message to move a token to another account without triggering actions", + "type": "object", + "required": [ + "transfer_nft" + ], + "properties": { + "transfer_nft": { + "type": "object", + "required": [ + "recipient", + "token_id" + ], + "properties": { + "recipient": { + "type": "string" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Send is a base message to transfer a token to a contract and trigger an action on the receiving contract.", + "type": "object", + "required": [ + "send_nft" + ], + "properties": { + "send_nft": { + "type": "object", + "required": [ + "contract", + "msg", + "token_id" + ], + "properties": { + "contract": { + "type": "string" + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Allows operator to transfer / send the token from the owner's account. If expiration is set, then this allowance has a time/height limit", + "type": "object", + "required": [ + "approve" + ], + "properties": { + "approve": { + "type": "object", + "required": [ + "spender", + "token_id" + ], + "properties": { + "expires": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "spender": { + "type": "string" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Remove previously granted Approval", + "type": "object", + "required": [ + "revoke" + ], + "properties": { + "revoke": { + "type": "object", + "required": [ + "spender", + "token_id" + ], + "properties": { + "spender": { + "type": "string" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Allows operator to transfer / send any token from the owner's account. If expiration is set, then this allowance has a time/height limit", + "type": "object", + "required": [ + "approve_all" + ], + "properties": { + "approve_all": { + "type": "object", + "required": [ + "operator" + ], + "properties": { + "expires": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "operator": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Remove previously granted ApproveAll permission", + "type": "object", + "required": [ + "revoke_all" + ], + "properties": { + "revoke_all": { + "type": "object", + "required": [ + "operator" + ], + "properties": { + "operator": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Mint a new NFT, can only be called by the contract minter", + "type": "object", + "required": [ + "mint" + ], + "properties": { + "mint": { + "type": "object", + "required": [ + "owner", + "token_id" + ], + "properties": { + "extension": { + "description": "Any custom extension used by this contract", + "anyOf": [ + { + "$ref": "#/definitions/Metadata" + }, + { + "type": "null" + } + ] + }, + "owner": { + "description": "The owner of the newly minter NFT", + "type": "string" + }, + "token_id": { + "description": "Unique ID of the NFT", + "type": "string" + }, + "token_uri": { + "description": "Universal resource identifier for this NFT Should point to a JSON file that conforms to the ERC721 Metadata JSON Schema", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Burn an NFT the sender has access to", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Extension msg", + "type": "object", + "required": [ + "extension" + ], + "properties": { + "extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Sets address to send withdrawn fees to. Only owner can call this.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Removes the withdraw address, so fees are sent to the contract. Only owner can call this.", + "type": "object", + "required": [ + "remove_withdraw_address" + ], + "properties": { + "remove_withdraw_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Withdraw from the contract to the given address. Anyone can call this, which is okay since withdraw address has been set by owner.", + "type": "object", + "required": [ + "withdraw_funds" + ], + "properties": { + "withdraw_funds": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Action": { + "description": "Actions that can be taken to alter the contract's ownership", + "oneOf": [ + { + "description": "Propose to transfer the contract's ownership to another account, optionally with an expiry time.\n\nCan only be called by the contract's current owner.\n\nAny existing pending ownership transfer is overwritten.", + "type": "object", + "required": [ + "transfer_ownership" + ], + "properties": { + "transfer_ownership": { + "type": "object", + "required": [ + "new_owner" + ], + "properties": { + "expiry": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "new_owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Accept the pending ownership transfer.\n\nCan only be called by the pending owner.", + "type": "string", + "enum": [ + "accept_ownership" + ] + }, + { + "description": "Give up the contract's ownership and the possibility of appointing a new owner.\n\nCan only be invoked by the contract's current owner.\n\nAny existing pending ownership transfer is canceled.", + "type": "string", + "enum": [ + "renounce_ownership" + ] + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Metadata": { + "type": "object", + "properties": { + "animation_url": { + "type": [ + "string", + "null" + ] + }, + "attributes": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Trait" + } + }, + "background_color": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "external_url": { + "type": [ + "string", + "null" + ] + }, + "image": { + "type": [ + "string", + "null" + ] + }, + "image_data": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "youtube_url": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Trait": { + "type": "object", + "required": [ + "trait_type", + "value" + ], + "properties": { + "display_type": { + "type": [ + "string", + "null" + ] + }, + "trait_type": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cw721-expiration/schema/instantiate_msg.json b/contracts/cw721-expiration/schema/instantiate_msg.json new file mode 100644 index 000000000..f52482235 --- /dev/null +++ b/contracts/cw721-expiration/schema/instantiate_msg.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "expiration_days", + "name", + "symbol" + ], + "properties": { + "expiration_days": { + "description": "max 65535 days", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "minter": { + "description": "The minter is the only one who can create new NFTs. This is designed for a base NFT that is controlled by an external program or contract. You will likely replace this with custom logic in custom NFTs", + "type": [ + "string", + "null" + ] + }, + "name": { + "description": "Name of the NFT contract", + "type": "string" + }, + "symbol": { + "description": "Symbol of the NFT contract", + "type": "string" + }, + "withdraw_address": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false +} diff --git a/contracts/cw721-expiration/schema/migrate_msg.json b/contracts/cw721-expiration/schema/migrate_msg.json new file mode 100644 index 000000000..f83fa9508 --- /dev/null +++ b/contracts/cw721-expiration/schema/migrate_msg.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "with_update" + ], + "properties": { + "with_update": { + "type": "object", + "properties": { + "creator": { + "type": [ + "string", + "null" + ] + }, + "minter": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/cw721-expiration/schema/query_msg.json b/contracts/cw721-expiration/schema/query_msg.json new file mode 100644 index 000000000..8fd419769 --- /dev/null +++ b/contracts/cw721-expiration/schema/query_msg.json @@ -0,0 +1,458 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "description": "Return the owner of the given token, error if token does not exist", + "type": "object", + "required": [ + "owner_of" + ], + "properties": { + "owner_of": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "include_expired": { + "description": "unset or false will filter out expired approvals, you must set to true to see them", + "type": [ + "boolean", + "null" + ] + }, + "include_expired_nft": { + "description": "unset or false will filter out expired nfts, you must set to true to see them", + "type": [ + "boolean", + "null" + ] + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return operator that can access all of the owner's tokens.", + "type": "object", + "required": [ + "approval" + ], + "properties": { + "approval": { + "type": "object", + "required": [ + "spender", + "token_id" + ], + "properties": { + "include_expired": { + "type": [ + "boolean", + "null" + ] + }, + "include_expired_nft": { + "description": "unset or false will filter out expired nfts, you must set to true to see them", + "type": [ + "boolean", + "null" + ] + }, + "spender": { + "type": "string" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return approvals that a token has", + "type": "object", + "required": [ + "approvals" + ], + "properties": { + "approvals": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "include_expired": { + "type": [ + "boolean", + "null" + ] + }, + "include_expired_nft": { + "description": "unset or false will filter out expired nfts, you must set to true to see them", + "type": [ + "boolean", + "null" + ] + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "With MetaData Extension. Returns metadata about one particular token, based on *ERC721 Metadata JSON Schema* but directly from the contract", + "type": "object", + "required": [ + "nft_info" + ], + "properties": { + "nft_info": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "include_expired_nft": { + "description": "unset or false will filter out expired nfts, you must set to true to see them", + "type": [ + "boolean", + "null" + ] + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "With MetaData Extension. Returns the result of both `NftInfo` and `OwnerOf` as one query as an optimization for clients", + "type": "object", + "required": [ + "all_nft_info" + ], + "properties": { + "all_nft_info": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "include_expired": { + "description": "unset or false will filter out expired approvals, you must set to true to see them", + "type": [ + "boolean", + "null" + ] + }, + "include_expired_nft": { + "description": "unset or false will filter out expired nfts, you must set to true to see them", + "type": [ + "boolean", + "null" + ] + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "With Enumerable extension. Returns all tokens owned by the given address, [] if unset.", + "type": "object", + "required": [ + "tokens" + ], + "properties": { + "tokens": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "include_expired_nft": { + "description": "unset or false will filter out expired nfts, you must set to true to see them", + "type": [ + "boolean", + "null" + ] + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "With Enumerable extension. Requires pagination. Lists all token_ids controlled by the contract.", + "type": "object", + "required": [ + "all_tokens" + ], + "properties": { + "all_tokens": { + "type": "object", + "properties": { + "include_expired_nft": { + "description": "unset or false will filter out expired nfts, you must set to true to see them", + "type": [ + "boolean", + "null" + ] + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return approval of a given operator for all tokens of an owner, error if not set", + "type": "object", + "required": [ + "operator" + ], + "properties": { + "operator": { + "type": "object", + "required": [ + "operator", + "owner" + ], + "properties": { + "include_expired": { + "type": [ + "boolean", + "null" + ] + }, + "operator": { + "type": "string" + }, + "owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "List all operators that can access all of the owner's tokens", + "type": "object", + "required": [ + "all_operators" + ], + "properties": { + "all_operators": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "include_expired": { + "description": "unset or false will filter out expired items, you must set to true to see them", + "type": [ + "boolean", + "null" + ] + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "owner": { + "type": "string" + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Total number of tokens issued, including all expired NFTs", + "type": "object", + "required": [ + "num_tokens" + ], + "properties": { + "num_tokens": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "contract_info" + ], + "properties": { + "contract_info": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "With MetaData Extension. Returns top-level metadata about the contract", + "type": "object", + "required": [ + "get_collection_info" + ], + "properties": { + "get_collection_info": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ownership" + ], + "properties": { + "ownership": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_minter_ownership" + ], + "properties": { + "get_minter_ownership": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return the minter", + "type": "object", + "required": [ + "minter" + ], + "properties": { + "minter": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Extension query", + "type": "object", + "required": [ + "extension" + ], + "properties": { + "extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_withdraw_address" + ], + "properties": { + "get_withdraw_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + } + } +} diff --git a/contracts/cw721-expiration/src/contract_tests.rs b/contracts/cw721-expiration/src/contract_tests.rs index 7cfe8f501..c17337ba2 100644 --- a/contracts/cw721-expiration/src/contract_tests.rs +++ b/contracts/cw721-expiration/src/contract_tests.rs @@ -3,33 +3,40 @@ use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; use cosmwasm_std::{ - from_json, to_json_binary, Addr, CosmosMsg, DepsMut, Response, StdError, WasmMsg, + from_json, to_json_binary, Addr, CosmosMsg, DepsMut, Empty, Response, StdError, WasmMsg, }; -use cw721::{ - Approval, ApprovalResponse, ContractInfoResponse, Cw721ReceiveMsg, Expiration, NftInfoResponse, - OperatorResponse, OperatorsResponse, OwnerOfResponse, TokensResponse, +use cw721::error::Cw721ContractError; +use cw721::msg::{ + ApprovalResponse, Cw721ExecuteMsg, NftInfoResponse, OperatorResponse, OperatorsResponse, + OwnerOfResponse, TokensResponse, }; -use cw_ownable::OwnershipError; +use cw721::receiver::Cw721ReceiveMsg; +use cw721::state::{CollectionInfo, MINTER}; +use cw721::{query::Cw721Query, Approval, Expiration}; +use cw_ownable::{Action, Ownership, OwnershipError}; use crate::state::Cw721ExpirationContract; use crate::{ - error::ContractError, msg::ExecuteMsg, msg::InstantiateMsg, msg::QueryMsg, Extension, - MinterResponse, + error::ContractError, msg::InstantiateMsg, msg::QueryMsg, DefaultOptionMetadataExtension, }; -use cw721_base::ContractError as Cw721ContractError; -const MINTER: &str = "merlin"; +const MINTER_ADDR: &str = "minter"; +const CREATOR_ADDR: &str = "creator"; const CONTRACT_NAME: &str = "Magic Power"; const SYMBOL: &str = "MGK"; -fn setup_contract(deps: DepsMut<'_>, expiration_days: u16) -> Cw721ExpirationContract<'static> { - let contract = Cw721ExpirationContract::default(); +fn setup_contract( + deps: DepsMut<'_>, + expiration_days: u16, +) -> Cw721ExpirationContract<'static, DefaultOptionMetadataExtension, Empty, Empty, Empty> { + let contract = + Cw721ExpirationContract::::default(); let msg = InstantiateMsg { expiration_days, name: CONTRACT_NAME.to_string(), symbol: SYMBOL.to_string(), - minter: Some(String::from(MINTER)), + minter: Some(String::from(MINTER_ADDR)), withdraw_address: None, }; let info = mock_info("creator", &[]); @@ -41,41 +48,115 @@ fn setup_contract(deps: DepsMut<'_>, expiration_days: u16) -> Cw721ExpirationCon #[test] fn proper_instantiation() { let mut deps = mock_dependencies(); - let contract = Cw721ExpirationContract::default(); + let contract = + Cw721ExpirationContract::::default(); let msg = InstantiateMsg { expiration_days: 1, name: CONTRACT_NAME.to_string(), symbol: SYMBOL.to_string(), - minter: Some(String::from(MINTER)), - withdraw_address: None, + minter: Some(String::from(MINTER_ADDR)), + withdraw_address: Some(String::from(CREATOR_ADDR)), }; let info = mock_info("creator", &[]); + let env = mock_env(); // we can just call .unwrap() to assert this was a success let res = contract - .instantiate(deps.as_mut(), mock_env(), info, msg) + .instantiate(deps.as_mut(), env.clone(), info, msg) .unwrap(); assert_eq!(0, res.messages.len()); // it worked, let's query the state - let res = contract.minter(deps.as_ref()).unwrap(); - assert_eq!(Some(MINTER.to_string()), res.minter); - let info = contract.contract_info(deps.as_ref()).unwrap(); + let minter_ownership = MINTER.get_ownership(deps.as_ref().storage).unwrap(); + assert_eq!(Some(Addr::unchecked(MINTER_ADDR)), minter_ownership.owner); + let collection_info = contract + .base_contract + .query_collection_info(deps.as_ref(), env.clone()) + .unwrap(); assert_eq!( - info, - ContractInfoResponse { + collection_info, + CollectionInfo { name: CONTRACT_NAME.to_string(), symbol: SYMBOL.to_string(), } ); - let count = contract.num_tokens(deps.as_ref()).unwrap(); + let withdraw_address = contract + .base_contract + .config + .withdraw_address + .may_load(deps.as_ref().storage) + .unwrap(); + assert_eq!(Some(CREATOR_ADDR.to_string()), withdraw_address); + + let count = contract + .base_contract + .query_num_tokens(deps.as_ref(), env) + .unwrap(); assert_eq!(0, count.count); // list the token_ids let tokens = contract - .all_tokens(deps.as_ref(), mock_env(), None, None, false) + .query_all_tokens_include_expired_nft(deps.as_ref(), mock_env(), None, None, false) + .unwrap(); + assert_eq!(0, tokens.tokens.len()); +} + +#[test] +fn proper_instantiation_with_collection_info() { + let mut deps = mock_dependencies(); + let contract = + Cw721ExpirationContract::::default(); + + let msg = InstantiateMsg { + expiration_days: 1, + name: CONTRACT_NAME.to_string(), + symbol: SYMBOL.to_string(), + minter: Some(String::from(MINTER_ADDR)), + withdraw_address: Some(String::from(CREATOR_ADDR)), + }; + let info = mock_info("creator", &[]); + let env = mock_env(); + + // we can just call .unwrap() to assert this was a success + let res = contract + .instantiate(deps.as_mut(), env.clone(), info, msg) + .unwrap(); + assert_eq!(0, res.messages.len()); + + // it worked, let's query the state + let minter_ownership = MINTER.get_ownership(deps.as_ref().storage).unwrap(); + assert_eq!(Some(Addr::unchecked(MINTER_ADDR)), minter_ownership.owner); + let collection_info = contract + .base_contract + .query_collection_info(deps.as_ref(), env.clone()) + .unwrap(); + assert_eq!( + collection_info, + CollectionInfo { + name: CONTRACT_NAME.to_string(), + symbol: SYMBOL.to_string(), + } + ); + + let withdraw_address = contract + .base_contract + .config + .withdraw_address + .may_load(deps.as_ref().storage) + .unwrap(); + assert_eq!(Some(CREATOR_ADDR.to_string()), withdraw_address); + + let count = contract + .base_contract + .query_num_tokens(deps.as_ref(), env) + .unwrap(); + assert_eq!(0, count.count); + + // list the token_ids + let tokens = contract + .query_all_tokens_include_expired_nft(deps.as_ref(), mock_env(), None, None, false) .unwrap(); assert_eq!(0, tokens.tokens.len()); } @@ -88,7 +169,7 @@ fn test_mint() { let token_id = "atomize".to_string(); let token_uri = "https://www.merriam-webster.com/dictionary/atomize".to_string(); - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id: token_id.clone(), owner: String::from("medusa"), token_uri: Some(token_uri.clone()), @@ -97,8 +178,9 @@ fn test_mint() { // random cannot mint let random = mock_info("random", &[]); + let env = mock_env(); let err = contract - .execute(deps.as_mut(), mock_env(), random, mint_msg.clone()) + .execute(deps.as_mut(), env.clone(), random, mint_msg.clone()) .unwrap_err(); assert_eq!( err, @@ -106,27 +188,30 @@ fn test_mint() { ); // minter can mint - let allowed = mock_info(MINTER, &[]); + let allowed = mock_info(MINTER_ADDR, &[]); let _ = contract .execute(deps.as_mut(), mock_env(), allowed, mint_msg) .unwrap(); // ensure num tokens increases - let count = contract.num_tokens(deps.as_ref()).unwrap(); + let count = contract + .base_contract + .query_num_tokens(deps.as_ref(), env) + .unwrap(); assert_eq!(1, count.count); // unknown nft returns error let _ = contract - .nft_info(deps.as_ref(), mock_env(), "unknown".to_string(), false) + .query_nft_info_include_expired_nft(deps.as_ref(), mock_env(), "unknown".to_string(), false) .unwrap_err(); // this nft info is correct let info = contract - .nft_info(deps.as_ref(), mock_env(), token_id.clone(), false) + .query_nft_info_include_expired_nft(deps.as_ref(), mock_env(), token_id.clone(), false) .unwrap(); assert_eq!( info, - NftInfoResponse:: { + NftInfoResponse:: { token_uri: Some(token_uri), extension: None, } @@ -134,7 +219,13 @@ fn test_mint() { // owner info is correct let owner = contract - .owner_of(deps.as_ref(), mock_env(), token_id.clone(), true, false) + .query_owner_of_include_expired_nft( + deps.as_ref(), + mock_env(), + token_id.clone(), + true, + false, + ) .unwrap(); assert_eq!( owner, @@ -152,14 +243,14 @@ fn test_mint() { assert_eq!(mint_timestamp, mock_env().block.time); // Cannot mint same token_id again - let mint_msg2 = ExecuteMsg::Mint { + let mint_msg2 = Cw721ExecuteMsg::Mint { token_id: token_id.clone(), owner: String::from("hercules"), token_uri: None, extension: None, }; - let allowed = mock_info(MINTER, &[]); + let allowed = mock_info(MINTER_ADDR, &[]); let err = contract .execute(deps.as_mut(), mock_env(), allowed, mint_msg2) .unwrap_err(); @@ -167,7 +258,7 @@ fn test_mint() { // list the token_ids let tokens = contract - .all_tokens(deps.as_ref(), mock_env(), None, None, false) + .query_all_tokens_include_expired_nft(deps.as_ref(), mock_env(), None, None, false) .unwrap(); assert_eq!(1, tokens.tokens.len()); assert_eq!(vec![token_id], tokens.tokens); @@ -181,7 +272,7 @@ fn test_update_minter() { let token_id = "petrify".to_string(); let token_uri = "https://www.merriam-webster.com/dictionary/petrify".to_string(); - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id, owner: String::from("medusa"), token_uri: Some(token_uri.clone()), @@ -189,7 +280,7 @@ fn test_update_minter() { }; // Minter can mint - let minter_info = mock_info(MINTER, &[]); + let minter_info = mock_info(MINTER_ADDR, &[]); let _ = contract .execute(deps.as_mut(), mock_env(), minter_info.clone(), mint_msg) .unwrap(); @@ -201,7 +292,7 @@ fn test_update_minter() { deps.as_mut(), mock_env(), minter_info.clone(), - ExecuteMsg::UpdateOwnership(cw_ownable::Action::TransferOwnership { + Cw721ExecuteMsg::UpdateOwnership(Action::TransferOwnership { new_owner: "random".to_string(), expiry: None, }), @@ -209,26 +300,18 @@ fn test_update_minter() { .unwrap(); // Minter does not change until ownership transfer completes. - let minter: MinterResponse = from_json( - contract - .query(deps.as_ref(), mock_env(), QueryMsg::Minter {}) - .unwrap(), - ) - .unwrap(); - assert_eq!(minter.minter, Some(MINTER.to_string())); - // Pending ownership transfer should be discoverable via query. - let ownership: cw_ownable::Ownership = from_json( + let ownership: Ownership = from_json( contract - .query(deps.as_ref(), mock_env(), QueryMsg::Ownership {}) + .query(deps.as_ref(), mock_env(), QueryMsg::GetMinterOwnership {}) .unwrap(), ) .unwrap(); assert_eq!( ownership, - cw_ownable::Ownership:: { - owner: Some(Addr::unchecked(MINTER)), + Ownership:: { + owner: Some(Addr::unchecked(MINTER_ADDR)), pending_owner: Some(Addr::unchecked("random")), pending_expiry: None, } @@ -241,20 +324,20 @@ fn test_update_minter() { deps.as_mut(), mock_env(), random_info.clone(), - ExecuteMsg::UpdateOwnership(cw_ownable::Action::AcceptOwnership), + Cw721ExecuteMsg::UpdateOwnership(Action::AcceptOwnership), ) .unwrap(); // Minter changes after ownership transfer is accepted. - let minter: MinterResponse = from_json( + let minter_ownership: Ownership = from_json( contract - .query(deps.as_ref(), mock_env(), QueryMsg::Minter {}) + .query(deps.as_ref(), mock_env(), QueryMsg::GetMinterOwnership {}) .unwrap(), ) .unwrap(); - assert_eq!(minter.minter, Some("random".to_string())); + assert_eq!(minter_ownership.owner, Some(random_info.sender.clone())); - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id: "randoms_token".to_string(), owner: String::from("medusa"), token_uri: Some(token_uri), @@ -284,20 +367,20 @@ fn test_burn() { let token_id = "petrify".to_string(); let token_uri = "https://www.merriam-webster.com/dictionary/petrify".to_string(); - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id: token_id.clone(), - owner: MINTER.to_string(), + owner: MINTER_ADDR.to_string(), token_uri: Some(token_uri), extension: None, }; - let burn_msg = ExecuteMsg::Burn { + let burn_msg = Cw721ExecuteMsg::Burn { token_id: token_id.clone(), }; // mint some NFT let mut env = mock_env(); - let minter = mock_info(MINTER, &[]); + let minter = mock_info(MINTER_ADDR, &[]); let _ = contract .execute(deps.as_mut(), env.clone(), minter.clone(), mint_msg.clone()) .unwrap(); @@ -318,17 +401,25 @@ fn test_burn() { .unwrap(); // ensure num tokens decreases - let count = contract.num_tokens(deps.as_ref()).unwrap(); + let count = contract + .base_contract + .query_num_tokens(deps.as_ref(), env.clone()) + .unwrap(); assert_eq!(0, count.count); // trying to get nft returns error let _ = contract - .nft_info(deps.as_ref(), env.clone(), "petrify".to_string(), false) + .query_nft_info_include_expired_nft( + deps.as_ref(), + env.clone(), + "petrify".to_string(), + false, + ) .unwrap_err(); // list the token_ids let tokens = contract - .all_tokens(deps.as_ref(), env.clone(), None, None, false) + .query_all_tokens_include_expired_nft(deps.as_ref(), env.clone(), None, None, false) .unwrap(); assert!(tokens.tokens.is_empty()); @@ -364,7 +455,7 @@ fn test_transfer_nft() { let token_uri = "https://www.merriam-webster.com/dictionary/melt".to_string(); let owner = "owner"; - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id: token_id.clone(), owner: String::from(owner), token_uri: Some(token_uri), @@ -372,14 +463,14 @@ fn test_transfer_nft() { }; let mut env = mock_env(); - let minter = mock_info(MINTER, &[]); + let minter = mock_info(MINTER_ADDR, &[]); contract .execute(deps.as_mut(), env.clone(), minter, mint_msg) .unwrap(); // random cannot transfer let random = mock_info("random", &[]); - let transfer_msg = ExecuteMsg::TransferNft { + let transfer_msg = Cw721ExecuteMsg::TransferNft { recipient: String::from("random"), token_id: token_id.clone(), }; @@ -395,7 +486,7 @@ fn test_transfer_nft() { // owner can let owner_info = mock_info(owner, &[]); let new_owner = "random"; - let transfer_msg = ExecuteMsg::TransferNft { + let transfer_msg = Cw721ExecuteMsg::TransferNft { recipient: String::from(new_owner), token_id: token_id.clone(), }; @@ -444,7 +535,7 @@ fn test_send_nft() { let token_id = "melt".to_string(); let token_uri = "https://www.merriam-webster.com/dictionary/melt".to_string(); - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id: token_id.clone(), owner: String::from("venus"), token_uri: Some(token_uri), @@ -452,14 +543,14 @@ fn test_send_nft() { }; let mut env = mock_env(); - let minter = mock_info(MINTER, &[]); + let minter = mock_info(MINTER_ADDR, &[]); contract .execute(deps.as_mut(), env.clone(), minter, mint_msg) .unwrap(); let msg = to_json_binary("You now have the melting power").unwrap(); let target = String::from("another_contract"); - let send_msg = ExecuteMsg::SendNft { + let send_msg = Cw721ExecuteMsg::SendNft { contract: target.clone(), token_id: token_id.clone(), msg: msg.clone(), @@ -530,7 +621,7 @@ fn test_approve_revoke() { let token_id = "grow".to_string(); let token_uri = "https://www.merriam-webster.com/dictionary/grow".to_string(); - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id: token_id.clone(), owner: String::from("demeter"), token_uri: Some(token_uri), @@ -538,14 +629,14 @@ fn test_approve_revoke() { }; let mut env = mock_env(); - let minter = mock_info(MINTER, &[]); + let minter = mock_info(MINTER_ADDR, &[]); contract .execute(deps.as_mut(), env.clone(), minter, mint_msg) .unwrap(); // token owner shows in approval query let res = contract - .approval( + .query_approval_include_expired_nft( deps.as_ref(), env.clone(), token_id.clone(), @@ -558,14 +649,14 @@ fn test_approve_revoke() { res, ApprovalResponse { approval: Approval { - spender: String::from("demeter"), + spender: Addr::unchecked("demeter"), expires: Expiration::Never {} } } ); // Give random transferring power - let approve_msg = ExecuteMsg::Approve { + let approve_msg = Cw721ExecuteMsg::Approve { spender: String::from("random"), token_id: token_id.clone(), expires: None, @@ -585,7 +676,7 @@ fn test_approve_revoke() { // test approval query let res = contract - .approval( + .query_approval_include_expired_nft( deps.as_ref(), env.clone(), token_id.clone(), @@ -598,7 +689,7 @@ fn test_approve_revoke() { res, ApprovalResponse { approval: Approval { - spender: String::from("random"), + spender: Addr::unchecked("random"), expires: Expiration::Never {} } } @@ -606,7 +697,7 @@ fn test_approve_revoke() { // random can now transfer let random = mock_info("random", &[]); - let transfer_msg = ExecuteMsg::TransferNft { + let transfer_msg = Cw721ExecuteMsg::TransferNft { recipient: String::from("person"), token_id: token_id.clone(), }; @@ -618,7 +709,7 @@ fn test_approve_revoke() { let query_msg = QueryMsg::OwnerOf { token_id: token_id.clone(), include_expired: None, - include_invalid: None, + include_expired_nft: None, }; let res: OwnerOfResponse = from_json( contract @@ -635,7 +726,7 @@ fn test_approve_revoke() { ); // Approve, revoke, and check for empty, to test revoke - let approve_msg = ExecuteMsg::Approve { + let approve_msg = Cw721ExecuteMsg::Approve { spender: String::from("random"), token_id: token_id.clone(), expires: None, @@ -650,7 +741,7 @@ fn test_approve_revoke() { ) .unwrap(); - let revoke_msg = ExecuteMsg::Revoke { + let revoke_msg = Cw721ExecuteMsg::Revoke { spender: String::from("random"), token_id: token_id.clone(), }; @@ -720,19 +811,19 @@ fn test_approve_all_revoke_all() { let token_id2 = "grow2".to_string(); let token_uri2 = "https://www.merriam-webster.com/dictionary/grow2".to_string(); - let mint_msg1 = ExecuteMsg::Mint { + let mint_msg1 = Cw721ExecuteMsg::Mint { token_id: token_id1.clone(), owner: String::from("demeter"), token_uri: Some(token_uri1), extension: None, }; - let minter = mock_info(MINTER, &[]); + let minter = mock_info(MINTER_ADDR, &[]); contract .execute(deps.as_mut(), mock_env(), minter.clone(), mint_msg1) .unwrap(); - let mint_msg2 = ExecuteMsg::Mint { + let mint_msg2 = Cw721ExecuteMsg::Mint { token_id: token_id2.clone(), owner: String::from("demeter"), token_uri: Some(token_uri2), @@ -745,12 +836,12 @@ fn test_approve_all_revoke_all() { // paginate the token_ids let tokens = contract - .all_tokens(deps.as_ref(), mock_env(), None, Some(1), false) + .query_all_tokens_include_expired_nft(deps.as_ref(), mock_env(), None, Some(1), false) .unwrap(); assert_eq!(1, tokens.tokens.len()); assert_eq!(vec![token_id1.clone()], tokens.tokens); let tokens = contract - .all_tokens( + .query_all_tokens_include_expired_nft( deps.as_ref(), mock_env(), Some(token_id1.clone()), @@ -762,7 +853,7 @@ fn test_approve_all_revoke_all() { assert_eq!(vec![token_id2.clone()], tokens.tokens); // demeter gives random full (operator) power over her tokens - let approve_all_msg = ExecuteMsg::ApproveAll { + let approve_all_msg = Cw721ExecuteMsg::ApproveAll { operator: String::from("random"), expires: None, }; @@ -780,7 +871,7 @@ fn test_approve_all_revoke_all() { // random can now transfer let random = mock_info("random", &[]); - let transfer_msg = ExecuteMsg::TransferNft { + let transfer_msg = Cw721ExecuteMsg::TransferNft { recipient: String::from("person"), token_id: token_id1, }; @@ -796,7 +887,7 @@ fn test_approve_all_revoke_all() { }; let msg: CosmosMsg = CosmosMsg::Wasm(inner_msg); - let send_msg = ExecuteMsg::SendNft { + let send_msg = Cw721ExecuteMsg::SendNft { contract: String::from("another_contract"), token_id: token_id2, msg: to_json_binary(&msg).unwrap(), @@ -806,7 +897,7 @@ fn test_approve_all_revoke_all() { .unwrap(); // Approve_all, revoke_all, and check for empty, to test revoke_all - let approve_all_msg = ExecuteMsg::ApproveAll { + let approve_all_msg = Cw721ExecuteMsg::ApproveAll { operator: String::from("operator"), expires: None, }; @@ -818,7 +909,8 @@ fn test_approve_all_revoke_all() { // query for operator should return approval let res = contract - .operator( + .base_contract + .query_operator( deps.as_ref(), mock_env(), String::from("person"), @@ -830,14 +922,14 @@ fn test_approve_all_revoke_all() { res, OperatorResponse { approval: Approval { - spender: String::from("operator"), + spender: Addr::unchecked("operator"), expires: Expiration::Never {} } } ); // query for other should throw error - let res = contract.operator( + let res = contract.base_contract.query_operator( deps.as_ref(), mock_env(), String::from("person"), @@ -850,7 +942,8 @@ fn test_approve_all_revoke_all() { } let res = contract - .operators( + .base_contract + .query_operators( deps.as_ref(), mock_env(), String::from("person"), @@ -863,7 +956,7 @@ fn test_approve_all_revoke_all() { res, OperatorsResponse { operators: vec![cw721::Approval { - spender: String::from("operator"), + spender: Addr::unchecked("operator"), expires: Expiration::Never {} }] } @@ -871,7 +964,7 @@ fn test_approve_all_revoke_all() { // second approval let buddy_expires = Expiration::AtHeight(1234567); - let approve_all_msg = ExecuteMsg::ApproveAll { + let approve_all_msg = Cw721ExecuteMsg::ApproveAll { operator: String::from("buddy"), expires: Some(buddy_expires), }; @@ -882,7 +975,8 @@ fn test_approve_all_revoke_all() { // and paginate queries let res = contract - .operators( + .base_contract + .query_operators( deps.as_ref(), mock_env(), String::from("person"), @@ -895,13 +989,14 @@ fn test_approve_all_revoke_all() { res, OperatorsResponse { operators: vec![cw721::Approval { - spender: String::from("buddy"), + spender: Addr::unchecked("buddy"), expires: buddy_expires, }] } ); let res = contract - .operators( + .base_contract + .query_operators( deps.as_ref(), mock_env(), String::from("person"), @@ -914,13 +1009,13 @@ fn test_approve_all_revoke_all() { res, OperatorsResponse { operators: vec![cw721::Approval { - spender: String::from("operator"), + spender: Addr::unchecked("operator"), expires: Expiration::Never {} }] } ); - let revoke_all_msg = ExecuteMsg::RevokeAll { + let revoke_all_msg = Cw721ExecuteMsg::RevokeAll { operator: String::from("operator"), }; contract @@ -928,7 +1023,7 @@ fn test_approve_all_revoke_all() { .unwrap(); // query for operator should return error - let res = contract.operator( + let res = contract.base_contract.query_operator( deps.as_ref(), mock_env(), String::from("person"), @@ -942,7 +1037,8 @@ fn test_approve_all_revoke_all() { // Approvals are removed / cleared without affecting others let res = contract - .operators( + .base_contract + .query_operators( deps.as_ref(), mock_env(), String::from("person"), @@ -955,7 +1051,7 @@ fn test_approve_all_revoke_all() { res, OperatorsResponse { operators: vec![cw721::Approval { - spender: String::from("buddy"), + spender: Addr::unchecked("buddy"), expires: buddy_expires, }] } @@ -965,7 +1061,8 @@ fn test_approve_all_revoke_all() { let mut late_env = mock_env(); late_env.block.height = 1234568; //expired let res = contract - .operators( + .base_contract + .query_operators( deps.as_ref(), late_env.clone(), String::from("person"), @@ -977,7 +1074,7 @@ fn test_approve_all_revoke_all() { assert_eq!(0, res.operators.len()); // query operator should also return error - let res = contract.operator( + let res = contract.base_contract.query_operator( deps.as_ref(), late_env, String::from("person"), @@ -995,7 +1092,7 @@ fn test_approve_all_revoke_all() { fn test_tokens_by_owner() { let mut deps = mock_dependencies(); let contract = setup_contract(deps.as_mut(), 1); - let minter = mock_info(MINTER, &[]); + let minter = mock_info(MINTER_ADDR, &[]); // Mint a couple tokens (from the same owner) let token_id1 = "grow1".to_string(); @@ -1004,7 +1101,7 @@ fn test_tokens_by_owner() { let ceres = String::from("ceres"); let token_id3 = "sing".to_string(); - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id: token_id1.clone(), owner: demeter.clone(), token_uri: None, @@ -1014,7 +1111,7 @@ fn test_tokens_by_owner() { .execute(deps.as_mut(), mock_env(), minter.clone(), mint_msg) .unwrap(); - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id: token_id2.clone(), owner: ceres.clone(), token_uri: None, @@ -1024,7 +1121,7 @@ fn test_tokens_by_owner() { .execute(deps.as_mut(), mock_env(), minter.clone(), mint_msg) .unwrap(); - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id: token_id3.clone(), owner: demeter.clone(), token_uri: None, @@ -1037,16 +1134,16 @@ fn test_tokens_by_owner() { // get all tokens in order: let expected = vec![token_id1.clone(), token_id2.clone(), token_id3.clone()]; let tokens = contract - .all_tokens(deps.as_ref(), mock_env(), None, None, false) + .query_all_tokens_include_expired_nft(deps.as_ref(), mock_env(), None, None, false) .unwrap(); assert_eq!(&expected, &tokens.tokens); // paginate let tokens = contract - .all_tokens(deps.as_ref(), mock_env(), None, Some(2), false) + .query_all_tokens_include_expired_nft(deps.as_ref(), mock_env(), None, Some(2), false) .unwrap(); assert_eq!(&expected[..2], &tokens.tokens[..]); let tokens = contract - .all_tokens( + .query_all_tokens_include_expired_nft( deps.as_ref(), mock_env(), Some(expected[1].clone()), @@ -1061,7 +1158,7 @@ fn test_tokens_by_owner() { let by_demeter = vec![token_id1, token_id3]; // all tokens by owner let tokens = contract - .tokens( + .query_tokens_include_expired_nft( deps.as_ref(), mock_env(), demeter.clone(), @@ -1072,13 +1169,13 @@ fn test_tokens_by_owner() { .unwrap(); assert_eq!(&by_demeter, &tokens.tokens); let tokens = contract - .tokens(deps.as_ref(), mock_env(), ceres, None, None, false) + .query_tokens_include_expired_nft(deps.as_ref(), mock_env(), ceres, None, None, false) .unwrap(); assert_eq!(&by_ceres, &tokens.tokens); // paginate for demeter let tokens = contract - .tokens( + .query_tokens_include_expired_nft( deps.as_ref(), mock_env(), demeter.clone(), @@ -1089,7 +1186,7 @@ fn test_tokens_by_owner() { .unwrap(); assert_eq!(&by_demeter[..1], &tokens.tokens[..]); let tokens = contract - .tokens( + .query_tokens_include_expired_nft( deps.as_ref(), mock_env(), demeter, @@ -1105,13 +1202,13 @@ fn test_tokens_by_owner() { fn test_nft_info() { let mut deps = mock_dependencies(); let contract = setup_contract(deps.as_mut(), 1); - let minter = mock_info(MINTER, &[]); + let minter = mock_info(MINTER_ADDR, &[]); let token_id = "grow1".to_string(); let owner = String::from("ark"); let mut env = mock_env(); - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id: token_id.clone(), owner, token_uri: None, @@ -1123,7 +1220,7 @@ fn test_nft_info() { // assert valid nft is returned contract - .nft_info(deps.as_ref(), env.clone(), token_id.clone(), false) + .query_nft_info_include_expired_nft(deps.as_ref(), env.clone(), token_id.clone(), false) .unwrap(); // assert invalid nft throws error @@ -1131,7 +1228,7 @@ fn test_nft_info() { let expiration = env.block.time.plus_days(1); env.block.time = expiration; let error = contract - .nft_info(deps.as_ref(), env, token_id.clone(), false) + .query_nft_info_include_expired_nft(deps.as_ref(), env, token_id.clone(), false) .unwrap_err(); assert_eq!( error, @@ -1147,13 +1244,13 @@ fn test_nft_info() { fn test_all_nft_info() { let mut deps = mock_dependencies(); let contract = setup_contract(deps.as_mut(), 1); - let minter = mock_info(MINTER, &[]); + let minter = mock_info(MINTER_ADDR, &[]); let token_id = "grow1".to_string(); let owner = String::from("ark"); let mut env = mock_env(); - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id: token_id.clone(), owner, token_uri: None, @@ -1165,7 +1262,13 @@ fn test_all_nft_info() { // assert valid nft is returned contract - .all_nft_info(deps.as_ref(), env.clone(), token_id.clone(), false, false) + .query_all_nft_info_include_expired_nft( + deps.as_ref(), + env.clone(), + token_id.clone(), + false, + false, + ) .unwrap(); // assert invalid nft throws error @@ -1173,7 +1276,7 @@ fn test_all_nft_info() { let expiration = env.block.time.plus_days(1); env.block.time = expiration; let error = contract - .all_nft_info(deps.as_ref(), env, token_id.clone(), false, false) + .query_all_nft_info_include_expired_nft(deps.as_ref(), env, token_id.clone(), false, false) .unwrap_err(); assert_eq!( error, @@ -1189,13 +1292,13 @@ fn test_all_nft_info() { fn test_owner_of() { let mut deps = mock_dependencies(); let contract = setup_contract(deps.as_mut(), 1); - let minter = mock_info(MINTER, &[]); + let minter = mock_info(MINTER_ADDR, &[]); let token_id = "grow1".to_string(); let owner = String::from("ark"); let mut env = mock_env(); - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id: token_id.clone(), owner, token_uri: None, @@ -1207,7 +1310,13 @@ fn test_owner_of() { // assert valid nft is returned contract - .owner_of(deps.as_ref(), env.clone(), token_id.clone(), false, false) + .query_owner_of_include_expired_nft( + deps.as_ref(), + env.clone(), + token_id.clone(), + false, + false, + ) .unwrap(); // assert invalid nft throws error @@ -1215,7 +1324,7 @@ fn test_owner_of() { let expiration = env.block.time.plus_days(1); env.block.time = expiration; let error = contract - .owner_of(deps.as_ref(), env, token_id.clone(), false, false) + .query_owner_of_include_expired_nft(deps.as_ref(), env, token_id.clone(), false, false) .unwrap_err(); assert_eq!( error, @@ -1231,13 +1340,13 @@ fn test_owner_of() { fn test_approval() { let mut deps = mock_dependencies(); let contract = setup_contract(deps.as_mut(), 1); - let minter = mock_info(MINTER, &[]); + let minter = mock_info(MINTER_ADDR, &[]); let token_id = "grow1".to_string(); let owner = String::from("ark"); let mut env = mock_env(); - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id: token_id.clone(), owner: owner.clone(), token_uri: None, @@ -1249,7 +1358,7 @@ fn test_approval() { // assert valid nft is returned contract - .approval( + .query_approval_include_expired_nft( deps.as_ref(), env.clone(), token_id.clone(), @@ -1264,7 +1373,14 @@ fn test_approval() { let expiration = env.block.time.plus_days(1); env.block.time = expiration; let error = contract - .approval(deps.as_ref(), env, token_id.clone(), owner, false, false) + .query_approval_include_expired_nft( + deps.as_ref(), + env, + token_id.clone(), + owner, + false, + false, + ) .unwrap_err(); assert_eq!( error, @@ -1280,13 +1396,13 @@ fn test_approval() { fn test_approvals() { let mut deps = mock_dependencies(); let contract = setup_contract(deps.as_mut(), 1); - let minter = mock_info(MINTER, &[]); + let minter = mock_info(MINTER_ADDR, &[]); let token_id = "grow1".to_string(); let owner = String::from("ark"); let mut env = mock_env(); - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id: token_id.clone(), owner, token_uri: None, @@ -1298,7 +1414,13 @@ fn test_approvals() { // assert valid nft is returned contract - .approvals(deps.as_ref(), env.clone(), token_id.clone(), false, false) + .query_approvals_include_expired_nft( + deps.as_ref(), + env.clone(), + token_id.clone(), + false, + false, + ) .unwrap(); // assert invalid nft throws error @@ -1306,7 +1428,7 @@ fn test_approvals() { let expiration = env.block.time.plus_days(1); env.block.time = expiration; let error = contract - .approvals(deps.as_ref(), env, token_id.clone(), false, false) + .query_approvals_include_expired_nft(deps.as_ref(), env, token_id.clone(), false, false) .unwrap_err(); assert_eq!( error, @@ -1322,13 +1444,13 @@ fn test_approvals() { fn test_tokens() { let mut deps = mock_dependencies(); let contract = setup_contract(deps.as_mut(), 1); - let minter = mock_info(MINTER, &[]); + let minter = mock_info(MINTER_ADDR, &[]); let token_id = "grow1".to_string(); let owner = String::from("ark"); let mut env = mock_env(); - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id: token_id.clone(), owner: owner.clone(), token_uri: None, @@ -1340,20 +1462,34 @@ fn test_tokens() { // assert valid nft is returned contract - .tokens(deps.as_ref(), env.clone(), owner.clone(), None, None, false) + .query_tokens_include_expired_nft( + deps.as_ref(), + env.clone(), + owner.clone(), + None, + None, + false, + ) .unwrap(); // assert invalid nft is not returned let expiration = env.block.time.plus_days(1); env.block.time = expiration; let tokens = contract - .tokens(deps.as_ref(), env.clone(), owner.clone(), None, None, false) + .query_tokens_include_expired_nft( + deps.as_ref(), + env.clone(), + owner.clone(), + None, + None, + false, + ) .unwrap(); assert_eq!(tokens, TokensResponse { tokens: vec![] }); // assert invalid nft is returned let tokens = contract - .tokens(deps.as_ref(), env, owner, None, None, true) + .query_tokens_include_expired_nft(deps.as_ref(), env, owner, None, None, true) .unwrap(); assert_eq!( tokens, @@ -1367,13 +1503,13 @@ fn test_tokens() { fn test_all_tokens() { let mut deps = mock_dependencies(); let contract = setup_contract(deps.as_mut(), 1); - let minter = mock_info(MINTER, &[]); + let minter = mock_info(MINTER_ADDR, &[]); let token_id = "grow1".to_string(); let owner = String::from("ark"); let mut env = mock_env(); - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id: token_id.clone(), owner: owner.clone(), token_uri: None, @@ -1385,20 +1521,20 @@ fn test_all_tokens() { // assert valid nft is returned contract - .all_tokens(deps.as_ref(), env.clone(), None, None, false) + .query_all_tokens_include_expired_nft(deps.as_ref(), env.clone(), None, None, false) .unwrap(); // assert invalid nft is not returned let expiration = env.block.time.plus_days(1); env.block.time = expiration; let tokens = contract - .tokens(deps.as_ref(), env.clone(), owner, None, None, false) + .query_tokens_include_expired_nft(deps.as_ref(), env.clone(), owner, None, None, false) .unwrap(); assert_eq!(tokens, TokensResponse { tokens: vec![] }); // assert invalid nft is returned let tokens = contract - .all_tokens(deps.as_ref(), env, None, None, true) + .query_all_tokens_include_expired_nft(deps.as_ref(), env, None, None, true) .unwrap(); assert_eq!( tokens, diff --git a/contracts/cw721-expiration/src/error.rs b/contracts/cw721-expiration/src/error.rs index bf041df59..211c6ceef 100644 --- a/contracts/cw721-expiration/src/error.rs +++ b/contracts/cw721-expiration/src/error.rs @@ -1,5 +1,5 @@ use cosmwasm_std::Timestamp; -use cw721_base::error::ContractError as Cw721ContractError; +use cw721::error::Cw721ContractError; use thiserror::Error; #[derive(Error, Debug, PartialEq)] diff --git a/contracts/cw721-expiration/src/execute.rs b/contracts/cw721-expiration/src/execute.rs index d698bf3e1..7755f0dd7 100644 --- a/contracts/cw721-expiration/src/execute.rs +++ b/contracts/cw721-expiration/src/execute.rs @@ -1,29 +1,52 @@ -use cosmwasm_std::{ - Addr, Binary, Coin, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, Storage, +use cosmwasm_std::{Binary, CustomMsg, DepsMut, Env, MessageInfo, Response}; +use cw721::{ + execute::Cw721Execute, + msg::{Cw721ExecuteMsg, Cw721InstantiateMsg}, + Expiration, }; -use cw721::{Cw721Execute, Expiration}; -use cw721_base::Cw721Contract; +use serde::de::DeserializeOwned; +use serde::Serialize; use crate::{ - error::ContractError, msg::ExecuteMsg, msg::InstantiateMsg, state::Cw721ExpirationContract, - Extension, + error::ContractError, msg::InstantiateMsg, state::Cw721ExpirationContract, CONTRACT_NAME, + CONTRACT_VERSION, }; -use cw721_base::InstantiateMsg as Cw721InstantiateMsg; -impl<'a> Cw721ExpirationContract<'a> { +impl<'a, TMetadataExtension, TCustomResponseMessage, TMetadataExtensionMsg, TQueryExtensionMsg> + Cw721ExpirationContract< + 'a, + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + > +where + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TCustomResponseMessage: CustomMsg, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, +{ + // -- instantiate -- pub fn instantiate( &self, deps: DepsMut, env: Env, info: MessageInfo, msg: InstantiateMsg, - ) -> Result { + ) -> Result, ContractError> { if msg.expiration_days == 0 { return Err(ContractError::MinExpiration {}); } - self.expiration_days + let contract = Cw721ExpirationContract::< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >::default(); + contract + .expiration_days .save(deps.storage, &msg.expiration_days)?; - Ok(self.base_contract.instantiate( + Ok(contract.base_contract.instantiate( deps, env, info, @@ -33,61 +56,63 @@ impl<'a> Cw721ExpirationContract<'a> { minter: msg.minter, withdraw_address: msg.withdraw_address, }, + CONTRACT_NAME, + CONTRACT_VERSION, )?) } + // -- execute -- pub fn execute( &self, deps: DepsMut, env: Env, info: MessageInfo, - msg: ExecuteMsg, - ) -> Result { + msg: Cw721ExecuteMsg, + ) -> Result, ContractError> { + let contract = Cw721ExpirationContract::< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >::default(); match msg { - ExecuteMsg::Mint { + Cw721ExecuteMsg::Mint { token_id, owner, token_uri, extension, - } => self.mint(deps, env, info, token_id, owner, token_uri, extension), - ExecuteMsg::Approve { + } => { + contract.mint_with_timestamp(deps, env, info, token_id, owner, token_uri, extension) + } + Cw721ExecuteMsg::Approve { spender, token_id, expires, - } => self.approve(deps, env, info, spender, token_id, expires), - ExecuteMsg::Revoke { spender, token_id } => { - self.revoke(deps, env, info, spender, token_id) - } - ExecuteMsg::ApproveAll { operator, expires } => { - self.approve_all(deps, env, info, operator, expires) + } => contract.approve_include_nft_expired(deps, env, info, spender, token_id, expires), + Cw721ExecuteMsg::Revoke { spender, token_id } => { + contract.revoke_include_nft_expired(deps, env, info, spender, token_id) } - ExecuteMsg::RevokeAll { operator } => self.revoke_all(deps, env, info, operator), - ExecuteMsg::TransferNft { + Cw721ExecuteMsg::TransferNft { recipient, token_id, - } => self.transfer_nft(deps, env, info, recipient, token_id), - ExecuteMsg::SendNft { - contract, + } => contract.transfer_nft_include_nft_expired(deps, env, info, recipient, token_id), + Cw721ExecuteMsg::SendNft { + contract: recipient, token_id, msg, - } => self.send_nft(deps, env, info, contract, token_id, msg), - ExecuteMsg::Burn { token_id } => self.burn(deps, env, info, token_id), - ExecuteMsg::UpdateOwnership(action) => Self::update_ownership(deps, env, info, action), - ExecuteMsg::Extension { msg: _ } => Ok(Response::default()), - ExecuteMsg::SetWithdrawAddress { address } => { - self.set_withdraw_address(deps, &info.sender, address) + } => contract.send_nft_include_nft_expired(deps, env, info, recipient, token_id, msg), + Cw721ExecuteMsg::Burn { token_id } => { + contract.burn_nft_include_nft_expired(deps, env, info, token_id) } - ExecuteMsg::RemoveWithdrawAddress {} => { - self.remove_withdraw_address(deps.storage, &info.sender) + _ => { + let response = contract.base_contract.execute(deps, env, info, msg)?; + Ok(response) } - ExecuteMsg::WithdrawFunds { amount } => self.withdraw_funds(deps.storage, &amount), } } -} -impl<'a> Cw721ExpirationContract<'a> { #[allow(clippy::too_many_arguments)] - pub fn mint( + pub fn mint_with_timestamp( &self, deps: DepsMut, env: Env, @@ -95,8 +120,8 @@ impl<'a> Cw721ExpirationContract<'a> { token_id: String, owner: String, token_uri: Option, - extension: Extension, - ) -> Result { + extension: TMetadataExtension, + ) -> Result, ContractError> { let mint_timstamp = env.block.time; self.mint_timestamps .save(deps.storage, &token_id, &mint_timstamp)?; @@ -107,81 +132,7 @@ impl<'a> Cw721ExpirationContract<'a> { Ok(res) } - pub fn update_ownership( - deps: DepsMut, - env: Env, - info: MessageInfo, - action: cw_ownable::Action, - ) -> Result { - Ok( - Cw721Contract::::update_ownership( - deps, env, info, action, - )?, - ) - } - - pub fn set_withdraw_address( - &self, - deps: DepsMut, - sender: &Addr, - address: String, - ) -> Result { - Ok(self - .base_contract - .set_withdraw_address(deps, sender, address)?) - } - - pub fn remove_withdraw_address( - &self, - storage: &mut dyn Storage, - sender: &Addr, - ) -> Result { - Ok(self - .base_contract - .remove_withdraw_address(storage, sender)?) - } - - pub fn withdraw_funds( - &self, - storage: &mut dyn Storage, - amount: &Coin, - ) -> Result { - Ok(self.base_contract.withdraw_funds(storage, amount)?) - } -} - -// execute -impl<'a> Cw721ExpirationContract<'a> { - fn transfer_nft( - &self, - deps: DepsMut, - env: Env, - info: MessageInfo, - recipient: String, - token_id: String, - ) -> Result, ContractError> { - self.assert_valid_nft(deps.as_ref(), &env, &token_id)?; - Ok(self - .base_contract - .transfer_nft(deps, env, info, recipient, token_id)?) - } - - fn send_nft( - &self, - deps: DepsMut, - env: Env, - info: MessageInfo, - contract: String, - token_id: String, - msg: Binary, - ) -> Result, ContractError> { - self.assert_valid_nft(deps.as_ref(), &env, &token_id)?; - Ok(self - .base_contract - .send_nft(deps, env, info, contract, token_id, msg)?) - } - - fn approve( + pub fn approve_include_nft_expired( &self, deps: DepsMut, env: Env, @@ -189,94 +140,64 @@ impl<'a> Cw721ExpirationContract<'a> { spender: String, token_id: String, expires: Option, - ) -> Result, ContractError> { - self.assert_valid_nft(deps.as_ref(), &env, &token_id)?; + ) -> Result, ContractError> { + self.assert_nft_expired(deps.as_ref(), &env, token_id.as_str())?; Ok(self .base_contract .approve(deps, env, info, spender, token_id, expires)?) } - fn revoke( + pub fn revoke_include_nft_expired( &self, deps: DepsMut, env: Env, info: MessageInfo, spender: String, token_id: String, - ) -> Result, ContractError> { - self.assert_valid_nft(deps.as_ref(), &env, &token_id)?; + ) -> Result, ContractError> { + self.assert_nft_expired(deps.as_ref(), &env, token_id.as_str())?; Ok(self .base_contract .revoke(deps, env, info, spender, token_id)?) } - fn approve_all( + pub fn transfer_nft_include_nft_expired( &self, deps: DepsMut, env: Env, info: MessageInfo, - operator: String, - expires: Option, - ) -> Result { + recipient: String, + token_id: String, + ) -> Result, ContractError> { + self.assert_nft_expired(deps.as_ref(), &env, token_id.as_str())?; Ok(self .base_contract - .approve_all(deps, env, info, operator, expires)?) + .transfer_nft(deps, env, info, recipient, token_id)?) } - fn revoke_all( + pub fn send_nft_include_nft_expired( &self, deps: DepsMut, env: Env, info: MessageInfo, - operator: String, - ) -> Result { - Ok(self.base_contract.revoke_all(deps, env, info, operator)?) + contract: String, + token_id: String, + msg: Binary, + ) -> Result, ContractError> { + self.assert_nft_expired(deps.as_ref(), &env, token_id.as_str())?; + Ok(self + .base_contract + .send_nft(deps, env, info, contract, token_id, msg)?) } - fn burn( + pub fn burn_nft_include_nft_expired( &self, deps: DepsMut, env: Env, info: MessageInfo, token_id: String, - ) -> Result { - self.assert_valid_nft(deps.as_ref(), &env, &token_id)?; - Ok(self.base_contract.burn(deps, env, info, token_id)?) - } -} - -// helpers -impl<'a> Cw721ExpirationContract<'a> { - /// throws contract error if nft is expired - pub fn is_valid_nft(&self, deps: Deps, env: &Env, token_id: &str) -> StdResult { - // any non-expired token approval can send - let mint_date = self.mint_timestamps.load(deps.storage, token_id)?; - let expiration_days = self.expiration_days.load(deps.storage)?; - let expiration = mint_date.plus_days(expiration_days.into()); - if env.block.time >= expiration { - return Ok(false); - } - Ok(true) - } - - /// throws contract error if nft is expired - pub fn assert_valid_nft( - &self, - deps: Deps, - env: &Env, - token_id: &str, - ) -> Result<(), ContractError> { - // any non-expired token approval can send - let mint_date = self.mint_timestamps.load(deps.storage, token_id)?; - let expiration_days = self.expiration_days.load(deps.storage)?; - let expiration = mint_date.plus_days(expiration_days.into()); - if env.block.time >= expiration { - return Err(ContractError::NftExpired { - token_id: token_id.to_string(), - mint_date, - expiration, - }); - } - Ok(()) + ) -> Result, ContractError> { + self.assert_nft_expired(deps.as_ref(), &env, token_id.as_str())?; + Ok(self.base_contract.burn_nft(deps, env, info, token_id)?) } } diff --git a/contracts/cw721-expiration/src/lib.rs b/contracts/cw721-expiration/src/lib.rs index c841de969..c7c35261a 100644 --- a/contracts/cw721-expiration/src/lib.rs +++ b/contracts/cw721-expiration/src/lib.rs @@ -8,20 +8,20 @@ pub mod state; mod contract_tests; use cosmwasm_std::Empty; +use cw721::state::DefaultOptionMetadataExtension; // Version info for migration const CONTRACT_NAME: &str = "crates.io:cw721-expiration"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); -pub type MinterResponse = cw721::MinterResponse; -pub type Extension = Option; +pub type MinterResponse = cw721::msg::MinterResponse; -pub type TokenInfo = cw721_base::state::TokenInfo; +pub type NftInfo = cw721::state::NftInfo; pub mod entry { use crate::{ error::ContractError, - msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, + msg::{InstantiateMsg, QueryMsg}, state::Cw721ExpirationContract, }; @@ -30,17 +30,20 @@ pub mod entry { #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response}; + use cw721::{msg::Cw721ExecuteMsg, state::DefaultOptionMetadataExtension}; // This makes a conscious choice on the various generics used by the contract #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( - mut deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, msg: InstantiateMsg, ) -> Result { - cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - Cw721ExpirationContract::default().instantiate(deps.branch(), env, info, msg) + let contract = + Cw721ExpirationContract::::default( + ); + contract.instantiate(deps, env, info, msg) } #[entry_point] @@ -48,19 +51,30 @@ pub mod entry { deps: DepsMut, env: Env, info: MessageInfo, - msg: ExecuteMsg, + msg: Cw721ExecuteMsg, ) -> Result { - Cw721ExpirationContract::default().execute(deps, env, info, msg) + let contract = + Cw721ExpirationContract::::default( + ); + contract.execute(deps, env, info, msg) } #[entry_point] - pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result { - Cw721ExpirationContract::default().query(deps, env, msg) + pub fn query( + deps: Deps, + env: Env, + msg: QueryMsg, + ) -> Result { + let contract = + Cw721ExpirationContract::::default( + ); + contract.query(deps, env, msg) } #[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate(_deps: DepsMut, _env: Env, _msg: Empty) -> Result { - Ok(Response::default()) + // TODO: allow migration e.g. from cw721-base + panic!("This contract does not support migrations") } } @@ -84,9 +98,9 @@ mod tests { mock_info("mrt", &[]), InstantiateMsg { expiration_days: 0, - name: "".into(), - symbol: "".into(), - minter: Some("mrt".into()), + name: "collection_name".into(), + symbol: "collection_symbol".into(), + minter: Some("minter".into()), withdraw_address: None, }, ) @@ -102,7 +116,7 @@ mod tests { expiration_days: 1, name: "".into(), symbol: "".into(), - minter: Some("mrt".into()), + minter: Some("minter".into()), withdraw_address: None, }, ) @@ -119,7 +133,7 @@ mod tests { assert_eq!( 1, - Cw721ExpirationContract::default() + Cw721ExpirationContract::::default() .expiration_days .load(deps.as_ref().storage) .unwrap() diff --git a/contracts/cw721-expiration/src/msg.rs b/contracts/cw721-expiration/src/msg.rs index 5aa019922..017b90699 100644 --- a/contracts/cw721-expiration/src/msg.rs +++ b/contracts/cw721-expiration/src/msg.rs @@ -1,8 +1,11 @@ -use crate::{Extension, MinterResponse}; +use crate::DefaultOptionMetadataExtension; use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::Empty; -use cw_ownable::cw_ownable_query; -pub type ExecuteMsg = cw721_base::ExecuteMsg; +use cosmwasm_std::Addr; +use cw721::state::CollectionInfo; +use cw_ownable::Ownership; + +// expose to all others using contract, so others dont need to import cw721 +pub use cw721::msg::{Cw721ExecuteMsg as ExecuteMsg, Cw721MigrateMsg as MigrateMsg, *}; #[cw_serde] pub struct InstantiateMsg { @@ -23,100 +26,114 @@ pub struct InstantiateMsg { pub withdraw_address: Option, } -#[cw_ownable_query] #[cw_serde] #[derive(QueryResponses)] -pub enum QueryMsg { +pub enum QueryMsg { + // -------- below adds `include_expired_nft` prop to cw721/src/msg.rs -------- /// Return the owner of the given token, error if token does not exist - #[returns(cw721::OwnerOfResponse)] + #[returns(cw721::msg::OwnerOfResponse)] OwnerOf { token_id: String, /// unset or false will filter out expired approvals, you must set to true to see them include_expired: Option, /// unset or false will filter out expired nfts, you must set to true to see them - include_invalid: Option, + include_expired_nft: Option, }, /// Return operator that can access all of the owner's tokens. - #[returns(cw721::ApprovalResponse)] + #[returns(cw721::msg::ApprovalResponse)] Approval { token_id: String, spender: String, include_expired: Option, /// unset or false will filter out expired nfts, you must set to true to see them - include_invalid: Option, + include_expired_nft: Option, }, /// Return approvals that a token has - #[returns(cw721::ApprovalsResponse)] + #[returns(cw721::msg::ApprovalsResponse)] Approvals { token_id: String, include_expired: Option, /// unset or false will filter out expired nfts, you must set to true to see them - include_invalid: Option, - }, - /// Return approval of a given operator for all tokens of an owner, error if not set - #[returns(cw721::OperatorResponse)] - Operator { - owner: String, - operator: String, - include_expired: Option, - }, - /// List all operators that can access all of the owner's tokens - #[returns(cw721::OperatorsResponse)] - AllOperators { - owner: String, - /// unset or false will filter out expired items, you must set to true to see them - include_expired: Option, - start_after: Option, - limit: Option, + include_expired_nft: Option, }, - /// Total number of tokens issued, including all expired NFTs - #[returns(cw721::NumTokensResponse)] - NumTokens {}, - /// With MetaData Extension. - /// Returns top-level metadata about the contract - #[returns(cw721::ContractInfoResponse)] - ContractInfo {}, /// With MetaData Extension. /// Returns metadata about one particular token, based on *ERC721 Metadata JSON Schema* /// but directly from the contract - #[returns(cw721::NftInfoResponse)] + #[returns(cw721::msg::NftInfoResponse)] NftInfo { token_id: String, /// unset or false will filter out expired nfts, you must set to true to see them - include_invalid: Option, + include_expired_nft: Option, }, + /// With MetaData Extension. /// Returns the result of both `NftInfo` and `OwnerOf` as one query as an optimization /// for clients - #[returns(cw721::AllNftInfoResponse)] + #[returns(cw721::msg::AllNftInfoResponse)] AllNftInfo { token_id: String, /// unset or false will filter out expired approvals, you must set to true to see them include_expired: Option, /// unset or false will filter out expired nfts, you must set to true to see them - include_invalid: Option, + include_expired_nft: Option, }, /// With Enumerable extension. /// Returns all tokens owned by the given address, [] if unset. - #[returns(cw721::TokensResponse)] + #[returns(cw721::msg::TokensResponse)] Tokens { owner: String, start_after: Option, limit: Option, /// unset or false will filter out expired nfts, you must set to true to see them - include_invalid: Option, + include_expired_nft: Option, }, + /// With Enumerable extension. /// Requires pagination. Lists all token_ids controlled by the contract. - #[returns(cw721::TokensResponse)] + #[returns(cw721::msg::TokensResponse)] AllTokens { start_after: Option, limit: Option, /// unset or false will filter out expired nfts, you must set to true to see them - include_invalid: Option, + include_expired_nft: Option, + }, + + // -------- below is from cw721/src/msg.rs -------- + /// Return approval of a given operator for all tokens of an owner, error if not set + #[returns(cw721::msg::OperatorResponse)] + Operator { + owner: String, + operator: String, + include_expired: Option, + }, + /// List all operators that can access all of the owner's tokens + #[returns(cw721::msg::OperatorsResponse)] + AllOperators { + owner: String, + /// unset or false will filter out expired items, you must set to true to see them + include_expired: Option, + start_after: Option, + limit: Option, }, + /// Total number of tokens issued, including all expired NFTs + #[returns(cw721::msg::NumTokensResponse)] + NumTokens {}, + + #[returns(cw721::state::CollectionInfo)] + ContractInfo {}, + + /// With MetaData Extension. + /// Returns top-level metadata about the contract + #[returns(CollectionInfo)] + GetCollectionInfo {}, + + #[returns(Ownership)] + Ownership {}, + + #[returns(Ownership)] + GetMinterOwnership {}, /// Return the minter #[returns(MinterResponse)] @@ -124,5 +141,11 @@ pub enum QueryMsg { /// Extension query #[returns(())] - Extension { msg: Empty }, + Extension { + msg: TQueryExtensionMsg, + phantom: Option, // dummy field to infer type + }, + + #[returns(Option)] + GetWithdrawAddress {}, } diff --git a/contracts/cw721-expiration/src/query.rs b/contracts/cw721-expiration/src/query.rs index a7d7626dc..c34e560a8 100644 --- a/contracts/cw721-expiration/src/query.rs +++ b/contracts/cw721-expiration/src/query.rs @@ -1,65 +1,153 @@ -use cosmwasm_std::{to_json_binary, Addr, Binary, Deps, Env, StdResult}; -use cw721::{ - AllNftInfoResponse, ApprovalResponse, ApprovalsResponse, ContractInfoResponse, Cw721Query, - MinterResponse, NftInfoResponse, NumTokensResponse, OperatorResponse, OperatorsResponse, - OwnerOfResponse, TokensResponse, +use cosmwasm_std::{to_json_binary, Binary, CustomMsg, Deps, Env, StdResult}; +use cw721::msg::{ + AllNftInfoResponse, ApprovalResponse, ApprovalsResponse, NftInfoResponse, OwnerOfResponse, + TokensResponse, }; +use cw721::query::Cw721Query; +use serde::de::DeserializeOwned; +use serde::Serialize; -use crate::{error::ContractError, msg::QueryMsg, state::Cw721ExpirationContract, Extension}; +use crate::{error::ContractError, msg::QueryMsg, state::Cw721ExpirationContract}; -impl<'a> Cw721ExpirationContract<'a> { - pub fn query(&self, deps: Deps, env: Env, msg: QueryMsg) -> Result { +impl<'a, TMetadataExtension, TCustomResponseMessage, TMetadataExtensionMsg, TQueryExtensionMsg> + Cw721ExpirationContract< + 'a, + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + > +where + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TCustomResponseMessage: CustomMsg, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, +{ + pub fn query( + &self, + deps: Deps, + env: Env, + msg: QueryMsg, + ) -> Result { + let contract = Cw721ExpirationContract::< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >::default(); match msg { - QueryMsg::Minter {} => Ok(to_json_binary(&self.minter(deps)?)?), - QueryMsg::ContractInfo {} => Ok(to_json_binary(&self.contract_info(deps)?)?), - QueryMsg::NftInfo { + // -------- msgs with `include_expired_nft` prop -------- + QueryMsg::OwnerOf { token_id, - include_invalid, - } => Ok(to_json_binary(&self.nft_info( - deps, - env, + include_expired: include_expired_approval, + include_expired_nft, + } => Ok(to_json_binary( + &contract.query_owner_of_include_expired_nft( + deps, + env, + token_id, + include_expired_approval.unwrap_or(false), + include_expired_nft.unwrap_or(false), + )?, + )?), + QueryMsg::Approval { token_id, - include_invalid.unwrap_or(false), - )?)?), - QueryMsg::OwnerOf { + spender, + include_expired, + include_expired_nft: include_invalid, + } => Ok(to_json_binary( + &contract.query_approval_include_expired_nft( + deps, + env, + token_id, + spender, + include_expired.unwrap_or(false), + include_invalid.unwrap_or(false), + )?, + )?), + QueryMsg::Approvals { token_id, include_expired, - include_invalid, - } => Ok(to_json_binary(&self.owner_of( - deps, - env, + include_expired_nft: include_invalid, + } => Ok(to_json_binary( + &contract.query_approvals_include_expired_nft( + deps, + env, + token_id, + include_expired.unwrap_or(false), + include_invalid.unwrap_or(false), + )?, + )?), + QueryMsg::NftInfo { token_id, - include_expired.unwrap_or(false), - include_invalid.unwrap_or(false), - )?)?), + include_expired_nft: include_expired, + } => Ok(to_json_binary( + &contract.query_nft_info_include_expired_nft( + deps, + env, + token_id, + include_expired.unwrap_or(false), + )?, + )?), QueryMsg::AllNftInfo { token_id, - include_expired, - include_invalid, - } => Ok(to_json_binary(&self.all_nft_info( - deps, - env, - token_id, - include_expired.unwrap_or(false), - include_invalid.unwrap_or(false), - )?)?), + include_expired: include_expired_approval, + include_expired_nft, + } => Ok(to_json_binary( + &contract.query_all_nft_info_include_expired_nft( + deps, + env, + token_id, + include_expired_approval.unwrap_or(false), + include_expired_nft.unwrap_or(false), + )?, + )?), + QueryMsg::Tokens { + owner, + start_after, + limit, + include_expired_nft: include_invalid, + } => Ok(to_json_binary( + &contract.query_tokens_include_expired_nft( + deps, + env, + owner, + start_after, + limit, + include_invalid.unwrap_or(false), + )?, + )?), + QueryMsg::AllTokens { + start_after, + limit, + include_expired_nft: include_invalid, + } => Ok(to_json_binary( + &contract.query_all_tokens_include_expired_nft( + deps, + env, + start_after, + limit, + include_invalid.unwrap_or(false), + )?, + )?), + // -------- below is from cw721/src/msg.rs -------- QueryMsg::Operator { owner, operator, - include_expired, - } => Ok(to_json_binary(&self.operator( + include_expired: include_expired_approval, + } => Ok(to_json_binary(&contract.base_contract.query_operator( deps, env, owner, operator, - include_expired.unwrap_or(false), + include_expired_approval.unwrap_or(false), )?)?), QueryMsg::AllOperators { owner, include_expired, start_after, limit, - } => Ok(to_json_binary(&self.operators( + } => Ok(to_json_binary(&contract.base_contract.query_operators( deps, env, owner, @@ -67,229 +155,201 @@ impl<'a> Cw721ExpirationContract<'a> { start_after, limit, )?)?), - QueryMsg::NumTokens {} => Ok(to_json_binary(&self.num_tokens(deps)?)?), - QueryMsg::Tokens { - owner, - start_after, - limit, - include_invalid, - } => Ok(to_json_binary(&self.tokens( - deps, - env, - owner, - start_after, - limit, - include_invalid.unwrap_or(false), - )?)?), - QueryMsg::AllTokens { - start_after, - limit, - include_invalid, - } => Ok(to_json_binary(&self.all_tokens( - deps, - env, - start_after, - limit, - include_invalid.unwrap_or(false), - )?)?), - QueryMsg::Approval { - token_id, - spender, - include_expired, - include_invalid, - } => Ok(to_json_binary(&self.approval( - deps, - env, - token_id, - spender, - include_expired.unwrap_or(false), - include_invalid.unwrap_or(false), - )?)?), - QueryMsg::Approvals { - token_id, - include_expired, - include_invalid, - } => Ok(to_json_binary(&self.approvals( - deps, - env, - token_id, - include_expired.unwrap_or(false), - include_invalid.unwrap_or(false), - )?)?), - QueryMsg::Ownership {} => Ok(to_json_binary(&Self::ownership(deps)?)?), - QueryMsg::Extension { msg: _ } => Ok(Binary::default()), + QueryMsg::NumTokens {} => Ok(to_json_binary( + &contract.base_contract.query_num_tokens(deps, env)?, + )?), + QueryMsg::ContractInfo {} => Ok(to_json_binary( + &contract.base_contract.query_collection_info(deps, env)?, + )?), + QueryMsg::GetCollectionInfo {} => Ok(to_json_binary( + &contract.base_contract.query_collection_info(deps, env)?, + )?), + QueryMsg::Ownership {} => Ok(to_json_binary( + &contract + .base_contract + .query_minter_ownership(deps.storage)?, + )?), + QueryMsg::GetMinterOwnership {} => Ok(to_json_binary( + &contract + .base_contract + .query_minter_ownership(deps.storage)?, + )?), + QueryMsg::Minter {} => Ok(to_json_binary( + &contract.base_contract.query_minter(deps.storage)?, + )?), + QueryMsg::Extension { msg, .. } => Ok(to_json_binary( + &contract.base_contract.query_extension(deps, env, msg)?, + )?), + QueryMsg::GetWithdrawAddress {} => Ok(to_json_binary( + &contract.base_contract.query_withdraw_address(deps)?, + )?), } } - pub fn minter(&self, deps: Deps) -> StdResult { - self.base_contract.minter(deps) - } - - pub fn ownership(deps: Deps) -> StdResult> { - cw_ownable::get_ownership(deps.storage) - } -} - -// queries -impl<'a> Cw721ExpirationContract<'a> { - pub fn contract_info(&self, deps: Deps) -> StdResult { - self.base_contract.contract_info(deps) - } - - pub fn num_tokens(&self, deps: Deps) -> StdResult { - self.base_contract.num_tokens(deps) - } - - pub fn nft_info( + pub fn query_nft_info_include_expired_nft( &self, deps: Deps, env: Env, token_id: String, - include_invalid: bool, - ) -> Result, ContractError> { - if !include_invalid { - self.assert_valid_nft(deps, &env, token_id.as_str())?; + include_expired_nft: bool, + ) -> Result, ContractError> { + if !include_expired_nft { + self.assert_nft_expired(deps, &env, token_id.as_str())?; } - Ok(self.base_contract.nft_info(deps, token_id)?) + Ok(self.base_contract.query_nft_info(deps, env, token_id)?) } - pub fn owner_of( + pub fn query_owner_of_include_expired_nft( &self, deps: Deps, env: Env, token_id: String, - include_expired: bool, - include_invalid: bool, + include_expired_approval: bool, + include_expired_nft: bool, ) -> Result { - if !include_invalid { - self.assert_valid_nft(deps, &env, token_id.as_str())?; + if !include_expired_nft { + self.assert_nft_expired(deps, &env, token_id.as_str())?; } Ok(self .base_contract - .owner_of(deps, env, token_id, include_expired)?) + .query_owner_of(deps, env, token_id, include_expired_approval)?) } - /// operator returns the approval status of an operator for a given owner if exists - pub fn operator( - &self, - deps: Deps, - env: Env, - owner: String, - operator: String, - include_expired: bool, - ) -> StdResult { - self.base_contract - .operator(deps, env, owner, operator, include_expired) - } - - /// operators returns all operators owner given access to - pub fn operators( - &self, - deps: Deps, - env: Env, - owner: String, - include_expired: bool, - start_after: Option, - limit: Option, - ) -> StdResult { - self.base_contract - .operators(deps, env, owner, include_expired, start_after, limit) - } - - pub fn approval( + pub fn query_approval_include_expired_nft( &self, deps: Deps, env: Env, token_id: String, spender: String, - include_expired: bool, - include_invalid: bool, + include_expired_approval: bool, + include_expired_nft: bool, ) -> Result { - if !include_invalid { - self.assert_valid_nft(deps, &env, token_id.as_str())?; + if !include_expired_nft { + self.assert_nft_expired(deps, &env, token_id.as_str())?; } - Ok(self - .base_contract - .approval(deps, env, token_id, spender, include_expired)?) + Ok(self.base_contract.query_approval( + deps, + env, + token_id, + spender, + include_expired_approval, + )?) } /// approvals returns all approvals owner given access to - pub fn approvals( + pub fn query_approvals_include_expired_nft( &self, deps: Deps, env: Env, token_id: String, - include_expired: bool, - include_invalid: bool, + include_expired_approval: bool, + include_expired_nft: bool, ) -> Result { - if !include_invalid { - self.assert_valid_nft(deps, &env, token_id.as_str())?; + if !include_expired_nft { + self.assert_nft_expired(deps, &env, token_id.as_str())?; } Ok(self .base_contract - .approvals(deps, env, token_id, include_expired)?) + .query_approvals(deps, env, token_id, include_expired_approval)?) } - pub fn tokens( + pub fn query_tokens_include_expired_nft( &self, deps: Deps, env: Env, owner: String, start_after: Option, limit: Option, - include_invalid: bool, + include_expired_nft: bool, ) -> StdResult { - let tokens = self.base_contract.tokens(deps, owner, start_after, limit)?; - if include_invalid { + let tokens = + self.base_contract + .query_tokens(deps, env.clone(), owner, start_after, limit)?; + if include_expired_nft { return Ok(tokens); } let filtered: Vec<_> = tokens .tokens .iter() .filter(|token_id| { - self.is_valid_nft(deps, &env, token_id).unwrap_or(false) // Convert Option to bool + self.is_nft_expired(deps, &env, token_id).unwrap_or(false) // Convert Option to bool }) .map(|token_id| token_id.to_string()) .collect(); Ok(TokensResponse { tokens: filtered }) } - pub fn all_tokens( + pub fn query_all_tokens_include_expired_nft( &self, deps: Deps, env: Env, start_after: Option, limit: Option, - include_invalid: bool, + include_expired_nft: bool, ) -> Result { - let tokens = self.base_contract.all_tokens(deps, start_after, limit)?; - if include_invalid { + let tokens = self + .base_contract + .query_all_tokens(deps, env.clone(), start_after, limit)?; + if include_expired_nft { return Ok(tokens); } let filtered: Vec<_> = tokens .tokens .iter() .filter(|token_id| { - self.is_valid_nft(deps, &env, token_id).unwrap_or(false) // Convert Option to bool + self.is_nft_expired(deps, &env, token_id).unwrap_or(false) // Convert Option to bool }) .map(|token_id| token_id.to_string()) .collect(); Ok(TokensResponse { tokens: filtered }) } - pub fn all_nft_info( + pub fn query_all_nft_info_include_expired_nft( &self, deps: Deps, env: Env, token_id: String, - include_expired: bool, - include_invalid: bool, - ) -> Result, ContractError> { - if !include_invalid { - self.assert_valid_nft(deps, &env, token_id.as_str())?; + include_expired_approval: bool, + include_expired_nft: bool, + ) -> Result, ContractError> { + if !include_expired_nft { + self.assert_nft_expired(deps, &env, token_id.as_str())?; } Ok(self .base_contract - .all_nft_info(deps, env, token_id, include_expired)?) + .query_all_nft_info(deps, env, token_id, include_expired_approval)?) + } + + // --- helpers --- + pub fn is_nft_expired(&self, deps: Deps, env: &Env, token_id: &str) -> StdResult { + // any non-expired token approval can send + let mint_date = self.mint_timestamps.load(deps.storage, token_id)?; + let expiration_days = self.expiration_days.load(deps.storage)?; + let expiration = mint_date.plus_days(expiration_days.into()); + if env.block.time >= expiration { + return Ok(false); + } + Ok(true) + } + + /// throws contract error if nft is expired + pub fn assert_nft_expired( + &self, + deps: Deps, + env: &Env, + token_id: &str, + ) -> Result<(), ContractError> { + // any non-expired token approval can send + let mint_date = self.mint_timestamps.load(deps.storage, token_id)?; + let expiration_days = self.expiration_days.load(deps.storage)?; + let expiration = mint_date.plus_days(expiration_days.into()); + if env.block.time >= expiration { + return Err(ContractError::NftExpired { + token_id: token_id.to_string(), + mint_date, + expiration, + }); + } + Ok(()) } } diff --git a/contracts/cw721-expiration/src/state.rs b/contracts/cw721-expiration/src/state.rs index c7ccb3d0d..ebde17ad3 100644 --- a/contracts/cw721-expiration/src/state.rs +++ b/contracts/cw721-expiration/src/state.rs @@ -1,20 +1,57 @@ -use cosmwasm_std::{Empty, Timestamp}; -use cw_storage_plus::{Item, Map}; +use cosmwasm_std::{CustomMsg, Timestamp}; + +// expose to all others using contract, so others dont need to import cw721 +pub use cw721::state::*; -use crate::Extension; +use cw721_base::Cw721Contract; +use cw_storage_plus::{Item, Map}; +use serde::de::DeserializeOwned; +use serde::Serialize; -pub struct Cw721ExpirationContract<'a> { +pub struct Cw721ExpirationContract< + 'a, + // Metadata defined in NftInfo (used for mint). + TMetadataExtension, + // Defines for `CosmosMsg::Custom` in response. Barely used, so `Empty` can be used. + TCustomResponseMessage, + // Message passed for updating metadata. + TMetadataExtensionMsg, + // Extension query message. + TQueryExtensionMsg, +> where + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, +{ pub expiration_days: Item<'a, u16>, // max 65535 days pub mint_timestamps: Map<'a, &'a str, Timestamp>, - pub base_contract: cw721_base::Cw721Contract<'a, Extension, Empty, Empty, Empty>, + pub base_contract: Cw721Contract< + 'a, + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >, } -impl Default for Cw721ExpirationContract<'static> { +impl Default + for Cw721ExpirationContract< + 'static, + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + > +where + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, +{ fn default() -> Self { Self { expiration_days: Item::new("expiration_days"), mint_timestamps: Map::new("mint_timestamps"), - base_contract: cw721_base::Cw721Contract::default(), + base_contract: Cw721Contract::default(), } } } diff --git a/contracts/cw721-fixed-price/Cargo.toml b/contracts/cw721-fixed-price/Cargo.toml index f47aeddb7..82f018e28 100644 --- a/contracts/cw721-fixed-price/Cargo.toml +++ b/contracts/cw721-fixed-price/Cargo.toml @@ -22,6 +22,7 @@ cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw2 = { workspace = true } cw20 = { workspace = true } +cw721 = { workspace = true } cw721-base = { workspace = true, features = ["library"] } cw-storage-plus = { workspace = true } cw-utils = { workspace = true } diff --git a/contracts/cw721-fixed-price/README.md b/contracts/cw721-fixed-price/README.md index af3256fc2..58baabc32 100644 --- a/contracts/cw721-fixed-price/README.md +++ b/contracts/cw721-fixed-price/README.md @@ -25,7 +25,7 @@ To generate an optimized build run: docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/rust-optimizer:0.12.3 + cosmwasm/rust-optimizer:0.16.0 ``` ### Testing diff --git a/contracts/cw721-fixed-price/schema/cw721-fixed-price.json b/contracts/cw721-fixed-price/schema/cw721-fixed-price.json index 694ec7f85..296c736a8 100644 --- a/contracts/cw721-fixed-price/schema/cw721-fixed-price.json +++ b/contracts/cw721-fixed-price/schema/cw721-fixed-price.json @@ -1,6 +1,6 @@ { "contract_name": "cw721-fixed-price", - "contract_version": "0.18.0", + "contract_version": "0.19.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -23,7 +23,7 @@ "extension": { "anyOf": [ { - "$ref": "#/definitions/Empty" + "$ref": "#/definitions/Metadata" }, { "type": "null" @@ -68,9 +68,90 @@ "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", "type": "string" }, - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" + "Metadata": { + "type": "object", + "properties": { + "animation_url": { + "type": [ + "string", + "null" + ] + }, + "attributes": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Trait" + } + }, + "background_color": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "external_url": { + "type": [ + "string", + "null" + ] + }, + "image": { + "type": [ + "string", + "null" + ] + }, + "image_data": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "youtube_url": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "Trait": { + "type": "object", + "required": [ + "trait_type", + "value" + ], + "properties": { + "display_type": { + "type": [ + "string", + "null" + ] + }, + "trait_type": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", @@ -180,7 +261,7 @@ "extension": { "anyOf": [ { - "$ref": "#/definitions/Empty" + "$ref": "#/definitions/Metadata" }, { "type": "null" @@ -219,9 +300,90 @@ "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", "type": "string" }, - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" + "Metadata": { + "type": "object", + "properties": { + "animation_url": { + "type": [ + "string", + "null" + ] + }, + "attributes": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Trait" + } + }, + "background_color": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "external_url": { + "type": [ + "string", + "null" + ] + }, + "image": { + "type": [ + "string", + "null" + ] + }, + "image_data": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "youtube_url": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "Trait": { + "type": "object", + "required": [ + "trait_type", + "value" + ], + "properties": { + "display_type": { + "type": [ + "string", + "null" + ] + }, + "trait_type": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", diff --git a/contracts/cw721-fixed-price/src/contract.rs b/contracts/cw721-fixed-price/src/contract.rs index 1b9d7a4f4..eea581249 100644 --- a/contracts/cw721-fixed-price/src/contract.rs +++ b/contracts/cw721-fixed-price/src/contract.rs @@ -11,10 +11,9 @@ use cosmwasm_std::{ }; use cw2::set_contract_version; use cw20::Cw20ReceiveMsg; -use cw721_base::{ - helpers::Cw721Contract, msg::ExecuteMsg as Cw721ExecuteMsg, - msg::InstantiateMsg as Cw721InstantiateMsg, -}; +use cw721::helpers::Cw721Contract; +use cw721::msg::{Cw721ExecuteMsg, Cw721InstantiateMsg}; +use cw721::state::DefaultOptionMetadataExtension; use cw_utils::parse_reply_instantiate_data; // version info for migration info @@ -160,7 +159,7 @@ pub fn execute_receive( return Err(ContractError::WrongPaymentAmount {}); } - let mint_msg = Cw721ExecuteMsg::<_, Empty>::Mint { + let mint_msg = Cw721ExecuteMsg::::Mint { token_id: config.unused_token_id.to_string(), owner: sender, token_uri: config.token_uri.clone().into(), @@ -169,8 +168,13 @@ pub fn execute_receive( match config.cw721_address.clone() { Some(cw721) => { - let callback = - Cw721Contract::(cw721, PhantomData, PhantomData).call(mint_msg)?; + let callback = Cw721Contract::( + cw721, + PhantomData, + PhantomData, + PhantomData, + ) + .call(mint_msg)?; config.unused_token_id += 1; CONFIG.save(deps.storage, &config)?; @@ -185,7 +189,7 @@ mod tests { use super::*; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MOCK_CONTRACT_ADDR}; use cosmwasm_std::{from_json, to_json_binary, CosmosMsg, SubMsgResponse, SubMsgResult}; - use cw721_base::Extension; + use cw721::state::DefaultOptionMetadataExtension; use prost::Message; const NFT_CONTRACT_ADDR: &str = "nftcontract"; @@ -378,7 +382,7 @@ mod tests { let info = mock_info(MOCK_CONTRACT_ADDR, &[]); let res = execute(deps.as_mut(), mock_env(), info, msg).unwrap(); - let mint_msg = Cw721ExecuteMsg::::Mint { + let mint_msg = Cw721ExecuteMsg::::Mint { token_id: String::from("0"), owner: String::from("minter"), token_uri: Some(String::from("https://ipfs.io/ipfs/Q")), diff --git a/contracts/cw721-fixed-price/src/msg.rs b/contracts/cw721-fixed-price/src/msg.rs index cdf723854..ae9745b43 100644 --- a/contracts/cw721-fixed-price/src/msg.rs +++ b/contracts/cw721-fixed-price/src/msg.rs @@ -1,7 +1,7 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Uint128}; use cw20::Cw20ReceiveMsg; -use cw721_base::Extension; +use cw721::state::DefaultOptionMetadataExtension; #[cw_serde] pub struct InstantiateMsg { @@ -13,7 +13,7 @@ pub struct InstantiateMsg { pub token_code_id: u64, pub cw20_address: Addr, pub token_uri: String, - pub extension: Extension, + pub extension: DefaultOptionMetadataExtension, pub withdraw_address: Option, } @@ -39,6 +39,6 @@ pub struct ConfigResponse { pub name: String, pub symbol: String, pub token_uri: String, - pub extension: Extension, + pub extension: DefaultOptionMetadataExtension, pub unused_token_id: u32, } diff --git a/contracts/cw721-fixed-price/src/state.rs b/contracts/cw721-fixed-price/src/state.rs index a1ad80c99..9179a8cd5 100644 --- a/contracts/cw721-fixed-price/src/state.rs +++ b/contracts/cw721-fixed-price/src/state.rs @@ -1,6 +1,9 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Uint128}; -use cw721_base::Extension; + +// expose to all others using contract, so others dont need to import cw721 +pub use cw721::state::*; + use cw_storage_plus::Item; #[cw_serde] @@ -13,7 +16,7 @@ pub struct Config { pub name: String, pub symbol: String, pub token_uri: String, - pub extension: Extension, + pub extension: DefaultOptionMetadataExtension, pub unused_token_id: u32, } diff --git a/contracts/cw721-metadata-onchain/.cargo/config b/contracts/cw721-metadata-onchain/.cargo/config deleted file mode 100644 index 7d1a066c8..000000000 --- a/contracts/cw721-metadata-onchain/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib" -schema = "run --example schema" diff --git a/contracts/cw721-metadata-onchain/Cargo.toml b/contracts/cw721-metadata-onchain/Cargo.toml deleted file mode 100644 index 75944e575..000000000 --- a/contracts/cw721-metadata-onchain/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "cw721-metadata-onchain" -description = "Example extending CW721 NFT to store metadata on chain" -authors = [ - "Ethan Frey ", - "Orkun Külçe ", -] -version = { workspace = true } -edition = { workspace = true } -license = { workspace = true } -repository = { workspace = true } -homepage = { workspace = true } -documentation = { workspace = true } - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] -# use library feature to disable all instantiate/execute/query exports -library = [] - -[dependencies] -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } -cw2 = { workspace = true } -cw721 = { workspace = true } -cw721-base = { workspace = true, features = ["library"] } -schemars = { workspace = true } -serde = { workspace = true } diff --git a/contracts/cw721-metadata-onchain/NOTICE b/contracts/cw721-metadata-onchain/NOTICE deleted file mode 100644 index bd298d741..000000000 --- a/contracts/cw721-metadata-onchain/NOTICE +++ /dev/null @@ -1,14 +0,0 @@ -Cw721_metadata_onchain -Copyright (C) 2021 Confio OÜ - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/contracts/cw721-metadata-onchain/README.md b/contracts/cw721-metadata-onchain/README.md deleted file mode 100644 index dd449656e..000000000 --- a/contracts/cw721-metadata-onchain/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# CW721 Metadata Onchain - -NFT creators may want to store their NFT metadata on-chain so other contracts are able to interact with it. -With CW721-Base in CosmWasm, we allow you to store any data on chain you wish, using a generic `extension: T`. - -In order to support on-chain metadata, and to demonstrate how to use the extension ability, we have created this simple contract. -There is no business logic here, but looking at `lib.rs` will show you how do define custom data that is included when minting and -available in all queries. - -In particular, here we define: - -```rust -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] -pub struct Trait { - pub display_type: Option, - pub trait_type: String, - pub value: String, -} - -#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] -pub struct Metadata { - pub image: Option, - pub image_data: Option, - pub external_url: Option, - pub description: Option, - pub name: Option, - pub attributes: Option>, - pub background_color: Option, - pub animation_url: Option, - pub youtube_url: Option, -} - -pub type Extension = Option; -``` - -In particular, the fields defined conform to the properties supported in the [OpenSea Metadata Standard](https://docs.opensea.io/docs/metadata-standards). - - -This means when you query `NftInfo{name: "Enterprise"}`, you will get something like: - -```json -{ - "name": "Enterprise", - "token_uri": "https://starships.example.com/Starship/Enterprise.json", - "extension": { - "image": null, - "image_data": null, - "external_url": null, - "description": "Spaceship with Warp Drive", - "name": "Starship USS Enterprise", - "attributes": null, - "background_color": null, - "animation_url": null, - "youtube_url": null - } -} -``` - -Please look at the test code for an example usage in Rust. - -## Notice - -Feel free to use this contract out of the box, or as inspiration for further customization of cw721-base. -We will not be adding new features or business logic here. diff --git a/contracts/cw721-metadata-onchain/examples/schema.rs b/contracts/cw721-metadata-onchain/examples/schema.rs deleted file mode 100644 index 36087d75d..000000000 --- a/contracts/cw721-metadata-onchain/examples/schema.rs +++ /dev/null @@ -1,11 +0,0 @@ -use cosmwasm_schema::write_api; - -use cw721_metadata_onchain::{ExecuteMsg, InstantiateMsg, QueryMsg}; - -fn main() { - write_api! { - instantiate: InstantiateMsg, - execute: ExecuteMsg, - query: QueryMsg, - } -} diff --git a/contracts/cw721-metadata-onchain/schema/cw721-metadata-onchain.json b/contracts/cw721-metadata-onchain/schema/cw721-metadata-onchain.json deleted file mode 100644 index c6dfa6cd1..000000000 --- a/contracts/cw721-metadata-onchain/schema/cw721-metadata-onchain.json +++ /dev/null @@ -1,1875 +0,0 @@ -{ - "contract_name": "cw721-metadata-onchain", - "contract_version": "0.18.0", - "idl_version": "1.0.0", - "instantiate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "name", - "symbol" - ], - "properties": { - "minter": { - "description": "The minter is the only one who can create new NFTs. This is designed for a base NFT that is controlled by an external program or contract. You will likely replace this with custom logic in custom NFTs", - "type": [ - "string", - "null" - ] - }, - "name": { - "description": "Name of the NFT contract", - "type": "string" - }, - "symbol": { - "description": "Symbol of the NFT contract", - "type": "string" - }, - "withdraw_address": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - }, - "execute": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "description": "This is like Cw721ExecuteMsg but we add a Mint command for an owner to make this stand-alone. You will likely want to remove mint and use other control logic in any contract that inherits this.", - "oneOf": [ - { - "description": "Transfer is a base message to move a token to another account without triggering actions", - "type": "object", - "required": [ - "transfer_nft" - ], - "properties": { - "transfer_nft": { - "type": "object", - "required": [ - "recipient", - "token_id" - ], - "properties": { - "recipient": { - "type": "string" - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Send is a base message to transfer a token to a contract and trigger an action on the receiving contract.", - "type": "object", - "required": [ - "send_nft" - ], - "properties": { - "send_nft": { - "type": "object", - "required": [ - "contract", - "msg", - "token_id" - ], - "properties": { - "contract": { - "type": "string" - }, - "msg": { - "$ref": "#/definitions/Binary" - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Allows operator to transfer / send the token from the owner's account. If expiration is set, then this allowance has a time/height limit", - "type": "object", - "required": [ - "approve" - ], - "properties": { - "approve": { - "type": "object", - "required": [ - "spender", - "token_id" - ], - "properties": { - "expires": { - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "spender": { - "type": "string" - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Remove previously granted Approval", - "type": "object", - "required": [ - "revoke" - ], - "properties": { - "revoke": { - "type": "object", - "required": [ - "spender", - "token_id" - ], - "properties": { - "spender": { - "type": "string" - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Allows operator to transfer / send any token from the owner's account. If expiration is set, then this allowance has a time/height limit", - "type": "object", - "required": [ - "approve_all" - ], - "properties": { - "approve_all": { - "type": "object", - "required": [ - "operator" - ], - "properties": { - "expires": { - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "operator": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Remove previously granted ApproveAll permission", - "type": "object", - "required": [ - "revoke_all" - ], - "properties": { - "revoke_all": { - "type": "object", - "required": [ - "operator" - ], - "properties": { - "operator": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Mint a new NFT, can only be called by the contract minter", - "type": "object", - "required": [ - "mint" - ], - "properties": { - "mint": { - "type": "object", - "required": [ - "owner", - "token_id" - ], - "properties": { - "extension": { - "description": "Any custom extension used by this contract", - "anyOf": [ - { - "$ref": "#/definitions/Metadata" - }, - { - "type": "null" - } - ] - }, - "owner": { - "description": "The owner of the newly minter NFT", - "type": "string" - }, - "token_id": { - "description": "Unique ID of the NFT", - "type": "string" - }, - "token_uri": { - "description": "Universal resource identifier for this NFT Should point to a JSON file that conforms to the ERC721 Metadata JSON Schema", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Burn an NFT the sender has access to", - "type": "object", - "required": [ - "burn" - ], - "properties": { - "burn": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Extension msg", - "type": "object", - "required": [ - "extension" - ], - "properties": { - "extension": { - "type": "object", - "required": [ - "msg" - ], - "properties": { - "msg": { - "$ref": "#/definitions/Empty" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Sets address to send withdrawn fees to. Only owner can call this.", - "type": "object", - "required": [ - "set_withdraw_address" - ], - "properties": { - "set_withdraw_address": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Removes the withdraw address, so fees are sent to the contract. Only owner can call this.", - "type": "object", - "required": [ - "remove_withdraw_address" - ], - "properties": { - "remove_withdraw_address": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Withdraw from the contract to the given address. Anyone can call this, which is okay since withdraw address has been set by owner.", - "type": "object", - "required": [ - "withdraw_funds" - ], - "properties": { - "withdraw_funds": { - "type": "object", - "required": [ - "amount" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Coin" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Update the contract's ownership. The `action` to be provided can be either to propose transferring ownership to an account, accept a pending ownership transfer, or renounce the ownership permanently.", - "type": "object", - "required": [ - "update_ownership" - ], - "properties": { - "update_ownership": { - "$ref": "#/definitions/Action" - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Action": { - "description": "Actions that can be taken to alter the contract's ownership", - "oneOf": [ - { - "description": "Propose to transfer the contract's ownership to another account, optionally with an expiry time.\n\nCan only be called by the contract's current owner.\n\nAny existing pending ownership transfer is overwritten.", - "type": "object", - "required": [ - "transfer_ownership" - ], - "properties": { - "transfer_ownership": { - "type": "object", - "required": [ - "new_owner" - ], - "properties": { - "expiry": { - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "new_owner": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Accept the pending ownership transfer.\n\nCan only be called by the pending owner.", - "type": "string", - "enum": [ - "accept_ownership" - ] - }, - { - "description": "Give up the contract's ownership and the possibility of appointing a new owner.\n\nCan only be invoked by the contract's current owner.\n\nAny existing pending ownership transfer is canceled.", - "type": "string", - "enum": [ - "renounce_ownership" - ] - } - ] - }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", - "type": "string" - }, - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "denom": { - "type": "string" - } - } - }, - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Metadata": { - "type": "object", - "properties": { - "animation_url": { - "type": [ - "string", - "null" - ] - }, - "attributes": { - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Trait" - } - }, - "background_color": { - "type": [ - "string", - "null" - ] - }, - "description": { - "type": [ - "string", - "null" - ] - }, - "external_url": { - "type": [ - "string", - "null" - ] - }, - "image": { - "type": [ - "string", - "null" - ] - }, - "image_data": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": [ - "string", - "null" - ] - }, - "youtube_url": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Trait": { - "type": "object", - "required": [ - "trait_type", - "value" - ], - "properties": { - "display_type": { - "type": [ - "string", - "null" - ] - }, - "trait_type": { - "type": "string" - }, - "value": { - "type": "string" - } - }, - "additionalProperties": false - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "query": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "oneOf": [ - { - "description": "Return the owner of the given token, error if token does not exist", - "type": "object", - "required": [ - "owner_of" - ], - "properties": { - "owner_of": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "include_expired": { - "description": "unset or false will filter out expired approvals, you must set to true to see them", - "type": [ - "boolean", - "null" - ] - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Return operator that can access all of the owner's tokens.", - "type": "object", - "required": [ - "approval" - ], - "properties": { - "approval": { - "type": "object", - "required": [ - "spender", - "token_id" - ], - "properties": { - "include_expired": { - "type": [ - "boolean", - "null" - ] - }, - "spender": { - "type": "string" - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Return approvals that a token has", - "type": "object", - "required": [ - "approvals" - ], - "properties": { - "approvals": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "include_expired": { - "type": [ - "boolean", - "null" - ] - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Return approval of a given operator for all tokens of an owner, error if not set", - "type": "object", - "required": [ - "operator" - ], - "properties": { - "operator": { - "type": "object", - "required": [ - "operator", - "owner" - ], - "properties": { - "include_expired": { - "type": [ - "boolean", - "null" - ] - }, - "operator": { - "type": "string" - }, - "owner": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "List all operators that can access all of the owner's tokens", - "type": "object", - "required": [ - "all_operators" - ], - "properties": { - "all_operators": { - "type": "object", - "required": [ - "owner" - ], - "properties": { - "include_expired": { - "description": "unset or false will filter out expired items, you must set to true to see them", - "type": [ - "boolean", - "null" - ] - }, - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "owner": { - "type": "string" - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Total number of tokens issued", - "type": "object", - "required": [ - "num_tokens" - ], - "properties": { - "num_tokens": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "With MetaData Extension. Returns top-level metadata about the contract", - "type": "object", - "required": [ - "contract_info" - ], - "properties": { - "contract_info": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "With MetaData Extension. Returns metadata about one particular token, based on *ERC721 Metadata JSON Schema* but directly from the contract", - "type": "object", - "required": [ - "nft_info" - ], - "properties": { - "nft_info": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "With MetaData Extension. Returns the result of both `NftInfo` and `OwnerOf` as one query as an optimization for clients", - "type": "object", - "required": [ - "all_nft_info" - ], - "properties": { - "all_nft_info": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "include_expired": { - "description": "unset or false will filter out expired approvals, you must set to true to see them", - "type": [ - "boolean", - "null" - ] - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "With Enumerable extension. Returns all tokens owned by the given address, [] if unset.", - "type": "object", - "required": [ - "tokens" - ], - "properties": { - "tokens": { - "type": "object", - "required": [ - "owner" - ], - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "owner": { - "type": "string" - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "With Enumerable extension. Requires pagination. Lists all token_ids controlled by the contract.", - "type": "object", - "required": [ - "all_tokens" - ], - "properties": { - "all_tokens": { - "type": "object", - "properties": { - "limit": { - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - }, - "start_after": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Return the minter", - "type": "object", - "required": [ - "minter" - ], - "properties": { - "minter": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Extension query", - "type": "object", - "required": [ - "extension" - ], - "properties": { - "extension": { - "type": "object", - "required": [ - "msg" - ], - "properties": { - "msg": { - "$ref": "#/definitions/Empty" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "get_withdraw_address" - ], - "properties": { - "get_withdraw_address": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Query the contract's ownership information", - "type": "object", - "required": [ - "ownership" - ], - "properties": { - "ownership": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - } - } - }, - "migrate": null, - "sudo": null, - "responses": { - "all_nft_info": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AllNftInfoResponse_for_Empty", - "type": "object", - "required": [ - "access", - "info" - ], - "properties": { - "access": { - "description": "Who can transfer the token", - "allOf": [ - { - "$ref": "#/definitions/OwnerOfResponse" - } - ] - }, - "info": { - "description": "Data on the token itself,", - "allOf": [ - { - "$ref": "#/definitions/NftInfoResponse_for_Empty" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "NftInfoResponse_for_Empty": { - "type": "object", - "required": [ - "extension" - ], - "properties": { - "extension": { - "description": "You can add any custom metadata here when you extend cw721-base", - "allOf": [ - { - "$ref": "#/definitions/Empty" - } - ] - }, - "token_uri": { - "description": "Universal resource identifier for this NFT Should point to a JSON file that conforms to the ERC721 Metadata JSON Schema", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - }, - "OwnerOfResponse": { - "type": "object", - "required": [ - "approvals", - "owner" - ], - "properties": { - "approvals": { - "description": "If set this address is approved to transfer/send the token as well", - "type": "array", - "items": { - "$ref": "#/definitions/Approval" - } - }, - "owner": { - "description": "Owner of the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "all_operators": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "OperatorsResponse", - "type": "object", - "required": [ - "operators" - ], - "properties": { - "operators": { - "type": "array", - "items": { - "$ref": "#/definitions/Approval" - } - } - }, - "additionalProperties": false, - "definitions": { - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "all_tokens": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "TokensResponse", - "type": "object", - "required": [ - "tokens" - ], - "properties": { - "tokens": { - "description": "Contains all token_ids in lexicographical ordering If there are more than `limit`, use `start_after` in future queries to achieve pagination.", - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - }, - "approval": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ApprovalResponse", - "type": "object", - "required": [ - "approval" - ], - "properties": { - "approval": { - "$ref": "#/definitions/Approval" - } - }, - "additionalProperties": false, - "definitions": { - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "approvals": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ApprovalsResponse", - "type": "object", - "required": [ - "approvals" - ], - "properties": { - "approvals": { - "type": "array", - "items": { - "$ref": "#/definitions/Approval" - } - } - }, - "additionalProperties": false, - "definitions": { - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "contract_info": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ContractInfoResponse", - "type": "object", - "required": [ - "name", - "symbol" - ], - "properties": { - "name": { - "type": "string" - }, - "symbol": { - "type": "string" - } - }, - "additionalProperties": false - }, - "extension": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Null", - "type": "null" - }, - "get_withdraw_address": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Nullable_String", - "type": [ - "string", - "null" - ] - }, - "minter": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MinterResponse", - "description": "Shows who can mint these tokens", - "type": "object", - "properties": { - "minter": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - }, - "nft_info": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "NftInfoResponse_for_Empty", - "type": "object", - "required": [ - "extension" - ], - "properties": { - "extension": { - "description": "You can add any custom metadata here when you extend cw721-base", - "allOf": [ - { - "$ref": "#/definitions/Empty" - } - ] - }, - "token_uri": { - "description": "Universal resource identifier for this NFT Should point to a JSON file that conforms to the ERC721 Metadata JSON Schema", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false, - "definitions": { - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - } - } - }, - "num_tokens": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "NumTokensResponse", - "type": "object", - "required": [ - "count" - ], - "properties": { - "count": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - "operator": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "OperatorResponse", - "type": "object", - "required": [ - "approval" - ], - "properties": { - "approval": { - "$ref": "#/definitions/Approval" - } - }, - "additionalProperties": false, - "definitions": { - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "owner_of": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "OwnerOfResponse", - "type": "object", - "required": [ - "approvals", - "owner" - ], - "properties": { - "approvals": { - "description": "If set this address is approved to transfer/send the token as well", - "type": "array", - "items": { - "$ref": "#/definitions/Approval" - } - }, - "owner": { - "description": "Owner of the token", - "type": "string" - } - }, - "additionalProperties": false, - "definitions": { - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "ownership": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Ownership_for_String", - "description": "The contract's ownership info", - "type": "object", - "properties": { - "owner": { - "description": "The contract's current owner. `None` if the ownership has been renounced.", - "type": [ - "string", - "null" - ] - }, - "pending_expiry": { - "description": "The deadline for the pending owner to accept the ownership. `None` if there isn't a pending ownership transfer, or if a transfer exists and it doesn't have a deadline.", - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "pending_owner": { - "description": "The account who has been proposed to take over the ownership. `None` if there isn't a pending ownership transfer.", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false, - "definitions": { - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } - }, - "tokens": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "TokensResponse", - "type": "object", - "required": [ - "tokens" - ], - "properties": { - "tokens": { - "description": "Contains all token_ids in lexicographical ordering If there are more than `limit`, use `start_after` in future queries to achieve pagination.", - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - } - } -} diff --git a/contracts/cw721-metadata-onchain/src/lib.rs b/contracts/cw721-metadata-onchain/src/lib.rs deleted file mode 100644 index e8a133122..000000000 --- a/contracts/cw721-metadata-onchain/src/lib.rs +++ /dev/null @@ -1,143 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::Empty; -pub use cw721_base::{ContractError, InstantiateMsg}; - -// Version info for migration -const CONTRACT_NAME: &str = "crates.io:cw721-metadata-onchain"; -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -#[cw_serde] -pub struct Trait { - pub display_type: Option, - pub trait_type: String, - pub value: String, -} - -// see: https://docs.opensea.io/docs/metadata-standards -#[cw_serde] -#[derive(Default)] -pub struct Metadata { - pub image: Option, - pub image_data: Option, - pub external_url: Option, - pub description: Option, - pub name: Option, - pub attributes: Option>, - pub background_color: Option, - pub animation_url: Option, - pub youtube_url: Option, -} - -pub type Extension = Option; - -pub type Cw721MetadataContract<'a> = cw721_base::Cw721Contract<'a, Extension, Empty, Empty, Empty>; -pub type ExecuteMsg = cw721_base::ExecuteMsg; -pub type QueryMsg = cw721_base::QueryMsg; - -#[cfg(not(feature = "library"))] -pub mod entry { - use super::*; - - use cosmwasm_std::entry_point; - use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; - - // This makes a conscious choice on the various generics used by the contract - #[entry_point] - pub fn instantiate( - mut deps: DepsMut, - env: Env, - info: MessageInfo, - msg: InstantiateMsg, - ) -> Result { - cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - - Cw721MetadataContract::default().instantiate(deps.branch(), env, info, msg) - } - - #[entry_point] - pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, - ) -> Result { - Cw721MetadataContract::default().execute(deps, env, info, msg) - } - - #[entry_point] - pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - Cw721MetadataContract::default().query(deps, env, msg) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; - use cw721::Cw721Query; - - const CREATOR: &str = "creator"; - - /// Make sure cw2 version info is properly initialized during instantiation, - /// and NOT overwritten by the base contract. - #[test] - fn proper_cw2_initialization() { - let mut deps = mock_dependencies(); - - entry::instantiate( - deps.as_mut(), - mock_env(), - mock_info("larry", &[]), - InstantiateMsg { - name: "".into(), - symbol: "".into(), - minter: None, - withdraw_address: None, - }, - ) - .unwrap(); - - let version = cw2::get_contract_version(deps.as_ref().storage).unwrap(); - assert_eq!(version.contract, CONTRACT_NAME); - assert_ne!(version.contract, cw721_base::CONTRACT_NAME); - } - - #[test] - fn use_metadata_extension() { - let mut deps = mock_dependencies(); - let contract = Cw721MetadataContract::default(); - - let info = mock_info(CREATOR, &[]); - let init_msg = InstantiateMsg { - name: "SpaceShips".to_string(), - symbol: "SPACE".to_string(), - minter: None, - withdraw_address: None, - }; - contract - .instantiate(deps.as_mut(), mock_env(), info.clone(), init_msg) - .unwrap(); - - let token_id = "Enterprise"; - let token_uri = Some("https://starships.example.com/Starship/Enterprise.json".into()); - let extension = Some(Metadata { - description: Some("Spaceship with Warp Drive".into()), - name: Some("Starship USS Enterprise".to_string()), - ..Metadata::default() - }); - let exec_msg = ExecuteMsg::Mint { - token_id: token_id.to_string(), - owner: "john".to_string(), - token_uri: token_uri.clone(), - extension: extension.clone(), - }; - contract - .execute(deps.as_mut(), mock_env(), info, exec_msg) - .unwrap(); - - let res = contract.nft_info(deps.as_ref(), token_id.into()).unwrap(); - assert_eq!(res.token_uri, token_uri); - assert_eq!(res.extension, extension); - } -} diff --git a/contracts/cw721-non-transferable/examples/schema.rs b/contracts/cw721-non-transferable/examples/schema.rs index cd1cde28c..6db9bbd51 100644 --- a/contracts/cw721-non-transferable/examples/schema.rs +++ b/contracts/cw721-non-transferable/examples/schema.rs @@ -1,14 +1,14 @@ use std::env::current_dir; use std::fs::create_dir_all; -use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for}; +use cosmwasm_schema::{export_schema_with_title, remove_schemas, schema_for}; -use cw721::{ - AllNftInfoResponse, ApprovalResponse, ApprovalsResponse, ContractInfoResponse, Cw721ExecuteMsg, - MinterResponse, NftInfoResponse, NumTokensResponse, OperatorsResponse, OwnerOfResponse, - TokensResponse, +use cosmwasm_std::Empty; +use cw721::state::DefaultOptionMetadataExtension; +use cw721_non_transferable::{ + msg::{ExecuteMsg, MigrateMsg}, + InstantiateMsg, QueryMsg, }; -use cw721_non_transferable::{Extension, InstantiateMsg, QueryMsg}; fn main() { let mut out_dir = current_dir().unwrap(); @@ -16,25 +16,13 @@ fn main() { create_dir_all(&out_dir).unwrap(); remove_schemas(&out_dir).unwrap(); - export_schema(&schema_for!(InstantiateMsg), &out_dir); - export_schema_with_title(&schema_for!(Cw721ExecuteMsg), &out_dir, "Cw721ExecuteMsg"); - export_schema(&schema_for!(QueryMsg), &out_dir); + // entry points - generate always with title for avoiding name suffixes like "..._empty_for_..." due to generics + export_schema_with_title(&schema_for!(InstantiateMsg), &out_dir, "InstantiateMsg"); export_schema_with_title( - &schema_for!(AllNftInfoResponse), + &schema_for!(ExecuteMsg::), &out_dir, - "AllNftInfoResponse", + "ExecuteMsg", ); - export_schema(&schema_for!(ApprovalResponse), &out_dir); - export_schema(&schema_for!(ApprovalsResponse), &out_dir); - export_schema(&schema_for!(OperatorsResponse), &out_dir); - export_schema(&schema_for!(ContractInfoResponse), &out_dir); - export_schema(&schema_for!(MinterResponse), &out_dir); - export_schema_with_title( - &schema_for!(NftInfoResponse), - &out_dir, - "NftInfoResponse", - ); - export_schema(&schema_for!(NumTokensResponse), &out_dir); - export_schema(&schema_for!(OwnerOfResponse), &out_dir); - export_schema(&schema_for!(TokensResponse), &out_dir); + export_schema_with_title(&schema_for!(QueryMsg), &out_dir, "QueryMsg"); + export_schema_with_title(&schema_for!(MigrateMsg), &out_dir, "MigrateMsg"); } diff --git a/contracts/cw721-non-transferable/schema/all_nft_info_response.json b/contracts/cw721-non-transferable/schema/all_nft_info_response.json deleted file mode 100644 index d263321e2..000000000 --- a/contracts/cw721-non-transferable/schema/all_nft_info_response.json +++ /dev/null @@ -1,160 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AllNftInfoResponse", - "type": "object", - "required": [ - "access", - "info" - ], - "properties": { - "access": { - "description": "Who can transfer the token", - "allOf": [ - { - "$ref": "#/definitions/OwnerOfResponse" - } - ] - }, - "info": { - "description": "Data on the token itself,", - "allOf": [ - { - "$ref": "#/definitions/NftInfoResponse_for_Nullable_Empty" - } - ] - } - }, - "additionalProperties": false, - "definitions": { - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "NftInfoResponse_for_Nullable_Empty": { - "type": "object", - "properties": { - "extension": { - "description": "You can add any custom metadata here when you extend cw721-base", - "anyOf": [ - { - "$ref": "#/definitions/Empty" - }, - { - "type": "null" - } - ] - }, - "token_uri": { - "description": "Universal resource identifier for this NFT Should point to a JSON file that conforms to the ERC721 Metadata JSON Schema", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - }, - "OwnerOfResponse": { - "type": "object", - "required": [ - "approvals", - "owner" - ], - "properties": { - "approvals": { - "description": "If set this address is approved to transfer/send the token as well", - "type": "array", - "items": { - "$ref": "#/definitions/Approval" - } - }, - "owner": { - "description": "Owner of the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/cw721-non-transferable/schema/approval_response.json b/contracts/cw721-non-transferable/schema/approval_response.json deleted file mode 100644 index b29eab59e..000000000 --- a/contracts/cw721-non-transferable/schema/approval_response.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ApprovalResponse", - "type": "object", - "required": [ - "approval" - ], - "properties": { - "approval": { - "$ref": "#/definitions/Approval" - } - }, - "additionalProperties": false, - "definitions": { - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/cw721-non-transferable/schema/approvals_response.json b/contracts/cw721-non-transferable/schema/approvals_response.json deleted file mode 100644 index 7cdac0015..000000000 --- a/contracts/cw721-non-transferable/schema/approvals_response.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ApprovalsResponse", - "type": "object", - "required": [ - "approvals" - ], - "properties": { - "approvals": { - "type": "array", - "items": { - "$ref": "#/definitions/Approval" - } - } - }, - "additionalProperties": false, - "definitions": { - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/cw721-non-transferable/schema/cw721_execute_msg.json b/contracts/cw721-non-transferable/schema/cw721_execute_msg.json deleted file mode 100644 index 228b5c249..000000000 --- a/contracts/cw721-non-transferable/schema/cw721_execute_msg.json +++ /dev/null @@ -1,265 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Cw721ExecuteMsg", - "oneOf": [ - { - "description": "Transfer is a base message to move a token to another account without triggering actions", - "type": "object", - "required": [ - "transfer_nft" - ], - "properties": { - "transfer_nft": { - "type": "object", - "required": [ - "recipient", - "token_id" - ], - "properties": { - "recipient": { - "type": "string" - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Send is a base message to transfer a token to a contract and trigger an action on the receiving contract.", - "type": "object", - "required": [ - "send_nft" - ], - "properties": { - "send_nft": { - "type": "object", - "required": [ - "contract", - "msg", - "token_id" - ], - "properties": { - "contract": { - "type": "string" - }, - "msg": { - "$ref": "#/definitions/Binary" - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Allows operator to transfer / send the token from the owner's account. If expiration is set, then this allowance has a time/height limit", - "type": "object", - "required": [ - "approve" - ], - "properties": { - "approve": { - "type": "object", - "required": [ - "spender", - "token_id" - ], - "properties": { - "expires": { - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "spender": { - "type": "string" - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Remove previously granted Approval", - "type": "object", - "required": [ - "revoke" - ], - "properties": { - "revoke": { - "type": "object", - "required": [ - "spender", - "token_id" - ], - "properties": { - "spender": { - "type": "string" - }, - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Allows operator to transfer / send any token from the owner's account. If expiration is set, then this allowance has a time/height limit", - "type": "object", - "required": [ - "approve_all" - ], - "properties": { - "approve_all": { - "type": "object", - "required": [ - "operator" - ], - "properties": { - "expires": { - "anyOf": [ - { - "$ref": "#/definitions/Expiration" - }, - { - "type": "null" - } - ] - }, - "operator": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Remove previously granted ApproveAll permission", - "type": "object", - "required": [ - "revoke_all" - ], - "properties": { - "revoke_all": { - "type": "object", - "required": [ - "operator" - ], - "properties": { - "operator": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Burn an NFT the sender has access to", - "type": "object", - "required": [ - "burn" - ], - "properties": { - "burn": { - "type": "object", - "required": [ - "token_id" - ], - "properties": { - "token_id": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", - "type": "string" - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/cw721-non-transferable/schema/execute_msg.json b/contracts/cw721-non-transferable/schema/execute_msg.json new file mode 100644 index 000000000..2bcf2fea7 --- /dev/null +++ b/contracts/cw721-non-transferable/schema/execute_msg.json @@ -0,0 +1,562 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "update_ownership" + ], + "properties": { + "update_ownership": { + "$ref": "#/definitions/Action" + } + }, + "additionalProperties": false + }, + { + "description": "Transfer is a base message to move a token to another account without triggering actions", + "type": "object", + "required": [ + "transfer_nft" + ], + "properties": { + "transfer_nft": { + "type": "object", + "required": [ + "recipient", + "token_id" + ], + "properties": { + "recipient": { + "type": "string" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Send is a base message to transfer a token to a contract and trigger an action on the receiving contract.", + "type": "object", + "required": [ + "send_nft" + ], + "properties": { + "send_nft": { + "type": "object", + "required": [ + "contract", + "msg", + "token_id" + ], + "properties": { + "contract": { + "type": "string" + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Allows operator to transfer / send the token from the owner's account. If expiration is set, then this allowance has a time/height limit", + "type": "object", + "required": [ + "approve" + ], + "properties": { + "approve": { + "type": "object", + "required": [ + "spender", + "token_id" + ], + "properties": { + "expires": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "spender": { + "type": "string" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Remove previously granted Approval", + "type": "object", + "required": [ + "revoke" + ], + "properties": { + "revoke": { + "type": "object", + "required": [ + "spender", + "token_id" + ], + "properties": { + "spender": { + "type": "string" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Allows operator to transfer / send any token from the owner's account. If expiration is set, then this allowance has a time/height limit", + "type": "object", + "required": [ + "approve_all" + ], + "properties": { + "approve_all": { + "type": "object", + "required": [ + "operator" + ], + "properties": { + "expires": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "operator": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Remove previously granted ApproveAll permission", + "type": "object", + "required": [ + "revoke_all" + ], + "properties": { + "revoke_all": { + "type": "object", + "required": [ + "operator" + ], + "properties": { + "operator": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Mint a new NFT, can only be called by the contract minter", + "type": "object", + "required": [ + "mint" + ], + "properties": { + "mint": { + "type": "object", + "required": [ + "owner", + "token_id" + ], + "properties": { + "extension": { + "description": "Any custom extension used by this contract", + "anyOf": [ + { + "$ref": "#/definitions/Metadata" + }, + { + "type": "null" + } + ] + }, + "owner": { + "description": "The owner of the newly minter NFT", + "type": "string" + }, + "token_id": { + "description": "Unique ID of the NFT", + "type": "string" + }, + "token_uri": { + "description": "Universal resource identifier for this NFT Should point to a JSON file that conforms to the ERC721 Metadata JSON Schema", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Burn an NFT the sender has access to", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Extension msg", + "type": "object", + "required": [ + "extension" + ], + "properties": { + "extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Sets address to send withdrawn fees to. Only owner can call this.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Removes the withdraw address, so fees are sent to the contract. Only owner can call this.", + "type": "object", + "required": [ + "remove_withdraw_address" + ], + "properties": { + "remove_withdraw_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Withdraw from the contract to the given address. Anyone can call this, which is okay since withdraw address has been set by owner.", + "type": "object", + "required": [ + "withdraw_funds" + ], + "properties": { + "withdraw_funds": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Action": { + "description": "Actions that can be taken to alter the contract's ownership", + "oneOf": [ + { + "description": "Propose to transfer the contract's ownership to another account, optionally with an expiry time.\n\nCan only be called by the contract's current owner.\n\nAny existing pending ownership transfer is overwritten.", + "type": "object", + "required": [ + "transfer_ownership" + ], + "properties": { + "transfer_ownership": { + "type": "object", + "required": [ + "new_owner" + ], + "properties": { + "expiry": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "new_owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Accept the pending ownership transfer.\n\nCan only be called by the pending owner.", + "type": "string", + "enum": [ + "accept_ownership" + ] + }, + { + "description": "Give up the contract's ownership and the possibility of appointing a new owner.\n\nCan only be invoked by the contract's current owner.\n\nAny existing pending ownership transfer is canceled.", + "type": "string", + "enum": [ + "renounce_ownership" + ] + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "Expiration": { + "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", + "oneOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "$ref": "#/definitions/Timestamp" + } + }, + "additionalProperties": false + }, + { + "description": "Never will never expire. Used to express the empty variant", + "type": "object", + "required": [ + "never" + ], + "properties": { + "never": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Metadata": { + "type": "object", + "properties": { + "animation_url": { + "type": [ + "string", + "null" + ] + }, + "attributes": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Trait" + } + }, + "background_color": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "external_url": { + "type": [ + "string", + "null" + ] + }, + "image": { + "type": [ + "string", + "null" + ] + }, + "image_data": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "youtube_url": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "Trait": { + "type": "object", + "required": [ + "trait_type", + "value" + ], + "properties": { + "display_type": { + "type": [ + "string", + "null" + ] + }, + "trait_type": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/cw721-non-transferable/schema/migrate_msg.json b/contracts/cw721-non-transferable/schema/migrate_msg.json new file mode 100644 index 000000000..f83fa9508 --- /dev/null +++ b/contracts/cw721-non-transferable/schema/migrate_msg.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "with_update" + ], + "properties": { + "with_update": { + "type": "object", + "properties": { + "creator": { + "type": [ + "string", + "null" + ] + }, + "minter": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/cw721-non-transferable/schema/nft_info_response.json b/contracts/cw721-non-transferable/schema/nft_info_response.json deleted file mode 100644 index 51b1f072c..000000000 --- a/contracts/cw721-non-transferable/schema/nft_info_response.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "NftInfoResponse", - "type": "object", - "properties": { - "extension": { - "description": "You can add any custom metadata here when you extend cw721-base", - "anyOf": [ - { - "$ref": "#/definitions/Empty" - }, - { - "type": "null" - } - ] - }, - "token_uri": { - "description": "Universal resource identifier for this NFT Should point to a JSON file that conforms to the ERC721 Metadata JSON Schema", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false, - "definitions": { - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - } - } -} diff --git a/contracts/cw721-non-transferable/schema/num_tokens_response.json b/contracts/cw721-non-transferable/schema/num_tokens_response.json deleted file mode 100644 index aff5850c8..000000000 --- a/contracts/cw721-non-transferable/schema/num_tokens_response.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "NumTokensResponse", - "type": "object", - "required": [ - "count" - ], - "properties": { - "count": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false -} diff --git a/contracts/cw721-non-transferable/schema/operators_response.json b/contracts/cw721-non-transferable/schema/operators_response.json deleted file mode 100644 index 533a096dd..000000000 --- a/contracts/cw721-non-transferable/schema/operators_response.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "OperatorsResponse", - "type": "object", - "required": [ - "operators" - ], - "properties": { - "operators": { - "type": "array", - "items": { - "$ref": "#/definitions/Approval" - } - } - }, - "additionalProperties": false, - "definitions": { - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/cw721-non-transferable/schema/owner_of_response.json b/contracts/cw721-non-transferable/schema/owner_of_response.json deleted file mode 100644 index abb9006d8..000000000 --- a/contracts/cw721-non-transferable/schema/owner_of_response.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "OwnerOfResponse", - "type": "object", - "required": [ - "approvals", - "owner" - ], - "properties": { - "approvals": { - "description": "If set this address is approved to transfer/send the token as well", - "type": "array", - "items": { - "$ref": "#/definitions/Approval" - } - }, - "owner": { - "description": "Owner of the token", - "type": "string" - } - }, - "additionalProperties": false, - "definitions": { - "Approval": { - "type": "object", - "required": [ - "expires", - "spender" - ], - "properties": { - "expires": { - "description": "When the Approval expires (maybe Expiration::never)", - "allOf": [ - { - "$ref": "#/definitions/Expiration" - } - ] - }, - "spender": { - "description": "Account that can transfer/send the token", - "type": "string" - } - }, - "additionalProperties": false - }, - "Expiration": { - "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", - "oneOf": [ - { - "description": "AtHeight will expire when `env.block.height` >= height", - "type": "object", - "required": [ - "at_height" - ], - "properties": { - "at_height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "additionalProperties": false - }, - { - "description": "AtTime will expire when `env.block.time` >= time", - "type": "object", - "required": [ - "at_time" - ], - "properties": { - "at_time": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - }, - { - "description": "Never will never expire. Used to express the empty variant", - "type": "object", - "required": [ - "never" - ], - "properties": { - "never": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Timestamp": { - "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", - "allOf": [ - { - "$ref": "#/definitions/Uint64" - } - ] - }, - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/cw721-non-transferable/schema/query_msg.json b/contracts/cw721-non-transferable/schema/query_msg.json index ffbb412b3..4cde7f9f8 100644 --- a/contracts/cw721-non-transferable/schema/query_msg.json +++ b/contracts/cw721-non-transferable/schema/query_msg.json @@ -291,6 +291,19 @@ } }, "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_withdraw_address" + ], + "properties": { + "get_withdraw_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false } ] } diff --git a/contracts/cw721-non-transferable/schema/tokens_response.json b/contracts/cw721-non-transferable/schema/tokens_response.json deleted file mode 100644 index 4728d37e2..000000000 --- a/contracts/cw721-non-transferable/schema/tokens_response.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "TokensResponse", - "type": "object", - "required": [ - "tokens" - ], - "properties": { - "tokens": { - "description": "Contains all token_ids in lexicographical ordering If there are more than `limit`, use `start_after` in future queries to achieve pagination.", - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false -} diff --git a/contracts/cw721-non-transferable/src/lib.rs b/contracts/cw721-non-transferable/src/lib.rs index ed3fde750..ffdf96069 100644 --- a/contracts/cw721-non-transferable/src/lib.rs +++ b/contracts/cw721-non-transferable/src/lib.rs @@ -1,8 +1,9 @@ pub use crate::msg::{InstantiateMsg, QueryMsg}; use cosmwasm_std::Empty; +use cw721::state::DefaultOptionMetadataExtension; pub use cw721_base::{ entry::{execute as _execute, query as _query}, - ContractError, Cw721Contract, ExecuteMsg, Extension, InstantiateMsg as Cw721BaseInstantiateMsg, + Cw721Contract, }; pub mod msg; @@ -13,7 +14,8 @@ pub mod state; const CONTRACT_NAME: &str = "crates.io:cw721-non-transferable"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); -pub type Cw721NonTransferableContract<'a> = Cw721Contract<'a, Extension, Empty, Empty, Empty>; +pub type Cw721NonTransferableContract<'a> = + Cw721Contract<'a, DefaultOptionMetadataExtension, Empty, Empty, Empty>; #[cfg(not(feature = "library"))] pub mod entry { @@ -24,6 +26,9 @@ pub mod entry { entry_point, to_json_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, }; + use cw721::error::Cw721ContractError; + use cw721::execute::Cw721Execute; + use cw721::msg::{Cw721ExecuteMsg, Cw721InstantiateMsg}; #[entry_point] pub fn instantiate( @@ -31,7 +36,7 @@ pub mod entry { env: Env, info: MessageInfo, msg: InstantiateMsg, - ) -> Result { + ) -> Result { let admin_addr: Option = msg .admin .as_deref() @@ -42,7 +47,7 @@ pub mod entry { CONFIG.save(deps.storage, &config)?; - let cw721_base_instantiate_msg = Cw721BaseInstantiateMsg { + let cw721_base_instantiate_msg = Cw721InstantiateMsg { name: msg.name, symbol: msg.symbol, minter: msg.minter, @@ -54,6 +59,8 @@ pub mod entry { env, info, cw721_base_instantiate_msg, + "contract_name", + "contract_version", )?; cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; @@ -68,28 +75,28 @@ pub mod entry { deps: DepsMut, env: Env, info: MessageInfo, - msg: ExecuteMsg, - ) -> Result { + msg: Cw721ExecuteMsg, + ) -> Result { let config = CONFIG.load(deps.storage)?; match config.admin { Some(admin) => { if admin == info.sender { _execute(deps, env, info, msg) } else { - Err(ContractError::Ownership( + Err(Cw721ContractError::Ownership( cw721_base::OwnershipError::NotOwner, )) } } None => match msg { - ExecuteMsg::Mint { + Cw721ExecuteMsg::Mint { token_id, owner, token_uri, extension, } => Cw721NonTransferableContract::default() .mint(deps, info, token_id, owner, token_uri, extension), - _ => Err(ContractError::Ownership( + _ => Err(Cw721ContractError::Ownership( cw721_base::OwnershipError::NotOwner, )), }, diff --git a/contracts/cw721-non-transferable/src/msg.rs b/contracts/cw721-non-transferable/src/msg.rs index 703a9a7d6..8c8af154c 100644 --- a/contracts/cw721-non-transferable/src/msg.rs +++ b/contracts/cw721-non-transferable/src/msg.rs @@ -1,6 +1,8 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::Empty; -use cw721_base::msg::QueryMsg as Cw721QueryMsg; +// expose to all others using contract, so others dont need to import cw721 +pub use cw721::msg::{Cw721ExecuteMsg as ExecuteMsg, Cw721MigrateMsg as MigrateMsg, *}; +use cw721::state::DefaultOptionMetadataExtension; #[cw_serde] pub struct InstantiateMsg { @@ -14,6 +16,8 @@ pub struct InstantiateMsg { #[cw_serde] pub enum QueryMsg { Admin {}, + + // -- below copied from Cw721QueryMsg OwnerOf { token_id: String, include_expired: Option, @@ -35,6 +39,7 @@ pub enum QueryMsg { }, NumTokens {}, ContractInfo {}, + NftInfo { token_id: String, }, @@ -52,10 +57,12 @@ pub enum QueryMsg { limit: Option, }, Minter {}, + + GetWithdrawAddress {}, } -impl From for Cw721QueryMsg { - fn from(msg: QueryMsg) -> Cw721QueryMsg { +impl From for Cw721QueryMsg { + fn from(msg: QueryMsg) -> Cw721QueryMsg { match msg { QueryMsg::OwnerOf { token_id, @@ -87,7 +94,11 @@ impl From for Cw721QueryMsg { Cw721QueryMsg::AllTokens { start_after, limit } } QueryMsg::Minter {} => Cw721QueryMsg::Minter {}, - _ => unreachable!("cannot convert {:?} to Cw721QueryMsg", msg), + QueryMsg::GetWithdrawAddress {} => Cw721QueryMsg::GetWithdrawAddress {}, + QueryMsg::AllOperators { .. } => unreachable!("AllOperators is not supported!"), + QueryMsg::Approval { .. } => unreachable!("Approval is not supported!"), + QueryMsg::Approvals { .. } => unreachable!("Approvals is not supported!"), + QueryMsg::Admin { .. } => unreachable!("Approvals is not supported!"), } } } diff --git a/contracts/cw721-non-transferable/src/state.rs b/contracts/cw721-non-transferable/src/state.rs index 594bde6e1..13ef71c10 100644 --- a/contracts/cw721-non-transferable/src/state.rs +++ b/contracts/cw721-non-transferable/src/state.rs @@ -2,6 +2,9 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::Addr; use cw_storage_plus::Item; +// expose to all others using contract, so others dont need to import cw721 +pub use cw721::state::*; + #[cw_serde] pub struct Config { pub admin: Option, diff --git a/contracts/cw721-receiver-tester/schema/cw721-receiver-tester.json b/contracts/cw721-receiver-tester/schema/cw721-receiver-tester.json index 639e533cb..a26f45a43 100644 --- a/contracts/cw721-receiver-tester/schema/cw721-receiver-tester.json +++ b/contracts/cw721-receiver-tester/schema/cw721-receiver-tester.json @@ -1,6 +1,6 @@ { "contract_name": "cw721-receiver-tester", - "contract_version": "0.18.0", + "contract_version": "0.19.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/cw721-receiver-tester/src/msg.rs b/contracts/cw721-receiver-tester/src/msg.rs index 137c09046..f6b7cce50 100644 --- a/contracts/cw721-receiver-tester/src/msg.rs +++ b/contracts/cw721-receiver-tester/src/msg.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -use cw721::Cw721ReceiveMsg; +use cw721::receiver::Cw721ReceiveMsg; #[cw_serde] pub struct InstantiateMsg {} diff --git a/packages/cw1155/Cargo.toml b/packages/cw1155/Cargo.toml index a31669731..103a3224a 100644 --- a/packages/cw1155/Cargo.toml +++ b/packages/cw1155/Cargo.toml @@ -2,16 +2,17 @@ name = "cw1155" authors = ["shab "] description = "Definition and types for the CosmWasm-1155 interface" -version = { workspace = true } -edition = { workspace = true } -license = { workspace = true } -repository = { workspace = true } -homepage = { workspace = true } +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } documentation = { workspace = true } [dependencies] cw2 = { workspace = true } cw721 = { workspace = true } +cw-storage-plus = { workspace = true } cw-utils = { workspace = true } cw-ownable = { workspace = true } cosmwasm-std = { workspace = true } diff --git a/packages/cw1155/examples/schema.rs b/packages/cw1155/examples/schema.rs index f29cfdbe1..915ade699 100644 --- a/packages/cw1155/examples/schema.rs +++ b/packages/cw1155/examples/schema.rs @@ -1,14 +1,12 @@ use cosmwasm_schema::write_api; use cosmwasm_std::Empty; - -use cw1155::{Cw1155ExecuteMsg, Cw1155InstantiateMsg, Cw1155QueryMsg}; - -type Extension = Option; +use cw1155::msg::{Cw1155ExecuteMsg, Cw1155InstantiateMsg, Cw1155QueryMsg}; +use cw721::state::DefaultOptionMetadataExtension; fn main() { write_api! { instantiate: Cw1155InstantiateMsg, - execute: Cw1155ExecuteMsg, - query: Cw1155QueryMsg, + execute: Cw1155ExecuteMsg, + query: Cw1155QueryMsg, } } diff --git a/packages/cw1155/src/event.rs b/packages/cw1155/src/event.rs index d5252a722..430808100 100644 --- a/packages/cw1155/src/event.rs +++ b/packages/cw1155/src/event.rs @@ -1,4 +1,4 @@ -use crate::TokenAmount; +use crate::msg::TokenAmount; use cosmwasm_std::{attr, Addr, Attribute, Event, Uint128}; /// Tracks token transfer actions diff --git a/packages/cw1155/src/execute.rs b/packages/cw1155/src/execute.rs new file mode 100644 index 000000000..f47a4a0b6 --- /dev/null +++ b/packages/cw1155/src/execute.rs @@ -0,0 +1,753 @@ +use cosmwasm_std::{ + Addr, BankMsg, Binary, CustomMsg, DepsMut, Empty, Env, Event, MessageInfo, Order, Response, + StdResult, Storage, SubMsg, Uint128, +}; +use cw2::set_contract_version; +use cw721::execute::{migrate_version, Cw721Execute}; +use cw721::state::CollectionInfo; +use cw_utils::Expiration; +use serde::de::DeserializeOwned; +use serde::Serialize; + +use crate::event::{ + ApproveAllEvent, ApproveEvent, BurnEvent, MintEvent, RevokeAllEvent, RevokeEvent, TransferEvent, +}; +use crate::msg::{Balance, Cw1155MintMsg, TokenAmount, TokenApproval}; +use crate::receiver::Cw1155BatchReceiveMsg; +use crate::state::TokenInfo; +use crate::{ + error::Cw1155ContractError, + msg::{Cw1155ExecuteMsg, Cw1155InstantiateMsg}, + receiver::Cw1155ReceiveMsg, + state::Cw1155Config, +}; + +pub trait Cw1155Execute< + // Metadata defined in NftInfo (used for mint). + TMetadataExtension, + // Defines for `CosmosMsg::Custom` in response. Barely used, so `Empty` can be used. + TCustomResponseMessage, + // Message passed for updating metadata. + TMetadataExtensionMsg, + // Extension query message. + TQueryExtensionMsg +>: Cw721Execute< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg,TQueryExtensionMsg> where + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TCustomResponseMessage: CustomMsg, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, +{ + fn instantiate( + &self, + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: Cw1155InstantiateMsg, + contract_name: &str, + contract_version: &str, + ) -> Result, Cw1155ContractError> { + set_contract_version(deps.storage, contract_name, contract_version)?; + let config = Cw1155Config::::default(); + let collection_info = CollectionInfo { + name: msg.name, + symbol: msg.symbol, + }; + config + .collection_info + .save(deps.storage, &collection_info)?; + + // store minter + let minter = match msg.minter { + Some(owner) => deps.api.addr_validate(&owner)?, + None => info.sender, + }; + self.initialize_minter(deps.storage, deps.api, Some(minter.as_ref()))?; + + // store total supply + config.supply.save(deps.storage, &Uint128::zero())?; + + Ok(Response::default().add_attribute("minter", minter)) + } + + fn execute( + &self, + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: Cw1155ExecuteMsg, + ) -> Result, Cw1155ContractError> { + let env = ExecuteEnv { deps, env, info }; + match msg { + // cw1155 + Cw1155ExecuteMsg::SendBatch { + from, + to, + batch, + msg, + } => self.send_batch(env, from, to, batch, msg), + Cw1155ExecuteMsg::MintBatch { recipient, msgs } => { + self.mint_batch(env, recipient, msgs) + } + Cw1155ExecuteMsg::BurnBatch { from, batch } => self.burn_batch(env, from, batch), + Cw1155ExecuteMsg::ApproveAll { operator, expires } => { + self.approve_all_cw1155(env, operator, expires) + } + Cw1155ExecuteMsg::RevokeAll { operator } => self.revoke_all_cw1155(env, operator), + + // cw721 + Cw1155ExecuteMsg::Send { + from, + to, + token_id, + amount, + msg, + } => self.send(env, from, to, token_id, amount, msg), + Cw1155ExecuteMsg::Mint { recipient, msg } => self.mint_cw1155(env, recipient, msg), + Cw1155ExecuteMsg::Burn { + from, + token_id, + amount, + } => self.burn(env, from, token_id, amount), + Cw1155ExecuteMsg::Approve { + spender, + token_id, + amount, + expires, + } => self.approve_token(env, spender, token_id, amount, expires), + Cw1155ExecuteMsg::Revoke { + spender, + token_id, + amount, + } => self.revoke_token(env, spender, token_id, amount), + Cw1155ExecuteMsg::UpdateOwnership(action) => Self::update_ownership(env, action), + + Cw1155ExecuteMsg::Extension { .. } => unimplemented!(), + } + } + + fn migrate( + &self, + deps: DepsMut, + _env: Env, + _msg: Empty, + contract_name: &str, + contract_version: &str, + ) -> Result { + let response = Response::::default(); + // migrate + let response = migrate_version(deps.storage, contract_name, contract_version, response)?; + Ok(response) + } + + fn mint_cw1155( + &self, + env: ExecuteEnv, + recipient: String, + msg: Cw1155MintMsg, + ) -> Result, Cw1155ContractError> { + let ExecuteEnv { + mut deps, + info, + env, + } = env; + let config = Cw1155Config::::default(); + + cw_ownable::assert_owner(deps.storage, &info.sender)?; + + let to = deps.api.addr_validate(&recipient)?; + + let mut rsp = Response::default(); + + let event = self.update_balances( + &mut deps, + &env, + None, + Some(to), + vec![TokenAmount { + token_id: msg.token_id.to_string(), + amount: msg.amount, + }], + )?; + rsp = rsp.add_event(event); + + // store token info if not exist (if it is the first mint) + if !config.tokens.has(deps.storage, &msg.token_id) { + let token_info = TokenInfo { + token_uri: msg.token_uri, + extension: msg.extension, + }; + config + .tokens + .save(deps.storage, &msg.token_id, &token_info)?; + } + + Ok(rsp) + } + + fn mint_batch( + &self, + env: ExecuteEnv, + recipient: String, + msgs: Vec>, + ) -> Result, Cw1155ContractError> { + let ExecuteEnv { + mut deps, + info, + env, + } = env; + let config = Cw1155Config::::default(); + + cw_ownable::assert_owner(deps.storage, &info.sender)?; + + let to = deps.api.addr_validate(&recipient)?; + + let batch = msgs + .iter() + .map(|msg| { + // store token info if not exist (if it is the first mint) + if !config.tokens.has(deps.storage, &msg.token_id) { + let token_info = TokenInfo { + token_uri: msg.token_uri.clone(), + extension: msg.extension.clone(), + }; + config + .tokens + .save(deps.storage, &msg.token_id, &token_info)?; + } + Ok(TokenAmount { + token_id: msg.token_id.to_string(), + amount: msg.amount, + }) + }) + .collect::>>()?; + + let mut rsp = Response::default(); + let event = self.update_balances(&mut deps, &env, None, Some(to), batch)?; + rsp = rsp.add_event(event); + + Ok(rsp) + } + + fn send( + &self, + env: ExecuteEnv, + from: Option, + to: String, + token_id: String, + amount: Uint128, + msg: Option, + ) -> Result, Cw1155ContractError> { + let ExecuteEnv { + mut deps, + env, + info, + } = env; + + let from = if let Some(from) = from { + deps.api.addr_validate(&from)? + } else { + info.sender.clone() + }; + let to = deps.api.addr_validate(&to)?; + + let balance_update = + self.verify_approval(deps.storage, &env, &info, &from, &token_id, amount)?; + + let mut rsp = Response::::default(); + + let event = self.update_balances( + &mut deps, + &env, + Some(from.clone()), + Some(to.clone()), + vec![TokenAmount { + token_id: token_id.to_string(), + amount: balance_update.amount, + }], + )?; + rsp.events.push(event); + + if let Some(msg) = msg { + rsp.messages.push(SubMsg::new( + Cw1155ReceiveMsg { + operator: info.sender.to_string(), + from: Some(from.to_string()), + amount, + token_id, + msg, + } + .into_cosmos_msg(&info, to)?, + )); + } else { + // transfer funds along to recipient + if info.funds.len() > 0 { + let transfer_msg = BankMsg::Send { + to_address: to.to_string(), + amount: info.funds.to_vec(), + }; + rsp.messages.push(SubMsg::new(transfer_msg)); + } + } + + Ok(rsp) + } + + fn send_batch( + &self, + env: ExecuteEnv, + from: Option, + to: String, + batch: Vec, + msg: Option, + ) -> Result, Cw1155ContractError> { + let ExecuteEnv { + mut deps, + env, + info, + } = env; + + let from = if let Some(from) = from { + deps.api.addr_validate(&from)? + } else { + info.sender.clone() + }; + let to = deps.api.addr_validate(&to)?; + + let batch = self.verify_approvals(deps.storage, &env, &info, &from, batch)?; + + let mut rsp = Response::::default(); + let event = self.update_balances( + &mut deps, + &env, + Some(from.clone()), + Some(to.clone()), + batch.to_vec(), + )?; + rsp.events.push(event); + + if let Some(msg) = msg { + rsp.messages.push(SubMsg::new( + Cw1155BatchReceiveMsg { + operator: info.sender.to_string(), + from: Some(from.to_string()), + batch, + msg, + } + .into_cosmos_msg(&info, to)?, + )); + } else { + // transfer funds along to recipient + if info.funds.len() > 0 { + let transfer_msg = BankMsg::Send { + to_address: to.to_string(), + amount: info.funds.to_vec(), + }; + rsp.messages.push(SubMsg::new(transfer_msg)); + } + } + + Ok(rsp) + } + + fn burn( + &self, + env: ExecuteEnv, + from: Option, + token_id: String, + amount: Uint128, + ) -> Result, Cw1155ContractError> { + let ExecuteEnv { + mut deps, + info, + env, + } = env; + + let from = if let Some(from) = from { + deps.api.addr_validate(&from)? + } else { + info.sender.clone() + }; + + // whoever can transfer these tokens can burn + let balance_update = + self.verify_approval(deps.storage, &env, &info, &from, &token_id, amount)?; + + let mut rsp = Response::default(); + + let event = self.update_balances( + &mut deps, + &env, + Some(from), + None, + vec![TokenAmount { + token_id, + amount: balance_update.amount, + }], + )?; + rsp = rsp.add_event(event); + + Ok(rsp) + } + + fn burn_batch( + &self, + env: ExecuteEnv, + from: Option, + batch: Vec, + ) -> Result, Cw1155ContractError> { + let ExecuteEnv { + mut deps, + info, + env, + } = env; + + let from = if let Some(from) = from { + deps.api.addr_validate(&from)? + } else { + info.sender.clone() + }; + + let batch = self.verify_approvals(deps.storage, &env, &info, &from, batch)?; + + let mut rsp = Response::default(); + let event = self.update_balances(&mut deps, &env, Some(from), None, batch)?; + rsp = rsp.add_event(event); + + Ok(rsp) + } + + fn approve_token( + &self, + env: ExecuteEnv, + operator: String, + token_id: String, + amount: Option, + expiration: Option, + ) -> Result, Cw1155ContractError> { + let ExecuteEnv { deps, info, env } = env; + let config = Cw1155Config::::default(); + + // reject expired data as invalid + let expiration = expiration.unwrap_or_default(); + if expiration.is_expired(&env.block) { + return Err(Cw1155ContractError::Expired {}); + } + + // get sender's token balance to get valid approval amount + let balance = config + .balances + .load(deps.storage, (info.sender.clone(), token_id.to_string()))?; + let approval_amount = amount.unwrap_or(Uint128::MAX).min(balance.amount); + + // store the approval + let operator = deps.api.addr_validate(&operator)?; + config.token_approves.save( + deps.storage, + (&token_id, &info.sender, &operator), + &TokenApproval { + amount: approval_amount, + expiration, + }, + )?; + + let mut rsp = Response::default(); + + let event = ApproveEvent::new(&info.sender, &operator, &token_id, approval_amount).into(); + rsp = rsp.add_event(event); + + Ok(rsp) + } + + fn approve_all_cw1155( + &self, + env: ExecuteEnv, + operator: String, + expires: Option, + ) -> Result, Cw1155ContractError> { + let ExecuteEnv { deps, info, env } = env; + let config = Cw1155Config::::default(); + + // reject expired data as invalid + let expires = expires.unwrap_or_default(); + if expires.is_expired(&env.block) { + return Err(Cw1155ContractError::Expired {}); + } + + // set the operator for us + let operator = deps.api.addr_validate(&operator)?; + config + .approves + .save(deps.storage, (&info.sender, &operator), &expires)?; + + let mut rsp = Response::default(); + + let event = ApproveAllEvent::new(&info.sender, &operator).into(); + rsp = rsp.add_event(event); + + Ok(rsp) + } + + fn revoke_token( + &self, + env: ExecuteEnv, + operator: String, + token_id: String, + amount: Option, + ) -> Result, Cw1155ContractError> { + let ExecuteEnv { deps, info, .. } = env; + let config = Cw1155Config::::default(); + let operator = deps.api.addr_validate(&operator)?; + + // get prev approval amount to get valid revoke amount + let prev_approval = config + .token_approves + .load(deps.storage, (&token_id, &info.sender, &operator))?; + let revoke_amount = amount.unwrap_or(Uint128::MAX).min(prev_approval.amount); + + // remove or update approval + if revoke_amount == prev_approval.amount { + config + .token_approves + .remove(deps.storage, (&token_id, &info.sender, &operator)); + } else { + config.token_approves.update( + deps.storage, + (&token_id, &info.sender, &operator), + |prev| -> StdResult<_> { + let mut new_approval = prev.unwrap(); + new_approval.amount = new_approval.amount.checked_sub(revoke_amount)?; + Ok(new_approval) + }, + )?; + } + + let mut rsp = Response::default(); + + let event = RevokeEvent::new(&info.sender, &operator, &token_id, revoke_amount).into(); + rsp = rsp.add_event(event); + + Ok(rsp) + } + + fn revoke_all_cw1155( + &self, + env: ExecuteEnv, + operator: String, + ) -> Result, Cw1155ContractError> { + let ExecuteEnv { deps, info, .. } = env; + let config = Cw1155Config::::default(); + let operator = deps.api.addr_validate(&operator)?; + + config + .approves + .remove(deps.storage, (&info.sender, &operator)); + + let mut rsp = Response::default(); + + let event = RevokeAllEvent::new(&info.sender, &operator).into(); + rsp = rsp.add_event(event); + + Ok(rsp) + } + + /// When from is None: mint new tokens + /// When to is None: burn tokens + /// When both are Some: transfer tokens + /// + /// Make sure permissions are checked before calling this. + fn update_balances( + &self, + deps: &mut DepsMut, + env: &Env, + from: Option, + to: Option, + tokens: Vec, + ) -> Result { + let config = Cw1155Config::::default(); + if let Some(from) = &from { + for TokenAmount { token_id, amount } in tokens.iter() { + config.balances.update( + deps.storage, + (from.clone(), token_id.to_string()), + |balance: Option| -> StdResult<_> { + let mut new_balance = balance.unwrap(); + new_balance.amount = new_balance.amount.checked_sub(*amount)?; + Ok(new_balance) + }, + )?; + } + } + + if let Some(to) = &to { + for TokenAmount { token_id, amount } in tokens.iter() { + config.balances.update( + deps.storage, + (to.clone(), token_id.to_string()), + |balance: Option| -> StdResult<_> { + let mut new_balance: Balance = if let Some(balance) = balance { + balance + } else { + Balance { + owner: to.clone(), + amount: Uint128::zero(), + token_id: token_id.to_string(), + } + }; + + new_balance.amount = new_balance.amount.checked_add(*amount)?; + Ok(new_balance) + }, + )?; + } + } + + let event = if let Some(from) = &from { + for TokenAmount { token_id, amount } in &tokens { + // remove token approvals + for (operator, approval) in config + .token_approves + .prefix((token_id, from)) + .range(deps.storage, None, None, Order::Ascending) + .collect::>>()? + { + if approval.is_expired(env) || approval.amount <= *amount { + config + .token_approves + .remove(deps.storage, (token_id, from, &operator)); + } else { + config.token_approves.update( + deps.storage, + (token_id, from, &operator), + |prev| -> StdResult<_> { + let mut new_approval = prev.unwrap(); + new_approval.amount = new_approval.amount.checked_sub(*amount)?; + Ok(new_approval) + }, + )?; + } + } + + // decrement tokens if burning + if to.is_none() { + config.decrement_tokens(deps.storage, token_id, amount)?; + } + } + + if let Some(to) = &to { + // transfer + TransferEvent::new(from, to, tokens).into() + } else { + // burn + BurnEvent::new(from, tokens).into() + } + } else if let Some(to) = &to { + // mint + for TokenAmount { token_id, amount } in &tokens { + config.increment_tokens(deps.storage, token_id, amount)?; + } + MintEvent::new(to, tokens).into() + } else { + panic!("Invalid transfer: from and to cannot both be None") + }; + + Ok(event) + } + + /// returns valid token amount if the sender can execute or is approved to execute + fn verify_approval( + &self, + storage: &dyn Storage, + env: &Env, + info: &MessageInfo, + owner: &Addr, + token_id: &str, + amount: Uint128, + ) -> Result { + let config = Cw1155Config::::default(); + let operator = &info.sender; + + let owner_balance = config + .balances + .load(storage, (owner.clone(), token_id.to_string()))?; + let mut balance_update = TokenAmount { + token_id: token_id.to_string(), + amount: owner_balance.amount.min(amount), + }; + + // owner or all operator can execute + if owner == operator || config.verify_all_approval(storage, env, owner, operator) { + return Ok(balance_update); + } + + // token operator can execute up to approved amount + if let Some(token_approval) = + self.get_active_token_approval(storage, env, owner, operator, token_id) + { + balance_update.amount = balance_update.amount.min(token_approval.amount); + return Ok(balance_update); + } + + Err(Cw1155ContractError::Unauthorized {}) + } + + /// returns valid token amounts if the sender can execute or is approved to execute on all provided tokens + fn verify_approvals( + &self, + storage: &dyn Storage, + env: &Env, + info: &MessageInfo, + owner: &Addr, + tokens: Vec, + ) -> Result, Cw1155ContractError> { + tokens + .iter() + .map(|TokenAmount { token_id, amount }| { + self.verify_approval(storage, env, info, owner, token_id, *amount) + }) + .collect() + } + + fn get_active_token_approval( + &self, + storage: &dyn Storage, + env: &Env, + owner: &Addr, + operator: &Addr, + token_id: &str, + ) -> Option { + let config = Cw1155Config::::default(); + match config + .token_approves + .load(storage, (token_id, owner, operator)) + { + Ok(approval) => { + if !approval.is_expired(env) { + Some(approval) + } else { + None + } + } + Err(_) => None, + } + } + + fn update_ownership( + env: ExecuteEnv, + action: cw_ownable::Action, + ) -> Result, Cw1155ContractError> { + let ExecuteEnv { deps, info, env } = env; + let ownership = + cw_ownable::update_ownership(deps.api, deps.storage, &env.block, &info.sender, action)?; + Ok(Response::new().add_attributes(ownership.into_attributes())) + } +} + +/// To mitigate clippy::too_many_arguments warning +pub struct ExecuteEnv<'a> { + deps: DepsMut<'a>, + env: Env, + info: MessageInfo, +} diff --git a/packages/cw1155/src/lib.rs b/packages/cw1155/src/lib.rs index 41a928d3b..18183c764 100644 --- a/packages/cw1155/src/lib.rs +++ b/packages/cw1155/src/lib.rs @@ -1,21 +1,9 @@ -mod error; -mod event; -mod msg; -mod query; -mod receiver; +pub mod error; +pub mod event; +pub mod execute; +pub mod msg; +pub mod query; +pub mod receiver; +pub mod state; pub use cw_utils::Expiration; - -pub use crate::receiver::{Cw1155BatchReceiveMsg, Cw1155ReceiveMsg}; - -pub use crate::msg::{ - Approval, Balance, Cw1155ExecuteMsg, Cw1155InstantiateMsg, Cw1155MintMsg, OwnerToken, - TokenAmount, TokenApproval, -}; -pub use crate::query::{ - AllTokenInfoResponse, ApprovedForAllResponse, BalanceResponse, BalancesResponse, - Cw1155QueryMsg, IsApprovedForAllResponse, NumTokensResponse, TokenInfoResponse, TokensResponse, -}; - -pub use crate::error::Cw1155ContractError; -pub use crate::event::*; diff --git a/packages/cw1155/src/msg.rs b/packages/cw1155/src/msg.rs index c6a0dd8b3..2630f035c 100644 --- a/packages/cw1155/src/msg.rs +++ b/packages/cw1155/src/msg.rs @@ -1,8 +1,9 @@ -use cosmwasm_schema::cw_serde; +use cosmwasm_schema::{cw_serde, QueryResponses}; use std::fmt::{Display, Formatter}; use cosmwasm_std::{Addr, Binary, Env, Uint128}; -use cw_ownable::cw_ownable_execute; +use cw721::Approval; +use cw_ownable::{cw_ownable_execute, cw_ownable_query}; use cw_utils::Expiration; #[cw_serde] @@ -24,7 +25,7 @@ pub struct Cw1155InstantiateMsg { /// use other control logic in any contract that inherits this. #[cw_ownable_execute] #[cw_serde] -pub enum Cw1155ExecuteMsg { +pub enum Cw1155ExecuteMsg { // cw1155 /// BatchSendFrom is a base message to move multiple types of tokens in batch, /// if `env.sender` is the owner or has sufficient pre-approval. @@ -40,7 +41,7 @@ pub enum Cw1155ExecuteMsg { /// Mint a batch of tokens, can only be called by the contract minter MintBatch { recipient: String, - msgs: Vec>, + msgs: Vec>, }, /// BatchBurn is a base message to burn multiple types of tokens in batch. BurnBatch { @@ -73,7 +74,7 @@ pub enum Cw1155ExecuteMsg { /// Mint a new NFT, can only be called by the contract minter Mint { recipient: String, - msg: Cw1155MintMsg, + msg: Cw1155MintMsg, }, /// Burn is a base message to burn tokens. Burn { @@ -100,7 +101,125 @@ pub enum Cw1155ExecuteMsg { }, /// Extension msg - Extension { msg: E }, + Extension { msg: TMetadataExtensionMsg }, +} + +#[cw_ownable_query] +#[cw_serde] +#[derive(QueryResponses)] +pub enum Cw1155QueryMsg { + // cw1155 + /// Returns the current balance of the given account, 0 if unset. + #[returns(BalanceResponse)] + BalanceOf(OwnerToken), + /// Returns the current balance of the given batch of accounts/tokens, 0 if unset. + #[returns(BalancesResponse)] + BalanceOfBatch(Vec), + /// Query approved status `owner` granted to `operator`. + #[returns(IsApprovedForAllResponse)] + IsApprovedForAll { owner: String, operator: String }, + /// Return approvals that a token owner has + #[returns(Vec)] + TokenApprovals { + owner: String, + token_id: String, + include_expired: Option, + }, + /// List all operators that can access all of the owner's tokens. + #[returns(ApprovedForAllResponse)] + ApprovalsForAll { + owner: String, + /// unset or false will filter out expired approvals, you must set to true to see them + include_expired: Option, + start_after: Option, + limit: Option, + }, + /// Returns all current balances of the given token id. Supports pagination + #[returns(BalancesResponse)] + AllBalances { + token_id: String, + start_after: Option, + limit: Option, + }, + /// Total number of tokens issued + #[returns(NumTokensResponse)] + NumTokens { + token_id: Option, // optional token id to get supply of, otherwise total supply + }, + + // cw721 + /// With MetaData Extension. + /// Returns top-level metadata about the contract. + #[returns(cw721::state::CollectionInfo)] + ContractInfo {}, + /// Query Minter. + #[returns(cw721::msg::MinterResponse)] + Minter {}, + /// With MetaData Extension. + /// Query metadata of token + #[returns(TokenInfoResponse)] + TokenInfo { token_id: String }, + /// With Enumerable extension. + /// Returns all tokens owned by the given address, [] if unset. + #[returns(cw721::msg::TokensResponse)] + Tokens { + owner: String, + start_after: Option, + limit: Option, + }, + /// With Enumerable extension. + /// Requires pagination. Lists all token_ids controlled by the contract. + #[returns(cw721::msg::TokensResponse)] + AllTokens { + start_after: Option, + limit: Option, + }, + + /// Extension query + #[returns(())] + Extension { + msg: TQueryExtensionMsg, + phantom: Option, // dummy field to infer type + }, +} + +#[cw_serde] +pub struct BalanceResponse { + pub balance: Uint128, +} + +#[cw_serde] +pub struct BalancesResponse { + pub balances: Vec, +} + +#[cw_serde] +pub struct NumTokensResponse { + pub count: Uint128, +} + +#[cw_serde] +pub struct ApprovedForAllResponse { + pub operators: Vec, +} + +#[cw_serde] +pub struct IsApprovedForAllResponse { + pub approved: bool, +} + +#[cw_serde] +pub struct AllTokenInfoResponse { + pub token_id: String, + pub info: TokenInfoResponse, +} + +#[cw_serde] +pub struct TokenInfoResponse { + /// Should be a url point to a json file + pub token_uri: Option, + /// You can add any custom metadata here when you extend cw1155-base + pub extension: T, } #[cw_serde] @@ -153,11 +272,3 @@ pub struct Balance { pub owner: Addr, pub amount: Uint128, } - -#[cw_serde] -pub struct Approval { - /// Account that can transfer/send the token - pub spender: String, - /// When the Approval expires (maybe Expiration::never) - pub expires: Expiration, -} diff --git a/packages/cw1155/src/query.rs b/packages/cw1155/src/query.rs index f13ff9db1..f0113c34b 100644 --- a/packages/cw1155/src/query.rs +++ b/packages/cw1155/src/query.rs @@ -1,129 +1,272 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use schemars::JsonSchema; +use cosmwasm_std::{to_json_binary, Addr, Binary, CustomMsg, Deps, Env, Order, StdResult, Uint128}; +use cw721::msg::TokensResponse; +use cw721::query::Cw721Query; +use cw721::Approval; +use cw_storage_plus::Bound; +use cw_utils::{maybe_addr, Expiration}; +use serde::de::DeserializeOwned; +use serde::Serialize; -use crate::{Approval, Balance, OwnerToken}; -use cosmwasm_std::Uint128; -use cw_ownable::cw_ownable_query; +use crate::msg::{ + ApprovedForAllResponse, Balance, BalanceResponse, BalancesResponse, Cw1155QueryMsg, + IsApprovedForAllResponse, OwnerToken, +}; +use crate::msg::{NumTokensResponse, TokenInfoResponse}; +use crate::state::Cw1155Config; -#[cw_ownable_query] -#[cw_serde] -#[derive(QueryResponses)] -pub enum Cw1155QueryMsg { - // cw1155 - /// Returns the current balance of the given account, 0 if unset. - #[returns(BalanceResponse)] - BalanceOf(OwnerToken), - /// Returns the current balance of the given batch of accounts/tokens, 0 if unset. - #[returns(BalancesResponse)] - BalanceOfBatch(Vec), - /// Query approved status `owner` granted to `operator`. - #[returns(IsApprovedForAllResponse)] - IsApprovedForAll { owner: String, operator: String }, - /// Return approvals that a token owner has - #[returns(Vec)] - TokenApprovals { - owner: String, - token_id: String, - include_expired: Option, - }, - /// List all operators that can access all of the owner's tokens. - #[returns(ApprovedForAllResponse)] - ApprovalsForAll { - owner: String, - /// unset or false will filter out expired approvals, you must set to true to see them - include_expired: Option, - start_after: Option, +pub const DEFAULT_LIMIT: u32 = 10; +pub const MAX_LIMIT: u32 = 1000; + +pub trait Cw1155Query< + // Metadata defined in NftInfo. + TMetadataExtension, + // Defines for `CosmosMsg::Custom` in response. Barely used, so `Empty` can be used. + TCustomResponseMessage, + // Message passed for updating metadata. + TMetadataExtensionMsg, + // Extension query message. + TQueryExtensionMsg +>: Cw721Query where + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TCustomResponseMessage: CustomMsg, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, +{ + fn query( + &self, + deps: Deps, + env: Env, + msg: Cw1155QueryMsg, + ) -> StdResult { + match msg { + Cw1155QueryMsg::Minter {} => { + to_json_binary(&self.query_minter(deps.storage)?) + } + Cw1155QueryMsg::BalanceOf(OwnerToken { owner, token_id }) => { + let config = Cw1155Config::::default(); + let owner_addr = deps.api.addr_validate(&owner)?; + let balance = config + .balances + .may_load(deps.storage, (owner_addr.clone(), token_id.clone()))? + .unwrap_or(Balance { + owner: owner_addr, + token_id, + amount: Uint128::new(0), + }); + to_json_binary(&BalanceResponse { + balance: balance.amount, + }) + } + Cw1155QueryMsg::AllBalances { + token_id, + start_after, + limit, + } => to_json_binary(&self.query_all_balances(deps, token_id, start_after, limit)?), + Cw1155QueryMsg::BalanceOfBatch(batch) => { + let config = Cw1155Config::::default(); + let balances = batch + .into_iter() + .map(|OwnerToken { owner, token_id }| { + let owner = Addr::unchecked(owner); + config.balances + .load(deps.storage, (owner.clone(), token_id.to_string())) + .unwrap_or(Balance { + owner, + token_id, + amount: Uint128::zero(), + }) + }) + .collect::>(); + to_json_binary(&BalancesResponse { balances }) + } + Cw1155QueryMsg::TokenApprovals { + owner, + token_id, + include_expired, + } => { + let config = Cw1155Config::::default(); + let owner = deps.api.addr_validate(&owner)?; + let approvals = config + .token_approves + .prefix((&token_id, &owner)) + .range(deps.storage, None, None, Order::Ascending) + .filter_map(|approval| { + let (_, approval) = approval.unwrap(); + if include_expired.unwrap_or(false) || !approval.is_expired(&env) { + Some(approval) + } else { + None + } + }) + .collect::>(); + to_json_binary(&approvals) + } + Cw1155QueryMsg::ApprovalsForAll { + owner, + include_expired, + start_after, + limit, + } => { + let owner_addr = deps.api.addr_validate(&owner)?; + let start_addr = maybe_addr(deps.api, start_after)?; + to_json_binary(&self.query_all_approvals( + deps, + env, + owner_addr, + include_expired.unwrap_or(false), + start_addr, + limit, + )?) + } + Cw1155QueryMsg::IsApprovedForAll { owner, operator } => { + let config = Cw1155Config::::default(); + let owner_addr = deps.api.addr_validate(&owner)?; + let operator_addr = deps.api.addr_validate(&operator)?; + let approved = + config.verify_all_approval(deps.storage, &env, &owner_addr, &operator_addr); + to_json_binary(&IsApprovedForAllResponse { approved }) + } + Cw1155QueryMsg::TokenInfo { token_id } => { + let config = Cw1155Config::::default(); + let token_info = config.tokens.load(deps.storage, &token_id)?; + to_json_binary(&TokenInfoResponse:: { + token_uri: token_info.token_uri, + extension: token_info.extension, + }) + } + Cw1155QueryMsg::Tokens { + owner, + start_after, + limit, + } => { + let owner_addr = deps.api.addr_validate(&owner)?; + to_json_binary(&self.query_owner_tokens(deps, owner_addr, start_after, limit)?) + } + Cw1155QueryMsg::ContractInfo {} => { + to_json_binary(&self.query_collection_info(deps, env)?) + } + Cw1155QueryMsg::NumTokens { token_id } => { + let config = Cw1155Config::::default(); + let count = if let Some(token_id) = token_id { + config.token_count(deps.storage, &token_id)? + } else { + config.supply.load(deps.storage)? + }; + to_json_binary(&NumTokensResponse { count }) + } + Cw1155QueryMsg::AllTokens { start_after, limit } => { + to_json_binary(&self.query_all_tokens_cw1155(deps, start_after, limit)?) + } + Cw1155QueryMsg::Ownership {} => { + to_json_binary(&cw_ownable::get_ownership(deps.storage)?) + } + + Cw1155QueryMsg::Extension { msg: ext_msg, .. } => { + self.query_extension(deps, env, ext_msg) + } + } + } + + fn query_all_approvals( + &self, + deps: Deps, + env: Env, + owner: Addr, + include_expired: bool, + start_after: Option, limit: Option, - }, - /// Returns all current balances of the given token id. Supports pagination - #[returns(BalancesResponse)] - AllBalances { - token_id: String, + ) -> StdResult { + let config = Cw1155Config::::default(); + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + let start = start_after.as_ref().map(Bound::exclusive); + + let operators = config + .approves + .prefix(&owner) + .range(deps.storage, start, None, Order::Ascending) + .filter(|r| { + include_expired || r.is_err() || !r.as_ref().unwrap().1.is_expired(&env.block) + }) + .take(limit) + .map(build_approval) + .collect::>()?; + Ok(ApprovedForAllResponse { operators }) + } + + fn query_owner_tokens( + &self, + deps: Deps, + owner: Addr, start_after: Option, limit: Option, - }, - /// Total number of tokens issued - #[returns(cw721::NumTokensResponse)] - NumTokens { - token_id: Option, // optional token id to get supply of, otherwise total supply - }, + ) -> StdResult { + let config = Cw1155Config::::default(); + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + let start = start_after.as_ref().map(|s| Bound::exclusive(s.as_str())); + + let tokens = config + .balances + .prefix(owner) + .keys(deps.storage, start, None, Order::Ascending) + .take(limit) + .collect::>()?; + Ok(TokensResponse { tokens }) + } - // cw721 - /// With MetaData Extension. - /// Returns top-level metadata about the contract. - #[returns(cw721::ContractInfoResponse)] - ContractInfo {}, - /// Query Minter. - #[returns(cw721::MinterResponse)] - Minter {}, - /// With MetaData Extension. - /// Query metadata of token - #[returns(TokenInfoResponse)] - TokenInfo { token_id: String }, - /// With Enumerable extension. - /// Returns all tokens owned by the given address, [] if unset. - #[returns(TokensResponse)] - Tokens { - owner: String, + fn query_all_tokens_cw1155( + &self, + deps: Deps, start_after: Option, limit: Option, - }, - /// With Enumerable extension. - /// Requires pagination. Lists all token_ids controlled by the contract. - #[returns(TokensResponse)] - AllTokens { + ) -> StdResult { + let config = Cw1155Config::::default(); + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + let start = start_after.as_ref().map(|s| Bound::exclusive(s.as_str())); + let tokens = config + .tokens + .keys(deps.storage, start, None, Order::Ascending) + .take(limit) + .collect::>()?; + Ok(TokensResponse { tokens }) + } + + fn query_all_balances( + &self, + deps: Deps, + token_id: String, start_after: Option, limit: Option, - }, + ) -> StdResult { + let config = Cw1155Config::::default(); + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - /// Extension query - #[returns(())] - Extension { msg: Q }, -} + let start = if let Some(start_after) = start_after { + let start_key = (Addr::unchecked(start_after), token_id.clone()); + Some(Bound::exclusive::<(Addr, String)>(start_key)) + } else { + None + }; -#[cw_serde] -pub struct BalanceResponse { - pub balance: Uint128, -} - -#[cw_serde] -pub struct BalancesResponse { - pub balances: Vec, -} - -#[cw_serde] -pub struct NumTokensResponse { - pub count: Uint128, -} - -#[cw_serde] -pub struct ApprovedForAllResponse { - pub operators: Vec, -} - -#[cw_serde] -pub struct IsApprovedForAllResponse { - pub approved: bool, -} - -#[cw_serde] -pub struct AllTokenInfoResponse { - pub token_id: String, - pub info: TokenInfoResponse, -} + let balances: Vec = config + .balances + .idx + .token_id + .prefix(token_id) + .range(deps.storage, start, None, Order::Ascending) + .take(limit) + .map(|item| { + let (_, v) = item.unwrap(); + v + }) + .collect(); -#[cw_serde] -pub struct TokenInfoResponse { - /// Should be a url point to a json file - pub token_uri: Option, - /// You can add any custom metadata here when you extend cw1155-base - pub extension: T, + Ok(BalancesResponse { balances }) + } } -#[cw_serde] -pub struct TokensResponse { - /// Contains all token_ids in lexicographical ordering - /// If there are more than `limit`, use `start_from` in future queries - /// to achieve pagination. - pub tokens: Vec, +fn build_approval(item: StdResult<(Addr, Expiration)>) -> StdResult { + item.map(|(addr, expires)| Approval { + spender: addr.into(), + expires, + }) } diff --git a/packages/cw1155/src/receiver.rs b/packages/cw1155/src/receiver.rs index b1efe4e33..090fb8e45 100644 --- a/packages/cw1155/src/receiver.rs +++ b/packages/cw1155/src/receiver.rs @@ -1,4 +1,4 @@ -use crate::TokenAmount; +use crate::msg::TokenAmount; use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_json_binary, Binary, CosmosMsg, MessageInfo, StdResult, Uint128, WasmMsg}; use schemars::JsonSchema; diff --git a/packages/cw1155/src/state.rs b/packages/cw1155/src/state.rs new file mode 100644 index 000000000..8d4be7259 --- /dev/null +++ b/packages/cw1155/src/state.rs @@ -0,0 +1,190 @@ +use crate::msg::{Balance, TokenApproval}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, CustomMsg, Env, StdError, StdResult, Storage, Uint128}; +use cw721::state::CollectionInfo; +use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, MultiIndex}; +use cw_utils::Expiration; +use serde::de::DeserializeOwned; +use serde::Serialize; +use std::marker::PhantomData; + +pub struct Cw1155Config< + 'a, + // Metadata defined in NftInfo (used for mint). + TMetadataExtension, + // Defines for `CosmosMsg::Custom` in response. Barely used, so `Empty` can be used. + TCustomResponseMessage, + // Message passed for updating metadata. + TMetadataExtensionMsg, + // Extension query message. + TQueryExtensionMsg, +> where + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, +{ + pub collection_info: Item<'a, CollectionInfo>, + pub supply: Item<'a, Uint128>, // total supply of all tokens + // key: token id + pub token_count: Map<'a, &'a str, Uint128>, // total supply of a specific token + // key: (owner, token id) + pub balances: IndexedMap<'a, (Addr, String), Balance, BalanceIndexes<'a>>, + // key: (owner, spender) + pub approves: Map<'a, (&'a Addr, &'a Addr), Expiration>, + // key: (token id, owner, spender) + pub token_approves: Map<'a, (&'a str, &'a Addr, &'a Addr), TokenApproval>, + // key: token id + pub tokens: Map<'a, &'a str, TokenInfo>, + + pub(crate) _custom_response: PhantomData, + pub(crate) _custom_execute: PhantomData, + pub(crate) _custom_query: PhantomData, +} + +impl Default + for Cw1155Config< + 'static, + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + > +where + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, +{ + fn default() -> Self { + Self::new( + "cw1155_contract_info", + "tokens", + "token_count", + "supply", + "balances", + "balances__token_id", + "approves", + "token_approves", + ) + } +} + +impl<'a, TMetadataExtension, TCustomResponseMessage, TMetadataExtensionMsg, TQueryExtensionMsg> + Cw1155Config< + 'a, + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + > +where + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, +{ + #[allow(clippy::too_many_arguments)] + fn new( + contract_info_key: &'a str, + tokens_key: &'a str, + token_count_key: &'a str, + supply_key: &'a str, + balances_key: &'a str, + balances_token_id_key: &'a str, + approves_key: &'a str, + token_approves_key: &'a str, + ) -> Self { + let balances_indexes = BalanceIndexes { + token_id: MultiIndex::new( + |_, b| b.token_id.to_string(), + balances_key, + balances_token_id_key, + ), + }; + Self { + collection_info: Item::new(contract_info_key), + tokens: Map::new(tokens_key), + token_count: Map::new(token_count_key), + supply: Item::new(supply_key), + balances: IndexedMap::new(balances_key, balances_indexes), + approves: Map::new(approves_key), + token_approves: Map::new(token_approves_key), + _custom_execute: PhantomData, + _custom_response: PhantomData, + _custom_query: PhantomData, + } + } + + pub fn token_count(&self, storage: &dyn Storage, token_id: &'a str) -> StdResult { + Ok(self + .token_count + .may_load(storage, token_id)? + .unwrap_or_default()) + } + + pub fn increment_tokens( + &self, + storage: &mut dyn Storage, + token_id: &'a str, + amount: &Uint128, + ) -> StdResult { + // increment token count + let val = self.token_count(storage, token_id)? + amount; + self.token_count.save(storage, token_id, &val)?; + + // increment total supply + self.supply.update(storage, |prev| { + Ok::(prev.checked_add(*amount)?) + })?; + + Ok(val) + } + + pub fn decrement_tokens( + &self, + storage: &mut dyn Storage, + token_id: &'a str, + amount: &Uint128, + ) -> StdResult { + // decrement token count + let val = self.token_count(storage, token_id)?.checked_sub(*amount)?; + self.token_count.save(storage, token_id, &val)?; + + // decrement total supply + self.supply.update(storage, |prev| { + Ok::(prev.checked_sub(*amount)?) + })?; + + Ok(val) + } + + pub fn verify_all_approval( + &self, + storage: &dyn Storage, + env: &Env, + owner: &Addr, + operator: &Addr, + ) -> bool { + match self.approves.load(storage, (owner, operator)) { + Ok(ex) => !ex.is_expired(&env.block), + Err(_) => false, + } + } +} + +#[cw_serde] +pub struct TokenInfo { + /// Metadata JSON Schema + pub token_uri: Option, + /// You can add any custom metadata here when you extend cw1155-base + pub extension: T, +} + +pub struct BalanceIndexes<'a> { + pub token_id: MultiIndex<'a, String, Balance, (Addr, String)>, +} + +impl<'a> IndexList for BalanceIndexes<'a> { + fn get_indexes(&'_ self) -> Box> + '_> { + let v: Vec<&dyn Index> = vec![&self.token_id]; + Box::new(v.into_iter()) + } +} diff --git a/packages/cw721/Cargo.toml b/packages/cw721/Cargo.toml index 0f5967cff..e6f4775cc 100644 --- a/packages/cw721/Cargo.toml +++ b/packages/cw721/Cargo.toml @@ -15,6 +15,17 @@ documentation = { workspace = true } [dependencies] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } +cw-ownable = { workspace = true } +cw-storage-plus = { workspace = true } cw-utils = { workspace = true } +cw2 = { workspace = true } +cw721-016 = { workspace = true } schemars = { workspace = true } serde = { workspace = true } +thiserror = { workspace = true } + +[dev-dependencies] +cw-multi-test = { workspace = true } +cw721-base-016 = { workspace = true, features = ["library"] } +cw721-base-017 = { workspace = true, features = ["library"] } +cw721-base-018 = { workspace = true, features = ["library"] } diff --git a/packages/cw721/README.md b/packages/cw721/README.md index 883809f04..b9e0ee991 100644 --- a/packages/cw721/README.md +++ b/packages/cw721/README.md @@ -1,42 +1,58 @@ # CW721 Spec: Non Fungible Tokens -CW721 is a specification for non-fungible tokens based on CosmWasm. -The name and design is based on Ethereum's ERC721 standard, -with some enhancements. The types in here can be imported by -contracts that wish to implement this spec, or by contracts that call +CW721 is a specification for non-fungible tokens (NFTs) based on CosmWasm. +The name and design is based on Ethereum's [ERC721](https://eips.ethereum.org/EIPS/eip-721) standard, +with some enhancements. The types in here can be imported by +contracts that wish to implement this spec, or by contracts that call to any standard cw721 contract. The specification is split into multiple sections, a contract may only implement some of this functionality, but must implement the base. -## Base +## `cw721` package + +The CW721 package provides 2 traits with default implementations (aka `utilities`): + +- `Cw721Execute` with `Cw721ExecuteMsg` e.g. for minting, burning, + sending, approving, and transferring NFTs. It also allows updating + collection info, withdraw address, creator and minter ownership. +- `Cw721Query` with `Cw721QueryMsg` e.g. for NFTs, tokens, approvals, various kinds of ownerships (creator and minter). + +Default implementations are opionated and uses a `Cw721Config` store. Custom cw721 +contracts may re-implement each utlitiy to their own need. + +### `cw721-base` This handles ownership, transfers, and allowances. These must be supported -as is by all CW721 contracts. Note that all tokens must have an owner, +as is by all CW721 contracts. Note that all tokens must have an owner, as well as an ID. The ID is an arbitrary string, unique within the contract. +`cw721-base` contract is the base contract for handling NFT collections with +either offchain (stored in `token_uri`) or onchain (stored in `NftInfo`'s extension') +metadata. Contract itself is lightweight, since all logic is provided in `cw721` package. + ### Messages -`TransferNft{recipient, token_id}` - -This transfers ownership of the token to `recipient` account. This is -designed to send to an address controlled by a private key and *does not* +`TransferNft{recipient, token_id}` - +This transfers ownership of the token to `recipient` account. This is +designed to send to an address controlled by a private key and _does not_ trigger any actions on the recipient if it is a contract. -Requires `token_id` to point to a valid token, and `env.sender` to be -the owner of it, or have an allowance to transfer it. +Requires `token_id` to point to a valid token, and `env.sender` to be +the owner of it, or have an allowance to transfer it. -`SendNft{contract, token_id, msg}` - -This transfers ownership of the token to `contract` account. `contract` +`SendNft{contract, token_id, msg}` - +This transfers ownership of the token to `contract` account. `contract` must be an address controlled by a smart contract, which implements -the CW721Receiver interface. The `msg` will be passed to the recipient +the CW721Receiver interface. The `msg` will be passed to the recipient contract, along with the token_id. -Requires `token_id` to point to a valid token, and `env.sender` to be -the owner of it, or have an allowance to transfer it. +Requires `token_id` to point to a valid token, and `env.sender` to be +the owner of it, or have an allowance to transfer it. `Approve{spender, token_id, expires}` - Grants permission to `spender` to transfer or send the given token. This can only be performed when -`env.sender` is the owner of the given `token_id` or an `operator`. +`env.sender` is the owner of the given `token_id` or an `operator`. There can be multiple spender accounts per token, and they are cleared once the token is transferred or sent. @@ -72,14 +88,14 @@ expired owners in the results, otherwise, ignore them. operators that can access all of the owner's tokens. Return type is `OperatorsResponse`. If `include_expired` is set, show expired owners in the results, otherwise, ignore them. If `start_after` is set, then it returns the -first `limit` operators *after* the given one. +first `limit` operators _after_ the given one. `NumTokens{}` - Total number of tokens issued ### Receiver The counter-part to `SendNft` is `ReceiveNft`, which must be implemented by -any contract that wishes to manage CW721 tokens. This is generally *not* +any contract that wishes to manage CW721 tokens. This is generally _not_ implemented by any CW721 contract. `ReceiveNft{sender, token_id, msg}` - This is designed to handle `SendNft` @@ -91,18 +107,18 @@ The `sender` is the original account requesting to move the token and `msg` is a `Binary` data that can be decoded into a contract-specific message. This can be empty if we have only one default action, or it may be a `ReceiveMsg` variant to clarify the intention. For example, -if I send to an exchange, I can specify the price I want to list the token +if I send to an exchange, I can specify the price I want to list the token for. - + ## Metadata ### Queries -`ContractInfo{}` - This returns top-level metadata about the contract. +`CollectionInfo{}` - This returns top-level metadata about the contract. Namely, `name` and `symbol`. `NftInfo{token_id}` - This returns metadata about one particular token. -The return value is based on *ERC721 Metadata JSON Schema*, but directly +The return value is based on _ERC721 Metadata JSON Schema_, but directly from the contract, not as a Uri. Only the image link is a Uri. `AllNftInfo{token_id}` - This returns the result of both `NftInfo` @@ -120,14 +136,72 @@ value (suggested 30). Contracts can define other `DefaultLimit` and `MaxLimit` values without violating the CW721 spec, and clients should not rely on any particular values. -If `start_after` is unset, the query returns the first results, ordered +If `start_after` is unset, the query returns the first results, ordered lexicographically by `token_id`. If `start_after` is set, then it returns the -first `limit` tokens *after* the given one. This allows straightforward +first `limit` tokens _after_ the given one. This allows straightforward pagination by taking the last result returned (a `token_id`) and using it -as the `start_after` value in a future query. +as the `start_after` value in a future query. `Tokens{owner, start_after, limit}` - List all token_ids that belong to a given owner. Return type is `TokensResponse{tokens: Vec}`. -`AllTokens{start_after, limit}` - Requires pagination. Lists all token_ids controlled by +`AllTokens{start_after, limit}` - Requires pagination. Lists all token_ids controlled by the contract. + +### NftInfo Extension - CW721 Metadata Onchain + +NFT creators may want to store their NFT metadata on-chain so other contracts are able to interact with it. +With CW721 in CosmWasm, we allow you to store any data on chain you wish, using a generic `extension: T`. + +In order to support on-chain metadata, and to demonstrate how to use the extension ability, we have created this simple contract. +There is no business logic here, but looking at `lib.rs` will show you how do define custom data that is included when minting and +available in all queries. + +In particular, here we define: + +```rust +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct Trait { + pub display_type: Option, + pub trait_type: String, + pub value: String, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)] +pub struct Metadata { + pub image: Option, + pub image_data: Option, + pub external_url: Option, + pub description: Option, + pub name: Option, + pub attributes: Option>, + pub background_color: Option, + pub animation_url: Option, + pub youtube_url: Option, +} + +pub type Extension = Option; +``` + +In particular, the fields defined conform to the properties supported in the [OpenSea Metadata Standard](https://docs.opensea.io/docs/metadata-standards). + + +This means when you query `NftInfo{name: "Enterprise"}`, you will get something like: + +```json +{ + "name": "Enterprise", + "token_uri": "https://starships.example.com/Starship/Enterprise.json", + "extension": { + "image": null, + "image_data": null, + "external_url": null, + "description": "Spaceship with Warp Drive", + "name": "Starship USS Enterprise", + "attributes": null, + "background_color": null, + "animation_url": null, + "youtube_url": null + } +} +``` diff --git a/packages/cw721/examples/schema.rs b/packages/cw721/examples/schema.rs index 2999afa75..fc8c4a765 100644 --- a/packages/cw721/examples/schema.rs +++ b/packages/cw721/examples/schema.rs @@ -2,27 +2,54 @@ use std::env::current_dir; use std::fs::create_dir_all; use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for}; -use cosmwasm_std::Empty; +use cosmwasm_std::Empty; use cw721::{ - AllNftInfoResponse, ApprovalResponse, ApprovalsResponse, ContractInfoResponse, Cw721ExecuteMsg, - Cw721QueryMsg, Cw721ReceiveMsg, NftInfoResponse, NumTokensResponse, OperatorResponse, - OperatorsResponse, OwnerOfResponse, TokensResponse, + msg::{ + AllNftInfoResponse, ApprovalResponse, ApprovalsResponse, CollectionInfoMsg, + Cw721ExecuteMsg, Cw721InstantiateMsg, Cw721MigrateMsg, Cw721QueryMsg, MinterResponse, + NftInfoResponse, NumTokensResponse, OperatorResponse, OperatorsResponse, OwnerOfResponse, + TokensResponse, + }, + receiver::Cw721ReceiveMsg, + state::{CollectionInfo, DefaultOptionMetadataExtension}, }; - -type Extension = Option; - fn main() { let mut out_dir = current_dir().unwrap(); out_dir.push("schema"); create_dir_all(&out_dir).unwrap(); remove_schemas(&out_dir).unwrap(); - export_schema(&schema_for!(Cw721ExecuteMsg), &out_dir); - export_schema(&schema_for!(Cw721QueryMsg), &out_dir); - export_schema(&schema_for!(Cw721ReceiveMsg), &out_dir); + // entry points - generate always with title for avoiding name suffixes like "..._empty_for_..." due to generics + export_schema_with_title( + &schema_for!(Cw721InstantiateMsg), + &out_dir, + "Cw721InstantiateMsg", + ); + export_schema_with_title( + &schema_for!(Cw721ExecuteMsg::), + &out_dir, + "Cw721ExecuteMsg", + ); export_schema_with_title( - &schema_for!(AllNftInfoResponse), + &schema_for!(Cw721QueryMsg), + &out_dir, + "Cw721QueryMsg", + ); + export_schema_with_title(&schema_for!(Cw721MigrateMsg), &out_dir, "Cw721MigrateMsg"); + + // messages + export_schema_with_title(&schema_for!(Cw721ReceiveMsg), &out_dir, "Cw721ReceiveMsg"); + export_schema(&schema_for!(CollectionInfoMsg), &out_dir); + + // responses + export_schema_with_title( + &schema_for!(NftInfoResponse), + &out_dir, + "NftInfoResponse", + ); + export_schema_with_title( + &schema_for!(AllNftInfoResponse), &out_dir, "AllNftInfoResponse", ); @@ -30,13 +57,9 @@ fn main() { export_schema(&schema_for!(ApprovalsResponse), &out_dir); export_schema(&schema_for!(OperatorResponse), &out_dir); export_schema(&schema_for!(OperatorsResponse), &out_dir); - export_schema(&schema_for!(ContractInfoResponse), &out_dir); + export_schema_with_title(&schema_for!(CollectionInfo), &out_dir, "CollectionInfo"); export_schema(&schema_for!(OwnerOfResponse), &out_dir); - export_schema_with_title( - &schema_for!(NftInfoResponse), - &out_dir, - "NftInfoResponse", - ); + export_schema(&schema_for!(MinterResponse), &out_dir); export_schema(&schema_for!(NumTokensResponse), &out_dir); export_schema(&schema_for!(TokensResponse), &out_dir); } diff --git a/packages/cw721/schema/all_nft_info_response.json b/packages/cw721/schema/all_nft_info_response.json index d263321e2..8a03851da 100644 --- a/packages/cw721/schema/all_nft_info_response.json +++ b/packages/cw721/schema/all_nft_info_response.json @@ -19,13 +19,17 @@ "description": "Data on the token itself,", "allOf": [ { - "$ref": "#/definitions/NftInfoResponse_for_Nullable_Empty" + "$ref": "#/definitions/NftInfoResponse_for_Nullable_Metadata" } ] } }, "additionalProperties": false, "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, "Approval": { "type": "object", "required": [ @@ -43,15 +47,15 @@ }, "spender": { "description": "Account that can transfer/send the token", - "type": "string" + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] } }, "additionalProperties": false }, - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" - }, "Expiration": { "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", "oneOf": [ @@ -99,14 +103,77 @@ } ] }, - "NftInfoResponse_for_Nullable_Empty": { + "Metadata": { + "type": "object", + "properties": { + "animation_url": { + "type": [ + "string", + "null" + ] + }, + "attributes": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Trait" + } + }, + "background_color": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "external_url": { + "type": [ + "string", + "null" + ] + }, + "image": { + "type": [ + "string", + "null" + ] + }, + "image_data": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "youtube_url": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "NftInfoResponse_for_Nullable_Metadata": { "type": "object", "properties": { "extension": { "description": "You can add any custom metadata here when you extend cw721-base", "anyOf": [ { - "$ref": "#/definitions/Empty" + "$ref": "#/definitions/Metadata" }, { "type": "null" @@ -152,6 +219,28 @@ } ] }, + "Trait": { + "type": "object", + "required": [ + "trait_type", + "value" + ], + "properties": { + "display_type": { + "type": [ + "string", + "null" + ] + }, + "trait_type": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + }, "Uint64": { "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", "type": "string" diff --git a/packages/cw721/schema/approval_response.json b/packages/cw721/schema/approval_response.json index b29eab59e..c72b71b2d 100644 --- a/packages/cw721/schema/approval_response.json +++ b/packages/cw721/schema/approval_response.json @@ -12,6 +12,10 @@ }, "additionalProperties": false, "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, "Approval": { "type": "object", "required": [ @@ -29,7 +33,11 @@ }, "spender": { "description": "Account that can transfer/send the token", - "type": "string" + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] } }, "additionalProperties": false diff --git a/packages/cw721/schema/approvals_response.json b/packages/cw721/schema/approvals_response.json index 7cdac0015..01caf4d31 100644 --- a/packages/cw721/schema/approvals_response.json +++ b/packages/cw721/schema/approvals_response.json @@ -15,6 +15,10 @@ }, "additionalProperties": false, "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, "Approval": { "type": "object", "required": [ @@ -32,7 +36,11 @@ }, "spender": { "description": "Account that can transfer/send the token", - "type": "string" + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] } }, "additionalProperties": false diff --git a/packages/cw721/schema/contract_info_response.json b/packages/cw721/schema/collection_info.json similarity index 88% rename from packages/cw721/schema/contract_info_response.json rename to packages/cw721/schema/collection_info.json index 4a805a825..ebb8e6e8e 100644 --- a/packages/cw721/schema/contract_info_response.json +++ b/packages/cw721/schema/collection_info.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ContractInfoResponse", + "title": "CollectionInfo", "type": "object", "required": [ "name", diff --git a/contracts/cw721-non-transferable/schema/contract_info_response.json b/packages/cw721/schema/collection_info_msg.json similarity index 88% rename from contracts/cw721-non-transferable/schema/contract_info_response.json rename to packages/cw721/schema/collection_info_msg.json index 4a805a825..060c8f6a4 100644 --- a/contracts/cw721-non-transferable/schema/contract_info_response.json +++ b/packages/cw721/schema/collection_info_msg.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ContractInfoResponse", + "title": "CollectionInfoMsg", "type": "object", "required": [ "name", diff --git a/packages/cw721/schema/cw721_execute_msg.json b/packages/cw721/schema/cw721_execute_msg.json index 228b5c249..77cf0910f 100644 --- a/packages/cw721/schema/cw721_execute_msg.json +++ b/packages/cw721/schema/cw721_execute_msg.json @@ -2,6 +2,18 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "Cw721ExecuteMsg", "oneOf": [ + { + "type": "object", + "required": [ + "update_ownership" + ], + "properties": { + "update_ownership": { + "$ref": "#/definitions/Action" + } + }, + "additionalProperties": false + }, { "description": "Transfer is a base message to move a token to another account without triggering actions", "type": "object", @@ -174,6 +186,52 @@ }, "additionalProperties": false }, + { + "description": "Mint a new NFT, can only be called by the contract minter", + "type": "object", + "required": [ + "mint" + ], + "properties": { + "mint": { + "type": "object", + "required": [ + "owner", + "token_id" + ], + "properties": { + "extension": { + "description": "Any custom extension used by this contract", + "anyOf": [ + { + "$ref": "#/definitions/Metadata" + }, + { + "type": "null" + } + ] + }, + "owner": { + "description": "The owner of the newly minter NFT", + "type": "string" + }, + "token_id": { + "description": "Unique ID of the NFT", + "type": "string" + }, + "token_uri": { + "description": "Universal resource identifier for this NFT Should point to a JSON file that conforms to the ERC721 Metadata JSON Schema", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Burn an NFT the sender has access to", "type": "object", @@ -195,13 +253,163 @@ } }, "additionalProperties": false + }, + { + "description": "Extension msg", + "type": "object", + "required": [ + "extension" + ], + "properties": { + "extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Sets address to send withdrawn fees to. Only owner can call this.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Removes the withdraw address, so fees are sent to the contract. Only owner can call this.", + "type": "object", + "required": [ + "remove_withdraw_address" + ], + "properties": { + "remove_withdraw_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Withdraw from the contract to the given address. Anyone can call this, which is okay since withdraw address has been set by owner.", + "type": "object", + "required": [ + "withdraw_funds" + ], + "properties": { + "withdraw_funds": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { + "Action": { + "description": "Actions that can be taken to alter the contract's ownership", + "oneOf": [ + { + "description": "Propose to transfer the contract's ownership to another account, optionally with an expiry time.\n\nCan only be called by the contract's current owner.\n\nAny existing pending ownership transfer is overwritten.", + "type": "object", + "required": [ + "transfer_ownership" + ], + "properties": { + "transfer_ownership": { + "type": "object", + "required": [ + "new_owner" + ], + "properties": { + "expiry": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "new_owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Accept the pending ownership transfer.\n\nCan only be called by the pending owner.", + "type": "string", + "enum": [ + "accept_ownership" + ] + }, + { + "description": "Give up the contract's ownership and the possibility of appointing a new owner.\n\nCan only be invoked by the contract's current owner.\n\nAny existing pending ownership transfer is canceled.", + "type": "string", + "enum": [ + "renounce_ownership" + ] + } + ] + }, "Binary": { "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", "type": "string" }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, "Expiration": { "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)", "oneOf": [ @@ -249,6 +457,69 @@ } ] }, + "Metadata": { + "type": "object", + "properties": { + "animation_url": { + "type": [ + "string", + "null" + ] + }, + "attributes": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Trait" + } + }, + "background_color": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "external_url": { + "type": [ + "string", + "null" + ] + }, + "image": { + "type": [ + "string", + "null" + ] + }, + "image_data": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "youtube_url": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, "Timestamp": { "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", "allOf": [ @@ -257,6 +528,32 @@ } ] }, + "Trait": { + "type": "object", + "required": [ + "trait_type", + "value" + ], + "properties": { + "display_type": { + "type": [ + "string", + "null" + ] + }, + "trait_type": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, "Uint64": { "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", "type": "string" diff --git a/packages/cw721/schema/cw721_instantiate_msg.json b/packages/cw721/schema/cw721_instantiate_msg.json new file mode 100644 index 000000000..337caa3c9 --- /dev/null +++ b/packages/cw721/schema/cw721_instantiate_msg.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw721InstantiateMsg", + "type": "object", + "required": [ + "name", + "symbol" + ], + "properties": { + "minter": { + "description": "The minter is the only one who can create new NFTs. This is designed for a base NFT that is controlled by an external program or contract. You will likely replace this with custom logic in custom NFTs", + "type": [ + "string", + "null" + ] + }, + "name": { + "description": "Name of the NFT contract", + "type": "string" + }, + "symbol": { + "description": "Symbol of the NFT contract", + "type": "string" + }, + "withdraw_address": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false +} diff --git a/packages/cw721/schema/cw721_migrate_msg.json b/packages/cw721/schema/cw721_migrate_msg.json new file mode 100644 index 000000000..4bc17cd39 --- /dev/null +++ b/packages/cw721/schema/cw721_migrate_msg.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw721MigrateMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "with_update" + ], + "properties": { + "with_update": { + "type": "object", + "properties": { + "creator": { + "type": [ + "string", + "null" + ] + }, + "minter": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/packages/cw721/schema/cw721_query_msg.json b/packages/cw721/schema/cw721_query_msg.json index 8d7c18acc..0323ea65c 100644 --- a/packages/cw721/schema/cw721_query_msg.json +++ b/packages/cw721/schema/cw721_query_msg.json @@ -3,7 +3,7 @@ "title": "Cw721QueryMsg", "oneOf": [ { - "description": "Return the owner of the given token, error if token does not exist Return type: OwnerOfResponse", + "description": "Return the owner of the given token, error if token does not exist", "type": "object", "required": [ "owner_of" @@ -32,7 +32,7 @@ "additionalProperties": false }, { - "description": "Return operator that can access all of the owner's tokens. Return type: `ApprovalResponse`", + "description": "Return operator that can access all of the owner's tokens.", "type": "object", "required": [ "approval" @@ -64,7 +64,7 @@ "additionalProperties": false }, { - "description": "Return approvals that a token has Return type: `ApprovalsResponse`", + "description": "Return approvals that a token has", "type": "object", "required": [ "approvals" @@ -92,7 +92,7 @@ "additionalProperties": false }, { - "description": "Return approval of a given operator for all tokens of an owner, error if not set Return type: `OperatorResponse`", + "description": "Return approval of a given operator for all tokens of an owner, error if not set", "type": "object", "required": [ "operator" @@ -124,7 +124,7 @@ "additionalProperties": false }, { - "description": "List all operators that can access all of the owner's tokens Return type: `OperatorsResponse`", + "description": "List all operators that can access all of the owner's tokens", "type": "object", "required": [ "all_operators" @@ -181,7 +181,6 @@ "additionalProperties": false }, { - "description": "With MetaData Extension. Returns top-level metadata about the contract: `ContractInfoResponse`", "type": "object", "required": [ "contract_info" @@ -195,7 +194,20 @@ "additionalProperties": false }, { - "description": "With MetaData Extension. Returns metadata about one particular token, based on *ERC721 Metadata JSON Schema* but directly from the contract: `NftInfoResponse`", + "type": "object", + "required": [ + "ownership" + ], + "properties": { + "ownership": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "With MetaData Extension. Returns metadata about one particular token, based on *ERC721 Metadata JSON Schema* but directly from the contract", "type": "object", "required": [ "nft_info" @@ -217,7 +229,7 @@ "additionalProperties": false }, { - "description": "With MetaData Extension. Returns the result of both `NftInfo` and `OwnerOf` as one query as an optimization for clients: `AllNftInfo`", + "description": "With MetaData Extension. Returns the result of both `NftInfo` and `OwnerOf` as one query as an optimization for clients", "type": "object", "required": [ "all_nft_info" @@ -246,7 +258,7 @@ "additionalProperties": false }, { - "description": "With Enumerable extension. Returns all tokens owned by the given address, [] if unset. Return type: TokensResponse.", + "description": "With Enumerable extension. Returns all tokens owned by the given address, [] if unset.", "type": "object", "required": [ "tokens" @@ -282,7 +294,7 @@ "additionalProperties": false }, { - "description": "With Enumerable extension. Requires pagination. Lists all token_ids controlled by the contract. Return type: TokensResponse.", + "description": "With Enumerable extension. Requires pagination. Lists all token_ids controlled by the contract.", "type": "object", "required": [ "all_tokens" @@ -310,6 +322,61 @@ } }, "additionalProperties": false + }, + { + "description": "Return the minter", + "type": "object", + "required": [ + "minter" + ], + "properties": { + "minter": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_withdraw_address" + ], + "properties": { + "get_withdraw_address": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Do not use - dummy extension query, needed for inferring type parameter during compile", + "type": "object", + "required": [ + "extension" + ], + "properties": { + "extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" } - ] + } } diff --git a/contracts/cw721-non-transferable/schema/minter_response.json b/packages/cw721/schema/minter_response.json similarity index 68% rename from contracts/cw721-non-transferable/schema/minter_response.json rename to packages/cw721/schema/minter_response.json index e79df37e8..3d450621a 100644 --- a/contracts/cw721-non-transferable/schema/minter_response.json +++ b/packages/cw721/schema/minter_response.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "MinterResponse", - "description": "Shows who can mint these tokens", + "description": "Deprecated: use Cw721QueryMsg::GetMinterOwnership instead! Shows who can mint these tokens.", "type": "object", "properties": { "minter": { diff --git a/packages/cw721/schema/nft_info_response.json b/packages/cw721/schema/nft_info_response.json index 51b1f072c..0f0859230 100644 --- a/packages/cw721/schema/nft_info_response.json +++ b/packages/cw721/schema/nft_info_response.json @@ -7,7 +7,7 @@ "description": "You can add any custom metadata here when you extend cw721-base", "anyOf": [ { - "$ref": "#/definitions/Empty" + "$ref": "#/definitions/Metadata" }, { "type": "null" @@ -24,9 +24,90 @@ }, "additionalProperties": false, "definitions": { - "Empty": { - "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", - "type": "object" + "Metadata": { + "type": "object", + "properties": { + "animation_url": { + "type": [ + "string", + "null" + ] + }, + "attributes": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Trait" + } + }, + "background_color": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "external_url": { + "type": [ + "string", + "null" + ] + }, + "image": { + "type": [ + "string", + "null" + ] + }, + "image_data": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "youtube_url": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "Trait": { + "type": "object", + "required": [ + "trait_type", + "value" + ], + "properties": { + "display_type": { + "type": [ + "string", + "null" + ] + }, + "trait_type": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "additionalProperties": false } } } diff --git a/packages/cw721/schema/operator_response.json b/packages/cw721/schema/operator_response.json index 9e2dbd3d2..097e08213 100644 --- a/packages/cw721/schema/operator_response.json +++ b/packages/cw721/schema/operator_response.json @@ -12,6 +12,10 @@ }, "additionalProperties": false, "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, "Approval": { "type": "object", "required": [ @@ -29,7 +33,11 @@ }, "spender": { "description": "Account that can transfer/send the token", - "type": "string" + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] } }, "additionalProperties": false diff --git a/packages/cw721/schema/operators_response.json b/packages/cw721/schema/operators_response.json index 533a096dd..426cd48cc 100644 --- a/packages/cw721/schema/operators_response.json +++ b/packages/cw721/schema/operators_response.json @@ -15,6 +15,10 @@ }, "additionalProperties": false, "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, "Approval": { "type": "object", "required": [ @@ -32,7 +36,11 @@ }, "spender": { "description": "Account that can transfer/send the token", - "type": "string" + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] } }, "additionalProperties": false diff --git a/packages/cw721/schema/owner_of_response.json b/packages/cw721/schema/owner_of_response.json index abb9006d8..36b85a171 100644 --- a/packages/cw721/schema/owner_of_response.json +++ b/packages/cw721/schema/owner_of_response.json @@ -21,6 +21,10 @@ }, "additionalProperties": false, "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, "Approval": { "type": "object", "required": [ @@ -38,7 +42,11 @@ }, "spender": { "description": "Account that can transfer/send the token", - "type": "string" + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] } }, "additionalProperties": false diff --git a/packages/cw721/src/error.rs b/packages/cw721/src/error.rs new file mode 100644 index 000000000..99e274b3f --- /dev/null +++ b/packages/cw721/src/error.rs @@ -0,0 +1,27 @@ +use cosmwasm_std::StdError; +use cw_ownable::OwnershipError; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum Cw721ContractError { + #[error(transparent)] + Std(#[from] StdError), + + #[error(transparent)] + Ownership(#[from] OwnershipError), + + #[error(transparent)] + Version(#[from] cw2::VersionError), + + #[error("token_id already claimed")] + Claimed {}, + + #[error("Cannot set approval that is already expired")] + Expired {}, + + #[error("Approval not found for: {spender}")] + ApprovalNotFound { spender: String }, + + #[error("No withdraw address set")] + NoWithdrawAddress {}, +} diff --git a/packages/cw721/src/execute.rs b/packages/cw721/src/execute.rs new file mode 100644 index 000000000..73c68fd4e --- /dev/null +++ b/packages/cw721/src/execute.rs @@ -0,0 +1,698 @@ +use cosmwasm_std::{ + Addr, Api, BankMsg, Binary, Coin, CustomMsg, Deps, DepsMut, Empty, Env, MessageInfo, Response, + StdResult, Storage, +}; +use cw_ownable::{none_or, Action, Ownership, OwnershipError, OwnershipStore}; +use cw_storage_plus::Item; +use cw_utils::Expiration; +use serde::de::DeserializeOwned; +use serde::Serialize; + +use crate::{ + error::Cw721ContractError, + msg::{Cw721ExecuteMsg, Cw721InstantiateMsg, Cw721MigrateMsg}, + receiver::Cw721ReceiveMsg, + state::{CollectionInfo, Cw721Config, DefaultOptionMetadataExtension, NftInfo, MINTER}, + Approval, +}; + +pub trait Cw721Execute< + // Metadata defined in NftInfo (used for mint). + TMetadataExtension, + // Defines for `CosmosMsg::Custom` in response. Barely used, so `Empty` can be used. + TCustomResponseMessage, + // Message passed for updating metadata. + TMetadataExtensionMsg, + // Extension query message. + TQueryExtensionMsg, +> where + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TCustomResponseMessage: CustomMsg, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, +{ + fn instantiate( + &self, + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: Cw721InstantiateMsg, + contract_name: &str, + contract_version: &str, + ) -> Result, Cw721ContractError> { + cw2::set_contract_version(deps.storage, contract_name, contract_version)?; + let config = Cw721Config::< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >::default(); + let collection_info = CollectionInfo { + name: msg.name, + symbol: msg.symbol, + }; + config + .collection_info + .save(deps.storage, &collection_info)?; + + let minter = match msg.minter { + Some(owner) => deps.api.addr_validate(&owner)?, + None => info.sender, + }; + self.initialize_minter(deps.storage, deps.api, Some(minter.as_ref()))?; + + if let Some(withdraw_address) = msg.withdraw_address { + self.set_withdraw_address(deps, &minter, withdraw_address)?; + } + + Ok(Response::default().add_attribute("minter", minter)) + } + + fn execute( + &self, + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: Cw721ExecuteMsg, + ) -> Result, Cw721ContractError> { + match msg { + Cw721ExecuteMsg::Mint { + token_id, + owner, + token_uri, + extension, + } => self.mint(deps, info, token_id, owner, token_uri, extension), + Cw721ExecuteMsg::Approve { + spender, + token_id, + expires, + } => self.approve(deps, env, info, spender, token_id, expires), + Cw721ExecuteMsg::Revoke { spender, token_id } => { + self.revoke(deps, env, info, spender, token_id) + } + Cw721ExecuteMsg::ApproveAll { operator, expires } => { + self.approve_all(deps, env, info, operator, expires) + } + Cw721ExecuteMsg::RevokeAll { operator } => self.revoke_all(deps, env, info, operator), + Cw721ExecuteMsg::TransferNft { + recipient, + token_id, + } => self.transfer_nft(deps, env, info, recipient, token_id), + Cw721ExecuteMsg::SendNft { + contract, + token_id, + msg, + } => self.send_nft(deps, env, info, contract, token_id, msg), + Cw721ExecuteMsg::Burn { token_id } => self.burn_nft(deps, env, info, token_id), + Cw721ExecuteMsg::UpdateOwnership(action) => { + self.update_minter_ownership(deps, env, info, action) + } + Cw721ExecuteMsg::Extension { msg } => { + self.update_metadata_extension(deps, env, info, msg) + } + Cw721ExecuteMsg::SetWithdrawAddress { address } => { + self.set_withdraw_address(deps, &info.sender, address) + } + Cw721ExecuteMsg::RemoveWithdrawAddress {} => { + self.remove_withdraw_address(deps.storage, &info.sender) + } + Cw721ExecuteMsg::WithdrawFunds { amount } => self.withdraw_funds(deps.storage, &amount), + } + } + + fn migrate( + &self, + deps: DepsMut, + env: Env, + msg: Cw721MigrateMsg, + contract_name: &str, + contract_version: &str, + ) -> Result { + let response = Response::::default(); + // first migrate legacy data ... + let response = + migrate_legacy_minter_and_creator(deps.storage, deps.api, &env, &msg, response)?; + let response = migrate_legacy_collection_info(deps.storage, &env, &msg, response)?; + // ... then migrate + let response = migrate_version(deps.storage, contract_name, contract_version, response)?; + // ... and update creator and minter AFTER legacy migration + let response = migrate_minter(deps.storage, deps.api, &env, &msg, response)?; + Ok(response) + } + + // ------- ERC721-based functions ------- + fn transfer_nft( + &self, + deps: DepsMut, + env: Env, + info: MessageInfo, + recipient: String, + token_id: String, + ) -> Result, Cw721ContractError> { + _transfer_nft::(deps, &env, &info, &recipient, &token_id)?; + + Ok(Response::new() + .add_attribute("action", "transfer_nft") + .add_attribute("sender", info.sender) + .add_attribute("recipient", recipient) + .add_attribute("token_id", token_id)) + } + + fn send_nft( + &self, + deps: DepsMut, + env: Env, + info: MessageInfo, + contract: String, + token_id: String, + msg: Binary, + ) -> Result, Cw721ContractError> { + // Transfer token + _transfer_nft::(deps, &env, &info, &contract, &token_id)?; + + let send = Cw721ReceiveMsg { + sender: info.sender.to_string(), + token_id: token_id.clone(), + msg, + }; + + // Send message + Ok(Response::new() + .add_message(send.into_cosmos_msg(contract.clone())?) + .add_attribute("action", "send_nft") + .add_attribute("sender", info.sender) + .add_attribute("recipient", contract) + .add_attribute("token_id", token_id)) + } + + fn approve( + &self, + deps: DepsMut, + env: Env, + info: MessageInfo, + spender: String, + token_id: String, + expires: Option, + ) -> Result, Cw721ContractError> { + _update_approvals::( + deps, &env, &info, &spender, &token_id, true, expires, + )?; + + Ok(Response::new() + .add_attribute("action", "approve") + .add_attribute("sender", info.sender) + .add_attribute("spender", spender) + .add_attribute("token_id", token_id)) + } + + fn revoke( + &self, + deps: DepsMut, + env: Env, + info: MessageInfo, + spender: String, + token_id: String, + ) -> Result, Cw721ContractError> { + _update_approvals::( + deps, &env, &info, &spender, &token_id, false, None, + )?; + + Ok(Response::new() + .add_attribute("action", "revoke") + .add_attribute("sender", info.sender) + .add_attribute("spender", spender) + .add_attribute("token_id", token_id)) + } + + fn approve_all( + &self, + deps: DepsMut, + env: Env, + info: MessageInfo, + operator: String, + expires: Option, + ) -> Result, Cw721ContractError> { + // reject expired data as invalid + let expires = expires.unwrap_or_default(); + if expires.is_expired(&env.block) { + return Err(Cw721ContractError::Expired {}); + } + + // set the operator for us + let operator_addr = deps.api.addr_validate(&operator)?; + let config = Cw721Config::< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >::default(); + config + .operators + // stores info.sender as key (=granter, NFT owner) and operator as value (operator only(!) has control over NFTs of granter) + // check is done in `check_can_send()` + .save(deps.storage, (&info.sender, &operator_addr), &expires)?; + + Ok(Response::new() + .add_attribute("action", "approve_all") + .add_attribute("sender", info.sender) + .add_attribute("operator", operator)) + } + + fn revoke_all( + &self, + deps: DepsMut, + _env: Env, + info: MessageInfo, + operator: String, + ) -> Result, Cw721ContractError> { + let operator_addr = deps.api.addr_validate(&operator)?; + let config = Cw721Config::< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >::default(); + config + .operators + .remove(deps.storage, (&info.sender, &operator_addr)); + + Ok(Response::new() + .add_attribute("action", "revoke_all") + .add_attribute("sender", info.sender) + .add_attribute("operator", operator)) + } + + fn burn_nft( + &self, + deps: DepsMut, + env: Env, + info: MessageInfo, + token_id: String, + ) -> Result, Cw721ContractError> { + let config = Cw721Config::< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >::default(); + let token = config.nft_info.load(deps.storage, &token_id)?; + check_can_send(deps.as_ref(), &env, &info, &token)?; + + config.nft_info.remove(deps.storage, &token_id)?; + config.decrement_tokens(deps.storage)?; + + Ok(Response::new() + .add_attribute("action", "burn") + .add_attribute("sender", info.sender) + .add_attribute("token_id", token_id)) + } + + // ------- opionated cw721 functions ------- + fn initialize_minter( + &self, + storage: &mut dyn Storage, + api: &dyn Api, + minter: Option<&str>, + ) -> StdResult> { + MINTER.initialize_owner(storage, api, minter) + } + + fn mint( + &self, + deps: DepsMut, + info: MessageInfo, + token_id: String, + owner: String, + token_uri: Option, + extension: TMetadataExtension, + ) -> Result, Cw721ContractError> { + MINTER.assert_owner(deps.storage, &info.sender)?; + + // create the token + let token = NftInfo { + owner: deps.api.addr_validate(&owner)?, + approvals: vec![], + token_uri, + extension, + }; + let config = Cw721Config::< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >::default(); + config + .nft_info + .update(deps.storage, &token_id, |old| match old { + Some(_) => Err(Cw721ContractError::Claimed {}), + None => Ok(token), + })?; + + config.increment_tokens(deps.storage)?; + + Ok(Response::new() + .add_attribute("action", "mint") + .add_attribute("minter", info.sender) + .add_attribute("owner", owner) + .add_attribute("token_id", token_id)) + } + + fn update_minter_ownership( + &self, + deps: DepsMut, + env: Env, + info: MessageInfo, + action: Action, + ) -> Result, Cw721ContractError> { + let ownership = + MINTER.update_ownership(deps.api, deps.storage, &env.block, &info.sender, action)?; + Ok(Response::new() + .add_attribute("update_minter_ownership", info.sender) + .add_attributes(ownership.into_attributes())) + } + + /// Allows creator to update onchain metadata. For now this is a no-op. + fn update_metadata_extension( + &self, + deps: DepsMut, + _env: Env, + info: MessageInfo, + _msg: TMetadataExtensionMsg, + ) -> Result, Cw721ContractError> { + cw_ownable::assert_owner(deps.storage, &info.sender)?; + Ok(Response::new().add_attribute("action", "update_metadata_extension")) + } + + fn set_withdraw_address( + &self, + deps: DepsMut, + sender: &Addr, + address: String, + ) -> Result, Cw721ContractError> { + cw_ownable::assert_owner(deps.storage, sender)?; + deps.api.addr_validate(&address)?; + let config = Cw721Config::< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >::default(); + config.withdraw_address.save(deps.storage, &address)?; + Ok(Response::new() + .add_attribute("action", "set_withdraw_address") + .add_attribute("address", address)) + } + + fn remove_withdraw_address( + &self, + storage: &mut dyn Storage, + sender: &Addr, + ) -> Result, Cw721ContractError> { + cw_ownable::assert_owner(storage, sender)?; + let config = Cw721Config::< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >::default(); + let address = config.withdraw_address.may_load(storage)?; + match address { + Some(address) => { + config.withdraw_address.remove(storage); + Ok(Response::new() + .add_attribute("action", "remove_withdraw_address") + .add_attribute("address", address)) + } + None => Err(Cw721ContractError::NoWithdrawAddress {}), + } + } + + fn withdraw_funds( + &self, + storage: &mut dyn Storage, + amount: &Coin, + ) -> Result, Cw721ContractError> { + let withdraw_address = Cw721Config::< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >::default() + .withdraw_address + .may_load(storage)?; + match withdraw_address { + Some(address) => { + let msg = BankMsg::Send { + to_address: address, + amount: vec![amount.clone()], + }; + Ok(Response::new() + .add_message(msg) + .add_attribute("action", "withdraw_funds") + .add_attribute("amount", amount.amount.to_string()) + .add_attribute("denom", amount.denom.to_string())) + } + None => Err(Cw721ContractError::NoWithdrawAddress {}), + } + } +} + +// ------- helper cw721 functions ------- +fn _transfer_nft( + deps: DepsMut, + env: &Env, + info: &MessageInfo, + recipient: &str, + token_id: &str, +) -> Result, Cw721ContractError> +where + TMetadataExtension: Serialize + DeserializeOwned + Clone, +{ + let config = Cw721Config::::default(); + let mut token = config.nft_info.load(deps.storage, token_id)?; + // ensure we have permissions + check_can_send(deps.as_ref(), env, info, &token)?; + // set owner and remove existing approvals + token.owner = deps.api.addr_validate(recipient)?; + token.approvals = vec![]; + config.nft_info.save(deps.storage, token_id, &token)?; + Ok(token) +} + +#[allow(clippy::too_many_arguments)] +fn _update_approvals( + deps: DepsMut, + env: &Env, + info: &MessageInfo, + spender: &str, + token_id: &str, + // if add == false, remove. if add == true, remove then set with this expiration + add: bool, + expires: Option, +) -> Result, Cw721ContractError> +where + TMetadataExtension: Serialize + DeserializeOwned + Clone, +{ + let config = Cw721Config::::default(); + let mut token = config.nft_info.load(deps.storage, token_id)?; + // ensure we have permissions + check_can_approve(deps.as_ref(), env, info, &token)?; + + // update the approval list (remove any for the same spender before adding) + let spender_addr = deps.api.addr_validate(spender)?; + token.approvals.retain(|apr| apr.spender != spender_addr); + + // only difference between approve and revoke + if add { + // reject expired data as invalid + let expires = expires.unwrap_or_default(); + if expires.is_expired(&env.block) { + return Err(Cw721ContractError::Expired {}); + } + let approval = Approval { + spender: spender_addr, + expires, + }; + token.approvals.push(approval); + } + + config.nft_info.save(deps.storage, token_id, &token)?; + + Ok(token) +} + +/// returns true if the sender can execute approve or reject on the contract +pub fn check_can_approve( + deps: Deps, + env: &Env, + info: &MessageInfo, + token: &NftInfo, +) -> Result<(), Cw721ContractError> +where + TMetadataExtension: Serialize + DeserializeOwned + Clone, +{ + // owner can approve + if token.owner == info.sender { + return Ok(()); + } + // operator can approve + let config = Cw721Config::::default(); + let op = config + .operators + .may_load(deps.storage, (&token.owner, &info.sender))?; + match op { + Some(ex) => { + if ex.is_expired(&env.block) { + Err(Cw721ContractError::Ownership(OwnershipError::NotOwner)) + } else { + Ok(()) + } + } + None => Err(Cw721ContractError::Ownership(OwnershipError::NotOwner)), + } +} + +/// returns true iff the sender can transfer ownership of the token +pub fn check_can_send( + deps: Deps, + env: &Env, + info: &MessageInfo, + token: &NftInfo, +) -> Result<(), Cw721ContractError> { + // owner can send + if token.owner == info.sender { + return Ok(()); + } + + // any non-expired token approval can send + if token + .approvals + .iter() + .any(|apr| apr.spender == info.sender && !apr.is_expired(&env.block)) + { + return Ok(()); + } + + // operator can send + let config = Cw721Config::::default(); + let op = config + .operators + // has token owner approved/gave grant to sender for full control over owner's NFTs? + .may_load(deps.storage, (&token.owner, &info.sender))?; + + match op { + Some(ex) => { + if ex.is_expired(&env.block) { + Err(Cw721ContractError::Ownership(OwnershipError::NotOwner)) + } else { + Ok(()) + } + } + None => Err(Cw721ContractError::Ownership(OwnershipError::NotOwner)), + } +} + +// ------- migrate ------- +pub fn migrate_version( + storage: &mut dyn Storage, + contradct_name: &str, + contract_version: &str, + response: Response, +) -> StdResult { + let response = response + .add_attribute("from_version", cw2::get_contract_version(storage)?.version) + .add_attribute("to_version", contract_version); + + // update contract version + cw2::set_contract_version(storage, contradct_name, contract_version)?; + Ok(response) +} + +pub fn migrate_minter( + storage: &mut dyn Storage, + api: &dyn Api, + _env: &Env, + msg: &Cw721MigrateMsg, + response: Response, +) -> StdResult { + match msg { + Cw721MigrateMsg::WithUpdate { minter, .. } => { + if let Some(minter) = minter { + MINTER.initialize_owner(storage, api, Some(minter.as_str()))?; + return Ok(response.add_attribute("creator", minter)); + } + } + } + Ok(response) +} + +/// Migrates only in case ownership is not present +/// !!! Important note here: !!! +/// - creator owns the contract and can update collection info +/// - minter can mint new tokens +/// +/// Before v0.19.0 there were confusing naming conventions: +/// - v0.17.0: minter was replaced by cw_ownable, as a result minter is owner +/// - v0.16.0 and below: minter was stored in dedicated `minter` store (so NOT using cw_ownable at all) +pub fn migrate_legacy_minter_and_creator( + storage: &mut dyn Storage, + api: &dyn Api, + _env: &Env, + _msg: &Cw721MigrateMsg, + response: Response, +) -> Result { + let minter = MINTER.item.may_load(storage)?; + // no migration in case minter is already set + if minter.is_some() { + return Ok(response); + } + // in v0.17/18 cw_ownable::OWNERSHIP was used for minter, now it is used for creator + let ownership_previously_used_as_minter = OwnershipStore::new("collection_minter") + .item + .may_load(storage)?; + let creator_and_minter = match ownership_previously_used_as_minter { + // v0.18 migration + Some(ownership) => { + // owner is used for both: creator and minter + // since it is already set for creator, we only need to migrate minter + let owner = ownership.owner.map(|a| a.to_string()); + MINTER.initialize_owner(storage, api, owner.as_deref())?; + owner + } + // v0.17 and older migration + None => { + let legacy_minter_store: Item = Item::new("minter"); + let legacy_minter = legacy_minter_store.load(storage)?; + MINTER.initialize_owner(storage, api, Some(legacy_minter.as_str()))?; + Some(legacy_minter.to_string()) + } + }; + Ok(response.add_attribute("creator_and_minter", none_or(creator_and_minter.as_ref()))) +} + +/// Migrates only in case collection_info is not present +pub fn migrate_legacy_collection_info( + storage: &mut dyn Storage, + _env: &Env, + _msg: &Cw721MigrateMsg, + response: Response, +) -> Result { + let contract = Cw721Config::::default(); + match contract.collection_info.may_load(storage)? { + Some(_) => Ok(response), + None => { + // contract info is legacy collection info + let legacy_collection_info_store: Item = + Item::new("nft_info"); + let legacy_collection_info = legacy_collection_info_store.load(storage)?; + let collection_info = CollectionInfo { + name: legacy_collection_info.name.clone(), + symbol: legacy_collection_info.symbol.clone(), + }; + contract.collection_info.save(storage, &collection_info)?; + Ok(response + .add_attribute("migrated collection name", legacy_collection_info.name) + .add_attribute("migrated collection symbol", legacy_collection_info.symbol)) + } + } +} diff --git a/contracts/cw721-base/src/helpers.rs b/packages/cw721/src/helpers.rs similarity index 72% rename from contracts/cw721-base/src/helpers.rs rename to packages/cw721/src/helpers.rs index ab28a52d6..67af25325 100644 --- a/contracts/cw721-base/src/helpers.rs +++ b/packages/cw721/src/helpers.rs @@ -1,32 +1,42 @@ use std::marker::PhantomData; +use crate::msg::{ + AllNftInfoResponse, ApprovalResponse, ApprovalsResponse, NftInfoResponse, NumTokensResponse, + OperatorsResponse, OwnerOfResponse, TokensResponse, +}; +use crate::msg::{Cw721ExecuteMsg, Cw721QueryMsg}; +use crate::state::CollectionInfo; +use crate::Approval; use cosmwasm_schema::cw_serde; use cosmwasm_std::{ to_json_binary, Addr, CosmosMsg, CustomMsg, QuerierWrapper, StdResult, WasmMsg, WasmQuery, }; -use cw721::{ - AllNftInfoResponse, Approval, ApprovalResponse, ApprovalsResponse, ContractInfoResponse, - NftInfoResponse, NumTokensResponse, OperatorsResponse, OwnerOfResponse, TokensResponse, -}; use serde::de::DeserializeOwned; use serde::Serialize; -use crate::{ExecuteMsg, QueryMsg}; - #[cw_serde] -pub struct Cw721Contract( +pub struct Cw721Contract( pub Addr, - pub PhantomData, - pub PhantomData, + pub PhantomData, + pub PhantomData, + pub PhantomData, ); #[allow(dead_code)] -impl Cw721Contract { +impl + Cw721Contract +where + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, +{ pub fn addr(&self) -> Addr { self.0.clone() } - pub fn call(&self, msg: ExecuteMsg) -> StdResult { + pub fn call( + &self, + msg: Cw721ExecuteMsg, + ) -> StdResult { let msg = to_json_binary(&msg)?; Ok(WasmMsg::Execute { contract_addr: self.addr().into(), @@ -39,7 +49,7 @@ impl Cw721Contract { pub fn query( &self, querier: &QuerierWrapper, - req: QueryMsg, + req: Cw721QueryMsg, ) -> StdResult { let query = WasmQuery::Smart { contract_addr: self.addr().into(), @@ -57,7 +67,7 @@ impl Cw721Contract { token_id: T, include_expired: bool, ) -> StdResult { - let req = QueryMsg::OwnerOf { + let req = Cw721QueryMsg::OwnerOf { token_id: token_id.into(), include_expired: Some(include_expired), }; @@ -71,7 +81,7 @@ impl Cw721Contract { spender: T, include_expired: Option, ) -> StdResult { - let req = QueryMsg::Approval { + let req = Cw721QueryMsg::Approval { token_id: token_id.into(), spender: spender.into(), include_expired, @@ -86,7 +96,7 @@ impl Cw721Contract { token_id: T, include_expired: Option, ) -> StdResult { - let req = QueryMsg::Approvals { + let req = Cw721QueryMsg::Approvals { token_id: token_id.into(), include_expired, }; @@ -102,7 +112,7 @@ impl Cw721Contract { start_after: Option, limit: Option, ) -> StdResult> { - let req = QueryMsg::AllOperators { + let req = Cw721QueryMsg::AllOperators { owner: owner.into(), include_expired: Some(include_expired), start_after, @@ -113,14 +123,14 @@ impl Cw721Contract { } pub fn num_tokens(&self, querier: &QuerierWrapper) -> StdResult { - let req = QueryMsg::NumTokens {}; + let req = Cw721QueryMsg::NumTokens {}; let res: NumTokensResponse = self.query(querier, req)?; Ok(res.count) } /// With metadata extension - pub fn contract_info(&self, querier: &QuerierWrapper) -> StdResult { - let req = QueryMsg::ContractInfo {}; + pub fn collection_info(&self, querier: &QuerierWrapper) -> StdResult { + let req = Cw721QueryMsg::ContractInfo {}; self.query(querier, req) } @@ -130,7 +140,7 @@ impl Cw721Contract { querier: &QuerierWrapper, token_id: T, ) -> StdResult> { - let req = QueryMsg::NftInfo { + let req = Cw721QueryMsg::NftInfo { token_id: token_id.into(), }; self.query(querier, req) @@ -143,7 +153,7 @@ impl Cw721Contract { token_id: T, include_expired: bool, ) -> StdResult> { - let req = QueryMsg::AllNftInfo { + let req = Cw721QueryMsg::AllNftInfo { token_id: token_id.into(), include_expired: Some(include_expired), }; @@ -158,7 +168,7 @@ impl Cw721Contract { start_after: Option, limit: Option, ) -> StdResult { - let req = QueryMsg::Tokens { + let req = Cw721QueryMsg::Tokens { owner: owner.into(), start_after, limit, @@ -173,15 +183,10 @@ impl Cw721Contract { start_after: Option, limit: Option, ) -> StdResult { - let req = QueryMsg::AllTokens { start_after, limit }; + let req = Cw721QueryMsg::AllTokens { start_after, limit }; self.query(querier, req) } - /// returns true if the contract supports the metadata extension - pub fn has_metadata(&self, querier: &QuerierWrapper) -> bool { - self.contract_info(querier).is_ok() - } - /// returns true if the contract supports the enumerable extension pub fn has_enumerable(&self, querier: &QuerierWrapper) -> bool { self.tokens(querier, self.addr(), None, Some(1)).is_ok() diff --git a/packages/cw721/src/lib.rs b/packages/cw721/src/lib.rs index 0784c6272..dc1f7a396 100644 --- a/packages/cw721/src/lib.rs +++ b/packages/cw721/src/lib.rs @@ -1,15 +1,13 @@ -mod msg; -mod query; -mod receiver; -mod traits; +pub mod error; +pub mod execute; +pub mod helpers; +pub mod msg; +pub mod query; +pub mod receiver; +pub mod state; pub use cw_utils::Expiration; +pub use state::Approval; -pub use crate::msg::Cw721ExecuteMsg; -pub use crate::query::{ - AllNftInfoResponse, Approval, ApprovalResponse, ApprovalsResponse, ContractInfoResponse, - Cw721QueryMsg, MinterResponse, NftInfoResponse, NumTokensResponse, OperatorResponse, - OperatorsResponse, OwnerOfResponse, TokensResponse, -}; -pub use crate::receiver::Cw721ReceiveMsg; -pub use crate::traits::{Cw721, Cw721Execute, Cw721Query}; +#[cfg(test)] +pub mod testing; diff --git a/packages/cw721/src/msg.rs b/packages/cw721/src/msg.rs index 87ce6cea3..2f975db52 100644 --- a/packages/cw721/src/msg.rs +++ b/packages/cw721/src/msg.rs @@ -1,11 +1,20 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::Binary; +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Addr, Binary, Coin}; +use cw_ownable::{Action, Ownership}; use cw_utils::Expiration; +use crate::state::CollectionInfo; +use crate::Approval; + #[cw_serde] -pub enum Cw721ExecuteMsg { +pub enum Cw721ExecuteMsg { + UpdateOwnership(Action), + /// Transfer is a base message to move a token to another account without triggering actions - TransferNft { recipient: String, token_id: String }, + TransferNft { + recipient: String, + token_id: String, + }, /// Send is a base message to transfer a token to a contract and trigger an action /// on the receiving contract. SendNft { @@ -21,7 +30,10 @@ pub enum Cw721ExecuteMsg { expires: Option, }, /// Remove previously granted Approval - Revoke { spender: String, token_id: String }, + Revoke { + spender: String, + token_id: String, + }, /// Allows operator to transfer / send any token from the owner's account. /// If expiration is set, then this allowance has a time/height limit ApproveAll { @@ -29,7 +41,236 @@ pub enum Cw721ExecuteMsg { expires: Option, }, /// Remove previously granted ApproveAll permission - RevokeAll { operator: String }, + RevokeAll { + operator: String, + }, + + /// Mint a new NFT, can only be called by the contract minter + Mint { + /// Unique ID of the NFT + token_id: String, + /// The owner of the newly minter NFT + owner: String, + /// Universal resource identifier for this NFT + /// Should point to a JSON file that conforms to the ERC721 + /// Metadata JSON Schema + token_uri: Option, + /// Any custom extension used by this contract + extension: TMetadataExtension, + }, + /// Burn an NFT the sender has access to - Burn { token_id: String }, + Burn { + token_id: String, + }, + + /// Extension msg + Extension { + msg: TMetadataExtensionMsg, + }, + + /// Sets address to send withdrawn fees to. Only owner can call this. + SetWithdrawAddress { + address: String, + }, + /// Removes the withdraw address, so fees are sent to the contract. Only owner can call this. + RemoveWithdrawAddress {}, + /// Withdraw from the contract to the given address. Anyone can call this, + /// which is okay since withdraw address has been set by owner. + WithdrawFunds { + amount: Coin, + }, +} + +#[cw_serde] +pub struct Cw721InstantiateMsg { + /// Name of the NFT contract + pub name: String, + /// Symbol of the NFT contract + pub symbol: String, + + /// The minter is the only one who can create new NFTs. + /// This is designed for a base NFT that is controlled by an external program + /// or contract. You will likely replace this with custom logic in custom NFTs + pub minter: Option, + + pub withdraw_address: Option, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum Cw721QueryMsg { + /// Return the owner of the given token, error if token does not exist + #[returns(OwnerOfResponse)] + OwnerOf { + token_id: String, + /// unset or false will filter out expired approvals, you must set to true to see them + include_expired: Option, + }, + /// Return operator that can access all of the owner's tokens. + #[returns(ApprovalResponse)] + Approval { + token_id: String, + spender: String, + include_expired: Option, + }, + /// Return approvals that a token has + #[returns(ApprovalsResponse)] + Approvals { + token_id: String, + include_expired: Option, + }, + /// Return approval of a given operator for all tokens of an owner, error if not set + #[returns(OperatorResponse)] + Operator { + owner: String, + operator: String, + include_expired: Option, + }, + /// List all operators that can access all of the owner's tokens + #[returns(OperatorsResponse)] + AllOperators { + owner: String, + /// unset or false will filter out expired items, you must set to true to see them + include_expired: Option, + start_after: Option, + limit: Option, + }, + /// Total number of tokens issued + #[returns(NumTokensResponse)] + NumTokens {}, + + #[returns(CollectionInfo)] + ContractInfo {}, + + #[returns(Ownership)] + Ownership {}, + + /// With MetaData Extension. + /// Returns metadata about one particular token, based on *ERC721 Metadata JSON Schema* + /// but directly from the contract + #[returns(NftInfoResponse)] + NftInfo { token_id: String }, + /// With MetaData Extension. + /// Returns the result of both `NftInfo` and `OwnerOf` as one query as an optimization + /// for clients + #[returns(AllNftInfoResponse)] + AllNftInfo { + token_id: String, + /// unset or false will filter out expired approvals, you must set to true to see them + include_expired: Option, + }, + + /// With Enumerable extension. + /// Returns all tokens owned by the given address, [] if unset. + #[returns(TokensResponse)] + Tokens { + owner: String, + start_after: Option, + limit: Option, + }, + /// With Enumerable extension. + /// Requires pagination. Lists all token_ids controlled by the contract. + #[returns(TokensResponse)] + AllTokens { + start_after: Option, + limit: Option, + }, + + /// Return the minter + #[returns(MinterResponse)] + Minter {}, + + #[returns(Option)] + GetWithdrawAddress {}, + + // -- below queries, Extension and GetCollectionInfoExtension, are just dummies, since type annotations are required for + // -- TMetadataExtension and TCollectionInfoExtension, Error: + // -- "type annotations needed: cannot infer type for type parameter `TMetadataExtension` declared on the enum `Cw721QueryMsg`" + /// Do not use - dummy extension query, needed for inferring type parameter during compile + #[returns(())] + Extension { + msg: TQueryExtensionMsg, + phantom: Option, // dummy field to infer type + }, +} + +#[cw_serde] +pub enum Cw721MigrateMsg { + WithUpdate { + minter: Option, + creator: Option, + }, +} + +#[cw_serde] +pub struct CollectionInfoMsg { + pub name: String, + pub symbol: String, +} + +#[cw_serde] +pub struct OwnerOfResponse { + /// Owner of the token + pub owner: String, + /// If set this address is approved to transfer/send the token as well + pub approvals: Vec, +} + +#[cw_serde] +pub struct ApprovalResponse { + pub approval: Approval, +} + +#[cw_serde] +pub struct ApprovalsResponse { + pub approvals: Vec, +} + +#[cw_serde] +pub struct OperatorResponse { + pub approval: Approval, +} + +#[cw_serde] +pub struct OperatorsResponse { + pub operators: Vec, +} + +#[cw_serde] +pub struct NumTokensResponse { + pub count: u64, +} + +#[cw_serde] +pub struct NftInfoResponse { + /// Universal resource identifier for this NFT + /// Should point to a JSON file that conforms to the ERC721 + /// Metadata JSON Schema + pub token_uri: Option, + /// You can add any custom metadata here when you extend cw721-base + pub extension: TMetadataExtension, +} + +#[cw_serde] +pub struct AllNftInfoResponse { + /// Who can transfer the token + pub access: OwnerOfResponse, + /// Data on the token itself, + pub info: NftInfoResponse, +} + +#[cw_serde] +pub struct TokensResponse { + /// Contains all token_ids in lexicographical ordering + /// If there are more than `limit`, use `start_after` in future queries + /// to achieve pagination. + pub tokens: Vec, +} + +/// Deprecated: use Cw721QueryMsg::GetMinterOwnership instead! +/// Shows who can mint these tokens. +#[cw_serde] +pub struct MinterResponse { + pub minter: Option, } diff --git a/packages/cw721/src/query.rs b/packages/cw721/src/query.rs index 0c921217d..24013d9f8 100644 --- a/packages/cw721/src/query.rs +++ b/packages/cw721/src/query.rs @@ -1,155 +1,484 @@ -use cosmwasm_schema::cw_serde; -use cw_utils::Expiration; - -#[cw_serde] -pub enum Cw721QueryMsg { - /// Return the owner of the given token, error if token does not exist - /// Return type: OwnerOfResponse - OwnerOf { - token_id: String, - /// unset or false will filter out expired approvals, you must set to true to see them - include_expired: Option, +use cosmwasm_std::{ + to_json_binary, Addr, Binary, BlockInfo, CustomMsg, Deps, Env, Order, StdError, StdResult, + Storage, +}; +use cw_ownable::Ownership; +use cw_storage_plus::Bound; +use cw_utils::{maybe_addr, Expiration}; +use serde::de::DeserializeOwned; +use serde::Serialize; + +use crate::{ + msg::{ + AllNftInfoResponse, ApprovalResponse, ApprovalsResponse, Cw721QueryMsg, MinterResponse, + NftInfoResponse, NumTokensResponse, OperatorResponse, OperatorsResponse, OwnerOfResponse, + TokensResponse, }, - /// Return operator that can access all of the owner's tokens. - /// Return type: `ApprovalResponse` - Approval { + state::{Approval, CollectionInfo, Cw721Config, NftInfo, MINTER}, +}; + +pub const DEFAULT_LIMIT: u32 = 10; +pub const MAX_LIMIT: u32 = 1000; + +pub trait Cw721Query< + // Metadata defined in NftInfo. + TMetadataExtension, + // Defines for `CosmosMsg::Custom` in response. Barely used, so `Empty` can be used. + TCustomResponseMessage, + // Message passed for updating metadata. + TMetadataExtensionMsg, + // Extension query message. + TQueryExtensionMsg, +> where + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TCustomResponseMessage: CustomMsg, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, +{ + fn query( + &self, + deps: Deps, + env: Env, + msg: Cw721QueryMsg, + ) -> StdResult { + match msg { + Cw721QueryMsg::Minter {} => to_json_binary(&self.query_minter(deps.storage)?), + Cw721QueryMsg::ContractInfo {} => { + to_json_binary(&self.query_collection_info(deps, env)?) + } + Cw721QueryMsg::NftInfo { token_id } => { + to_json_binary(&self.query_nft_info(deps, env, token_id)?) + } + Cw721QueryMsg::OwnerOf { + token_id, + include_expired, + } => to_json_binary(&self.query_owner_of( + deps, + env, + token_id, + include_expired.unwrap_or(false), + )?), + Cw721QueryMsg::AllNftInfo { + token_id, + include_expired, + } => to_json_binary(&self.query_all_nft_info( + deps, + env, + token_id, + include_expired.unwrap_or(false), + )?), + Cw721QueryMsg::Operator { + owner, + operator, + include_expired, + } => to_json_binary(&self.query_operator( + deps, + env, + owner, + operator, + include_expired.unwrap_or(false), + )?), + Cw721QueryMsg::AllOperators { + owner, + include_expired, + start_after, + limit, + } => to_json_binary(&self.query_operators( + deps, + env, + owner, + include_expired.unwrap_or(false), + start_after, + limit, + )?), + Cw721QueryMsg::NumTokens {} => to_json_binary(&self.query_num_tokens(deps, env)?), + Cw721QueryMsg::Tokens { + owner, + start_after, + limit, + } => to_json_binary(&self.query_tokens(deps, env, owner, start_after, limit)?), + Cw721QueryMsg::AllTokens { start_after, limit } => { + to_json_binary(&self.query_all_tokens(deps, env, start_after, limit)?) + } + Cw721QueryMsg::Approval { + token_id, + spender, + include_expired, + } => to_json_binary(&self.query_approval( + deps, + env, + token_id, + spender, + include_expired.unwrap_or(false), + )?), + Cw721QueryMsg::Approvals { + token_id, + include_expired, + } => to_json_binary(&self.query_approvals( + deps, + env, + token_id, + include_expired.unwrap_or(false), + )?), + Cw721QueryMsg::Ownership {} => { + to_json_binary(&self.query_minter_ownership(deps.storage)?) + } + Cw721QueryMsg::Extension { msg, .. } => { + to_json_binary(&self.query_extension(deps, env, msg)?) + } + Cw721QueryMsg::GetWithdrawAddress {} => { + to_json_binary(&self.query_withdraw_address(deps)?) + } + } + } + + fn query_minter(&self, storage: &dyn Storage) -> StdResult { + let minter = MINTER + .get_ownership(storage)? + .owner + .map(|a| a.into_string()); + + Ok(MinterResponse { minter }) + } + + fn query_minter_ownership(&self, storage: &dyn Storage) -> StdResult> { + MINTER.get_ownership(storage) + } + + fn query_collection_info(&self, deps: Deps, _env: Env) -> StdResult { + Cw721Config::< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >::default() + .collection_info + .load(deps.storage) + } + + fn query_num_tokens(&self, deps: Deps, _env: Env) -> StdResult { + let count = Cw721Config::< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >::default() + .token_count(deps.storage)?; + Ok(NumTokensResponse { count }) + } + + fn query_nft_info( + &self, + deps: Deps, + _env: Env, token_id: String, - spender: String, - include_expired: Option, - }, - /// Return approvals that a token has - /// Return type: `ApprovalsResponse` - Approvals { + ) -> StdResult> { + let info = Cw721Config::< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >::default() + .nft_info + .load(deps.storage, &token_id)?; + Ok(NftInfoResponse { + token_uri: info.token_uri, + extension: info.extension, + }) + } + + fn query_owner_of( + &self, + deps: Deps, + env: Env, token_id: String, - include_expired: Option, - }, - /// Return approval of a given operator for all tokens of an owner, error if not set - /// Return type: `OperatorResponse` - Operator { + include_expired_approval: bool, + ) -> StdResult { + let nft_info = Cw721Config::< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >::default() + .nft_info + .load(deps.storage, &token_id)?; + Ok(OwnerOfResponse { + owner: nft_info.owner.to_string(), + approvals: humanize_approvals(&env.block, &nft_info, include_expired_approval), + }) + } + + /// operator returns the approval status of an operator for a given owner if exists + fn query_operator( + &self, + deps: Deps, + env: Env, owner: String, operator: String, - include_expired: Option, - }, - /// List all operators that can access all of the owner's tokens - /// Return type: `OperatorsResponse` - AllOperators { + include_expired_approval: bool, + ) -> StdResult { + let owner_addr = deps.api.addr_validate(&owner)?; + let operator_addr = deps.api.addr_validate(&operator)?; + + let info = Cw721Config::< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >::default() + .operators + .may_load(deps.storage, (&owner_addr, &operator_addr))?; + + if let Some(expires) = info { + if !include_expired_approval && expires.is_expired(&env.block) { + return Err(StdError::not_found("Approval not found")); + } + + return Ok(OperatorResponse { + approval: Approval { + spender: operator_addr, + expires, + }, + }); + } + + Err(StdError::not_found("Approval not found")) + } + + /// operators returns all operators owner given access to + fn query_operators( + &self, + deps: Deps, + env: Env, owner: String, - /// unset or false will filter out expired items, you must set to true to see them - include_expired: Option, + include_expired_approval: bool, start_after: Option, limit: Option, - }, - /// Total number of tokens issued - NumTokens {}, - - /// With MetaData Extension. - /// Returns top-level metadata about the contract: `ContractInfoResponse` - ContractInfo {}, - /// With MetaData Extension. - /// Returns metadata about one particular token, based on *ERC721 Metadata JSON Schema* - /// but directly from the contract: `NftInfoResponse` - NftInfo { token_id: String }, - /// With MetaData Extension. - /// Returns the result of both `NftInfo` and `OwnerOf` as one query as an optimization - /// for clients: `AllNftInfo` - AllNftInfo { + ) -> StdResult { + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + let start_addr = maybe_addr(deps.api, start_after)?; + let start = start_addr.as_ref().map(Bound::exclusive); + + let owner_addr = deps.api.addr_validate(&owner)?; + let res: StdResult> = Cw721Config::< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >::default() + .operators + .prefix(&owner_addr) + .range(deps.storage, start, None, Order::Ascending) + .filter(|r| { + include_expired_approval || r.is_err() || !r.as_ref().unwrap().1.is_expired(&env.block) + }) + .take(limit) + .map(parse_approval) + .collect(); + Ok(OperatorsResponse { operators: res? }) + } + + fn query_approval( + &self, + deps: Deps, + env: Env, token_id: String, - /// unset or false will filter out expired approvals, you must set to true to see them - include_expired: Option, - }, + spender: String, + include_expired_approval: bool, + ) -> StdResult { + let token = Cw721Config::< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >::default() + .nft_info + .load(deps.storage, &token_id)?; + + // token owner has absolute approval + if token.owner == spender { + let approval = Approval { + spender: token.owner, + expires: Expiration::Never {}, + }; + return Ok(ApprovalResponse { approval }); + } + + let filtered: Vec<_> = token + .approvals + .into_iter() + .filter(|t| t.spender == spender) + .filter(|t| include_expired_approval || !t.is_expired(&env.block)) + .map(|a| Approval { + spender: a.spender, + expires: a.expires, + }) + .collect(); + + if filtered.is_empty() { + return Err(StdError::not_found("Approval not found")); + } + // we expect only one item + let approval = filtered[0].clone(); + + Ok(ApprovalResponse { approval }) + } + + /// approvals returns all approvals owner given access to + fn query_approvals( + &self, + deps: Deps, + env: Env, + token_id: String, + include_expired_approval: bool, + ) -> StdResult { + let token = Cw721Config::< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >::default() + .nft_info + .load(deps.storage, &token_id)?; + let approvals: Vec<_> = token + .approvals + .into_iter() + .filter(|t| include_expired_approval || !t.is_expired(&env.block)) + .map(|a| Approval { + spender: a.spender, + expires: a.expires, + }) + .collect(); - /// With Enumerable extension. - /// Returns all tokens owned by the given address, [] if unset. - /// Return type: TokensResponse. - Tokens { + Ok(ApprovalsResponse { approvals }) + } + + fn query_tokens( + &self, + deps: Deps, + _env: Env, owner: String, start_after: Option, limit: Option, - }, - /// With Enumerable extension. - /// Requires pagination. Lists all token_ids controlled by the contract. - /// Return type: TokensResponse. - AllTokens { - start_after: Option, - limit: Option, - }, -} + ) -> StdResult { + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + let start = start_after.map(|s| Bound::ExclusiveRaw(s.into())); -#[cw_serde] -pub struct OwnerOfResponse { - /// Owner of the token - pub owner: String, - /// If set this address is approved to transfer/send the token as well - pub approvals: Vec, -} + let owner_addr = deps.api.addr_validate(&owner)?; + let tokens: Vec = Cw721Config::< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >::default() + .nft_info + .idx + .owner + .prefix(owner_addr) + .keys(deps.storage, start, None, Order::Ascending) + .take(limit) + .collect::>>()?; -#[cw_serde] -pub struct Approval { - /// Account that can transfer/send the token - pub spender: String, - /// When the Approval expires (maybe Expiration::never) - pub expires: Expiration, -} + Ok(TokensResponse { tokens }) + } -#[cw_serde] -pub struct ApprovalResponse { - pub approval: Approval, -} + fn query_all_tokens( + &self, + deps: Deps, + _env: Env, + start_after: Option, + limit: Option, + ) -> StdResult { + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + let start = start_after.map(|s| Bound::ExclusiveRaw(s.into())); -#[cw_serde] -pub struct ApprovalsResponse { - pub approvals: Vec, -} + let tokens: StdResult> = Cw721Config::< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >::default() + .nft_info + .range(deps.storage, start, None, Order::Ascending) + .take(limit) + .map(|item| item.map(|(k, _)| k)) + .collect(); -#[cw_serde] -pub struct OperatorResponse { - pub approval: Approval, -} + Ok(TokensResponse { tokens: tokens? }) + } -#[cw_serde] -pub struct OperatorsResponse { - pub operators: Vec, -} - -#[cw_serde] -pub struct NumTokensResponse { - pub count: u64, -} + fn query_all_nft_info( + &self, + deps: Deps, + env: Env, + token_id: String, + include_expired_approval: bool, + ) -> StdResult> { + let nft_info = Cw721Config::< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >::default() + .nft_info + .load(deps.storage, &token_id)?; + Ok(AllNftInfoResponse { + access: OwnerOfResponse { + owner: nft_info.owner.to_string(), + approvals: humanize_approvals(&env.block, &nft_info, include_expired_approval), + }, + info: NftInfoResponse { + token_uri: nft_info.token_uri, + extension: nft_info.extension, + }, + }) + } -#[cw_serde] -pub struct ContractInfoResponse { - pub name: String, - pub symbol: String, -} + /// No-op returning empty Binary + fn query_extension( + &self, + _deps: Deps, + _env: Env, + _msg: TQueryExtensionMsg, + ) -> StdResult { + Ok(Binary::default()) + } -#[cw_serde] -pub struct NftInfoResponse { - /// Universal resource identifier for this NFT - /// Should point to a JSON file that conforms to the ERC721 - /// Metadata JSON Schema - pub token_uri: Option, - /// You can add any custom metadata here when you extend cw721-base - pub extension: T, + fn query_withdraw_address(&self, deps: Deps) -> StdResult> { + Cw721Config::< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >::default() + .withdraw_address + .may_load(deps.storage) + } } -#[cw_serde] -pub struct AllNftInfoResponse { - /// Who can transfer the token - pub access: OwnerOfResponse, - /// Data on the token itself, - pub info: NftInfoResponse, +pub fn parse_approval(item: StdResult<(Addr, Expiration)>) -> StdResult { + item.map(|(spender, expires)| Approval { spender, expires }) } -#[cw_serde] -pub struct TokensResponse { - /// Contains all token_ids in lexicographical ordering - /// If there are more than `limit`, use `start_after` in future queries - /// to achieve pagination. - pub tokens: Vec, +pub fn humanize_approvals( + block: &BlockInfo, + nft_info: &NftInfo, + include_expired_approval: bool, +) -> Vec +where + TMetadataExtension: Serialize + DeserializeOwned + Clone, +{ + nft_info + .approvals + .iter() + .filter(|apr| include_expired_approval || !apr.is_expired(block)) + .map(humanize_approval) + .collect() } -/// Shows who can mint these tokens -#[cw_serde] -pub struct MinterResponse { - pub minter: Option, +pub fn humanize_approval(approval: &Approval) -> Approval { + Approval { + spender: approval.spender.clone(), + expires: approval.expires, + } } diff --git a/packages/cw721/src/receiver.rs b/packages/cw721/src/receiver.rs index e67097927..6d050b29c 100644 --- a/packages/cw721/src/receiver.rs +++ b/packages/cw721/src/receiver.rs @@ -19,9 +19,12 @@ impl Cw721ReceiveMsg { } /// creates a cosmos_msg sending this struct to the named contract - pub fn into_cosmos_msg, C>(self, contract_addr: T) -> StdResult> + pub fn into_cosmos_msg, TCustomResponseMessage>( + self, + contract_addr: TAddress, + ) -> StdResult> where - C: Clone + std::fmt::Debug + PartialEq + JsonSchema, + TCustomResponseMessage: Clone + std::fmt::Debug + PartialEq + JsonSchema, { let msg = self.into_json_binary()?; let execute = WasmMsg::Execute { diff --git a/packages/cw721/src/state.rs b/packages/cw721/src/state.rs new file mode 100644 index 000000000..a7babdbbb --- /dev/null +++ b/packages/cw721/src/state.rs @@ -0,0 +1,205 @@ +use std::marker::PhantomData; + +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, BlockInfo, CustomMsg, StdResult, Storage}; +use cw_ownable::{OwnershipStore, OWNERSHIP_KEY}; +use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, MultiIndex}; +use cw_utils::Expiration; +use serde::de::DeserializeOwned; +use serde::Serialize; + +/// - minter is stored in the contract storage using cw_ownable::OwnershipStore (same as for OWNERSHIP but with different key) +pub const MINTER: OwnershipStore = OwnershipStore::new(OWNERSHIP_KEY); + +/// Default CollectionInfoExtension with RoyaltyInfo +pub type DefaultOptionMetadataExtension = Option; + +pub struct Cw721Config< + 'a, + // Metadata defined in NftInfo (used for mint). + TMetadataExtension, + // Defines for `CosmosMsg::Custom` in response. Barely used, so `Empty` can be used. + TCustomResponseMessage, + // Message passed for updating metadata. + TMetadataExtensionMsg, + // Extension query message. + TQueryExtensionMsg, +> where + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, +{ + /// Note: replaces deprecated/legacy key "nft_info"! + pub collection_info: Item<'a, CollectionInfo>, + pub token_count: Item<'a, u64>, + /// Stored as (granter, operator) giving operator full control over granter's account. + /// NOTE: granter is the owner, so operator has only control for NFTs owned by granter! + pub operators: Map<'a, (&'a Addr, &'a Addr), Expiration>, + pub nft_info: + IndexedMap<'a, &'a str, NftInfo, TokenIndexes<'a, TMetadataExtension>>, + pub withdraw_address: Item<'a, String>, + + pub(crate) _custom_response: PhantomData, + pub(crate) _custom_execute: PhantomData, + pub(crate) _custom_query: PhantomData, +} + +impl Default + for Cw721Config< + 'static, + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + > +where + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, +{ + fn default() -> Self { + Self::new( + "collection_info", // Note: replaces deprecated/legacy key "nft_info" + "num_tokens", + "operators", + "tokens", + "tokens__owner", + "withdraw_address", + ) + } +} + +impl<'a, TMetadataExtension, TCustomResponseMessage, TMetadataExtensionMsg, TQueryExtensionMsg> + Cw721Config< + 'a, + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + > +where + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, +{ + fn new( + collection_info_key: &'a str, + token_count_key: &'a str, + operator_key: &'a str, + nft_info_key: &'a str, + nft_info_owner_key: &'a str, + withdraw_address_key: &'a str, + ) -> Self { + let indexes = TokenIndexes { + owner: MultiIndex::new(token_owner_idx, nft_info_key, nft_info_owner_key), + }; + Self { + collection_info: Item::new(collection_info_key), + token_count: Item::new(token_count_key), + operators: Map::new(operator_key), + nft_info: IndexedMap::new(nft_info_key, indexes), + withdraw_address: Item::new(withdraw_address_key), + _custom_response: PhantomData, + _custom_execute: PhantomData, + _custom_query: PhantomData, + } + } + + pub fn token_count(&self, storage: &dyn Storage) -> StdResult { + Ok(self.token_count.may_load(storage)?.unwrap_or_default()) + } + + pub fn increment_tokens(&self, storage: &mut dyn Storage) -> StdResult { + let val = self.token_count(storage)? + 1; + self.token_count.save(storage, &val)?; + Ok(val) + } + + pub fn decrement_tokens(&self, storage: &mut dyn Storage) -> StdResult { + let val = self.token_count(storage)? - 1; + self.token_count.save(storage, &val)?; + Ok(val) + } +} + +pub fn token_owner_idx(_pk: &[u8], d: &NftInfo) -> Addr { + d.owner.clone() +} + +#[cw_serde] +pub struct NftInfo { + /// The owner of the newly minted NFT + pub owner: Addr, + /// Approvals are stored here, as we clear them all upon transfer and cannot accumulate much + pub approvals: Vec, + + /// Universal resource identifier for this NFT + /// Should point to a JSON file that conforms to the ERC721 + /// Metadata JSON Schema + pub token_uri: Option, + + /// You can add any custom metadata here when you extend cw721-base + pub extension: TMetadataExtension, +} + +#[cw_serde] +pub struct Approval { + /// Account that can transfer/send the token + pub spender: Addr, + /// When the Approval expires (maybe Expiration::never) + pub expires: Expiration, +} + +impl Approval { + pub fn is_expired(&self, block: &BlockInfo) -> bool { + self.expires.is_expired(block) + } +} + +pub struct TokenIndexes<'a, TMetadataExtension> +where + TMetadataExtension: Serialize + DeserializeOwned + Clone, +{ + pub owner: MultiIndex<'a, Addr, NftInfo, String>, +} + +impl<'a, TMetadataExtension> IndexList> + for TokenIndexes<'a, TMetadataExtension> +where + TMetadataExtension: Serialize + DeserializeOwned + Clone, +{ + fn get_indexes( + &'_ self, + ) -> Box>> + '_> { + let v: Vec<&dyn Index>> = vec![&self.owner]; + Box::new(v.into_iter()) + } +} + +#[cw_serde] +pub struct CollectionInfo { + pub name: String, + pub symbol: String, +} + +// see: https://docs.opensea.io/docs/metadata-standards +#[cw_serde] +#[derive(Default)] +pub struct Metadata { + pub image: Option, + pub image_data: Option, + pub external_url: Option, + pub description: Option, + pub name: Option, + pub attributes: Option>, + pub background_color: Option, + pub animation_url: Option, + pub youtube_url: Option, +} + +#[cw_serde] +pub struct Trait { + pub display_type: Option, + pub trait_type: String, + pub value: String, +} diff --git a/packages/cw721/src/testing/contract.rs b/packages/cw721/src/testing/contract.rs new file mode 100644 index 000000000..1158f42fd --- /dev/null +++ b/packages/cw721/src/testing/contract.rs @@ -0,0 +1,93 @@ +use cosmwasm_std::CustomMsg; +use serde::de::DeserializeOwned; +use serde::Serialize; + +use crate::execute::Cw721Execute; +use crate::query::Cw721Query; +use crate::state::Cw721Config; + +pub struct Cw721Contract< + 'a, + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, +> where + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TCustomResponseMessage: CustomMsg, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, +{ + pub config: Cw721Config< + 'a, + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + >, +} + +impl Default + for Cw721Contract< + 'static, + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + > +where + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TCustomResponseMessage: CustomMsg, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, +{ + fn default() -> Self { + Self { + config: Cw721Config::default(), + } + } +} + +impl<'a, TMetadataExtension, TCustomResponseMessage, TMetadataExtensionMsg, TQueryExtensionMsg> + Cw721Execute< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + > + for Cw721Contract< + 'a, + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + > +where + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TCustomResponseMessage: CustomMsg, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, +{ +} + +impl<'a, TMetadataExtension, TCustomResponseMessage, TMetadataExtensionMsg, TQueryExtensionMsg> + Cw721Query< + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + > + for Cw721Contract< + 'a, + TMetadataExtension, + TCustomResponseMessage, + TMetadataExtensionMsg, + TQueryExtensionMsg, + > +where + TMetadataExtension: Serialize + DeserializeOwned + Clone, + TCustomResponseMessage: CustomMsg, + TMetadataExtensionMsg: CustomMsg, + TQueryExtensionMsg: Serialize + DeserializeOwned + Clone, +{ +} diff --git a/contracts/cw721-base/src/contract_tests.rs b/packages/cw721/src/testing/contract_tests.rs similarity index 67% rename from contracts/cw721-base/src/contract_tests.rs rename to packages/cw721/src/testing/contract_tests.rs index b789748a8..2e4f4776f 100644 --- a/contracts/cw721-base/src/contract_tests.rs +++ b/packages/cw721/src/testing/contract_tests.rs @@ -1,32 +1,49 @@ #![cfg(test)] + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; use cosmwasm_std::{ from_json, to_json_binary, Addr, Coin, CosmosMsg, DepsMut, Empty, Response, StdError, WasmMsg, }; -use cw721::{ - Approval, ApprovalResponse, ContractInfoResponse, Cw721Query, Cw721ReceiveMsg, Expiration, - MinterResponse, NftInfoResponse, OperatorResponse, OperatorsResponse, OwnerOfResponse, +use crate::error::Cw721ContractError; +use crate::msg::{ + ApprovalResponse, NftInfoResponse, OperatorResponse, OperatorsResponse, OwnerOfResponse, }; -use cw_ownable::OwnershipError; +use crate::msg::{Cw721ExecuteMsg, Cw721InstantiateMsg, Cw721QueryMsg}; +use crate::receiver::Cw721ReceiveMsg; +use crate::state::{CollectionInfo, DefaultOptionMetadataExtension, MINTER}; +use crate::{execute::Cw721Execute, query::Cw721Query, Approval, Expiration}; +use cw_ownable::{Action, Ownership, OwnershipError}; -use crate::{ContractError, Cw721Contract, ExecuteMsg, Extension, InstantiateMsg, QueryMsg}; +use super::contract::Cw721Contract; -const MINTER: &str = "merlin"; +const MINTER_ADDR: &str = "minter"; +const CREATOR_ADDR: &str = "creator"; const CONTRACT_NAME: &str = "Magic Power"; const SYMBOL: &str = "MGK"; -fn setup_contract(deps: DepsMut<'_>) -> Cw721Contract<'static, Extension, Empty, Empty, Empty> { +fn setup_contract( + deps: DepsMut<'_>, +) -> Cw721Contract<'static, DefaultOptionMetadataExtension, Empty, Empty, Empty> { let contract = Cw721Contract::default(); - let msg = InstantiateMsg { + let msg = Cw721InstantiateMsg { name: CONTRACT_NAME.to_string(), symbol: SYMBOL.to_string(), - minter: Some(String::from(MINTER)), + minter: Some(String::from(MINTER_ADDR)), withdraw_address: None, }; let info = mock_info("creator", &[]); - let res = contract.instantiate(deps, mock_env(), info, msg).unwrap(); + let res = contract + .instantiate( + deps, + mock_env(), + info, + msg, + "contract_name", + "contract_version", + ) + .unwrap(); assert_eq!(0, res.messages.len()); contract } @@ -34,45 +51,120 @@ fn setup_contract(deps: DepsMut<'_>) -> Cw721Contract<'static, Extension, Empty, #[test] fn proper_instantiation() { let mut deps = mock_dependencies(); - let contract = Cw721Contract::::default(); + let contract = Cw721Contract::::default(); - let msg = InstantiateMsg { + let msg = Cw721InstantiateMsg { name: CONTRACT_NAME.to_string(), symbol: SYMBOL.to_string(), - minter: Some(String::from(MINTER)), - withdraw_address: Some(String::from(MINTER)), + minter: Some(String::from(MINTER_ADDR)), + withdraw_address: Some(String::from(CREATOR_ADDR)), }; let info = mock_info("creator", &[]); + let env = mock_env(); // we can just call .unwrap() to assert this was a success let res = contract - .instantiate(deps.as_mut(), mock_env(), info, msg) + .instantiate( + deps.as_mut(), + env.clone(), + info, + msg, + "contract_name", + "contract_version", + ) .unwrap(); assert_eq!(0, res.messages.len()); // it worked, let's query the state - let res = contract.minter(deps.as_ref()).unwrap(); - assert_eq!(Some(MINTER.to_string()), res.minter); - let info = contract.contract_info(deps.as_ref()).unwrap(); + let minter_ownership = MINTER.get_ownership(deps.as_ref().storage).unwrap(); + assert_eq!(Some(Addr::unchecked(MINTER_ADDR)), minter_ownership.owner); + let collection_info = contract + .query_collection_info(deps.as_ref(), env.clone()) + .unwrap(); + assert_eq!( + collection_info, + CollectionInfo { + name: CONTRACT_NAME.to_string(), + symbol: SYMBOL.to_string(), + } + ); + + let withdraw_address = contract + .config + .withdraw_address + .may_load(deps.as_ref().storage) + .unwrap(); + assert_eq!(Some(CREATOR_ADDR.to_string()), withdraw_address); + + let count = contract + .query_num_tokens(deps.as_ref(), env.clone()) + .unwrap(); + assert_eq!(0, count.count); + + // list the token_ids + let tokens = contract + .query_all_tokens(deps.as_ref(), env, None, None) + .unwrap(); + assert_eq!(0, tokens.tokens.len()); +} + +#[test] +fn proper_instantiation_with_collection_info() { + let mut deps = mock_dependencies(); + let contract = Cw721Contract::::default(); + + let msg = Cw721InstantiateMsg { + name: CONTRACT_NAME.to_string(), + symbol: SYMBOL.to_string(), + minter: Some(String::from(MINTER_ADDR)), + withdraw_address: Some(String::from(CREATOR_ADDR)), + }; + let collection_info = mock_info("creator", &[]); + let env = mock_env(); + + // we can just call .unwrap() to assert this was a success + let res = contract + .instantiate( + deps.as_mut(), + env.clone(), + collection_info, + msg, + "contract_name", + "contract_version", + ) + .unwrap(); + assert_eq!(0, res.messages.len()); + + // it worked, let's query the state + let minter_ownership = MINTER.get_ownership(deps.as_ref().storage).unwrap(); + assert_eq!(Some(Addr::unchecked(MINTER_ADDR)), minter_ownership.owner); + let info = contract + .query_collection_info(deps.as_ref(), env.clone()) + .unwrap(); assert_eq!( info, - ContractInfoResponse { + CollectionInfo { name: CONTRACT_NAME.to_string(), symbol: SYMBOL.to_string(), } ); let withdraw_address = contract + .config .withdraw_address .may_load(deps.as_ref().storage) .unwrap(); - assert_eq!(Some(MINTER.to_string()), withdraw_address); + assert_eq!(Some(CREATOR_ADDR.to_string()), withdraw_address); - let count = contract.num_tokens(deps.as_ref()).unwrap(); + let count = contract + .query_num_tokens(deps.as_ref(), env.clone()) + .unwrap(); assert_eq!(0, count.count); // list the token_ids - let tokens = contract.all_tokens(deps.as_ref(), None, None).unwrap(); + let tokens = contract + .query_all_tokens(deps.as_ref(), env, None, None) + .unwrap(); assert_eq!(0, tokens.tokens.len()); } @@ -84,7 +176,7 @@ fn minting() { let token_id = "petrify".to_string(); let token_uri = "https://www.merriam-webster.com/dictionary/petrify".to_string(); - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id: token_id.clone(), owner: String::from("medusa"), token_uri: Some(token_uri.clone()), @@ -93,31 +185,36 @@ fn minting() { // random cannot mint let random = mock_info("random", &[]); + let env = mock_env(); let err = contract - .execute(deps.as_mut(), mock_env(), random, mint_msg.clone()) + .execute(deps.as_mut(), env.clone(), random, mint_msg.clone()) .unwrap_err(); - assert_eq!(err, ContractError::Ownership(OwnershipError::NotOwner)); + assert_eq!(err, Cw721ContractError::Ownership(OwnershipError::NotOwner)); // minter can mint - let allowed = mock_info(MINTER, &[]); + let allowed = mock_info(MINTER_ADDR, &[]); let _ = contract - .execute(deps.as_mut(), mock_env(), allowed, mint_msg) + .execute(deps.as_mut(), env.clone(), allowed, mint_msg) .unwrap(); // ensure num tokens increases - let count = contract.num_tokens(deps.as_ref()).unwrap(); + let count = contract + .query_num_tokens(deps.as_ref(), env.clone()) + .unwrap(); assert_eq!(1, count.count); // unknown nft returns error let _ = contract - .nft_info(deps.as_ref(), "unknown".to_string()) + .query_nft_info(deps.as_ref(), env.clone(), "unknown".to_string()) .unwrap_err(); // this nft info is correct - let info = contract.nft_info(deps.as_ref(), token_id.clone()).unwrap(); + let info = contract + .query_nft_info(deps.as_ref(), env.clone(), token_id.clone()) + .unwrap(); assert_eq!( info, - NftInfoResponse:: { + NftInfoResponse:: { token_uri: Some(token_uri), extension: None, } @@ -125,7 +222,7 @@ fn minting() { // owner info is correct let owner = contract - .owner_of(deps.as_ref(), mock_env(), token_id.clone(), true) + .query_owner_of(deps.as_ref(), mock_env(), token_id.clone(), true) .unwrap(); assert_eq!( owner, @@ -136,21 +233,23 @@ fn minting() { ); // Cannot mint same token_id again - let mint_msg2 = ExecuteMsg::Mint { + let mint_msg2 = Cw721ExecuteMsg::Mint { token_id: token_id.clone(), owner: String::from("hercules"), token_uri: None, extension: None, }; - let allowed = mock_info(MINTER, &[]); + let allowed = mock_info(MINTER_ADDR, &[]); let err = contract .execute(deps.as_mut(), mock_env(), allowed, mint_msg2) .unwrap_err(); - assert_eq!(err, ContractError::Claimed {}); + assert_eq!(err, Cw721ContractError::Claimed {}); // list the token_ids - let tokens = contract.all_tokens(deps.as_ref(), None, None).unwrap(); + let tokens = contract + .query_all_tokens(deps.as_ref(), env, None, None) + .unwrap(); assert_eq!(1, tokens.tokens.len()); assert_eq!(vec![token_id], tokens.tokens); } @@ -163,7 +262,7 @@ fn test_update_minter() { let token_id = "petrify".to_string(); let token_uri = "https://www.merriam-webster.com/dictionary/petrify".to_string(); - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id, owner: String::from("medusa"), token_uri: Some(token_uri.clone()), @@ -171,7 +270,7 @@ fn test_update_minter() { }; // Minter can mint - let minter_info = mock_info(MINTER, &[]); + let minter_info = mock_info(MINTER_ADDR, &[]); let _ = contract .execute(deps.as_mut(), mock_env(), minter_info.clone(), mint_msg) .unwrap(); @@ -183,7 +282,7 @@ fn test_update_minter() { deps.as_mut(), mock_env(), minter_info.clone(), - ExecuteMsg::UpdateOwnership(cw_ownable::Action::TransferOwnership { + Cw721ExecuteMsg::UpdateOwnership(Action::TransferOwnership { new_owner: "random".to_string(), expiry: None, }), @@ -191,26 +290,18 @@ fn test_update_minter() { .unwrap(); // Minter does not change until ownership transfer completes. - let minter: MinterResponse = from_json( - contract - .query(deps.as_ref(), mock_env(), QueryMsg::Minter {}) - .unwrap(), - ) - .unwrap(); - assert_eq!(minter.minter, Some(MINTER.to_string())); - // Pending ownership transfer should be discoverable via query. - let ownership: cw_ownable::Ownership = from_json( + let ownership: Ownership = from_json( contract - .query(deps.as_ref(), mock_env(), QueryMsg::Ownership {}) + .query(deps.as_ref(), mock_env(), Cw721QueryMsg::Ownership {}) .unwrap(), ) .unwrap(); assert_eq!( ownership, - cw_ownable::Ownership:: { - owner: Some(Addr::unchecked(MINTER)), + Ownership:: { + owner: Some(Addr::unchecked(MINTER_ADDR)), pending_owner: Some(Addr::unchecked("random")), pending_expiry: None, } @@ -223,20 +314,20 @@ fn test_update_minter() { deps.as_mut(), mock_env(), random_info.clone(), - ExecuteMsg::UpdateOwnership(cw_ownable::Action::AcceptOwnership), + Cw721ExecuteMsg::UpdateOwnership(Action::AcceptOwnership), ) .unwrap(); // Minter changes after ownership transfer is accepted. - let minter: MinterResponse = from_json( + let minter_ownership: Ownership = from_json( contract - .query(deps.as_ref(), mock_env(), QueryMsg::Minter {}) + .query(deps.as_ref(), mock_env(), Cw721QueryMsg::Ownership {}) .unwrap(), ) .unwrap(); - assert_eq!(minter.minter, Some("random".to_string())); + assert_eq!(minter_ownership.owner, Some(random_info.sender.clone())); - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id: "randoms_token".to_string(), owner: String::from("medusa"), token_uri: Some(token_uri), @@ -244,10 +335,10 @@ fn test_update_minter() { }; // Old owner can not mint. - let err: ContractError = contract + let err: Cw721ContractError = contract .execute(deps.as_mut(), mock_env(), minter_info, mint_msg.clone()) .unwrap_err(); - assert_eq!(err, ContractError::Ownership(OwnershipError::NotOwner)); + assert_eq!(err, Cw721ContractError::Ownership(OwnershipError::NotOwner)); // New owner can mint. let _ = contract @@ -263,44 +354,49 @@ fn burning() { let token_id = "petrify".to_string(); let token_uri = "https://www.merriam-webster.com/dictionary/petrify".to_string(); - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id: token_id.clone(), - owner: MINTER.to_string(), + owner: MINTER_ADDR.to_string(), token_uri: Some(token_uri), extension: None, }; - let burn_msg = ExecuteMsg::Burn { token_id }; + let burn_msg = Cw721ExecuteMsg::Burn { token_id }; // mint some NFT - let allowed = mock_info(MINTER, &[]); + let allowed = mock_info(MINTER_ADDR, &[]); let _ = contract .execute(deps.as_mut(), mock_env(), allowed.clone(), mint_msg) .unwrap(); // random not allowed to burn let random = mock_info("random", &[]); + let env = mock_env(); let err = contract - .execute(deps.as_mut(), mock_env(), random, burn_msg.clone()) + .execute(deps.as_mut(), env.clone(), random, burn_msg.clone()) .unwrap_err(); - assert_eq!(err, ContractError::Ownership(OwnershipError::NotOwner)); + assert_eq!(err, Cw721ContractError::Ownership(OwnershipError::NotOwner)); let _ = contract - .execute(deps.as_mut(), mock_env(), allowed, burn_msg) + .execute(deps.as_mut(), env.clone(), allowed, burn_msg) .unwrap(); // ensure num tokens decreases - let count = contract.num_tokens(deps.as_ref()).unwrap(); + let count = contract + .query_num_tokens(deps.as_ref(), env.clone()) + .unwrap(); assert_eq!(0, count.count); // trying to get nft returns error let _ = contract - .nft_info(deps.as_ref(), "petrify".to_string()) + .query_nft_info(deps.as_ref(), env.clone(), "petrify".to_string()) .unwrap_err(); // list the token_ids - let tokens = contract.all_tokens(deps.as_ref(), None, None).unwrap(); + let tokens = contract + .query_all_tokens(deps.as_ref(), env, None, None) + .unwrap(); assert!(tokens.tokens.is_empty()); } @@ -313,21 +409,21 @@ fn transferring_nft() { let token_id = "melt".to_string(); let token_uri = "https://www.merriam-webster.com/dictionary/melt".to_string(); - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id: token_id.clone(), owner: String::from("venus"), token_uri: Some(token_uri), extension: None, }; - let minter = mock_info(MINTER, &[]); + let minter = mock_info(MINTER_ADDR, &[]); contract .execute(deps.as_mut(), mock_env(), minter, mint_msg) .unwrap(); // random cannot transfer let random = mock_info("random", &[]); - let transfer_msg = ExecuteMsg::TransferNft { + let transfer_msg = Cw721ExecuteMsg::TransferNft { recipient: String::from("random"), token_id: token_id.clone(), }; @@ -335,11 +431,11 @@ fn transferring_nft() { let err = contract .execute(deps.as_mut(), mock_env(), random, transfer_msg) .unwrap_err(); - assert_eq!(err, ContractError::Ownership(OwnershipError::NotOwner)); + assert_eq!(err, Cw721ContractError::Ownership(OwnershipError::NotOwner)); // owner can let random = mock_info("venus", &[]); - let transfer_msg = ExecuteMsg::TransferNft { + let transfer_msg = Cw721ExecuteMsg::TransferNft { recipient: String::from("random"), token_id: token_id.clone(), }; @@ -367,21 +463,21 @@ fn sending_nft() { let token_id = "melt".to_string(); let token_uri = "https://www.merriam-webster.com/dictionary/melt".to_string(); - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id: token_id.clone(), owner: String::from("venus"), token_uri: Some(token_uri), extension: None, }; - let minter = mock_info(MINTER, &[]); + let minter = mock_info(MINTER_ADDR, &[]); contract .execute(deps.as_mut(), mock_env(), minter, mint_msg) .unwrap(); let msg = to_json_binary("You now have the melting power").unwrap(); let target = String::from("another_contract"); - let send_msg = ExecuteMsg::SendNft { + let send_msg = Cw721ExecuteMsg::SendNft { contract: target.clone(), token_id: token_id.clone(), msg: msg.clone(), @@ -391,7 +487,7 @@ fn sending_nft() { let err = contract .execute(deps.as_mut(), mock_env(), random, send_msg.clone()) .unwrap_err(); - assert_eq!(err, ContractError::Ownership(OwnershipError::NotOwner)); + assert_eq!(err, Cw721ContractError::Ownership(OwnershipError::NotOwner)); // but owner can let random = mock_info("venus", &[]); @@ -433,21 +529,21 @@ fn approving_revoking() { let token_id = "grow".to_string(); let token_uri = "https://www.merriam-webster.com/dictionary/grow".to_string(); - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id: token_id.clone(), owner: String::from("demeter"), token_uri: Some(token_uri), extension: None, }; - let minter = mock_info(MINTER, &[]); + let minter = mock_info(MINTER_ADDR, &[]); contract .execute(deps.as_mut(), mock_env(), minter, mint_msg) .unwrap(); // token owner shows in approval query let res = contract - .approval( + .query_approval( deps.as_ref(), mock_env(), token_id.clone(), @@ -459,14 +555,14 @@ fn approving_revoking() { res, ApprovalResponse { approval: Approval { - spender: String::from("demeter"), + spender: Addr::unchecked("demeter"), expires: Expiration::Never {} } } ); // Give random transferring power - let approve_msg = ExecuteMsg::Approve { + let approve_msg = Cw721ExecuteMsg::Approve { spender: String::from("random"), token_id: token_id.clone(), expires: None, @@ -486,7 +582,7 @@ fn approving_revoking() { // test approval query let res = contract - .approval( + .query_approval( deps.as_ref(), mock_env(), token_id.clone(), @@ -498,7 +594,7 @@ fn approving_revoking() { res, ApprovalResponse { approval: Approval { - spender: String::from("random"), + spender: Addr::unchecked("random"), expires: Expiration::Never {} } } @@ -506,7 +602,7 @@ fn approving_revoking() { // random can now transfer let random = mock_info("random", &[]); - let transfer_msg = ExecuteMsg::TransferNft { + let transfer_msg = Cw721ExecuteMsg::TransferNft { recipient: String::from("person"), token_id: token_id.clone(), }; @@ -515,7 +611,7 @@ fn approving_revoking() { .unwrap(); // Approvals are removed / cleared - let query_msg = QueryMsg::OwnerOf { + let query_msg = Cw721QueryMsg::OwnerOf { token_id: token_id.clone(), include_expired: None, }; @@ -534,7 +630,7 @@ fn approving_revoking() { ); // Approve, revoke, and check for empty, to test revoke - let approve_msg = ExecuteMsg::Approve { + let approve_msg = Cw721ExecuteMsg::Approve { spender: String::from("random"), token_id: token_id.clone(), expires: None, @@ -544,7 +640,7 @@ fn approving_revoking() { .execute(deps.as_mut(), mock_env(), owner.clone(), approve_msg) .unwrap(); - let revoke_msg = ExecuteMsg::Revoke { + let revoke_msg = Cw721ExecuteMsg::Revoke { spender: String::from("random"), token_id, }; @@ -580,41 +676,44 @@ fn approving_all_revoking_all() { let token_id2 = "grow2".to_string(); let token_uri2 = "https://www.merriam-webster.com/dictionary/grow2".to_string(); - let mint_msg1 = ExecuteMsg::Mint { + let mint_msg1 = Cw721ExecuteMsg::Mint { token_id: token_id1.clone(), owner: String::from("demeter"), token_uri: Some(token_uri1), extension: None, }; - let minter = mock_info(MINTER, &[]); + let minter = mock_info(MINTER_ADDR, &[]); contract .execute(deps.as_mut(), mock_env(), minter.clone(), mint_msg1) .unwrap(); - let mint_msg2 = ExecuteMsg::Mint { + let mint_msg2 = Cw721ExecuteMsg::Mint { token_id: token_id2.clone(), owner: String::from("demeter"), token_uri: Some(token_uri2), extension: None, }; + let env = mock_env(); contract - .execute(deps.as_mut(), mock_env(), minter, mint_msg2) + .execute(deps.as_mut(), env.clone(), minter, mint_msg2) .unwrap(); // paginate the token_ids - let tokens = contract.all_tokens(deps.as_ref(), None, Some(1)).unwrap(); + let tokens = contract + .query_all_tokens(deps.as_ref(), env.clone(), None, Some(1)) + .unwrap(); assert_eq!(1, tokens.tokens.len()); assert_eq!(vec![token_id1.clone()], tokens.tokens); let tokens = contract - .all_tokens(deps.as_ref(), Some(token_id1.clone()), Some(3)) + .query_all_tokens(deps.as_ref(), env, Some(token_id1.clone()), Some(3)) .unwrap(); assert_eq!(1, tokens.tokens.len()); assert_eq!(vec![token_id2.clone()], tokens.tokens); // demeter gives random full (operator) power over her tokens - let approve_all_msg = ExecuteMsg::ApproveAll { + let approve_all_msg = Cw721ExecuteMsg::ApproveAll { operator: String::from("random"), expires: None, }; @@ -632,7 +731,7 @@ fn approving_all_revoking_all() { // random can now transfer let random = mock_info("random", &[]); - let transfer_msg = ExecuteMsg::TransferNft { + let transfer_msg = Cw721ExecuteMsg::TransferNft { recipient: String::from("person"), token_id: token_id1, }; @@ -648,7 +747,7 @@ fn approving_all_revoking_all() { }; let msg: CosmosMsg = CosmosMsg::Wasm(inner_msg); - let send_msg = ExecuteMsg::SendNft { + let send_msg = Cw721ExecuteMsg::SendNft { contract: String::from("another_contract"), token_id: token_id2, msg: to_json_binary(&msg).unwrap(), @@ -658,7 +757,7 @@ fn approving_all_revoking_all() { .unwrap(); // Approve_all, revoke_all, and check for empty, to test revoke_all - let approve_all_msg = ExecuteMsg::ApproveAll { + let approve_all_msg = Cw721ExecuteMsg::ApproveAll { operator: String::from("operator"), expires: None, }; @@ -670,7 +769,7 @@ fn approving_all_revoking_all() { // query for operator should return approval let res = contract - .operator( + .query_operator( deps.as_ref(), mock_env(), String::from("person"), @@ -682,14 +781,14 @@ fn approving_all_revoking_all() { res, OperatorResponse { approval: Approval { - spender: String::from("operator"), + spender: Addr::unchecked("operator"), expires: Expiration::Never {} } } ); // query for other should throw error - let res = contract.operator( + let res = contract.query_operator( deps.as_ref(), mock_env(), String::from("person"), @@ -702,7 +801,7 @@ fn approving_all_revoking_all() { } let res = contract - .operators( + .query_operators( deps.as_ref(), mock_env(), String::from("person"), @@ -714,8 +813,8 @@ fn approving_all_revoking_all() { assert_eq!( res, OperatorsResponse { - operators: vec![cw721::Approval { - spender: String::from("operator"), + operators: vec![Approval { + spender: Addr::unchecked("operator"), expires: Expiration::Never {} }] } @@ -723,7 +822,7 @@ fn approving_all_revoking_all() { // second approval let buddy_expires = Expiration::AtHeight(1234567); - let approve_all_msg = ExecuteMsg::ApproveAll { + let approve_all_msg = Cw721ExecuteMsg::ApproveAll { operator: String::from("buddy"), expires: Some(buddy_expires), }; @@ -734,7 +833,7 @@ fn approving_all_revoking_all() { // and paginate queries let res = contract - .operators( + .query_operators( deps.as_ref(), mock_env(), String::from("person"), @@ -746,14 +845,14 @@ fn approving_all_revoking_all() { assert_eq!( res, OperatorsResponse { - operators: vec![cw721::Approval { - spender: String::from("buddy"), + operators: vec![Approval { + spender: Addr::unchecked("buddy"), expires: buddy_expires, }] } ); let res = contract - .operators( + .query_operators( deps.as_ref(), mock_env(), String::from("person"), @@ -765,14 +864,14 @@ fn approving_all_revoking_all() { assert_eq!( res, OperatorsResponse { - operators: vec![cw721::Approval { - spender: String::from("operator"), + operators: vec![Approval { + spender: Addr::unchecked("operator"), expires: Expiration::Never {} }] } ); - let revoke_all_msg = ExecuteMsg::RevokeAll { + let revoke_all_msg = Cw721ExecuteMsg::RevokeAll { operator: String::from("operator"), }; contract @@ -780,7 +879,7 @@ fn approving_all_revoking_all() { .unwrap(); // query for operator should return error - let res = contract.operator( + let res = contract.query_operator( deps.as_ref(), mock_env(), String::from("person"), @@ -794,7 +893,7 @@ fn approving_all_revoking_all() { // Approvals are removed / cleared without affecting others let res = contract - .operators( + .query_operators( deps.as_ref(), mock_env(), String::from("person"), @@ -806,8 +905,8 @@ fn approving_all_revoking_all() { assert_eq!( res, OperatorsResponse { - operators: vec![cw721::Approval { - spender: String::from("buddy"), + operators: vec![Approval { + spender: Addr::unchecked("buddy"), expires: buddy_expires, }] } @@ -817,7 +916,7 @@ fn approving_all_revoking_all() { let mut late_env = mock_env(); late_env.block.height = 1234568; //expired let res = contract - .operators( + .query_operators( deps.as_ref(), late_env.clone(), String::from("person"), @@ -829,7 +928,7 @@ fn approving_all_revoking_all() { assert_eq!(0, res.operators.len()); // query operator should also return error - let res = contract.operator( + let res = contract.query_operator( deps.as_ref(), late_env, String::from("person"), @@ -848,18 +947,23 @@ fn test_set_withdraw_address() { let mut deps = mock_dependencies(); let contract = setup_contract(deps.as_mut()); - // other cant set + // other than minter cant set let err = contract .set_withdraw_address(deps.as_mut(), &Addr::unchecked("other"), "foo".to_string()) .unwrap_err(); - assert_eq!(err, ContractError::Ownership(OwnershipError::NotOwner)); + assert_eq!(err, Cw721ContractError::Ownership(OwnershipError::NotOwner)); // minter can set contract - .set_withdraw_address(deps.as_mut(), &Addr::unchecked(MINTER), "foo".to_string()) + .set_withdraw_address( + deps.as_mut(), + &Addr::unchecked(MINTER_ADDR), + "foo".to_string(), + ) .unwrap(); let withdraw_address = contract + .config .withdraw_address .load(deps.as_ref().storage) .unwrap(); @@ -871,32 +975,44 @@ fn test_remove_withdraw_address() { let mut deps = mock_dependencies(); let contract = setup_contract(deps.as_mut()); - // other cant remove + // other than creator cant remove let err = contract .remove_withdraw_address(deps.as_mut().storage, &Addr::unchecked("other")) .unwrap_err(); - assert_eq!(err, ContractError::Ownership(OwnershipError::NotOwner)); + assert_eq!(err, Cw721ContractError::Ownership(OwnershipError::NotOwner)); - // no owner set yet + // no withdraw address set yet let err = contract - .remove_withdraw_address(deps.as_mut().storage, &Addr::unchecked(MINTER)) + .remove_withdraw_address(deps.as_mut().storage, &Addr::unchecked(MINTER_ADDR)) .unwrap_err(); - assert_eq!(err, ContractError::NoWithdrawAddress {}); + assert_eq!(err, Cw721ContractError::NoWithdrawAddress {}); // set and remove contract - .set_withdraw_address(deps.as_mut(), &Addr::unchecked(MINTER), "foo".to_string()) + .set_withdraw_address( + deps.as_mut(), + &Addr::unchecked(MINTER_ADDR), + "foo".to_string(), + ) .unwrap(); contract - .remove_withdraw_address(deps.as_mut().storage, &Addr::unchecked(MINTER)) + .remove_withdraw_address(deps.as_mut().storage, &Addr::unchecked(MINTER_ADDR)) .unwrap(); - assert!(!contract.withdraw_address.exists(deps.as_ref().storage)); + assert!(!contract + .config + .withdraw_address + .exists(deps.as_ref().storage)); // test that we can set again contract - .set_withdraw_address(deps.as_mut(), &Addr::unchecked(MINTER), "foo".to_string()) + .set_withdraw_address( + deps.as_mut(), + &Addr::unchecked(MINTER_ADDR), + "foo".to_string(), + ) .unwrap(); let withdraw_address = contract + .config .withdraw_address .load(deps.as_ref().storage) .unwrap(); @@ -912,11 +1028,15 @@ fn test_withdraw_funds() { let err = contract .withdraw_funds(deps.as_mut().storage, &Coin::new(100, "uark")) .unwrap_err(); - assert_eq!(err, ContractError::NoWithdrawAddress {}); + assert_eq!(err, Cw721ContractError::NoWithdrawAddress {}); // set and withdraw by non-owner contract - .set_withdraw_address(deps.as_mut(), &Addr::unchecked(MINTER), "foo".to_string()) + .set_withdraw_address( + deps.as_mut(), + &Addr::unchecked(MINTER_ADDR), + "foo".to_string(), + ) .unwrap(); contract .withdraw_funds(deps.as_mut().storage, &Coin::new(100, "uark")) @@ -927,7 +1047,7 @@ fn test_withdraw_funds() { fn query_tokens_by_owner() { let mut deps = mock_dependencies(); let contract = setup_contract(deps.as_mut()); - let minter = mock_info(MINTER, &[]); + let minter = mock_info(MINTER_ADDR, &[]); // Mint a couple tokens (from the same owner) let token_id1 = "grow1".to_string(); @@ -936,7 +1056,7 @@ fn query_tokens_by_owner() { let ceres = String::from("ceres"); let token_id3 = "sing".to_string(); - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id: token_id1.clone(), owner: demeter.clone(), token_uri: None, @@ -946,7 +1066,7 @@ fn query_tokens_by_owner() { .execute(deps.as_mut(), mock_env(), minter.clone(), mint_msg) .unwrap(); - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id: token_id2.clone(), owner: ceres.clone(), token_uri: None, @@ -956,25 +1076,30 @@ fn query_tokens_by_owner() { .execute(deps.as_mut(), mock_env(), minter.clone(), mint_msg) .unwrap(); - let mint_msg = ExecuteMsg::Mint { + let mint_msg = Cw721ExecuteMsg::Mint { token_id: token_id3.clone(), owner: demeter.clone(), token_uri: None, extension: None, }; + let env = mock_env(); contract - .execute(deps.as_mut(), mock_env(), minter, mint_msg) + .execute(deps.as_mut(), env.clone(), minter, mint_msg) .unwrap(); // get all tokens in order: let expected = vec![token_id1.clone(), token_id2.clone(), token_id3.clone()]; - let tokens = contract.all_tokens(deps.as_ref(), None, None).unwrap(); + let tokens = contract + .query_all_tokens(deps.as_ref(), env.clone(), None, None) + .unwrap(); assert_eq!(&expected, &tokens.tokens); // paginate - let tokens = contract.all_tokens(deps.as_ref(), None, Some(2)).unwrap(); + let tokens = contract + .query_all_tokens(deps.as_ref(), env.clone(), None, Some(2)) + .unwrap(); assert_eq!(&expected[..2], &tokens.tokens[..]); let tokens = contract - .all_tokens(deps.as_ref(), Some(expected[1].clone()), None) + .query_all_tokens(deps.as_ref(), env.clone(), Some(expected[1].clone()), None) .unwrap(); assert_eq!(&expected[2..], &tokens.tokens[..]); @@ -983,19 +1108,27 @@ fn query_tokens_by_owner() { let by_demeter = vec![token_id1, token_id3]; // all tokens by owner let tokens = contract - .tokens(deps.as_ref(), demeter.clone(), None, None) + .query_tokens(deps.as_ref(), env.clone(), demeter.clone(), None, None) .unwrap(); assert_eq!(&by_demeter, &tokens.tokens); - let tokens = contract.tokens(deps.as_ref(), ceres, None, None).unwrap(); + let tokens = contract + .query_tokens(deps.as_ref(), env.clone(), ceres, None, None) + .unwrap(); assert_eq!(&by_ceres, &tokens.tokens); // paginate for demeter let tokens = contract - .tokens(deps.as_ref(), demeter.clone(), None, Some(1)) + .query_tokens(deps.as_ref(), env.clone(), demeter.clone(), None, Some(1)) .unwrap(); assert_eq!(&by_demeter[..1], &tokens.tokens[..]); let tokens = contract - .tokens(deps.as_ref(), demeter, Some(by_demeter[0].clone()), Some(3)) + .query_tokens( + deps.as_ref(), + env, + demeter, + Some(by_demeter[0].clone()), + Some(3), + ) .unwrap(); assert_eq!(&by_demeter[1..], &tokens.tokens[..]); } diff --git a/packages/cw721/src/testing/mod.rs b/packages/cw721/src/testing/mod.rs new file mode 100644 index 000000000..b94a351ae --- /dev/null +++ b/packages/cw721/src/testing/mod.rs @@ -0,0 +1,4 @@ +mod contract; +mod contract_tests; +mod multi_tests; +mod unit_tests; diff --git a/packages/cw721/src/testing/multi_tests.rs b/packages/cw721/src/testing/multi_tests.rs new file mode 100644 index 000000000..ed01666f8 --- /dev/null +++ b/packages/cw721/src/testing/multi_tests.rs @@ -0,0 +1,941 @@ +use crate::{ + error::Cw721ContractError, + execute::Cw721Execute, + msg::{ + Cw721ExecuteMsg, Cw721InstantiateMsg, Cw721MigrateMsg, Cw721QueryMsg, MinterResponse, + OwnerOfResponse, + }, + query::Cw721Query, + state::DefaultOptionMetadataExtension, +}; +use cosmwasm_std::{ + to_json_binary, Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, QuerierWrapper, Response, + StdResult, WasmMsg, +}; +use cw_multi_test::{App, Contract, ContractWrapper, Executor}; +use cw_ownable::{Ownership, OwnershipError}; +use cw_utils::Expiration; + +use super::contract::Cw721Contract; + +pub const CREATOR_ADDR: &str = "creator"; +pub const MINTER_ADDR: &str = "minter"; +pub const OTHER_ADDR: &str = "other"; +pub const NFT_OWNER_ADDR: &str = "nft_owner"; + +pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: Cw721InstantiateMsg, +) -> Result { + let contract = Cw721Contract::::default(); + contract.instantiate(deps, env, info, msg, "contract_name", "contract_version") +} + +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: Cw721ExecuteMsg, +) -> Result { + let contract = Cw721Contract::::default(); + contract.execute(deps, env, info, msg) +} + +pub fn query( + deps: Deps, + env: Env, + msg: Cw721QueryMsg, +) -> StdResult { + let contract = Cw721Contract::::default(); + contract.query(deps, env, msg) +} + +pub fn migrate( + deps: DepsMut, + env: Env, + msg: Cw721MigrateMsg, +) -> Result { + let contract = Cw721Contract::::default(); + contract.migrate(deps, env, msg, "contract_name", "contract_version") +} + +fn cw721_base_latest_contract() -> Box> { + let contract = ContractWrapper::new(execute, instantiate, query).with_migrate(migrate); + Box::new(contract) +} + +fn cw721_base_016_contract() -> Box> { + use cw721_base_016 as v16; + let contract = ContractWrapper::new( + v16::entry::execute, + v16::entry::instantiate, + v16::entry::query, + ); + Box::new(contract) +} + +fn cw721_base_017_contract() -> Box> { + use cw721_base_017 as v17; + let contract = ContractWrapper::new( + v17::entry::execute, + v17::entry::instantiate, + v17::entry::query, + ); + Box::new(contract) +} + +fn cw721_base_018_contract() -> Box> { + use cw721_base_018 as v18; + let contract = ContractWrapper::new( + v18::entry::execute, + v18::entry::instantiate, + v18::entry::query, + ); + Box::new(contract) +} + +fn query_owner(querier: QuerierWrapper, cw721: &Addr, token_id: String) -> Addr { + let resp: OwnerOfResponse = querier + .query_wasm_smart( + cw721, + &Cw721QueryMsg::::OwnerOf { + token_id, + include_expired: None, + }, + ) + .unwrap(); + Addr::unchecked(resp.owner) +} + +fn mint_transfer_and_burn(app: &mut App, cw721: Addr, sender: Addr, token_id: String) { + app.execute_contract( + sender.clone(), + cw721.clone(), + &Cw721ExecuteMsg::::Mint { + token_id: token_id.clone(), + owner: sender.to_string(), + token_uri: None, + extension: Empty::default(), + }, + &[], + ) + .unwrap(); + + let owner = query_owner(app.wrap(), &cw721, token_id.clone()); + assert_eq!(owner, sender.to_string()); + + app.execute_contract( + sender, + cw721.clone(), + &Cw721ExecuteMsg::::TransferNft { + recipient: "burner".to_string(), + token_id: token_id.clone(), + }, + &[], + ) + .unwrap(); + + let owner = query_owner(app.wrap(), &cw721, token_id.clone()); + assert_eq!(owner, "burner".to_string()); + + app.execute_contract( + Addr::unchecked("burner"), + cw721, + &Cw721ExecuteMsg::::Burn { token_id }, + &[], + ) + .unwrap(); +} + +#[test] +fn test_operator() { + // --- setup --- + let mut app = App::default(); + let admin = Addr::unchecked("admin"); + let code_id = app.store_code(cw721_base_latest_contract()); + let other = Addr::unchecked(OTHER_ADDR); + let cw721 = app + .instantiate_contract( + code_id, + other.clone(), + &Cw721InstantiateMsg { + name: "collection".to_string(), + symbol: "symbol".to_string(), + minter: Some(MINTER_ADDR.to_string()), + withdraw_address: None, + }, + &[], + "cw721-base", + Some(admin.to_string()), + ) + .unwrap(); + // mint + let minter = Addr::unchecked(MINTER_ADDR); + let nft_owner = Addr::unchecked(NFT_OWNER_ADDR); + app.execute_contract( + minter, + cw721.clone(), + &Cw721ExecuteMsg::::Mint { + token_id: "1".to_string(), + owner: nft_owner.to_string(), + token_uri: None, + extension: Empty::default(), + }, + &[], + ) + .unwrap(); + + // --- test operator/approve all --- + // owner adds other user as operator using approve all + app.execute_contract( + nft_owner.clone(), + cw721.clone(), + &Cw721ExecuteMsg::::ApproveAll { + operator: other.to_string(), + expires: Some(Expiration::Never {}), + }, + &[], + ) + .unwrap(); + + // transfer by operator + app.execute_contract( + other.clone(), + cw721.clone(), + &Cw721ExecuteMsg::::TransferNft { + recipient: other.to_string(), + token_id: "1".to_string(), + }, + &[], + ) + .unwrap(); + // check other is new owner + let owner_response: OwnerOfResponse = app + .wrap() + .query_wasm_smart( + &cw721, + &Cw721QueryMsg::::OwnerOf { + token_id: "1".to_string(), + include_expired: None, + }, + ) + .unwrap(); + assert_eq!(owner_response.owner, other.to_string()); + // check previous owner cant transfer + let err: Cw721ContractError = app + .execute_contract( + nft_owner.clone(), + cw721.clone(), + &Cw721ExecuteMsg::::TransferNft { + recipient: other.to_string(), + token_id: "1".to_string(), + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!(err, Cw721ContractError::Ownership(OwnershipError::NotOwner)); + + // transfer back to previous owner + app.execute_contract( + other.clone(), + cw721.clone(), + &Cw721ExecuteMsg::::TransferNft { + recipient: nft_owner.to_string(), + token_id: "1".to_string(), + }, + &[], + ) + .unwrap(); + // check owner + let owner_response: OwnerOfResponse = app + .wrap() + .query_wasm_smart( + &cw721, + &Cw721QueryMsg::::OwnerOf { + token_id: "1".to_string(), + include_expired: None, + }, + ) + .unwrap(); + assert_eq!(owner_response.owner, nft_owner.to_string()); + + // other user is still operator and can transfer! + app.execute_contract( + other.clone(), + cw721.clone(), + &Cw721ExecuteMsg::::TransferNft { + recipient: other.to_string(), + token_id: "1".to_string(), + }, + &[], + ) + .unwrap(); + // check other is new owner + let owner_response: OwnerOfResponse = app + .wrap() + .query_wasm_smart( + &cw721, + &Cw721QueryMsg::::OwnerOf { + token_id: "1".to_string(), + include_expired: None, + }, + ) + .unwrap(); + assert_eq!(owner_response.owner, other.to_string()); + + // -- test revoke + // transfer to previous owner + app.execute_contract( + other.clone(), + cw721.clone(), + &Cw721ExecuteMsg::::TransferNft { + recipient: nft_owner.to_string(), + token_id: "1".to_string(), + }, + &[], + ) + .unwrap(); + + // revoke operator + app.execute_contract( + nft_owner, + cw721.clone(), + &Cw721ExecuteMsg::::RevokeAll { + operator: other.to_string(), + }, + &[], + ) + .unwrap(); + + // other not operator anymore and cant send + let err: Cw721ContractError = app + .execute_contract( + other.clone(), + cw721, + &Cw721ExecuteMsg::::TransferNft { + recipient: other.to_string(), + token_id: "1".to_string(), + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!(err, Cw721ContractError::Ownership(OwnershipError::NotOwner)); +} + +/// Instantiates a 0.16 version of this contract and tests that tokens +/// can be minted, transferred, and burnred after migration. +#[test] +fn test_migration_legacy_to_latest() { + // case 1: migrate from v0.16 to latest by using existing minter addr + { + use cw721_base_016 as v16; + let mut app = App::default(); + let admin = Addr::unchecked("admin"); + + let code_id_016 = app.store_code(cw721_base_016_contract()); + let code_id_latest = app.store_code(cw721_base_latest_contract()); + + let legacy_creator_and_minter = Addr::unchecked("legacy_creator_and_minter"); + + let cw721 = app + .instantiate_contract( + code_id_016, + legacy_creator_and_minter.clone(), + &v16::InstantiateMsg { + name: "collection".to_string(), + symbol: "symbol".to_string(), + minter: legacy_creator_and_minter.to_string(), + }, + &[], + "cw721-base", + Some(admin.to_string()), + ) + .unwrap(); + + mint_transfer_and_burn( + &mut app, + cw721.clone(), + legacy_creator_and_minter.clone(), + "1".to_string(), + ); + + // migrate + app.execute( + admin, + WasmMsg::Migrate { + contract_addr: cw721.to_string(), + new_code_id: code_id_latest, + msg: to_json_binary(&Cw721MigrateMsg::WithUpdate { + minter: None, + creator: None, + }) + .unwrap(), + } + .into(), + ) + .unwrap(); + + // non-minter user cant mint + let other = Addr::unchecked(OTHER_ADDR); + let err: Cw721ContractError = app + .execute_contract( + other.clone(), + cw721.clone(), + &Cw721ExecuteMsg::::Mint { + token_id: "1".to_string(), + owner: other.to_string(), + token_uri: None, + extension: Empty::default(), + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!(err, Cw721ContractError::Ownership(OwnershipError::NotOwner)); + + // legacy minter can still mint + mint_transfer_and_burn( + &mut app, + cw721.clone(), + legacy_creator_and_minter.clone(), + "1".to_string(), + ); + + // check new mint query response works. + let m: MinterResponse = app + .wrap() + .query_wasm_smart(&cw721, &Cw721QueryMsg::::Minter {}) + .unwrap(); + assert_eq!(m.minter, Some(legacy_creator_and_minter.to_string())); + + // check that the new response is backwards compatable when minter + // is not None. + let m: v16::MinterResponse = app + .wrap() + .query_wasm_smart(&cw721, &Cw721QueryMsg::::Minter {}) + .unwrap(); + assert_eq!(m.minter, legacy_creator_and_minter.to_string()); + + // check minter ownership query works + let minter_ownership: Ownership = app + .wrap() + .query_wasm_smart(&cw721, &Cw721QueryMsg::::Ownership {}) + .unwrap(); + assert_eq!(minter_ownership.owner, Some(legacy_creator_and_minter)); + } + // case 2: migrate from v0.16 to latest by providing new creator and minter addr + { + use cw721_base_016 as v16; + let mut app = App::default(); + let admin = Addr::unchecked("admin"); + + let code_id_016 = app.store_code(cw721_base_016_contract()); + let code_id_latest = app.store_code(cw721_base_latest_contract()); + + let legacy_creator_and_minter = Addr::unchecked("legacy_creator_and_minter"); + + let cw721 = app + .instantiate_contract( + code_id_016, + legacy_creator_and_minter.clone(), + &v16::InstantiateMsg { + name: "collection".to_string(), + symbol: "symbol".to_string(), + minter: legacy_creator_and_minter.to_string(), + }, + &[], + "cw721-base", + Some(admin.to_string()), + ) + .unwrap(); + + mint_transfer_and_burn( + &mut app, + cw721.clone(), + legacy_creator_and_minter.clone(), + "1".to_string(), + ); + + // migrate + app.execute( + admin, + WasmMsg::Migrate { + contract_addr: cw721.to_string(), + new_code_id: code_id_latest, + msg: to_json_binary(&Cw721MigrateMsg::WithUpdate { + minter: Some(MINTER_ADDR.to_string()), + creator: Some(CREATOR_ADDR.to_string()), + }) + .unwrap(), + } + .into(), + ) + .unwrap(); + + // legacy minter user cant mint + let err: Cw721ContractError = app + .execute_contract( + legacy_creator_and_minter.clone(), + cw721.clone(), + &Cw721ExecuteMsg::::Mint { + token_id: "1".to_string(), + owner: legacy_creator_and_minter.to_string(), + token_uri: None, + extension: Empty::default(), + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!(err, Cw721ContractError::Ownership(OwnershipError::NotOwner)); + + // new minter can mint + let minter = Addr::unchecked(MINTER_ADDR); + mint_transfer_and_burn(&mut app, cw721.clone(), minter.clone(), "1".to_string()); + + // check new mint query response works. + let m: MinterResponse = app + .wrap() + .query_wasm_smart(&cw721, &Cw721QueryMsg::::Minter {}) + .unwrap(); + assert_eq!(m.minter, Some(minter.to_string())); + + // check that the new response is backwards compatable when minter + // is not None. + let m: v16::MinterResponse = app + .wrap() + .query_wasm_smart(&cw721, &Cw721QueryMsg::::Minter {}) + .unwrap(); + assert_eq!(m.minter, minter.to_string()); + + // check minter ownership query works + let minter_ownership: Ownership = app + .wrap() + .query_wasm_smart(&cw721, &Cw721QueryMsg::::Ownership {}) + .unwrap(); + assert_eq!(minter_ownership.owner, Some(minter)); + } + // case 3: migrate from v0.17 to latest by using existing minter addr + { + use cw721_base_017 as v17; + let mut app = App::default(); + let admin = Addr::unchecked("admin"); + + let code_id_017 = app.store_code(cw721_base_017_contract()); + let code_id_latest = app.store_code(cw721_base_latest_contract()); + + let legacy_creator_and_minter = Addr::unchecked("legacy_creator_and_minter"); + + let cw721 = app + .instantiate_contract( + code_id_017, + legacy_creator_and_minter.clone(), + &v17::InstantiateMsg { + name: "collection".to_string(), + symbol: "symbol".to_string(), + minter: legacy_creator_and_minter.to_string(), + }, + &[], + "cw721-base", + Some(admin.to_string()), + ) + .unwrap(); + + mint_transfer_and_burn( + &mut app, + cw721.clone(), + legacy_creator_and_minter.clone(), + "1".to_string(), + ); + + // migrate + app.execute( + admin, + WasmMsg::Migrate { + contract_addr: cw721.to_string(), + new_code_id: code_id_latest, + msg: to_json_binary(&Cw721MigrateMsg::WithUpdate { + minter: None, + creator: None, + }) + .unwrap(), + } + .into(), + ) + .unwrap(); + + // non-minter user cant mint + let other = Addr::unchecked(OTHER_ADDR); + let err: Cw721ContractError = app + .execute_contract( + other.clone(), + cw721.clone(), + &Cw721ExecuteMsg::::Mint { + token_id: "1".to_string(), + owner: other.to_string(), + token_uri: None, + extension: Empty::default(), + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!(err, Cw721ContractError::Ownership(OwnershipError::NotOwner)); + + // legacy minter can still mint + mint_transfer_and_burn( + &mut app, + cw721.clone(), + legacy_creator_and_minter.clone(), + "1".to_string(), + ); + + // check new mint query response works. + let m: MinterResponse = app + .wrap() + .query_wasm_smart(&cw721, &Cw721QueryMsg::::Minter {}) + .unwrap(); + assert_eq!(m.minter, Some(legacy_creator_and_minter.to_string())); + + // check that the new response is backwards compatable when minter + // is not None. + let m: v17::MinterResponse = app + .wrap() + .query_wasm_smart(&cw721, &Cw721QueryMsg::::Minter {}) + .unwrap(); + assert_eq!(m.minter, Some(legacy_creator_and_minter.to_string())); + + // check minter ownership query works + let minter_ownership: Ownership = app + .wrap() + .query_wasm_smart(&cw721, &Cw721QueryMsg::::Ownership {}) + .unwrap(); + assert_eq!(minter_ownership.owner, Some(legacy_creator_and_minter)); + } + // case 4: migrate from v0.17 to latest by providing new creator and minter addr + { + use cw721_base_017 as v17; + let mut app = App::default(); + let admin = Addr::unchecked("admin"); + + let code_id_017 = app.store_code(cw721_base_017_contract()); + let code_id_latest = app.store_code(cw721_base_latest_contract()); + + let legacy_creator_and_minter = Addr::unchecked("legacy_creator_and_minter"); + + let cw721 = app + .instantiate_contract( + code_id_017, + legacy_creator_and_minter.clone(), + &v17::InstantiateMsg { + name: "collection".to_string(), + symbol: "symbol".to_string(), + minter: legacy_creator_and_minter.to_string(), + }, + &[], + "cw721-base", + Some(admin.to_string()), + ) + .unwrap(); + + mint_transfer_and_burn( + &mut app, + cw721.clone(), + legacy_creator_and_minter.clone(), + "1".to_string(), + ); + + // migrate + app.execute( + admin, + WasmMsg::Migrate { + contract_addr: cw721.to_string(), + new_code_id: code_id_latest, + msg: to_json_binary(&Cw721MigrateMsg::WithUpdate { + minter: Some(MINTER_ADDR.to_string()), + creator: Some(CREATOR_ADDR.to_string()), + }) + .unwrap(), + } + .into(), + ) + .unwrap(); + + // legacy minter user cant mint + let err: Cw721ContractError = app + .execute_contract( + legacy_creator_and_minter.clone(), + cw721.clone(), + &Cw721ExecuteMsg::::Mint { + token_id: "1".to_string(), + owner: legacy_creator_and_minter.to_string(), + token_uri: None, + extension: Empty::default(), + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!(err, Cw721ContractError::Ownership(OwnershipError::NotOwner)); + + // new minter can mint + let minter = Addr::unchecked(MINTER_ADDR); + mint_transfer_and_burn(&mut app, cw721.clone(), minter.clone(), "1".to_string()); + + // check new mint query response works. + let m: MinterResponse = app + .wrap() + .query_wasm_smart(&cw721, &Cw721QueryMsg::::Minter {}) + .unwrap(); + assert_eq!(m.minter, Some(minter.to_string())); + + // check that the new response is backwards compatable when minter + // is not None. + let m: v17::MinterResponse = app + .wrap() + .query_wasm_smart(&cw721, &Cw721QueryMsg::::Minter {}) + .unwrap(); + assert_eq!(m.minter, Some(minter.to_string())); + + // check minter ownership query works + let minter_ownership: Ownership = app + .wrap() + .query_wasm_smart(&cw721, &Cw721QueryMsg::::Ownership {}) + .unwrap(); + assert_eq!(minter_ownership.owner, Some(minter)); + } + // case 5: migrate from v0.18 to latest by using existing minter addr + { + use cw721_base_018 as v18; + let mut app = App::default(); + let admin = Addr::unchecked("admin"); + + let code_id_018 = app.store_code(cw721_base_018_contract()); + let code_id_latest = app.store_code(cw721_base_latest_contract()); + + let legacy_creator_and_minter = Addr::unchecked("legacy_creator_and_minter"); + + let cw721 = app + .instantiate_contract( + code_id_018, + legacy_creator_and_minter.clone(), + &v18::InstantiateMsg { + name: "collection".to_string(), + symbol: "symbol".to_string(), + minter: legacy_creator_and_minter.to_string(), + }, + &[], + "cw721-base", + Some(admin.to_string()), + ) + .unwrap(); + + mint_transfer_and_burn( + &mut app, + cw721.clone(), + legacy_creator_and_minter.clone(), + "1".to_string(), + ); + + // migrate + app.execute( + admin, + WasmMsg::Migrate { + contract_addr: cw721.to_string(), + new_code_id: code_id_latest, + msg: to_json_binary(&Cw721MigrateMsg::WithUpdate { + minter: None, + creator: None, + }) + .unwrap(), + } + .into(), + ) + .unwrap(); + + // non-minter user cant mint + let other = Addr::unchecked(OTHER_ADDR); + let err: Cw721ContractError = app + .execute_contract( + other.clone(), + cw721.clone(), + &Cw721ExecuteMsg::::Mint { + token_id: "1".to_string(), + owner: other.to_string(), + token_uri: None, + extension: Empty::default(), + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!(err, Cw721ContractError::Ownership(OwnershipError::NotOwner)); + + // legacy minter can still mint + mint_transfer_and_burn( + &mut app, + cw721.clone(), + legacy_creator_and_minter.clone(), + "1".to_string(), + ); + + // check new mint query response works. + let m: MinterResponse = app + .wrap() + .query_wasm_smart(&cw721, &Cw721QueryMsg::::Minter {}) + .unwrap(); + assert_eq!(m.minter, Some(legacy_creator_and_minter.to_string())); + + // check that the new response is backwards compatable when minter + // is not None. + let m: v18::MinterResponse = app + .wrap() + .query_wasm_smart(&cw721, &Cw721QueryMsg::::Minter {}) + .unwrap(); + assert_eq!(m.minter, Some(legacy_creator_and_minter.to_string())); + + // check minter ownership query works + let minter_ownership: Ownership = app + .wrap() + .query_wasm_smart(&cw721, &Cw721QueryMsg::::Ownership {}) + .unwrap(); + assert_eq!(minter_ownership.owner, Some(legacy_creator_and_minter)); + } + // case 6: migrate from v0.18 to latest by providing new creator and minter addr + { + use cw721_base_018 as v18; + let mut app = App::default(); + let admin = Addr::unchecked("admin"); + + let code_id_018 = app.store_code(cw721_base_018_contract()); + let code_id_latest = app.store_code(cw721_base_latest_contract()); + + let legacy_creator_and_minter = Addr::unchecked("legacy_creator_and_minter"); + + let cw721 = app + .instantiate_contract( + code_id_018, + legacy_creator_and_minter.clone(), + &v18::InstantiateMsg { + name: "collection".to_string(), + symbol: "symbol".to_string(), + minter: legacy_creator_and_minter.to_string(), + }, + &[], + "cw721-base", + Some(admin.to_string()), + ) + .unwrap(); + + mint_transfer_and_burn( + &mut app, + cw721.clone(), + legacy_creator_and_minter.clone(), + "1".to_string(), + ); + + // migrate + app.execute( + admin, + WasmMsg::Migrate { + contract_addr: cw721.to_string(), + new_code_id: code_id_latest, + msg: to_json_binary(&Cw721MigrateMsg::WithUpdate { + minter: Some(MINTER_ADDR.to_string()), + creator: Some(CREATOR_ADDR.to_string()), + }) + .unwrap(), + } + .into(), + ) + .unwrap(); + + // legacy minter user cant mint + let err: Cw721ContractError = app + .execute_contract( + legacy_creator_and_minter.clone(), + cw721.clone(), + &Cw721ExecuteMsg::::Mint { + token_id: "1".to_string(), + owner: legacy_creator_and_minter.to_string(), + token_uri: None, + extension: Empty::default(), + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap(); + assert_eq!(err, Cw721ContractError::Ownership(OwnershipError::NotOwner)); + + // new minter can mint + let minter = Addr::unchecked(MINTER_ADDR); + mint_transfer_and_burn(&mut app, cw721.clone(), minter.clone(), "1".to_string()); + + // check new mint query response works. + let m: MinterResponse = app + .wrap() + .query_wasm_smart(&cw721, &Cw721QueryMsg::::Minter {}) + .unwrap(); + assert_eq!(m.minter, Some(minter.to_string())); + + // check that the new response is backwards compatable when minter + // is not None. + let m: v18::MinterResponse = app + .wrap() + .query_wasm_smart(&cw721, &Cw721QueryMsg::::Minter {}) + .unwrap(); + assert_eq!(m.minter, Some(minter.to_string())); + + // check minter ownership query works + let minter_ownership: Ownership = app + .wrap() + .query_wasm_smart(&cw721, &Cw721QueryMsg::::Ownership {}) + .unwrap(); + assert_eq!(minter_ownership.owner, Some(minter)); + } +} + +/// Test backward compatibility using instantiate msg from a 0.16 version on latest contract. +/// This ensures existing 3rd party contracts doesnt need to update as well. +#[test] +fn test_instantiate_016_msg() { + use cw721_base_016 as v16; + let mut app = App::default(); + let admin = || Addr::unchecked("admin"); + + let code_id_latest = app.store_code(cw721_base_latest_contract()); + + let cw721 = app + .instantiate_contract( + code_id_latest, + admin(), + &v16::InstantiateMsg { + name: "collection".to_string(), + symbol: "symbol".to_string(), + minter: admin().into_string(), + }, + &[], + "cw721-base", + Some(admin().into_string()), + ) + .unwrap(); + + // assert withdraw address is None + let withdraw_addr: Option = app + .wrap() + .query_wasm_smart(cw721, &Cw721QueryMsg::::GetWithdrawAddress {}) + .unwrap(); + assert!(withdraw_addr.is_none()); +} diff --git a/packages/cw721/src/testing/unit_tests.rs b/packages/cw721/src/testing/unit_tests.rs new file mode 100644 index 000000000..79b84eab2 --- /dev/null +++ b/packages/cw721/src/testing/unit_tests.rs @@ -0,0 +1,255 @@ +use crate::{ + execute::Cw721Execute, + msg::{Cw721ExecuteMsg, Cw721InstantiateMsg}, + query::{Cw721Query, MAX_LIMIT}, + state::{CollectionInfo, DefaultOptionMetadataExtension, Metadata, MINTER}, +}; +use cosmwasm_std::{ + testing::{mock_dependencies, mock_env, mock_info}, + Addr, Empty, +}; +use cw2::ContractVersion; +use cw_storage_plus::Item; +use unit_tests::{contract::Cw721Contract, multi_tests::CREATOR_ADDR}; + +use super::*; + +/// Make sure cw2 version info is properly initialized during instantiation. +#[test] +fn proper_cw2_initialization() { + let mut deps = mock_dependencies(); + + Cw721Contract::::default() + .instantiate( + deps.as_mut(), + mock_env(), + mock_info("larry", &[]), + Cw721InstantiateMsg { + name: "collection_name".into(), + symbol: "collection_symbol".into(), + minter: Some("minter".into()), + withdraw_address: None, + }, + "contract_name", + "contract_version", + ) + .unwrap(); + + let minter = MINTER + .get_ownership(deps.as_ref().storage) + .unwrap() + .owner + .map(|a| a.into_string()); + assert_eq!(minter, Some("minter".to_string())); + + let version = cw2::get_contract_version(deps.as_ref().storage).unwrap(); + assert_eq!( + version, + ContractVersion { + contract: "contract_name".into(), + version: "contract_version".into(), + }, + ); +} + +#[test] +fn proper_owner_initialization() { + let mut deps = mock_dependencies(); + + let info_owner = mock_info("owner", &[]); + Cw721Contract::::default() + .instantiate( + deps.as_mut(), + mock_env(), + info_owner.clone(), + Cw721InstantiateMsg { + name: "collection_name".into(), + symbol: "collection_symbol".into(), + minter: None, + withdraw_address: None, + }, + "contract_name", + "contract_version", + ) + .unwrap(); + + let minter = MINTER.item.load(deps.as_ref().storage).unwrap().owner; + assert_eq!(minter, Some(info_owner.sender)); +} + +#[test] +fn use_metadata_extension() { + let mut deps = mock_dependencies(); + let contract = Cw721Contract::::default(); + + let info = mock_info(CREATOR_ADDR, &[]); + let init_msg = Cw721InstantiateMsg { + name: "collection_name".into(), + symbol: "collection_symbol".into(), + minter: None, + withdraw_address: None, + }; + let env = mock_env(); + contract + .instantiate( + deps.as_mut(), + env.clone(), + info.clone(), + init_msg, + "contract_name", + "contract_version", + ) + .unwrap(); + + let token_id = "Enterprise"; + let token_uri = Some("https://starships.example.com/Starship/Enterprise.json".into()); + let extension = Some(Metadata { + description: Some("Spaceship with Warp Drive".into()), + name: Some("Starship USS Enterprise".to_string()), + ..Metadata::default() + }); + let exec_msg = Cw721ExecuteMsg::Mint { + token_id: token_id.to_string(), + owner: "john".to_string(), + token_uri: token_uri.clone(), + extension: extension.clone(), + }; + contract + .execute(deps.as_mut(), env.clone(), info, exec_msg) + .unwrap(); + + let res = contract + .query_nft_info(deps.as_ref(), env, token_id.into()) + .unwrap(); + assert_eq!(res.token_uri, token_uri); + assert_eq!(res.extension, extension); +} + +#[test] +fn test_migrate() { + let mut deps = mock_dependencies(); + + let env = mock_env(); + use cw721_base_016 as v16; + v16::entry::instantiate( + deps.as_mut(), + env.clone(), + mock_info("owner", &[]), + v16::InstantiateMsg { + name: "legacy_name".into(), + symbol: "legacy_symbol".into(), + minter: "legacy_minter".into(), + }, + ) + .unwrap(); + + // mint 200 NFTs before migration + for i in 0..200 { + let info = mock_info("legacy_minter", &[]); + let msg = v16::ExecuteMsg::Mint(v16::msg::MintMsg { + token_id: i.to_string(), + owner: "owner".into(), + token_uri: None, + extension: None, + }); + v16::entry::execute(deps.as_mut(), env.clone(), info, msg).unwrap(); + } + + // assert new data before migration: + // - ownership and collection info throws NotFound Error + MINTER.item.load(deps.as_ref().storage).unwrap_err(); // cw_ownable in v16 is used for minter + let contract = Cw721Contract::::default(); + contract + .query_collection_info(deps.as_ref(), env.clone()) + .unwrap_err(); + // - query in new minter and creator ownership store throws NotFound Error (in v16 it was stored outside cw_ownable, in dedicated "minter" store) + MINTER.get_ownership(deps.as_ref().storage).unwrap_err(); + // assert legacy data before migration: + // - version + let version = cw2::get_contract_version(deps.as_ref().storage) + .unwrap() + .version; + assert_eq!(version, "0.16.0"); + // - legacy minter is set + let legacy_minter_store: Item = Item::new("minter"); + let legacy_minter = legacy_minter_store.load(deps.as_ref().storage).unwrap(); + assert_eq!(legacy_minter, "legacy_minter"); + // - legacy collection info is set + let legacy_collection_info_store: Item = Item::new("nft_info"); + let all_tokens = contract + .query_all_tokens(deps.as_ref(), env.clone(), None, Some(MAX_LIMIT)) + .unwrap(); + assert_eq!(all_tokens.tokens.len(), 200); + for token_id in 0..200 { + let token = contract + .query_owner_of(deps.as_ref(), env.clone(), token_id.to_string(), false) + .unwrap(); + assert_eq!(token.owner.as_str(), "owner"); + } + + Cw721Contract::::default() + .migrate( + deps.as_mut(), + env.clone(), + crate::msg::Cw721MigrateMsg::WithUpdate { + minter: None, + creator: None, + }, + "contract_name", + "contract_version", + ) + .unwrap(); + + // version + let version = cw2::get_contract_version(deps.as_ref().storage) + .unwrap() + .version; + assert_eq!(version, "contract_version"); + assert_ne!(version, "0.16.0"); + + // assert minter ownership + let minter_ownership = MINTER + .get_ownership(deps.as_ref().storage) + .unwrap() + .owner + .map(|a| a.into_string()); + assert_eq!(minter_ownership, Some("legacy_minter".to_string())); + + // assert collection info + let collection_info = contract + .query_collection_info(deps.as_ref(), env.clone()) + .unwrap(); + let legacy_contract_info = CollectionInfo { + name: "legacy_name".to_string(), + symbol: "legacy_symbol".to_string(), + }; + assert_eq!(collection_info, legacy_contract_info); + + // assert tokens + let all_tokens = contract + .query_all_tokens(deps.as_ref(), env.clone(), None, Some(MAX_LIMIT)) + .unwrap(); + assert_eq!(all_tokens.tokens.len(), 200); + + // assert legacy data is still there (allowing backward migration in case of issues) + // - minter + let legacy_minter = legacy_minter_store.load(deps.as_ref().storage).unwrap(); + assert_eq!(legacy_minter, "legacy_minter"); + // - collection info + let legacy_collection_info = legacy_collection_info_store + .load(deps.as_ref().storage) + .unwrap(); + assert_eq!(legacy_collection_info.name, "legacy_name"); + assert_eq!(legacy_collection_info.symbol, "legacy_symbol"); + // - tokens are unchanged/still exist + let all_tokens = contract + .query_all_tokens(deps.as_ref(), env.clone(), None, Some(MAX_LIMIT)) + .unwrap(); + assert_eq!(all_tokens.tokens.len(), 200); + for token_id in 0..200 { + let token = contract + .query_owner_of(deps.as_ref(), env.clone(), token_id.to_string(), false) + .unwrap(); + assert_eq!(token.owner.as_str(), "owner"); + } +} diff --git a/packages/cw721/src/traits.rs b/packages/cw721/src/traits.rs deleted file mode 100644 index 1022a0eb3..000000000 --- a/packages/cw721/src/traits.rs +++ /dev/null @@ -1,168 +0,0 @@ -use serde::de::DeserializeOwned; -use serde::Serialize; - -use crate::{ - AllNftInfoResponse, ApprovalResponse, ApprovalsResponse, ContractInfoResponse, NftInfoResponse, - NumTokensResponse, OperatorResponse, OperatorsResponse, OwnerOfResponse, TokensResponse, -}; -use cosmwasm_std::{Binary, CustomMsg, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; -use cw_utils::Expiration; - -pub trait Cw721: Cw721Execute + Cw721Query -where - T: Serialize + DeserializeOwned + Clone, - C: CustomMsg, -{ -} - -pub trait Cw721Execute -where - T: Serialize + DeserializeOwned + Clone, - C: CustomMsg, -{ - type Err: ToString; - - fn transfer_nft( - &self, - deps: DepsMut, - env: Env, - info: MessageInfo, - recipient: String, - token_id: String, - ) -> Result, Self::Err>; - - fn send_nft( - &self, - deps: DepsMut, - env: Env, - info: MessageInfo, - contract: String, - token_id: String, - msg: Binary, - ) -> Result, Self::Err>; - - fn approve( - &self, - deps: DepsMut, - env: Env, - info: MessageInfo, - spender: String, - token_id: String, - expires: Option, - ) -> Result, Self::Err>; - - fn revoke( - &self, - deps: DepsMut, - env: Env, - info: MessageInfo, - spender: String, - token_id: String, - ) -> Result, Self::Err>; - - fn approve_all( - &self, - deps: DepsMut, - env: Env, - info: MessageInfo, - operator: String, - expires: Option, - ) -> Result, Self::Err>; - - fn revoke_all( - &self, - deps: DepsMut, - env: Env, - info: MessageInfo, - operator: String, - ) -> Result, Self::Err>; - - fn burn( - &self, - deps: DepsMut, - env: Env, - info: MessageInfo, - token_id: String, - ) -> Result, Self::Err>; -} - -pub trait Cw721Query -where - T: Serialize + DeserializeOwned + Clone, -{ - // TODO: use custom error? - // How to handle the two derived error types? - - fn contract_info(&self, deps: Deps) -> StdResult; - - fn num_tokens(&self, deps: Deps) -> StdResult; - - fn nft_info(&self, deps: Deps, token_id: String) -> StdResult>; - - fn owner_of( - &self, - deps: Deps, - env: Env, - token_id: String, - include_expired: bool, - ) -> StdResult; - - fn operator( - &self, - deps: Deps, - env: Env, - owner: String, - operator: String, - include_expired: bool, - ) -> StdResult; - - fn operators( - &self, - deps: Deps, - env: Env, - owner: String, - include_expired: bool, - start_after: Option, - limit: Option, - ) -> StdResult; - - fn approval( - &self, - deps: Deps, - env: Env, - token_id: String, - spender: String, - include_expired: bool, - ) -> StdResult; - - fn approvals( - &self, - deps: Deps, - env: Env, - token_id: String, - include_expired: bool, - ) -> StdResult; - - fn tokens( - &self, - deps: Deps, - owner: String, - start_after: Option, - limit: Option, - ) -> StdResult; - - fn all_tokens( - &self, - deps: Deps, - start_after: Option, - limit: Option, - ) -> StdResult; - - fn all_nft_info( - &self, - deps: Deps, - env: Env, - token_id: String, - include_expired: bool, - ) -> StdResult>; -} From f142b3b169882abc6d7c8bf29e3343ee4b34d312 Mon Sep 17 00:00:00 2001 From: shab Date: Tue, 9 Jul 2024 18:04:10 -0400 Subject: [PATCH 44/57] add more tests --- contracts/cw1155-base/src/contract_tests.rs | 450 +++++++++++++++++++- 1 file changed, 443 insertions(+), 7 deletions(-) diff --git a/contracts/cw1155-base/src/contract_tests.rs b/contracts/cw1155-base/src/contract_tests.rs index 1345e6f40..1131e6397 100644 --- a/contracts/cw1155-base/src/contract_tests.rs +++ b/contracts/cw1155-base/src/contract_tests.rs @@ -10,8 +10,8 @@ mod tests { use cw1155::execute::Cw1155Execute; use cw1155::msg::{ ApprovedForAllResponse, Balance, BalanceResponse, BalancesResponse, Cw1155InstantiateMsg, - Cw1155MintMsg, Cw1155QueryMsg, NumTokensResponse, OwnerToken, TokenAmount, - TokenInfoResponse, + Cw1155MintMsg, Cw1155QueryMsg, IsApprovedForAllResponse, NumTokensResponse, OwnerToken, + TokenAmount, TokenInfoResponse, }; use cw1155::query::Cw1155Query; use cw1155::receiver::Cw1155BatchReceiveMsg; @@ -102,6 +102,40 @@ mod tests { ])) ); + // verify supply + assert_eq!( + from_json::( + contract + .query( + deps.as_ref(), + mock_env(), + Cw1155BaseQueryMsg::NumTokens { + token_id: Some(token1.clone()), + }, + ) + .unwrap() + ) + .unwrap(), + NumTokensResponse { + count: Uint128::one() + } + ); + assert_eq!( + from_json::( + contract + .query( + deps.as_ref(), + mock_env(), + Cw1155BaseQueryMsg::NumTokens { token_id: None }, + ) + .unwrap() + ) + .unwrap(), + NumTokensResponse { + count: Uint128::one() + } + ); + // query balance assert_eq!( to_json_binary(&BalanceResponse { @@ -230,6 +264,57 @@ mod tests { ) .unwrap(); + // verify supply + assert_eq!( + from_json::( + contract + .query( + deps.as_ref(), + mock_env(), + Cw1155BaseQueryMsg::NumTokens { + token_id: Some(token2.clone()), + }, + ) + .unwrap() + ) + .unwrap(), + NumTokensResponse { + count: Uint128::one() + } + ); + assert_eq!( + from_json::( + contract + .query( + deps.as_ref(), + mock_env(), + Cw1155BaseQueryMsg::NumTokens { + token_id: Some(token3.clone()), + }, + ) + .unwrap() + ) + .unwrap(), + NumTokensResponse { + count: Uint128::one() + } + ); + assert_eq!( + from_json::( + contract + .query( + deps.as_ref(), + mock_env(), + Cw1155BaseQueryMsg::NumTokens { token_id: None }, + ) + .unwrap() + ) + .unwrap(), + NumTokensResponse { + count: Uint128::new(3) + } + ); + // invalid batch transfer, (user2 not approved yet) let batch_transfer_msg = Cw1155BaseExecuteMsg::SendBatch { from: Some(user2.clone()), @@ -273,6 +358,19 @@ mod tests { ) .unwrap(); + // verify approval status + assert_eq!( + contract.query( + deps.as_ref(), + mock_env(), + Cw1155QueryMsg::IsApprovedForAll { + owner: user1.to_string(), + operator: minter.to_string(), + }, + ), + to_json_binary(&IsApprovedForAllResponse { approved: true }) + ); + // valid batch transfer assert_eq!( contract @@ -372,6 +470,18 @@ mod tests { .all(|approval| approval.spender == minter) ); + assert_eq!( + contract.query( + deps.as_ref(), + mock_env(), + Cw1155QueryMsg::IsApprovedForAll { + owner: user1.to_string(), + operator: minter.to_string(), + }, + ), + to_json_binary(&IsApprovedForAllResponse { approved: false }) + ); + // transfer without approval assert!(matches!( contract.execute( @@ -410,6 +520,60 @@ mod tests { ])) ); + // verify supply + assert_eq!( + from_json::( + contract + .query( + deps.as_ref(), + mock_env(), + Cw1155BaseQueryMsg::NumTokens { + token_id: Some(token1.clone()), + }, + ) + .unwrap() + ) + .unwrap(), + NumTokensResponse { + count: Uint128::zero() + } + ); + assert_eq!( + from_json::( + contract + .query( + deps.as_ref(), + mock_env(), + Cw1155BaseQueryMsg::NumTokens { token_id: None }, + ) + .unwrap() + ) + .unwrap(), + NumTokensResponse { + count: Uint128::new(2) + } + ); + + // verify balance + assert_eq!( + from_json::( + contract + .query( + deps.as_ref(), + mock_env(), + Cw1155BaseQueryMsg::BalanceOf(OwnerToken { + owner: user1.clone(), + token_id: token1.clone(), + }), + ) + .unwrap() + ) + .unwrap(), + BalanceResponse { + balance: Uint128::zero() + } + ); + // burn them all assert_eq!( contract @@ -438,6 +602,94 @@ mod tests { ("amounts", "1,1"), ])) ); + + // verify supply + assert_eq!( + from_json::( + contract + .query( + deps.as_ref(), + mock_env(), + Cw1155BaseQueryMsg::NumTokens { + token_id: Some(token2.clone()), + }, + ) + .unwrap() + ) + .unwrap(), + NumTokensResponse { + count: Uint128::zero() + } + ); + assert_eq!( + from_json::( + contract + .query( + deps.as_ref(), + mock_env(), + Cw1155BaseQueryMsg::NumTokens { + token_id: Some(token3.clone()), + }, + ) + .unwrap() + ) + .unwrap(), + NumTokensResponse { + count: Uint128::zero() + } + ); + assert_eq!( + from_json::( + contract + .query( + deps.as_ref(), + mock_env(), + Cw1155BaseQueryMsg::NumTokens { token_id: None }, + ) + .unwrap() + ) + .unwrap(), + NumTokensResponse { + count: Uint128::zero() + } + ); + + // verify balances + assert_eq!( + from_json::( + contract + .query( + deps.as_ref(), + mock_env(), + Cw1155BaseQueryMsg::BalanceOfBatch(vec![ + OwnerToken { + owner: user1.clone(), + token_id: token2.clone(), + }, + OwnerToken { + owner: user1.clone(), + token_id: token3.clone(), + }, + ]), + ) + .unwrap() + ) + .unwrap(), + BalancesResponse { + balances: vec![ + Balance { + token_id: token2.to_string(), + owner: Addr::unchecked(&user1), + amount: Uint128::zero(), + }, + Balance { + token_id: token3.to_string(), + owner: Addr::unchecked(&user1), + amount: Uint128::zero(), + } + ] + }, + ); } #[test] @@ -446,6 +698,7 @@ mod tests { let receiver = String::from("receive_contract"); let minter = String::from("minter"); let user1 = String::from("user1"); + let token1 = "token1".to_owned(); let token2 = "token2".to_owned(); let operator_info = mock_info("operator", &[]); let dummy_msg = Binary::default(); @@ -473,14 +726,38 @@ mod tests { deps.as_mut(), mock_env(), mock_info(minter.as_ref(), &[]), - Cw1155BaseExecuteMsg::Mint { + Cw1155BaseExecuteMsg::MintBatch { recipient: user1.clone(), - msg: Cw1155MintMsg { - token_id: token2.clone(), + msgs: vec![ + Cw1155MintMsg { + token_id: token1.clone(), + amount: 5u64.into(), + token_uri: None, + extension: None, + }, + Cw1155MintMsg { + token_id: token2.clone(), + amount: 5u64.into(), + token_uri: None, + extension: None, + }, + ], + }, + ) + .unwrap(); + contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(minter.as_ref(), &[]), + Cw1155BaseExecuteMsg::MintBatch { + recipient: receiver.clone(), + msgs: vec![Cw1155MintMsg { + token_id: token1.clone(), amount: 1u64.into(), token_uri: None, extension: None, - }, + }], }, ) .unwrap(); @@ -512,7 +789,7 @@ mod tests { token_id: token2.to_string(), amount: 1u64.into(), }], - msg: dummy_msg, + msg: dummy_msg.clone(), } .into_cosmos_msg(&operator_info, receiver.clone()) .unwrap() @@ -524,6 +801,153 @@ mod tests { ("amount", "1"), ])) ); + + // verify balances + assert_eq!( + from_json::( + contract + .query( + deps.as_ref(), + mock_env(), + Cw1155BaseQueryMsg::BalanceOfBatch(vec![ + OwnerToken { + owner: user1.clone(), + token_id: token2.clone(), + }, + OwnerToken { + owner: receiver.clone(), + token_id: token2.clone(), + } + ]), + ) + .unwrap() + ) + .unwrap(), + BalancesResponse { + balances: vec![ + Balance { + token_id: token2.to_string(), + owner: Addr::unchecked(&user1), + amount: Uint128::new(4), + }, + Balance { + token_id: token2.to_string(), + owner: Addr::unchecked(&receiver), + amount: Uint128::one(), + } + ] + }, + ); + + // BatchSend + assert_eq!( + contract + .execute( + deps.as_mut(), + mock_env(), + mock_info(user1.as_ref(), &[]), + Cw1155BaseExecuteMsg::SendBatch { + from: None, + to: receiver.clone(), + batch: vec![ + TokenAmount { + token_id: token1.to_string(), + amount: 1u64.into(), + }, + TokenAmount { + token_id: token2.to_string(), + amount: 1u64.into(), + }, + ], + msg: Some(dummy_msg.clone()), + }, + ) + .unwrap(), + Response::new() + .add_message( + Cw1155BatchReceiveMsg { + operator: user1.clone(), + from: Some(user1.clone()), + batch: vec![ + TokenAmount { + token_id: token1.to_string(), + amount: 1u64.into(), + }, + TokenAmount { + token_id: token2.to_string(), + amount: 1u64.into(), + } + ], + msg: dummy_msg, + } + .into_cosmos_msg(&operator_info, receiver.clone()) + .unwrap() + ) + .add_event(Event::new("transfer_batch").add_attributes(vec![ + ("sender", user1.as_str()), + ("recipient", receiver.as_str()), + ( + "token_ids", + &format!("{},{}", token1.as_str(), token2.as_str()) + ), + ("amounts", &format!("{},{}", 1, 1)), + ])) + ); + + // verify balances + assert_eq!( + from_json::( + contract + .query( + deps.as_ref(), + mock_env(), + Cw1155BaseQueryMsg::BalanceOfBatch(vec![ + OwnerToken { + owner: user1.clone(), + token_id: token1.clone(), + }, + OwnerToken { + owner: user1.clone(), + token_id: token2.clone(), + }, + OwnerToken { + owner: receiver.clone(), + token_id: token1.clone(), + }, + OwnerToken { + owner: receiver.clone(), + token_id: token2.clone(), + } + ]), + ) + .unwrap() + ) + .unwrap(), + BalancesResponse { + balances: vec![ + Balance { + token_id: token1.to_string(), + owner: Addr::unchecked(&user1), + amount: Uint128::new(4), + }, + Balance { + token_id: token2.to_string(), + owner: Addr::unchecked(&user1), + amount: Uint128::new(3), + }, + Balance { + token_id: token1.to_string(), + owner: Addr::unchecked(&receiver), + amount: Uint128::new(2), + }, + Balance { + token_id: token2.to_string(), + owner: Addr::unchecked(&receiver), + amount: Uint128::new(2), + } + ] + }, + ); } #[test] @@ -755,6 +1179,18 @@ mod tests { }], }) ); + + assert_eq!( + contract.query( + deps.as_ref(), + mock_env(), + Cw1155QueryMsg::IsApprovedForAll { + owner: users[0].to_string(), + operator: users[3].to_string(), + }, + ), + to_json_binary(&IsApprovedForAllResponse { approved: true }) + ); } #[test] From 9dbbc6ee8145a351d8dbf87e5e3c01b3cd4420cb Mon Sep 17 00:00:00 2001 From: shab Date: Tue, 23 Jul 2024 12:06:26 -0400 Subject: [PATCH 45/57] add sender/owner to transfer/mint/burn events --- contracts/cw1155-base/src/contract_tests.rs | 11 +++++++-- packages/cw1155/src/event.rs | 26 ++++++++++++++++----- packages/cw1155/src/execute.rs | 15 ++++++++---- 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/contracts/cw1155-base/src/contract_tests.rs b/contracts/cw1155-base/src/contract_tests.rs index 1131e6397..060dedefc 100644 --- a/contracts/cw1155-base/src/contract_tests.rs +++ b/contracts/cw1155-base/src/contract_tests.rs @@ -96,6 +96,7 @@ mod tests { ) .unwrap(), Response::new().add_event(Event::new("mint_single").add_attributes(vec![ + ("sender", minter.as_str()), ("recipient", user1.as_str()), ("token_id", token1.as_str()), ("amount", "1"), @@ -194,7 +195,8 @@ mod tests { ) .unwrap(), Response::new().add_event(Event::new("transfer_single").add_attributes(vec![ - ("sender", user1.as_str()), + ("owner", user1.as_str()), + ("sender", minter.as_str()), ("recipient", user2.as_str()), ("token_id", token1.as_str()), ("amount", "1"), @@ -382,7 +384,8 @@ mod tests { ) .unwrap(), Response::new().add_event(Event::new("transfer_batch").add_attributes(vec![ - ("sender", user2.as_str()), + ("owner", user2.as_str()), + ("sender", minter.as_str()), ("recipient", user1.as_str()), ("token_ids", &format!("{},{},{}", token1, token2, token3)), ("amounts", "1,1,1"), @@ -514,6 +517,7 @@ mod tests { ) .unwrap(), Response::new().add_event(Event::new("burn_single").add_attributes(vec![ + ("owner", user1.as_str()), ("sender", user1.as_str()), ("token_id", &token1), ("amount", "1"), @@ -597,6 +601,7 @@ mod tests { ) .unwrap(), Response::new().add_event(Event::new("burn_batch").add_attributes(vec![ + ("owner", user1.as_str()), ("sender", user1.as_str()), ("token_ids", &format!("{},{}", token2, token3)), ("amounts", "1,1"), @@ -795,6 +800,7 @@ mod tests { .unwrap() ) .add_event(Event::new("transfer_single").add_attributes(vec![ + ("owner", user1.as_str()), ("sender", user1.as_str()), ("recipient", receiver.as_str()), ("token_id", token2.as_str()), @@ -884,6 +890,7 @@ mod tests { .unwrap() ) .add_event(Event::new("transfer_batch").add_attributes(vec![ + ("owner", user1.as_str()), ("sender", user1.as_str()), ("recipient", receiver.as_str()), ( diff --git a/packages/cw1155/src/event.rs b/packages/cw1155/src/event.rs index 430808100..caddc5933 100644 --- a/packages/cw1155/src/event.rs +++ b/packages/cw1155/src/event.rs @@ -1,17 +1,24 @@ use crate::msg::TokenAmount; -use cosmwasm_std::{attr, Addr, Attribute, Event, Uint128}; +use cosmwasm_std::{attr, Addr, Attribute, Event, MessageInfo, Uint128}; /// Tracks token transfer actions pub struct TransferEvent { + pub owner: Addr, pub sender: Addr, pub recipient: Addr, pub tokens: Vec, } impl TransferEvent { - pub fn new(sender: &Addr, recipient: &Addr, tokens: Vec) -> Self { + pub fn new( + info: &MessageInfo, + from: Option, + recipient: &Addr, + tokens: Vec, + ) -> Self { Self { - sender: sender.clone(), + owner: from.unwrap_or_else(|| info.sender.clone()), + sender: info.sender.clone(), recipient: recipient.clone(), tokens, } @@ -29,6 +36,7 @@ impl From for Event { } )) .add_attributes(vec![ + attr("owner", event.owner.as_str()), attr("sender", event.sender.as_str()), attr("recipient", event.recipient.as_str()), ]) @@ -38,13 +46,15 @@ impl From for Event { /// Tracks token mint actions pub struct MintEvent { + pub sender: Addr, pub recipient: Addr, pub tokens: Vec, } impl MintEvent { - pub fn new(recipient: &Addr, tokens: Vec) -> Self { + pub fn new(info: &MessageInfo, recipient: &Addr, tokens: Vec) -> Self { Self { + sender: info.sender.clone(), recipient: recipient.clone(), tokens, } @@ -61,6 +71,7 @@ impl From for Event { "batch" } )) + .add_attribute("sender", event.sender.as_str()) .add_attribute("recipient", event.recipient.as_str()) .add_attributes(token_attributes(event.tokens)) } @@ -68,14 +79,16 @@ impl From for Event { /// Tracks token burn actions pub struct BurnEvent { + pub owner: Addr, pub sender: Addr, pub tokens: Vec, } impl BurnEvent { - pub fn new(sender: &Addr, tokens: Vec) -> Self { + pub fn new(info: &MessageInfo, from: Option, tokens: Vec) -> Self { Self { - sender: sender.clone(), + owner: from.unwrap_or_else(|| info.sender.clone()), + sender: info.sender.clone(), tokens, } } @@ -91,6 +104,7 @@ impl From for Event { "batch" } )) + .add_attribute("owner", event.owner.as_str()) .add_attribute("sender", event.sender.as_str()) .add_attributes(token_attributes(event.tokens)) } diff --git a/packages/cw1155/src/execute.rs b/packages/cw1155/src/execute.rs index f47a4a0b6..68b74fe17 100644 --- a/packages/cw1155/src/execute.rs +++ b/packages/cw1155/src/execute.rs @@ -164,6 +164,7 @@ pub trait Cw1155Execute< let event = self.update_balances( &mut deps, &env, + &info, None, Some(to), vec![TokenAmount { @@ -225,7 +226,7 @@ pub trait Cw1155Execute< .collect::>>()?; let mut rsp = Response::default(); - let event = self.update_balances(&mut deps, &env, None, Some(to), batch)?; + let event = self.update_balances(&mut deps, &env, &info, None, Some(to), batch)?; rsp = rsp.add_event(event); Ok(rsp) @@ -261,6 +262,7 @@ pub trait Cw1155Execute< let event = self.update_balances( &mut deps, &env, + &info, Some(from.clone()), Some(to.clone()), vec![TokenAmount { @@ -322,6 +324,7 @@ pub trait Cw1155Execute< let event = self.update_balances( &mut deps, &env, + &info, Some(from.clone()), Some(to.clone()), batch.to_vec(), @@ -380,6 +383,7 @@ pub trait Cw1155Execute< let event = self.update_balances( &mut deps, &env, + &info, Some(from), None, vec![TokenAmount { @@ -413,7 +417,7 @@ pub trait Cw1155Execute< let batch = self.verify_approvals(deps.storage, &env, &info, &from, batch)?; let mut rsp = Response::default(); - let event = self.update_balances(&mut deps, &env, Some(from), None, batch)?; + let event = self.update_balances(&mut deps, &env, &info, Some(from), None, batch)?; rsp = rsp.add_event(event); Ok(rsp) @@ -562,6 +566,7 @@ pub trait Cw1155Execute< &self, deps: &mut DepsMut, env: &Env, + info: &MessageInfo, from: Option, to: Option, tokens: Vec, @@ -638,17 +643,17 @@ pub trait Cw1155Execute< if let Some(to) = &to { // transfer - TransferEvent::new(from, to, tokens).into() + TransferEvent::new(info, Some(from.clone()), to, tokens).into() } else { // burn - BurnEvent::new(from, tokens).into() + BurnEvent::new(info, Some(from.clone()), tokens).into() } } else if let Some(to) = &to { // mint for TokenAmount { token_id, amount } in &tokens { config.increment_tokens(deps.storage, token_id, amount)?; } - MintEvent::new(to, tokens).into() + MintEvent::new(info, to, tokens).into() } else { panic!("Invalid transfer: from and to cannot both be None") }; From 49d70aab75e424cddc0400b29bad13311ea61b10 Mon Sep 17 00:00:00 2001 From: shab Date: Tue, 23 Jul 2024 12:28:44 -0400 Subject: [PATCH 46/57] implement update_metadata() for updating exension/token_uri. not on base api but method is available to be added to extensions if required --- packages/cw1155/src/event.rs | 26 +++++++++++++++++++++ packages/cw1155/src/execute.rs | 41 +++++++++++++++++++++++++++++++++- packages/cw721/src/error.rs | 3 +++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/packages/cw1155/src/event.rs b/packages/cw1155/src/event.rs index caddc5933..947c53820 100644 --- a/packages/cw1155/src/event.rs +++ b/packages/cw1155/src/event.rs @@ -238,3 +238,29 @@ pub fn token_attributes(tokens: Vec) -> Vec { ), ] } + +pub struct UpdateMetadataEvent { + pub token_id: String, + pub token_uri: String, + pub extension_update: bool, +} + +impl UpdateMetadataEvent { + pub fn new(token_id: &str, token_uri: &str, extension_update: bool) -> Self { + Self { + token_id: token_id.to_string(), + token_uri: token_uri.to_string(), + extension_update, + } + } +} + +impl From for Event { + fn from(event: UpdateMetadataEvent) -> Self { + Event::new("update_metadata").add_attributes(vec![ + attr("token_id", event.token_id), + attr("token_uri", event.token_uri), + attr("extension_update", event.extension_update.to_string()), + ]) + } +} diff --git a/packages/cw1155/src/execute.rs b/packages/cw1155/src/execute.rs index 68b74fe17..3351dc776 100644 --- a/packages/cw1155/src/execute.rs +++ b/packages/cw1155/src/execute.rs @@ -3,6 +3,7 @@ use cosmwasm_std::{ StdResult, Storage, SubMsg, Uint128, }; use cw2::set_contract_version; +use cw721::error::Cw721ContractError; use cw721::execute::{migrate_version, Cw721Execute}; use cw721::state::CollectionInfo; use cw_utils::Expiration; @@ -10,7 +11,8 @@ use serde::de::DeserializeOwned; use serde::Serialize; use crate::event::{ - ApproveAllEvent, ApproveEvent, BurnEvent, MintEvent, RevokeAllEvent, RevokeEvent, TransferEvent, + ApproveAllEvent, ApproveEvent, BurnEvent, MintEvent, RevokeAllEvent, RevokeEvent, + TransferEvent, UpdateMetadataEvent, }; use crate::msg::{Balance, Cw1155MintMsg, TokenAmount, TokenApproval}; use crate::receiver::Cw1155BatchReceiveMsg; @@ -748,6 +750,43 @@ pub trait Cw1155Execute< cw_ownable::update_ownership(deps.api, deps.storage, &env.block, &info.sender, action)?; Ok(Response::new().add_attributes(ownership.into_attributes())) } + + /// Allows creator to update onchain metadata and token uri. This is not available on the base, but the implementation + /// is available here for contracts that want to use it. + /// From `update_uri` on ERC-1155. + fn update_metadata( + &self, + deps: DepsMut, + info: MessageInfo, + token_id: String, + extension: Option, + token_uri: Option, + ) -> Result, Cw721ContractError> { + cw_ownable::assert_owner(deps.storage, &info.sender)?; + + if extension.is_none() && token_uri.is_none() { + return Err(Cw721ContractError::NoUpdatesRequested {}); + } + + let config = Cw1155Config::::default(); + let mut token_info = config.tokens.load(deps.storage, &token_id)?; + + // update extension + let extension_update = if let Some(extension) = extension { + token_info.extension = extension; + true + } else { + false + }; + + // update token uri + token_info.token_uri = token_uri; + + // store token + config.tokens.save(deps.storage, &token_id, &token_info)?; + + Ok(Response::new().add_event(UpdateMetadataEvent::new(&token_id, &token_info.token_uri.unwrap_or_default(), extension_update).into())) + } } /// To mitigate clippy::too_many_arguments warning diff --git a/packages/cw721/src/error.rs b/packages/cw721/src/error.rs index 99e274b3f..137a05476 100644 --- a/packages/cw721/src/error.rs +++ b/packages/cw721/src/error.rs @@ -24,4 +24,7 @@ pub enum Cw721ContractError { #[error("No withdraw address set")] NoWithdrawAddress {}, + + #[error("Must provide either 'token_uri' or 'extension' to update.")] + NoUpdatesRequested {}, } From e076bd12860d2912380405d06f65a9d56601ae29 Mon Sep 17 00:00:00 2001 From: shab Date: Tue, 23 Jul 2024 12:32:27 -0400 Subject: [PATCH 47/57] clippy --- contracts/cw1155-royalties/src/query.rs | 5 +---- contracts/cw2981-royalties/src/lib.rs | 1 - contracts/cw2981-royalties/src/query.rs | 5 +---- packages/cw1155/src/execute.rs | 4 ++-- packages/cw1155/src/query.rs | 2 +- 5 files changed, 5 insertions(+), 12 deletions(-) diff --git a/contracts/cw1155-royalties/src/query.rs b/contracts/cw1155-royalties/src/query.rs index 17e44e58a..a5d310f29 100644 --- a/contracts/cw1155-royalties/src/query.rs +++ b/contracts/cw1155-royalties/src/query.rs @@ -23,10 +23,7 @@ pub fn query_royalties_info( let royalty_from_sale_price = sale_price * royalty_percentage; let royalty_address = match token_info.extension { - Some(ext) => match ext.royalty_payment_address { - Some(addr) => addr, - None => String::from(""), - }, + Some(ext) => ext.royalty_payment_address.unwrap_or_default(), None => String::from(""), }; diff --git a/contracts/cw2981-royalties/src/lib.rs b/contracts/cw2981-royalties/src/lib.rs index ad229e815..66d78e18f 100644 --- a/contracts/cw2981-royalties/src/lib.rs +++ b/contracts/cw2981-royalties/src/lib.rs @@ -10,7 +10,6 @@ pub use cw721_base::{ execute::Cw721Execute, msg::InstantiateMsg, query::Cw721Query, Cw721Contract, }; -use crate::error::ContractError; use crate::msg::QueryMsg; // Version info for migration diff --git a/contracts/cw2981-royalties/src/query.rs b/contracts/cw2981-royalties/src/query.rs index 33704e3dc..ec32536bf 100644 --- a/contracts/cw2981-royalties/src/query.rs +++ b/contracts/cw2981-royalties/src/query.rs @@ -25,10 +25,7 @@ pub fn query_royalties_info( let royalty_from_sale_price = sale_price * royalty_percentage; let royalty_address = match token_info.extension { - Some(ext) => match ext.royalty_payment_address { - Some(addr) => addr, - None => String::from(""), - }, + Some(ext) => ext.royalty_payment_address.unwrap_or_default(), None => String::from(""), }; diff --git a/packages/cw1155/src/execute.rs b/packages/cw1155/src/execute.rs index 3351dc776..50d667db6 100644 --- a/packages/cw1155/src/execute.rs +++ b/packages/cw1155/src/execute.rs @@ -287,7 +287,7 @@ pub trait Cw1155Execute< )); } else { // transfer funds along to recipient - if info.funds.len() > 0 { + if !info.funds.is_empty() { let transfer_msg = BankMsg::Send { to_address: to.to_string(), amount: info.funds.to_vec(), @@ -345,7 +345,7 @@ pub trait Cw1155Execute< )); } else { // transfer funds along to recipient - if info.funds.len() > 0 { + if !info.funds.is_empty() { let transfer_msg = BankMsg::Send { to_address: to.to_string(), amount: info.funds.to_vec(), diff --git a/packages/cw1155/src/query.rs b/packages/cw1155/src/query.rs index f0113c34b..a192e9a45 100644 --- a/packages/cw1155/src/query.rs +++ b/packages/cw1155/src/query.rs @@ -266,7 +266,7 @@ pub trait Cw1155Query< fn build_approval(item: StdResult<(Addr, Expiration)>) -> StdResult { item.map(|(addr, expires)| Approval { - spender: addr.into(), + spender: addr, expires, }) } From de3e70911630d16af44516d5261ad2c1aca548a4 Mon Sep 17 00:00:00 2001 From: shab Date: Wed, 24 Jul 2024 12:48:16 -0400 Subject: [PATCH 48/57] change events to be attributes under wasm like cw721 has --- contracts/cw1155-base/src/contract_tests.rs | 35 +-- packages/cw1155/src/event.rs | 223 +++++++++++--------- packages/cw1155/src/execute.rs | 43 ++-- 3 files changed, 169 insertions(+), 132 deletions(-) diff --git a/contracts/cw1155-base/src/contract_tests.rs b/contracts/cw1155-base/src/contract_tests.rs index 060dedefc..43daf366c 100644 --- a/contracts/cw1155-base/src/contract_tests.rs +++ b/contracts/cw1155-base/src/contract_tests.rs @@ -95,12 +95,13 @@ mod tests { mint_msg, ) .unwrap(), - Response::new().add_event(Event::new("mint_single").add_attributes(vec![ + Response::new().add_attributes(vec![ + ("action", "mint_single"), ("sender", minter.as_str()), ("recipient", user1.as_str()), ("token_id", token1.as_str()), ("amount", "1"), - ])) + ]) ); // verify supply @@ -194,13 +195,14 @@ mod tests { transfer_msg, ) .unwrap(), - Response::new().add_event(Event::new("transfer_single").add_attributes(vec![ + Response::new().add_attributes(vec![ + ("action", "transfer_single"), ("owner", user1.as_str()), ("sender", minter.as_str()), ("recipient", user2.as_str()), ("token_id", token1.as_str()), ("amount", "1"), - ])) + ]) ); // query balance @@ -383,13 +385,14 @@ mod tests { batch_transfer_msg, ) .unwrap(), - Response::new().add_event(Event::new("transfer_batch").add_attributes(vec![ + Response::new().add_attributes(vec![ + ("action", "transfer_batch"), ("owner", user2.as_str()), ("sender", minter.as_str()), ("recipient", user1.as_str()), ("token_ids", &format!("{},{},{}", token1, token2, token3)), ("amounts", "1,1,1"), - ])) + ]) ); // batch query @@ -516,12 +519,13 @@ mod tests { }, ) .unwrap(), - Response::new().add_event(Event::new("burn_single").add_attributes(vec![ + Response::new().add_attributes(vec![ + ("action", "burn_single"), ("owner", user1.as_str()), ("sender", user1.as_str()), ("token_id", &token1), ("amount", "1"), - ])) + ]) ); // verify supply @@ -600,12 +604,13 @@ mod tests { }, ) .unwrap(), - Response::new().add_event(Event::new("burn_batch").add_attributes(vec![ + Response::new().add_attributes(vec![ + ("action", "burn_batch"), ("owner", user1.as_str()), ("sender", user1.as_str()), ("token_ids", &format!("{},{}", token2, token3)), ("amounts", "1,1"), - ])) + ]) ); // verify supply @@ -799,13 +804,14 @@ mod tests { .into_cosmos_msg(&operator_info, receiver.clone()) .unwrap() ) - .add_event(Event::new("transfer_single").add_attributes(vec![ + .add_attributes(vec![ + ("action", "transfer_single"), ("owner", user1.as_str()), ("sender", user1.as_str()), ("recipient", receiver.as_str()), ("token_id", token2.as_str()), ("amount", "1"), - ])) + ]) ); // verify balances @@ -889,7 +895,8 @@ mod tests { .into_cosmos_msg(&operator_info, receiver.clone()) .unwrap() ) - .add_event(Event::new("transfer_batch").add_attributes(vec![ + .add_attributes(vec![ + ("action", "transfer_batch"), ("owner", user1.as_str()), ("sender", user1.as_str()), ("recipient", receiver.as_str()), @@ -898,7 +905,7 @@ mod tests { &format!("{},{}", token1.as_str(), token2.as_str()) ), ("amounts", &format!("{},{}", 1, 1)), - ])) + ]) ); // verify balances diff --git a/packages/cw1155/src/event.rs b/packages/cw1155/src/event.rs index 947c53820..1141a9e39 100644 --- a/packages/cw1155/src/event.rs +++ b/packages/cw1155/src/event.rs @@ -1,5 +1,5 @@ use crate::msg::TokenAmount; -use cosmwasm_std::{attr, Addr, Attribute, Event, MessageInfo, Uint128}; +use cosmwasm_std::{attr, Addr, Attribute, MessageInfo, Uint128}; /// Tracks token transfer actions pub struct TransferEvent { @@ -25,22 +25,19 @@ impl TransferEvent { } } -impl From for Event { - fn from(event: TransferEvent) -> Self { - Event::new(format!( - "transfer_{}", - if event.tokens.len() == 1 { - "single" - } else { - "batch" - } - )) - .add_attributes(vec![ - attr("owner", event.owner.as_str()), - attr("sender", event.sender.as_str()), - attr("recipient", event.recipient.as_str()), - ]) - .add_attributes(token_attributes(event.tokens)) +impl IntoIterator for TransferEvent { + type Item = Attribute; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + let mut attrs = vec![ + event_action("transfer", &self.tokens), + attr("owner", self.owner.as_str()), + attr("sender", self.sender.as_str()), + attr("recipient", self.recipient.as_str()), + ]; + attrs.extend(token_attributes(self.tokens)); + attrs.into_iter() } } @@ -61,19 +58,18 @@ impl MintEvent { } } -impl From for Event { - fn from(event: MintEvent) -> Self { - Event::new(format!( - "mint_{}", - if event.tokens.len() == 1 { - "single" - } else { - "batch" - } - )) - .add_attribute("sender", event.sender.as_str()) - .add_attribute("recipient", event.recipient.as_str()) - .add_attributes(token_attributes(event.tokens)) +impl IntoIterator for MintEvent { + type Item = Attribute; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + let mut attrs = vec![ + event_action("mint", &self.tokens), + attr("sender", self.sender.as_str()), + attr("recipient", self.recipient.as_str()), + ]; + attrs.extend(token_attributes(self.tokens)); + attrs.into_iter() } } @@ -94,19 +90,18 @@ impl BurnEvent { } } -impl From for Event { - fn from(event: BurnEvent) -> Self { - Event::new(format!( - "burn_{}", - if event.tokens.len() == 1 { - "single" - } else { - "batch" - } - )) - .add_attribute("owner", event.owner.as_str()) - .add_attribute("sender", event.sender.as_str()) - .add_attributes(token_attributes(event.tokens)) +impl IntoIterator for BurnEvent { + type Item = Attribute; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + let mut attrs = vec![ + event_action("burn", &self.tokens), + attr("owner", self.owner.as_str()), + attr("sender", self.sender.as_str()), + ]; + attrs.extend(token_attributes(self.tokens)); + attrs.into_iter() } } @@ -129,14 +124,19 @@ impl ApproveEvent { } } -impl From for Event { - fn from(event: ApproveEvent) -> Self { - Event::new("approve_single").add_attributes(vec![ - attr("sender", event.sender.as_str()), - attr("operator", event.operator.as_str()), - attr("token_id", event.token_id), - attr("amount", event.amount.to_string()), - ]) +impl IntoIterator for ApproveEvent { + type Item = Attribute; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + vec![ + attr("action", "approve_single"), + attr("sender", self.sender.as_str()), + attr("operator", self.operator.as_str()), + attr("token_id", self.token_id), + attr("amount", self.amount.to_string()), + ] + .into_iter() } } @@ -159,14 +159,19 @@ impl RevokeEvent { } } -impl From for Event { - fn from(event: RevokeEvent) -> Self { - Event::new("revoke_single").add_attributes(vec![ - attr("sender", event.sender.as_str()), - attr("operator", event.operator.as_str()), - attr("token_id", event.token_id), - attr("amount", event.amount.to_string()), - ]) +impl IntoIterator for RevokeEvent { + type Item = Attribute; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + vec![ + attr("action", "revoke_single"), + attr("sender", self.sender.as_str()), + attr("operator", self.operator.as_str()), + attr("token_id", self.token_id), + attr("amount", self.amount.to_string()), + ] + .into_iter() } } @@ -185,12 +190,17 @@ impl ApproveAllEvent { } } -impl From for Event { - fn from(event: ApproveAllEvent) -> Self { - Event::new("approve_all").add_attributes(vec![ - attr("sender", event.sender.as_str()), - attr("operator", event.operator.as_str()), - ]) +impl IntoIterator for ApproveAllEvent { + type Item = Attribute; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + vec![ + attr("action", "approve_all"), + attr("sender", self.sender.as_str()), + attr("operator", self.operator.as_str()), + ] + .into_iter() } } @@ -209,15 +219,60 @@ impl RevokeAllEvent { } } -impl From for Event { - fn from(event: RevokeAllEvent) -> Self { - Event::new("revoke_all").add_attributes(vec![ - attr("sender", event.sender.as_str()), - attr("operator", event.operator.as_str()), - ]) +impl IntoIterator for RevokeAllEvent { + type Item = Attribute; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + vec![ + attr("action", "revoke_all"), + attr("sender", self.sender.as_str()), + attr("operator", self.operator.as_str()), + ] + .into_iter() + } +} + +pub struct UpdateMetadataEvent { + pub token_id: String, + pub token_uri: String, + pub extension_update: bool, +} + +impl UpdateMetadataEvent { + pub fn new(token_id: &str, token_uri: &str, extension_update: bool) -> Self { + Self { + token_id: token_id.to_string(), + token_uri: token_uri.to_string(), + extension_update, + } + } +} + +impl IntoIterator for UpdateMetadataEvent { + type Item = Attribute; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + vec![ + attr("action", "update_metadata"), + attr("token_id", self.token_id), + attr("token_uri", self.token_uri), + attr("extension_update", self.extension_update.to_string()), + ] + .into_iter() } } +pub fn event_action(action: &str, tokens: &Vec) -> Attribute { + let action = format!( + "{}_{}", + action, + if tokens.len() == 1 { "single" } else { "batch" } + ); + attr("action", action) +} + pub fn token_attributes(tokens: Vec) -> Vec { vec![ attr( @@ -238,29 +293,3 @@ pub fn token_attributes(tokens: Vec) -> Vec { ), ] } - -pub struct UpdateMetadataEvent { - pub token_id: String, - pub token_uri: String, - pub extension_update: bool, -} - -impl UpdateMetadataEvent { - pub fn new(token_id: &str, token_uri: &str, extension_update: bool) -> Self { - Self { - token_id: token_id.to_string(), - token_uri: token_uri.to_string(), - extension_update, - } - } -} - -impl From for Event { - fn from(event: UpdateMetadataEvent) -> Self { - Event::new("update_metadata").add_attributes(vec![ - attr("token_id", event.token_id), - attr("token_uri", event.token_uri), - attr("extension_update", event.extension_update.to_string()), - ]) - } -} diff --git a/packages/cw1155/src/execute.rs b/packages/cw1155/src/execute.rs index 50d667db6..e8c0c99da 100644 --- a/packages/cw1155/src/execute.rs +++ b/packages/cw1155/src/execute.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{ - Addr, BankMsg, Binary, CustomMsg, DepsMut, Empty, Env, Event, MessageInfo, Order, Response, + Addr, Attribute, BankMsg, Binary, CustomMsg, DepsMut, Empty, Env, MessageInfo, Order, Response, StdResult, Storage, SubMsg, Uint128, }; use cw2::set_contract_version; @@ -9,6 +9,7 @@ use cw721::state::CollectionInfo; use cw_utils::Expiration; use serde::de::DeserializeOwned; use serde::Serialize; +use std::vec::IntoIter; use crate::event::{ ApproveAllEvent, ApproveEvent, BurnEvent, MintEvent, RevokeAllEvent, RevokeEvent, @@ -174,7 +175,7 @@ pub trait Cw1155Execute< amount: msg.amount, }], )?; - rsp = rsp.add_event(event); + rsp = rsp.add_attributes(event); // store token info if not exist (if it is the first mint) if !config.tokens.has(deps.storage, &msg.token_id) { @@ -229,7 +230,7 @@ pub trait Cw1155Execute< let mut rsp = Response::default(); let event = self.update_balances(&mut deps, &env, &info, None, Some(to), batch)?; - rsp = rsp.add_event(event); + rsp = rsp.add_attributes(event); Ok(rsp) } @@ -272,7 +273,7 @@ pub trait Cw1155Execute< amount: balance_update.amount, }], )?; - rsp.events.push(event); + rsp.attributes.extend(event); if let Some(msg) = msg { rsp.messages.push(SubMsg::new( @@ -331,7 +332,7 @@ pub trait Cw1155Execute< Some(to.clone()), batch.to_vec(), )?; - rsp.events.push(event); + rsp.attributes.extend(event); if let Some(msg) = msg { rsp.messages.push(SubMsg::new( @@ -393,7 +394,7 @@ pub trait Cw1155Execute< amount: balance_update.amount, }], )?; - rsp = rsp.add_event(event); + rsp = rsp.add_attributes(event); Ok(rsp) } @@ -420,7 +421,7 @@ pub trait Cw1155Execute< let mut rsp = Response::default(); let event = self.update_balances(&mut deps, &env, &info, Some(from), None, batch)?; - rsp = rsp.add_event(event); + rsp = rsp.add_attributes(event); Ok(rsp) } @@ -461,8 +462,8 @@ pub trait Cw1155Execute< let mut rsp = Response::default(); - let event = ApproveEvent::new(&info.sender, &operator, &token_id, approval_amount).into(); - rsp = rsp.add_event(event); + let event = ApproveEvent::new(&info.sender, &operator, &token_id, approval_amount); + rsp = rsp.add_attributes(event); Ok(rsp) } @@ -490,8 +491,8 @@ pub trait Cw1155Execute< let mut rsp = Response::default(); - let event = ApproveAllEvent::new(&info.sender, &operator).into(); - rsp = rsp.add_event(event); + let event = ApproveAllEvent::new(&info.sender, &operator); + rsp = rsp.add_attributes(event); Ok(rsp) } @@ -532,8 +533,8 @@ pub trait Cw1155Execute< let mut rsp = Response::default(); - let event = RevokeEvent::new(&info.sender, &operator, &token_id, revoke_amount).into(); - rsp = rsp.add_event(event); + let event = RevokeEvent::new(&info.sender, &operator, &token_id, revoke_amount); + rsp = rsp.add_attributes(event); Ok(rsp) } @@ -553,8 +554,8 @@ pub trait Cw1155Execute< let mut rsp = Response::default(); - let event = RevokeAllEvent::new(&info.sender, &operator).into(); - rsp = rsp.add_event(event); + let event = RevokeAllEvent::new(&info.sender, &operator); + rsp = rsp.add_attributes(event); Ok(rsp) } @@ -572,7 +573,7 @@ pub trait Cw1155Execute< from: Option, to: Option, tokens: Vec, - ) -> Result { + ) -> Result, Cw1155ContractError> { let config = Cw1155Config::::default(); if let Some(from) = &from { for TokenAmount { token_id, amount } in tokens.iter() { @@ -611,7 +612,7 @@ pub trait Cw1155Execute< } } - let event = if let Some(from) = &from { + let event: IntoIter = if let Some(from) = &from { for TokenAmount { token_id, amount } in &tokens { // remove token approvals for (operator, approval) in config @@ -645,17 +646,17 @@ pub trait Cw1155Execute< if let Some(to) = &to { // transfer - TransferEvent::new(info, Some(from.clone()), to, tokens).into() + TransferEvent::new(info, Some(from.clone()), to, tokens).into_iter() } else { // burn - BurnEvent::new(info, Some(from.clone()), tokens).into() + BurnEvent::new(info, Some(from.clone()), tokens).into_iter() } } else if let Some(to) = &to { // mint for TokenAmount { token_id, amount } in &tokens { config.increment_tokens(deps.storage, token_id, amount)?; } - MintEvent::new(info, to, tokens).into() + MintEvent::new(info, to, tokens).into_iter() } else { panic!("Invalid transfer: from and to cannot both be None") }; @@ -785,7 +786,7 @@ pub trait Cw1155Execute< // store token config.tokens.save(deps.storage, &token_id, &token_info)?; - Ok(Response::new().add_event(UpdateMetadataEvent::new(&token_id, &token_info.token_uri.unwrap_or_default(), extension_update).into())) + Ok(Response::new().add_attributes(UpdateMetadataEvent::new(&token_id, &token_info.token_uri.unwrap_or_default(), extension_update))) } } From f0e2812003d3e4d2417300932e309f3fb26cef3d Mon Sep 17 00:00:00 2001 From: shab Date: Wed, 24 Jul 2024 13:35:08 -0400 Subject: [PATCH 49/57] use same collection key as cw721 --- contracts/cw1155-base/src/contract_tests.rs | 3 +-- packages/cw1155/src/state.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/contracts/cw1155-base/src/contract_tests.rs b/contracts/cw1155-base/src/contract_tests.rs index 43daf366c..d91f61cd0 100644 --- a/contracts/cw1155-base/src/contract_tests.rs +++ b/contracts/cw1155-base/src/contract_tests.rs @@ -3,8 +3,7 @@ mod tests { use crate::{Cw1155BaseContract, Cw1155BaseExecuteMsg, Cw1155BaseQueryMsg}; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; use cosmwasm_std::{ - from_json, to_json_binary, Addr, Binary, Empty, Event, OverflowError, Response, StdError, - Uint128, + from_json, to_json_binary, Addr, Binary, Empty, OverflowError, Response, StdError, Uint128, }; use cw1155::error::Cw1155ContractError; use cw1155::execute::Cw1155Execute; diff --git a/packages/cw1155/src/state.rs b/packages/cw1155/src/state.rs index 8d4be7259..00b21d326 100644 --- a/packages/cw1155/src/state.rs +++ b/packages/cw1155/src/state.rs @@ -56,7 +56,7 @@ where { fn default() -> Self { Self::new( - "cw1155_contract_info", + "collection_info", "tokens", "token_count", "supply", From 45fc627681681e28a36149459f0d2ea7fff73629 Mon Sep 17 00:00:00 2001 From: shab Date: Wed, 24 Jul 2024 14:14:46 -0400 Subject: [PATCH 50/57] zero amount check --- packages/cw1155/src/error.rs | 3 +++ packages/cw1155/src/execute.rs | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/packages/cw1155/src/error.rs b/packages/cw1155/src/error.rs index 3a70aafd2..b4cfff6ef 100644 --- a/packages/cw1155/src/error.rs +++ b/packages/cw1155/src/error.rs @@ -22,4 +22,7 @@ pub enum Cw1155ContractError { #[error("Expired")] Expired {}, + + #[error("Zero amount provided")] + InvalidZeroAmount {}, } diff --git a/packages/cw1155/src/execute.rs b/packages/cw1155/src/execute.rs index e8c0c99da..6dc2a712c 100644 --- a/packages/cw1155/src/execute.rs +++ b/packages/cw1155/src/execute.rs @@ -577,6 +577,9 @@ pub trait Cw1155Execute< let config = Cw1155Config::::default(); if let Some(from) = &from { for TokenAmount { token_id, amount } in tokens.iter() { + if amount.is_zero() { + return Err(Cw1155ContractError::InvalidZeroAmount {}); + } config.balances.update( deps.storage, (from.clone(), token_id.to_string()), @@ -591,6 +594,9 @@ pub trait Cw1155Execute< if let Some(to) = &to { for TokenAmount { token_id, amount } in tokens.iter() { + if amount.is_zero() { + return Err(Cw1155ContractError::InvalidZeroAmount {}); + } config.balances.update( deps.storage, (to.clone(), token_id.to_string()), @@ -614,6 +620,9 @@ pub trait Cw1155Execute< let event: IntoIter = if let Some(from) = &from { for TokenAmount { token_id, amount } in &tokens { + if amount.is_zero() { + return Err(Cw1155ContractError::InvalidZeroAmount {}); + } // remove token approvals for (operator, approval) in config .token_approves @@ -654,6 +663,9 @@ pub trait Cw1155Execute< } else if let Some(to) = &to { // mint for TokenAmount { token_id, amount } in &tokens { + if amount.is_zero() { + return Err(Cw1155ContractError::InvalidZeroAmount {}); + } config.increment_tokens(deps.storage, token_id, amount)?; } MintEvent::new(info, to, tokens).into_iter() From 5b888092be361dea919f19107de5187a04fb10ec Mon Sep 17 00:00:00 2001 From: shab Date: Wed, 24 Jul 2024 21:45:12 -0400 Subject: [PATCH 51/57] use rust version 1.75 --- Cargo.toml | 66 +++++++++++++++++++++++++-------------------------- Makefile.toml | 10 ++++---- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 45ec8a234..b0327b860 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,61 +2,61 @@ members = ["packages/*", "contracts/*"] [workspace.package] -version = "0.19.0" -edition = "2021" -license = "Apache-2.0" -repository = "https://github.com/CosmWasm/cw-nfts" -homepage = "https://cosmwasm.com" +version = "0.19.0" +edition = "2021" +license = "Apache-2.0" +repository = "https://github.com/CosmWasm/cw-nfts" +homepage = "https://cosmwasm.com" documentation = "https://docs.cosmwasm.com" -rust-version = "1.78" +rust-version = "1.75" [workspace.dependencies] cosmwasm-schema = "^1.5" -cosmwasm-std = "^1.5" -cw2 = "^1.1" -cw20 = "^1.1" -cw721 = { version = "*", path = "./packages/cw721" } -cw721-016 = { git = "https://github.com/CosmWasm/cw-nfts", tag = "v0.16.0", package = "cw721" } # needed for backwards compatibility and legacy migration -cw721-017 = { git = "https://github.com/CosmWasm/cw-nfts", tag = "v0.17.0", package = "cw721" } # needed for testing legacy migration -cw721-018 = { git = "https://github.com/CosmWasm/cw-nfts", tag = "v0.18.0", package = "cw721" } # needed for testing legacy migration -cw721-base = { version = "*", path = "./contracts/cw721-base" } -cw721-base-016 = { git = "https://github.com/CosmWasm/cw-nfts", tag = "v0.16.0", package = "cw721-base" } # needed for testing legacy migration -cw721-base-017 = { git = "https://github.com/CosmWasm/cw-nfts", tag = "v0.17.0", package = "cw721-base" } # needed for testing legacy migration -cw721-base-018 = { git = "https://github.com/CosmWasm/cw-nfts", tag = "v0.18.0", package = "cw721-base" } # needed for testing legacy migration +cosmwasm-std = "^1.5" +cw2 = "^1.1" +cw20 = "^1.1" +cw721 = { version = "*", path = "./packages/cw721" } +cw721-016 = { git = "https://github.com/CosmWasm/cw-nfts", tag = "v0.16.0", package = "cw721" } # needed for backwards compatibility and legacy migration +cw721-017 = { git = "https://github.com/CosmWasm/cw-nfts", tag = "v0.17.0", package = "cw721" } # needed for testing legacy migration +cw721-018 = { git = "https://github.com/CosmWasm/cw-nfts", tag = "v0.18.0", package = "cw721" } # needed for testing legacy migration +cw721-base = { version = "*", path = "./contracts/cw721-base" } +cw721-base-016 = { git = "https://github.com/CosmWasm/cw-nfts", tag = "v0.16.0", package = "cw721-base" } # needed for testing legacy migration +cw721-base-017 = { git = "https://github.com/CosmWasm/cw-nfts", tag = "v0.17.0", package = "cw721-base" } # needed for testing legacy migration +cw721-base-018 = { git = "https://github.com/CosmWasm/cw-nfts", tag = "v0.18.0", package = "cw721-base" } # needed for testing legacy migration cw1155 = { path = "./packages/cw1155", version = "*" } cw1155-base = { path = "./contracts/cw1155-base", version = "*" } -cw-multi-test = "^0.20" -cw-ownable = { git = "https://github.com/public-awesome/cw-plus-plus.git", rev = "28c1a09bfc6b4f1942fefe3eb0b50faf9d3b1523"} # TODO: switch to official https://github.com/larry0x/cw-plus-plus once merged +cw-multi-test = "^0.20" +cw-ownable = { git = "https://github.com/public-awesome/cw-plus-plus.git", rev = "28c1a09bfc6b4f1942fefe3eb0b50faf9d3b1523" } # TODO: switch to official https://github.com/larry0x/cw-plus-plus once merged cw-storage-plus = "^1.1" -cw-utils = "^1.0" -schemars = "^0.8" -serde = { version = "1.0.152", default-features = false, features = ["derive"] } -thiserror = "^1.0" +cw-utils = "^1.0" +schemars = "^0.8" +serde = { version = "1.0.152", default-features = false, features = ["derive"] } +thiserror = "^1.0" [profile.release.package.cw721-base] codegen-units = 1 -incremental = false +incremental = false [profile.release.package.cw721-metadata-onchain] codegen-units = 1 -incremental = false +incremental = false [profile.release.package.cw721-fixed-price] codegen-units = 1 -incremental = false +incremental = false [profile.release.package.cw721-non-transferable] codegen-units = 1 -incremental = false +incremental = false [profile.release.package.cw721-receiver] codegen-units = 1 -incremental = false +incremental = false [profile.release] -rpath = false -lto = true -overflow-checks = true -opt-level = 3 -debug = false +rpath = false +lto = true +overflow-checks = true +opt-level = 3 +debug = false debug-assertions = false diff --git a/Makefile.toml b/Makefile.toml index 633e010ed..72b7e5243 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -6,19 +6,19 @@ skip_core_tasks = true [tasks.fmt] command = "cargo" -args = ["fmt", "--all", "--check"] +args = ["fmt", "--all", "--check"] [tasks.test] command = "cargo" -args = ["test", "--locked"] +args = ["test", "--locked"] [tasks.lint] command = "cargo" -args = ["clippy", "--tests", "--", "-D", "warnings"] +args = ["clippy", "--tests", "--", "-D", "warnings"] [tasks.build] command = "cargo" -args = ["build", "--release", "--locked", "--target", "wasm32-unknown-unknown"] +args = ["build", "--release", "--locked", "--target", "wasm32-unknown-unknown"] [tasks.optimize] # https://hub.docker.com/r/cosmwasm/workspace-optimizer/tags https://hub.docker.com/r/cosmwasm/workspace-optimizer-arm64/tags @@ -32,7 +32,7 @@ fi docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - ${image}:0.16.0 + ${image}:0.15.1 """ [tasks.schema] From 3ba934156e4e29894d34cbf13ef896ae46ce031c Mon Sep 17 00:00:00 2001 From: shab Date: Fri, 9 Aug 2024 11:19:34 -0400 Subject: [PATCH 52/57] add Cw2981QueryExtensionMsg for convenience (possibly also to add back the extension msg? not sure why it was moved to root of query msg, which breaks existing cw2981s) --- contracts/cw2981-royalties/src/msg.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/contracts/cw2981-royalties/src/msg.rs b/contracts/cw2981-royalties/src/msg.rs index 30e3cc702..f045551c3 100644 --- a/contracts/cw2981-royalties/src/msg.rs +++ b/contracts/cw2981-royalties/src/msg.rs @@ -213,3 +213,25 @@ pub struct RoyaltiesInfoResponse { pub struct CheckRoyaltiesResponse { pub royalty_payments: bool, } + +#[cw_serde] +pub enum Cw2981QueryExtensionMsg { + /// Should be called on sale to see if royalties are owed + /// by the marketplace selling the NFT, if CheckRoyalties + /// returns true + /// See https://eips.ethereum.org/EIPS/eip-2981 + RoyaltyInfo { + token_id: String, + // the denom of this sale must also be the denom returned by RoyaltiesInfoResponse + // this was originally implemented as a Coin + // however that would mean you couldn't buy using CW20s + // as CW20 is just mapping of addr -> balance + sale_price: Uint128, + }, + /// Called against contract to determine if this NFT + /// implements royalties. Should return a boolean as part of + /// CheckRoyaltiesResponse - default can simply be true + /// if royalties are implemented at token level + /// (i.e. always check on sale) + CheckRoyalties {}, +} From 1ac83365c6738e750ee2bb6608bb16af8a563e24 Mon Sep 17 00:00:00 2001 From: shab Date: Fri, 9 Aug 2024 11:36:42 -0400 Subject: [PATCH 53/57] fixes cw2981 extension query msg --- contracts/cw2981-royalties/src/lib.rs | 5 ++++- contracts/cw2981-royalties/src/msg.rs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/contracts/cw2981-royalties/src/lib.rs b/contracts/cw2981-royalties/src/lib.rs index 66d78e18f..f441ffc82 100644 --- a/contracts/cw2981-royalties/src/lib.rs +++ b/contracts/cw2981-royalties/src/lib.rs @@ -58,8 +58,11 @@ pub mod entry { use super::*; + use crate::error::ContractError; use cosmwasm_std::entry_point; - use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; + use cosmwasm_std::{ + to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, + }; #[entry_point] pub fn instantiate( diff --git a/contracts/cw2981-royalties/src/msg.rs b/contracts/cw2981-royalties/src/msg.rs index f045551c3..d808e2ffb 100644 --- a/contracts/cw2981-royalties/src/msg.rs +++ b/contracts/cw2981-royalties/src/msg.rs @@ -129,7 +129,7 @@ pub enum QueryMsg { // -- TMetadataExtension and TCollectionInfoExtension, Error: // -- "type annotations needed: cannot infer type for type parameter `TMetadataExtension` declared on the enum `Cw721QueryMsg`" #[returns(())] - Extension { msg: Extension }, + Extension { msg: Cw2981QueryExtensionMsg }, } impl From for Cw721QueryMsg { From 2d0420ff57e10407245e8080c6f5a9f4c8cbe72d Mon Sep 17 00:00:00 2001 From: shab Date: Fri, 9 Aug 2024 12:05:59 -0400 Subject: [PATCH 54/57] fix cw2981 entry mod --- contracts/cw2981-royalties/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/cw2981-royalties/src/lib.rs b/contracts/cw2981-royalties/src/lib.rs index f441ffc82..e0187d413 100644 --- a/contracts/cw2981-royalties/src/lib.rs +++ b/contracts/cw2981-royalties/src/lib.rs @@ -52,19 +52,19 @@ pub type MintExtension = Option; pub type Cw2981Contract<'a> = Cw721Contract<'a, Extension, Empty, Empty, QueryMsg>; pub type ExecuteMsg = cw721_base::msg::ExecuteMsg; -#[cfg(not(feature = "library"))] pub mod entry { use self::msg::QueryMsg; use super::*; use crate::error::ContractError; + #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, }; - #[entry_point] + #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( mut deps: DepsMut, env: Env, @@ -81,7 +81,7 @@ pub mod entry { )?) } - #[entry_point] + #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, env: Env, @@ -109,7 +109,7 @@ pub mod entry { .map_err(Into::into) } - #[entry_point] + #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::RoyaltyInfo { From 501bfcfdbf2699590c9d86d00d072a5571dafae0 Mon Sep 17 00:00:00 2001 From: shab Date: Fri, 16 Aug 2024 11:58:00 -0400 Subject: [PATCH 55/57] adds query OwnersOf - gets balances of owners of a token id, and also returns total count of owners for that token id --- packages/cw1155/src/msg.rs | 12 ++++++++++++ packages/cw1155/src/query.rs | 20 +++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/packages/cw1155/src/msg.rs b/packages/cw1155/src/msg.rs index 2630f035c..6b9c796ed 100644 --- a/packages/cw1155/src/msg.rs +++ b/packages/cw1155/src/msg.rs @@ -112,6 +112,12 @@ pub enum Cw1155QueryMsg { /// Returns the current balance of the given account, 0 if unset. #[returns(BalanceResponse)] BalanceOf(OwnerToken), + #[returns(OwnersOfResponse)] + OwnersOf { + token_id: String, + limit: Option, + start_after: Option, + }, /// Returns the current balance of the given batch of accounts/tokens, 0 if unset. #[returns(BalancesResponse)] BalanceOfBatch(Vec), @@ -272,3 +278,9 @@ pub struct Balance { pub owner: Addr, pub amount: Uint128, } + +#[cw_serde] +pub struct OwnersOfResponse { + pub balances: Vec, + pub count: u64, +} diff --git a/packages/cw1155/src/query.rs b/packages/cw1155/src/query.rs index a192e9a45..ac760be90 100644 --- a/packages/cw1155/src/query.rs +++ b/packages/cw1155/src/query.rs @@ -9,7 +9,7 @@ use serde::Serialize; use crate::msg::{ ApprovedForAllResponse, Balance, BalanceResponse, BalancesResponse, Cw1155QueryMsg, - IsApprovedForAllResponse, OwnerToken, + IsApprovedForAllResponse, OwnerToken, OwnersOfResponse, }; use crate::msg::{NumTokensResponse, TokenInfoResponse}; use crate::state::Cw1155Config; @@ -57,6 +57,24 @@ pub trait Cw1155Query< balance: balance.amount, }) } + Cw1155QueryMsg::OwnersOf{ token_id, limit, start_after } => { + let config = Cw1155Config::::default(); + let start_after = start_after.map(|address| Bound::exclusive((Addr::unchecked(address), token_id.to_string()))); + let balances = config + .balances + .idx + .token_id + .prefix(token_id.to_string()) + .range_raw(deps.storage, start_after, None, Order::Ascending) + .take(limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize) + .map(|item| { + let (_, v) = item.unwrap(); + v + }).collect::>(); + let count = config.balances.idx.token_id.prefix(token_id) + .keys(deps.storage, None, None, Order::Ascending).count() as u64; + to_json_binary(&OwnersOfResponse{ balances, count }) + } Cw1155QueryMsg::AllBalances { token_id, start_after, From 76a30e0f419d792f5f1e54c468c80c001cc41506 Mon Sep 17 00:00:00 2001 From: shab Date: Fri, 16 Aug 2024 12:26:08 -0400 Subject: [PATCH 56/57] error instead of take min when transferring tokens with amount > than balance / approved balance --- packages/cw1155/src/error.rs | 8 +++++++- packages/cw1155/src/execute.rs | 23 +++++++++++++++++------ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/packages/cw1155/src/error.rs b/packages/cw1155/src/error.rs index b4cfff6ef..6a16cb3c9 100644 --- a/packages/cw1155/src/error.rs +++ b/packages/cw1155/src/error.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{OverflowError, StdError}; +use cosmwasm_std::{OverflowError, StdError, Uint128}; use cw2::VersionError; use cw_ownable::OwnershipError; use thiserror::Error; @@ -25,4 +25,10 @@ pub enum Cw1155ContractError { #[error("Zero amount provided")] InvalidZeroAmount {}, + + #[error("Not enough tokens available for this action. Available: {available}, Requested: {requested}.")] + NotEnoughTokens { + available: Uint128, + requested: Uint128, + }, } diff --git a/packages/cw1155/src/execute.rs b/packages/cw1155/src/execute.rs index 6dc2a712c..c1ce597e5 100644 --- a/packages/cw1155/src/execute.rs +++ b/packages/cw1155/src/execute.rs @@ -689,16 +689,22 @@ pub trait Cw1155Execute< let config = Cw1155Config::::default(); let operator = &info.sender; - let owner_balance = config - .balances - .load(storage, (owner.clone(), token_id.to_string()))?; - let mut balance_update = TokenAmount { + let balance_update = TokenAmount { token_id: token_id.to_string(), - amount: owner_balance.amount.min(amount), + amount, }; // owner or all operator can execute if owner == operator || config.verify_all_approval(storage, env, owner, operator) { + let owner_balance = config + .balances + .load(storage, (owner.clone(), token_id.to_string()))?; + if owner_balance.amount < amount { + return Err(Cw1155ContractError::NotEnoughTokens { + available: owner_balance.amount, + requested: amount, + }); + } return Ok(balance_update); } @@ -706,7 +712,12 @@ pub trait Cw1155Execute< if let Some(token_approval) = self.get_active_token_approval(storage, env, owner, operator, token_id) { - balance_update.amount = balance_update.amount.min(token_approval.amount); + if token_approval.amount < amount { + return Err(Cw1155ContractError::NotEnoughTokens { + available: token_approval.amount, + requested: amount, + }); + } return Ok(balance_update); } From 58e692b661cdc53449cd41e2c3d7555ae53b298b Mon Sep 17 00:00:00 2001 From: shab Date: Thu, 22 Aug 2024 21:19:39 -0400 Subject: [PATCH 57/57] bump rust version in ci --- .circleci/config.yml | 18 +++++++++--------- contracts/cw2981-royalties/src/lib.rs | 5 ++--- packages/cw1155/examples/schema.rs | 4 ++-- packages/cw1155/src/event.rs | 2 +- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 181601ec6..27c652e3a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -194,7 +194,7 @@ jobs: contract_cw1155_base: docker: - - image: rust:1.65.0 + - image: rust:1.78.0 working_directory: ~/project/contracts/cw1155-base steps: - checkout: @@ -204,7 +204,7 @@ jobs: command: rustc --version; cargo --version; rustup --version - restore_cache: keys: - - cargocache-cw1155-base-rust:1.65.0-{{ checksum "~/project/Cargo.lock" }} + - cargocache-cw1155-base-rust:1.78.0-{{ checksum "~/project/Cargo.lock" }} - run: name: Unit Tests environment: @@ -226,11 +226,11 @@ jobs: paths: - /usr/local/cargo/registry - target - key: cargocache-cw1155-base-rust:1.65.0-{{ checksum "~/project/Cargo.lock" }} + key: cargocache-cw1155-base-rust:1.78.0-{{ checksum "~/project/Cargo.lock" }} contract_cw1155_royalties: docker: - - image: rust:1.65.0 + - image: rust:1.78.0 working_directory: ~/project/contracts/cw1155-royalties steps: - checkout: @@ -240,7 +240,7 @@ jobs: command: rustc --version; cargo --version; rustup --version - restore_cache: keys: - - cargocache-cw1155-royalties-rust:1.65.0-{{ checksum "~/project/Cargo.lock" }} + - cargocache-cw1155-royalties-rust:1.78.0-{{ checksum "~/project/Cargo.lock" }} - run: name: Unit Tests environment: @@ -262,11 +262,11 @@ jobs: paths: - /usr/local/cargo/registry - target - key: cargocache-cw1155-royalties-rust:1.65.0-{{ checksum "~/project/Cargo.lock" }} + key: cargocache-cw1155-royalties-rust:1.78.0-{{ checksum "~/project/Cargo.lock" }} package_cw1155: docker: - - image: rust:1.65.0 + - image: rust:1.78.0 working_directory: ~/project/packages/cw1155 steps: - checkout: @@ -276,7 +276,7 @@ jobs: command: rustc --version; cargo --version; rustup --version; rustup target list --installed - restore_cache: keys: - - cargocache-v2-cw1155:1.65.0-{{ checksum "~/project/Cargo.lock" }} + - cargocache-v2-cw1155:1.78.0-{{ checksum "~/project/Cargo.lock" }} - run: name: Build library for native target command: cargo build --locked @@ -299,7 +299,7 @@ jobs: paths: - /usr/local/cargo/registry - target - key: cargocache-v2-cw1155:1.65.0-{{ checksum "~/project/Cargo.lock" }} + key: cargocache-v2-cw1155:1.78.0-{{ checksum "~/project/Cargo.lock" }} lint: docker: diff --git a/contracts/cw2981-royalties/src/lib.rs b/contracts/cw2981-royalties/src/lib.rs index 5f7bf9fd8..fa5ead489 100644 --- a/contracts/cw2981-royalties/src/lib.rs +++ b/contracts/cw2981-royalties/src/lib.rs @@ -12,9 +12,7 @@ use cw721::{ pub use query::{check_royalties, query_royalties_info}; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{to_json_binary, Empty}; - -use crate::error::ContractError; +use cosmwasm_std::Empty; // Version info for migration const CONTRACT_NAME: &str = "crates.io:cw2981-royalties"; @@ -142,6 +140,7 @@ mod tests { use cosmwasm_std::{from_json, Uint128}; + use crate::error::ContractError; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; use cw721::msg::Cw721InstantiateMsg; use cw721::traits::Cw721Query; diff --git a/packages/cw1155/examples/schema.rs b/packages/cw1155/examples/schema.rs index 915ade699..19706e535 100644 --- a/packages/cw1155/examples/schema.rs +++ b/packages/cw1155/examples/schema.rs @@ -1,12 +1,12 @@ use cosmwasm_schema::write_api; use cosmwasm_std::Empty; use cw1155::msg::{Cw1155ExecuteMsg, Cw1155InstantiateMsg, Cw1155QueryMsg}; -use cw721::state::DefaultOptionMetadataExtension; +use cw721::DefaultOptionalNftExtension; fn main() { write_api! { instantiate: Cw1155InstantiateMsg, - execute: Cw1155ExecuteMsg, + execute: Cw1155ExecuteMsg, query: Cw1155QueryMsg, } } diff --git a/packages/cw1155/src/event.rs b/packages/cw1155/src/event.rs index 1141a9e39..497c836e1 100644 --- a/packages/cw1155/src/event.rs +++ b/packages/cw1155/src/event.rs @@ -264,7 +264,7 @@ impl IntoIterator for UpdateMetadataEvent { } } -pub fn event_action(action: &str, tokens: &Vec) -> Attribute { +pub fn event_action(action: &str, tokens: &[TokenAmount]) -> Attribute { let action = format!( "{}_{}", action,