From e60f0db9f6f059f0ec5bf9f8a32e5643da124e89 Mon Sep 17 00:00:00 2001 From: Vu Ngoc Quang Date: Mon, 24 Apr 2023 00:21:43 +0700 Subject: [PATCH 1/4] Cw1155 package --- packages/cw1155/.cargo/config | 4 + packages/cw1155/Cargo.toml | 21 + packages/cw1155/README.md | 104 +++ packages/cw1155/examples/schema.rs | 12 + packages/cw1155/schema/cw1155.json | 833 ++++++++++++++++++ packages/cw1155/schema/raw/execute.json | 392 +++++++++ packages/cw1155/schema/raw/instantiate.json | 6 + packages/cw1155/schema/raw/query.json | 218 +++++ .../schema/raw/response_to_all_tokens.json | 18 + .../raw/response_to_approved_for_all.json | 100 +++ .../schema/raw/response_to_balance.json | 20 + .../schema/raw/response_to_batch_balance.json | 23 + .../raw/response_to_is_approved_for_all.json | 14 + .../schema/raw/response_to_token_info.json | 15 + .../cw1155/schema/raw/response_to_tokens.json | 18 + packages/cw1155/src/event.rs | 56 ++ packages/cw1155/src/lib.rs | 14 + packages/cw1155/src/msg.rs | 67 ++ packages/cw1155/src/query.rs | 95 ++ packages/cw1155/src/receiver.rs | 71 ++ 20 files changed, 2101 insertions(+) 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/examples/schema.rs create mode 100644 packages/cw1155/schema/cw1155.json create mode 100644 packages/cw1155/schema/raw/execute.json create mode 100644 packages/cw1155/schema/raw/instantiate.json create mode 100644 packages/cw1155/schema/raw/query.json create mode 100644 packages/cw1155/schema/raw/response_to_all_tokens.json create mode 100644 packages/cw1155/schema/raw/response_to_approved_for_all.json create mode 100644 packages/cw1155/schema/raw/response_to_balance.json create mode 100644 packages/cw1155/schema/raw/response_to_batch_balance.json create mode 100644 packages/cw1155/schema/raw/response_to_is_approved_for_all.json create mode 100644 packages/cw1155/schema/raw/response_to_token_info.json create mode 100644 packages/cw1155/schema/raw/response_to_tokens.json 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/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..9a623d361 --- /dev/null +++ b/packages/cw1155/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "cw1155" +description = "Definition and types for the CosmWasm-1155 interface" +authors = [ + "Huang Yi ", + "Vu Ngoc Quang ", +] +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } + +[dependencies] +cw-utils = { workspace = true } +cosmwasm-std = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +cosmwasm-schema = { workspace = true } + diff --git a/packages/cw1155/README.md b/packages/cw1155/README.md new file mode 100644 index 000000000..a37785ca6 --- /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. + +`Mint {to, token_id, value, msg}` - This mints some tokens to `to` account, If `to` is controlled by a smart contract, +it should implement `CW1155Receiver` interface, `msg` will be passed to it along with other fields, otherwise, `msg` +should be `None`. + +`BatchMint {to, batch: Vec<(token_id, value)>, msg}` - Batched version of `Mint`. + +`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 + +`Balance { owner, token_id }` - Query the balance of `owner` on particular type of token, default to `0` when record not +exist. + +`BatchBalance { owner, token_ids }` - Query the balance of `owner` on multiple types of tokens, batched version of +`Balance`. + +`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 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. diff --git a/packages/cw1155/examples/schema.rs b/packages/cw1155/examples/schema.rs new file mode 100644 index 000000000..b7bcb2065 --- /dev/null +++ b/packages/cw1155/examples/schema.rs @@ -0,0 +1,12 @@ +use cosmwasm_schema::write_api; +use cosmwasm_std::Empty; + +use cw1155::{Cw1155ExecuteMsg, Cw1155QueryMsg}; + +fn main() { + write_api! { + instantiate: Empty, + execute: Cw1155ExecuteMsg, + query: Cw1155QueryMsg + } +} diff --git a/packages/cw1155/schema/cw1155.json b/packages/cw1155/schema/cw1155.json new file mode 100644 index 000000000..fd8e1dca5 --- /dev/null +++ b/packages/cw1155/schema/cw1155.json @@ -0,0 +1,833 @@ +{ + "contract_name": "cw1155", + "contract_version": "0.17.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "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" + }, + "execute": { + "$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 + } + }, + "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 + } + }, + "additionalProperties": false + }, + { + "description": "Mint is a base message to mint tokens.", + "type": "object", + "required": [ + "mint" + ], + "properties": { + "mint": { + "type": "object", + "required": [ + "to", + "token_id", + "value" + ], + "properties": { + "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 + } + }, + "additionalProperties": false + }, + { + "description": "BatchMint is a base message to mint multiple types of tokens in batch.", + "type": "object", + "required": [ + "batch_mint" + ], + "properties": { + "batch_mint": { + "type": "object", + "required": [ + "batch", + "to" + ], + "properties": { + "batch": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/Uint128" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "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 + } + }, + "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 + } + }, + "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 + } + }, + "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 + } + ], + "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" + } + ] + }, + "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": "Returns the current balance of the given address, 0 if unset.", + "type": "object", + "required": [ + "balance" + ], + "properties": { + "balance": { + "type": "object", + "required": [ + "owner", + "token_id" + ], + "properties": { + "owner": { + "type": "string" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the current balance of the given address for a batch of tokens, 0 if unset.", + "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 + } + }, + "additionalProperties": false + }, + { + "description": "List all operators that can access all of the owner's tokens.", + "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 + } + }, + "additionalProperties": false + }, + { + "description": "Query approved status `owner` granted to `operator`.", + "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 + } + }, + "additionalProperties": false + }, + { + "description": "With MetaData Extension. Query metadata of token", + "type": "object", + "required": [ + "token_info" + ], + "properties": { + "token_info": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "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 + } + ] + }, + "migrate": null, + "sudo": null, + "responses": { + "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_from` in future queries to achieve pagination.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "approved_for_all": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ApprovedForAllResponse", + "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" + } + } + }, + "balance": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "BalanceResponse", + "type": "object", + "required": [ + "balance" + ], + "properties": { + "balance": { + "$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" + } + } + }, + "batch_balance": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "BatchBalanceResponse", + "type": "object", + "required": [ + "balances" + ], + "properties": { + "balances": { + "type": "array", + "items": { + "$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" + } + } + }, + "is_approved_for_all": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IsApprovedForAllResponse", + "type": "object", + "required": [ + "approved" + ], + "properties": { + "approved": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "token_info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TokenInfoResponse", + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "description": "Should be a url point to a json file", + "type": "string" + } + }, + "additionalProperties": false + }, + "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_from` in future queries to achieve pagination.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + } +} diff --git a/packages/cw1155/schema/raw/execute.json b/packages/cw1155/schema/raw/execute.json new file mode 100644 index 000000000..76934b9ca --- /dev/null +++ b/packages/cw1155/schema/raw/execute.json @@ -0,0 +1,392 @@ +{ + "$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 + } + }, + "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 + } + }, + "additionalProperties": false + }, + { + "description": "Mint is a base message to mint tokens.", + "type": "object", + "required": [ + "mint" + ], + "properties": { + "mint": { + "type": "object", + "required": [ + "to", + "token_id", + "value" + ], + "properties": { + "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 + } + }, + "additionalProperties": false + }, + { + "description": "BatchMint is a base message to mint multiple types of tokens in batch.", + "type": "object", + "required": [ + "batch_mint" + ], + "properties": { + "batch_mint": { + "type": "object", + "required": [ + "batch", + "to" + ], + "properties": { + "batch": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/Uint128" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "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 + } + }, + "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 + } + }, + "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 + } + }, + "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 + } + ], + "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" + } + ] + }, + "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/raw/instantiate.json b/packages/cw1155/schema/raw/instantiate.json new file mode 100644 index 000000000..5f6dfaf43 --- /dev/null +++ b/packages/cw1155/schema/raw/instantiate.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "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/raw/query.json b/packages/cw1155/schema/raw/query.json new file mode 100644 index 000000000..6a3af7b86 --- /dev/null +++ b/packages/cw1155/schema/raw/query.json @@ -0,0 +1,218 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "description": "Returns the current balance of the given address, 0 if unset.", + "type": "object", + "required": [ + "balance" + ], + "properties": { + "balance": { + "type": "object", + "required": [ + "owner", + "token_id" + ], + "properties": { + "owner": { + "type": "string" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the current balance of the given address for a batch of tokens, 0 if unset.", + "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 + } + }, + "additionalProperties": false + }, + { + "description": "List all operators that can access all of the owner's tokens.", + "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 + } + }, + "additionalProperties": false + }, + { + "description": "Query approved status `owner` granted to `operator`.", + "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 + } + }, + "additionalProperties": false + }, + { + "description": "With MetaData Extension. Query metadata of token", + "type": "object", + "required": [ + "token_info" + ], + "properties": { + "token_info": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "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 + } + ] +} diff --git a/packages/cw1155/schema/raw/response_to_all_tokens.json b/packages/cw1155/schema/raw/response_to_all_tokens.json new file mode 100644 index 000000000..14499395f --- /dev/null +++ b/packages/cw1155/schema/raw/response_to_all_tokens.json @@ -0,0 +1,18 @@ +{ + "$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" + } + } + }, + "additionalProperties": false +} diff --git a/packages/cw1155/schema/raw/response_to_approved_for_all.json b/packages/cw1155/schema/raw/response_to_approved_for_all.json new file mode 100644 index 000000000..7811894fb --- /dev/null +++ b/packages/cw1155/schema/raw/response_to_approved_for_all.json @@ -0,0 +1,100 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ApprovedForAllResponse", + "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/packages/cw1155/schema/raw/response_to_balance.json b/packages/cw1155/schema/raw/response_to_balance.json new file mode 100644 index 000000000..7dcf4d4a5 --- /dev/null +++ b/packages/cw1155/schema/raw/response_to_balance.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "BalanceResponse", + "type": "object", + "required": [ + "balance" + ], + "properties": { + "balance": { + "$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" + } + } +} diff --git a/packages/cw1155/schema/raw/response_to_batch_balance.json b/packages/cw1155/schema/raw/response_to_batch_balance.json new file mode 100644 index 000000000..610f2be98 --- /dev/null +++ b/packages/cw1155/schema/raw/response_to_batch_balance.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "BatchBalanceResponse", + "type": "object", + "required": [ + "balances" + ], + "properties": { + "balances": { + "type": "array", + "items": { + "$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" + } + } +} diff --git a/packages/cw1155/schema/raw/response_to_is_approved_for_all.json b/packages/cw1155/schema/raw/response_to_is_approved_for_all.json new file mode 100644 index 000000000..0b6deba51 --- /dev/null +++ b/packages/cw1155/schema/raw/response_to_is_approved_for_all.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IsApprovedForAllResponse", + "type": "object", + "required": [ + "approved" + ], + "properties": { + "approved": { + "type": "boolean" + } + }, + "additionalProperties": false +} diff --git a/packages/cw1155/schema/raw/response_to_token_info.json b/packages/cw1155/schema/raw/response_to_token_info.json new file mode 100644 index 000000000..9f5585870 --- /dev/null +++ b/packages/cw1155/schema/raw/response_to_token_info.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TokenInfoResponse", + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "description": "Should be a url point to a json file", + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/packages/cw1155/schema/raw/response_to_tokens.json b/packages/cw1155/schema/raw/response_to_tokens.json new file mode 100644 index 000000000..14499395f --- /dev/null +++ b/packages/cw1155/schema/raw/response_to_tokens.json @@ -0,0 +1,18 @@ +{ + "$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" + } + } + }, + "additionalProperties": false +} diff --git a/packages/cw1155/src/event.rs b/packages/cw1155/src/event.rs new file mode 100644 index 000000000..2d7709fa8 --- /dev/null +++ b/packages/cw1155/src/event.rs @@ -0,0 +1,56 @@ +use cosmwasm_std::{attr, Response, Uint128}; +use cw_utils::Event; + +/// Tracks token transfer/mint/burn actions +pub struct TransferEvent<'a> { + pub from: Option<&'a str>, + pub to: Option<&'a str>, + pub token_id: &'a str, + pub amount: Uint128, +} + +impl<'a> Event for TransferEvent<'a> { + fn add_attributes(&self, rsp: &mut Response) { + rsp.attributes.push(attr("action", "transfer")); + rsp.attributes.push(attr("token_id", self.token_id)); + rsp.attributes.push(attr("amount", self.amount)); + if let Some(from) = self.from { + rsp.attributes.push(attr("from", from.to_string())); + } + if let Some(to) = self.to { + rsp.attributes.push(attr("to", to.to_string())); + } + } +} + +/// Tracks token metadata changes +pub struct MetadataEvent<'a> { + pub url: &'a str, + pub token_id: &'a str, +} + +impl<'a> Event for MetadataEvent<'a> { + fn add_attributes(&self, rsp: &mut Response) { + rsp.attributes.push(attr("action", "set_metadata")); + rsp.attributes.push(attr("url", self.url)); + rsp.attributes.push(attr("token_id", self.token_id)); + } +} + +/// Tracks approve_all status changes +pub struct ApproveAllEvent<'a> { + pub sender: &'a str, + pub operator: &'a str, + 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.to_string())); + rsp.attributes + .push(attr("operator", self.operator.to_string())); + 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..dfea27c4f --- /dev/null +++ b/packages/cw1155/src/lib.rs @@ -0,0 +1,14 @@ +pub use cw_utils::Expiration; + +pub use crate::event::{ApproveAllEvent, MetadataEvent, TransferEvent}; +pub use crate::msg::{Cw1155ExecuteMsg, TokenId}; +pub use crate::query::{ + Approval, ApprovedForAllResponse, BalanceResponse, BatchBalanceResponse, Cw1155QueryMsg, + IsApprovedForAllResponse, TokenInfoResponse, TokensResponse, +}; +pub use crate::receiver::{Cw1155BatchReceiveMsg, Cw1155ReceiveMsg}; + +mod event; +mod msg; +mod query; +mod receiver; diff --git a/packages/cw1155/src/msg.rs b/packages/cw1155/src/msg.rs new file mode 100644 index 000000000..9e0a2bc92 --- /dev/null +++ b/packages/cw1155/src/msg.rs @@ -0,0 +1,67 @@ +use cosmwasm_schema::cw_serde; + +use cosmwasm_std::{Binary, Uint128}; +use cw_utils::Expiration; + +pub type TokenId = String; + +#[cw_serde] +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: TokenId, + 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<(TokenId, Uint128)>, + /// `None` means don't call the receiver interface + msg: Option, + }, + /// Mint is a base message to mint tokens. + Mint { + /// If `to` is not contract, `msg` should be `None` + to: String, + token_id: TokenId, + value: Uint128, + /// `None` means don't call the receiver interface + msg: Option, + }, + /// BatchMint is a base message to mint multiple types of tokens in batch. + BatchMint { + /// If `to` is not contract, `msg` should be `None` + to: String, + batch: Vec<(TokenId, Uint128)>, + /// `None` means don't call the receiver interface + msg: Option, + }, + /// Burn is a base message to burn tokens. + Burn { + from: String, + token_id: TokenId, + value: Uint128, + }, + /// BatchBurn is a base message to burn multiple types of tokens in batch. + BatchBurn { + from: String, + batch: Vec<(TokenId, 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..1a0ff03d6 --- /dev/null +++ b/packages/cw1155/src/query.rs @@ -0,0 +1,95 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; + +use cosmwasm_std::Uint128; +use cw_utils::Expiration; + +use crate::msg::TokenId; + +#[cw_serde] +#[derive(QueryResponses)] +pub enum Cw1155QueryMsg { + /// Returns the current balance of the given address, 0 if unset. + #[returns(BalanceResponse)] + Balance { owner: String, token_id: TokenId }, + /// Returns the current balance of the given address for a batch of tokens, 0 if unset. + #[returns(BatchBalanceResponse)] + BatchBalance { + owner: String, + token_ids: Vec, + }, + /// List all operators that can access all of the owner's tokens. + #[returns(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`. + #[returns(IsApprovedForAllResponse)] + IsApprovedForAll { owner: String, operator: String }, + + /// With MetaData Extension. + /// Query metadata of token + #[returns(TokenInfoResponse)] + TokenInfo { token_id: TokenId }, + + /// 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, + }, +} + +#[cw_serde] +pub struct BalanceResponse { + pub balance: Uint128, +} + +#[cw_serde] +pub struct BatchBalanceResponse { + pub balances: Vec, +} + +#[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, +} + +#[cw_serde] +pub struct IsApprovedForAllResponse { + pub approved: bool, +} + +#[cw_serde] +pub struct TokenInfoResponse { + /// Should be a url point to a json file + pub url: String, +} + +#[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, +} diff --git a/packages/cw1155/src/receiver.rs b/packages/cw1155/src/receiver.rs new file mode 100644 index 000000000..2241c2ecd --- /dev/null +++ b/packages/cw1155/src/receiver.rs @@ -0,0 +1,71 @@ +use cosmwasm_schema::cw_serde; + +use cosmwasm_std::{to_binary, Binary, CosmosMsg, StdResult, Uint128, WasmMsg}; + +use crate::msg::TokenId; + +/// Cw1155ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg +#[cw_serde] +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: TokenId, + 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 +#[cw_serde] +pub struct Cw1155BatchReceiveMsg { + pub operator: String, + pub from: Option, + pub batch: Vec<(TokenId, 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 +#[cw_serde] +enum ReceiverExecuteMsg { + Receive(Cw1155ReceiveMsg), + BatchReceive(Cw1155BatchReceiveMsg), +} From afc6582e1b494b41d36d4b46d0411c54e846543f Mon Sep 17 00:00:00 2001 From: Vu Ngoc Quang Date: Mon, 24 Apr 2023 23:46:36 +0700 Subject: [PATCH 2/4] cw1155-base contract --- Cargo.lock | 26 + Cargo.toml | 1 + contracts/cw1155-base/.cargo/config | 5 + contracts/cw1155-base/Cargo.toml | 29 + contracts/cw1155-base/examples/schema.rs | 12 + contracts/cw1155-base/src/contract.rs | 1225 ++++++++++++++++++++++ contracts/cw1155-base/src/error.rs | 14 + contracts/cw1155-base/src/lib.rs | 6 + contracts/cw1155-base/src/msg.rs | 9 + contracts/cw1155-base/src/state.rs | 13 + 10 files changed, 1340 insertions(+) create mode 100644 contracts/cw1155-base/.cargo/config create mode 100644 contracts/cw1155-base/Cargo.toml create mode 100644 contracts/cw1155-base/examples/schema.rs create mode 100644 contracts/cw1155-base/src/contract.rs create mode 100644 contracts/cw1155-base/src/error.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/state.rs diff --git a/Cargo.lock b/Cargo.lock index 757b0c3d6..39bdce5db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -301,6 +301,32 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw1155" +version = "0.17.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 1.0.1", + "schemars", + "serde", +] + +[[package]] +name = "cw1155-base" +version = "0.13.4" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.0.1", + "cw-utils 1.0.1", + "cw1155", + "cw2 1.0.1 (git+https://github.com/mars-protocol/cw-plus?rev=1a3a944)", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw2" version = "0.16.0" diff --git a/Cargo.toml b/Cargo.toml index 3fc75c507..e98e1158e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ cosmwasm-std = "1.2.1" cw2 = { git = "https://github.com/mars-protocol/cw-plus", rev = "1a3a944" } cw20 = "1.0.1" cw721 = { version = "0.17.0", path = "./packages/cw721" } +cw1155 = { version = "0.17.0", path = "./packages/cw1155" } cw721-base = { version = "0.17.0", path = "./contracts/cw721-base" } cw721-base-016 = { version = "0.16.0", package = "cw721-base" } cw-multi-test = "0.16.2" 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..a78269442 --- /dev/null +++ b/contracts/cw1155-base/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "cw1155-base" +version = "0.13.4" +authors = ["Huang Yi "] +edition = "2018" +description = "Basic implementation of a CosmWasm-1155 compliant token" +license = "Apache-2.0" +repository = "https://github.com/CosmWasm/cw-plus" +homepage = "https://cosmwasm.com" +documentation = "https://docs.cosmwasm.com" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all init/handle/query exports +library = [] + +[dependencies] +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 } +thiserror = { workspace = true } +cosmwasm-schema = { workspace = true } diff --git a/contracts/cw1155-base/examples/schema.rs b/contracts/cw1155-base/examples/schema.rs new file mode 100644 index 000000000..a4c4d13d2 --- /dev/null +++ b/contracts/cw1155-base/examples/schema.rs @@ -0,0 +1,12 @@ +use cosmwasm_schema::write_api; + +use cw1155::{Cw1155ExecuteMsg, Cw1155QueryMsg}; +use cw1155_base::msg::InstantiateMsg; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: Cw1155ExecuteMsg, + query: Cw1155QueryMsg, + } +} diff --git a/contracts/cw1155-base/src/contract.rs b/contracts/cw1155-base/src/contract.rs new file mode 100644 index 000000000..8150809f1 --- /dev/null +++ b/contracts/cw1155-base/src/contract.rs @@ -0,0 +1,1225 @@ +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Order, Response, StdResult, SubMsg, + Uint128, +}; +use cw_storage_plus::Bound; + +use cw1155::{ + ApproveAllEvent, ApprovedForAllResponse, BalanceResponse, BatchBalanceResponse, + Cw1155BatchReceiveMsg, Cw1155ExecuteMsg, Cw1155QueryMsg, Cw1155ReceiveMsg, Expiration, + IsApprovedForAllResponse, TokenId, TokenInfoResponse, TokensResponse, TransferEvent, +}; +use cw2::set_contract_version; +use cw_utils::{maybe_addr, Event}; + +use crate::error::ContractError; +use crate::msg::InstantiateMsg; +use crate::state::{APPROVES, BALANCES, MINTER, TOKENS}; + +// version info for migration info +const CONTRACT_NAME: &str = "crates.io:cw1155-base"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +const DEFAULT_LIMIT: u32 = 10; +const MAX_LIMIT: u32 = 30; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + 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)?; + MINTER.save(deps.storage, &minter)?; + Ok(Response::default()) +} + +/// To mitigate clippy::too_many_arguments warning +pub struct ExecuteEnv<'a> { + deps: DepsMut<'a>, + env: Env, + info: MessageInfo, +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: Cw1155ExecuteMsg, +) -> Result { + let env = ExecuteEnv { deps, env, info }; + match msg { + Cw1155ExecuteMsg::SendFrom { + from, + to, + token_id, + value, + msg, + } => execute_send_from(env, from, to, token_id, value, msg), + Cw1155ExecuteMsg::BatchSendFrom { + from, + to, + batch, + msg, + } => execute_batch_send_from(env, from, to, batch, msg), + Cw1155ExecuteMsg::Mint { + to, + token_id, + value, + msg, + } => execute_mint(env, to, token_id, value, msg), + Cw1155ExecuteMsg::BatchMint { to, batch, msg } => execute_batch_mint(env, to, batch, msg), + Cw1155ExecuteMsg::Burn { + from, + token_id, + value, + } => execute_burn(env, from, token_id, value), + Cw1155ExecuteMsg::BatchBurn { from, batch } => execute_batch_burn(env, from, batch), + Cw1155ExecuteMsg::ApproveAll { operator, expires } => { + execute_approve_all(env, operator, expires) + } + Cw1155ExecuteMsg::RevokeAll { operator } => execute_revoke_all(env, operator), + } +} + +/// 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<'a>( + deps: &'a mut DepsMut, + from: Option<&'a Addr>, + to: Option<&'a Addr>, + token_id: &'a str, + amount: Uint128, +) -> Result, ContractError> { + if let Some(from_addr) = from { + BALANCES.update( + deps.storage, + (from_addr, token_id), + |balance: Option| -> StdResult<_> { + Ok(balance.unwrap_or_default().checked_sub(amount)?) + }, + )?; + } + + if let Some(to_addr) = to { + BALANCES.update( + deps.storage, + (to_addr, token_id), + |balance: Option| -> StdResult<_> { + Ok(balance.unwrap_or_default().checked_add(amount)?) + }, + )?; + } + + Ok(TransferEvent { + from: from.map(|x| x.as_ref()), + to: to.map(|x| x.as_ref()), + token_id, + amount, + }) +} + +/// returns true iff the sender can execute approve or reject on the contract +fn check_can_approve(deps: Deps, env: &Env, owner: &Addr, operator: &Addr) -> StdResult { + // owner can approve + if owner == operator { + return Ok(true); + } + // operator can approve + let op = APPROVES.may_load(deps.storage, (owner, operator))?; + Ok(match op { + Some(ex) => !ex.is_expired(&env.block), + None => false, + }) +} + +fn guard_can_approve( + deps: Deps, + env: &Env, + owner: &Addr, + operator: &Addr, +) -> Result<(), ContractError> { + if !check_can_approve(deps, env, owner, operator)? { + Err(ContractError::Unauthorized {}) + } else { + Ok(()) + } +} + +pub fn execute_send_from( + env: ExecuteEnv, + from: String, + to: String, + token_id: TokenId, + 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; + + guard_can_approve(deps.as_ref(), &env, &from_addr, &info.sender)?; + + let mut rsp = Response::default(); + + let event = execute_transfer_inner( + &mut deps, + Some(&from_addr), + Some(&to_addr), + &token_id, + 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: token_id.clone(), + msg, + } + .into_cosmos_msg(to)?, + )] + } + + Ok(rsp) +} + +pub fn execute_mint( + env: ExecuteEnv, + to: String, + token_id: TokenId, + amount: Uint128, + msg: Option, +) -> Result { + let ExecuteEnv { mut deps, info, .. } = env; + + let to_addr = deps.api.addr_validate(&to)?; + + if info.sender != MINTER.load(deps.storage)? { + return Err(ContractError::Unauthorized {}); + } + + let mut rsp = Response::default(); + + let event = execute_transfer_inner(&mut deps, None, Some(&to_addr), &token_id, amount)?; + event.add_attributes(&mut rsp); + + if let Some(msg) = msg { + rsp.messages = vec![SubMsg::new( + Cw1155ReceiveMsg { + operator: info.sender.to_string(), + from: None, + amount, + token_id: token_id.clone(), + msg, + } + .into_cosmos_msg(to)?, + )] + } + + // insert if not exist + if !TOKENS.has(deps.storage, &token_id) { + // we must save some valid data here + TOKENS.save(deps.storage, &token_id, &String::new())?; + } + + Ok(rsp) +} + +pub fn execute_burn( + env: ExecuteEnv, + from: String, + token_id: TokenId, + 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 + guard_can_approve(deps.as_ref(), &env, &from_addr, &info.sender)?; + + let mut rsp = Response::default(); + let event = execute_transfer_inner(&mut deps, Some(&from_addr), None, &token_id, amount)?; + event.add_attributes(&mut rsp); + Ok(rsp) +} + +pub fn execute_batch_send_from( + env: ExecuteEnv, + from: String, + to: String, + batch: Vec<(TokenId, 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)?; + + 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 = execute_transfer_inner( + &mut deps, + Some(&from_addr), + Some(&to_addr), + token_id, + *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 execute_batch_mint( + env: ExecuteEnv, + to: String, + batch: Vec<(TokenId, Uint128)>, + msg: Option, +) -> Result { + let ExecuteEnv { mut deps, info, .. } = env; + if info.sender != MINTER.load(deps.storage)? { + return Err(ContractError::Unauthorized {}); + } + + let to_addr = deps.api.addr_validate(&to)?; + + let mut rsp = Response::default(); + + for (token_id, amount) in batch.iter() { + let event = execute_transfer_inner(&mut deps, None, Some(&to_addr), token_id, *amount)?; + event.add_attributes(&mut rsp); + + // insert if not exist + if !TOKENS.has(deps.storage, token_id) { + // we must save some valid data here + TOKENS.save(deps.storage, token_id, &String::new())?; + } + } + + if let Some(msg) = msg { + rsp.messages = vec![SubMsg::new( + Cw1155BatchReceiveMsg { + operator: info.sender.to_string(), + from: None, + batch, + msg, + } + .into_cosmos_msg(to)?, + )] + }; + + Ok(rsp) +} + +pub fn execute_batch_burn( + env: ExecuteEnv, + from: String, + batch: Vec<(TokenId, Uint128)>, +) -> Result { + let ExecuteEnv { + mut deps, + info, + env, + } = env; + + let from_addr = deps.api.addr_validate(&from)?; + + 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 = execute_transfer_inner(&mut deps, Some(&from_addr), None, &token_id, amount)?; + event.add_attributes(&mut rsp); + } + Ok(rsp) +} + +pub fn execute_approve_all( + 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)?; + APPROVES.save(deps.storage, (&info.sender, &operator_addr), &expires)?; + + let mut rsp = Response::default(); + ApproveAllEvent { + sender: info.sender.as_ref(), + operator: &operator, + approved: true, + } + .add_attributes(&mut rsp); + Ok(rsp) +} + +pub fn execute_revoke_all(env: ExecuteEnv, operator: String) -> Result { + let ExecuteEnv { deps, info, .. } = env; + let operator_addr = deps.api.addr_validate(&operator)?; + APPROVES.remove(deps.storage, (&info.sender, &operator_addr)); + + let mut rsp = Response::default(); + ApproveAllEvent { + sender: info.sender.as_ref(), + operator: &operator, + approved: false, + } + .add_attributes(&mut rsp); + Ok(rsp) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: Cw1155QueryMsg) -> StdResult { + match msg { + Cw1155QueryMsg::Balance { owner, token_id } => { + let owner_addr = deps.api.addr_validate(&owner)?; + let balance = BALANCES + .may_load(deps.storage, (&owner_addr, &token_id))? + .unwrap_or_default(); + to_binary(&BalanceResponse { balance }) + } + Cw1155QueryMsg::BatchBalance { owner, token_ids } => { + let owner_addr = deps.api.addr_validate(&owner)?; + let balances = token_ids + .into_iter() + .map(|token_id| -> StdResult<_> { + Ok(BALANCES + .may_load(deps.storage, (&owner_addr, &token_id))? + .unwrap_or_default()) + }) + .collect::>()?; + to_binary(&BatchBalanceResponse { balances }) + } + Cw1155QueryMsg::IsApprovedForAll { owner, operator } => { + let owner_addr = deps.api.addr_validate(&owner)?; + let operator_addr = deps.api.addr_validate(&operator)?; + let approved = 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(&query_all_approvals( + deps, + env, + owner_addr, + include_expired.unwrap_or(false), + start_addr, + limit, + )?) + } + Cw1155QueryMsg::TokenInfo { token_id } => { + let url = TOKENS.load(deps.storage, &token_id)?; + to_binary(&TokenInfoResponse { url }) + } + Cw1155QueryMsg::Tokens { + owner, + start_after, + limit, + } => { + let owner_addr = deps.api.addr_validate(&owner)?; + to_binary(&query_tokens(deps, owner_addr, start_after, limit)?) + } + Cw1155QueryMsg::AllTokens { start_after, limit } => { + to_binary(&query_all_tokens(deps, start_after, limit)?) + } + } +} + +fn build_approval(item: StdResult<(Addr, Expiration)>) -> StdResult { + item.map(|(addr, expires)| cw1155::Approval { + spender: addr.into(), + expires, + }) +} + +fn query_all_approvals( + 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 = 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( + 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 = BALANCES + .prefix(&owner) + .keys(deps.storage, start, None, Order::Ascending) + .take(limit) + .collect::>()?; + Ok(TokensResponse { tokens }) +} + +fn query_all_tokens( + 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 = TOKENS + .keys(deps.storage, start, None, Order::Ascending) + .take(limit) + .collect::>()?; + Ok(TokensResponse { tokens }) +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::{OverflowError, StdError}; + + use super::*; + + #[test] + fn check_transfers() { + // 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 + // - batch mint token2 and token3, success + // - 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 = 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 = Cw1155ExecuteMsg::Mint { + to: user1.clone(), + token_id: token1.clone(), + value: 1u64.into(), + msg: None, + }; + assert!(matches!( + execute( + deps.as_mut(), + mock_env(), + mock_info(user1.as_ref(), &[]), + mint_msg.clone(), + ), + Err(ContractError::Unauthorized {}) + )); + + // valid mint + assert_eq!( + 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() + }), + query( + deps.as_ref(), + mock_env(), + Cw1155QueryMsg::Balance { + owner: user1.clone(), + token_id: token1.clone(), + } + ), + ); + + let transfer_msg = Cw1155ExecuteMsg::SendFrom { + from: user1.clone(), + to: user2.clone(), + token_id: token1.clone(), + value: 1u64.into(), + msg: None, + }; + + // not approved yet + assert!(matches!( + execute( + deps.as_mut(), + mock_env(), + mock_info(minter.as_ref(), &[]), + transfer_msg.clone(), + ), + Err(ContractError::Unauthorized {}) + )); + + // approve + execute( + deps.as_mut(), + mock_env(), + mock_info(user1.as_ref(), &[]), + Cw1155ExecuteMsg::ApproveAll { + operator: minter.clone(), + expires: None, + }, + ) + .unwrap(); + + // transfer + assert_eq!( + 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!( + query( + deps.as_ref(), + mock_env(), + Cw1155QueryMsg::Balance { + owner: user2.clone(), + token_id: token1.clone(), + } + ), + to_binary(&BalanceResponse { + balance: 1u64.into() + }), + ); + assert_eq!( + query( + deps.as_ref(), + mock_env(), + Cw1155QueryMsg::Balance { + owner: user1.clone(), + token_id: token1.clone(), + } + ), + to_binary(&BalanceResponse { + balance: 0u64.into() + }), + ); + + // batch mint token2 and token3 + assert_eq!( + execute( + deps.as_mut(), + mock_env(), + mock_info(minter.as_ref(), &[]), + Cw1155ExecuteMsg::BatchMint { + to: user2.clone(), + batch: vec![(token2.clone(), 1u64.into()), (token3.clone(), 1u64.into())], + msg: None + }, + ) + .unwrap(), + Response::new() + .add_attribute("action", "transfer") + .add_attribute("token_id", &token2) + .add_attribute("amount", 1u64.to_string()) + .add_attribute("to", &user2) + .add_attribute("action", "transfer") + .add_attribute("token_id", &token3) + .add_attribute("amount", 1u64.to_string()) + .add_attribute("to", &user2) + ); + + // invalid batch transfer, (user2 not approved yet) + let batch_transfer_msg = Cw1155ExecuteMsg::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!( + execute( + deps.as_mut(), + mock_env(), + mock_info(minter.as_ref(), &[]), + batch_transfer_msg.clone(), + ), + Err(ContractError::Unauthorized {}), + )); + + // user2 approve + execute( + deps.as_mut(), + mock_env(), + mock_info(user2.as_ref(), &[]), + Cw1155ExecuteMsg::ApproveAll { + operator: minter.clone(), + expires: None, + }, + ) + .unwrap(); + + // valid batch transfer + assert_eq!( + 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!( + 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 + execute( + deps.as_mut(), + mock_env(), + mock_info(user1.as_ref(), &[]), + Cw1155ExecuteMsg::RevokeAll { + operator: minter.clone(), + }, + ) + .unwrap(); + + // query approval status + assert_eq!( + query( + deps.as_ref(), + mock_env(), + Cw1155QueryMsg::IsApprovedForAll { + owner: user1.clone(), + operator: minter.clone(), + } + ), + to_binary(&IsApprovedForAllResponse { approved: false }), + ); + + // tranfer without approval + assert!(matches!( + execute( + deps.as_mut(), + mock_env(), + mock_info(minter.as_ref(), &[]), + Cw1155ExecuteMsg::SendFrom { + from: user1.clone(), + to: user2, + token_id: token1.clone(), + value: 1u64.into(), + msg: None, + }, + ), + Err(ContractError::Unauthorized {}) + )); + + // burn token1 + assert_eq!( + execute( + deps.as_mut(), + mock_env(), + mock_info(user1.as_ref(), &[]), + Cw1155ExecuteMsg::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!( + execute( + deps.as_mut(), + mock_env(), + mock_info(user1.as_ref(), &[]), + Cw1155ExecuteMsg::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 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 dummy_msg = Binary::default(); + + let mut deps = mock_dependencies(); + let msg = InstantiateMsg { + minter: minter.clone(), + }; + let res = instantiate(deps.as_mut(), mock_env(), mock_info("operator", &[]), msg).unwrap(); + assert_eq!(0, res.messages.len()); + + execute( + deps.as_mut(), + mock_env(), + mock_info(minter.as_ref(), &[]), + Cw1155ExecuteMsg::Mint { + to: user1.clone(), + token_id: token2.clone(), + value: 1u64.into(), + msg: None, + }, + ) + .unwrap(); + + // mint to contract + assert_eq!( + execute( + deps.as_mut(), + mock_env(), + mock_info(minter.as_ref(), &[]), + Cw1155ExecuteMsg::Mint { + to: receiver.clone(), + token_id: token1.clone(), + value: 1u64.into(), + msg: Some(dummy_msg.clone()), + }, + ) + .unwrap(), + Response::new() + .add_message( + Cw1155ReceiveMsg { + operator: minter.clone(), + from: None, + amount: 1u64.into(), + token_id: token1.clone(), + msg: dummy_msg.clone(), + } + .into_cosmos_msg(receiver.clone()) + .unwrap() + ) + .add_attribute("action", "transfer") + .add_attribute("token_id", &token1) + .add_attribute("amount", 1u64.to_string()) + .add_attribute("to", &receiver) + ); + + // BatchSendFrom + assert_eq!( + execute( + deps.as_mut(), + mock_env(), + mock_info(user1.as_ref(), &[]), + Cw1155ExecuteMsg::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() { + // 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 = instantiate(deps.as_mut(), mock_env(), mock_info("operator", &[]), msg).unwrap(); + assert_eq!(0, res.messages.len()); + + execute( + deps.as_mut(), + mock_env(), + mock_info(minter.as_ref(), &[]), + Cw1155ExecuteMsg::BatchMint { + to: users[0].clone(), + batch: tokens + .iter() + .map(|token_id| (token_id.clone(), 1u64.into())) + .collect::>(), + msg: None, + }, + ) + .unwrap(); + + assert_eq!( + 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!( + 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!( + 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!( + query( + deps.as_ref(), + mock_env(), + Cw1155QueryMsg::TokenInfo { + token_id: "token5".to_owned() + }, + ), + to_binary(&TokenInfoResponse { url: "".to_owned() }) + ); + + for user in users[1..].iter() { + execute( + deps.as_mut(), + mock_env(), + mock_info(users[0].as_ref(), &[]), + Cw1155ExecuteMsg::ApproveAll { + operator: user.clone(), + expires: None, + }, + ) + .unwrap(); + } + + assert_eq!( + 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 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 = instantiate(deps.as_mut(), env.clone(), mock_info("operator", &[]), msg).unwrap(); + assert_eq!(0, res.messages.len()); + + execute( + deps.as_mut(), + env.clone(), + mock_info(minter.as_ref(), &[]), + Cw1155ExecuteMsg::Mint { + to: user1.clone(), + token_id: token1, + value: 1u64.into(), + msg: None, + }, + ) + .unwrap(); + + // invalid expires should be rejected + assert!(matches!( + execute( + deps.as_mut(), + env.clone(), + mock_info(user1.as_ref(), &[]), + Cw1155ExecuteMsg::ApproveAll { + operator: user2.clone(), + expires: Some(Expiration::AtHeight(5)), + }, + ), + Err(_) + )); + + execute( + deps.as_mut(), + env.clone(), + mock_info(user1.as_ref(), &[]), + Cw1155ExecuteMsg::ApproveAll { + operator: user2.clone(), + expires: Some(Expiration::AtHeight(100)), + }, + ) + .unwrap(); + + let query_msg = Cw1155QueryMsg::IsApprovedForAll { + owner: user1, + operator: user2, + }; + assert_eq!( + 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!( + query(deps.as_ref(), env, query_msg,), + to_binary(&IsApprovedForAllResponse { approved: false }) + ); + } + + #[test] + fn mint_overflow() { + 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 = instantiate(deps.as_mut(), env.clone(), mock_info("operator", &[]), msg).unwrap(); + assert_eq!(0, res.messages.len()); + + execute( + deps.as_mut(), + env.clone(), + mock_info(minter.as_ref(), &[]), + Cw1155ExecuteMsg::Mint { + to: user1.clone(), + token_id: token1.clone(), + value: u128::MAX.into(), + msg: None, + }, + ) + .unwrap(); + + assert!(matches!( + execute( + deps.as_mut(), + env, + mock_info(minter.as_ref(), &[]), + Cw1155ExecuteMsg::Mint { + to: user1, + token_id: token1, + value: 1u64.into(), + msg: None, + }, + ), + Err(ContractError::Std(StdError::Overflow { + source: OverflowError { .. }, + .. + })) + )); + } +} diff --git a/contracts/cw1155-base/src/error.rs b/contracts/cw1155-base/src/error.rs new file mode 100644 index 000000000..109695593 --- /dev/null +++ b/contracts/cw1155-base/src/error.rs @@ -0,0 +1,14 @@ +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("Expired")] + Expired {}, +} diff --git a/contracts/cw1155-base/src/lib.rs b/contracts/cw1155-base/src/lib.rs new file mode 100644 index 000000000..dfedc9dc6 --- /dev/null +++ b/contracts/cw1155-base/src/lib.rs @@ -0,0 +1,6 @@ +pub mod contract; +mod error; +pub mod msg; +pub mod state; + +pub use crate::error::ContractError; diff --git a/contracts/cw1155-base/src/msg.rs b/contracts/cw1155-base/src/msg.rs new file mode 100644 index 000000000..07e718d5b --- /dev/null +++ b/contracts/cw1155-base/src/msg.rs @@ -0,0 +1,9 @@ +use cosmwasm_schema::cw_serde; + +#[cw_serde] +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, +} diff --git a/contracts/cw1155-base/src/state.rs b/contracts/cw1155-base/src/state.rs new file mode 100644 index 000000000..1238f5094 --- /dev/null +++ b/contracts/cw1155-base/src/state.rs @@ -0,0 +1,13 @@ +use cosmwasm_std::{Addr, Uint128}; +use cw1155::Expiration; +use cw_storage_plus::{Item, Map}; + +/// Store the minter address who have permission to mint new tokens. +pub const MINTER: Item = Item::new("minter"); +/// Store the balance map, `(owner, token_id) -> balance` +pub const BALANCES: Map<(&Addr, &str), Uint128> = Map::new("balances"); +/// Store the approval status, `(owner, spender) -> expiration` +pub const APPROVES: Map<(&Addr, &Addr), Expiration> = Map::new("approves"); +/// Store the tokens metadata url, also supports enumerating tokens, +/// An entry for token_id must exist as long as there's tokens in circulation. +pub const TOKENS: Map<&str, String> = Map::new("tokens"); From d494099547c6b829f674efd0bfa722587e20148f Mon Sep 17 00:00:00 2001 From: Vu Ngoc Quang Date: Tue, 25 Apr 2023 22:55:26 +0700 Subject: [PATCH 3/4] add cw1155-base schema --- contracts/cw1155-base/schema/cw1155-base.json | 842 ++++++++++++++++++ 1 file changed, 842 insertions(+) create mode 100644 contracts/cw1155-base/schema/cw1155-base.json diff --git a/contracts/cw1155-base/schema/cw1155-base.json b/contracts/cw1155-base/schema/cw1155-base.json new file mode 100644 index 000000000..88f2d0d81 --- /dev/null +++ b/contracts/cw1155-base/schema/cw1155-base.json @@ -0,0 +1,842 @@ +{ + "contract_name": "cw1155-base", + "contract_version": "0.13.4", + "idl_version": "1.0.0", + "instantiate": { + "$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" + } + }, + "additionalProperties": false + }, + "execute": { + "$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 + } + }, + "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 + } + }, + "additionalProperties": false + }, + { + "description": "Mint is a base message to mint tokens.", + "type": "object", + "required": [ + "mint" + ], + "properties": { + "mint": { + "type": "object", + "required": [ + "to", + "token_id", + "value" + ], + "properties": { + "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 + } + }, + "additionalProperties": false + }, + { + "description": "BatchMint is a base message to mint multiple types of tokens in batch.", + "type": "object", + "required": [ + "batch_mint" + ], + "properties": { + "batch_mint": { + "type": "object", + "required": [ + "batch", + "to" + ], + "properties": { + "batch": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/Uint128" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "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 + } + }, + "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 + } + }, + "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 + } + }, + "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 + } + ], + "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" + } + ] + }, + "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": "Returns the current balance of the given address, 0 if unset.", + "type": "object", + "required": [ + "balance" + ], + "properties": { + "balance": { + "type": "object", + "required": [ + "owner", + "token_id" + ], + "properties": { + "owner": { + "type": "string" + }, + "token_id": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the current balance of the given address for a batch of tokens, 0 if unset.", + "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 + } + }, + "additionalProperties": false + }, + { + "description": "List all operators that can access all of the owner's tokens.", + "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 + } + }, + "additionalProperties": false + }, + { + "description": "Query approved status `owner` granted to `operator`.", + "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 + } + }, + "additionalProperties": false + }, + { + "description": "With MetaData Extension. Query metadata of token", + "type": "object", + "required": [ + "token_info" + ], + "properties": { + "token_info": { + "type": "object", + "required": [ + "token_id" + ], + "properties": { + "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 + } + ] + }, + "migrate": null, + "sudo": null, + "responses": { + "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_from` in future queries to achieve pagination.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "approved_for_all": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ApprovedForAllResponse", + "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" + } + } + }, + "balance": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "BalanceResponse", + "type": "object", + "required": [ + "balance" + ], + "properties": { + "balance": { + "$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" + } + } + }, + "batch_balance": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "BatchBalanceResponse", + "type": "object", + "required": [ + "balances" + ], + "properties": { + "balances": { + "type": "array", + "items": { + "$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" + } + } + }, + "is_approved_for_all": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "IsApprovedForAllResponse", + "type": "object", + "required": [ + "approved" + ], + "properties": { + "approved": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "token_info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TokenInfoResponse", + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "description": "Should be a url point to a json file", + "type": "string" + } + }, + "additionalProperties": false + }, + "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_from` in future queries to achieve pagination.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + } +} From 9f41a625f7f3546ae2e0f09e53468c2e8e056391 Mon Sep 17 00:00:00 2001 From: Vu Ngoc Quang Date: Sat, 29 Apr 2023 14:06:57 +0700 Subject: [PATCH 4/4] add circle ci for cw1155 --- .circleci/config.yml | 80 ++++++++++++++++++- .gitignore | 3 +- Cargo.lock | 2 +- contracts/cw1155-base/Cargo.toml | 32 ++++---- contracts/cw1155-base/schema/cw1155-base.json | 2 +- packages/cw1155/Cargo.toml | 5 +- 6 files changed, 99 insertions(+), 25 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index fa6f9af76..22b651d23 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,6 +7,8 @@ workflows: - contract_cw721_metadata_onchain - contract_cw721_fixed_price - package_cw721 + - contract_cw1155_base + - package_cw1155 - lint - wasm-build deploy: @@ -127,6 +129,43 @@ jobs: - target key: cargocache-cw721-fixed-price-rust:1.65.0-{{ checksum "~/project/Cargo.lock" }} + contract_cw1155_base: + docker: + - image: rust:1.65.0 + 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.65.0-{{ 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 | grep -v '/schema/raw/' || true) + 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.65.0-{{ checksum "~/project/Cargo.lock" }} + + package_cw721: docker: - image: rust:1.65.0 @@ -139,7 +178,44 @@ jobs: command: rustc --version; cargo --version; rustup --version; rustup target list --installed - restore_cache: keys: - - cargocache-v2-cw721:1.64.0-{{ checksum "~/project/Cargo.lock" }} + - cargocache-v2-cw721:1.65.0-{{ 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 | grep -v '/schema/raw/' || true) + 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-cw721:1.65.0-{{ checksum "~/project/Cargo.lock" }} + + package_cw1155: + docker: + - image: rust:1.65.0 + 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.65.0-{{ checksum "~/project/Cargo.lock" }} - run: name: Build library for native target command: cargo build --locked @@ -162,7 +238,7 @@ jobs: paths: - /usr/local/cargo/registry - target - key: cargocache-v2-cw721:1.64.0-{{ checksum "~/project/Cargo.lock" }} + key: cargocache-v2-cw721:1.65.0-{{ checksum "~/project/Cargo.lock" }} lint: docker: diff --git a/.gitignore b/.gitignore index b9ecc5875..3114f0b61 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ target/ hash.txt contracts.txt artifacts/ +**/schema/raw/* # code coverage -tarpaulin-report.* \ No newline at end of file +tarpaulin-report.* diff --git a/Cargo.lock b/Cargo.lock index 39bdce5db..c2d6b8861 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -314,7 +314,7 @@ dependencies = [ [[package]] name = "cw1155-base" -version = "0.13.4" +version = "0.17.0" dependencies = [ "cosmwasm-schema", "cosmwasm-std", diff --git a/contracts/cw1155-base/Cargo.toml b/contracts/cw1155-base/Cargo.toml index a78269442..e24738ffb 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 = ["Huang Yi "] -edition = "2018" -description = "Basic implementation of a CosmWasm-1155 compliant token" -license = "Apache-2.0" -repository = "https://github.com/CosmWasm/cw-plus" -homepage = "https://cosmwasm.com" -documentation = "https://docs.cosmwasm.com" +name = "cw1155-base" +description = "Basic implementation of cw1155 multiple tokens contract" +authors = ["Huang Yi "] +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } [lib] crate-type = ["cdylib", "rlib"] @@ -18,12 +18,12 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cw-utils = { workspace = true } -cw2 = { workspace = true } -cw1155 = { workspace = true } +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 } -thiserror = { workspace = true } +cosmwasm-std = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } cosmwasm-schema = { workspace = true } diff --git a/contracts/cw1155-base/schema/cw1155-base.json b/contracts/cw1155-base/schema/cw1155-base.json index 88f2d0d81..3796bdcb9 100644 --- a/contracts/cw1155-base/schema/cw1155-base.json +++ b/contracts/cw1155-base/schema/cw1155-base.json @@ -1,6 +1,6 @@ { "contract_name": "cw1155-base", - "contract_version": "0.13.4", + "contract_version": "0.17.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/packages/cw1155/Cargo.toml b/packages/cw1155/Cargo.toml index 9a623d361..b65f3e430 100644 --- a/packages/cw1155/Cargo.toml +++ b/packages/cw1155/Cargo.toml @@ -1,10 +1,7 @@ [package] name = "cw1155" description = "Definition and types for the CosmWasm-1155 interface" -authors = [ - "Huang Yi ", - "Vu Ngoc Quang ", -] +authors = ["Huang Yi "] version = { workspace = true } edition = { workspace = true } license = { workspace = true }