From 648ec1317d1f4f498853cf7881bbb37b34da0e4a Mon Sep 17 00:00:00 2001 From: Tomasz Kurcz Date: Wed, 23 Nov 2022 17:14:17 +0100 Subject: [PATCH] Standardize spec events --- packages/cw1/README.md | 41 +++--- packages/cw20/README.md | 312 ++++++++++++++++++++++++---------------- packages/cw3/README.md | 203 +++++++++++++------------- packages/cw4/README.md | 149 +++++++++---------- 4 files changed, 382 insertions(+), 323 deletions(-) diff --git a/packages/cw1/README.md b/packages/cw1/README.md index 72a95915a..77ed3c25a 100644 --- a/packages/cw1/README.md +++ b/packages/cw1/README.md @@ -1,33 +1,30 @@ # CW1 Spec: Proxy Contracts -CW1 is a specification for proxy contracts based on CosmWasm. -It is a very simple, but flexible interface designed for the case -where one contract is meant to hold assets (or rights) on behalf of -other contracts. +CW1 is a specification for proxy contracts based on CosmWasm. It is a very simple, but flexible interface designed for +the case where one contract is meant to hold assets (or rights) on behalf of other contracts. -The simplest example is a contract that will accept messages from -the creator and resend them from its address. Simply by making this -transferable, you can then begin to transfer non-transferable assets -(eg. staked tokens, voting power, etc). +The simplest example is a contract that will accept messages from the creator and resend them from its address. Simply +by making this transferable, you can then begin to transfer non-transferable assets (eg. staked tokens, voting power, +etc). -You can imagine more complex examples, such as a "1 of N" multisig, -or conditional approval, where "sub-accounts" have the right to spend -a limited amount of funds from this account, with a "admin account" -retaining full control. +You can imagine more complex examples, such as a "1 of N" multisig, or conditional approval, where "sub-accounts" have +the right to spend a limited amount of funds from this account, with a "admin account" retaining full control. -The common denominator is that they allow you to immediately -execute arbitrary `CosmosMsg` in the same transaction. +The common denominator is that they allow you to immediately execute arbitrary `CosmosMsg` in the same transaction. ### Messages -`Execute{msgs}` - This accepts `Vec` and checks permissions -before re-dispatching all those messages from the contract address. +`Execute{msgs}` - This accepts `Vec` and checks permissions before re-dispatching all those messages from the +contract address. It emits the following attributes: + +| Key | Value | +| -------- | ------------ | +| "action" | "execute" | +| "owner" | [msg sender] | ### Queries -`CanExecute{sender, msg}` - This accepts one `CosmosMsg` and checks permissions, -returning true or false based on the permissions. If `CanExecute` returns true -then a call to `Execute` from that sender, with the same message, -before any further state changes, should also succeed. This can be used -to dynamically provide some client info on a generic cw1 contract without -knowing the extension details. (eg. detect if they can send coins or stake) +`CanExecute{sender, msg}` - This accepts one `CosmosMsg` and checks permissions, returning true or false based on the +permissions. If `CanExecute` returns true then a call to `Execute` from that sender, with the same message, before any +further state changes, should also succeed. This can be used to dynamically provide some client info on a generic cw1 +contract without knowing the extension details. (eg. detect if they can send coins or stake) diff --git a/packages/cw20/README.md b/packages/cw20/README.md index c09cec75b..8eb118445 100644 --- a/packages/cw20/README.md +++ b/packages/cw20/README.md @@ -1,188 +1,256 @@ # CW20 Spec: Fungible Tokens -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. +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. +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 +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 -`info.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. +`Transfer{recipient, amount}` - Moves `amount` tokens from the `info.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 -`info.sender` account to the `contract` 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. +Attributes emitted: -`Burn{amount}` - Remove `amount` tokens from the balance of `info.sender` -and reduce `total_supply` by the same amount. +| Key | Value | +| -------- | ---------- | +| "action" | "transfer" | +| "from" | sender | +| "to" | recipient | +| "amount" | amount | + +`Send{contract, amount, msg}` - Moves `amount` tokens from the `info.sender` account to the `contract` 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. + +Attributes emitted: + +| Key | Value | +| -------- | --------- | +| "action" | "send" | +| "from" | sender | +| "to" | recipient | +| "amount" | amount | + +`Burn{amount}` - Remove `amount` tokens from the balance of `info.sender` and reduce `total_supply` by the same amount. + +Attributes emitted: + +| Key | Value | +| -------- | ------ | +| "action" | "burn" | +| "from" | sender | +| "amount" | 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}`. +`Balance{address}` - Returns the balance of the given address. Returns "0" if the address is unknown to the contract. +Return type is `BalanceResponse{balance}`. `TokenInfo{}` - Returns the token info of the contract. Return type is `TokenInfoResponse{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. +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 `info.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. +`Receive{sender, amount, msg}` - This is designed to handle `Send` messages. The address of the contract is stored in +`info.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. +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 +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. -There was an issue with race conditions in the original ERC20 approval spec. -If you had an approval of 50 and I then want to reduce it to 20, I submit a -Tx to set the allowance to 20. If you see that and immediately submit a tx -using the entire 50, you then get access to the other 20. Not only did you quickly -spend the 50 before I could reduce it, you get another 20 for free. +There was an issue with race conditions in the original ERC20 approval spec. If you had an approval of 50 and I then +want to reduce it to 20, I submit a Tx to set the allowance to 20. If you see that and immediately submit a tx using the +entire 50, you then get access to the other 20. Not only did you quickly spend the 50 before I could reduce it, you get +another 20 for free. -The solution discussed in the Ethereum community was an `IncreaseAllowance` -and `DecreaseAllowance` operator (instead of `Approve`). To originally set -an approval, use `IncreaseAllowance`, which works fine with no previous allowance. -`DecreaseAllowance` is meant to be robust, that is if you decrease by more than -the current allowance (eg. the user spent some in the middle), it will just round -down to 0 and not make any underflow error. +The solution discussed in the Ethereum community was an `IncreaseAllowance` and `DecreaseAllowance` operator (instead of +`Approve`). To originally set an approval, use `IncreaseAllowance`, which works fine with no previous allowance. +`DecreaseAllowance` is meant to be robust, that is if you decrease by more than the current allowance (eg. the user +spent some in the middle), it will just round down to 0 and not make any underflow error. ### Messages -`IncreaseAllowance{spender, amount, expires}` - Set or increase the allowance -such that `spender` may access up to `amount + current_allowance` tokens -from the `info.sender` account. This may optionally come with an `Expiration` -time, which if set limits when the approval can be used (by time or height). - -`DecreaseAllowance{spender, amount, expires}` - Decrease or clear the allowance -such that `spender` may access up to `current_allowance - amount` tokens -from the `info.sender` account. This may optionally come with an `Expiration` -time, which if set limits when the approval can be used (by time or height). -If `amount >= current_allowance`, this will clear the allowance (delete it). - -`TransferFrom{owner, recipient, amount}` - This makes use of an allowance -and if there was a valid, un-expired pre-approval for the `info.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. **Note** `SendFrom` will set the `Receive{sender}` -to be the `info.sender` (the account that triggered the transfer) -rather than the `owner` account (the account the money is coming from). -This is an open question whether we should switch this? - -`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. +`IncreaseAllowance{spender, amount, expires}` - Set or increase the allowance such that `spender` may access up to +`amount + current_allowance` tokens from the `info.sender` account. This may optionally come with an `Expiration` time, +which if set limits when the approval can be used (by time or height). + +Attributes emitted: + +| Key | Value | +| --------- | -------------------- | +| "action" | "increase_allowance" | +| "owner" | sender | +| "spender" | spender | +| "amount" | amount | + +`DecreaseAllowance{spender, amount, expires}` - Decrease or clear the allowance such that `spender` may access up to +`current_allowance - amount` tokens from the `info.sender` account. This may optionally come with an `Expiration` time, +which if set limits when the approval can be used (by time or height). If `amount >= current_allowance`, this will clear +the allowance (delete it). + +Attributes emitted: + +| Key | Value | +| --------- | -------------------- | +| "action" | "decrease_allowance" | +| "owner" | sender | +| "spender" | spender | +| "amount" | amount | + +`TransferFrom{owner, recipient, amount}` - This makes use of an allowance and if there was a valid, un-expired +pre-approval for the `info.sender`, then we move `amount` tokens from `owner` to `recipient` and deduct it from the +available allowance. + +Attributes emitted: + +| Key | Value | +| -------- | ------------------------ | +| "action" | "transfer_from" | +| "from" | account transferred from | +| "to" | recipient | +| "by" | message sender | +| "amount" | amount | + +`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. +**Note** `SendFrom` will set the `Receive{sender}` to be the `info.sender` (the account that triggered the transfer) +rather than the `owner` account (the account the money is coming from). This is an open question whether we should +switch this? + +Attributes emitted: + +| Key | Value | +| -------- | ----------------- | +| "action" | "send_from" | +| "from" | account sent from | +| "to" | recipient | +| "by" | message sender | +| "amount" | amount | + +`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. + +Attributes emitted: + +| Key | Value | +| -------- | ------------------ | +| "action" | "burn_from" | +| "from" | account burnt from | +| "by" | message sender | +| "amount" | amount | ### 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}`. +`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. +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 `info.sender` is the allowed minter, -this will create `amount` new tokens (updating total supply) and -add them to the balance of `recipient`, as long as it does not exceed the cap. +`Mint{recipient, amount}` - If the `info.sender` is the allowed minter, this will create `amount` new tokens (updating +total supply) and add them to the balance of `recipient`, as long as it does not exceed the cap. + +Attributes emitted: -`UpdateMinter { new_minter: Option }` - Callable only by the -current minter. If `new_minter` is `Some(address)` the minter is set -to the specified address, otherwise the minter is removed and no -future minters may be set. +| Key | Value | +| -------- | --------- | +| "action" | "mint" | +| "to" | recipient | +| "amount" | amount | + +`UpdateMinter { new_minter: Option }` - Callable only by the current minter. If `new_minter` is `Some(address)` +the minter is set to the specified address, otherwise the minter is removed and no future minters may be set. + +Attributes emitted: + +| Key | Value | +| ------------ | ----------------------------------- | +| "action" | "update_minter" | +| "new_minter" | minter address or "None" if removed | ### Queries -`Minter{}` - Returns who and how much can be minted. Return type is -`MinterResponse {minter, cap}`. Cap may be unset. +`Minter{}` - Returns who and how much can be minted. Return type is `MinterResponse {minter, cap}`. Cap may be unset. -If the cap is set, it defines the maximum `total_supply` that may ever exist. -If initial supply is 1000 and cap is `Some(2000)`, you can only mint 1000 more tokens. -However, if someone then burns 500 tokens, the minter can mint those 500 again. -This allows for dynamic token supply within a set of parameters, especially when -the minter is a smart contract. +If the cap is set, it defines the maximum `total_supply` that may ever exist. If initial supply is 1000 and cap is +`Some(2000)`, you can only mint 1000 more tokens. However, if someone then burns 500 tokens, the minter can mint those +500 again. This allows for dynamic token supply within a set of parameters, especially when the minter is a smart +contract. ## Enumerable -This should be enabled with all blockchains that have iterator support. -It allows us to get lists of results with pagination. +This should be enabled with all blockchains that have iterator support. It allows us to get lists of results with +pagination. ### Queries -`AllAllowances{owner, start_after, limit}` - Returns the list of all non-expired allowances -by the given owner. `start_after` and `limit` provide pagination. +`AllAllowances{owner, start_after, limit}` - Returns the list of all non-expired allowances by the given owner. +`start_after` and `limit` provide pagination. -`AllAccounts{start_after, limit}` - Returns the list of all accounts that have been created on -the contract (just the addresses). `start_after` and `limit` provide pagination. +`AllAccounts{start_after, limit}` - Returns the list of all accounts that have been created on the contract (just the +addresses). `start_after` and `limit` provide pagination. ## Marketing -This allows us to attach more metadata on the token to help with displaying the token in -wallets. When you see a token's website, then see it in a wallet, you know what it is. -However, if you see it in a wallet or a DEX trading pair, there is no clear way to find out -any more info about it. +This allows us to attach more metadata on the token to help with displaying the token in wallets. When you see a token's +website, then see it in a wallet, you know what it is. However, if you see it in a wallet or a DEX trading pair, there +is no clear way to find out any more info about it. -This extension allows us to attach more "Marketing" metadata, which has no effect on the -on-chain functionality of the token, but is very useful in providing a better client-side -experience. Note, that we add a new role `marketing`, which can update such info, but not -affect on-chain logic. +This extension allows us to attach more "Marketing" metadata, which has no effect on the on-chain functionality of the +token, but is very useful in providing a better client-side experience. Note, that we add a new role `marketing`, which +can update such info, but not affect on-chain logic. ### Messages -`UploadLogo{url | embedded}` - If the `info.sender` is the allowed marketing account, -this will either set a new URL reference where the logo is served, or allow them to upload -a small (less than 5KB) SVG or PNG logo onto the blockchain to be served. +`UploadLogo{url | embedded}` - If the `info.sender` is the allowed marketing account, this will either set a new URL +reference where the logo is served, or allow them to upload a small (less than 5KB) SVG or PNG logo onto the blockchain +to be served. + +Attributes emitted: + +| Key | Value | +| -------- | ------------- | +| "action" | "upload_logo" | + +`UpdateMarketing{project, description, marketing}` - If the `info.sender` is the allowed marketing account, this will +update some marketing-related metadata on the contract. + +Attributes emitted: -`UpdateMarketing{project, description, marketing}` - If the `info.sender` is the allowed marketing -account, this will update some marketing-related metadata on the contract. +| Key | Value | +| -------- | ------------------ | +| "action" | "update_marketing" | ### Queries `MarketingInfo{}` - Returns marketing-related metadata. Return type is `MarketingInfoResponse {project, description, logo, marketing}`. -`DownloadLogo{}` - If the token's logo was previously uploaded to the blockchain -(see `UploadLogo` message), then it returns the raw data to be displayed in a browser. -Return type is `DownloadLogoResponse{ mime_type, data }`. +`DownloadLogo{}` - If the token's logo was previously uploaded to the blockchain (see `UploadLogo` message), then it +returns the raw data to be displayed in a browser. Return type is `DownloadLogoResponse{ mime_type, data }`. diff --git a/packages/cw3/README.md b/packages/cw3/README.md index dccb35b8e..7f4cfe95b 100644 --- a/packages/cw3/README.md +++ b/packages/cw3/README.md @@ -1,131 +1,122 @@ # CW3 Spec: MultiSig/Voting Contracts -CW3 is a specification for voting contracts based on CosmWasm. -It is an extension of CW1 (which served as an immediate 1 of N multisig). -In this case, no key can immediately execute, but only propose -a set of messages for execution. The proposal, subsequent -approvals, and signature aggregation all happen on chain. +CW3 is a specification for voting contracts based on CosmWasm. It is an extension of CW1 (which served as an immediate 1 +of N multisig). In this case, no key can immediately execute, but only propose a set of messages for execution. The +proposal, subsequent approvals, and signature aggregation all happen on chain. There are at least 3 different cases we want to cover in this spec: -- K of N immutible multisig. One key proposes a set of messages, - after K-1 others approve it, it can be executed with the - multisig address. -- K of N flexible, mutable multisig. Like above, but with - multiple contracts. One contract stores the group, which is - referenced from multiple multisig contracts (which in turn - implement cw3). One cw3 contracts is able to update the - group content (maybe needing 67% vote). Other cw3 contracts - may hold tokens, staking rights, etc with various execution - thresholds, all controlled by one group. (Group interface - and updating them will be defined in a future spec, likely cw4). - -This should fit in this interface (possibly with some -extensions for pieces, but the usage should look the -same externally): - -- Token weighted voting. People lock tokens in a contract - for voting rights. There is a vote threshold to execute - messages. The voting set is dynamic. This has a similar - "propose, approve, execute" flow, but we will need to - support clear YES/NO votes and quora not just absolute - thresholds. - -The common denominator is that they allow you to propose -arbitrary `CosmosMsg` to a contract, and allow a series -of votes/approvals to determine if it can be executed, -as well as a final step to execute any approved proposal once. +- K of N immutible multisig. One key proposes a set of messages, after K-1 others approve it, it can be executed with + the multisig address. +- K of N flexible, mutable multisig. Like above, but with multiple contracts. One contract stores the group, which is + referenced from multiple multisig contracts (which in turn implement cw3). One cw3 contracts is able to update the + group content (maybe needing 67% vote). Other cw3 contracts may hold tokens, staking rights, etc with various + execution thresholds, all controlled by one group. (Group interface and updating them will be defined in a future + spec, likely cw4). + +This should fit in this interface (possibly with some extensions for pieces, but the usage should look the same +externally): + +- Token weighted voting. People lock tokens in a contract for voting rights. There is a vote threshold to execute + messages. The voting set is dynamic. This has a similar "propose, approve, execute" flow, but we will need to support + clear YES/NO votes and quora not just absolute thresholds. + +The common denominator is that they allow you to propose arbitrary `CosmosMsg` to a contract, and allow a series of +votes/approvals to determine if it can be executed, as well as a final step to execute any approved proposal once. ## Base -The following interfaces must be implemented for all cw3 -contracts. Note that updating the members of the voting -contract is not contained here (one approach is defined in cw4). -Also, how to change the threshold rules (if at all) is not -standardized. Those are considered admin tasks, and the common -API is designed for standard usage, as that is where we can -standardize the most tooling without limiting more complex -governance controls. +The following interfaces must be implemented for all cw3 contracts. Note that updating the members of the voting +contract is not contained here (one approach is defined in cw4). Also, how to change the threshold rules (if at all) is +not standardized. Those are considered admin tasks, and the common API is designed for standard usage, as that is where +we can standardize the most tooling without limiting more complex governance controls. ### Messages -`Propose{title, description, msgs, earliest, latest}` - This accepts -`Vec` and creates a new proposal. This will return -an auto-generated ID in the `Data` field (and the logs) that -can be used to reference the proposal later. - -If the Proposer is a valid voter on the proposal, this will imply a Yes vote by -the Proposer for a faster workflow, especially useful in eg. 2 of 3 -or 3 of 5 multisig, we don't need to propose in one block, get result, -and vote in another block. - -Earliest and latest are optional and can request the first -and last height/time that we can try `Execute`. For a vote, -we may require at least 2 days to pass, but no more than 7. -This is optional and even if set, may be modified by the contract -(overriding or just enforcing min/max/default values). - -Many implementations will want to restrict who can propose. -Maybe only people in the voting set. Maybe there is some -deposit to be made along with the proposal. This is not -in the spec but left open to the implementation. - -`Vote{proposal_id, vote}` - Given a proposal_id, you can -vote yes, no, abstain or veto. Each signed may have a -different "weight" in the voting and they apply their -entire weight on the vote. - -Many contracts (like typical -multisig with absolute threshold) may consider veto and -abstain as no and just count yes votes. Contracts with quora -may count abstain towards quora but not yes or no for threshold. -Some contracts may give extra power to veto rather than a -simple no, but this may just act like a normal no vote. - -`Execute{proposal_id}` - This will check if the voting -conditions have passed for the given proposal. If it has -succeeded, the proposal is marked as `Executed` and the -messages are dispatched. If the messages fail (eg out of gas), -this is all reverted and can be tried again later with -more gas. - -`Close{proposal_id}` - This will check if the voting conditions -have failed for the given proposal. If so (eg. time expired -and insufficient votes), then the proposal is marked `Failed`. -This is not strictly necessary, as it will only act when -it is impossible the contract would ever be executed, -but can be triggered to provide some better UI. +`Propose{title, description, msgs, earliest, latest}` - This accepts `Vec` and creates a new proposal. This +will return an auto-generated ID in the `Data` field (and the logs) that can be used to reference the proposal later. + +If the Proposer is a valid voter on the proposal, this will imply a Yes vote by the Proposer for a faster workflow, +especially useful in eg. 2 of 3 or 3 of 5 multisig, we don't need to propose in one block, get result, and vote in +another block. + +Earliest and latest are optional and can request the first and last height/time that we can try `Execute`. For a vote, +we may require at least 2 days to pass, but no more than 7. This is optional and even if set, may be modified by the +contract (overriding or just enforcing min/max/default values). + +Many implementations will want to restrict who can propose. Maybe only people in the voting set. Maybe there is some +deposit to be made along with the proposal. This is not in the spec but left open to the implementation. + +Attributes emitted: + +| Key | Value | +| ------------- | ---------------------- | +| "action" | "propose" | +| "sender" | msg sender | +| "proposal_id" | a UID for the proposal | +| "status" | new proposal status | + +`Vote{proposal_id, vote}` - Given a proposal_id, you can vote yes, no, abstain or veto. Each signed may have a different +"weight" in the voting and they apply their entire weight on the vote. + +Many contracts (like typical multisig with absolute threshold) may consider veto and abstain as no and just count yes +votes. Contracts with quora may count abstain towards quora but not yes or no for threshold. Some contracts may give +extra power to veto rather than a simple no, but this may just act like a normal no vote. + +Attributes emitted: + +| Key | Value | +| ------------- | ---------------------- | +| "action" | "vote" | +| "sender" | msg sender | +| "proposal_id" | a UID for the proposal | +| "status" | new proposal status | + +`Execute{proposal_id}` - This will check if the voting conditions have passed for the given proposal. If it has +succeeded, the proposal is marked as `Executed` and the messages are dispatched. If the messages fail (eg out of gas), +this is all reverted and can be tried again later with more gas. + +Attributes emitted: + +| Key | Value | +| ------------- | ---------------------- | +| "action" | "execute" | +| "sender" | msg sender | +| "proposal_id" | a UID for the proposal | + +`Close{proposal_id}` - This will check if the voting conditions have failed for the given proposal. If so (eg. time +expired and insufficient votes), then the proposal is marked `Failed`. This is not strictly necessary, as it will only +act when it is impossible the contract would ever be executed, but can be triggered to provide some better UI. + +Attributes emitted: + +| Key | Value | +| ------------- | ---------------------- | +| "action" | "close" | +| "sender" | msg sender | +| "proposal_id" | a UID for the proposal | ### Queries -`Threshold{}` - This returns information on the rules needed -to declare a contract a success. What percentage of the votes -and how they are tallied. +`Threshold{}` - This returns information on the rules needed to declare a contract a success. What percentage of the +votes and how they are tallied. -`Proposal{proposal_id}` - Returns the information set when -creating the proposal, along with the current status. +`Proposal{proposal_id}` - Returns the information set when creating the proposal, along with the current status. -`ListProposals{start_after, limit}` - Returns the same info -as `Proposal`, but for all proposals along with pagination. -Starts at proposal_id 1 and accending. +`ListProposals{start_after, limit}` - Returns the same info as `Proposal`, but for all proposals along with pagination. +Starts at proposal_id 1 and accending. -`ReverseProposals{start_before, limit}` - Returns the same info -as `Proposal`, but for all proposals along with pagination. -Starts at latest proposal_id and descending. (Often this -is what you will want for a UI) +`ReverseProposals{start_before, limit}` - Returns the same info as `Proposal`, but for all proposals along with +pagination. Starts at latest proposal_id and descending. (Often this is what you will want for a UI) -`Vote{proposal_id, voter}` - Returns how the given -voter (HumanAddr) voted on the proposal. (May be null) +`Vote{proposal_id, voter}` - Returns how the given voter (HumanAddr) voted on the proposal. (May be null) -`ListVotes{proposal_id, start_after, limit}` - Returns the same info -as `Vote`, but for all votes along with pagination. -Returns the voters sorted by the voters' address in -lexographically ascending order. +`ListVotes{proposal_id, start_after, limit}` - Returns the same info as `Vote`, but for all votes along with pagination. +Returns the voters sorted by the voters' address in lexographically ascending order. ## Voter Info -Information on who can vote is contract dependent. But -we will work on a common API to display some of this. +Information on who can vote is contract dependent. But we will work on a common API to display some of this. `Voter { address }` - returns voting power (weight) of this address, if any diff --git a/packages/cw4/README.md b/packages/cw4/README.md index d2203088d..7494d7fc1 100644 --- a/packages/cw4/README.md +++ b/packages/cw4/README.md @@ -1,91 +1,98 @@ # CW4 Spec: Group Members -CW4 is a spec for storing group membership, which can be combined -with CW3 multisigs. The purpose is to store a set of members/voters -that can be accessed to determine permissions in another section. - -Since this is often deployed as a contract pair, we expect this -contract to often be queried with `QueryRaw` and the internal -layout of some of the data structures becomes part of the public API. -Implementations may add more data structures, but at least -the ones laid out here should be under the specified keys and in the -same format. - -In this case, a cw3 contract could *read* an external group contract with -no significant cost besides reading local storage. However, updating -that group contract (if allowed), would be an external message and -will be charged as part of the overhead for each contract. +CW4 is a spec for storing group membership, which can be combined with CW3 multisigs. The purpose is to store a set of +members/voters that can be accessed to determine permissions in another section. + +Since this is often deployed as a contract pair, we expect this contract to often be queried with `QueryRaw` and the +internal layout of some of the data structures becomes part of the public API. Implementations may add more data +structures, but at least the ones laid out here should be under the specified keys and in the same format. + +In this case, a cw3 contract could _read_ an external group contract with no significant cost besides reading local +storage. However, updating that group contract (if allowed), would be an external message and will be charged as part of +the overhead for each contract. ## Messages -We define an `InstantiateMsg{admin, members}` to make it easy to set up a group -as part of another flow. Implementations should work with this setup, -but may add extra `Option` fields for non-essential extensions to -configure in the `instantiate` phase. +We define an `InstantiateMsg{admin, members}` to make it easy to set up a group as part of another flow. Implementations +should work with this setup, but may add extra `Option` fields for non-essential extensions to configure in the +`instantiate` phase. There are three messages supported by a group contract: `UpdateAdmin{admin}` - changes (or clears) the admin for the contract -`AddHook{addr}` - adds a contract address to be called upon every - `UpdateMembers` call. This can only be called by the admin, and care must - be taken. A contract returning an error or running out of gas will - revert the membership change (see more in Hooks section below). +Attributes emitted: + +| Key | Value | +| --------- | ------------------------ | +| "action" | "update_members" | +| "sender" | msg sender | +| "added" | count of added members | +| "removed" | count of removed members | + +`AddHook{addr}` - adds a contract address to be called upon every `UpdateMembers` call. This can only be called by the +admin, and care must be taken. A contract returning an error or running out of gas will revert the membership change +(see more in Hooks section below). + +Attributes emitted: + +| Key | Value | +| -------- | ------------ | +| "action" | "add_hook" | +| "sender" | msg sender | +| "hook" | hook address | + +`RemoveHook{addr}` - unregister a contract address that was previously set by `AddHook`. -`RemoveHook{addr}` - unregister a contract address that was previously set - by `AddHook`. +Attributes emitted: -Only the `admin` may execute any of these function. Thus, by omitting an -`admin`, we end up with a similar functionality ad `cw3-fixed-multisig`. -If we include one, it may often be desired to be a `cw3` contract that -uses this group contract as a group. This leads to a bit of chicken-and-egg -problem, but we cover how to instantiate that in +| Key | Value | +| -------- | ------------- | +| "action" | "remove_hook" | +| "sender" | msg sender | +| "hook" | hook address | + +Only the `admin` may execute any of these function. Thus, by omitting an `admin`, we end up with a similar functionality +ad `cw3-fixed-multisig`. If we include one, it may often be desired to be a `cw3` contract that uses this group contract +as a group. This leads to a bit of chicken-and-egg problem, but we cover how to instantiate that in [`cw3-flex-multisig`](../../contracts/cw3-flex-multisig/README.md#instantiation). ## Queries ### Smart -`TotalWeight{}` - Returns the total weight of all current members, - this is very useful if some conditions are defined on a "percentage of members". - -`Member{addr, height}` - Returns the weight of this voter if they are a member of the - group (may be 0), or `None` if they are not a member of the group. - If height is set and the cw4 implementation supports snapshots, - this will return the weight of that member at - the beginning of the block with the given height. - -`MemberList{start_after, limit}` - Allows us to paginate over the list - of all members. 0-weight members will be included. Removed members will not. +`TotalWeight{}` - Returns the total weight of all current members, this is very useful if some conditions are defined on +a "percentage of members". + +`Member{addr, height}` - Returns the weight of this voter if they are a member of the group (may be 0), or `None` if +they are not a member of the group. If height is set and the cw4 implementation supports snapshots, this will return the +weight of that member at the beginning of the block with the given height. + +`MemberList{start_after, limit}` - Allows us to paginate over the list of all members. 0-weight members will be +included. Removed members will not. `Admin{}` - Returns the `admin` address, or `None` if unset. ### Raw -In addition to the above "SmartQueries", which make up the public API, -we define two raw queries that are designed for more efficiency -in contract-contract calls. These use keys exported by `cw4` - -`TOTAL_KEY` - making a raw query with this key (`b"total"`) will return a - JSON-encoded `u64` - -`members_key()` - takes a `CanonicalAddr` and returns a key that can be - used for raw query (`"\x00\x07members" || addr`). This will return - empty bytes if the member is not inside the group, otherwise a - JSON-encoded `u64` - +In addition to the above "SmartQueries", which make up the public API, we define two raw queries that are designed for +more efficiency in contract-contract calls. These use keys exported by `cw4` + +`TOTAL_KEY` - making a raw query with this key (`b"total"`) will return a JSON-encoded `u64` + +`members_key()` - takes a `CanonicalAddr` and returns a key that can be used for raw query +(`"\x00\x07members" || addr`). This will return empty bytes if the member is not inside the group, otherwise a +JSON-encoded `u64` + ## Hooks -One special feature of the `cw4` contracts is they allow the admin to -register multiple hooks. These are special contracts that need to react -to changes in the group membership, and this allows them stay in sync. -Again, note this is a powerful ability and you should only set hooks -to contracts you fully trust, generally some contracts you deployed +One special feature of the `cw4` contracts is they allow the admin to register multiple hooks. These are special +contracts that need to react to changes in the group membership, and this allows them stay in sync. Again, note this is +a powerful ability and you should only set hooks to contracts you fully trust, generally some contracts you deployed alongside the group. -If a contract is registered as a hook on a cw4 contract, then anytime -`UpdateMembers` is successfully executed, the hook will receive a `handle` -call with the following format: +If a contract is registered as a hook on a cw4 contract, then anytime `UpdateMembers` is successfully executed, the hook +will receive a `handle` call with the following format: ```json { @@ -101,17 +108,13 @@ call with the following format: } ``` -See [hook.rs](./src/hook.rs) for full details. Note that this example -shows an update or an existing member. `old_weight` will -be missing if the address was added for the first time. And -`new_weight` will be missing if the address was removed. +See [hook.rs](./src/hook.rs) for full details. Note that this example shows an update or an existing member. +`old_weight` will be missing if the address was added for the first time. And `new_weight` will be missing if the +address was removed. -The receiving contract must be able to handle the `MemberChangedHookMsg` -and should only return an error if it wants to change the functionality -of the group contract (eg. a multisig that wants to prevent membership -changes while there is an open proposal). However, such cases are quite -rare and often point to fragile code. +The receiving contract must be able to handle the `MemberChangedHookMsg` and should only return an error if it wants to +change the functionality of the group contract (eg. a multisig that wants to prevent membership changes while there is +an open proposal). However, such cases are quite rare and often point to fragile code. -Note that the message sender will be the group contract that was updated. -Make sure you check this when handling, so external actors cannot -call this hook, only the trusted group. +Note that the message sender will be the group contract that was updated. Make sure you check this when handling, so +external actors cannot call this hook, only the trusted group.