diff --git a/Cargo.lock b/Cargo.lock index 8a9a0d5bd..25d22e8ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -357,6 +357,7 @@ name = "cw20" version = "0.1.0" dependencies = [ "cosmwasm-schema", + "cosmwasm-std", "schemars", "serde", ] diff --git a/packages/cw20/Cargo.toml b/packages/cw20/Cargo.toml index b56406e01..97f2e52a8 100644 --- a/packages/cw20/Cargo.toml +++ b/packages/cw20/Cargo.toml @@ -5,6 +5,7 @@ authors = ["Ethan Frey "] edition = "2018" [dependencies] +cosmwasm-std = { version = "0.8.1" } schemars = "0.7" serde = { version = "1.0.103", default-features = false, features = ["derive"] } diff --git a/packages/cw20/README.md b/packages/cw20/README.md new file mode 100644 index 000000000..ddebd0eb4 --- /dev/null +++ b/packages/cw20/README.md @@ -0,0 +1,116 @@ +# CW20 Spec + +CW20 is a specification for fungible tokens based on CosmWasm. +The name and design is loosely based on Ethereum's ERC20 standard, +but many changes have been made. The types in here can be imported by +contracts that wish to implement this spec, or by contracts that call +to any standard cw20 contract. + +The specification is split into multiple sections, a contract may only +implement some of this functionality, but must implement the base. + +## Base + +This handles balances and transfers. Note that all amounts are +handled as `Uint128` (128 bit integers with JSON string representation). +Handling decimals is left to the UI and not interpreted + +### Messages + +`Transfer{recipient, amount}` - Moves `amount` tokens from the +`env.sender` account to the `recipient` account. This is designed to +send to an address controlled by a private key and *does not* trigger +any actions on the recipient if it is a contract. + +`Send{contract, amount, msg}` - Moves `amount` tokens from the +`env.sender` account to the `recipient` account. `contract` must be an +address of a contract that implements the `Receiver` interface. The `msg` +will be passed to the recipient contract, along with the amount. + +`Burn{amount}` - Remove `amount` tokens from the balance of `env.sender` +and reduce `total_supply` by the same amount. + +### Queries + +`Balance{address}` - Returns the balance of the given address. +Returns "0" if the address is unknown to the contract. Return type +is `BalanceResponse{balance}`. + +`Meta{}` - Returns the meta-data of the contract. Return type is +`MetaData{name, symbol, decimal, total_supply}`. + +### Receiver + +The counter-part to `Send` is `Receive`, which must be implemented by +any contract that wishes to manage CW20 tokens. This is generally *not* +implemented by any CW20 contract. + +`Receive{sender, amount, msg}` - This is designed to handle `Send` +messages. The address of the contract is stored in `env.sender` +so it cannot be faked. The contract should ensure the sender matches +the token contract it expects to handle, and not allow arbitrary addresses. + +The `sender` is the original account requesting to move the tokens +and `msg` is a `Binary` data that can be decoded into a contract-specific +message. This can be empty if we have only one default action, +or it may be a `ReceiveMsg` variant to clarify the intention. For example, +if I send to a uniswap contract, I can specify which token I want to swap +against using this field. + +## Allowances + +A contract may allow actors to delegate some of their balance to other +accounts. This is not as essential as with ERC20 as we use `Send`/`Receive` +to send tokens to a contract, not `Approve`/`TransferFrom`. But it +is still a nice use-case, and you can see how the Cosmos SDK wants to add +payment allowances to native tokens. This is mainly designed to provide +access to other public-key-based accounts. + +### Messages + +`Approve{spender, amount, expires}` - Sets an allowance such that `spender` +may access up to `amount` tokens from the `env.sender` account. This may +optionally come with an `Expiration` time, which if set limits when the +approval can be used (by time or height). + +`TransferFrom{owner, recipient, amount}` - This makes use of an allowance +and if there was a valid, un-expired pre-approval for the `env.sender`, +then we move `amount` tokens from `owner` to `recipient` and deduct it +from the available allowance. + +`SendFrom{owner, contract, amount, msg}` - `SendFrom` is to `Send`, what +`TransferFrom` is to `Transfer`. This allows a pre-approved account to +not just transfer the tokens, but to send them to another contract +to trigger a given action. `SendFrom` will set the `Receive{sender}` +to be the `owner` account (the account the money is coming from) +rather than `env.sender` (the account that triggered the transfer). + +`BurnFrom{owner, amount}` - This works like `TransferFrom`, but burns +the tokens instead of transfering them. This will reduce the owner's +balance, `total_supply` and the caller's allowance. + +TODO: IncreaseApproval/DecreaseApproval to store delta's rather than absolute values?? + +### Queries + +`Allowance{owner, spender}` - This returns the available allowance +that `spender` can access from the `owner`'s account, along with the +expiration info. Return type is `AllowanceResponse{balance, expiration}`. + +## Mintable + +This allows another contract to mint new tokens, possibly with a cap. +There is only one minter specified here, if you want more complex +access management, please use a multisig or other contract as the +minter address and handle updating the ACL there. + +### Messages + +`Mint{recipient, amount}` - If the `env.sender` is the allowed minter, +this will create `amount` new tokens (updating total supply) and +add them to the balance of `recipient`. + +### Queries + +`Minter{}` - Returns who and how much can be minted. Return type is +`MinterResponse {minter, cap}`. Cap may be unset. diff --git a/packages/cw20/examples/schema.rs b/packages/cw20/examples/schema.rs index 223ecb79e..a72b4a245 100644 --- a/packages/cw20/examples/schema.rs +++ b/packages/cw20/examples/schema.rs @@ -3,7 +3,10 @@ use std::fs::create_dir_all; use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; -use cw20::msg::HandleMsg; +use cw20::{ + AllowanceResponse, BalanceResponse, Cw20HandleMsg, Cw20QueryMsg, Cw20ReceiveMsg, MetaResponse, + MinterResponse, +}; fn main() { let mut out_dir = current_dir().unwrap(); @@ -11,5 +14,11 @@ fn main() { create_dir_all(&out_dir).unwrap(); remove_schemas(&out_dir).unwrap(); - export_schema(&schema_for!(HandleMsg), &out_dir); + export_schema(&schema_for!(Cw20HandleMsg), &out_dir); + export_schema(&schema_for!(Cw20QueryMsg), &out_dir); + export_schema(&schema_for!(Cw20ReceiveMsg), &out_dir); + export_schema(&schema_for!(AllowanceResponse), &out_dir); + export_schema(&schema_for!(BalanceResponse), &out_dir); + export_schema(&schema_for!(MetaResponse), &out_dir); + export_schema(&schema_for!(MinterResponse), &out_dir); } diff --git a/packages/cw20/schema/allowance_response.json b/packages/cw20/schema/allowance_response.json new file mode 100644 index 000000000..a0eac421b --- /dev/null +++ b/packages/cw20/schema/allowance_response.json @@ -0,0 +1,76 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AllowanceResponse", + "type": "object", + "required": [ + "allowance" + ], + "properties": { + "allowance": { + "$ref": "#/definitions/Uint128" + }, + "expires": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + } + }, + "definitions": { + "Expiration": { + "anyOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + } + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + } + } + ] + }, + "Uint128": { + "type": "string" + } + } +} diff --git a/packages/cw20/schema/balance_response.json b/packages/cw20/schema/balance_response.json new file mode 100644 index 000000000..efa5d6ce0 --- /dev/null +++ b/packages/cw20/schema/balance_response.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "BalanceResponse", + "type": "object", + "required": [ + "balance" + ], + "properties": { + "balance": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "type": "string" + } + } +} diff --git a/packages/cw20/schema/cw20_handle_msg.json b/packages/cw20/schema/cw20_handle_msg.json new file mode 100644 index 000000000..76dd01f7a --- /dev/null +++ b/packages/cw20/schema/cw20_handle_msg.json @@ -0,0 +1,292 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw20HandleMsg", + "anyOf": [ + { + "description": "Transfer is a base message to move tokens to another account without triggering actions", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "recipient" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "recipient": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "description": "Burn is a base message to destroy tokens forever", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + } + } + } + } + }, + { + "description": "Send is a base message to transfer tokens to a contract and trigger an action on the receiving contract.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "contract" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "contract": { + "$ref": "#/definitions/HumanAddr" + }, + "msg": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + } + } + } + } + }, + { + "description": "Only with \"approval\" extension. Allows spender to access up to amount tokens from the owner's (env.sender) account, with an optional expiration.", + "type": "object", + "required": [ + "approve" + ], + "properties": { + "approve": { + "type": "object", + "required": [ + "amount", + "spender" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "expires": { + "anyOf": [ + { + "$ref": "#/definitions/Expiration" + }, + { + "type": "null" + } + ] + }, + "spender": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "description": "Only with \"approval\" extension. Transfers amount tokens from owner -> recipient if `env.sender` has sufficient pre-approval.", + "type": "object", + "required": [ + "transfer_from" + ], + "properties": { + "transfer_from": { + "type": "object", + "required": [ + "amount", + "owner", + "recipient" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + }, + "recipient": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "description": "Only with \"approval\" extension. Sends amount tokens from owner -> contract if `env.sender` has sufficient pre-approval.", + "type": "object", + "required": [ + "send_from" + ], + "properties": { + "send_from": { + "type": "object", + "required": [ + "amount", + "contract", + "owner" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "contract": { + "$ref": "#/definitions/HumanAddr" + }, + "msg": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "description": "Only with \"approval\" extension. Destroys tokens forever", + "type": "object", + "required": [ + "burn_from" + ], + "properties": { + "burn_from": { + "type": "object", + "required": [ + "amount", + "owner" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "owner": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "description": "Only with the \"mintable\" extension. If authorized, creates amount new tokens and adds to the recipient balance.", + "type": "object", + "required": [ + "mint" + ], + "properties": { + "mint": { + "type": "object", + "required": [ + "amount", + "recipient" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "recipient": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + } + ], + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + }, + "Expiration": { + "anyOf": [ + { + "description": "AtHeight will expire when `env.block.height` >= height", + "type": "object", + "required": [ + "at_height" + ], + "properties": { + "at_height": { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + } + }, + { + "description": "AtTime will expire when `env.block.time` >= time", + "type": "object", + "required": [ + "at_time" + ], + "properties": { + "at_time": { + "type": "object", + "required": [ + "time" + ], + "properties": { + "time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + } + } + ] + }, + "HumanAddr": { + "type": "string" + }, + "Uint128": { + "type": "string" + } + } +} diff --git a/packages/cw20/schema/cw20_query_msg.json b/packages/cw20/schema/cw20_query_msg.json new file mode 100644 index 000000000..9c901fd5a --- /dev/null +++ b/packages/cw20/schema/cw20_query_msg.json @@ -0,0 +1,79 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw20QueryMsg", + "anyOf": [ + { + "description": "Returns the current balance of the given address, 0 if unset. Return type: BalanceResponse.", + "type": "object", + "required": [ + "balance" + ], + "properties": { + "balance": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "description": "Returns metadata on the contract - name, decimals, supply, etc. Return type: MetaResponse.", + "type": "object", + "required": [ + "meta" + ], + "properties": { + "meta": { + "type": "object" + } + } + }, + { + "description": "Only with \"allowance\" extension. Returns how much spender can use from owner account, 0 if unset. Return type: AllowanceResponse.", + "type": "object", + "required": [ + "allowance" + ], + "properties": { + "allowance": { + "type": "object", + "required": [ + "owner", + "spender" + ], + "properties": { + "owner": { + "$ref": "#/definitions/HumanAddr" + }, + "spender": { + "$ref": "#/definitions/HumanAddr" + } + } + } + } + }, + { + "description": "Only with \"mintable\" extension. Returns who can mint and how much. Return type: MinterResponse.", + "type": "object", + "required": [ + "minter" + ], + "properties": { + "minter": { + "type": "object" + } + } + } + ], + "definitions": { + "HumanAddr": { + "type": "string" + } + } +} diff --git a/packages/cw20/schema/cw20_receive_msg.json b/packages/cw20/schema/cw20_receive_msg.json new file mode 100644 index 000000000..2efcf88ee --- /dev/null +++ b/packages/cw20/schema/cw20_receive_msg.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cw20ReceiveMsg", + "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a HandleMsg", + "type": "object", + "required": [ + "amount", + "sender" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "msg": { + "anyOf": [ + { + "$ref": "#/definitions/Binary" + }, + { + "type": "null" + } + ] + }, + "sender": { + "$ref": "#/definitions/HumanAddr" + } + }, + "definitions": { + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec", + "type": "string" + }, + "HumanAddr": { + "type": "string" + }, + "Uint128": { + "type": "string" + } + } +} diff --git a/packages/cw20/schema/handle_msg.json b/packages/cw20/schema/handle_msg.json deleted file mode 100644 index a9783e3e1..000000000 --- a/packages/cw20/schema/handle_msg.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "HandleMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "increment" - ], - "properties": { - "increment": { - "type": "object" - } - } - }, - { - "type": "object", - "required": [ - "reset" - ], - "properties": { - "reset": { - "type": "object", - "required": [ - "count" - ], - "properties": { - "count": { - "type": "integer", - "format": "int32" - } - } - } - } - } - ] -} diff --git a/packages/cw20/schema/meta_response.json b/packages/cw20/schema/meta_response.json new file mode 100644 index 000000000..211cde46f --- /dev/null +++ b/packages/cw20/schema/meta_response.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MetaResponse", + "type": "object", + "required": [ + "decimals", + "name", + "symbol", + "total_supply" + ], + "properties": { + "decimals": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "name": { + "type": "string" + }, + "symbol": { + "type": "string" + }, + "total_supply": { + "$ref": "#/definitions/Uint128" + } + }, + "definitions": { + "Uint128": { + "type": "string" + } + } +} diff --git a/packages/cw20/schema/minter_response.json b/packages/cw20/schema/minter_response.json new file mode 100644 index 000000000..38df5a302 --- /dev/null +++ b/packages/cw20/schema/minter_response.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MinterResponse", + "type": "object", + "required": [ + "minter" + ], + "properties": { + "cap": { + "description": "cap is how many more tokens can be issued by the minter", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "minter": { + "$ref": "#/definitions/HumanAddr" + } + }, + "definitions": { + "HumanAddr": { + "type": "string" + }, + "Uint128": { + "type": "string" + } + } +} diff --git a/packages/cw20/src/helpers.rs b/packages/cw20/src/helpers.rs new file mode 100644 index 000000000..6e3d0a884 --- /dev/null +++ b/packages/cw20/src/helpers.rs @@ -0,0 +1,114 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{ + to_binary, Api, CanonicalAddr, CosmosMsg, HumanAddr, Querier, StdResult, Uint128, WasmMsg, + WasmQuery, +}; + +use crate::{ + AllowanceResponse, BalanceResponse, Cw20HandleMsg, Cw20QueryMsg, MetaResponse, MinterResponse, +}; + +/// Cw20Contract is a wrapper around HumanAddr that provides a lot of helpers +/// for working with this. +/// +/// If you wish to persist this, convert to Cw20CanonicalContract via .canonical() +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct Cw20Contract(pub HumanAddr); + +impl Cw20Contract { + pub fn addr(&self) -> HumanAddr { + self.0.clone() + } + + /// Convert this address to a form fit for storage + pub fn canonical(&self, api: &A) -> StdResult { + let canon = api.canonical_address(&self.0)?; + Ok(Cw20CanonicalContract(canon)) + } + + pub fn call>(&self, msg: T) -> StdResult { + let msg = to_binary(&msg.into())?; + Ok(WasmMsg::Execute { + contract_addr: self.addr(), + msg, + send: vec![], + } + .into()) + } + + /// Get token balance for the given address + pub fn balance(&self, querier: &Q, address: HumanAddr) -> StdResult { + let msg = Cw20QueryMsg::Balance { address }; + let query = WasmQuery::Smart { + contract_addr: self.addr(), + msg: to_binary(&msg)?, + } + .into(); + let res: BalanceResponse = querier.query(&query)?; + Ok(res.balance) + } + + /// Get metadata from the contract. This is a good check that the address + /// is a valid Cw20 contract. + pub fn meta(&self, querier: &Q) -> StdResult { + let msg = Cw20QueryMsg::Meta {}; + let query = WasmQuery::Smart { + contract_addr: self.addr(), + msg: to_binary(&msg)?, + } + .into(); + querier.query(&query) + } + + /// Get allowance of spender to use owner's account + pub fn allowance( + &self, + querier: &Q, + owner: HumanAddr, + spender: HumanAddr, + ) -> StdResult { + let msg = Cw20QueryMsg::Allowance { owner, spender }; + let query = WasmQuery::Smart { + contract_addr: self.addr(), + msg: to_binary(&msg)?, + } + .into(); + querier.query(&query) + } + + /// Find info on who can mint, and how much + pub fn minter(&self, querier: &Q) -> StdResult { + let msg = Cw20QueryMsg::Minter {}; + let query = WasmQuery::Smart { + contract_addr: self.addr(), + msg: to_binary(&msg)?, + } + .into(); + querier.query(&query) + } + + /// returns true if the contract supports the allowance extension + pub fn has_allowance(&self, querier: &Q) -> bool { + self.allowance(querier, self.addr(), self.addr()).is_ok() + } + + /// returns true if the contract supports the mintable extension + pub fn is_mintable(&self, querier: &Q) -> bool { + self.minter(querier).is_ok() + } +} + +/// This is a respresentation of Cw20Contract for storage. +/// Don't use it directly, just translate to the Cw20Contract when needed. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct Cw20CanonicalContract(pub CanonicalAddr); + +impl Cw20CanonicalContract { + /// Convert this address to a form fit for usage in messages and queries + pub fn human(&self, api: &A) -> StdResult { + let human = api.human_address(&self.0)?; + Ok(Cw20Contract(human)) + } +} diff --git a/packages/cw20/src/lib.rs b/packages/cw20/src/lib.rs index ff275852f..b7952dbd5 100644 --- a/packages/cw20/src/lib.rs +++ b/packages/cw20/src/lib.rs @@ -1,4 +1,14 @@ -pub mod msg; +mod helpers; +mod msg; +mod query; +mod receiver; + +pub use crate::helpers::{Cw20CanonicalContract, Cw20Contract}; +pub use crate::msg::Cw20HandleMsg; +pub use crate::query::{ + AllowanceResponse, BalanceResponse, Cw20QueryMsg, MetaResponse, MinterResponse, +}; +pub use crate::receiver::Cw20ReceiveMsg; #[cfg(test)] mod tests { diff --git a/packages/cw20/src/msg.rs b/packages/cw20/src/msg.rs index 889b15fc1..d2b3756e1 100644 --- a/packages/cw20/src/msg.rs +++ b/packages/cw20/src/msg.rs @@ -1,9 +1,62 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +use cosmwasm_std::{Binary, HumanAddr, Uint128}; + +#[derive(Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum Cw20HandleMsg { + /// Transfer is a base message to move tokens to another account without triggering actions + Transfer { + recipient: HumanAddr, + amount: Uint128, + }, + /// Burn is a base message to destroy tokens forever + Burn { amount: Uint128 }, + /// Send is a base message to transfer tokens to a contract and trigger an action + /// on the receiving contract. + Send { + contract: HumanAddr, + amount: Uint128, + msg: Option, + }, + /// Only with "approval" extension. Allows spender to access up to amount tokens + /// from the owner's (env.sender) account, with an optional expiration. + Approve { + spender: HumanAddr, + amount: Uint128, + expires: Option, + }, + /// Only with "approval" extension. Transfers amount tokens from owner -> recipient + /// if `env.sender` has sufficient pre-approval. + TransferFrom { + owner: HumanAddr, + recipient: HumanAddr, + amount: Uint128, + }, + /// Only with "approval" extension. Sends amount tokens from owner -> contract + /// if `env.sender` has sufficient pre-approval. + SendFrom { + owner: HumanAddr, + contract: HumanAddr, + amount: Uint128, + msg: Option, + }, + /// Only with "approval" extension. Destroys tokens forever + BurnFrom { owner: HumanAddr, amount: Uint128 }, + /// Only with the "mintable" extension. If authorized, creates amount new tokens + /// and adds to the recipient balance. + Mint { + recipient: HumanAddr, + amount: Uint128, + }, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] -pub enum HandleMsg { - Increment {}, - Reset { count: i32 }, +pub enum Expiration { + /// AtHeight will expire when `env.block.height` >= height + AtHeight { height: u64 }, + /// AtTime will expire when `env.block.time` >= time + AtTime { time: u64 }, } diff --git a/packages/cw20/src/query.rs b/packages/cw20/src/query.rs new file mode 100644 index 000000000..1033f414c --- /dev/null +++ b/packages/cw20/src/query.rs @@ -0,0 +1,54 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{HumanAddr, Uint128}; + +use crate::msg::Expiration; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum Cw20QueryMsg { + /// Returns the current balance of the given address, 0 if unset. + /// Return type: BalanceResponse. + Balance { address: HumanAddr }, + /// Returns metadata on the contract - name, decimals, supply, etc. + /// Return type: MetaResponse. + Meta {}, + /// Only with "allowance" extension. + /// Returns how much spender can use from owner account, 0 if unset. + /// Return type: AllowanceResponse. + Allowance { + owner: HumanAddr, + spender: HumanAddr, + }, + /// Only with "mintable" extension. + /// Returns who can mint and how much. + /// Return type: MinterResponse. + Minter {}, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema)] +pub struct BalanceResponse { + pub balance: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema)] +pub struct MetaResponse { + pub name: String, + pub symbol: String, + pub decimals: u8, + pub total_supply: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema)] +pub struct AllowanceResponse { + pub allowance: Uint128, + pub expires: Option, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema)] +pub struct MinterResponse { + pub minter: HumanAddr, + /// cap is how many more tokens can be issued by the minter + pub cap: Option, +} diff --git a/packages/cw20/src/receiver.rs b/packages/cw20/src/receiver.rs new file mode 100644 index 000000000..dfe6cfcd2 --- /dev/null +++ b/packages/cw20/src/receiver.rs @@ -0,0 +1,39 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{to_binary, Binary, CosmosMsg, HumanAddr, StdResult, Uint128, WasmMsg}; + +/// Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a HandleMsg +#[derive(Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct Cw20ReceiveMsg { + sender: HumanAddr, + amount: Uint128, + msg: Option, +} + +impl Cw20ReceiveMsg { + /// serializes the message + pub fn into_binary(self) -> StdResult { + let msg = ReceiverHandleMsg::Receive(self); + to_binary(&msg) + } + + /// creates a cosmos_msg sending this struct to the named contract + pub fn into_cosmos_msg(self, contract_addr: HumanAddr) -> StdResult { + let msg = self.into_binary()?; + let execute = WasmMsg::Execute { + contract_addr, + msg, + send: vec![], + }; + Ok(execute.into()) + } +} + +// This is just a helper to properly serialize the above message +#[derive(Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +enum ReceiverHandleMsg { + Receive(Cw20ReceiveMsg), +}