From 631af8863b735da5d33029b66005fb2e618853e5 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Wed, 24 Apr 2024 15:37:33 +0400 Subject: [PATCH 01/32] feat: bump cosmwasm-std to 1.5.4 --- Cargo.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index db92af5f..455b43ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -258,9 +258,9 @@ dependencies = [ [[package]] name = "bnum" -version = "0.8.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab9008b6bb9fc80b5277f2fe481c09e828743d9151203e804583eb4c9e15b31d" +checksum = "56953345e39537a3e18bdaeba4cb0c58a78c1f61f361dc0fa7c5c7340ae87c5f" [[package]] name = "builder-unlock" @@ -312,9 +312,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "cosmwasm-crypto" -version = "1.5.2" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6aa9f904de106fa16443ad14ec2abe75e94ba003bb61c681c0e43d4c58d2a" +checksum = "e6b4c3f9c4616d6413d4b5fc4c270a4cc32a374b9be08671e80e1a019f805d8f" dependencies = [ "digest 0.10.7", "ecdsa", @@ -326,9 +326,9 @@ dependencies = [ [[package]] name = "cosmwasm-derive" -version = "1.5.2" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40abec852f3d4abec6d44ead9a58b78325021a1ead1e7229c3471414e57b2e49" +checksum = "c586ced10c3b00e809ee664a895025a024f60d65d34fe4c09daed4a4db68a3f3" dependencies = [ "syn 1.0.109", ] @@ -359,9 +359,9 @@ dependencies = [ [[package]] name = "cosmwasm-std" -version = "1.5.2" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad011ae7447188e26e4a7dbca2fcd0fc186aa21ae5c86df0503ea44c78f9e469" +checksum = "712fe58f39d55c812f7b2c84e097cdede3a39d520f89b6dc3153837e31741927" dependencies = [ "base64", "bech32", @@ -855,9 +855,9 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "k256" -version = "0.13.3" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" dependencies = [ "cfg-if", "ecdsa", @@ -1110,9 +1110,9 @@ dependencies = [ [[package]] name = "serde-json-wasm" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a62a1fad1e1828b24acac8f2b468971dade7b8c3c2e672bcadefefb1f8c137" +checksum = "9e9213a07d53faa0b8dd81e767a54a8188a242fdb9be99ab75ec576a774bfdd7" dependencies = [ "serde", ] From 73f7f7e682ff79fd13d76d64c504fc28dc867685 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Thu, 25 Apr 2024 16:16:29 +0400 Subject: [PATCH 02/32] feat: add schema generator --- .github/workflows/tests_and_checks.yml | 6 + ...ock_schema.rs => builder_unlock_schema.rs} | 0 schemas/astro-assembly/astro-assembly.json | 3386 +++++++++++++++++ schemas/astro-assembly/raw/execute.json | 1053 +++++ schemas/astro-assembly/raw/instantiate.json | 110 + schemas/astro-assembly/raw/query.json | 196 + .../raw/response_to_config.json | 110 + .../raw/response_to_proposal.json | 894 +++++ .../raw/response_to_proposal_voters.json | 40 + .../raw/response_to_proposal_votes.json | 42 + .../raw/response_to_proposals.json | 920 +++++ .../raw/response_to_total_voting_power.json | 6 + .../raw/response_to_user_voting_power.json | 6 + schemas/builder-unlock/builder-unlock.json | 955 +++++ schemas/builder-unlock/raw/execute.json | 385 ++ schemas/builder-unlock/raw/instantiate.json | 35 + schemas/builder-unlock/raw/query.json | 161 + .../raw/response_to_allocation.json | 148 + .../raw/response_to_allocations.json | 97 + .../raw/response_to_config.json | 44 + .../raw/response_to_simulate_withdraw.json | 26 + .../builder-unlock/raw/response_to_state.json | 44 + .../raw/response_to_unlocked_tokens.json | 6 + scripts/build_schemas.sh | 25 + 24 files changed, 8695 insertions(+) rename contracts/builder_unlock/examples/{unlock_schema.rs => builder_unlock_schema.rs} (100%) create mode 100644 schemas/astro-assembly/astro-assembly.json create mode 100644 schemas/astro-assembly/raw/execute.json create mode 100644 schemas/astro-assembly/raw/instantiate.json create mode 100644 schemas/astro-assembly/raw/query.json create mode 100644 schemas/astro-assembly/raw/response_to_config.json create mode 100644 schemas/astro-assembly/raw/response_to_proposal.json create mode 100644 schemas/astro-assembly/raw/response_to_proposal_voters.json create mode 100644 schemas/astro-assembly/raw/response_to_proposal_votes.json create mode 100644 schemas/astro-assembly/raw/response_to_proposals.json create mode 100644 schemas/astro-assembly/raw/response_to_total_voting_power.json create mode 100644 schemas/astro-assembly/raw/response_to_user_voting_power.json create mode 100644 schemas/builder-unlock/builder-unlock.json create mode 100644 schemas/builder-unlock/raw/execute.json create mode 100644 schemas/builder-unlock/raw/instantiate.json create mode 100644 schemas/builder-unlock/raw/query.json create mode 100644 schemas/builder-unlock/raw/response_to_allocation.json create mode 100644 schemas/builder-unlock/raw/response_to_allocations.json create mode 100644 schemas/builder-unlock/raw/response_to_config.json create mode 100644 schemas/builder-unlock/raw/response_to_simulate_withdraw.json create mode 100644 schemas/builder-unlock/raw/response_to_state.json create mode 100644 schemas/builder-unlock/raw/response_to_unlocked_tokens.json create mode 100755 scripts/build_schemas.sh diff --git a/.github/workflows/tests_and_checks.yml b/.github/workflows/tests_and_checks.yml index 3eedb028..87fdc90d 100644 --- a/.github/workflows/tests_and_checks.yml +++ b/.github/workflows/tests_and_checks.yml @@ -73,3 +73,9 @@ jobs: command: fmt args: --all -- --check + - name: Generate and check schemas + run: | + $GITHUB_WORKSPACE/scripts/build_schemas.sh + git add -A $GITHUB_WORKSPACE/schemas # consider new contract schemas + git diff-index --cached HEAD --exit-code -- + diff --git a/contracts/builder_unlock/examples/unlock_schema.rs b/contracts/builder_unlock/examples/builder_unlock_schema.rs similarity index 100% rename from contracts/builder_unlock/examples/unlock_schema.rs rename to contracts/builder_unlock/examples/builder_unlock_schema.rs diff --git a/schemas/astro-assembly/astro-assembly.json b/schemas/astro-assembly/astro-assembly.json new file mode 100644 index 00000000..2c519640 --- /dev/null +++ b/schemas/astro-assembly/astro-assembly.json @@ -0,0 +1,3386 @@ +{ + "contract_name": "astro-assembly", + "contract_version": "2.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "description": "This structure holds the parameters used for creating an Assembly contract.", + "type": "object", + "required": [ + "builder_unlock_addr", + "proposal_effective_delay", + "proposal_expiration_period", + "proposal_required_deposit", + "proposal_required_quorum", + "proposal_required_threshold", + "proposal_voting_period", + "staking_addr", + "whitelisted_links" + ], + "properties": { + "builder_unlock_addr": { + "description": "Address of the builder unlock contract", + "type": "string" + }, + "generator_controller_addr": { + "description": "Generator controller contract capable of immediate proposals", + "type": [ + "string", + "null" + ] + }, + "hub_addr": { + "description": "Hub contract that handles voting from Outposts", + "type": [ + "string", + "null" + ] + }, + "ibc_controller": { + "description": "Astroport IBC controller contract", + "type": [ + "string", + "null" + ] + }, + "proposal_effective_delay": { + "description": "Proposal effective delay", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "proposal_expiration_period": { + "description": "Proposal expiration period", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "proposal_required_deposit": { + "description": "Proposal required deposit", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "proposal_required_quorum": { + "description": "Proposal required quorum", + "type": "string" + }, + "proposal_required_threshold": { + "description": "Proposal required threshold", + "type": "string" + }, + "proposal_voting_period": { + "description": "Proposal voting period", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "staking_addr": { + "description": "Astroport xASTRO staking address. xASTRO denom and tracker contract address are queried on assembly instantiation.", + "type": "string" + }, + "voting_escrow_delegator_addr": { + "description": "Voting Escrow delegator address", + "type": [ + "string", + "null" + ] + }, + "vxastro_token_addr": { + "description": "Address of vxASTRO token", + "type": [ + "string", + "null" + ] + }, + "whitelisted_links": { + "description": "Whitelisted links", + "type": "array", + "items": { + "type": "string" + } + } + }, + "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" + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "description": "This enum describes all execute functions available in the contract.", + "oneOf": [ + { + "description": "Submit a new governance proposal", + "type": "object", + "required": [ + "submit_proposal" + ], + "properties": { + "submit_proposal": { + "type": "object", + "required": [ + "description", + "title" + ], + "properties": { + "description": { + "type": "string" + }, + "ibc_channel": { + "description": "If proposal should be executed on a remote chain this field should specify governance channel", + "type": [ + "string", + "null" + ] + }, + "link": { + "type": [ + "string", + "null" + ] + }, + "messages": { + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_Empty" + } + }, + "title": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Cast a vote for an active proposal", + "type": "object", + "required": [ + "cast_vote" + ], + "properties": { + "cast_vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "description": "Proposal identifier", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "description": "Vote option", + "allOf": [ + { + "$ref": "#/definitions/ProposalVoteOption" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Set the status of a proposal that expired", + "type": "object", + "required": [ + "end_proposal" + ], + "properties": { + "end_proposal": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "proposal_id": { + "description": "Proposal identifier", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Checks that proposal messages are correct.", + "type": "object", + "required": [ + "check_messages" + ], + "properties": { + "check_messages": { + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_Empty" + } + } + }, + "additionalProperties": false + }, + { + "description": "The last endpoint which is executed only if all proposal messages have been passed", + "type": "object", + "required": [ + "check_messages_passed" + ], + "properties": { + "check_messages_passed": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Execute a successful proposal", + "type": "object", + "required": [ + "execute_proposal" + ], + "properties": { + "execute_proposal": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "proposal_id": { + "description": "Proposal identifier", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Update parameters in the Assembly contract ## Executor Only the Assembly contract is allowed to update its own parameters", + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "$ref": "#/definitions/UpdateConfig" + } + }, + "additionalProperties": false + }, + { + "description": "Update proposal status InProgress -> Executed or Failed. ## Executor Only the IBC controller contract is allowed to call this method.", + "type": "object", + "required": [ + "i_b_c_proposal_completed" + ], + "properties": { + "i_b_c_proposal_completed": { + "type": "object", + "required": [ + "proposal_id", + "status" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "status": { + "$ref": "#/definitions/ProposalStatus" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "execute_from_multisig" + ], + "properties": { + "execute_from_multisig": { + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_Empty" + } + } + }, + "additionalProperties": false + } + ], + "definitions": { + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CosmosMsg_for_Empty": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "additionalProperties": false + } + ] + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "GovMsg": { + "description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```", + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See .", + "allOf": [ + { + "$ref": "#/definitions/VoteOption" + } + ] + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "existing channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "ProposalStatus": { + "description": "This enum describes available statuses/states for a Proposal.", + "type": "string", + "enum": [ + "active", + "passed", + "rejected", + "in_progress", + "failed", + "executed", + "expired" + ] + }, + "ProposalVoteOption": { + "description": "This enum describes available options for voting on a proposal.", + "type": "string", + "enum": [ + "for", + "against" + ] + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "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" + }, + "UpdateConfig": { + "description": "This structure stores the params used when updating the main Assembly contract params.", + "type": "object", + "properties": { + "builder_unlock_addr": { + "description": "Builder unlock contract address", + "type": [ + "string", + "null" + ] + }, + "ibc_controller": { + "description": "Astroport IBC controller contract", + "type": [ + "string", + "null" + ] + }, + "proposal_effective_delay": { + "description": "Proposal effective delay", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "proposal_expiration_period": { + "description": "Proposal expiration period", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "proposal_required_deposit": { + "description": "Proposal required deposit", + "type": [ + "integer", + "null" + ], + "format": "uint128", + "minimum": 0.0 + }, + "proposal_required_quorum": { + "description": "Proposal required quorum", + "type": [ + "string", + "null" + ] + }, + "proposal_required_threshold": { + "description": "Proposal required threshold", + "type": [ + "string", + "null" + ] + }, + "proposal_voting_period": { + "description": "Proposal voting period", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "whitelist_add": { + "description": "Links to add to whitelist", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "whitelist_remove": { + "description": "Links to remove from whitelist", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThe contract address is non-predictable. But it is guaranteed that when emitting the same Instantiate message multiple times, multiple instances on different addresses will be generated. See also Instantiate2.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "description": "Thie enum describes all the queries available in the contract.", + "oneOf": [ + { + "description": "Return the contract's configuration", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return the current list of proposals", + "type": "object", + "required": [ + "proposals" + ], + "properties": { + "proposals": { + "type": "object", + "properties": { + "limit": { + "description": "The amount of proposals to return", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start": { + "description": "Id from which to start querying", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return proposal voters of specified proposal", + "type": "object", + "required": [ + "proposal_voters" + ], + "properties": { + "proposal_voters": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "limit": { + "description": "The amount of proposals to return", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "proposal_id": { + "description": "Proposal unique id", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "start_after": { + "description": "Address after which to query", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return information about a specific proposal", + "type": "object", + "required": [ + "proposal" + ], + "properties": { + "proposal": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return information about the votes cast on a specific proposal", + "type": "object", + "required": [ + "proposal_votes" + ], + "properties": { + "proposal_votes": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return user voting power for a specific proposal", + "type": "object", + "required": [ + "user_voting_power" + ], + "properties": { + "user_voting_power": { + "type": "object", + "required": [ + "proposal_id", + "user" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return total voting power for a specific proposal", + "type": "object", + "required": [ + "total_voting_power" + ], + "properties": { + "total_voting_power": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": null, + "sudo": null, + "responses": { + "config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config", + "description": "This structure stores general parameters for the Assembly contract.", + "type": "object", + "required": [ + "builder_unlock_addr", + "proposal_effective_delay", + "proposal_expiration_period", + "proposal_required_deposit", + "proposal_required_quorum", + "proposal_required_threshold", + "proposal_voting_period", + "whitelisted_links", + "xastro_denom", + "xastro_denom_tracking" + ], + "properties": { + "builder_unlock_addr": { + "description": "Builder unlock contract address", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "ibc_controller": { + "description": "Astroport IBC controller contract", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "proposal_effective_delay": { + "description": "Proposal effective delay", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "proposal_expiration_period": { + "description": "Proposal expiration period", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "proposal_required_deposit": { + "description": "Proposal required deposit", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "proposal_required_quorum": { + "description": "Proposal required quorum", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "proposal_required_threshold": { + "description": "Proposal required threshold", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "proposal_voting_period": { + "description": "Proposal voting period", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "whitelisted_links": { + "description": "Whitelisted links", + "type": "array", + "items": { + "type": "string" + } + }, + "xastro_denom": { + "description": "xASTRO token denom", + "type": "string" + }, + "xastro_denom_tracking": { + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "proposal": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Proposal", + "description": "This structure stores data for a proposal.", + "type": "object", + "required": [ + "against_power", + "delayed_end_block", + "deposit_amount", + "description", + "end_block", + "expiration_block", + "for_power", + "messages", + "outpost_against_power", + "outpost_for_power", + "proposal_id", + "start_block", + "start_time", + "status", + "submitter", + "title", + "total_voting_power" + ], + "properties": { + "against_power": { + "description": "`Against` power of proposal", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "delayed_end_block": { + "description": "Delayed end block of proposal", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "deposit_amount": { + "description": "Amount of xASTRO deposited in order to post the proposal", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "description": { + "description": "Proposal description", + "type": "string" + }, + "end_block": { + "description": "End block of proposal", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "expiration_block": { + "description": "Expiration block of proposal", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "for_power": { + "description": "`For` power of proposal", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "ibc_channel": { + "description": "IBC channel", + "type": [ + "string", + "null" + ] + }, + "link": { + "description": "Proposal link", + "type": [ + "string", + "null" + ] + }, + "messages": { + "description": "Proposal messages", + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_Empty" + } + }, + "outpost_against_power": { + "description": "`Against` power of proposal cast from all Outposts", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "outpost_for_power": { + "description": "`For` power of proposal cast from all Outposts", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "proposal_id": { + "description": "Unique proposal ID", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "start_block": { + "description": "Start block of proposal", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "start_time": { + "description": "Start time of proposal", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "status": { + "description": "Status of the proposal", + "allOf": [ + { + "$ref": "#/definitions/ProposalStatus" + } + ] + }, + "submitter": { + "description": "The address of the proposal submitter", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "title": { + "description": "Proposal title", + "type": "string" + }, + "total_voting_power": { + "description": "Total voting power 1 second before the proposal was created", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CosmosMsg_for_Empty": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "additionalProperties": false + } + ] + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "GovMsg": { + "description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```", + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See .", + "allOf": [ + { + "$ref": "#/definitions/VoteOption" + } + ] + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "existing channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "ProposalStatus": { + "description": "This enum describes available statuses/states for a Proposal.", + "type": "string", + "enum": [ + "active", + "passed", + "rejected", + "in_progress", + "failed", + "executed", + "expired" + ] + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "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" + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThe contract address is non-predictable. But it is guaranteed that when emitting the same Instantiate message multiple times, multiple instances on different addresses will be generated. See also Instantiate2.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + } + } + }, + "proposal_voters": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_ProposalVoterResponse", + "type": "array", + "items": { + "$ref": "#/definitions/ProposalVoterResponse" + }, + "definitions": { + "ProposalVoteOption": { + "description": "This enum describes available options for voting on a proposal.", + "type": "string", + "enum": [ + "for", + "against" + ] + }, + "ProposalVoterResponse": { + "type": "object", + "required": [ + "address", + "vote_option" + ], + "properties": { + "address": { + "description": "The address of the voter", + "type": "string" + }, + "vote_option": { + "description": "The option address voted with", + "allOf": [ + { + "$ref": "#/definitions/ProposalVoteOption" + } + ] + } + }, + "additionalProperties": false + } + } + }, + "proposal_votes": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ProposalVotesResponse", + "description": "This structure describes a proposal vote response.", + "type": "object", + "required": [ + "against_power", + "for_power", + "proposal_id" + ], + "properties": { + "against_power": { + "description": "Total amount of `against` votes for a proposal.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "for_power": { + "description": "Total amount of `for` votes for a proposal", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "proposal_id": { + "description": "Proposal identifier", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "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" + } + } + }, + "proposals": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ProposalListResponse", + "description": "This structure describes a proposal list response.", + "type": "object", + "required": [ + "proposal_count", + "proposal_list" + ], + "properties": { + "proposal_count": { + "description": "The amount of proposals returned", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "proposal_list": { + "description": "The list of proposals that are returned", + "type": "array", + "items": { + "$ref": "#/definitions/Proposal" + } + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CosmosMsg_for_Empty": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "additionalProperties": false + } + ] + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "GovMsg": { + "description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```", + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See .", + "allOf": [ + { + "$ref": "#/definitions/VoteOption" + } + ] + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "existing channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "Proposal": { + "description": "This structure stores data for a proposal.", + "type": "object", + "required": [ + "against_power", + "delayed_end_block", + "deposit_amount", + "description", + "end_block", + "expiration_block", + "for_power", + "messages", + "outpost_against_power", + "outpost_for_power", + "proposal_id", + "start_block", + "start_time", + "status", + "submitter", + "title", + "total_voting_power" + ], + "properties": { + "against_power": { + "description": "`Against` power of proposal", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "delayed_end_block": { + "description": "Delayed end block of proposal", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "deposit_amount": { + "description": "Amount of xASTRO deposited in order to post the proposal", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "description": { + "description": "Proposal description", + "type": "string" + }, + "end_block": { + "description": "End block of proposal", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "expiration_block": { + "description": "Expiration block of proposal", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "for_power": { + "description": "`For` power of proposal", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "ibc_channel": { + "description": "IBC channel", + "type": [ + "string", + "null" + ] + }, + "link": { + "description": "Proposal link", + "type": [ + "string", + "null" + ] + }, + "messages": { + "description": "Proposal messages", + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_Empty" + } + }, + "outpost_against_power": { + "description": "`Against` power of proposal cast from all Outposts", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "outpost_for_power": { + "description": "`For` power of proposal cast from all Outposts", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "proposal_id": { + "description": "Unique proposal ID", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "start_block": { + "description": "Start block of proposal", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "start_time": { + "description": "Start time of proposal", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "status": { + "description": "Status of the proposal", + "allOf": [ + { + "$ref": "#/definitions/ProposalStatus" + } + ] + }, + "submitter": { + "description": "The address of the proposal submitter", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "title": { + "description": "Proposal title", + "type": "string" + }, + "total_voting_power": { + "description": "Total voting power 1 second before the proposal was created", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + }, + "ProposalStatus": { + "description": "This enum describes available statuses/states for a Proposal.", + "type": "string", + "enum": [ + "active", + "passed", + "rejected", + "in_progress", + "failed", + "executed", + "expired" + ] + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "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" + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThe contract address is non-predictable. But it is guaranteed that when emitting the same Instantiate message multiple times, multiple instances on different addresses will be generated. See also Instantiate2.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + } + } + }, + "total_voting_power": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "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" + }, + "user_voting_power": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "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/schemas/astro-assembly/raw/execute.json b/schemas/astro-assembly/raw/execute.json new file mode 100644 index 00000000..d81a1dda --- /dev/null +++ b/schemas/astro-assembly/raw/execute.json @@ -0,0 +1,1053 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "description": "This enum describes all execute functions available in the contract.", + "oneOf": [ + { + "description": "Submit a new governance proposal", + "type": "object", + "required": [ + "submit_proposal" + ], + "properties": { + "submit_proposal": { + "type": "object", + "required": [ + "description", + "title" + ], + "properties": { + "description": { + "type": "string" + }, + "ibc_channel": { + "description": "If proposal should be executed on a remote chain this field should specify governance channel", + "type": [ + "string", + "null" + ] + }, + "link": { + "type": [ + "string", + "null" + ] + }, + "messages": { + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_Empty" + } + }, + "title": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Cast a vote for an active proposal", + "type": "object", + "required": [ + "cast_vote" + ], + "properties": { + "cast_vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "description": "Proposal identifier", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "description": "Vote option", + "allOf": [ + { + "$ref": "#/definitions/ProposalVoteOption" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Set the status of a proposal that expired", + "type": "object", + "required": [ + "end_proposal" + ], + "properties": { + "end_proposal": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "proposal_id": { + "description": "Proposal identifier", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Checks that proposal messages are correct.", + "type": "object", + "required": [ + "check_messages" + ], + "properties": { + "check_messages": { + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_Empty" + } + } + }, + "additionalProperties": false + }, + { + "description": "The last endpoint which is executed only if all proposal messages have been passed", + "type": "object", + "required": [ + "check_messages_passed" + ], + "properties": { + "check_messages_passed": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Execute a successful proposal", + "type": "object", + "required": [ + "execute_proposal" + ], + "properties": { + "execute_proposal": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "proposal_id": { + "description": "Proposal identifier", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Update parameters in the Assembly contract ## Executor Only the Assembly contract is allowed to update its own parameters", + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "$ref": "#/definitions/UpdateConfig" + } + }, + "additionalProperties": false + }, + { + "description": "Update proposal status InProgress -> Executed or Failed. ## Executor Only the IBC controller contract is allowed to call this method.", + "type": "object", + "required": [ + "i_b_c_proposal_completed" + ], + "properties": { + "i_b_c_proposal_completed": { + "type": "object", + "required": [ + "proposal_id", + "status" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "status": { + "$ref": "#/definitions/ProposalStatus" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "execute_from_multisig" + ], + "properties": { + "execute_from_multisig": { + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_Empty" + } + } + }, + "additionalProperties": false + } + ], + "definitions": { + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CosmosMsg_for_Empty": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "additionalProperties": false + } + ] + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "GovMsg": { + "description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```", + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See .", + "allOf": [ + { + "$ref": "#/definitions/VoteOption" + } + ] + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "existing channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "ProposalStatus": { + "description": "This enum describes available statuses/states for a Proposal.", + "type": "string", + "enum": [ + "active", + "passed", + "rejected", + "in_progress", + "failed", + "executed", + "expired" + ] + }, + "ProposalVoteOption": { + "description": "This enum describes available options for voting on a proposal.", + "type": "string", + "enum": [ + "for", + "against" + ] + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "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" + }, + "UpdateConfig": { + "description": "This structure stores the params used when updating the main Assembly contract params.", + "type": "object", + "properties": { + "builder_unlock_addr": { + "description": "Builder unlock contract address", + "type": [ + "string", + "null" + ] + }, + "ibc_controller": { + "description": "Astroport IBC controller contract", + "type": [ + "string", + "null" + ] + }, + "proposal_effective_delay": { + "description": "Proposal effective delay", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "proposal_expiration_period": { + "description": "Proposal expiration period", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "proposal_required_deposit": { + "description": "Proposal required deposit", + "type": [ + "integer", + "null" + ], + "format": "uint128", + "minimum": 0.0 + }, + "proposal_required_quorum": { + "description": "Proposal required quorum", + "type": [ + "string", + "null" + ] + }, + "proposal_required_threshold": { + "description": "Proposal required threshold", + "type": [ + "string", + "null" + ] + }, + "proposal_voting_period": { + "description": "Proposal voting period", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "whitelist_add": { + "description": "Links to add to whitelist", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "whitelist_remove": { + "description": "Links to remove from whitelist", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThe contract address is non-predictable. But it is guaranteed that when emitting the same Instantiate message multiple times, multiple instances on different addresses will be generated. See also Instantiate2.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/schemas/astro-assembly/raw/instantiate.json b/schemas/astro-assembly/raw/instantiate.json new file mode 100644 index 00000000..a92e80e1 --- /dev/null +++ b/schemas/astro-assembly/raw/instantiate.json @@ -0,0 +1,110 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "description": "This structure holds the parameters used for creating an Assembly contract.", + "type": "object", + "required": [ + "builder_unlock_addr", + "proposal_effective_delay", + "proposal_expiration_period", + "proposal_required_deposit", + "proposal_required_quorum", + "proposal_required_threshold", + "proposal_voting_period", + "staking_addr", + "whitelisted_links" + ], + "properties": { + "builder_unlock_addr": { + "description": "Address of the builder unlock contract", + "type": "string" + }, + "generator_controller_addr": { + "description": "Generator controller contract capable of immediate proposals", + "type": [ + "string", + "null" + ] + }, + "hub_addr": { + "description": "Hub contract that handles voting from Outposts", + "type": [ + "string", + "null" + ] + }, + "ibc_controller": { + "description": "Astroport IBC controller contract", + "type": [ + "string", + "null" + ] + }, + "proposal_effective_delay": { + "description": "Proposal effective delay", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "proposal_expiration_period": { + "description": "Proposal expiration period", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "proposal_required_deposit": { + "description": "Proposal required deposit", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "proposal_required_quorum": { + "description": "Proposal required quorum", + "type": "string" + }, + "proposal_required_threshold": { + "description": "Proposal required threshold", + "type": "string" + }, + "proposal_voting_period": { + "description": "Proposal voting period", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "staking_addr": { + "description": "Astroport xASTRO staking address. xASTRO denom and tracker contract address are queried on assembly instantiation.", + "type": "string" + }, + "voting_escrow_delegator_addr": { + "description": "Voting Escrow delegator address", + "type": [ + "string", + "null" + ] + }, + "vxastro_token_addr": { + "description": "Address of vxASTRO token", + "type": [ + "string", + "null" + ] + }, + "whitelisted_links": { + "description": "Whitelisted links", + "type": "array", + "items": { + "type": "string" + } + } + }, + "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/schemas/astro-assembly/raw/query.json b/schemas/astro-assembly/raw/query.json new file mode 100644 index 00000000..645ab719 --- /dev/null +++ b/schemas/astro-assembly/raw/query.json @@ -0,0 +1,196 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "description": "Thie enum describes all the queries available in the contract.", + "oneOf": [ + { + "description": "Return the contract's configuration", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return the current list of proposals", + "type": "object", + "required": [ + "proposals" + ], + "properties": { + "proposals": { + "type": "object", + "properties": { + "limit": { + "description": "The amount of proposals to return", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start": { + "description": "Id from which to start querying", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return proposal voters of specified proposal", + "type": "object", + "required": [ + "proposal_voters" + ], + "properties": { + "proposal_voters": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "limit": { + "description": "The amount of proposals to return", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "proposal_id": { + "description": "Proposal unique id", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "start_after": { + "description": "Address after which to query", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return information about a specific proposal", + "type": "object", + "required": [ + "proposal" + ], + "properties": { + "proposal": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return information about the votes cast on a specific proposal", + "type": "object", + "required": [ + "proposal_votes" + ], + "properties": { + "proposal_votes": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return user voting power for a specific proposal", + "type": "object", + "required": [ + "user_voting_power" + ], + "properties": { + "user_voting_power": { + "type": "object", + "required": [ + "proposal_id", + "user" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return total voting power for a specific proposal", + "type": "object", + "required": [ + "total_voting_power" + ], + "properties": { + "total_voting_power": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/schemas/astro-assembly/raw/response_to_config.json b/schemas/astro-assembly/raw/response_to_config.json new file mode 100644 index 00000000..75d1d491 --- /dev/null +++ b/schemas/astro-assembly/raw/response_to_config.json @@ -0,0 +1,110 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config", + "description": "This structure stores general parameters for the Assembly contract.", + "type": "object", + "required": [ + "builder_unlock_addr", + "proposal_effective_delay", + "proposal_expiration_period", + "proposal_required_deposit", + "proposal_required_quorum", + "proposal_required_threshold", + "proposal_voting_period", + "whitelisted_links", + "xastro_denom", + "xastro_denom_tracking" + ], + "properties": { + "builder_unlock_addr": { + "description": "Builder unlock contract address", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "ibc_controller": { + "description": "Astroport IBC controller contract", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "proposal_effective_delay": { + "description": "Proposal effective delay", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "proposal_expiration_period": { + "description": "Proposal expiration period", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "proposal_required_deposit": { + "description": "Proposal required deposit", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "proposal_required_quorum": { + "description": "Proposal required quorum", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "proposal_required_threshold": { + "description": "Proposal required threshold", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "proposal_voting_period": { + "description": "Proposal voting period", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "whitelisted_links": { + "description": "Whitelisted links", + "type": "array", + "items": { + "type": "string" + } + }, + "xastro_denom": { + "description": "xASTRO token denom", + "type": "string" + }, + "xastro_denom_tracking": { + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/schemas/astro-assembly/raw/response_to_proposal.json b/schemas/astro-assembly/raw/response_to_proposal.json new file mode 100644 index 00000000..21be9c2c --- /dev/null +++ b/schemas/astro-assembly/raw/response_to_proposal.json @@ -0,0 +1,894 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Proposal", + "description": "This structure stores data for a proposal.", + "type": "object", + "required": [ + "against_power", + "delayed_end_block", + "deposit_amount", + "description", + "end_block", + "expiration_block", + "for_power", + "messages", + "outpost_against_power", + "outpost_for_power", + "proposal_id", + "start_block", + "start_time", + "status", + "submitter", + "title", + "total_voting_power" + ], + "properties": { + "against_power": { + "description": "`Against` power of proposal", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "delayed_end_block": { + "description": "Delayed end block of proposal", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "deposit_amount": { + "description": "Amount of xASTRO deposited in order to post the proposal", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "description": { + "description": "Proposal description", + "type": "string" + }, + "end_block": { + "description": "End block of proposal", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "expiration_block": { + "description": "Expiration block of proposal", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "for_power": { + "description": "`For` power of proposal", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "ibc_channel": { + "description": "IBC channel", + "type": [ + "string", + "null" + ] + }, + "link": { + "description": "Proposal link", + "type": [ + "string", + "null" + ] + }, + "messages": { + "description": "Proposal messages", + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_Empty" + } + }, + "outpost_against_power": { + "description": "`Against` power of proposal cast from all Outposts", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "outpost_for_power": { + "description": "`For` power of proposal cast from all Outposts", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "proposal_id": { + "description": "Unique proposal ID", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "start_block": { + "description": "Start block of proposal", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "start_time": { + "description": "Start time of proposal", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "status": { + "description": "Status of the proposal", + "allOf": [ + { + "$ref": "#/definitions/ProposalStatus" + } + ] + }, + "submitter": { + "description": "The address of the proposal submitter", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "title": { + "description": "Proposal title", + "type": "string" + }, + "total_voting_power": { + "description": "Total voting power 1 second before the proposal was created", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CosmosMsg_for_Empty": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "additionalProperties": false + } + ] + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "GovMsg": { + "description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```", + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See .", + "allOf": [ + { + "$ref": "#/definitions/VoteOption" + } + ] + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "existing channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "ProposalStatus": { + "description": "This enum describes available statuses/states for a Proposal.", + "type": "string", + "enum": [ + "active", + "passed", + "rejected", + "in_progress", + "failed", + "executed", + "expired" + ] + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "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" + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThe contract address is non-predictable. But it is guaranteed that when emitting the same Instantiate message multiple times, multiple instances on different addresses will be generated. See also Instantiate2.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/schemas/astro-assembly/raw/response_to_proposal_voters.json b/schemas/astro-assembly/raw/response_to_proposal_voters.json new file mode 100644 index 00000000..4145f8bb --- /dev/null +++ b/schemas/astro-assembly/raw/response_to_proposal_voters.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_ProposalVoterResponse", + "type": "array", + "items": { + "$ref": "#/definitions/ProposalVoterResponse" + }, + "definitions": { + "ProposalVoteOption": { + "description": "This enum describes available options for voting on a proposal.", + "type": "string", + "enum": [ + "for", + "against" + ] + }, + "ProposalVoterResponse": { + "type": "object", + "required": [ + "address", + "vote_option" + ], + "properties": { + "address": { + "description": "The address of the voter", + "type": "string" + }, + "vote_option": { + "description": "The option address voted with", + "allOf": [ + { + "$ref": "#/definitions/ProposalVoteOption" + } + ] + } + }, + "additionalProperties": false + } + } +} diff --git a/schemas/astro-assembly/raw/response_to_proposal_votes.json b/schemas/astro-assembly/raw/response_to_proposal_votes.json new file mode 100644 index 00000000..e6b6834f --- /dev/null +++ b/schemas/astro-assembly/raw/response_to_proposal_votes.json @@ -0,0 +1,42 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ProposalVotesResponse", + "description": "This structure describes a proposal vote response.", + "type": "object", + "required": [ + "against_power", + "for_power", + "proposal_id" + ], + "properties": { + "against_power": { + "description": "Total amount of `against` votes for a proposal.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "for_power": { + "description": "Total amount of `for` votes for a proposal", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "proposal_id": { + "description": "Proposal identifier", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "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/schemas/astro-assembly/raw/response_to_proposals.json b/schemas/astro-assembly/raw/response_to_proposals.json new file mode 100644 index 00000000..32e2b6f6 --- /dev/null +++ b/schemas/astro-assembly/raw/response_to_proposals.json @@ -0,0 +1,920 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ProposalListResponse", + "description": "This structure describes a proposal list response.", + "type": "object", + "required": [ + "proposal_count", + "proposal_list" + ], + "properties": { + "proposal_count": { + "description": "The amount of proposals returned", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "proposal_list": { + "description": "The list of proposals that are returned", + "type": "array", + "items": { + "$ref": "#/definitions/Proposal" + } + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "BankMsg": { + "description": "The message types of the bank module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto", + "oneOf": [ + { + "description": "Sends native tokens from the contract to the given address.\n\nThis is translated to a [MsgSend](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/bank/v1beta1/tx.proto#L19-L28). `from_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "send" + ], + "properties": { + "send": { + "type": "object", + "required": [ + "amount", + "to_address" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "to_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will burn the given coins from the contract's account. There is no Cosmos SDK message that performs this, but it can be done by calling the bank keeper. Important if a contract controls significant token supply that must be retired.", + "type": "object", + "required": [ + "burn" + ], + "properties": { + "burn": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Binary": { + "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "CosmosMsg_for_Empty": { + "oneOf": [ + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/definitions/BankMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/Empty" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "staking" + ], + "properties": { + "staking": { + "$ref": "#/definitions/StakingMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "distribution" + ], + "properties": { + "distribution": { + "$ref": "#/definitions/DistributionMsg" + } + }, + "additionalProperties": false + }, + { + "description": "A Stargate message encoded the same way as a protobuf [Any](https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto). This is the same structure as messages in `TxBody` from [ADR-020](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-020-protobuf-transaction-encoding.md)", + "type": "object", + "required": [ + "stargate" + ], + "properties": { + "stargate": { + "type": "object", + "required": [ + "type_url", + "value" + ], + "properties": { + "type_url": { + "type": "string" + }, + "value": { + "$ref": "#/definitions/Binary" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "ibc" + ], + "properties": { + "ibc": { + "$ref": "#/definitions/IbcMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "wasm" + ], + "properties": { + "wasm": { + "$ref": "#/definitions/WasmMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "gov" + ], + "properties": { + "gov": { + "$ref": "#/definitions/GovMsg" + } + }, + "additionalProperties": false + } + ] + }, + "DistributionMsg": { + "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgSetWithdrawAddress](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L29-L37). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "set_withdraw_address" + ], + "properties": { + "set_withdraw_address": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "description": "The `withdraw_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [[MsgWithdrawDelegatorReward](https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto#L42-L50). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "withdraw_delegator_reward" + ], + "properties": { + "withdraw_delegator_reward": { + "type": "object", + "required": [ + "validator" + ], + "properties": { + "validator": { + "description": "The `validator_address`", + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Empty": { + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" + }, + "GovMsg": { + "description": "This message type allows the contract interact with the [x/gov] module in order to cast votes.\n\n[x/gov]: https://github.com/cosmos/cosmos-sdk/tree/v0.45.12/x/gov\n\n## Examples\n\nCast a simple vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); use cosmwasm_std::{GovMsg, VoteOption};\n\n#[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::Vote { proposal_id: 4, vote: VoteOption::Yes, })) } ```\n\nCast a weighted vote:\n\n``` # use cosmwasm_std::{ # HexBinary, # Storage, Api, Querier, DepsMut, Deps, entry_point, Env, StdError, MessageInfo, # Response, QueryResponse, # }; # type ExecuteMsg = (); # #[cfg(feature = \"cosmwasm_1_2\")] use cosmwasm_std::{Decimal, GovMsg, VoteOption, WeightedVoteOption};\n\n# #[cfg(feature = \"cosmwasm_1_2\")] #[entry_point] pub fn execute( deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { // ... Ok(Response::new().add_message(GovMsg::VoteWeighted { proposal_id: 4, options: vec![ WeightedVoteOption { option: VoteOption::Yes, weight: Decimal::percent(65), }, WeightedVoteOption { option: VoteOption::Abstain, weight: Decimal::percent(35), }, ], })) } ```", + "oneOf": [ + { + "description": "This maps directly to [MsgVote](https://github.com/cosmos/cosmos-sdk/blob/v0.42.5/proto/cosmos/gov/v1beta1/tx.proto#L46-L56) in the Cosmos SDK with voter set to the contract address.", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "description": "The vote option.\n\nThis should be called \"option\" for consistency with Cosmos SDK. Sorry for that. See .", + "allOf": [ + { + "$ref": "#/definitions/VoteOption" + } + ] + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcMsg": { + "description": "These are messages in the IBC lifecycle. Only usable by IBC-enabled contracts (contracts that directly speak the IBC protocol via 6 entry points)", + "oneOf": [ + { + "description": "Sends bank tokens owned by the contract to the given address on another chain. The channel must already be established between the ibctransfer module on this chain and a matching module on the remote chain. We cannot select the port_id, this is whatever the local chain has bound the ibctransfer module to.", + "type": "object", + "required": [ + "transfer" + ], + "properties": { + "transfer": { + "type": "object", + "required": [ + "amount", + "channel_id", + "timeout", + "to_address" + ], + "properties": { + "amount": { + "description": "packet data only supports one coin https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + }, + "channel_id": { + "description": "existing channel to send the tokens over", + "type": "string" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + }, + "to_address": { + "description": "address on the remote chain to receive these tokens", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sends an IBC packet with given data over the existing channel. Data should be encoded in a format defined by the channel version, and the module on the other side should know how to parse this.", + "type": "object", + "required": [ + "send_packet" + ], + "properties": { + "send_packet": { + "type": "object", + "required": [ + "channel_id", + "data", + "timeout" + ], + "properties": { + "channel_id": { + "type": "string" + }, + "data": { + "$ref": "#/definitions/Binary" + }, + "timeout": { + "description": "when packet times out, measured on remote chain", + "allOf": [ + { + "$ref": "#/definitions/IbcTimeout" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This will close an existing channel that is owned by this contract. Port is auto-assigned to the contract's IBC port", + "type": "object", + "required": [ + "close_channel" + ], + "properties": { + "close_channel": { + "type": "object", + "required": [ + "channel_id" + ], + "properties": { + "channel_id": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "IbcTimeout": { + "description": "In IBC each package must set at least one type of timeout: the timestamp or the block height. Using this rather complex enum instead of two timeout fields we ensure that at least one timeout is set.", + "type": "object", + "properties": { + "block": { + "anyOf": [ + { + "$ref": "#/definitions/IbcTimeoutBlock" + }, + { + "type": "null" + } + ] + }, + "timestamp": { + "anyOf": [ + { + "$ref": "#/definitions/Timestamp" + }, + { + "type": "null" + } + ] + } + } + }, + "IbcTimeoutBlock": { + "description": "IBCTimeoutHeight Height is a monotonically increasing data type that can be compared against another Height for the purposes of updating and freezing clients. Ordering is (revision_number, timeout_height)", + "type": "object", + "required": [ + "height", + "revision" + ], + "properties": { + "height": { + "description": "block height after which the packet times out. the height within the given revision", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "revision": { + "description": "the version that the client is currently on (e.g. after resetting the chain this could increment 1 as height drops to 0)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "Proposal": { + "description": "This structure stores data for a proposal.", + "type": "object", + "required": [ + "against_power", + "delayed_end_block", + "deposit_amount", + "description", + "end_block", + "expiration_block", + "for_power", + "messages", + "outpost_against_power", + "outpost_for_power", + "proposal_id", + "start_block", + "start_time", + "status", + "submitter", + "title", + "total_voting_power" + ], + "properties": { + "against_power": { + "description": "`Against` power of proposal", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "delayed_end_block": { + "description": "Delayed end block of proposal", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "deposit_amount": { + "description": "Amount of xASTRO deposited in order to post the proposal", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "description": { + "description": "Proposal description", + "type": "string" + }, + "end_block": { + "description": "End block of proposal", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "expiration_block": { + "description": "Expiration block of proposal", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "for_power": { + "description": "`For` power of proposal", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "ibc_channel": { + "description": "IBC channel", + "type": [ + "string", + "null" + ] + }, + "link": { + "description": "Proposal link", + "type": [ + "string", + "null" + ] + }, + "messages": { + "description": "Proposal messages", + "type": "array", + "items": { + "$ref": "#/definitions/CosmosMsg_for_Empty" + } + }, + "outpost_against_power": { + "description": "`Against` power of proposal cast from all Outposts", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "outpost_for_power": { + "description": "`For` power of proposal cast from all Outposts", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "proposal_id": { + "description": "Unique proposal ID", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "start_block": { + "description": "Start block of proposal", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "start_time": { + "description": "Start time of proposal", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "status": { + "description": "Status of the proposal", + "allOf": [ + { + "$ref": "#/definitions/ProposalStatus" + } + ] + }, + "submitter": { + "description": "The address of the proposal submitter", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "title": { + "description": "Proposal title", + "type": "string" + }, + "total_voting_power": { + "description": "Total voting power 1 second before the proposal was created", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + }, + "ProposalStatus": { + "description": "This enum describes available statuses/states for a Proposal.", + "type": "string", + "enum": [ + "active", + "passed", + "rejected", + "in_progress", + "failed", + "executed", + "expired" + ] + }, + "StakingMsg": { + "description": "The message types of the staking module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto", + "oneOf": [ + { + "description": "This is translated to a [MsgDelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "delegate" + ], + "properties": { + "delegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgUndelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "undelegate" + ], + "properties": { + "undelegate": { + "type": "object", + "required": [ + "amount", + "validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "This is translated to a [MsgBeginRedelegate](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105). `delegator_address` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "redelegate" + ], + "properties": { + "redelegate": { + "type": "object", + "required": [ + "amount", + "dst_validator", + "src_validator" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Coin" + }, + "dst_validator": { + "type": "string" + }, + "src_validator": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Timestamp": { + "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```", + "allOf": [ + { + "$ref": "#/definitions/Uint64" + } + ] + }, + "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" + }, + "VoteOption": { + "type": "string", + "enum": [ + "yes", + "no", + "abstain", + "no_with_veto" + ] + }, + "WasmMsg": { + "description": "The message types of the wasm module.\n\nSee https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto", + "oneOf": [ + { + "description": "Dispatches a call to another contract at a known address (with known ABI).\n\nThis is translated to a [MsgExecuteContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L68-L78). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "execute" + ], + "properties": { + "execute": { + "type": "object", + "required": [ + "contract_addr", + "funds", + "msg" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "msg": { + "description": "msg is the json-encoded ExecuteMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Instantiates a new contracts from previously uploaded Wasm code.\n\nThe contract address is non-predictable. But it is guaranteed that when emitting the same Instantiate message multiple times, multiple instances on different addresses will be generated. See also Instantiate2.\n\nThis is translated to a [MsgInstantiateContract](https://github.com/CosmWasm/wasmd/blob/v0.29.2/proto/cosmwasm/wasm/v1/tx.proto#L53-L71). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "instantiate" + ], + "properties": { + "instantiate": { + "type": "object", + "required": [ + "code_id", + "funds", + "label", + "msg" + ], + "properties": { + "admin": { + "type": [ + "string", + "null" + ] + }, + "code_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "funds": { + "type": "array", + "items": { + "$ref": "#/definitions/Coin" + } + }, + "label": { + "description": "A human-readable label for the contract.\n\nValid values should: - not be empty - not be bigger than 128 bytes (or some chain-specific limit) - not start / end with whitespace", + "type": "string" + }, + "msg": { + "description": "msg is the JSON-encoded InstantiateMsg struct (as raw Binary)", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Migrates a given contracts to use new wasm code. Passes a MigrateMsg to allow us to customize behavior.\n\nOnly the contract admin (as defined in wasmd), if any, is able to make this call.\n\nThis is translated to a [MsgMigrateContract](https://github.com/CosmWasm/wasmd/blob/v0.14.0/x/wasm/internal/types/tx.proto#L86-L96). `sender` is automatically filled with the current contract's address.", + "type": "object", + "required": [ + "migrate" + ], + "properties": { + "migrate": { + "type": "object", + "required": [ + "contract_addr", + "msg", + "new_code_id" + ], + "properties": { + "contract_addr": { + "type": "string" + }, + "msg": { + "description": "msg is the json-encoded MigrateMsg struct that will be passed to the new code", + "allOf": [ + { + "$ref": "#/definitions/Binary" + } + ] + }, + "new_code_id": { + "description": "the code_id of the new logic to place in the given contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Sets a new admin (for migrate) on the given contract. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "update_admin" + ], + "properties": { + "update_admin": { + "type": "object", + "required": [ + "admin", + "contract_addr" + ], + "properties": { + "admin": { + "type": "string" + }, + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Clears the admin on the given contract, so no more migration possible. Fails if this contract is not currently admin of the target contract.", + "type": "object", + "required": [ + "clear_admin" + ], + "properties": { + "clear_admin": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "type": "string" + } + } + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/schemas/astro-assembly/raw/response_to_total_voting_power.json b/schemas/astro-assembly/raw/response_to_total_voting_power.json new file mode 100644 index 00000000..25b73e8f --- /dev/null +++ b/schemas/astro-assembly/raw/response_to_total_voting_power.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "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/schemas/astro-assembly/raw/response_to_user_voting_power.json b/schemas/astro-assembly/raw/response_to_user_voting_power.json new file mode 100644 index 00000000..25b73e8f --- /dev/null +++ b/schemas/astro-assembly/raw/response_to_user_voting_power.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "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/schemas/builder-unlock/builder-unlock.json b/schemas/builder-unlock/builder-unlock.json new file mode 100644 index 00000000..97dd320a --- /dev/null +++ b/schemas/builder-unlock/builder-unlock.json @@ -0,0 +1,955 @@ +{ + "contract_name": "builder-unlock", + "contract_version": "3.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "astro_denom", + "max_allocations_amount", + "owner" + ], + "properties": { + "astro_denom": { + "description": "ASTRO token denom", + "type": "string" + }, + "max_allocations_amount": { + "description": "Max ASTRO tokens to allocate", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "owner": { + "description": "Account that can create new allocations", + "type": "string" + } + }, + "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" + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "description": "This enum describes all the execute functions available in the contract.", + "oneOf": [ + { + "description": "CreateAllocations creates new ASTRO allocations", + "type": "object", + "required": [ + "create_allocations" + ], + "properties": { + "create_allocations": { + "type": "object", + "required": [ + "allocations" + ], + "properties": { + "allocations": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/CreateAllocationParams" + } + ], + "maxItems": 2, + "minItems": 2 + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Withdraw claims withdrawable ASTRO", + "type": "object", + "required": [ + "withdraw" + ], + "properties": { + "withdraw": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "ProposeNewReceiver allows a user to change the receiver address for their ASTRO allocation", + "type": "object", + "required": [ + "propose_new_receiver" + ], + "properties": { + "propose_new_receiver": { + "type": "object", + "required": [ + "new_receiver" + ], + "properties": { + "new_receiver": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "DropNewReceiver allows a user to remove the previously proposed new receiver for their ASTRO allocation", + "type": "object", + "required": [ + "drop_new_receiver" + ], + "properties": { + "drop_new_receiver": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "ClaimReceiver allows newly proposed receivers to claim ASTRO allocations ownership", + "type": "object", + "required": [ + "claim_receiver" + ], + "properties": { + "claim_receiver": { + "type": "object", + "required": [ + "prev_receiver" + ], + "properties": { + "prev_receiver": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Increase the ASTRO allocation of a receiver", + "type": "object", + "required": [ + "increase_allocation" + ], + "properties": { + "increase_allocation": { + "type": "object", + "required": [ + "amount", + "receiver" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "receiver": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Decrease the ASTRO allocation of a receiver", + "type": "object", + "required": [ + "decrease_allocation" + ], + "properties": { + "decrease_allocation": { + "type": "object", + "required": [ + "amount", + "receiver" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "receiver": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Transfer unallocated tokens (only accessible to the owner)", + "type": "object", + "required": [ + "transfer_unallocated" + ], + "properties": { + "transfer_unallocated": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "recipient": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Propose a new owner for the contract", + "type": "object", + "required": [ + "propose_new_owner" + ], + "properties": { + "propose_new_owner": { + "type": "object", + "required": [ + "expires_in", + "new_owner" + ], + "properties": { + "expires_in": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "new_owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Remove the ownership transfer proposal", + "type": "object", + "required": [ + "drop_ownership_proposal" + ], + "properties": { + "drop_ownership_proposal": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Claim contract ownership", + "type": "object", + "required": [ + "claim_ownership" + ], + "properties": { + "claim_ownership": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Update parameters in the contract configuration", + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "required": [ + "new_max_allocations_amount" + ], + "properties": { + "new_max_allocations_amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Update a schedule of allocation for specified accounts", + "type": "object", + "required": [ + "update_unlock_schedules" + ], + "properties": { + "update_unlock_schedules": { + "type": "object", + "required": [ + "new_unlock_schedules" + ], + "properties": { + "new_unlock_schedules": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/Schedule" + } + ], + "maxItems": 2, + "minItems": 2 + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "CreateAllocationParams": { + "description": "This structure stores the parameters used to describe an ASTRO allocation.", + "type": "object", + "required": [ + "amount", + "unlock_schedule" + ], + "properties": { + "amount": { + "description": "Total amount of ASTRO tokens allocated to a specific account", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "unlock_schedule": { + "description": "Parameters controlling the unlocking process", + "allOf": [ + { + "$ref": "#/definitions/Schedule" + } + ] + } + }, + "additionalProperties": false + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Schedule": { + "description": "This structure stores the parameters describing a typical unlock schedule.", + "type": "object", + "required": [ + "cliff", + "duration", + "start_time" + ], + "properties": { + "cliff": { + "description": "Cliff period during which no tokens can be withdrawn out of the contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "duration": { + "description": "Time after the cliff during which the remaining tokens linearly unlock", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "percent_at_cliff": { + "description": "Percentage of tokens unlocked at the cliff", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "start_time": { + "description": "Timestamp for the start of the unlock schedule (in seconds)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "description": "This enum describes all the queries available in the contract.", + "oneOf": [ + { + "description": "Config returns the configuration for this contract", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "State returns the state of this contract", + "type": "object", + "required": [ + "state" + ], + "properties": { + "state": { + "type": "object", + "properties": { + "timestamp": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Allocation returns the parameters and current status of an allocation", + "type": "object", + "required": [ + "allocation" + ], + "properties": { + "allocation": { + "type": "object", + "required": [ + "account" + ], + "properties": { + "account": { + "description": "Account whose allocation status we query", + "type": "string" + }, + "timestamp": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Allocations returns a vector that contains builder unlock allocations by specified parameters", + "type": "object", + "required": [ + "allocations" + ], + "properties": { + "allocations": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "UnlockedTokens returns the unlocked tokens from an allocation", + "type": "object", + "required": [ + "unlocked_tokens" + ], + "properties": { + "unlocked_tokens": { + "type": "object", + "required": [ + "account" + ], + "properties": { + "account": { + "description": "Account whose amount of unlocked ASTRO we query for", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "SimulateWithdraw simulates how many ASTRO will be released if a withdrawal is attempted", + "type": "object", + "required": [ + "simulate_withdraw" + ], + "properties": { + "simulate_withdraw": { + "type": "object", + "required": [ + "account" + ], + "properties": { + "account": { + "description": "Account for which we simulate a withdrawal", + "type": "string" + }, + "timestamp": { + "description": "Timestamp used to simulate how much ASTRO the account can withdraw", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": null, + "sudo": null, + "responses": { + "allocation": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AllocationResponse", + "description": "This structure stores the parameters used to return the response when querying for an allocation data.", + "type": "object", + "required": [ + "params", + "status" + ], + "properties": { + "params": { + "description": "The allocation parameters", + "allOf": [ + { + "$ref": "#/definitions/AllocationParams" + } + ] + }, + "status": { + "description": "The allocation status", + "allOf": [ + { + "$ref": "#/definitions/AllocationStatus" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "AllocationParams": { + "type": "object", + "required": [ + "unlock_schedule" + ], + "properties": { + "proposed_receiver": { + "description": "Proposed new receiver who will get the ASTRO allocation", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "unlock_schedule": { + "description": "Parameters controlling the unlocking process", + "allOf": [ + { + "$ref": "#/definitions/Schedule" + } + ] + } + }, + "additionalProperties": false + }, + "AllocationStatus": { + "description": "This structure stores the parameters used to describe the status of an allocation.", + "type": "object", + "required": [ + "amount", + "astro_withdrawn", + "unlocked_amount_checkpoint" + ], + "properties": { + "amount": { + "description": "Total amount of ASTRO tokens allocated to a specific account", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "astro_withdrawn": { + "description": "Amount of ASTRO already withdrawn", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "unlocked_amount_checkpoint": { + "description": "Already unlocked amount after decreasing", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Schedule": { + "description": "This structure stores the parameters describing a typical unlock schedule.", + "type": "object", + "required": [ + "cliff", + "duration", + "start_time" + ], + "properties": { + "cliff": { + "description": "Cliff period during which no tokens can be withdrawn out of the contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "duration": { + "description": "Time after the cliff during which the remaining tokens linearly unlock", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "percent_at_cliff": { + "description": "Percentage of tokens unlocked at the cliff", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "start_time": { + "description": "Timestamp for the start of the unlock schedule (in seconds)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "allocations": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Tuple_of_String_and_AllocationParams", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/AllocationParams" + } + ], + "maxItems": 2, + "minItems": 2 + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "AllocationParams": { + "type": "object", + "required": [ + "unlock_schedule" + ], + "properties": { + "proposed_receiver": { + "description": "Proposed new receiver who will get the ASTRO allocation", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "unlock_schedule": { + "description": "Parameters controlling the unlocking process", + "allOf": [ + { + "$ref": "#/definitions/Schedule" + } + ] + } + }, + "additionalProperties": false + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Schedule": { + "description": "This structure stores the parameters describing a typical unlock schedule.", + "type": "object", + "required": [ + "cliff", + "duration", + "start_time" + ], + "properties": { + "cliff": { + "description": "Cliff period during which no tokens can be withdrawn out of the contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "duration": { + "description": "Time after the cliff during which the remaining tokens linearly unlock", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "percent_at_cliff": { + "description": "Percentage of tokens unlocked at the cliff", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "start_time": { + "description": "Timestamp for the start of the unlock schedule (in seconds)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config", + "description": "This structure stores general parameters for the builder unlock contract.", + "type": "object", + "required": [ + "astro_denom", + "max_allocations_amount", + "owner" + ], + "properties": { + "astro_denom": { + "description": "ASTRO token denom", + "type": "string" + }, + "max_allocations_amount": { + "description": "Max ASTRO tokens to allocate", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "owner": { + "description": "Account that can create new unlock schedules", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "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" + } + } + }, + "simulate_withdraw": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SimulateWithdrawResponse", + "description": "This structure stores the parameters used to return a response when simulating a withdrawal.", + "type": "object", + "required": [ + "astro_to_withdraw" + ], + "properties": { + "astro_to_withdraw": { + "description": "Amount of ASTRO to receive", + "allOf": [ + { + "$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" + } + } + }, + "state": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "State", + "description": "This structure stores the total and the remaining amount of ASTRO to be unlocked by all accounts.", + "type": "object", + "required": [ + "remaining_astro_tokens", + "total_astro_deposited", + "unallocated_astro_tokens" + ], + "properties": { + "remaining_astro_tokens": { + "description": "Currently available ASTRO tokens that still need to be unlocked and/or withdrawn", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "total_astro_deposited": { + "description": "Amount of ASTRO tokens deposited into the contract", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "unallocated_astro_tokens": { + "description": "Amount of ASTRO tokens deposited into the contract but not assigned to an allocation", + "allOf": [ + { + "$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" + } + } + }, + "unlocked_tokens": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "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/schemas/builder-unlock/raw/execute.json b/schemas/builder-unlock/raw/execute.json new file mode 100644 index 00000000..6de62944 --- /dev/null +++ b/schemas/builder-unlock/raw/execute.json @@ -0,0 +1,385 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "description": "This enum describes all the execute functions available in the contract.", + "oneOf": [ + { + "description": "CreateAllocations creates new ASTRO allocations", + "type": "object", + "required": [ + "create_allocations" + ], + "properties": { + "create_allocations": { + "type": "object", + "required": [ + "allocations" + ], + "properties": { + "allocations": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/CreateAllocationParams" + } + ], + "maxItems": 2, + "minItems": 2 + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Withdraw claims withdrawable ASTRO", + "type": "object", + "required": [ + "withdraw" + ], + "properties": { + "withdraw": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "ProposeNewReceiver allows a user to change the receiver address for their ASTRO allocation", + "type": "object", + "required": [ + "propose_new_receiver" + ], + "properties": { + "propose_new_receiver": { + "type": "object", + "required": [ + "new_receiver" + ], + "properties": { + "new_receiver": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "DropNewReceiver allows a user to remove the previously proposed new receiver for their ASTRO allocation", + "type": "object", + "required": [ + "drop_new_receiver" + ], + "properties": { + "drop_new_receiver": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "ClaimReceiver allows newly proposed receivers to claim ASTRO allocations ownership", + "type": "object", + "required": [ + "claim_receiver" + ], + "properties": { + "claim_receiver": { + "type": "object", + "required": [ + "prev_receiver" + ], + "properties": { + "prev_receiver": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Increase the ASTRO allocation of a receiver", + "type": "object", + "required": [ + "increase_allocation" + ], + "properties": { + "increase_allocation": { + "type": "object", + "required": [ + "amount", + "receiver" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "receiver": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Decrease the ASTRO allocation of a receiver", + "type": "object", + "required": [ + "decrease_allocation" + ], + "properties": { + "decrease_allocation": { + "type": "object", + "required": [ + "amount", + "receiver" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "receiver": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Transfer unallocated tokens (only accessible to the owner)", + "type": "object", + "required": [ + "transfer_unallocated" + ], + "properties": { + "transfer_unallocated": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "recipient": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Propose a new owner for the contract", + "type": "object", + "required": [ + "propose_new_owner" + ], + "properties": { + "propose_new_owner": { + "type": "object", + "required": [ + "expires_in", + "new_owner" + ], + "properties": { + "expires_in": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "new_owner": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Remove the ownership transfer proposal", + "type": "object", + "required": [ + "drop_ownership_proposal" + ], + "properties": { + "drop_ownership_proposal": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Claim contract ownership", + "type": "object", + "required": [ + "claim_ownership" + ], + "properties": { + "claim_ownership": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Update parameters in the contract configuration", + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "required": [ + "new_max_allocations_amount" + ], + "properties": { + "new_max_allocations_amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Update a schedule of allocation for specified accounts", + "type": "object", + "required": [ + "update_unlock_schedules" + ], + "properties": { + "update_unlock_schedules": { + "type": "object", + "required": [ + "new_unlock_schedules" + ], + "properties": { + "new_unlock_schedules": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/Schedule" + } + ], + "maxItems": 2, + "minItems": 2 + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "CreateAllocationParams": { + "description": "This structure stores the parameters used to describe an ASTRO allocation.", + "type": "object", + "required": [ + "amount", + "unlock_schedule" + ], + "properties": { + "amount": { + "description": "Total amount of ASTRO tokens allocated to a specific account", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "unlock_schedule": { + "description": "Parameters controlling the unlocking process", + "allOf": [ + { + "$ref": "#/definitions/Schedule" + } + ] + } + }, + "additionalProperties": false + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Schedule": { + "description": "This structure stores the parameters describing a typical unlock schedule.", + "type": "object", + "required": [ + "cliff", + "duration", + "start_time" + ], + "properties": { + "cliff": { + "description": "Cliff period during which no tokens can be withdrawn out of the contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "duration": { + "description": "Time after the cliff during which the remaining tokens linearly unlock", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "percent_at_cliff": { + "description": "Percentage of tokens unlocked at the cliff", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "start_time": { + "description": "Timestamp for the start of the unlock schedule (in seconds)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/schemas/builder-unlock/raw/instantiate.json b/schemas/builder-unlock/raw/instantiate.json new file mode 100644 index 00000000..a6895052 --- /dev/null +++ b/schemas/builder-unlock/raw/instantiate.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "astro_denom", + "max_allocations_amount", + "owner" + ], + "properties": { + "astro_denom": { + "description": "ASTRO token denom", + "type": "string" + }, + "max_allocations_amount": { + "description": "Max ASTRO tokens to allocate", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "owner": { + "description": "Account that can create new allocations", + "type": "string" + } + }, + "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/schemas/builder-unlock/raw/query.json b/schemas/builder-unlock/raw/query.json new file mode 100644 index 00000000..cc0d46cb --- /dev/null +++ b/schemas/builder-unlock/raw/query.json @@ -0,0 +1,161 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "description": "This enum describes all the queries available in the contract.", + "oneOf": [ + { + "description": "Config returns the configuration for this contract", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "State returns the state of this contract", + "type": "object", + "required": [ + "state" + ], + "properties": { + "state": { + "type": "object", + "properties": { + "timestamp": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Allocation returns the parameters and current status of an allocation", + "type": "object", + "required": [ + "allocation" + ], + "properties": { + "allocation": { + "type": "object", + "required": [ + "account" + ], + "properties": { + "account": { + "description": "Account whose allocation status we query", + "type": "string" + }, + "timestamp": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Allocations returns a vector that contains builder unlock allocations by specified parameters", + "type": "object", + "required": [ + "allocations" + ], + "properties": { + "allocations": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "UnlockedTokens returns the unlocked tokens from an allocation", + "type": "object", + "required": [ + "unlocked_tokens" + ], + "properties": { + "unlocked_tokens": { + "type": "object", + "required": [ + "account" + ], + "properties": { + "account": { + "description": "Account whose amount of unlocked ASTRO we query for", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "SimulateWithdraw simulates how many ASTRO will be released if a withdrawal is attempted", + "type": "object", + "required": [ + "simulate_withdraw" + ], + "properties": { + "simulate_withdraw": { + "type": "object", + "required": [ + "account" + ], + "properties": { + "account": { + "description": "Account for which we simulate a withdrawal", + "type": "string" + }, + "timestamp": { + "description": "Timestamp used to simulate how much ASTRO the account can withdraw", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/schemas/builder-unlock/raw/response_to_allocation.json b/schemas/builder-unlock/raw/response_to_allocation.json new file mode 100644 index 00000000..8546d0f2 --- /dev/null +++ b/schemas/builder-unlock/raw/response_to_allocation.json @@ -0,0 +1,148 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AllocationResponse", + "description": "This structure stores the parameters used to return the response when querying for an allocation data.", + "type": "object", + "required": [ + "params", + "status" + ], + "properties": { + "params": { + "description": "The allocation parameters", + "allOf": [ + { + "$ref": "#/definitions/AllocationParams" + } + ] + }, + "status": { + "description": "The allocation status", + "allOf": [ + { + "$ref": "#/definitions/AllocationStatus" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "AllocationParams": { + "type": "object", + "required": [ + "unlock_schedule" + ], + "properties": { + "proposed_receiver": { + "description": "Proposed new receiver who will get the ASTRO allocation", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "unlock_schedule": { + "description": "Parameters controlling the unlocking process", + "allOf": [ + { + "$ref": "#/definitions/Schedule" + } + ] + } + }, + "additionalProperties": false + }, + "AllocationStatus": { + "description": "This structure stores the parameters used to describe the status of an allocation.", + "type": "object", + "required": [ + "amount", + "astro_withdrawn", + "unlocked_amount_checkpoint" + ], + "properties": { + "amount": { + "description": "Total amount of ASTRO tokens allocated to a specific account", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "astro_withdrawn": { + "description": "Amount of ASTRO already withdrawn", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "unlocked_amount_checkpoint": { + "description": "Already unlocked amount after decreasing", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Schedule": { + "description": "This structure stores the parameters describing a typical unlock schedule.", + "type": "object", + "required": [ + "cliff", + "duration", + "start_time" + ], + "properties": { + "cliff": { + "description": "Cliff period during which no tokens can be withdrawn out of the contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "duration": { + "description": "Time after the cliff during which the remaining tokens linearly unlock", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "percent_at_cliff": { + "description": "Percentage of tokens unlocked at the cliff", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "start_time": { + "description": "Timestamp for the start of the unlock schedule (in seconds)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/schemas/builder-unlock/raw/response_to_allocations.json b/schemas/builder-unlock/raw/response_to_allocations.json new file mode 100644 index 00000000..0665f066 --- /dev/null +++ b/schemas/builder-unlock/raw/response_to_allocations.json @@ -0,0 +1,97 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Tuple_of_String_and_AllocationParams", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/AllocationParams" + } + ], + "maxItems": 2, + "minItems": 2 + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "AllocationParams": { + "type": "object", + "required": [ + "unlock_schedule" + ], + "properties": { + "proposed_receiver": { + "description": "Proposed new receiver who will get the ASTRO allocation", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "unlock_schedule": { + "description": "Parameters controlling the unlocking process", + "allOf": [ + { + "$ref": "#/definitions/Schedule" + } + ] + } + }, + "additionalProperties": false + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Schedule": { + "description": "This structure stores the parameters describing a typical unlock schedule.", + "type": "object", + "required": [ + "cliff", + "duration", + "start_time" + ], + "properties": { + "cliff": { + "description": "Cliff period during which no tokens can be withdrawn out of the contract", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "duration": { + "description": "Time after the cliff during which the remaining tokens linearly unlock", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "percent_at_cliff": { + "description": "Percentage of tokens unlocked at the cliff", + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, + "start_time": { + "description": "Timestamp for the start of the unlock schedule (in seconds)", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/schemas/builder-unlock/raw/response_to_config.json b/schemas/builder-unlock/raw/response_to_config.json new file mode 100644 index 00000000..17115579 --- /dev/null +++ b/schemas/builder-unlock/raw/response_to_config.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config", + "description": "This structure stores general parameters for the builder unlock contract.", + "type": "object", + "required": [ + "astro_denom", + "max_allocations_amount", + "owner" + ], + "properties": { + "astro_denom": { + "description": "ASTRO token denom", + "type": "string" + }, + "max_allocations_amount": { + "description": "Max ASTRO tokens to allocate", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "owner": { + "description": "Account that can create new unlock schedules", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "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/schemas/builder-unlock/raw/response_to_simulate_withdraw.json b/schemas/builder-unlock/raw/response_to_simulate_withdraw.json new file mode 100644 index 00000000..f5b0b8bf --- /dev/null +++ b/schemas/builder-unlock/raw/response_to_simulate_withdraw.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SimulateWithdrawResponse", + "description": "This structure stores the parameters used to return a response when simulating a withdrawal.", + "type": "object", + "required": [ + "astro_to_withdraw" + ], + "properties": { + "astro_to_withdraw": { + "description": "Amount of ASTRO to receive", + "allOf": [ + { + "$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/schemas/builder-unlock/raw/response_to_state.json b/schemas/builder-unlock/raw/response_to_state.json new file mode 100644 index 00000000..d940aae5 --- /dev/null +++ b/schemas/builder-unlock/raw/response_to_state.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "State", + "description": "This structure stores the total and the remaining amount of ASTRO to be unlocked by all accounts.", + "type": "object", + "required": [ + "remaining_astro_tokens", + "total_astro_deposited", + "unallocated_astro_tokens" + ], + "properties": { + "remaining_astro_tokens": { + "description": "Currently available ASTRO tokens that still need to be unlocked and/or withdrawn", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "total_astro_deposited": { + "description": "Amount of ASTRO tokens deposited into the contract", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "unallocated_astro_tokens": { + "description": "Amount of ASTRO tokens deposited into the contract but not assigned to an allocation", + "allOf": [ + { + "$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/schemas/builder-unlock/raw/response_to_unlocked_tokens.json b/schemas/builder-unlock/raw/response_to_unlocked_tokens.json new file mode 100644 index 00000000..25b73e8f --- /dev/null +++ b/schemas/builder-unlock/raw/response_to_unlocked_tokens.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "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/scripts/build_schemas.sh b/scripts/build_schemas.sh new file mode 100755 index 00000000..b719b88a --- /dev/null +++ b/scripts/build_schemas.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +set -e + +TARGETS="$(cargo metadata --no-deps --locked --manifest-path "$PWD/Cargo.toml" --format-version 1 | + jq -r --arg contracts "$PWD/contracts" -r \ + '.packages[] + | select(.manifest_path | startswith($contracts)) + | .name + " " + (.targets[] | select(.kind==["example"]) | .name)')" + +rm -rf schemas + +while read -r contract schema_builder; do + if [[ ! "$schema_builder" =~ "_schema" ]]; then + echo "Skipping example $schema_builder" + continue + fi + echo "Building $contract $schema_builder" + cargo run --locked --example "$schema_builder" + + mkdir -p "schemas/$contract" + mv "$PWD/schema/"* "$PWD/schemas/$contract/" +done <<<"$TARGETS" + +rmdir schema From 690f6c4eb72277ee12069e95ad844b82fd0f8e2f Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Mon, 13 May 2024 13:33:29 +0400 Subject: [PATCH 03/32] revamp and simplify vxASTRO --- Cargo.lock | 1324 ----------------- Cargo.toml | 13 +- contracts/assembly/Cargo.toml | 2 +- contracts/assembly/tests/common/helper.rs | 2 +- contracts/nft/.cargo/config | 5 - contracts/nft/Cargo.toml | 28 - contracts/nft/README.md | 11 - contracts/nft/examples/nft_schema.rs | 45 - contracts/nft/src/contract.rs | 62 - contracts/nft/src/lib.rs | 1 - contracts/voting_escrow/Cargo.toml | 41 +- contracts/voting_escrow/README.md | 679 ++++++--- .../{schema.rs => voting_escrow_schema.rs} | 3 +- contracts/voting_escrow/src/contract.rs | 921 ++---------- contracts/voting_escrow/src/error.rs | 42 +- contracts/voting_escrow/src/lib.rs | 7 +- .../voting_escrow/src/marketing_validation.rs | 19 +- contracts/voting_escrow/src/state.rs | 189 ++- contracts/voting_escrow/src/utils.rs | 141 -- contracts/voting_escrow/tests/integration.rs | 1183 --------------- .../tests/plots/constant_decay.png | Bin 157448 -> 0 bytes .../tests/plots/variable_decay.png | Bin 189306 -> 0 bytes contracts/voting_escrow/tests/simulation.rs | 439 ------ contracts/voting_escrow/tests/test_utils.rs | 498 ------- .../tests/vxastro_integration.rs | 1 + .../voting_escrow_delegation/.cargo/config | 5 - contracts/voting_escrow_delegation/Cargo.toml | 33 - contracts/voting_escrow_delegation/README.md | 132 -- .../voting_escrow_delegation_schema.rs | 10 - .../voting_escrow_delegation/src/contract.rs | 447 ------ .../voting_escrow_delegation/src/error.rs | 42 - .../voting_escrow_delegation/src/helpers.rs | 160 -- contracts/voting_escrow_delegation/src/lib.rs | 5 - .../voting_escrow_delegation/src/state.rs | 16 - .../tests/integration.rs | 914 ------------ .../tests/simulation.rs | 371 ----- .../tests/test_helper.rs | 190 --- packages/astroport-governance/src/lib.rs | 10 +- packages/astroport-governance/src/nft.rs | 5 - packages/astroport-governance/src/utils.rs | 65 +- .../astroport-governance/src/voting_escrow.rs | 237 +-- .../src/voting_escrow_delegation.rs | 93 -- 42 files changed, 789 insertions(+), 7602 deletions(-) delete mode 100644 Cargo.lock delete mode 100644 contracts/nft/.cargo/config delete mode 100644 contracts/nft/Cargo.toml delete mode 100644 contracts/nft/README.md delete mode 100644 contracts/nft/examples/nft_schema.rs delete mode 100644 contracts/nft/src/contract.rs delete mode 100644 contracts/nft/src/lib.rs rename contracts/voting_escrow/examples/{schema.rs => voting_escrow_schema.rs} (79%) delete mode 100644 contracts/voting_escrow/src/utils.rs delete mode 100644 contracts/voting_escrow/tests/integration.rs delete mode 100644 contracts/voting_escrow/tests/plots/constant_decay.png delete mode 100644 contracts/voting_escrow/tests/plots/variable_decay.png delete mode 100644 contracts/voting_escrow/tests/simulation.rs delete mode 100644 contracts/voting_escrow/tests/test_utils.rs create mode 100644 contracts/voting_escrow/tests/vxastro_integration.rs delete mode 100644 contracts/voting_escrow_delegation/.cargo/config delete mode 100644 contracts/voting_escrow_delegation/Cargo.toml delete mode 100644 contracts/voting_escrow_delegation/README.md delete mode 100644 contracts/voting_escrow_delegation/examples/voting_escrow_delegation_schema.rs delete mode 100644 contracts/voting_escrow_delegation/src/contract.rs delete mode 100644 contracts/voting_escrow_delegation/src/error.rs delete mode 100644 contracts/voting_escrow_delegation/src/helpers.rs delete mode 100644 contracts/voting_escrow_delegation/src/lib.rs delete mode 100644 contracts/voting_escrow_delegation/src/state.rs delete mode 100644 contracts/voting_escrow_delegation/tests/integration.rs delete mode 100644 contracts/voting_escrow_delegation/tests/simulation.rs delete mode 100644 contracts/voting_escrow_delegation/tests/test_helper.rs delete mode 100644 packages/astroport-governance/src/nft.rs delete mode 100644 packages/astroport-governance/src/voting_escrow_delegation.rs diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 455b43ea..00000000 --- a/Cargo.lock +++ /dev/null @@ -1,1324 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "ahash" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] - -[[package]] -name = "anyhow" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" - -[[package]] -name = "astro-assembly" -version = "2.0.0" -dependencies = [ - "anyhow", - "astro-satellite", - "astroport 4.0.0", - "astroport-governance 3.0.0", - "astroport-staking", - "astroport-tokenfactory-tracker", - "builder-unlock", - "cosmwasm-schema", - "cosmwasm-std", - "cw-multi-test 0.20.0 (git+https://github.com/astroport-fi/cw-multi-test?branch=feat/bank_with_send_hooks)", - "cw-storage-plus 1.2.0", - "cw-utils 1.0.3", - "cw2 1.1.2", - "ibc-controller-package 1.0.0", - "osmosis-std", - "test-case", - "thiserror", -] - -[[package]] -name = "astro-satellite" -version = "1.1.0" -source = "git+https://github.com/astroport-fi/astroport_ibc?tag=v1.2.1#61f3cf90ac7e48de93224e906171ebe206d7f860" -dependencies = [ - "astro-satellite-package", - "astroport-ibc 1.2.1 (git+https://github.com/astroport-fi/astroport_ibc?tag=v1.2.1)", - "cosmwasm-schema", - "cosmwasm-std", - "cosmwasm-storage", - "cw-storage-plus 0.15.1", - "cw-utils 0.15.1", - "cw2 0.15.1", - "ibc-controller-package 0.1.0", - "itertools 0.10.5", - "thiserror", -] - -[[package]] -name = "astro-satellite-package" -version = "0.1.0" -source = "git+https://github.com/astroport-fi/astroport_ibc?tag=v1.2.1#61f3cf90ac7e48de93224e906171ebe206d7f860" -dependencies = [ - "astroport-governance 1.2.0 (git+https://github.com/astroport-fi/astroport-governance)", - "cosmwasm-schema", - "cosmwasm-std", -] - -[[package]] -name = "astroport" -version = "2.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d102b618016b3c1f1ebb5750617a73dbd294a3c941e54b12deabc931d771bc6e" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 0.15.1", - "cw-utils 0.15.1", - "cw20 0.15.1", - "itertools 0.10.5", - "uint", -] - -[[package]] -name = "astroport" -version = "2.10.0" -source = "git+https://github.com/astroport-fi/astroport-core?branch=feat/merge_hidden_2023_05_22#11e7a81d4b18a40bed916177061a549633e02b1b" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 0.15.1", - "cw-utils 1.0.3", - "cw20 0.15.1", - "cw3", - "itertools 0.10.5", - "uint", -] - -[[package]] -name = "astroport" -version = "4.0.0" -source = "git+https://github.com/astroport-fi/hidden_astroport_core#350dd47ee30af5749251a822fd2f7e08942cf854" -dependencies = [ - "astroport-circular-buffer", - "cosmwasm-schema", - "cosmwasm-std", - "cw-asset", - "cw-storage-plus 1.2.0", - "cw-utils 1.0.3", - "cw20 1.1.2", - "itertools 0.12.0", - "uint", -] - -[[package]] -name = "astroport-circular-buffer" -version = "0.2.0" -source = "git+https://github.com/astroport-fi/hidden_astroport_core#350dd47ee30af5749251a822fd2f7e08942cf854" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 1.2.0", - "thiserror", -] - -[[package]] -name = "astroport-governance" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72806ace350e81c4e1cab7e275ef91f05bad830275d697d67ad1bd4acc6f016d" -dependencies = [ - "astroport 2.9.5", - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 0.15.1", - "cw20 0.15.1", -] - -[[package]] -name = "astroport-governance" -version = "1.2.0" -source = "git+https://github.com/astroport-fi/astroport-governance#f0ef7c6dde76fc77ce360262923366a5cde3c3f8" -dependencies = [ - "astroport 2.10.0", - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 0.15.1", - "cw20 0.15.1", -] - -[[package]] -name = "astroport-governance" -version = "3.0.0" -dependencies = [ - "astroport 4.0.0", - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 1.2.0", - "cw20 1.1.2", - "thiserror", -] - -[[package]] -name = "astroport-ibc" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93c0ce66970f190873b30f862b0cd39fb0d8499678a1860446aa60d9618671f4" -dependencies = [ - "cosmwasm-schema", -] - -[[package]] -name = "astroport-ibc" -version = "1.2.1" -source = "git+https://github.com/astroport-fi/astroport_ibc?tag=v1.2.1#61f3cf90ac7e48de93224e906171ebe206d7f860" -dependencies = [ - "cosmwasm-schema", -] - -[[package]] -name = "astroport-staking" -version = "2.0.0" -source = "git+https://github.com/astroport-fi/hidden_astroport_core#350dd47ee30af5749251a822fd2f7e08942cf854" -dependencies = [ - "astroport 4.0.0", - "cosmwasm-std", - "cw-storage-plus 1.2.0", - "cw-utils 1.0.3", - "cw2 1.1.2", - "osmosis-std", - "thiserror", -] - -[[package]] -name = "astroport-tokenfactory-tracker" -version = "1.0.0" -source = "git+https://github.com/astroport-fi/hidden_astroport_core#350dd47ee30af5749251a822fd2f7e08942cf854" -dependencies = [ - "astroport 4.0.0", - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 1.2.0", - "cw2 1.1.2", - "thiserror", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - -[[package]] -name = "bech32" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" - -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bnum" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56953345e39537a3e18bdaeba4cb0c58a78c1f61f361dc0fa7c5c7340ae87c5f" - -[[package]] -name = "builder-unlock" -version = "3.0.0" -dependencies = [ - "astroport 4.0.0", - "astroport-governance 3.0.0", - "cosmwasm-schema", - "cosmwasm-std", - "cw-multi-test 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cw-storage-plus 1.2.0", - "cw-utils 1.0.3", - "cw2 1.1.2", - "thiserror", -] - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41daef31d7a747c5c847246f36de49ced6f7403b4cdabc807a97b5cc184cda7a" -dependencies = [ - "num-traits", -] - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "cosmwasm-crypto" -version = "1.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b4c3f9c4616d6413d4b5fc4c270a4cc32a374b9be08671e80e1a019f805d8f" -dependencies = [ - "digest 0.10.7", - "ecdsa", - "ed25519-zebra", - "k256", - "rand_core 0.6.4", - "thiserror", -] - -[[package]] -name = "cosmwasm-derive" -version = "1.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c586ced10c3b00e809ee664a895025a024f60d65d34fe4c09daed4a4db68a3f3" -dependencies = [ - "syn 1.0.109", -] - -[[package]] -name = "cosmwasm-schema" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b166215fbfe93dc5575bae062aa57ae7bb41121cffe53bac33b033257949d2a9" -dependencies = [ - "cosmwasm-schema-derive", - "schemars", - "serde", - "serde_json", - "thiserror", -] - -[[package]] -name = "cosmwasm-schema-derive" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf12f8e20bb29d1db66b7ca590bc2f670b548d21e9be92499bc0f9022a994a8" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "cosmwasm-std" -version = "1.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712fe58f39d55c812f7b2c84e097cdede3a39d520f89b6dc3153837e31741927" -dependencies = [ - "base64", - "bech32", - "bnum", - "cosmwasm-crypto", - "cosmwasm-derive", - "derivative", - "forward_ref", - "hex", - "schemars", - "serde", - "serde-json-wasm", - "sha2 0.10.8", - "static_assertions", - "thiserror", -] - -[[package]] -name = "cosmwasm-storage" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66de2ab9db04757bcedef2b5984fbe536903ada4a8a9766717a4a71197ef34f6" -dependencies = [ - "cosmwasm-std", - "serde", -] - -[[package]] -name = "cpufeatures" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" -dependencies = [ - "libc", -] - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "crypto-bigint" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "subtle", - "zeroize", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "curve25519-dalek" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" -dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.5.1", - "subtle", - "zeroize", -] - -[[package]] -name = "cw-address-like" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "451a4691083a88a3c0630a8a88799e9d4cd6679b7ce8ff22b8da2873ff31d380" -dependencies = [ - "cosmwasm-std", -] - -[[package]] -name = "cw-asset" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "431e57314dceabd29a682c78bb3ff7c641f8bdc8b915400bb9956cb911e8e571" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-address-like", - "cw-storage-plus 1.2.0", - "cw20 1.1.2", - "thiserror", -] - -[[package]] -name = "cw-multi-test" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fff029689ae89127cf6d7655809a68d712f3edbdb9686c70b018ba438b26ca" -dependencies = [ - "anyhow", - "bech32", - "cosmwasm-std", - "cw-storage-plus 1.2.0", - "cw-utils 1.0.3", - "derivative", - "itertools 0.12.0", - "prost 0.12.3", - "schemars", - "serde", - "sha2 0.10.8", - "thiserror", -] - -[[package]] -name = "cw-multi-test" -version = "0.20.0" -source = "git+https://github.com/astroport-fi/cw-multi-test?branch=feat/bank_with_send_hooks#80ebf1aff909d5438fff093b6243c5d7cbf924b3" -dependencies = [ - "anyhow", - "bech32", - "cosmwasm-std", - "cw-storage-plus 1.2.0", - "cw-utils 1.0.3", - "derivative", - "itertools 0.12.0", - "prost 0.12.3", - "schemars", - "serde", - "sha2 0.10.8", - "thiserror", -] - -[[package]] -name = "cw-storage-plus" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc6cf70ef7686e2da9ad7b067c5942cd3e88dd9453f7af42f54557f8af300fb0" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", -] - -[[package]] -name = "cw-storage-plus" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5ff29294ee99373e2cd5fd21786a3c0ced99a52fec2ca347d565489c61b723c" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", -] - -[[package]] -name = "cw-utils" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ae0b69fa7679de78825b4edeeec045066aa2b2c4b6e063d80042e565bb4da5c" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw2 0.15.1", - "schemars", - "semver", - "serde", - "thiserror", -] - -[[package]] -name = "cw-utils" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c4a657e5caacc3a0d00ee96ca8618745d050b8f757c709babafb81208d4239c" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw2 1.1.2", - "schemars", - "semver", - "serde", - "thiserror", -] - -[[package]] -name = "cw2" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5abb8ecea72e09afff830252963cb60faf945ce6cef2c20a43814516082653da" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 0.15.1", - "schemars", - "serde", -] - -[[package]] -name = "cw2" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c120b24fbbf5c3bedebb97f2cc85fbfa1c3287e09223428e7e597b5293c1fa" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 1.2.0", - "schemars", - "semver", - "serde", - "thiserror", -] - -[[package]] -name = "cw20" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6025276fb6e603e974c21f3e4606982cdc646080e8fba3198816605505e1d9a" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-utils 0.15.1", - "schemars", - "serde", -] - -[[package]] -name = "cw20" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "526e39bb20534e25a1cd0386727f0038f4da294e5e535729ba3ef54055246abd" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-utils 1.0.3", - "schemars", - "serde", -] - -[[package]] -name = "cw3" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2967fbd073d4b626dd9e7148e05a84a3bebd9794e71342e12351110ffbb12395" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-utils 1.0.3", - "cw20 1.1.2", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "der" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" -dependencies = [ - "const-oid", - "zeroize", -] - -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer 0.10.4", - "const-oid", - "crypto-common", - "subtle", -] - -[[package]] -name = "dyn-clone" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" - -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest 0.10.7", - "elliptic-curve", - "rfc6979", - "signature", - "spki", -] - -[[package]] -name = "ed25519-zebra" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" -dependencies = [ - "curve25519-dalek", - "hashbrown", - "hex", - "rand_core 0.6.4", - "serde", - "sha2 0.9.9", - "zeroize", -] - -[[package]] -name = "either" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" - -[[package]] -name = "elliptic-curve" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct", - "crypto-bigint", - "digest 0.10.7", - "ff", - "generic-array", - "group", - "pkcs8", - "rand_core 0.6.4", - "sec1", - "subtle", - "zeroize", -] - -[[package]] -name = "ff" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "forward_ref" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", - "zeroize", -] - -[[package]] -name = "getrandom" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash", -] - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest 0.10.7", -] - -[[package]] -name = "ibc-controller-package" -version = "0.1.0" -source = "git+https://github.com/astroport-fi/astroport_ibc?tag=v1.2.1#61f3cf90ac7e48de93224e906171ebe206d7f860" -dependencies = [ - "astroport-governance 1.2.0 (git+https://github.com/astroport-fi/astroport-governance)", - "astroport-ibc 1.2.1 (git+https://github.com/astroport-fi/astroport_ibc?tag=v1.2.1)", - "cosmwasm-schema", - "cosmwasm-std", -] - -[[package]] -name = "ibc-controller-package" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfcf94f5691716bfecb45e6bb6a82a5c11a392d501c2a695589c5087671f7c33" -dependencies = [ - "astroport-governance 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "astroport-ibc 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cosmwasm-schema", - "cosmwasm-std", -] - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" - -[[package]] -name = "k256" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" -dependencies = [ - "cfg-if", - "ecdsa", - "elliptic-curve", - "once_cell", - "sha2 0.10.8", - "signature", -] - -[[package]] -name = "libc" -version = "0.2.152" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" - -[[package]] -name = "num-traits" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" -dependencies = [ - "autocfg", -] - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - -[[package]] -name = "osmosis-std" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e87adf61f03306474ce79ab322d52dfff6b0bcf3aed1e12d8864ac0400dec1bf" -dependencies = [ - "chrono", - "cosmwasm-std", - "osmosis-std-derive", - "prost 0.12.3", - "prost-types 0.12.3", - "schemars", - "serde", - "serde-cw-value", -] - -[[package]] -name = "osmosis-std-derive" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5ebdfd1bc8ed04db596e110c6baa9b174b04f6ed1ec22c666ddc5cb3fa91bd7" -dependencies = [ - "itertools 0.10.5", - "proc-macro2", - "prost-types 0.11.9", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - -[[package]] -name = "proc-macro2" -version = "1.0.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "prost" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" -dependencies = [ - "bytes", - "prost-derive 0.11.9", -] - -[[package]] -name = "prost" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" -dependencies = [ - "bytes", - "prost-derive 0.12.3", -] - -[[package]] -name = "prost-derive" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" -dependencies = [ - "anyhow", - "itertools 0.10.5", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "prost-derive" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" -dependencies = [ - "anyhow", - "itertools 0.11.0", - "proc-macro2", - "quote", - "syn 2.0.48", -] - -[[package]] -name = "prost-types" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" -dependencies = [ - "prost 0.11.9", -] - -[[package]] -name = "prost-types" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" -dependencies = [ - "prost 0.12.3", -] - -[[package]] -name = "quote" -version = "1.0.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac", - "subtle", -] - -[[package]] -name = "ryu" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" - -[[package]] -name = "schemars" -version = "0.8.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" -dependencies = [ - "dyn-clone", - "schemars_derive", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 1.0.109", -] - -[[package]] -name = "sec1" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct", - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", -] - -[[package]] -name = "semver" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" - -[[package]] -name = "serde" -version = "1.0.195" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde-cw-value" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75d32da6b8ed758b7d850b6c3c08f1d7df51a4df3cb201296e63e34a78e99d4" -dependencies = [ - "serde", -] - -[[package]] -name = "serde-json-wasm" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e9213a07d53faa0b8dd81e767a54a8188a242fdb9be99ab75ec576a774bfdd7" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.195" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] - -[[package]] -name = "serde_derive_internals" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "serde_json" -version = "1.0.111" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.7", -] - -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest 0.10.7", - "rand_core 0.6.4", -] - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "subtle" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "test-case" -version = "3.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" -dependencies = [ - "test-case-macros", -] - -[[package]] -name = "test-case-core" -version = "3.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" -dependencies = [ - "cfg-if", - "proc-macro2", - "quote", - "syn 2.0.48", -] - -[[package]] -name = "test-case-macros" -version = "3.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", - "test-case-core", -] - -[[package]] -name = "thiserror" -version = "1.0.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] - -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "uint" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "zeroize" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/Cargo.toml b/Cargo.toml index 020c8f06..a5794493 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,14 +2,15 @@ resolver = "2" members = [ "packages/astroport-governance", -# "packages/astroport-tests", -# "packages/astroport-tests-lite", + # "packages/astroport-tests", + # "packages/astroport-tests-lite", "contracts/assembly", + "contracts/voting_escrow", "contracts/builder_unlock", -# "contracts/generator_controller_lite", -# "contracts/hub", -# "contracts/outpost", -# "contracts/voting_escrow_lite", + # "contracts/generator_controller_lite", + # "contracts/hub", + # "contracts/outpost", + # "contracts/voting_escrow_lite", ] [workspace.dependencies] diff --git a/contracts/assembly/Cargo.toml b/contracts/assembly/Cargo.toml index 46aa32aa..f4b89842 100644 --- a/contracts/assembly/Cargo.toml +++ b/contracts/assembly/Cargo.toml @@ -25,7 +25,7 @@ cosmwasm-schema.workspace = true cw-utils.workspace = true astroport-governance = { path = "../../packages/astroport-governance", version = "3" } astroport = { git = "https://github.com/astroport-fi/hidden_astroport_core", version = "4" } -astro-satellite = { git = "https://github.com/astroport-fi/astroport_ibc", tag = "v1.2.1", features = ["library"] } +astro-satellite = "1.1.0" ibc-controller-package = "1.0.0" [dev-dependencies] diff --git a/contracts/assembly/tests/common/helper.rs b/contracts/assembly/tests/common/helper.rs index 89b1b873..d1e195be 100644 --- a/contracts/assembly/tests/common/helper.rs +++ b/contracts/assembly/tests/common/helper.rs @@ -238,7 +238,7 @@ impl Helper { self.app.execute_contract( sender.clone(), self.staking.clone(), - &staking::ExecuteMsg::Enter {}, + &staking::ExecuteMsg::Enter { receiver: None }, &coins(amount, ASTRO_DENOM), ) } diff --git a/contracts/nft/.cargo/config b/contracts/nft/.cargo/config deleted file mode 100644 index 5a843b6c..00000000 --- a/contracts/nft/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib" -schema = "run --example nft_schema" diff --git a/contracts/nft/Cargo.toml b/contracts/nft/Cargo.toml deleted file mode 100644 index 56cd14f4..00000000 --- a/contracts/nft/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "astroport-nft" -version = "1.0.0" -authors = ["Ethan Frey ", "Orkun Külçe ", "Astroport"] -edition = "2021" -description = "Expanded implementation of cw721 NFTs compliant token for the Astroport NFT" -license = "Apache-2.0" -repository = "https://github.com/CosmWasm/cw-nfts" -homepage = "https://cosmwasm.com" -documentation = "https://docs.cosmwasm.com" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] -# use library feature to disable all instantiate/execute/query exports -library = [] - -[dependencies] -cw2 = "0.15" -cw721 = "0.15" -cw721-base = { version = "0.15", features = ["library"] } -cosmwasm-std = "1.1" -astroport-governance = { path = "../../packages/astroport-governance" } -cosmwasm-schema = "1.1" diff --git a/contracts/nft/README.md b/contracts/nft/README.md deleted file mode 100644 index 2c54ac76..00000000 --- a/contracts/nft/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Astroport NFT - -This is the contract implementation for the ASTRO NFT. - - -# Cw721 Basic - -This is a basic implementation of a cw721 NFT contract. It implements -the [CW721 spec](https://github.com/CosmWasm/cw-nfts/blob/main/contracts/cw721-base/README.md) and is designed to -be deployed as is, or imported into other contracts to easily build -cw721-compatible NFTs with custom logic. diff --git a/contracts/nft/examples/nft_schema.rs b/contracts/nft/examples/nft_schema.rs deleted file mode 100644 index b00b73c5..00000000 --- a/contracts/nft/examples/nft_schema.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::env::current_dir; -use std::fs::create_dir_all; - -use cosmwasm_schema::{export_schema, export_schema_with_title, remove_schemas, schema_for}; -use cosmwasm_std::Empty; - -use cw721::{ - AllNftInfoResponse, ApprovalResponse, ApprovalsResponse, ContractInfoResponse, NftInfoResponse, - NumTokensResponse, OperatorsResponse, OwnerOfResponse, TokensResponse, -}; - -use cw721_base::{ExecuteMsg, Extension, InstantiateMsg, MinterResponse, QueryMsg}; - -fn main() { - let mut out_dir = current_dir().unwrap(); - out_dir.push("schema"); - create_dir_all(&out_dir).unwrap(); - remove_schemas(&out_dir).unwrap(); - - export_schema(&schema_for!(InstantiateMsg), &out_dir); - export_schema_with_title( - &schema_for!(ExecuteMsg), - &out_dir, - "ExecuteMsg", - ); - export_schema(&schema_for!(QueryMsg), &out_dir); - export_schema_with_title( - &schema_for!(AllNftInfoResponse), - &out_dir, - "AllNftInfoResponse", - ); - export_schema(&schema_for!(ApprovalResponse), &out_dir); - export_schema(&schema_for!(ApprovalsResponse), &out_dir); - export_schema(&schema_for!(OperatorsResponse), &out_dir); - export_schema(&schema_for!(ContractInfoResponse), &out_dir); - export_schema(&schema_for!(MinterResponse), &out_dir); - export_schema_with_title( - &schema_for!(NftInfoResponse), - &out_dir, - "NftInfoResponse", - ); - export_schema(&schema_for!(NumTokensResponse), &out_dir); - export_schema(&schema_for!(OwnerOfResponse), &out_dir); - export_schema(&schema_for!(TokensResponse), &out_dir); -} diff --git a/contracts/nft/src/contract.rs b/contracts/nft/src/contract.rs deleted file mode 100644 index 6bed41f9..00000000 --- a/contracts/nft/src/contract.rs +++ /dev/null @@ -1,62 +0,0 @@ -use cosmwasm_std::{ - entry_point, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, -}; - -use astroport_governance::nft::MigrateMsg; -use cw2::set_contract_version; -use cw721::ContractInfoResponse; -use cw721_base::msg::{ExecuteMsg, InstantiateMsg}; -use cw721_base::state::Cw721Contract; -use cw721_base::{ContractError, Extension, QueryMsg}; - -/// Contract name that is used for migration. -const CONTRACT_NAME: &str = "astroport-nft"; -/// Contract version that is used for migration. -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -/// Creates a new contract with the specified parameters in the [`InstantiateMsg`]. -#[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 info = ContractInfoResponse { - name: msg.name, - symbol: msg.symbol, - }; - let tract = Cw721Contract::::default(); - tract.contract_info.save(deps.storage, &info)?; - - let minter = deps.api.addr_validate(msg.minter.as_str())?; - tract.minter.save(deps.storage, &minter)?; - Ok(Response::default()) -} - -/// Exposes execute functions available in the contract. -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - let tract = Cw721Contract::::default(); - tract.execute(deps, env, info, msg) -} - -/// Exposes queries available in the contract. -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - let tract = Cw721Contract::::default(); - tract.query(deps, env, msg) -} - -/// Used for contract migration. Returns a default object of type [`Response`]. -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { - Ok(Response::default()) -} diff --git a/contracts/nft/src/lib.rs b/contracts/nft/src/lib.rs deleted file mode 100644 index 2943dbb5..00000000 --- a/contracts/nft/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod contract; diff --git a/contracts/voting_escrow/Cargo.toml b/contracts/voting_escrow/Cargo.toml index 44795e97..647ce413 100644 --- a/contracts/voting_escrow/Cargo.toml +++ b/contracts/voting_escrow/Cargo.toml @@ -1,41 +1,34 @@ [package] -name = "voting-escrow" -version = "1.3.0" +name = "astroport-voting-escrow" +version = "1.0.0" authors = ["Astroport"] edition = "2021" +description = "Astroport Vote Escrowed xASTRO (vxASTRO)" +license = "GPL-3.0-only" repository = "https://github.com/astroport-fi/astroport-governance" homepage = "https://astroport.fi" -exclude = [ - # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. - "contract.wasm", - "hash.txt", -] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [lib] crate-type = ["cdylib", "rlib"] [features] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces backtraces = ["cosmwasm-std/backtraces"] +library = [] [dependencies] -cw2 = "0.15" -cw20 = "0.15" -cw20-base = { version = "0.15", features = ["library"] } -cosmwasm-std = "1.1" -cw-storage-plus = "0.15" -thiserror = { version = "1.0" } -astroport-governance = { path = "../../packages/astroport-governance" } -cosmwasm-schema = "1.1" +cw2.workspace = true +cw20 = "1" +cw-utils = "1" +cw20-base = { version = "1", features = ["library"] } +cosmwasm-std.workspace = true +cw-storage-plus.workspace = true +thiserror.workspace = true +cosmwasm-schema.workspace = true +astroport-governance = { path = "../../packages/astroport-governance", version = "3" } +astroport = { git = "https://github.com/astroport-fi/hidden_astroport_core", version = "4" } [dev-dependencies] -cw-multi-test = "0.15" -astroport-token = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -astroport-staking = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -astroport-escrow-fee-distributor = { path = "../escrow_fee_distributor" } +cw-multi-test = { git = "https://github.com/astroport-fi/cw-multi-test", branch = "feat/bank_with_send_hooks", features = ["cosmwasm_1_1"] } +astroport-staking = { git = "https://github.com/astroport-fi/hidden_astroport_core", version = "2" } anyhow = "1" proptest = "1.0" diff --git a/contracts/voting_escrow/README.md b/contracts/voting_escrow/README.md index 997f7707..19a80ffc 100644 --- a/contracts/voting_escrow/README.md +++ b/contracts/voting_escrow/README.md @@ -1,264 +1,527 @@ # Vote Escrowed Staked ASTRO -The vxASTRO contract allows xASTRO token holders to stake their tokens in order to boost their governance power as well as the amount of ASTRO they can get from Generator emissions. Voting power is boosted according to how long someone locks their xASTRO for. +[//]: # (TODO: Rewrite README) -Maximum lock time is 2 years, which gives the maximum possible boost of 2.5. For example, if a token holder locks 100 xASTRO for 2 years, they -get 250 vxASTRO. Their vxASTRO balance then goes down every week for the next 2 years (unless they relock) until it reaches zero. +[//]: # (The vxASTRO contract allows xASTRO token holders to stake their tokens in order to boost their governance power as well as the amount of ASTRO they can get from Generator emissions. Voting power is boosted according to how long someone locks their xASTRO for.) -## InstantiateMsg +[//]: # () -Initialize the contract with the initial owner and the address of the xASTRO token. +[//]: # (Maximum lock time is 2 years, which gives the maximum possible boost of 2.5. For example, if a token holder locks 100 xASTRO for 2 years, they) -```json -{ - "owner": "terra...", - "deposit_token_addr": "terra..." -} -``` +[//]: # (get 250 vxASTRO. Their vxASTRO balance then goes down every week for the next 2 years (unless they relock) until it reaches zero.) -## ExecuteMsg +[//]: # () -### `receive` +[//]: # (## InstantiateMsg) -Create new lock/vxASTRO position, deposit more xASTRO in the user's vxASTRO position or deposit on behalf of another address. +[//]: # () -```json -{ - "receive": { - "sender": "terra...", - "amount": "123", - "msg": "" - } -} -``` +[//]: # (Initialize the contract with the initial owner and the address of the xASTRO token.) -### `extend_lock_time` +[//]: # () -An example of extending the lock time for a vxASTRO position by 1 week. +[//]: # (```json) -```json -{ - "extend_lock_time": { - "time": 604800 - } -} -``` +[//]: # ({) -### `withdraw` +[//]: # ( "owner": "terra...",) -Withdraw the whole amount of xASTRO if the lock for a vxASTRO position expired. +[//]: # ( "deposit_token_addr": "terra...") -```json -{ - "withdraw": {} -} -``` +[//]: # (}) -### `propose_new_owner` +[//]: # (```) -Create a request to change contract ownership. The validity period of the offer is set by the `expires_in` variable. -Only the current contract owner can execute this method. +[//]: # () -```json -{ - "propose_new_owner": { - "owner": "terra...", - "expires_in": 1234567 - } -} -``` +[//]: # (## ExecuteMsg) -### `drop_ownership_proposal` +[//]: # () -Delete the contract ownership transfer proposal. Only the current contract owner can execute this method. +[//]: # (### `receive`) -```json -{ - "drop_ownership_proposal": {} -} -``` +[//]: # () -### `claim_ownership` +[//]: # (Create new lock/vxASTRO position, deposit more xASTRO in the user's vxASTRO position or deposit on behalf of another address.) -Used to claim contract ownership. Only the newly proposed contract owner can execute this method. +[//]: # () -```json -{ - "claim_ownership": {} -} -``` +[//]: # (```json) -### `update_blacklist` +[//]: # ({) -Updates the list of addresses that are prohibited from staking in vxASTRO or if they are already staked, from voting with their vxASTRO in the Astral Assembly. Only the contract owner can execute this method. +[//]: # ( "receive": {) -```json -{ - "append_addrs": ["terra...", "terra...", "terra..."], - "remove_addrs": ["terra...", "terra..."] -} -``` +[//]: # ( "sender": "terra...",) -### `update_config` +[//]: # ( "amount": "123",) -Updates contract parameters. +[//]: # ( "msg": "") -```json -{ - "new_guardian": "terra..." -} -``` +[//]: # ( }) -## QueryMsg +[//]: # (}) -All query messages are described below. A custom struct is defined for each query response. +[//]: # (```) -### `total_voting_power` +[//]: # () -Returns the total supply of vxASTRO at the current block. +[//]: # (### `extend_lock_time`) -```json -{ - "voting_power_response": { - "voting_power": 100 - } -} -``` +[//]: # () -### `user_voting_power` +[//]: # (An example of extending the lock time for a vxASTRO position by 1 week.) -Returns a user's vxASTRO balance at the current block. +[//]: # () -Request: +[//]: # (```json) -```json -{ - "user_voting_power": { - "user": "terra..." - } -} -``` +[//]: # ({) -Response: +[//]: # ( "extend_lock_time": {) -```json -{ - "voting_power_response": { - "voting_power": 10 - } -} -``` +[//]: # ( "time": 604800) -### `total_voting_power_at` +[//]: # ( }) -Returns the total vxASTRO supply at a specific timestamp (in seconds). +[//]: # (}) -Request: +[//]: # (```) -```json -{ - "total_voting_power_at": { - "time": 1234567 - } -} -``` +[//]: # () -Response: +[//]: # (### `withdraw`) -```json -{ - "voting_power_response": { - "voting_power": 10 - } -} -``` +[//]: # () -### `user_voting_power_at` +[//]: # (Withdraw the whole amount of xASTRO if the lock for a vxASTRO position expired.) -Returns the user's vxASTRO balance at a specific timestamp (in seconds). +[//]: # () -Request: +[//]: # (```json) -```json -{ - "user_voting_power_at": { - "user": "terra...", - "time": 1234567 - } -} -``` +[//]: # ({) -Response: +[//]: # ( "withdraw": {}) -```json -{ - "voting_power_response": { - "voting_power": 10 - } -} -``` +[//]: # (}) -### `lock_info` +[//]: # (```) -Returns the information about a user's vxASTRO position. - -Request: - -```json -{ - "lock_info": { - "user": "terra..." - } -} -``` - -Response: - -```json -{ - "lock_info_response": { - "amount": 10, - "coefficient": 2.5, - "start": 2600, - "end": 2704 - } -} -``` - -### `config` - -Returns the contract's config. - -```json -{ - "config_response": { - "owner": "terra...", - "deposit_token_addr" : "terra..." - } -} -``` - -### `blacklisted_voters` - -Returns blacklisted voters. - -```json -{ - "blacklisted_voters": { - "start_after": "terra...", - "limit": 5 - } -} -``` - -### `check_voters_are_blacklisted` - -Checks if specified addresses are blacklisted - -```json -{ - "check_voters_are_blacklisted": { - "voters": ["terra...", "terra..."] - } -} -``` \ No newline at end of file +[//]: # () + +[//]: # (### `propose_new_owner`) + +[//]: # () + +[//]: # (Create a request to change contract ownership. The validity period of the offer is set by the `expires_in` variable.) + +[//]: # (Only the current contract owner can execute this method.) + +[//]: # () + +[//]: # (```json) + +[//]: # ({) + +[//]: # ( "propose_new_owner": {) + +[//]: # ( "owner": "terra...",) + +[//]: # ( "expires_in": 1234567) + +[//]: # ( }) + +[//]: # (}) + +[//]: # (```) + +[//]: # () + +[//]: # (### `drop_ownership_proposal`) + +[//]: # () + +[//]: # (Delete the contract ownership transfer proposal. Only the current contract owner can execute this method.) + +[//]: # () + +[//]: # (```json) + +[//]: # ({) + +[//]: # ( "drop_ownership_proposal": {}) + +[//]: # (}) + +[//]: # (```) + +[//]: # () + +[//]: # (### `claim_ownership`) + +[//]: # () + +[//]: # (Used to claim contract ownership. Only the newly proposed contract owner can execute this method.) + +[//]: # () + +[//]: # (```json) + +[//]: # ({) + +[//]: # ( "claim_ownership": {}) + +[//]: # (}) + +[//]: # (```) + +[//]: # () + +[//]: # (### `update_blacklist`) + +[//]: # () + +[//]: # (Updates the list of addresses that are prohibited from staking in vxASTRO or if they are already staked, from voting with their vxASTRO in the Astral Assembly. Only the contract owner can execute this method.) + +[//]: # () + +[//]: # (```json) + +[//]: # ({) + +[//]: # ( "append_addrs": ["terra...", "terra...", "terra..."],) + +[//]: # ( "remove_addrs": ["terra...", "terra..."]) + +[//]: # (}) + +[//]: # (```) + +[//]: # () + +[//]: # (### `update_config`) + +[//]: # () + +[//]: # (Updates contract parameters.) + +[//]: # () + +[//]: # (```json) + +[//]: # ({) + +[//]: # ( "new_guardian": "terra...") + +[//]: # (}) + +[//]: # (```) + +[//]: # () + +[//]: # (## QueryMsg) + +[//]: # () + +[//]: # (All query messages are described below. A custom struct is defined for each query response.) + +[//]: # () + +[//]: # (### `total_voting_power`) + +[//]: # () + +[//]: # (Returns the total supply of vxASTRO at the current block.) + +[//]: # () + +[//]: # (```json) + +[//]: # ({) + +[//]: # ( "voting_power_response": {) + +[//]: # ( "voting_power": 100) + +[//]: # ( }) + +[//]: # (}) + +[//]: # (```) + +[//]: # () + +[//]: # (### `user_voting_power`) + +[//]: # () + +[//]: # (Returns a user's vxASTRO balance at the current block.) + +[//]: # () + +[//]: # (Request:) + +[//]: # () + +[//]: # (```json) + +[//]: # ({) + +[//]: # ( "user_voting_power": {) + +[//]: # ( "user": "terra...") + +[//]: # ( }) + +[//]: # (}) + +[//]: # (```) + +[//]: # () + +[//]: # (Response:) + +[//]: # () + +[//]: # (```json) + +[//]: # ({) + +[//]: # ( "voting_power_response": {) + +[//]: # ( "voting_power": 10) + +[//]: # ( }) + +[//]: # (}) + +[//]: # (```) + +[//]: # () + +[//]: # (### `total_voting_power_at`) + +[//]: # () + +[//]: # (Returns the total vxASTRO supply at a specific timestamp (in seconds).) + +[//]: # () + +[//]: # (Request:) + +[//]: # () + +[//]: # (```json) + +[//]: # ({) + +[//]: # ( "total_voting_power_at": {) + +[//]: # ( "time": 1234567) + +[//]: # ( }) + +[//]: # (}) + +[//]: # (```) + +[//]: # () + +[//]: # (Response:) + +[//]: # () + +[//]: # (```json) + +[//]: # ({) + +[//]: # ( "voting_power_response": {) + +[//]: # ( "voting_power": 10) + +[//]: # ( }) + +[//]: # (}) + +[//]: # (```) + +[//]: # () + +[//]: # (### `user_voting_power_at`) + +[//]: # () + +[//]: # (Returns the user's vxASTRO balance at a specific timestamp (in seconds).) + +[//]: # () + +[//]: # (Request:) + +[//]: # () + +[//]: # (```json) + +[//]: # ({) + +[//]: # ( "user_voting_power_at": {) + +[//]: # ( "user": "terra...",) + +[//]: # ( "time": 1234567) + +[//]: # ( }) + +[//]: # (}) + +[//]: # (```) + +[//]: # () + +[//]: # (Response:) + +[//]: # () + +[//]: # (```json) + +[//]: # ({) + +[//]: # ( "voting_power_response": {) + +[//]: # ( "voting_power": 10) + +[//]: # ( }) + +[//]: # (}) + +[//]: # (```) + +[//]: # () + +[//]: # (### `lock_info`) + +[//]: # () + +[//]: # (Returns the information about a user's vxASTRO position.) + +[//]: # () + +[//]: # (Request:) + +[//]: # () + +[//]: # (```json) + +[//]: # ({) + +[//]: # ( "lock_info": {) + +[//]: # ( "user": "terra...") + +[//]: # ( }) + +[//]: # (}) + +[//]: # (```) + +[//]: # () + +[//]: # (Response:) + +[//]: # () + +[//]: # (```json) + +[//]: # ({) + +[//]: # ( "lock_info_response": {) + +[//]: # ( "amount": 10,) + +[//]: # ( "coefficient": 2.5,) + +[//]: # ( "start": 2600,) + +[//]: # ( "end": 2704) + +[//]: # ( }) + +[//]: # (}) + +[//]: # (```) + +[//]: # () + +[//]: # (### `config`) + +[//]: # () + +[//]: # (Returns the contract's config.) + +[//]: # () + +[//]: # (```json) + +[//]: # ({) + +[//]: # ( "config_response": {) + +[//]: # ( "owner": "terra...",) + +[//]: # ( "deposit_token_addr" : "terra...") + +[//]: # ( }) + +[//]: # (}) + +[//]: # (```) + +[//]: # () + +[//]: # (### `blacklisted_voters`) + +[//]: # () + +[//]: # (Returns blacklisted voters.) + +[//]: # () + +[//]: # (```json) + +[//]: # ({) + +[//]: # ( "blacklisted_voters": {) + +[//]: # ( "start_after": "terra...",) + +[//]: # ( "limit": 5) + +[//]: # ( }) + +[//]: # (}) + +[//]: # (```) + +[//]: # () + +[//]: # (### `check_voters_are_blacklisted`) + +[//]: # () + +[//]: # (Checks if specified addresses are blacklisted) + +[//]: # () + +[//]: # (```json) + +[//]: # ({) + +[//]: # ( "check_voters_are_blacklisted": {) + +[//]: # ( "voters": ["terra...", "terra..."]) + +[//]: # ( }) + +[//]: # (}) + +[//]: # (```) \ No newline at end of file diff --git a/contracts/voting_escrow/examples/schema.rs b/contracts/voting_escrow/examples/voting_escrow_schema.rs similarity index 79% rename from contracts/voting_escrow/examples/schema.rs rename to contracts/voting_escrow/examples/voting_escrow_schema.rs index df00b578..f490ea49 100644 --- a/contracts/voting_escrow/examples/schema.rs +++ b/contracts/voting_escrow/examples/voting_escrow_schema.rs @@ -1,4 +1,4 @@ -use astroport_governance::voting_escrow::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use astroport_governance::voting_escrow::{ExecuteMsg, InstantiateMsg, QueryMsg}; use cosmwasm_schema::write_api; fn main() { @@ -6,6 +6,5 @@ fn main() { instantiate: InstantiateMsg, query: QueryMsg, execute: ExecuteMsg, - migrate: MigrateMsg, } } diff --git a/contracts/voting_escrow/src/contract.rs b/contracts/voting_escrow/src/contract.rs index b63f2af3..b442ba79 100644 --- a/contracts/voting_escrow/src/contract.rs +++ b/contracts/voting_escrow/src/contract.rs @@ -1,44 +1,28 @@ -use crate::astroport; -use astroport::common::{claim_ownership, drop_ownership_proposal, propose_new_owner}; -use astroport_governance::astroport::DecimalCheckedOps; - +use astroport::asset::{addr_opt_validate, validate_native_denom}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - attr, from_json, to_json_binary, Addr, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, - Response, StdError, StdResult, Storage, Uint128, WasmMsg, + attr, coins, ensure, to_json_binary, BankMsg, Binary, Deps, DepsMut, Env, MessageInfo, + Response, StdError, StdResult, Uint128, }; use cw2::set_contract_version; -use cw20::{ - BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg, Cw20ReceiveMsg, Logo, LogoInfo, - MarketingInfoResponse, MinterResponse, TokenInfoResponse, -}; +use cw20::{Logo, LogoInfo, MarketingInfoResponse, TokenInfoResponse}; use cw20_base::contract::{ execute_update_marketing, execute_upload_logo, query_download_logo, query_marketing_info, }; use cw20_base::state::{MinterData, TokenInfo, LOGO, MARKETING_INFO, TOKEN_INFO}; +use cw_utils::must_pay; -use crate::astroport::asset::addr_opt_validate; -use crate::astroport::common::validate_addresses; -use astroport_governance::utils::{get_period, get_periods_count, EPOCH_START, WEEK}; use astroport_governance::voting_escrow::{ - BlacklistedVotersResponse, ConfigResponse, Cw20HookMsg, ExecuteMsg, InstantiateMsg, - LockInfoResponse, MigrateMsg, QueryMsg, VotingPowerResponse, DEFAULT_LIMIT, MAX_LIMIT, + Config, ExecuteMsg, InstantiateMsg, LockInfoResponse, QueryMsg, }; use crate::error::ContractError; use crate::marketing_validation::{validate_marketing_info, validate_whitelist_links}; -use crate::state::{ - Config, Lock, Point, BLACKLIST, CONFIG, HISTORY, LAST_SLOPE_CHANGE, LOCKED, OWNERSHIP_PROPOSAL, -}; -use crate::utils::{ - adjust_vp_and_slope, blacklist_check, calc_coefficient, calc_voting_power, - cancel_scheduled_slope, fetch_last_checkpoint, fetch_slope_changes, schedule_slope_change, - time_limits_check, xastro_token_check, -}; +use crate::state::{get_total_vp, Lock, CONFIG}; /// Contract name that is used for migration. -const CONTRACT_NAME: &str = "astro-voting-escrow"; +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); /// Contract version that is used for migration. const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -51,43 +35,16 @@ pub fn instantiate( msg: InstantiateMsg, ) -> Result { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - let deposit_token_addr = deps.api.addr_validate(&msg.deposit_token_addr)?; - - let xastro_minter_resp: MinterResponse = deps - .querier - .query_wasm_smart(&deposit_token_addr, &Cw20QueryMsg::Minter {})?; - let staking_config: crate::astroport::staking::ConfigResponse = deps.querier.query_wasm_smart( - &xastro_minter_resp.minter, - &crate::astroport::staking::QueryMsg::Config {}, - )?; + validate_native_denom(&msg.deposit_denom)?; validate_whitelist_links(&msg.logo_urls_whitelist)?; - let guardian_addr = addr_opt_validate(deps.api, &msg.guardian_addr)?; let config = Config { - owner: deps.api.addr_validate(&msg.owner)?, - guardian_addr, - deposit_token_addr, - astro_addr: staking_config.deposit_token_addr, - xastro_staking_addr: deps.api.addr_validate(&xastro_minter_resp.minter)?, + deposit_denom: msg.deposit_denom.clone(), logo_urls_whitelist: msg.logo_urls_whitelist.clone(), }; CONFIG.save(deps.storage, &config)?; - let cur_period = get_period(env.block.time.seconds())?; - let point = Point { - power: Uint128::zero(), - start: cur_period, - end: 0, - slope: Default::default(), - }; - HISTORY.save( - deps.storage, - (env.contract.address.clone(), cur_period), - &point, - )?; - BLACKLIST.save(deps.storage, &vec![])?; - if let Some(marketing) = msg.marketing { if msg.logo_urls_whitelist.is_empty() { return Err(StdError::generic_err("Logo URLs whitelist can not be empty").into()); @@ -159,46 +116,58 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { - ExecuteMsg::ExtendLockTime { time } => extend_lock_time(deps, env, info, time), - ExecuteMsg::Receive(msg) => receive_cw20(deps, env, info, msg), - ExecuteMsg::Withdraw {} => withdraw(deps, env, info), - ExecuteMsg::ProposeNewOwner { - new_owner, - expires_in, - } => { + ExecuteMsg::Lock { receiver } => { let config = CONFIG.load(deps.storage)?; - propose_new_owner( - deps, - info, - env, - new_owner, - expires_in, - config.owner, - OWNERSHIP_PROPOSAL, - ) - .map_err(Into::into) + let deposit = must_pay(&info, &config.deposit_denom)?; + let receiver = addr_opt_validate(deps.api, &receiver)?.unwrap_or(info.sender); + let block_ts = env.block.time.seconds(); + + let mut position = Lock::load(deps.storage, block_ts, &receiver)?; + position.lock(deps.storage, deposit)?; + + Ok(Response::default().add_attributes([ + attr("action", "lock"), + attr("receiver", receiver), + attr("deposit_amount", deposit), + attr("new_lock_amount", position.amount), + ])) + } + ExecuteMsg::Unlock {} => { + let mut position = Lock::load(deps.storage, env.block.time.seconds(), &info.sender)?; + let unlock_time = position.unlock(deps.storage)?; + + // TODO: kick user from generator controller votes + + Ok(Response::default().add_attributes([ + attr("action", "unlock"), + attr("receiver", info.sender), + attr("unlocked_amount", position.amount), + attr("unlock_time", unlock_time.to_string()), + ])) } - ExecuteMsg::DropOwnershipProposal {} => { - let config: Config = CONFIG.load(deps.storage)?; + ExecuteMsg::Relock {} => { + let mut position = Lock::load(deps.storage, env.block.time.seconds(), &info.sender)?; + position.relock(deps.storage)?; - drop_ownership_proposal(deps, info, config.owner, OWNERSHIP_PROPOSAL) - .map_err(Into::into) + Ok(Response::default() + .add_attributes([attr("action", "relock"), attr("receiver", info.sender)])) } - ExecuteMsg::ClaimOwnership {} => { - claim_ownership(deps, info, env, OWNERSHIP_PROPOSAL, |deps, new_owner| { - CONFIG - .update::<_, StdError>(deps.storage, |mut v| { - v.owner = new_owner; - Ok(v) - }) - .map(|_| ()) - }) - .map_err(Into::into) + ExecuteMsg::Withdraw {} => { + let mut position = Lock::load(deps.storage, env.block.time.seconds(), &info.sender)?; + let config = CONFIG.load(deps.storage)?; + let amount = position.withdraw(deps.storage)?; + + let send_msg = BankMsg::Send { + to_address: info.sender.to_string(), + amount: coins(amount.u128(), &config.deposit_denom), + }; + + Ok(Response::new().add_message(send_msg).add_attributes([ + attr("action", "withdraw"), + attr("receiver", info.sender), + attr("withdrawn_amount", amount), + ])) } - ExecuteMsg::UpdateBlacklist { - append_addrs, - remove_addrs, - } => update_blacklist(deps, env, info, append_addrs, remove_addrs), ExecuteMsg::UpdateMarketing { project, description, @@ -214,500 +183,22 @@ pub fn execute( execute_upload_logo(deps, env, info, logo).map_err(Into::into) } ExecuteMsg::SetLogoUrlsWhitelist { whitelist } => { - let mut config = CONFIG.load(deps.storage)?; let marketing_info = MARKETING_INFO.load(deps.storage)?; - if info.sender != config.owner && Some(info.sender) != marketing_info.marketing { - Err(ContractError::Unauthorized {}) - } else { - validate_whitelist_links(&whitelist)?; - config.logo_urls_whitelist = whitelist; - CONFIG.save(deps.storage, &config)?; - Ok(Response::default().add_attribute("action", "set_logo_urls_whitelist")) - } - } - ExecuteMsg::UpdateConfig { new_guardian } => { - execute_update_config(deps, info, new_guardian) - } - } -} - -/// Checkpoint the total voting power (total supply of vxASTRO). -/// This function fetches the last available vxASTRO checkpoint, recalculates passed periods since the checkpoint and until now, -/// applies slope changes and saves all recalculated periods in [`HISTORY`]. -/// -/// * **add_voting_power** amount of vxASTRO to add to the total. -/// -/// * **reduce_power** amount of vxASTRO to subtract from the total. -/// -/// * **old_slope** old slope applied to the total voting power (vxASTRO supply). -/// -/// * **new_slope** new slope to be applied to the total voting power (vxASTRO supply). -fn checkpoint_total( - storage: &mut dyn Storage, - env: Env, - add_voting_power: Option, - reduce_power: Option, - old_slope: Uint128, - new_slope: Uint128, -) -> StdResult<()> { - let cur_period = get_period(env.block.time.seconds())?; - let cur_period_key = cur_period; - let contract_addr = env.contract.address; - let add_voting_power = add_voting_power.unwrap_or_default(); - - // Get last checkpoint - let last_checkpoint = fetch_last_checkpoint(storage, &contract_addr, cur_period_key)?; - let new_point = if let Some((_, mut point)) = last_checkpoint { - let last_slope_change = LAST_SLOPE_CHANGE.may_load(storage)?.unwrap_or(0); - if last_slope_change < cur_period { - let scheduled_slope_changes = - fetch_slope_changes(storage, last_slope_change, cur_period)?; - // Recalculating passed points - for (recalc_period, scheduled_change) in scheduled_slope_changes { - point = Point { - power: calc_voting_power(&point, recalc_period), - start: recalc_period, - slope: point.slope - scheduled_change, - ..point - }; - HISTORY.save(storage, (contract_addr.clone(), recalc_period), &point)? - } - - LAST_SLOPE_CHANGE.save(storage, &cur_period)? - } - - let new_power = (calc_voting_power(&point, cur_period) + add_voting_power) - .saturating_sub(reduce_power.unwrap_or_default()); - - Point { - power: new_power, - slope: point.slope - old_slope + new_slope, - start: cur_period, - ..point - } - } else { - Point { - power: add_voting_power, - slope: new_slope, - start: cur_period, - end: 0, // we don't use 'end' in total voting power calculations - } - }; - HISTORY.save(storage, (contract_addr, cur_period_key), &new_point) -} - -/// Checkpoint a user's voting power (vxASTRO balance). -/// This function fetches the user's last available checkpoint, calculates the user's current voting power, applies slope changes based on -/// `add_amount` and `new_end` parameters, schedules slope changes for total voting power and saves the new checkpoint for the current -/// period in [`HISTORY`] (using the user's address). -/// If a user already checkpointed themselves for the current period, then this function uses the current checkpoint as the latest -/// available one. -/// -/// * **addr** staker for which we checkpoint the voting power. -/// -/// * **add_amount** amount of vxASTRO to add to the staker's balance. -/// -/// * **new_end** new lock time for the staker's vxASTRO position. -fn checkpoint( - deps: DepsMut, - env: Env, - addr: Addr, - add_amount: Option, - new_end: Option, -) -> StdResult<()> { - let cur_period = get_period(env.block.time.seconds())?; - let cur_period_key = cur_period; - let add_amount = add_amount.unwrap_or_default(); - let mut old_slope = Default::default(); - let mut add_voting_power = Uint128::zero(); - - // Get the last user checkpoint - let last_checkpoint = fetch_last_checkpoint(deps.storage, &addr, cur_period_key)?; - let new_point = if let Some((_, point)) = last_checkpoint { - let end = new_end.unwrap_or(point.end); - let dt = end.saturating_sub(cur_period); - let current_power = calc_voting_power(&point, cur_period); - let new_slope = if dt != 0 { - if end > point.end && add_amount.is_zero() { - // This is extend_lock_time. Recalculating user's voting power - let mut lock = LOCKED.load(deps.storage, addr.clone())?; - let mut new_voting_power = calc_coefficient(dt).checked_mul_uint128(lock.amount)?; - let slope = adjust_vp_and_slope(&mut new_voting_power, dt)?; - // new_voting_power should always be >= current_power. saturating_sub is used for extra safety - add_voting_power = new_voting_power.saturating_sub(current_power); - lock.last_extend_lock_period = cur_period; - LOCKED.save(deps.storage, addr.clone(), &lock, env.block.height)?; - slope - } else { - // This is an increase in the user's lock amount - let raw_add_voting_power = calc_coefficient(dt).checked_mul_uint128(add_amount)?; - let mut new_voting_power = current_power.checked_add(raw_add_voting_power)?; - let slope = adjust_vp_and_slope(&mut new_voting_power, dt)?; - // new_voting_power should always be >= current_power. saturating_sub is used for extra safety - add_voting_power = new_voting_power.saturating_sub(current_power); - slope - } - } else { - Uint128::zero() - }; - - // Cancel the previously scheduled slope change - cancel_scheduled_slope(deps.storage, point.slope, point.end)?; - - // We need to subtract the slope point from the total voting power slope - old_slope = point.slope; - - Point { - power: current_power + add_voting_power, - slope: new_slope, - start: cur_period, - end, - } - } else { - // This error can't happen since this if-branch is intended for checkpoint creation - let end = - new_end.ok_or_else(|| StdError::generic_err("Checkpoint initialization error"))?; - let dt = end - cur_period; - add_voting_power = calc_coefficient(dt).checked_mul_uint128(add_amount)?; - let slope = adjust_vp_and_slope(&mut add_voting_power, dt)?; - Point { - power: add_voting_power, - slope, - start: cur_period, - end, - } - }; - - // Schedule a slope change - schedule_slope_change(deps.storage, new_point.slope, new_point.end)?; - - HISTORY.save(deps.storage, (addr, cur_period_key), &new_point)?; - checkpoint_total( - deps.storage, - env, - Some(add_voting_power), - None, - old_slope, - new_point.slope, - ) -} - -/// Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template. -/// -/// * **cw20_msg** CW20 message to process. -fn receive_cw20( - deps: DepsMut, - env: Env, - info: MessageInfo, - cw20_msg: Cw20ReceiveMsg, -) -> Result { - xastro_token_check(deps.storage, info.sender)?; - let sender = Addr::unchecked(cw20_msg.sender); - blacklist_check(deps.storage, &sender)?; - - match from_json(&cw20_msg.msg)? { - Cw20HookMsg::CreateLock { time } => create_lock(deps, env, sender, cw20_msg.amount, time), - Cw20HookMsg::ExtendLockAmount {} => deposit_for(deps, env, cw20_msg.amount, sender), - Cw20HookMsg::DepositFor { user } => { - let addr = deps.api.addr_validate(&user)?; - blacklist_check(deps.storage, &addr)?; - deposit_for(deps, env, cw20_msg.amount, addr) - } - } -} - -/// Creates a lock for the user that lasts for the specified time duration (in seconds). -/// Checks that the user is locking xASTRO tokens. -/// Checks that the lock time is within [`WEEK`]..[`MAX_LOCK_TIME`]. -/// Creates a lock if it doesn't exist and triggers a [`checkpoint`] for the staker. -/// If a lock already exists, then a [`ContractError`] is returned. -/// -/// * **user** staker for which we create a lock position. -/// -/// * **amount** amount of xASTRO deposited in the lock position. -/// -/// * **time** duration of the lock. -fn create_lock( - deps: DepsMut, - env: Env, - user: Addr, - amount: Uint128, - time: u64, -) -> Result { - time_limits_check(time)?; - - let block_period = get_period(env.block.time.seconds())?; - let end = block_period + get_periods_count(time); - - LOCKED.update(deps.storage, user.clone(), env.block.height, |lock_opt| { - if lock_opt.is_some() && !lock_opt.unwrap().amount.is_zero() { - return Err(ContractError::LockAlreadyExists {}); - } - Ok(Lock { - amount, - start: block_period, - end, - last_extend_lock_period: block_period, - }) - })?; - - checkpoint(deps, env, user, Some(amount), Some(end))?; - - Ok(Response::default().add_attribute("action", "create_lock")) -} - -/// Deposits an 'amount' of xASTRO tokens into 'user''s lock. -/// Checks that the user is transferring and locking xASTRO. -/// Triggers a [`checkpoint`] for the user. -/// If the user does not have a lock, then a [`ContractError`] is returned. -/// -/// * **amount** amount of xASTRO to deposit. -/// -/// * **user** user who's lock amount will increase. -fn deposit_for( - deps: DepsMut, - env: Env, - amount: Uint128, - user: Addr, -) -> Result { - LOCKED.update( - deps.storage, - user.clone(), - env.block.height, - |lock_opt| match lock_opt { - Some(mut lock) if !lock.amount.is_zero() => { - if lock.end <= get_period(env.block.time.seconds())? { - Err(ContractError::LockExpired {}) - } else { - lock.amount += amount; - Ok(lock) - } - } - _ => Err(ContractError::LockDoesNotExist {}), - }, - )?; - checkpoint(deps, env, user, Some(amount), None)?; - - Ok(Response::default().add_attribute("action", "deposit_for")) -} - -/// Withdraws the whole amount of locked xASTRO from a specific user lock. -/// If the user lock doesn't exist or if it has not yet expired, then a [`ContractError`] is returned. -fn withdraw(deps: DepsMut, env: Env, info: MessageInfo) -> Result { - let sender = info.sender; - // 'LockDoesNotExist' is thrown either when a lock does not exist in LOCKED or when a lock exists but lock.amount == 0 - let mut lock = LOCKED - .may_load(deps.storage, sender.clone())? - .filter(|lock| !lock.amount.is_zero()) - .ok_or(ContractError::LockDoesNotExist {})?; - - let cur_period = get_period(env.block.time.seconds())?; - if lock.end > cur_period { - Err(ContractError::LockHasNotExpired {}) - } else { - let config = CONFIG.load(deps.storage)?; - let transfer_msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: config.deposit_token_addr.to_string(), - msg: to_json_binary(&Cw20ExecuteMsg::Transfer { - recipient: sender.to_string(), - amount: lock.amount, - })?, - funds: vec![], - }); - lock.amount = Uint128::zero(); - LOCKED.save(deps.storage, sender.clone(), &lock, env.block.height)?; - - // We need to checkpoint and eliminate the slope influence on a future lock - HISTORY.save( - deps.storage, - (sender, cur_period), - &Point { - power: Uint128::zero(), - start: cur_period, - end: cur_period, - slope: Default::default(), - }, - )?; - - Ok(Response::default() - .add_message(transfer_msg) - .add_attribute("action", "withdraw")) - } -} - -/// Increase the current lock time for a staker by a specified time period. -/// Evaluates that the `time` is within [`WEEK`]..[`MAX_LOCK_TIME`] -/// and then it triggers a [`checkpoint`]. -/// If the user lock doesn't exist or if it expired, then a [`ContractError`] is returned. -/// -/// ## Note -/// The time is added to the lock's `end`. -/// For example, at period 0, the user has their xASTRO locked for 3 weeks. -/// In 1 week, they increase their lock time by 10 weeks, thus the unlock period becomes 13 weeks. -/// -/// * **time** increase in lock time applied to the staker's position. -fn extend_lock_time( - deps: DepsMut, - env: Env, - info: MessageInfo, - time: u64, -) -> Result { - let user = info.sender; - blacklist_check(deps.storage, &user)?; - let mut lock = LOCKED - .may_load(deps.storage, user.clone())? - .filter(|lock| !lock.amount.is_zero()) - .ok_or(ContractError::LockDoesNotExist {})?; - - // Disable the ability to extend the lock time by less than a week - time_limits_check(time)?; - - if lock.end <= get_period(env.block.time.seconds())? { - return Err(ContractError::LockExpired {}); - }; - - // Should not exceed MAX_LOCK_TIME - time_limits_check(EPOCH_START + lock.end * WEEK + time - env.block.time.seconds())?; - lock.end += get_periods_count(time); - LOCKED.save(deps.storage, user.clone(), &lock, env.block.height)?; - - checkpoint(deps, env, user, None, Some(lock.end))?; - - Ok(Response::default().add_attribute("action", "extend_lock_time")) -} - -/// Update the staker blacklist. Whitelists addresses specified in 'remove_addrs' -/// and blacklists new addresses specified in 'append_addrs'. Nullifies staker voting power and -/// cancels their contribution in the total voting power (total vxASTRO supply). -/// -/// * **append_addrs** array of addresses to blacklist. -/// -/// * **remove_addrs** array of addresses to whitelist. -fn update_blacklist( - mut deps: DepsMut, - env: Env, - info: MessageInfo, - append_addrs: Option>, - remove_addrs: Option>, -) -> Result { - let config = CONFIG.load(deps.storage)?; - // Permission check - if info.sender != config.owner && Some(info.sender) != config.guardian_addr { - return Err(ContractError::Unauthorized {}); - } - let append_addrs = append_addrs.unwrap_or_default(); - let remove_addrs = remove_addrs.unwrap_or_default(); - let blacklist = BLACKLIST.load(deps.storage)?; - let append: Vec<_> = validate_addresses(deps.api, &append_addrs)? - .into_iter() - .filter(|addr| !blacklist.contains(addr)) - .collect(); - let remove: Vec<_> = validate_addresses(deps.api, &remove_addrs)? - .into_iter() - .filter(|addr| blacklist.contains(addr)) - .collect(); - - if append.is_empty() && remove.is_empty() { - return Err(StdError::generic_err("Append and remove arrays are empty").into()); - } - - let cur_period = get_period(env.block.time.seconds())?; - let cur_period_key = cur_period; - let mut reduce_total_vp = Uint128::zero(); // accumulator for decreasing total voting power - let mut old_slopes = Uint128::zero(); // accumulator for old slopes - for addr in append.iter() { - let last_checkpoint = fetch_last_checkpoint(deps.storage, addr, cur_period_key)?; - if let Some((_, point)) = last_checkpoint { - // We need to checkpoint with zero power and zero slope - HISTORY.save( - deps.storage, - (addr.clone(), cur_period_key), - &Point { - power: Uint128::zero(), - slope: Default::default(), - start: cur_period, - end: cur_period, - }, - )?; - - let cur_power = calc_voting_power(&point, cur_period); - // User's contribution is already zero. Skipping them - if cur_power.is_zero() { - continue; - } - - // User's contribution in the total voting power calculation - reduce_total_vp += cur_power; - old_slopes += point.slope; - cancel_scheduled_slope(deps.storage, point.slope, point.end)?; - } - } + ensure!( + Some(info.sender) == marketing_info.marketing, + ContractError::Unauthorized {} + ); - if !reduce_total_vp.is_zero() || !old_slopes.is_zero() { - // Trigger a total voting power recalculation - checkpoint_total( - deps.storage, - env.clone(), - None, - Some(reduce_total_vp), - old_slopes, - Default::default(), - )?; - } + CONFIG.update::<_, ContractError>(deps.storage, |mut config| { + validate_whitelist_links(&whitelist)?; + config.logo_urls_whitelist = whitelist; + Ok(config) + })?; - for addr in remove.iter() { - let lock_opt = LOCKED.may_load(deps.storage, addr.clone())?; - if let Some(Lock { amount, end, .. }) = lock_opt { - checkpoint( - deps.branch(), - env.clone(), - addr.clone(), - Some(amount), - Some(end), - )?; + Ok(Response::default().add_attribute("action", "set_logo_urls_whitelist")) } } - - BLACKLIST.update(deps.storage, |blacklist| -> StdResult> { - let mut updated_blacklist: Vec<_> = blacklist - .into_iter() - .filter(|addr| !remove.contains(addr)) - .collect(); - updated_blacklist.extend(append); - Ok(updated_blacklist) - })?; - - let mut attrs = vec![attr("action", "update_blacklist")]; - if !append_addrs.is_empty() { - attrs.push(attr("added_addresses", append_addrs.join(","))) - } - if !remove_addrs.is_empty() { - attrs.push(attr("removed_addresses", remove_addrs.join(","))) - } - - Ok(Response::default().add_attributes(attrs)) -} - -/// Updates contracts' guardian address. -fn execute_update_config( - deps: DepsMut, - info: MessageInfo, - new_guardian: Option, -) -> Result { - let mut cfg = CONFIG.load(deps.storage)?; - - if cfg.owner != info.sender { - return Err(ContractError::Unauthorized {}); - } - - if let Some(new_guardian) = new_guardian { - cfg.guardian_addr = Some(deps.api.addr_validate(&new_guardian)?); - } - - CONFIG.save(deps.storage, &cfg)?; - - Ok(Response::default().add_attribute("action", "execute_update_config")) } /// Expose available contract queries. @@ -725,271 +216,47 @@ fn execute_update_config( #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::CheckVotersAreBlacklisted { voters } => { - to_json_binary(&check_voters_are_blacklisted(deps, voters)?) + QueryMsg::TotalVotingPower { time } => { + to_json_binary(&get_total_vp(deps.storage, env.block.time.seconds(), time)?) } - QueryMsg::BlacklistedVoters { start_after, limit } => { - to_json_binary(&get_blacklisted_voters(deps, start_after, limit)?) + QueryMsg::UserVotingPower { user, time } => { + to_json_binary(&query_user_voting_power(deps, env, user, time)?) } - QueryMsg::TotalVotingPower {} => to_json_binary(&get_total_voting_power(deps, env, None)?), - QueryMsg::UserVotingPower { user } => { - to_json_binary(&get_user_voting_power(deps, env, user, None)?) + QueryMsg::LockInfo { user } => { + let user = deps.api.addr_validate(&user)?; + let lock_info_resp: LockInfoResponse = + Lock::load(deps.storage, env.block.time.seconds(), &user)?.into(); + to_json_binary(&lock_info_resp) } - QueryMsg::TotalVotingPowerAt { time } => { - to_json_binary(&get_total_voting_power(deps, env, Some(time))?) + QueryMsg::Config {} => to_json_binary(&CONFIG.load(deps.storage)?), + QueryMsg::Balance { address } => { + to_json_binary(&query_user_voting_power(deps, env, address, None)?) } - QueryMsg::TotalVotingPowerAtPeriod { period } => { - to_json_binary(&get_total_voting_power_at_period(deps, env, period)?) - } - QueryMsg::UserVotingPowerAt { user, time } => { - to_json_binary(&get_user_voting_power(deps, env, user, Some(time))?) - } - QueryMsg::UserVotingPowerAtPeriod { user, period } => { - to_json_binary(&get_user_voting_power_at_period(deps, user, period)?) - } - QueryMsg::LockInfo { user } => to_json_binary(&get_user_lock_info(deps, env, user)?), - QueryMsg::UserDepositAtHeight { user, height } => { - to_json_binary(&get_user_deposit_at_height(deps, user, height)?) - } - QueryMsg::Config {} => { - let config = CONFIG.load(deps.storage)?; - to_json_binary(&ConfigResponse { - owner: config.owner.to_string(), - guardian_addr: config.guardian_addr, - deposit_token_addr: config.deposit_token_addr.to_string(), - astro_addr: config.astro_addr.to_string(), - xastro_staking_addr: config.xastro_staking_addr.to_string(), - logo_urls_whitelist: config.logo_urls_whitelist, - }) - } - QueryMsg::Balance { address } => to_json_binary(&get_user_balance(deps, env, address)?), QueryMsg::TokenInfo {} => to_json_binary(&query_token_info(deps, env)?), QueryMsg::MarketingInfo {} => to_json_binary(&query_marketing_info(deps)?), QueryMsg::DownloadLogo {} => to_json_binary(&query_download_logo(deps)?), } } -/// Checks if specified addresses are blacklisted. -/// -/// * **voters** addresses to check if they are blacklisted. -pub fn check_voters_are_blacklisted( - deps: Deps, - voters: Vec, -) -> StdResult { - let black_list = BLACKLIST.load(deps.storage)?; - - for voter in voters { - let voter_addr = deps.api.addr_validate(voter.as_str())?; - if !black_list.contains(&voter_addr) { - return Ok(BlacklistedVotersResponse::VotersNotBlacklisted { voter }); - } - } - - Ok(BlacklistedVotersResponse::VotersBlacklisted {}) -} - -/// Returns a list of blacklisted voters. -/// -/// * **start_after** is an optional field that specifies whether the function should return -/// a list of voters starting from a specific address onward. -/// -/// * **limit** max amount of voters addresses to return. -pub fn get_blacklisted_voters( - deps: Deps, - start_after: Option, - limit: Option, -) -> StdResult> { - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let mut black_list = BLACKLIST.load(deps.storage)?; - - if black_list.is_empty() { - return Ok(vec![]); - } - - black_list.sort(); - - let mut start_index = Default::default(); - if let Some(start_after) = start_after { - let start_addr = deps.api.addr_validate(start_after.as_str())?; - start_index = black_list - .iter() - .position(|addr| *addr == start_addr) - .ok_or_else(|| { - StdError::generic_err(format!( - "The {} address is not blacklisted", - start_addr.as_str() - )) - })? - + 1; // start from the next element of the slice - } - - // validate end index of the slice - let end_index = (start_index + limit).min(black_list.len()); - - Ok(black_list[start_index..end_index].to_vec()) -} - -/// Return a user's lock information. -/// -/// * **user** user for which we return lock information. -fn get_user_lock_info(deps: Deps, env: Env, user: String) -> StdResult { - let addr = deps.api.addr_validate(&user)?; - if let Some(lock) = LOCKED.may_load(deps.storage, addr.clone())? { - let cur_period = get_period(env.block.time.seconds())?; - let slope = fetch_last_checkpoint(deps.storage, &addr, cur_period)? - .map(|(_, point)| point.slope) - .unwrap_or_default(); - let resp = LockInfoResponse { - amount: lock.amount, - coefficient: calc_coefficient(lock.end - lock.last_extend_lock_period), - start: lock.start, - end: lock.end, - slope, - }; - Ok(resp) - } else { - Err(StdError::generic_err("User is not found")) - } -} - -/// Return a user's staked xASTRO amount at a given block height. -/// -/// * **user** user for which we return lock information. -/// -/// * **block_height** block height at which we return the staked xASTRO amount. -fn get_user_deposit_at_height(deps: Deps, user: String, block_height: u64) -> StdResult { - let addr = deps.api.addr_validate(&user)?; - let locked_opt = LOCKED.may_load_at_height(deps.storage, addr, block_height)?; - if let Some(lock) = locked_opt { - Ok(lock.amount) - } else { - Ok(Uint128::zero()) - } -} - -/// Calculates a user's voting power at a given timestamp. -/// If time is None, then it calculates the user's voting power at the current block. -/// -/// * **user** user/staker for which we fetch the current voting power (vxASTRO balance). -/// -/// * **time** timestamp at which to fetch the user's voting power (vxASTRO balance). -fn get_user_voting_power( - deps: Deps, - env: Env, - user: String, - time: Option, -) -> StdResult { - let period = get_period(time.unwrap_or_else(|| env.block.time.seconds()))?; - get_user_voting_power_at_period(deps, user, period) -} - -/// Calculates a user's voting power at a given period number. -/// -/// * **user** user/staker for which we fetch the current voting power (vxASTRO balance). -/// -/// * **period** period number at which to fetch the user's voting power (vxASTRO balance). -fn get_user_voting_power_at_period( - deps: Deps, - user: String, - period: u64, -) -> StdResult { - let user = deps.api.addr_validate(&user)?; - let last_checkpoint = fetch_last_checkpoint(deps.storage, &user, period)?; - - if let Some(point) = last_checkpoint.map(|(_, point)| point) { - // The voting power point at the specified `time` was found - let voting_power = if point.start == period { - point.power - } else { - // The point before the intended period was found, thus we can calculate the user's voting power for the period we want - calc_voting_power(&point, period) - }; - Ok(VotingPowerResponse { voting_power }) - } else { - // User not found - Ok(VotingPowerResponse { - voting_power: Uint128::zero(), - }) - } -} - -/// Calculates a user's voting power at the current block. -/// -/// * **user** user/staker for which we fetch the current voting power (vxASTRO balance). -fn get_user_balance(deps: Deps, env: Env, user: String) -> StdResult { - let vp_response = get_user_voting_power(deps, env, user, None)?; - Ok(BalanceResponse { - balance: vp_response.voting_power, - }) -} - -/// Calculates the total voting power (total vxASTRO supply) at the given timestamp. -/// If `time` is None, then it calculates the total voting power at the current block. -/// -/// * **time** timestamp at which we fetch the total voting power (vxASTRO supply). -fn get_total_voting_power( - deps: Deps, - env: Env, - time: Option, -) -> StdResult { - let period = get_period(time.unwrap_or_else(|| env.block.time.seconds()))?; - get_total_voting_power_at_period(deps, env, period) -} - -/// Calculates the total voting power (total vxASTRO supply) at the given period number. -/// -/// * **period** period number at which we fetch the total voting power (vxASTRO supply). -fn get_total_voting_power_at_period( - deps: Deps, - env: Env, - period: u64, -) -> StdResult { - let last_checkpoint = fetch_last_checkpoint(deps.storage, &env.contract.address, period)?; - - let point = last_checkpoint.map_or( - Point { - power: Uint128::zero(), - start: period, - end: period, - slope: Default::default(), - }, - |(_, point)| point, - ); - - let voting_power = if point.start == period { - point.power - } else { - let scheduled_slope_changes = fetch_slope_changes(deps.storage, point.start, period)?; - let mut init_point = point; - for (recalc_period, scheduled_change) in scheduled_slope_changes { - init_point = Point { - power: calc_voting_power(&init_point, recalc_period), - start: recalc_period, - slope: init_point.slope - scheduled_change, - ..init_point - } - } - calc_voting_power(&init_point, period) - }; - - Ok(VotingPowerResponse { voting_power }) -} - /// Fetch the vxASTRO token information, such as the token name, symbol, decimals and total supply (total voting power). -fn query_token_info(deps: Deps, env: Env) -> StdResult { - let info = TOKEN_INFO.load(deps.storage)?; - let total_vp = get_total_voting_power(deps, env, None)?; +pub fn query_token_info(deps: Deps, env: Env) -> StdResult { + let token_info = TOKEN_INFO.load(deps.storage)?; let res = TokenInfoResponse { - name: info.name, - symbol: info.symbol, - decimals: info.decimals, - total_supply: total_vp.voting_power, + name: token_info.name, + symbol: token_info.symbol, + decimals: token_info.decimals, + total_supply: get_total_vp(deps.storage, env.block.time.seconds(), None)?, }; Ok(res) } -/// Manages contract migration. -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { - Err(ContractError::MigrationError {}) +pub fn query_user_voting_power( + deps: Deps, + env: Env, + address: String, + timestamp: Option, +) -> StdResult { + let address = deps.api.addr_validate(&address)?; + let position = Lock::load_at_ts(deps.storage, env.block.time.seconds(), &address, timestamp)?; + Ok(position.amount) } diff --git a/contracts/voting_escrow/src/error.rs b/contracts/voting_escrow/src/error.rs index 7e1b425d..e3115810 100644 --- a/contracts/voting_escrow/src/error.rs +++ b/contracts/voting_escrow/src/error.rs @@ -1,5 +1,6 @@ -use cosmwasm_std::StdError; -use cw20_base::ContractError as cw20baseError; +use cosmwasm_std::{OverflowError, StdError}; +use cw20_base::ContractError as CW20Error; +use cw_utils::PaymentError; use thiserror::Error; /// This enum describes vxASTRO contract errors @@ -9,32 +10,29 @@ pub enum ContractError { Std(#[from] StdError), #[error("{0}")] - Cw20Base(#[from] cw20baseError), + PaymentError(#[from] PaymentError), - #[error("Unauthorized")] - Unauthorized {}, - - #[error("Lock already exists")] - LockAlreadyExists {}, + #[error("{0}")] + OverflowError(#[from] OverflowError), - #[error("Lock does not exist")] - LockDoesNotExist {}, + #[error("{0}")] + Cw20Base(#[from] CW20Error), - #[error("Lock time must be within limits (week <= lock time < 2 years)")] - LockTimeLimitsError {}, + #[error("Unauthorized")] + Unauthorized {}, - #[error("The lock time has not yet expired")] - LockHasNotExpired {}, + #[error("Marketing info validation error: {0}")] + MarketingInfoValidationError(String), - #[error("The lock expired. Withdraw and create new lock")] - LockExpired {}, + #[error("No withdrawal balance available")] + ZeroBalance {}, - #[error("The {0} address is blacklisted")] - AddressBlacklisted(String), + #[error("Unlock period not expired. Expected: at {0}")] + UnlockPeriodNotExpired(u64), - #[error("Marketing info validation error: {0}")] - MarketingInfoValidationError(String), + #[error("Position is not in unlocking state")] + NotInUnlockingState {}, - #[error("Contract can't be migrated!")] - MigrationError {}, + #[error("Position is already unlocking. Consider relocking to lock more tokens")] + PositionUnlocking {}, } diff --git a/contracts/voting_escrow/src/lib.rs b/contracts/voting_escrow/src/lib.rs index 7a92a40d..6e591cfd 100644 --- a/contracts/voting_escrow/src/lib.rs +++ b/contracts/voting_escrow/src/lib.rs @@ -1,10 +1,5 @@ pub mod contract; pub mod state; -// During development this import could be replaced with another astroport version. -// However, in production, the astroport version should be the same for all contracts. -pub use astroport_governance::astroport; - pub mod error; -mod marketing_validation; -mod utils; +pub mod marketing_validation; diff --git a/contracts/voting_escrow/src/marketing_validation.rs b/contracts/voting_escrow/src/marketing_validation.rs index aea35004..ab700981 100644 --- a/contracts/voting_escrow/src/marketing_validation.rs +++ b/contracts/voting_escrow/src/marketing_validation.rs @@ -1,17 +1,16 @@ -use crate::error::ContractError; -use crate::error::ContractError::MarketingInfoValidationError; - use cosmwasm_std::StdError; use cw20::Logo; +use crate::error::ContractError; + const SAFE_TEXT_CHARS: &str = "!&?#()*+'-.,/\""; const SAFE_LINK_CHARS: &str = "-_:/?#@!$&()*+,;=.~[]'%"; -fn validate_text(text: &str, name: &str) -> Result<(), ContractError> { +pub fn validate_text(text: &str, name: &str) -> Result<(), ContractError> { if text.chars().any(|c| { !c.is_ascii_alphanumeric() && !c.is_ascii_whitespace() && !SAFE_TEXT_CHARS.contains(c) }) { - Err(MarketingInfoValidationError(format!( + Err(ContractError::MarketingInfoValidationError(format!( "{name} contains invalid characters: {text}" ))) } else { @@ -22,7 +21,7 @@ fn validate_text(text: &str, name: &str) -> Result<(), ContractError> { pub fn validate_whitelist_links(links: &[String]) -> Result<(), ContractError> { links.iter().try_for_each(|link| { if !link.ends_with('/') { - return Err(MarketingInfoValidationError(format!( + return Err(ContractError::MarketingInfoValidationError(format!( "Whitelist link should end with '/': {link}" ))); } @@ -41,13 +40,13 @@ pub fn validate_link(link: &String) -> Result<(), ContractError> { } } -fn check_link(link: &String, whitelisted_links: &[String]) -> Result<(), ContractError> { +pub fn check_link(link: &String, whitelisted_links: &[String]) -> Result<(), ContractError> { if validate_link(link).is_err() { - Err(MarketingInfoValidationError(format!( + Err(ContractError::MarketingInfoValidationError(format!( "Logo link is invalid: {link}" ))) } else if !whitelisted_links.iter().any(|wl| link.starts_with(wl)) { - Err(MarketingInfoValidationError(format!( + Err(ContractError::MarketingInfoValidationError(format!( "Logo link is not whitelisted: {link}" ))) } else { @@ -55,7 +54,7 @@ fn check_link(link: &String, whitelisted_links: &[String]) -> Result<(), Contrac } } -pub(crate) fn validate_marketing_info( +pub fn validate_marketing_info( project: Option<&String>, description: Option<&String>, logo: Option<&Logo>, diff --git a/contracts/voting_escrow/src/state.rs b/contracts/voting_escrow/src/state.rs index 6ceb3e7c..fa99c719 100644 --- a/contracts/voting_escrow/src/state.rs +++ b/contracts/voting_escrow/src/state.rs @@ -1,74 +1,157 @@ -use crate::astroport::common::OwnershipProposal; +use astroport::common::OwnershipProposal; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Uint128}; -use cw_storage_plus::{Item, Map, SnapshotMap, Strategy}; +use cosmwasm_std::{ensure, Addr, StdResult, Storage, Uint128}; +use cw_storage_plus::{Item, SnapshotItem, SnapshotMap, Strategy}; -/// This structure stores the main parameters for the voting escrow contract. -#[cw_serde] -pub struct Config { - /// Address that's allowed to change contract parameters - pub owner: Addr, - /// Address that can only blacklist vxASTRO stakers and remove their governance power - pub guardian_addr: Option, - /// The xASTRO token contract address - pub deposit_token_addr: Addr, - /// The address of $ASTRO - pub astro_addr: Addr, - /// The address of $xASTRO staking contract - pub xastro_staking_addr: Addr, - /// The list of whitelisted logo urls prefixes - pub logo_urls_whitelist: Vec, -} +use astroport_governance::voting_escrow::{Config, LockInfoResponse}; -/// This structure stores points along the checkpoint history for every vxASTRO staker. -#[cw_serde] -pub struct Point { - /// The staker's vxASTRO voting power - pub power: Uint128, - /// The start period when the staker's voting power start to decrease - pub start: u64, - /// The period when the lock should expire - pub end: u64, - /// Weekly voting power decay - pub slope: Uint128, +use crate::error::ContractError; + +pub const UNLOCK_PERIOD: u64 = 86400 * 14; // 2 weeks + +/// Stores the contract config at the given key +pub const CONFIG: Item = Item::new("config"); + +fn default_addr() -> Addr { + Addr::unchecked("") } -/// This structure stores data about the lockup position for a specific vxASTRO staker. #[cw_serde] pub struct Lock { /// The total amount of xASTRO tokens that were deposited in the vxASTRO position pub amount: Uint128, - /// The start period when the lock was created - pub start: u64, - /// The timestamp when the lock position expires - pub end: u64, - /// the last period when the lock's time was increased - pub last_extend_lock_period: u64, + /// The timestamp when a lock will be unlocked. None for positions in Locked state + pub end: Option, + /// NOTE: The fields below are not stored in the state, it is used only in the contract logic + #[serde(default = "default_addr", skip)] + pub user: Addr, + /// Current block timestamp. + #[serde(skip)] + pub block_time: u64, } -/// Stores the contract config at the given key -pub const CONFIG: Item = Item::new("config"); +impl Lock { + pub fn load_at_ts( + storage: &dyn Storage, + block_time: u64, + user: &Addr, + timestamp: Option, + ) -> StdResult { + match timestamp.unwrap_or(block_time) { + timestamp if timestamp == block_time => LOCKED.may_load(storage, &user), + timestamp => LOCKED.may_load_at_height(storage, &user, timestamp), + } + .map(|lock| { + lock.unwrap_or_else(|| Lock { + amount: Uint128::zero(), + end: None, + user: user.clone(), + block_time, + }) + }) + } + + pub fn load(storage: &dyn Storage, block_time: u64, user: &Addr) -> StdResult { + Self::load_at_ts(storage, block_time, user, None) + } + + pub fn lock( + &mut self, + storage: &mut dyn Storage, + amount: Uint128, + ) -> Result<(), ContractError> { + ensure!(self.end.is_none(), ContractError::PositionUnlocking {}); + + self.amount += amount; + LOCKED.save(storage, &self.user, &self, self.block_time)?; + TOTAL_POWER + .update(storage, self.block_time, |total| { + Ok(total.unwrap_or_default() + amount) + }) + .map(|_| ()) + } + + pub fn unlock(&mut self, storage: &mut dyn Storage) -> Result { + ensure!(!self.amount.is_zero(), ContractError::ZeroBalance {}); + ensure!(self.end.is_none(), ContractError::PositionUnlocking {}); + + let end = self.block_time + UNLOCK_PERIOD; + self.end = Some(end); + LOCKED.save(storage, &self.user, &self, self.block_time)?; + + // Remove user's voting power from the total + TOTAL_POWER.update(storage, self.block_time, |total| -> StdResult<_> { + Ok(total.unwrap_or_default().checked_sub(self.amount)?) + })?; + + Ok(end) + } -/// Stores all user lock history -pub const LOCKED: SnapshotMap = SnapshotMap::new( + pub fn relock(&mut self, storage: &mut dyn Storage) -> Result<(), ContractError> { + ensure!(self.end.is_some(), ContractError::NotInUnlockingState {}); + + self.end = None; + LOCKED.save(storage, &self.user, &self, self.block_time)?; + + // Add user's voting power back to the total + TOTAL_POWER + .update(storage, self.block_time, |total| { + Ok(total.unwrap_or_default() + self.amount) + }) + .map(|_| ()) + } + + pub fn withdraw(&mut self, storage: &mut dyn Storage) -> Result { + if let Some(end) = self.end { + ensure!( + self.block_time >= end, + ContractError::UnlockPeriodNotExpired(end) + ); + + LOCKED.remove(storage, &self.user, self.block_time)?; + + Ok(self.amount) + } else { + Err(ContractError::NotInUnlockingState {}) + } + } +} + +impl From for LockInfoResponse { + fn from(lock: Lock) -> Self { + LockInfoResponse { + amount: lock.amount, + end: lock.end, + } + } +} + +pub fn get_total_vp( + storage: &dyn Storage, + block_time: u64, + timestamp: Option, +) -> StdResult { + match timestamp.unwrap_or(block_time) { + timestamp if timestamp == block_time => TOTAL_POWER.may_load(storage), + timestamp => TOTAL_POWER.may_load_at_height(storage, timestamp), + } + .map(Option::unwrap_or_default) +} + +/// Stores historical balances for each account +pub const LOCKED: SnapshotMap<&Addr, Lock> = SnapshotMap::new( "locked", "locked__checkpoints", "locked__changelog", Strategy::EveryBlock, ); -/// Stores the checkpoint history for every staker (addr => period) -/// Total voting power checkpoints are stored using a (contract_addr => period) key -pub const HISTORY: Map<(Addr, u64), Point> = Map::new("history"); - -/// Scheduled slope changes per period (week) -pub const SLOPE_CHANGES: Map = Map::new("slope_changes"); - -/// Last period when a scheduled slope change was applied -pub const LAST_SLOPE_CHANGE: Item = Item::new("last_slope_change"); +pub const TOTAL_POWER: SnapshotItem = SnapshotItem::new( + "total_power", + "total_power__checkpoints", + "total_power__changelog", + Strategy::EveryBlock, +); /// Contains a proposal to change contract ownership pub const OWNERSHIP_PROPOSAL: Item = Item::new("ownership_proposal"); - -/// Contains blacklisted staker addresses -pub const BLACKLIST: Item> = Item::new("blacklist"); diff --git a/contracts/voting_escrow/src/utils.rs b/contracts/voting_escrow/src/utils.rs deleted file mode 100644 index 3468c05b..00000000 --- a/contracts/voting_escrow/src/utils.rs +++ /dev/null @@ -1,141 +0,0 @@ -use crate::error::ContractError; -use astroport_governance::utils::{get_periods_count, MAX_LOCK_TIME, WEEK}; - -use cosmwasm_std::{Addr, Decimal, Order, StdResult, Storage, Uint128}; -use cw_storage_plus::Bound; - -use crate::state::{Point, BLACKLIST, CONFIG, HISTORY, LAST_SLOPE_CHANGE, SLOPE_CHANGES}; - -/// Checks that a timestamp is within limits. -pub(crate) fn time_limits_check(time: u64) -> Result<(), ContractError> { - if !(WEEK..=MAX_LOCK_TIME).contains(&time) { - Err(ContractError::LockTimeLimitsError {}) - } else { - Ok(()) - } -} - -/// Checks that the sender is the xASTRO token. -pub(crate) fn xastro_token_check(storage: &dyn Storage, sender: Addr) -> Result<(), ContractError> { - let config = CONFIG.load(storage)?; - if sender != config.deposit_token_addr { - Err(ContractError::Unauthorized {}) - } else { - Ok(()) - } -} - -/// Checks if the blacklist contains a specific address. -pub(crate) fn blacklist_check(storage: &dyn Storage, addr: &Addr) -> Result<(), ContractError> { - let blacklist = BLACKLIST.load(storage)?; - if blacklist.contains(addr) { - Err(ContractError::AddressBlacklisted(addr.to_string())) - } else { - Ok(()) - } -} - -/// Adjusting voting power according to the slope. The maximum loss is 103/104 * 104 which is -/// 0.000103 vxASTRO. -pub(crate) fn adjust_vp_and_slope(vp: &mut Uint128, dt: u64) -> StdResult { - let slope = vp.checked_div(Uint128::from(dt))?; - *vp = slope * Uint128::from(dt); - Ok(slope) -} - -/// Main function used to calculate a user's voting power at a specific period as: previous_power - slope*(x - previous_x). -pub(crate) fn calc_voting_power(point: &Point, period: u64) -> Uint128 { - let shift = point - .slope - .checked_mul(Uint128::from(period - point.start)) - .unwrap_or_else(|_| Uint128::zero()); - point - .power - .checked_sub(shift) - .unwrap_or_else(|_| Uint128::zero()) -} - -/// Coefficient calculation where 0 [`WEEK`] is equal to 1 and [`MAX_LOCK_TIME`] is 2.5. -pub(crate) fn calc_coefficient(interval: u64) -> Decimal { - // coefficient = 1 + 1.5 * (end - start) / MAX_LOCK_TIME - Decimal::one() + Decimal::from_ratio(15_u64 * interval, get_periods_count(MAX_LOCK_TIME) * 10) -} - -/// Fetches the last checkpoint in [`HISTORY`] for the given address. -pub(crate) fn fetch_last_checkpoint( - storage: &dyn Storage, - addr: &Addr, - period_key: u64, -) -> StdResult> { - HISTORY - .prefix(addr.clone()) - .range( - storage, - None, - Some(Bound::inclusive(period_key)), - Order::Descending, - ) - .next() - .transpose() -} - -/// Cancels scheduled slope change of total voting power only if the given period is in future. -/// Removes scheduled slope change if it became zero. -pub(crate) fn cancel_scheduled_slope( - storage: &mut dyn Storage, - slope: Uint128, - period: u64, -) -> StdResult<()> { - let end_period_key = period; - let last_slope_change = LAST_SLOPE_CHANGE.may_load(storage)?.unwrap_or(0); - match SLOPE_CHANGES.may_load(storage, end_period_key)? { - // We do not need to schedule a slope change in the past - Some(old_scheduled_change) if period > last_slope_change => { - let new_slope = old_scheduled_change - slope; - if !new_slope.is_zero() { - SLOPE_CHANGES.save(storage, end_period_key, &(old_scheduled_change - slope)) - } else { - SLOPE_CHANGES.remove(storage, end_period_key); - Ok(()) - } - } - _ => Ok(()), - } -} - -/// Schedules slope change of total voting power in the given period. -pub(crate) fn schedule_slope_change( - storage: &mut dyn Storage, - slope: Uint128, - period: u64, -) -> StdResult<()> { - if !slope.is_zero() { - SLOPE_CHANGES - .update(storage, period, |slope_opt| -> StdResult { - if let Some(pslope) = slope_opt { - Ok(pslope + slope) - } else { - Ok(slope) - } - }) - .map(|_| ()) - } else { - Ok(()) - } -} - -/// Fetches all slope changes between `last_slope_change` and `period`. -pub(crate) fn fetch_slope_changes( - storage: &dyn Storage, - last_slope_change: u64, - period: u64, -) -> StdResult> { - SLOPE_CHANGES - .range( - storage, - Some(Bound::exclusive(last_slope_change)), - Some(Bound::inclusive(period)), - Order::Ascending, - ) - .collect() -} diff --git a/contracts/voting_escrow/tests/integration.rs b/contracts/voting_escrow/tests/integration.rs deleted file mode 100644 index a86a4c54..00000000 --- a/contracts/voting_escrow/tests/integration.rs +++ /dev/null @@ -1,1183 +0,0 @@ -use astroport::token as astro; -use cosmwasm_std::{attr, to_json_binary, Addr, Fraction, StdError, Uint128}; -use cw20::{Cw20ExecuteMsg, Logo, LogoInfo, MarketingInfoResponse, MinterResponse}; -use cw_multi_test::{next_block, ContractWrapper, Executor}; -use voting_escrow::astroport; - -use astroport_governance::utils::{get_period, MAX_LOCK_TIME, WEEK}; -use astroport_governance::voting_escrow::{ - ConfigResponse, Cw20HookMsg, ExecuteMsg, LockInfoResponse, QueryMsg, -}; - -use crate::test_utils::{mock_app, Helper, MULTIPLIER}; - -mod test_utils; - -#[test] -fn lock_unlock_logic() { - let mut router = mock_app(); - let router_ref = &mut router; - let owner = Addr::unchecked("owner"); - let helper = Helper::init(router_ref, owner); - - helper.mint_xastro(router_ref, "owner", 100); - - // Mint ASTRO, stake it and mint xASTRO - helper.mint_xastro(router_ref, "user", 100); - helper.check_xastro_balance(router_ref, "user", 100); - - // Create invalid vx position - let err = helper - .create_lock(router_ref, "user", WEEK - 1, 1f32) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Lock time must be within limits (week <= lock time < 2 years)" - ); - let err = helper - .create_lock(router_ref, "user", MAX_LOCK_TIME + 1, 1f32) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Lock time must be within limits (week <= lock time < 2 years)" - ); - let err = helper - .create_lock(router_ref, "user", WEEK, 101f32) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - format!( - "Cannot Sub with {} and {}", - 100 * MULTIPLIER, - 101 * MULTIPLIER - ) - ); - - // Try to increase the lock time for a position that doesn't exist - let err = helper - .extend_lock_time(router_ref, "user", MAX_LOCK_TIME) - .unwrap_err(); - assert_eq!(err.root_cause().to_string(), "Lock does not exist"); - - // Try to withdraw from a non-existent lock - let err = helper.withdraw(router_ref, "user").unwrap_err(); - assert_eq!(err.root_cause().to_string(), "Lock does not exist"); - - // Try to deposit more xASTRO in a position that does not already exist - let err = helper - .extend_lock_amount(router_ref, "user", 1f32) - .unwrap_err(); - assert_eq!(err.root_cause().to_string(), "Lock does not exist"); - - // Current total voting power is 0 - let vp = helper.query_total_vp(router_ref).unwrap(); - assert_eq!(vp, 0.0); - - // Create valid voting escrow lock - helper - .create_lock(router_ref, "user", WEEK * 2, 90f32) - .unwrap(); - // Check that 90 xASTRO were actually debited - helper.check_xastro_balance(router_ref, "user", 10); - helper.check_xastro_balance(router_ref, helper.voting_instance.as_str(), 90); - - // A user can have a single vxASTRO position - let err = helper - .create_lock(router_ref, "user", MAX_LOCK_TIME, 1f32) - .unwrap_err(); - assert_eq!(err.root_cause().to_string(), "Lock already exists"); - - // Try to increase the lock time by less than a week - let err = helper - .extend_lock_time(router_ref, "user", 86400) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Lock time must be within limits (week <= lock time < 2 years)" - ); - - // Try to exceed MAX_LOCK_TIME - // We locked for 2 weeks so increasing by MAX_LOCK_TIME - week is impossible - let err = helper - .extend_lock_time(router_ref, "user", MAX_LOCK_TIME - WEEK) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Lock time must be within limits (week <= lock time < 2 years)" - ); - - // Add more xASTRO to the existing position - helper.extend_lock_amount(router_ref, "user", 9f32).unwrap(); - helper.check_xastro_balance(router_ref, "user", 1); - helper.check_xastro_balance(router_ref, helper.voting_instance.as_str(), 99); - - // Try to withdraw from a non-expired lock - let err = helper.withdraw(router_ref, "user").unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "The lock time has not yet expired" - ); - - // Go in the future - router_ref.update_block(next_block); - router_ref.update_block(|block| block.time = block.time.plus_seconds(WEEK)); - - // The lock has not yet expired since we locked for 2 weeks - let err = helper.withdraw(router_ref, "user").unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "The lock time has not yet expired" - ); - - // Go to the future again - router_ref.update_block(next_block); - router_ref.update_block(|block| block.time = block.time.plus_seconds(WEEK)); - - // Try to add more xASTRO to an expired position - let err = helper - .extend_lock_amount(router_ref, "user", 1f32) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "The lock expired. Withdraw and create new lock" - ); - // Try to increase the lock time for an expired position - let err = helper - .extend_lock_time(router_ref, "user", WEEK) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "The lock expired. Withdraw and create new lock" - ); - - // Imagine the user will withdraw their expired lock in 5 weeks - router_ref.update_block(next_block); - router_ref.update_block(|block| block.time = block.time.plus_seconds(5 * WEEK)); - - // Time has passed so we can withdraw - helper.withdraw(router_ref, "user").unwrap(); - helper.check_xastro_balance(router_ref, "user", 100); - helper.check_xastro_balance(router_ref, helper.voting_instance.as_str(), 0); - - // Check that the lock has disappeared - let err = helper - .extend_lock_amount(router_ref, "user", 1f32) - .unwrap_err(); - assert_eq!(err.root_cause().to_string(), "Lock does not exist"); -} - -#[test] -fn random_token_lock() { - let mut router = mock_app(); - let router_ref = &mut router; - let owner = Addr::unchecked("owner"); - let helper = Helper::init(router_ref, owner); - - let random_token_contract = Box::new(ContractWrapper::new_with_empty( - astroport_token::contract::execute, - astroport_token::contract::instantiate, - astroport_token::contract::query, - )); - let random_token_code_id = router.store_code(random_token_contract); - - let msg = astro::InstantiateMsg { - name: String::from("Random token"), - symbol: String::from("FOO"), - decimals: 6, - initial_balances: vec![], - mint: Some(MinterResponse { - minter: helper.owner.to_string(), - cap: None, - }), - marketing: None, - }; - - let random_token = router - .instantiate_contract( - random_token_code_id, - helper.owner.clone(), - &msg, - &[], - String::from("FOO"), - None, - ) - .unwrap(); - - let msg = cw20::Cw20ExecuteMsg::Mint { - recipient: String::from("user"), - amount: Uint128::from(100_u128), - }; - - router - .execute_contract(helper.owner.clone(), random_token.clone(), &msg, &[]) - .unwrap(); - - let cw20msg = Cw20ExecuteMsg::Send { - contract: helper.voting_instance.to_string(), - amount: Uint128::from(10_u128), - msg: to_json_binary(&Cw20HookMsg::CreateLock { time: WEEK }).unwrap(), - }; - let err = router - .execute_contract(Addr::unchecked("user"), random_token, &cw20msg, &[]) - .unwrap_err(); - - assert_eq!(err.root_cause().to_string(), "Unauthorized"); -} - -#[test] -fn new_lock_after_lock_expired() { - let mut router = mock_app(); - let router_ref = &mut router; - let helper = Helper::init(router_ref, Addr::unchecked("owner")); - - helper.mint_xastro(router_ref, "owner", 100); - - // Mint ASTRO, stake it and mint xASTRO - helper.mint_xastro(router_ref, "user", 100); - - helper - .create_lock(router_ref, "user", WEEK * 5, 50f32) - .unwrap(); - - let vp = helper.query_user_vp(router_ref, "user").unwrap(); - assert_eq!(vp, 53.605762); - let vp = helper.query_total_vp(router_ref).unwrap(); - assert_eq!(vp, 53.605762); - - // Go to the future - router_ref.update_block(next_block); - router_ref.update_block(|block| block.time = block.time.plus_seconds(WEEK * 5)); - - helper.withdraw(router_ref, "user").unwrap(); - helper.check_xastro_balance(router_ref, "user", 100); - - let vp = helper.query_user_vp(router_ref, "user").unwrap(); - assert_eq!(vp, 0.0); - let vp = helper.query_total_vp(router_ref).unwrap(); - assert_eq!(vp, 0.0); - - // Create a new lock in 3 weeks from now - router_ref.update_block(next_block); - router_ref.update_block(|block| block.time = block.time.plus_seconds(WEEK * 3)); - - helper - .create_lock(router_ref, "user", WEEK * 5, 100f32) - .unwrap(); - - let vp = helper.query_user_vp(router_ref, "user").unwrap(); - assert_eq!(vp, 107.21153); - let vp = helper.query_total_vp(router_ref).unwrap(); - assert_eq!(vp, 107.21153); -} - -/// Plot for this test case generated at tests/plots/constant_decay.png -#[test] -fn voting_constant_decay() { - let mut router = mock_app(); - let router_ref = &mut router; - let helper = Helper::init(router_ref, Addr::unchecked("owner")); - - // Mint ASTRO, stake it and mint xASTRO - helper.mint_xastro(router_ref, "user", 100); - helper.mint_xastro(router_ref, "user2", 50); - - helper - .create_lock(router_ref, "user", WEEK * 10, 30f32) - .unwrap(); - - let vp = helper.query_user_vp(router_ref, "user").unwrap(); - assert_eq!(vp, 34.32692); - let vp = helper.query_total_vp(router_ref).unwrap(); - assert_eq!(vp, 34.32692); - - // Since user2 did not lock their xASTRO, the contract does not have any information - let vp = helper.query_user_vp(router_ref, "user2").unwrap(); - assert_eq!(vp, 0.0); - - // Go to the future - router_ref.update_block(next_block); - router_ref.update_block(|block| block.time = block.time.plus_seconds(WEEK * 5)); - - // We can check voting power in the past - let res = helper - .query_user_vp_at( - router_ref, - "user", - router_ref.block_info().time.seconds() - WEEK, - ) - .unwrap(); - assert_eq!(res, 20.596151); - let res = helper - .query_user_vp_at( - router_ref, - "user", - router_ref.block_info().time.seconds() - 3 * WEEK, - ) - .unwrap(); - assert_eq!(res, 27.461536); - let res = helper - .query_total_vp_at( - router_ref, - router_ref.block_info().time.seconds() - 5 * WEEK, - ) - .unwrap(); - assert_eq!(res, 34.32692); - - // And we can even check voting power in the future - let res = helper - .query_user_vp_at( - router_ref, - "user", - router_ref.block_info().time.seconds() + WEEK, - ) - .unwrap(); - assert_eq!(res, 13.730768); - let res = helper - .query_user_vp_at( - router_ref, - "user", - router_ref.block_info().time.seconds() + 5 * WEEK, - ) - .unwrap(); - assert_eq!(res, 0.0); - - // Create lock for user2 - helper - .create_lock(router_ref, "user2", WEEK * 6, 50f32) - .unwrap(); - - let vp = helper.query_user_vp(router_ref, "user").unwrap(); - assert_eq!(vp, 17.16346); - let vp = helper.query_user_vp(router_ref, "user2").unwrap(); - assert_eq!(vp, 54.32692); - let vp = helper.query_total_vp(router_ref).unwrap(); - assert_eq!(vp, 71.49039); - let res = helper - .query_total_vp_at( - router_ref, - router_ref.block_info().time.seconds() + 4 * WEEK, - ) - .unwrap(); - assert_eq!(res, 21.541666); - - // Go to the future - router_ref.update_block(next_block); - router_ref.update_block(|block| block.time = block.time.plus_seconds(WEEK * 5)); - let vp = helper.query_user_vp(router_ref, "user").unwrap(); - assert_eq!(vp, 0.0); - let vp = helper.query_user_vp(router_ref, "user2").unwrap(); - assert_eq!(vp, 9.054487); - let vp = helper.query_total_vp(router_ref).unwrap(); - assert_eq!(vp, 9.054487); - - // Go to the future - router_ref.update_block(next_block); - router_ref.update_block(|block| block.time = block.time.plus_seconds(WEEK)); - let vp = helper.query_user_vp(router_ref, "user2").unwrap(); - assert_eq!(vp, 0.0); - let vp = helper.query_total_vp(router_ref).unwrap(); - assert_eq!(vp, 0.0); -} - -/// Plot for this test case is generated at tests/plots/variable_decay.png -#[test] -fn voting_variable_decay() { - let mut router = mock_app(); - let router_ref = &mut router; - let helper = Helper::init(router_ref, Addr::unchecked("owner")); - - // Mint ASTRO, stake it and mint xASTRO - helper.mint_xastro(router_ref, "owner", 100); - helper.mint_xastro(router_ref, "user", 100); - helper.mint_xastro(router_ref, "user2", 100); - - helper - .create_lock(router_ref, "user", WEEK * 10, 30f32) - .unwrap(); - - // Go to the future - router_ref.update_block(next_block); - router_ref.update_block(|block| block.time = block.time.plus_seconds(WEEK * 5)); - - // Create lock for user2 - helper - .create_lock(router_ref, "user2", WEEK * 6, 50f32) - .unwrap(); - let vp = helper.query_total_vp(router_ref).unwrap(); - assert_eq!(vp, 71.49039); - - // Go to the future - router_ref.update_block(next_block); - router_ref.update_block(|block| block.time = block.time.plus_seconds(WEEK * 4)); - - helper - .extend_lock_amount(router_ref, "user", 70f32) - .unwrap(); - helper - .extend_lock_time(router_ref, "user2", WEEK * 8) - .unwrap(); - let vp = helper.query_user_vp(router_ref, "user").unwrap(); - assert_eq!(vp, 74.44231); - let vp = helper.query_user_vp(router_ref, "user2").unwrap(); - assert_eq!(vp, 57.21153); - let vp = helper.query_total_vp(router_ref).unwrap(); - assert_eq!(vp, 131.65384); - - let res = helper - .query_user_vp_at( - router_ref, - "user2", - router_ref.block_info().time.seconds() + 4 * WEEK, - ) - .unwrap(); - assert_eq!(res, 34.32692); - let res = helper - .query_total_vp_at(router_ref, router_ref.block_info().time.seconds() + WEEK) - .unwrap(); - assert_eq!(res, 51.490376); - - // Go to the future - router_ref.update_block(next_block); - router_ref.update_block(|block| block.time = block.time.plus_seconds(WEEK)); - let vp = helper.query_user_vp(router_ref, "user").unwrap(); - assert_eq!(vp, 0.0); - let vp = helper.query_user_vp(router_ref, "user2").unwrap(); - assert_eq!(vp, 51.490376); - let vp = helper.query_total_vp(router_ref).unwrap(); - assert_eq!(vp, 51.490376); -} - -#[test] -fn check_queries() { - let mut router = mock_app(); - let router_ref = &mut router; - let owner = Addr::unchecked("owner"); - let helper = Helper::init(router_ref, owner); - - helper.mint_xastro(router_ref, "owner", 100); - - // Mint ASTRO, stake it and mint xASTRO - helper.mint_xastro(router_ref, "user", 100); - helper.check_xastro_balance(router_ref, "user", 100); - - // Create valid voting escrow lock - helper - .create_lock(router_ref, "user", WEEK * 2, 90f32) - .unwrap(); - // Check that 90 xASTRO were actually debited - helper.check_xastro_balance(router_ref, "user", 10); - helper.check_xastro_balance(router_ref, helper.voting_instance.as_str(), 90); - - // Validate user's lock - let cur_period = get_period(router_ref.block_info().time.seconds()).unwrap(); - let user_lock: LockInfoResponse = router_ref - .wrap() - .query_wasm_smart( - helper.voting_instance.clone(), - &QueryMsg::LockInfo { - user: "user".to_string(), - }, - ) - .unwrap(); - assert_eq!(user_lock.amount.u128(), 90_u128 * MULTIPLIER as u128); - assert_eq!(user_lock.start, cur_period); - assert_eq!(user_lock.end, cur_period + 2); - let coeff = user_lock.coefficient.numerator().u128() as f32 - / user_lock.coefficient.denominator().u128() as f32; - if (coeff - 1.02884f32).abs() > 1e-5 { - assert_eq!(coeff, 1.02884f32) - } - - let total_vp_at_period = helper - .query_total_vp_at_period(router_ref, cur_period) - .unwrap(); - let total_vp_at_ts = helper - .query_total_vp_at(router_ref, router_ref.block_info().time.seconds()) - .unwrap(); - assert_eq!(total_vp_at_period, total_vp_at_ts); - - let user_vp_at_period = helper - .query_user_vp_at_period(router_ref, "user", cur_period) - .unwrap(); - let user_vp = helper - .query_user_vp_at(router_ref, "user", router_ref.block_info().time.seconds()) - .unwrap(); - assert_eq!(user_vp_at_period, user_vp); - - // Check users' locked xASTRO balance history - helper.mint_xastro(router_ref, "user", 90); - // SnapshotMap checkpoints the data at the next block - let start_height = router_ref.block_info().height + 1; - let balance = helper - .query_locked_balance_at(router_ref, "user", start_height) - .unwrap(); - assert_eq!(balance, 90f32); - // Make the lockup to live longer - helper - .extend_lock_time(router_ref, "user", WEEK * 100) - .unwrap(); - - router_ref.update_block(next_block); - helper - .extend_lock_amount(router_ref, "user", 100f32) - .unwrap(); - let balance = helper - .query_locked_balance_at(router_ref, "user", start_height) - .unwrap(); - assert_eq!(balance, 90f32); - - router_ref.update_block(|bi| bi.height += 100000); - let balance = helper - .query_locked_balance_at(router_ref, "user", start_height) - .unwrap(); - assert_eq!(balance, 90f32); - let balance = helper - .query_locked_balance_at(router_ref, "user", start_height + 2) - .unwrap(); - assert_eq!(balance, 190f32); - // The user still has 190 xASTRO locked - let balance = helper - .query_locked_balance_at(router_ref, "user", router_ref.block_info().height) - .unwrap(); - assert_eq!(balance, 190f32); - - router_ref.update_block(|bi| { - bi.height += 1; - bi.time = bi.time.plus_seconds(WEEK * 102); - }); - helper.withdraw(router_ref, "user").unwrap(); - // Now the users' balance is zero - let cur_height = router_ref.block_info().height + 1; - let balance = helper - .query_locked_balance_at(router_ref, "user", cur_height) - .unwrap(); - // But one block before it had 190 xASTRO locked - assert_eq!(balance, 0f32); - let balance = helper - .query_locked_balance_at(router_ref, "user", cur_height - 1) - .unwrap(); - assert_eq!(balance, 190f32); - - // add users to the blacklist - helper - .update_blacklist( - router_ref, - Some(vec![ - "voter1".to_string(), - "voter2".to_string(), - "voter3".to_string(), - "voter4".to_string(), - "voter5".to_string(), - "voter6".to_string(), - "voter7".to_string(), - "voter8".to_string(), - ]), - None, - ) - .unwrap(); - - // query all blacklisted voters - let blacklisted_voters = helper - .query_blacklisted_voters(router_ref, None, None) - .unwrap(); - assert_eq!( - blacklisted_voters, - vec![ - Addr::unchecked("voter1"), - Addr::unchecked("voter2"), - Addr::unchecked("voter3"), - Addr::unchecked("voter4"), - Addr::unchecked("voter5"), - Addr::unchecked("voter6"), - Addr::unchecked("voter7"), - Addr::unchecked("voter8"), - ] - ); - - // query not blacklisted voter - let err = helper - .query_blacklisted_voters(router_ref, Some("voter9".to_string()), Some(10u32)) - .unwrap_err(); - assert_eq!( - StdError::generic_err( - "Querier contract error: Generic error: The voter9 address is not blacklisted" - ), - err - ); - - // query voters by specified parameters - let blacklisted_voters = helper - .query_blacklisted_voters(router_ref, Some("voter2".to_string()), Some(2u32)) - .unwrap(); - assert_eq!( - blacklisted_voters, - vec![Addr::unchecked("voter3"), Addr::unchecked("voter4")] - ); - - // add users to the blacklist - helper - .update_blacklist( - router_ref, - Some(vec!["voter0".to_string(), "voter33".to_string()]), - None, - ) - .unwrap(); - - // query voters by specified parameters - let blacklisted_voters = helper - .query_blacklisted_voters(router_ref, Some("voter2".to_string()), Some(2u32)) - .unwrap(); - assert_eq!( - blacklisted_voters, - vec![Addr::unchecked("voter3"), Addr::unchecked("voter33")] - ); - - let blacklisted_voters = helper - .query_blacklisted_voters(router_ref, Some("voter4".to_string()), Some(10u32)) - .unwrap(); - assert_eq!( - blacklisted_voters, - vec![ - Addr::unchecked("voter5"), - Addr::unchecked("voter6"), - Addr::unchecked("voter7"), - Addr::unchecked("voter8"), - ] - ); - - let empty_blacklist: Vec = vec![]; - let blacklisted_voters = helper - .query_blacklisted_voters(router_ref, Some("voter8".to_string()), Some(10u32)) - .unwrap(); - assert_eq!(blacklisted_voters, empty_blacklist); - - // check if voters are blacklisted - let res = helper - .check_voters_are_blacklisted(router_ref, vec!["voter1".to_string(), "voter9".to_string()]) - .unwrap(); - assert_eq!("Voter is not blacklisted: voter9", res.to_string()); - - let res = helper - .check_voters_are_blacklisted(router_ref, vec!["voter1".to_string(), "voter8".to_string()]) - .unwrap(); - assert_eq!("Voters are blacklisted!", res.to_string()); -} - -#[test] -fn check_deposit_for() { - let mut router = mock_app(); - let router_ref = &mut router; - let owner = Addr::unchecked("owner"); - let helper = Helper::init(router_ref, owner); - - helper.mint_xastro(router_ref, "owner", 100); - - // Mint ASTRO, stake it and mint xASTRO - helper.mint_xastro(router_ref, "user1", 100); - helper.check_xastro_balance(router_ref, "user1", 100); - helper.mint_xastro(router_ref, "user2", 100); - helper.check_xastro_balance(router_ref, "user2", 100); - - // 104 weeks ~ 2 years - helper - .create_lock(router_ref, "user1", 104 * WEEK, 50f32) - .unwrap(); - let vp = helper.query_user_vp(router_ref, "user1").unwrap(); - assert_eq!(124.99999, vp); - helper - .deposit_for(router_ref, "user2", "user1", 50f32) - .unwrap(); - let vp = helper.query_user_vp(router_ref, "user1").unwrap(); - assert_eq!(249.99998, vp); - helper.check_xastro_balance(router_ref, "user1", 50); - helper.check_xastro_balance(router_ref, "user2", 50); -} - -#[test] -fn check_update_owner() { - let mut app = mock_app(); - let owner = Addr::unchecked("owner"); - let helper = Helper::init(&mut app, owner); - - let new_owner = String::from("new_owner"); - - // New owner - let msg = ExecuteMsg::ProposeNewOwner { - new_owner: new_owner.clone(), - expires_in: 100, // seconds - }; - - // Unauthed check - let err = app - .execute_contract( - Addr::unchecked("not_owner"), - helper.voting_instance.clone(), - &msg, - &[], - ) - .unwrap_err(); - assert_eq!(err.root_cause().to_string(), "Generic error: Unauthorized"); - - // Claim before proposal - let err = app - .execute_contract( - Addr::unchecked(new_owner.clone()), - helper.voting_instance.clone(), - &ExecuteMsg::ClaimOwnership {}, - &[], - ) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Generic error: Ownership proposal not found" - ); - - // Propose new owner - app.execute_contract( - Addr::unchecked("owner"), - helper.voting_instance.clone(), - &msg, - &[], - ) - .unwrap(); - - // Claim from invalid addr - let err = app - .execute_contract( - Addr::unchecked("invalid_addr"), - helper.voting_instance.clone(), - &ExecuteMsg::ClaimOwnership {}, - &[], - ) - .unwrap_err(); - assert_eq!(err.root_cause().to_string(), "Generic error: Unauthorized"); - - // Claim ownership - app.execute_contract( - Addr::unchecked(new_owner.clone()), - helper.voting_instance.clone(), - &ExecuteMsg::ClaimOwnership {}, - &[], - ) - .unwrap(); - - // Let's query the contract state - let msg = QueryMsg::Config {}; - let res: ConfigResponse = app - .wrap() - .query_wasm_smart(&helper.voting_instance, &msg) - .unwrap(); - - assert_eq!(res.owner, new_owner) -} - -#[test] -fn check_blacklist() { - let mut router = mock_app(); - let router_ref = &mut router; - let owner = Addr::unchecked("owner"); - let helper = Helper::init(router_ref, owner); - - // Mint ASTRO, stake it and mint xASTRO - helper.mint_xastro(router_ref, "user1", 100); - helper.mint_xastro(router_ref, "user2", 100); - helper.mint_xastro(router_ref, "user3", 100); - - // Try to execute with empty arrays - let err = helper.update_blacklist(router_ref, None, None).unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Generic error: Append and remove arrays are empty" - ); - - // Blacklisting user2 - let res = helper - .update_blacklist(router_ref, Some(vec!["user2".to_string()]), None) - .unwrap(); - assert_eq!( - res.events[1].attributes[1], - attr("action", "update_blacklist") - ); - assert_eq!( - res.events[1].attributes[2], - attr("added_addresses", "user2") - ); - - helper - .create_lock(router_ref, "user1", WEEK * 10, 50f32) - .unwrap(); - // Try to create lock from a blacklisted address - let err = helper - .create_lock(router_ref, "user2", WEEK * 10, 100f32) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "The user2 address is blacklisted" - ); - let err = helper - .deposit_for(router_ref, "user2", "user3", 50f32) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "The user2 address is blacklisted" - ); - - // Since user2 is blacklisted, their xASTRO balance was left unchanged - helper.check_xastro_balance(router_ref, "user2", 100); - // And they did not create a lock, thus we have no information to query - let vp = helper.query_user_vp(router_ref, "user2").unwrap(); - assert_eq!(vp, 0.0); - - // Go to the future - router_ref.update_block(next_block); - router_ref.update_block(|block| block.time = block.time.plus_seconds(2 * WEEK)); - - // user2 is still blacklisted - let err = helper - .create_lock(router_ref, "user2", WEEK * 10, 100f32) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "The user2 address is blacklisted" - ); - - // Blacklisting user1 using the guardian - let msg = ExecuteMsg::UpdateBlacklist { - append_addrs: Some(vec!["user1".to_string()]), - remove_addrs: None, - }; - let res = router_ref - .execute_contract( - Addr::unchecked("guardian"), - helper.voting_instance.clone(), - &msg, - &[], - ) - .unwrap(); - assert_eq!( - res.events[1].attributes[1], - attr("action", "update_blacklist") - ); - assert_eq!( - res.events[1].attributes[2], - attr("added_addresses", "user1") - ); - - // user1 is now blacklisted - let err = helper - .extend_lock_time(router_ref, "user1", WEEK * 10) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "The user1 address is blacklisted" - ); - let err = helper - .extend_lock_amount(router_ref, "user1", 10f32) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "The user1 address is blacklisted" - ); - let err = helper - .deposit_for(router_ref, "user2", "user1", 50f32) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "The user2 address is blacklisted" - ); - let err = helper - .deposit_for(router_ref, "user3", "user1", 50f32) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "The user1 address is blacklisted" - ); - // user1 doesn't have voting power now - let vp = helper.query_user_vp(router_ref, "user1").unwrap(); - assert_eq!(vp, 0.0); - // But they have voting power in the past - let vp = helper - .query_user_vp_at( - router_ref, - "user1", - router_ref.block_info().time.seconds() - WEEK, - ) - .unwrap(); - assert_eq!(vp, 51.490376); - // Total voting power should be zero as well since there was only one vxASTRO position created by user1 - let vp = helper.query_total_vp(router_ref).unwrap(); - assert_eq!(vp, 0.0); - - // Go to the future - router_ref.update_block(next_block); - router_ref.update_block(|block| block.time = block.time.plus_seconds(20 * WEEK)); - - // The only option available for a blacklisted user is to withdraw their funds if their lock expired - helper.withdraw(router_ref, "user1").unwrap(); - - // Remove user1 from the blacklist - let res = helper - .update_blacklist(router_ref, None, Some(vec!["user1".to_string()])) - .unwrap(); - assert_eq!( - res.events[1].attributes[1], - attr("action", "update_blacklist") - ); - assert_eq!( - res.events[1].attributes[2], - attr("removed_addresses", "user1") - ); - - // Now user1 can create a new lock - helper - .create_lock(router_ref, "user1", WEEK, 10f32) - .unwrap(); -} - -#[test] -fn check_residual() { - let mut router = mock_app(); - let router_ref = &mut router; - let owner = Addr::unchecked("owner"); - let helper = Helper::init(router_ref, owner); - let lock_duration = 104; - let users_num = 1000; - let lock_amount = 100_000_000; - - helper.mint_xastro(router_ref, "owner", 100); - - for i in 1..(users_num / 2) { - let user = &format!("user{}", i); - helper.mint_xastro(router_ref, user, 100); - helper - .create_lock_u128(router_ref, user, WEEK * lock_duration, lock_amount) - .unwrap(); - } - - let mut sum = 0; - for i in 1..=users_num { - let user = &format!("user{}", i); - sum += helper.query_exact_user_vp(router_ref, user).unwrap(); - } - - assert_eq!(sum, helper.query_exact_total_vp(router_ref).unwrap()); - - router_ref.update_block(|bi| { - bi.height += 1; - bi.time = bi.time.plus_seconds(WEEK); - }); - - for i in (users_num / 2)..users_num { - let user = &format!("user{}", i); - helper.mint_xastro(router_ref, user, 1000000); - helper - .create_lock_u128(router_ref, user, WEEK * lock_duration, lock_amount) - .unwrap(); - } - - for _ in 1..104 { - sum = 0; - for i in 1..=users_num { - let user = &format!("user{}", i); - sum += helper.query_exact_user_vp(router_ref, user).unwrap(); - } - - let ve_vp = helper.query_exact_total_vp(router_ref).unwrap(); - let diff = (sum as f64 - ve_vp as f64).abs(); - assert_eq!(diff, 0.0, "diff: {}, sum: {}, ve_vp: {}", diff, sum, ve_vp); - - router_ref.update_block(|bi| { - bi.height += 1; - bi.time = bi.time.plus_seconds(WEEK); - }); - } -} - -#[test] -fn total_vp_multiple_slope_subtraction() { - let mut router = mock_app(); - let router_ref = &mut router; - let owner = Addr::unchecked("owner"); - let helper = Helper::init(router_ref, owner); - - helper.mint_xastro(router_ref, "user1", 1000); - helper - .create_lock(router_ref, "user1", 2 * WEEK, 100f32) - .unwrap(); - let total = helper.query_total_vp(router_ref).unwrap(); - assert_eq!(total, 102.88461); - - router_ref.update_block(|bi| bi.time = bi.time.plus_seconds(2 * WEEK)); - // Slope changes have been applied - let total = helper.query_total_vp(router_ref).unwrap(); - assert_eq!(total, 0.0); - - // Try to manipulate over expired lock 2 weeks later - router_ref.update_block(|bi| bi.time = bi.time.plus_seconds(2 * WEEK)); - let err = helper - .extend_lock_amount(router_ref, "user1", 100f32) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "The lock expired. Withdraw and create new lock" - ); - let err = helper - .create_lock(router_ref, "user1", 2 * WEEK, 100f32) - .unwrap_err(); - assert_eq!(err.root_cause().to_string(), "Lock already exists"); - let err = helper - .extend_lock_time(router_ref, "user1", 2 * WEEK) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "The lock expired. Withdraw and create new lock" - ); - let total = helper.query_total_vp(router_ref).unwrap(); - assert_eq!(total, 0f32); -} - -#[test] -fn marketing_info() { - let mut router = mock_app(); - let router_ref = &mut router; - let owner = Addr::unchecked("owner"); - let helper = Helper::init(router_ref, owner); - - let err = router_ref - .execute_contract( - helper.owner.clone(), - helper.voting_instance.clone(), - &ExecuteMsg::SetLogoUrlsWhitelist { - whitelist: vec![ - "@hello-test-url .com/".to_string(), - "example.com/".to_string(), - ], - }, - &[], - ) - .unwrap_err(); - assert_eq!( - &err.root_cause().to_string(), - "Generic error: Link contains invalid characters: @hello-test-url .com/" - ); - - let err = router_ref - .execute_contract( - helper.owner.clone(), - helper.voting_instance.clone(), - &ExecuteMsg::SetLogoUrlsWhitelist { - whitelist: vec!["example.com".to_string()], - }, - &[], - ) - .unwrap_err(); - assert_eq!( - &err.root_cause().to_string(), - "Marketing info validation error: Whitelist link should end with '/': example.com" - ); - - router_ref - .execute_contract( - helper.owner.clone(), - helper.voting_instance.clone(), - &ExecuteMsg::SetLogoUrlsWhitelist { - whitelist: vec!["example.com/".to_string()], - }, - &[], - ) - .unwrap(); - - let err = router_ref - .execute_contract( - helper.owner.clone(), - helper.voting_instance.clone(), - &ExecuteMsg::UpdateMarketing { - project: Some("".to_string()), - description: None, - marketing: None, - }, - &[], - ) - .unwrap_err(); - - assert_eq!( - &err.root_cause().to_string(), - "Marketing info validation error: project contains invalid characters: " - ); - - let err = router_ref - .execute_contract( - helper.owner.clone(), - helper.voting_instance.clone(), - &ExecuteMsg::UpdateMarketing { - project: None, - description: Some("".to_string()), - marketing: None, - }, - &[], - ) - .unwrap_err(); - assert_eq!( - &err.root_cause().to_string(), - "Marketing info validation error: description contains invalid characters: " - ); - - router_ref - .execute_contract( - helper.owner.clone(), - helper.voting_instance.clone(), - &ExecuteMsg::UpdateMarketing { - project: Some("Some project".to_string()), - description: Some("Some description".to_string()), - marketing: None, - }, - &[], - ) - .unwrap(); - - let config: ConfigResponse = router_ref - .wrap() - .query_wasm_smart(&helper.voting_instance, &QueryMsg::Config {}) - .unwrap(); - assert_eq!(config.logo_urls_whitelist, vec!["example.com/".to_string()]); - let marketing_info: MarketingInfoResponse = router_ref - .wrap() - .query_wasm_smart(&helper.voting_instance, &QueryMsg::MarketingInfo {}) - .unwrap(); - assert_eq!(marketing_info.project, Some("Some project".to_string())); - assert_eq!( - marketing_info.description, - Some("Some description".to_string()) - ); - - let err = router_ref - .execute_contract( - helper.owner.clone(), - helper.voting_instance.clone(), - &ExecuteMsg::UploadLogo(Logo::Url("https://some-website.com/logo.svg".to_string())), - &[], - ) - .unwrap_err(); - assert_eq!( - &err.root_cause().to_string(), - "Marketing info validation error: Logo link is not whitelisted: https://some-website.com/logo.svg", - ); - - router_ref - .execute_contract( - helper.owner.clone(), - helper.voting_instance.clone(), - &ExecuteMsg::UploadLogo(Logo::Url("example.com/logo.svg".to_string())), - &[], - ) - .unwrap(); - - let marketing_info: MarketingInfoResponse = router_ref - .wrap() - .query_wasm_smart(&helper.voting_instance, &QueryMsg::MarketingInfo {}) - .unwrap(); - assert_eq!( - marketing_info.logo.unwrap(), - LogoInfo::Url("example.com/logo.svg".to_string()) - ); -} diff --git a/contracts/voting_escrow/tests/plots/constant_decay.png b/contracts/voting_escrow/tests/plots/constant_decay.png deleted file mode 100644 index 1429076c0a5d1aa02dbcfc40d8f3638ad6c6d83d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157448 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

r+=KLw_DdDK0~@ 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

+FbI5uDrBXHR9utCTy?+Gm=Y`$}zF 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 diff --git a/contracts/voting_escrow/tests/plots/variable_decay.png b/contracts/voting_escrow/tests/plots/variable_decay.png deleted file mode 100644 index 39d7a6849255c9d195f1370fb77c7c87c7f15ec1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 189306 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 E01R<{h5!Hn diff --git a/contracts/voting_escrow/tests/simulation.rs b/contracts/voting_escrow/tests/simulation.rs deleted file mode 100644 index c146c7b5..00000000 --- a/contracts/voting_escrow/tests/simulation.rs +++ /dev/null @@ -1,439 +0,0 @@ -use crate::test_utils::{mock_app, Helper, MULTIPLIER}; -use anyhow::Result; -use astroport_governance::utils::{ - get_period, get_periods_count, EPOCH_START, MAX_LOCK_TIME, WEEK, -}; -use cosmwasm_std::Addr; -use cw_multi_test::{next_block, App, AppResponse}; -use std::collections::hash_map::Entry; -use std::collections::HashMap; - -mod test_utils; - -#[derive(Clone, Default, Debug)] -struct Point { - amount: f64, - end: u64, -} - -#[derive(Clone, Debug)] -enum Event { - CreateLock(f64, u64), - IncreaseTime(u64), - ExtendLock(f64), - Withdraw, - Blacklist, - Recover, -} - -use Event::*; - -struct Simulator { - // Point history (history[period][user] = point) - points: Vec>, - // Current user's lock (amount, end) - locked: HashMap, - users: Vec, - helper: Helper, - router: App, -} - -fn apply_coefficient(amount: f64, interval: u64) -> f64 { - let coeff = 1f64 + (1.5 * interval as f64) / get_periods_count(MAX_LOCK_TIME) as f64; - // Imitate decimal fraction multiplication - (amount * coeff * MULTIPLIER as f64).trunc() / MULTIPLIER as f64 -} - -impl Simulator { - fn new>(users: &[T]) -> Self { - let mut router = mock_app(); - Self { - points: vec![HashMap::new(); 10000], - locked: Default::default(), - users: users.iter().cloned().map(|user| user.into()).collect(), - helper: Helper::init(&mut router, Addr::unchecked("owner")), - router, - } - } - - fn mint(&mut self, user: &str, amount: u128) { - self.helper - .mint_xastro(&mut self.router, user, amount as u64) - } - - fn block_period(&self) -> u64 { - get_period(self.router.block_info().time.seconds()).unwrap() - } - - fn app_next_period(&mut self) { - self.router.update_block(next_block); - self.router - .update_block(|block| block.time = block.time.plus_seconds(WEEK)); - } - - fn create_lock(&mut self, user: &str, amount: f64, interval: u64) -> Result { - let block_period = self.block_period(); - let periods_interval = get_periods_count(interval); - self.helper - .create_lock(&mut self.router, user, interval, amount as f32) - .map(|response| { - self.add_point( - block_period as usize, - user, - apply_coefficient(amount, periods_interval), - block_period + periods_interval, - ); - self.locked.extend(vec![( - user.to_string(), - (amount, block_period + periods_interval), - )]); - response - }) - } - - fn increase_time(&mut self, user: &str, interval: u64) -> Result { - self.helper - .extend_lock_time(&mut self.router, user, interval) - .map(|response| { - let cur_period = self.block_period() as usize; - let periods_interval = get_periods_count(interval); - let end = if let Some(point) = self.get_user_point_at(cur_period, user) { - point.end - } else { - let prev_point = self - .get_prev_point(user) - .expect("We always need previous point!"); - prev_point.end - }; - let dt = end + periods_interval - cur_period as u64; - let lock = self.locked.get_mut(user).unwrap(); - lock.1 += periods_interval; - let amount = lock.0.to_owned(); - self.add_point( - cur_period, - user, - apply_coefficient(amount, dt), - end + periods_interval, - ); - response - }) - } - - fn extend_lock(&mut self, user: &str, amount: f64) -> Result { - self.helper - .extend_lock_amount(&mut self.router, user, amount as f32) - .map(|response| { - let cur_period = self.block_period() as usize; - let (user_balance, end) = - if let Some(point) = self.get_user_point_at(cur_period, user) { - (point.amount, point.end) - } else { - let prev_point = self - .get_prev_point(user) - .expect("We always need previous point!"); - (self.calc_user_balance_at(cur_period, user), prev_point.end) - }; - let vp = apply_coefficient(amount, end - cur_period as u64); - self.add_point(cur_period, user, user_balance + vp, end); - let mut lock = self.locked.get_mut(user).unwrap(); - lock.0 += amount; - response - }) - } - - fn withdraw(&mut self, user: &str) -> Result { - self.helper - .withdraw(&mut self.router, user) - .map(|response| { - let cur_period = self.block_period(); - self.add_point(cur_period as usize, user, 0.0, cur_period); - self.locked.remove(user); - response - }) - } - - fn append2blacklist(&mut self, user: &str) -> Result { - self.helper - .update_blacklist(&mut self.router, Some(vec![user.to_string()]), None) - .map(|response| { - let cur_period = self.block_period(); - self.add_point(cur_period as usize, user, 0.0, cur_period); - response - }) - } - - fn remove_from_blacklist(&mut self, user: &str) -> Result { - self.helper - .update_blacklist(&mut self.router, None, Some(vec![user.to_string()])) - .map(|response| { - let cur_period = self.block_period() as usize; - if let Some((amount, end)) = self.locked.get(user).copied() { - let dt = end.saturating_sub(cur_period as u64); - let new_amount = if dt != 0 { - apply_coefficient(amount, dt) - } else { - 0.0 - }; - self.add_point(cur_period, user, new_amount, end); - } - response - }) - } - - fn event_router(&mut self, user: &str, event: Event) { - println!("User {} Event {:?}", user, event); - match event { - Event::CreateLock(amount, interval) => { - if let Err(err) = self.create_lock(user, amount, interval) { - dbg!(err); - } - } - Event::IncreaseTime(interval) => { - if let Err(err) = self.increase_time(user, interval) { - dbg!(err); - } - } - Event::ExtendLock(amount) => { - if let Err(err) = self.extend_lock(user, amount) { - dbg!(err); - } - } - Event::Withdraw => { - if let Err(err) = self.withdraw(user) { - dbg!(err); - } - } - Event::Blacklist => { - if let Err(err) = self.append2blacklist(user) { - dbg!(err); - } - } - Event::Recover => { - if let Err(err) = self.remove_from_blacklist(user) { - dbg!(err); - } - } - } - let real_balance = self - .get_user_point_at(self.block_period() as usize, user) - .map(|point| point.amount) - .unwrap_or_else(|| self.calc_user_balance_at(self.block_period() as usize, user)); - let contract_balance = self - .helper - .query_user_vp(&mut self.router, user) - .unwrap_or(0.0) as f64; - if (real_balance - contract_balance).abs() >= 10e-3 { - assert_eq!(real_balance, contract_balance) - }; - } - - fn checkpoint_all_users(&mut self) { - let cur_period = self.block_period() as usize; - self.users.clone().iter().for_each(|user| { - // we need to calc point only if it was not calculated yet - if self.get_user_point_at(cur_period, user).is_none() { - self.checkpoint_user(user) - } - }) - } - - fn add_point>(&mut self, period: usize, user: T, amount: f64, end: u64) { - let map = &mut self.points[period]; - map.extend(vec![(user.into(), Point { amount, end })]); - } - - fn get_prev_point(&mut self, user: &str) -> Option { - let prev_period = (self.block_period() - 1) as usize; - self.get_user_point_at(prev_period, user) - } - - fn checkpoint_user(&mut self, user: &str) { - let cur_period = self.block_period() as usize; - let user_balance = self.calc_user_balance_at(cur_period, user); - let prev_point = self - .get_prev_point(user) - .expect("We always need previous point!"); - self.add_point(cur_period, user, user_balance, prev_point.end); - } - - fn get_user_point_at>(&mut self, period: usize, user: T) -> Option { - let points_map = &mut self.points[period]; - match points_map.entry(user.into()) { - Entry::Occupied(value) => Some(value.get().clone()), - Entry::Vacant(_) => None, - } - } - - fn calc_user_balance_at(&mut self, period: usize, user: &str) -> f64 { - match self.get_user_point_at(period, user) { - Some(point) => point.amount, - None => { - let prev_point = self - .get_user_point_at(period - 1, user) - .expect("We always need previous point!"); - let dt = prev_point.end.saturating_sub(period as u64 - 1); - if dt == 0 { - 0.0 - } else { - prev_point.amount - prev_point.amount / dt as f64 - } - } - } - } - - fn calc_total_balance_at(&mut self, period: usize) -> f64 { - self.users.clone().iter().fold(0.0, |acc, user| { - acc + self.get_user_point_at(period, user).unwrap().amount - }) - } -} - -use proptest::prelude::*; - -const MAX_PERIOD: usize = 10; -const MAX_USERS: usize = 6; -const MAX_EVENTS: usize = 100; - -fn amount_strategy() -> impl Strategy { - (1f64..=100f64).prop_map(|val| (val * MULTIPLIER as f64).trunc() / MULTIPLIER as f64) -} - -fn events_strategy() -> impl Strategy { - prop_oneof![ - Just(Event::Withdraw), - Just(Event::Blacklist), - Just(Event::Recover), - amount_strategy().prop_map(Event::ExtendLock), - (0..MAX_LOCK_TIME).prop_map(Event::IncreaseTime), - (amount_strategy(), 0..MAX_LOCK_TIME).prop_map(|(a, b)| Event::CreateLock(a, b)), - ] -} - -fn generate_cases() -> impl Strategy, Vec<(usize, String, Event)>)> { - let users_strategy = prop::collection::vec("[a-z]{4,32}", 1..MAX_USERS); - users_strategy.prop_flat_map(|users| { - ( - Just(users.clone()), - prop::collection::vec( - ( - 1..=MAX_PERIOD, - prop::sample::select(users), - events_strategy(), - ), - 0..MAX_EVENTS, - ), - ) - }) -} - -proptest! { - #[test] - fn run_simulations - ( - case in generate_cases() - ) { - let mut events: Vec> = vec![vec![]; MAX_PERIOD + 1]; - let (users, events_tuples) = case; - for (period, user, event) in events_tuples { - events[period].push((user, event)); - }; - - let mut simulator = Simulator::new(&users); - for user in users { - simulator.mint(&user, 10000); - simulator.add_point(0, user, 0.0, 104); - } - simulator.app_next_period(); - - for period in 1..=MAX_PERIOD { - if let Some(period_events) = events.get(period) { - for (user, event) in period_events { - simulator.event_router(user, event.clone()) - } - } - simulator.checkpoint_all_users(); - let real_balance = simulator.calc_total_balance_at(period); - let contract_balance = simulator - .helper - .query_total_vp(&mut simulator.router) - .unwrap_or(0.0) as f64; - if (real_balance - contract_balance).abs() >= 10e-3 { - assert_eq!(real_balance, contract_balance) - }; - // Evaluate historical periods - for check_period in 1..period { - let real_balance = simulator.calc_total_balance_at(check_period); - let contract_balance = simulator - .helper - .query_total_vp_at(&mut simulator.router, EPOCH_START + check_period as u64 * WEEK) - .unwrap_or(0.0) as f64; - if (real_balance - contract_balance).abs() >= 10e-3 { - assert_eq!(real_balance, contract_balance) - }; - } - simulator.app_next_period() - } - } -} - -#[test] -fn exact_simulation() { - let case = ( - ["bpcy"], - [ - (1, "bpcy", CreateLock(100.0, 3024000)), - (2, "bpcy", IncreaseTime(3024000)), - (3, "bpcy", Blacklist), - (3, "bpcy", Recover), - ], - ); - - let mut events: Vec> = vec![vec![]; MAX_PERIOD + 1]; - let (users, events_tuples) = case; - for (period, user, event) in events_tuples { - events[period].push((user.to_string(), event)); - } - - let mut simulator = Simulator::new(&users); - for user in users { - simulator.mint(user, 10000); - simulator.add_point(0, user, 0.0, 104); - } - simulator.app_next_period(); - - for period in 1..=MAX_PERIOD { - if let Some(period_events) = events.get(period) { - if !period_events.is_empty() { - println!("Period {}:", period); - } - for (user, event) in period_events { - simulator.event_router(user, event.clone()) - } - } - simulator.checkpoint_all_users(); - let real_balance = simulator.calc_total_balance_at(period); - let contract_balance = simulator - .helper - .query_total_vp(&mut simulator.router) - .unwrap_or(0.0) as f64; - if (real_balance - contract_balance).abs() >= 10e-3 { - println!("Assert failed at period {}", period); - assert_eq!(real_balance, contract_balance) - }; - // Evaluate historical periods - for check_period in 1..period { - let real_balance = simulator.calc_total_balance_at(check_period); - let contract_balance = simulator - .helper - .query_total_vp_at( - &mut simulator.router, - EPOCH_START + check_period as u64 * WEEK, - ) - .unwrap_or(0.0) as f64; - if (real_balance - contract_balance).abs() >= 10e-3 { - assert_eq!(real_balance, contract_balance) - }; - } - simulator.app_next_period() - } -} diff --git a/contracts/voting_escrow/tests/test_utils.rs b/contracts/voting_escrow/tests/test_utils.rs deleted file mode 100644 index 0a30d974..00000000 --- a/contracts/voting_escrow/tests/test_utils.rs +++ /dev/null @@ -1,498 +0,0 @@ -use anyhow::Result; -use astroport::{staking as xastro, token as astro}; -use astroport_governance::escrow_fee_distributor::InstantiateMsg as FeeDistributorInstantiateMsg; -use astroport_governance::utils::EPOCH_START; -use astroport_governance::voting_escrow::{ - BlacklistedVotersResponse, Cw20HookMsg, ExecuteMsg, InstantiateMsg, QueryMsg, - UpdateMarketingInfo, VotingPowerResponse, -}; -use cosmwasm_std::testing::{mock_env, MockApi, MockStorage}; -use cosmwasm_std::{ - attr, to_json_binary, Addr, QueryRequest, StdResult, Timestamp, Uint128, WasmQuery, -}; -use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg, Logo, MinterResponse}; -use cw_multi_test::{App, AppBuilder, AppResponse, BankKeeper, ContractWrapper, Executor}; -use voting_escrow::astroport; - -pub const MULTIPLIER: u64 = 1000000; - -pub struct Helper { - pub owner: Addr, - pub astro_token: Addr, - pub staking_instance: Addr, - pub xastro_token: Addr, - pub voting_instance: Addr, - pub fee_distributor_instance: Addr, -} - -impl Helper { - pub fn init(router: &mut App, owner: Addr) -> Self { - let astro_token_contract = Box::new(ContractWrapper::new_with_empty( - astroport_token::contract::execute, - astroport_token::contract::instantiate, - astroport_token::contract::query, - )); - - let astro_token_code_id = router.store_code(astro_token_contract); - - let msg = astro::InstantiateMsg { - name: String::from("Astro token"), - symbol: String::from("ASTRO"), - decimals: 6, - initial_balances: vec![], - mint: Some(MinterResponse { - minter: owner.to_string(), - cap: None, - }), - marketing: None, - }; - - let astro_token = router - .instantiate_contract( - astro_token_code_id, - owner.clone(), - &msg, - &[], - String::from("ASTRO"), - None, - ) - .unwrap(); - - let staking_contract = Box::new( - ContractWrapper::new_with_empty( - astroport_staking::contract::execute, - astroport_staking::contract::instantiate, - astroport_staking::contract::query, - ) - .with_reply_empty(astroport_staking::contract::reply), - ); - - let staking_code_id = router.store_code(staking_contract); - - let msg = xastro::InstantiateMsg { - owner: owner.to_string(), - token_code_id: astro_token_code_id, - deposit_token_addr: astro_token.to_string(), - marketing: None, - }; - let staking_instance = router - .instantiate_contract( - staking_code_id, - owner.clone(), - &msg, - &[], - String::from("xASTRO"), - None, - ) - .unwrap(); - - let res = router - .wrap() - .query::(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: staking_instance.to_string(), - msg: to_json_binary(&xastro::QueryMsg::Config {}).unwrap(), - })) - .unwrap(); - - let voting_contract = Box::new(ContractWrapper::new_with_empty( - voting_escrow::contract::execute, - voting_escrow::contract::instantiate, - voting_escrow::contract::query, - )); - - let voting_code_id = router.store_code(voting_contract); - - let marketing_info = UpdateMarketingInfo { - project: Some("Astroport".to_string()), - description: Some("Astroport is a decentralized application for managing the supply of space resources.".to_string()), - marketing: Some(owner.to_string()), - logo: Some(Logo::Url("https://astroport.com/logo.png".to_string())), - }; - - let msg = InstantiateMsg { - owner: owner.to_string(), - guardian_addr: Some("guardian".to_string()), - deposit_token_addr: res.share_token_addr.to_string(), - marketing: Some(marketing_info), - logo_urls_whitelist: vec!["https://astroport.com/".to_string()], - }; - let voting_instance = router - .instantiate_contract( - voting_code_id, - owner.clone(), - &msg, - &[], - String::from("vxASTRO"), - None, - ) - .unwrap(); - - let fee_distributor_contract = Box::new(ContractWrapper::new_with_empty( - astroport_escrow_fee_distributor::contract::execute, - astroport_escrow_fee_distributor::contract::instantiate, - astroport_escrow_fee_distributor::contract::query, - )); - - let fee_distributor_code_id = router.store_code(fee_distributor_contract); - - let msg = FeeDistributorInstantiateMsg { - owner: owner.to_string(), - astro_token: astro_token.to_string(), - voting_escrow_addr: voting_instance.to_string(), - claim_many_limit: None, - is_claim_disabled: None, - }; - - let fee_distributor_instance = router - .instantiate_contract( - fee_distributor_code_id, - owner.clone(), - &msg, - &[], - String::from("Fee distributor"), - None, - ) - .unwrap(); - - Self { - owner, - xastro_token: res.share_token_addr, - astro_token, - staking_instance, - voting_instance, - fee_distributor_instance, - } - } - - pub fn mint_xastro(&self, router: &mut App, to: &str, amount: u64) { - let amount = amount * MULTIPLIER; - let msg = cw20::Cw20ExecuteMsg::Mint { - recipient: String::from(to), - amount: Uint128::from(amount), - }; - let res = router - .execute_contract(self.owner.clone(), self.astro_token.clone(), &msg, &[]) - .unwrap(); - assert_eq!(res.events[1].attributes[1], attr("action", "mint")); - assert_eq!(res.events[1].attributes[2], attr("to", String::from(to))); - assert_eq!( - res.events[1].attributes[3], - attr("amount", Uint128::from(amount)) - ); - - let to_addr = Addr::unchecked(to); - let msg = Cw20ExecuteMsg::Send { - contract: self.staking_instance.to_string(), - msg: to_json_binary(&xastro::Cw20HookMsg::Enter {}).unwrap(), - amount: Uint128::from(amount), - }; - router - .execute_contract(to_addr, self.astro_token.clone(), &msg, &[]) - .unwrap(); - } - - #[allow(dead_code)] - pub fn check_xastro_balance(&self, router: &mut App, user: &str, amount: u64) { - let amount = amount * MULTIPLIER; - let res: BalanceResponse = router - .wrap() - .query_wasm_smart( - self.xastro_token.clone(), - &Cw20QueryMsg::Balance { - address: user.to_string(), - }, - ) - .unwrap(); - assert_eq!(res.balance.u128(), amount as u128); - } - - #[allow(dead_code)] - pub fn check_astro_balance(&self, router: &mut App, user: &str, amount: u64) { - let amount = amount * MULTIPLIER; - let res: BalanceResponse = router - .wrap() - .query_wasm_smart( - self.astro_token.clone(), - &Cw20QueryMsg::Balance { - address: user.to_string(), - }, - ) - .unwrap(); - assert_eq!(res.balance.u128(), amount as u128); - } - - pub fn create_lock( - &self, - router: &mut App, - user: &str, - time: u64, - amount: f32, - ) -> Result { - let amount = (amount * MULTIPLIER as f32) as u64; - let cw20msg = Cw20ExecuteMsg::Send { - contract: self.voting_instance.to_string(), - amount: Uint128::from(amount), - msg: to_json_binary(&Cw20HookMsg::CreateLock { time }).unwrap(), - }; - router.execute_contract( - Addr::unchecked(user), - self.xastro_token.clone(), - &cw20msg, - &[], - ) - } - - #[allow(dead_code)] - pub fn create_lock_u128( - &self, - router: &mut App, - user: &str, - time: u64, - amount: u128, - ) -> Result { - let cw20msg = Cw20ExecuteMsg::Send { - contract: self.voting_instance.to_string(), - amount: Uint128::from(amount), - msg: to_json_binary(&Cw20HookMsg::CreateLock { time }).unwrap(), - }; - router.execute_contract( - Addr::unchecked(user), - self.xastro_token.clone(), - &cw20msg, - &[], - ) - } - - pub fn extend_lock_amount( - &self, - router: &mut App, - user: &str, - amount: f32, - ) -> Result { - let amount = (amount * MULTIPLIER as f32) as u64; - let cw20msg = Cw20ExecuteMsg::Send { - contract: self.voting_instance.to_string(), - amount: Uint128::from(amount), - msg: to_json_binary(&Cw20HookMsg::ExtendLockAmount {}).unwrap(), - }; - router.execute_contract( - Addr::unchecked(user), - self.xastro_token.clone(), - &cw20msg, - &[], - ) - } - - #[allow(dead_code)] - pub fn deposit_for( - &self, - router: &mut App, - from: &str, - to: &str, - amount: f32, - ) -> Result { - let amount = (amount * MULTIPLIER as f32) as u64; - let cw20msg = Cw20ExecuteMsg::Send { - contract: self.voting_instance.to_string(), - amount: Uint128::from(amount), - msg: to_json_binary(&Cw20HookMsg::DepositFor { - user: to.to_string(), - }) - .unwrap(), - }; - router.execute_contract( - Addr::unchecked(from), - self.xastro_token.clone(), - &cw20msg, - &[], - ) - } - - pub fn extend_lock_time(&self, router: &mut App, user: &str, time: u64) -> Result { - router.execute_contract( - Addr::unchecked(user), - self.voting_instance.clone(), - &ExecuteMsg::ExtendLockTime { time }, - &[], - ) - } - - pub fn withdraw(&self, router: &mut App, user: &str) -> Result { - router.execute_contract( - Addr::unchecked(user), - self.voting_instance.clone(), - &ExecuteMsg::Withdraw {}, - &[], - ) - } - - pub fn update_blacklist( - &self, - router: &mut App, - append_addrs: Option>, - remove_addrs: Option>, - ) -> Result { - router.execute_contract( - Addr::unchecked("owner"), - self.voting_instance.clone(), - &ExecuteMsg::UpdateBlacklist { - append_addrs, - remove_addrs, - }, - &[], - ) - } - - pub fn query_user_vp(&self, router: &mut App, user: &str) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.voting_instance.clone(), - &QueryMsg::UserVotingPower { - user: user.to_string(), - }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - #[allow(dead_code)] - pub fn query_exact_user_vp(&self, router: &mut App, user: &str) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.voting_instance.clone(), - &QueryMsg::UserVotingPower { - user: user.to_string(), - }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128()) - } - - #[allow(dead_code)] - pub fn query_user_vp_at(&self, router: &mut App, user: &str, time: u64) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.voting_instance.clone(), - &QueryMsg::UserVotingPowerAt { - user: user.to_string(), - time, - }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - #[allow(dead_code)] - pub fn query_user_vp_at_period( - &self, - router: &mut App, - user: &str, - period: u64, - ) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.voting_instance.clone(), - &QueryMsg::UserVotingPowerAtPeriod { - user: user.to_string(), - period, - }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_total_vp(&self, router: &mut App) -> StdResult { - router - .wrap() - .query_wasm_smart(self.voting_instance.clone(), &QueryMsg::TotalVotingPower {}) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - #[allow(dead_code)] - pub fn query_exact_total_vp(&self, router: &mut App) -> StdResult { - router - .wrap() - .query_wasm_smart(self.voting_instance.clone(), &QueryMsg::TotalVotingPower {}) - .map(|vp: VotingPowerResponse| vp.voting_power.u128()) - } - - pub fn query_total_vp_at(&self, router: &mut App, time: u64) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.voting_instance.clone(), - &QueryMsg::TotalVotingPowerAt { time }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - #[allow(dead_code)] - pub fn query_total_vp_at_period(&self, router: &mut App, period: u64) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.voting_instance.clone(), - &QueryMsg::TotalVotingPowerAtPeriod { period }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - #[allow(dead_code)] - pub fn query_locked_balance_at( - &self, - router: &mut App, - user: &str, - height: u64, - ) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.voting_instance.clone(), - &QueryMsg::UserDepositAtHeight { - user: user.to_string(), - height, - }, - ) - .map(|vp: Uint128| vp.u128() as f32 / MULTIPLIER as f32) - } - - #[allow(dead_code)] - pub fn query_blacklisted_voters( - &self, - router: &mut App, - start_after: Option, - limit: Option, - ) -> StdResult> { - router.wrap().query_wasm_smart( - self.voting_instance.clone(), - &QueryMsg::BlacklistedVoters { start_after, limit }, - ) - } - - #[allow(dead_code)] - pub fn check_voters_are_blacklisted( - &self, - router: &mut App, - voters: Vec, - ) -> StdResult { - router.wrap().query_wasm_smart( - self.voting_instance.clone(), - &QueryMsg::CheckVotersAreBlacklisted { voters }, - ) - } -} - -pub fn mock_app() -> App { - let mut env = mock_env(); - env.block.time = Timestamp::from_seconds(EPOCH_START); - let api = MockApi::default(); - let bank = BankKeeper::new(); - let storage = MockStorage::new(); - - AppBuilder::new() - .with_api(api) - .with_block(env.block) - .with_bank(bank) - .with_storage(storage) - .build(|_, _, _| {}) -} diff --git a/contracts/voting_escrow/tests/vxastro_integration.rs b/contracts/voting_escrow/tests/vxastro_integration.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/contracts/voting_escrow/tests/vxastro_integration.rs @@ -0,0 +1 @@ + diff --git a/contracts/voting_escrow_delegation/.cargo/config b/contracts/voting_escrow_delegation/.cargo/config deleted file mode 100644 index 1fc8c8c2..00000000 --- a/contracts/voting_escrow_delegation/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -unit-test = "test --lib" -integration-test = "test --test integration" -schema = "run --example voting_escrow_delegation_schema" diff --git a/contracts/voting_escrow_delegation/Cargo.toml b/contracts/voting_escrow_delegation/Cargo.toml deleted file mode 100644 index 7e73d8e7..00000000 --- a/contracts/voting_escrow_delegation/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "voting-escrow-delegation" -version = "1.0.0" -authors = ["Astroport"] -edition = "2021" -repository = "https://github.com/astroport-fi/astroport-governance" -homepage = "https://astroport.fi" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] - -[dependencies] -cosmwasm-std = "1.1" -cw-storage-plus = "0.15" -cw-utils = "0.15" -cw2 = "0.15" -thiserror = { version = "1.0" } -astroport-governance = { path = "../../packages/astroport-governance" } -cw721 = "0.15" -cw721-base = { version = "0.15", features = ["library"] } -cosmwasm-schema = "1.1" - -[dev-dependencies] -cw-multi-test = "0.15" -astroport-tests = {path = "../../packages/astroport-tests"} -astroport-nft = { path = "../nft"} -anyhow = "1" -proptest = "1.0" \ No newline at end of file diff --git a/contracts/voting_escrow_delegation/README.md b/contracts/voting_escrow_delegation/README.md deleted file mode 100644 index a79df43b..00000000 --- a/contracts/voting_escrow_delegation/README.md +++ /dev/null @@ -1,132 +0,0 @@ -# Voting Escrow Delegation - -Contract allows delegating voting power to other account in NFT format. - -## InstantiateMsg - -Initialize the contract with the initial owner and the address of the xASTRO token. - -```json -{ - "owner": "terra...", - "nft_code_id": 123, - "voting_escrow_addr": "terra..." -} -``` - -## ExecuteMsg - -### `create_delegation` - -Delegates the voting power to another account, according to the specified parameters, in the form of an NFT token. - -```json -{ - "create_delegation": { - "bps": 5000, - "expire_time": 12345, - "token_id": "123", - "recipient": "terra..." - } -} -``` - -### `extend_delegation` - -Extends a previously created delegation by a new specified parameters. - -```json -{ - "extend_delegation": { - "bps": 5000, - "expire_time": 12345, - "token_id": "123" - } -} -``` - -### `propose_new_owner` - -Create a request to change contract ownership. The validity period of the offer is set by the `expires_in` variable. -Only the current contract owner can execute this method. - -```json -{ - "propose_new_owner": { - "owner": "terra...", - "expires_in": 1234567 - } -} -``` - -### `drop_ownership_proposal` - -Delete the contract ownership transfer proposal. Only the current contract owner can execute this method. - -```json -{ - "drop_ownership_proposal": {} -} -``` - -### `claim_ownership` - -Used to claim contract ownership. Only the newly proposed contract owner can execute this method. - -```json -{ - "claim_ownership": {} -} -``` - -### `update_config` - -Updates contract parameters. - -```json -{ - "new_voting_escrow": "terra..." -} -``` - -## QueryMsg - -All query messages are described below. A custom struct is defined for each query response. - -### `config` - -Returns the contract's config. - -```json -{ - "config": {} -} -``` - -### `adjusted_balance` - -Returns an adjusted voting power balance after accounting for delegations at specified timestamp. - -```json -{ - "adjusted_balance": { - "account": "terra...", - "timestamp": 1234 - } -} -``` - -### `delegated_voting_power` - -Returns an amount of delegated voting power. - -Request: - -```json -{ - "delegated_voting_power": { - "account": "terra...", - "timestamp": 1234 - } -} -``` \ No newline at end of file diff --git a/contracts/voting_escrow_delegation/examples/voting_escrow_delegation_schema.rs b/contracts/voting_escrow_delegation/examples/voting_escrow_delegation_schema.rs deleted file mode 100644 index 4a556c68..00000000 --- a/contracts/voting_escrow_delegation/examples/voting_escrow_delegation_schema.rs +++ /dev/null @@ -1,10 +0,0 @@ -use astroport_governance::voting_escrow_delegation::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use cosmwasm_schema::write_api; - -fn main() { - write_api! { - instantiate: InstantiateMsg, - query: QueryMsg, - execute: ExecuteMsg, - } -} diff --git a/contracts/voting_escrow_delegation/src/contract.rs b/contracts/voting_escrow_delegation/src/contract.rs deleted file mode 100644 index 1cd69c11..00000000 --- a/contracts/voting_escrow_delegation/src/contract.rs +++ /dev/null @@ -1,447 +0,0 @@ -use std::marker::PhantomData; - -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{ - attr, to_json_binary, Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Reply, ReplyOn, - Response, StdError, StdResult, SubMsg, Uint128, WasmMsg, -}; -use cw2::set_contract_version; -use cw721::NftInfoResponse; -use cw721_base::helpers as cw721_helpers; -use cw721_base::msg::{ExecuteMsg as ExecuteMsgNFT, InstantiateMsg as InstantiateMsgNFT}; -use cw721_base::{Extension, MintMsg}; -use cw_utils::parse_reply_instantiate_data; - -use astroport_governance::astroport::common::{ - claim_ownership, drop_ownership_proposal, propose_new_owner, -}; -use astroport_governance::utils::{calc_voting_power, get_period, get_periods_count}; -use astroport_governance::voting_escrow::{get_voting_power, get_voting_power_at, MAX_LIMIT}; -use astroport_governance::voting_escrow_delegation::{ - Config, ExecuteMsg, InstantiateMsg, QueryMsg, -}; - -use crate::error::ContractError; -use crate::helpers::{ - calc_delegation, calc_extend_delegation, calc_not_delegated_vp, calc_total_delegated_vp, - validate_parameters, -}; -use crate::state::{CONFIG, DELEGATED, OWNERSHIP_PROPOSAL, TOKENS}; - -// Version info for contract migration. -const CONTRACT_NAME: &str = "voting-escrow-delegation"; -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -/// Delegated voting power NFT information. -const TOKEN_NAME: &str = "Delegated VP NFT"; -const TOKEN_SYMBOL: &str = "VP-NFT"; - -/// Creates a new contract with the specified parameters in the [`InstantiateMsg`]. -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> Result { - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - - let config = Config { - owner: deps.api.addr_validate(&msg.owner)?, - nft_addr: Addr::unchecked(""), - voting_escrow_addr: deps.api.addr_validate(&msg.voting_escrow_addr)?, - }; - CONFIG.save(deps.storage, &config)?; - - // Create an Astroport NFT - let sub_msg = SubMsg { - msg: WasmMsg::Instantiate { - admin: Some(config.owner.to_string()), - code_id: msg.nft_code_id, - msg: to_json_binary(&InstantiateMsgNFT { - name: TOKEN_NAME.to_string(), - symbol: TOKEN_SYMBOL.to_string(), - minter: env.contract.address.to_string(), - })?, - funds: vec![], - label: String::from("Delegated VP NFT"), - } - .into(), - id: 1, - gas_limit: None, - reply_on: ReplyOn::Success, - }; - - Ok(Response::new() - .add_attribute("action", "instantiate") - .add_attribute("owner", config.owner) - .add_submessage(sub_msg)) -} - -/// Exposes all the execute functions available in the contract. -/// -/// ## Execute messages -/// * **ExecuteMsg::CreateDelegation { percentage, cancel_time, expire_time, token_id, recipient}** -/// Delegates voting power in percent into other account. -/// -/// * **ExecuteMsg::ExtendDelegation { percentage, cancel_time, expire_time, token_id, recipient}** -/// Extends an already created delegation with a new specified parameters -/// -/// * **ExecuteMsg::ProposeNewOwner { owner, expires_in }** Creates a new request to change -/// contract ownership. -/// -/// * **ExecuteMsg::DropOwnershipProposal {}** Removes a request to change contract ownership. -/// -/// * **ExecuteMsg::ClaimOwnership {}** Claims contract ownership. -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - match msg { - ExecuteMsg::CreateDelegation { - bps, - expire_time, - token_id, - recipient, - } => create_delegation(deps, env, info, bps, expire_time, token_id, recipient), - ExecuteMsg::ExtendDelegation { - bps, - expire_time, - token_id, - } => extend_delegation(deps, env, info, bps, expire_time, token_id), - ExecuteMsg::UpdateConfig { new_voting_escrow } => { - update_config(deps, info, new_voting_escrow) - } - ExecuteMsg::ProposeNewOwner { - new_owner, - expires_in, - } => { - let config = CONFIG.load(deps.storage)?; - propose_new_owner( - deps, - info, - env, - new_owner, - expires_in, - config.owner, - OWNERSHIP_PROPOSAL, - ) - .map_err(Into::into) - } - ExecuteMsg::DropOwnershipProposal {} => { - let config: Config = CONFIG.load(deps.storage)?; - - drop_ownership_proposal(deps, info, config.owner, OWNERSHIP_PROPOSAL) - .map_err(Into::into) - } - ExecuteMsg::ClaimOwnership {} => { - claim_ownership(deps, info, env, OWNERSHIP_PROPOSAL, |deps, new_owner| { - CONFIG - .update::<_, StdError>(deps.storage, |mut v| { - v.owner = new_owner; - Ok(v) - }) - .map(|_| ()) - }) - .map_err(Into::into) - } - } -} - -/// The entry point to the contract for processing replies from sub-messages. For now it only -/// sets the NFT contract address. -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result { - let mut config: Config = CONFIG.load(deps.storage)?; - - if config.nft_addr != Addr::unchecked("") { - return Err(ContractError::Unauthorized {}); - } - - let res = parse_reply_instantiate_data(msg)?; - config.nft_addr = deps.api.addr_validate(res.contract_address.as_str())?; - - CONFIG.save(deps.storage, &config)?; - Ok(Response::new()) -} - -/// Creates NFT with specified parameters and connect it with delegated voting power -/// in percent into other account. -/// -/// * **percentage** is a percentage value to determine the amount of -/// voting power to delegate -/// -/// * **expire_time** is a point in time, at least a day in the future, at which the value of the -/// voting power will reach 0. -/// -/// * **token_id** is an NFT identifier. -/// -/// * **recipient** is an account to receive the delegated voting power. -pub fn create_delegation( - deps: DepsMut, - env: Env, - info: MessageInfo, - bps: u16, - expire_time: u64, - token_id: String, - recipient: String, -) -> Result { - let recipient_addr = deps.api.addr_validate(recipient.as_str())?; - let delegator = info.sender; - let cfg = CONFIG.load(deps.storage)?; - let block_period = get_period(env.block.time.seconds())?; - let exp_period = block_period + get_periods_count(expire_time); - - // We can create only one NFT for a specific token ID - let nft_helper = cw721_helpers::Cw721Contract::( - cfg.nft_addr.clone(), - PhantomData, - PhantomData, - ); - - let nft_instance: StdResult> = - nft_helper.nft_info(&deps.querier, &token_id); - - if nft_instance.is_ok() { - return Err(ContractError::DelegationTokenAlreadyExists(token_id)); - } - - let vp = get_voting_power(&deps.querier, &cfg.voting_escrow_addr, &delegator)?; - if vp.is_zero() { - return Err(ContractError::ZeroVotingPower {}); - } - - validate_parameters( - &deps.querier, - &cfg, - &delegator, - block_period, - exp_period, - bps, - None, - )?; - - let not_delegated_vp = calc_not_delegated_vp(deps.as_ref(), &delegator, vp, block_period)?; - let delegation = calc_delegation(not_delegated_vp, block_period, exp_period, bps)?; - - DELEGATED.save(deps.storage, (&delegator, token_id.clone()), &delegation)?; - TOKENS.save(deps.storage, token_id.clone(), &delegation)?; - - Ok(Response::default() - .add_attributes(vec![ - attr("action", "create_delegation"), - attr("recipient", recipient), - attr("token_id", token_id.clone()), - attr("expire_time", expire_time.to_string()), - attr("bps", bps.to_string()), - attr("delegated_voting_power", delegation.power.to_string()), - ]) - .add_submessage(SubMsg::new(WasmMsg::Execute { - contract_addr: cfg.nft_addr.to_string(), - msg: to_json_binary(&ExecuteMsgNFT::::Mint(MintMsg::< - Extension, - > { - token_id, - owner: recipient_addr.to_string(), - token_uri: None, - extension: None, - }))?, - funds: vec![], - }))) -} - -/// Extends a previously created delegation by a new specified parameters. -/// -/// * **percentage** is a percentage value to determine the amount of voting power to delegate. -/// -/// * **expire_time** is a point in time, at least a day in the future, at which the value of the -/// voting power will reach 0. -/// -/// * **token_id** is an NFT identifier. -/// -/// * **recipient** is an account to receive the delegated voting power. -pub fn extend_delegation( - deps: DepsMut, - env: Env, - info: MessageInfo, - bps: u16, - expire_time: u64, - token_id: String, -) -> Result { - let delegator = info.sender; - let cfg = CONFIG.load(deps.storage)?; - - let old_delegation = DELEGATED.load(deps.storage, (&delegator, token_id.clone()))?; - - let vp = get_voting_power(&deps.querier, &cfg.voting_escrow_addr, &delegator)?; - if vp.is_zero() { - return Err(ContractError::ZeroVotingPower {}); - } - - let block_period = get_period(env.block.time.seconds())?; - let exp_period = block_period + get_periods_count(expire_time); - - validate_parameters( - &deps.querier, - &cfg, - &delegator, - block_period, - exp_period, - bps, - Some(&old_delegation), - )?; - - let new_delegation = calc_extend_delegation( - deps.as_ref(), - &delegator, - vp, - &old_delegation, - block_period, - exp_period, - bps, - )?; - - DELEGATED.save( - deps.storage, - (&delegator, token_id.clone()), - &new_delegation, - )?; - TOKENS.save(deps.storage, token_id.clone(), &new_delegation)?; - - Ok(Response::default().add_attributes(vec![ - attr("action", "extend_delegation"), - attr("token_id", token_id), - attr("expire_time", expire_time.to_string()), - attr("bps", bps.to_string()), - attr("delegated_voting_power", new_delegation.power.to_string()), - ])) -} - -/// Updates contract parameters. -/// -/// * **new_voting_escrow** is a new address of Voting Escrow contract. -fn update_config( - deps: DepsMut, - info: MessageInfo, - new_voting_escrow: Option, -) -> Result { - let mut cfg = CONFIG.load(deps.storage)?; - - if info.sender != cfg.owner { - return Err(ContractError::Unauthorized {}); - } - - if let Some(new_voting_escrow) = new_voting_escrow { - cfg.voting_escrow_addr = deps.api.addr_validate(&new_voting_escrow)?; - } - - CONFIG.save(deps.storage, &cfg)?; - - Ok(Response::default().add_attribute("action", "execute_update_config")) -} - -/// Expose available contract queries. -/// -/// ## Queries -/// * **QueryMsg::Config {}** Fetch contract config -/// -/// * **QueryMsg::AdjustedBalance { account, timestamp }** Adjusted voting power balance after -/// accounting for delegations. -/// -/// * **QueryMsg::AlreadyDelegatedVP { account, timestamp }** Returns the amount of delegated -/// voting power according to the given parameters. -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Config {} => to_json_binary(&CONFIG.load(deps.storage)?), - QueryMsg::AdjustedBalance { account, timestamp } => { - to_json_binary(&adjusted_balance(deps, env, account, timestamp)?) - } - QueryMsg::DelegatedVotingPower { account, timestamp } => { - to_json_binary(&delegated_vp(deps, env, account, timestamp)?) - } - } -} - -/// Returns an adjusted voting power balance after accounting for delegations. -/// -/// * **account** is an address of the account to return adjusted balance. -/// -/// * **timestamp** is a point in time, at least a day in the future, at which the value of -/// the voting power will reach 0. -fn adjusted_balance( - deps: Deps, - env: Env, - account: String, - timestamp: Option, -) -> StdResult { - let account = deps.api.addr_validate(account.as_str())?; - let config = CONFIG.load(deps.storage)?; - - let mut current_vp = if let Some(timestamp) = timestamp { - get_voting_power_at( - &deps.querier, - &config.voting_escrow_addr, - &account, - timestamp, - )? - } else { - get_voting_power(&deps.querier, &config.voting_escrow_addr, &account)? - }; - - let block_period = get_period(timestamp.unwrap_or_else(|| env.block.time.seconds()))?; - let total_delegated_vp = calc_total_delegated_vp(deps, &account, block_period)?; - - // we must to subtract the delegated voting power - current_vp = current_vp.checked_sub(total_delegated_vp)?; - - let nft_helper = - cw721_helpers::Cw721Contract::(config.nft_addr, PhantomData, PhantomData); - - let mut account_tokens = vec![]; - let mut start_after = None; - - // we need to take all tokens for specified account - loop { - let tokens = nft_helper - .tokens(&deps.querier, account.clone(), start_after, Some(MAX_LIMIT))? - .tokens; - if tokens.is_empty() { - break; - } - start_after = tokens.last().cloned(); - account_tokens.extend(tokens); - } - - for token_id in account_tokens { - let token = TOKENS.load(deps.storage, token_id)?; - - if token.start <= block_period && token.expire_period > block_period { - current_vp += calc_voting_power(token.slope, token.power, token.start, block_period); - } - } - - Ok(current_vp) -} - -/// Returns an amount of delegated voting power. -/// -/// * **account** is an address of the account to return adjusted balance. -/// -/// * **timestamp** is an optional field that specifies the period for which the function -/// returns voting power. -fn delegated_vp( - deps: Deps, - env: Env, - account: String, - timestamp: Option, -) -> StdResult { - let account = deps.api.addr_validate(account.as_str())?; - let block_period = get_period(timestamp.unwrap_or_else(|| env.block.time.seconds()))?; - - calc_total_delegated_vp(deps, &account, block_period) -} diff --git a/contracts/voting_escrow_delegation/src/error.rs b/contracts/voting_escrow_delegation/src/error.rs deleted file mode 100644 index b61303cc..00000000 --- a/contracts/voting_escrow_delegation/src/error.rs +++ /dev/null @@ -1,42 +0,0 @@ -use cosmwasm_std::StdError; -use cw_utils::ParseReplyError; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("Unauthorized")] - Unauthorized {}, - - #[error("{0}")] - ParseReplyError(#[from] ParseReplyError), - - #[error("You can't delegate with zero voting power")] - ZeroVotingPower {}, - - #[error("You have already delegated all the voting power.")] - AllVotingPowerIsDelegated {}, - - #[error("The delegation period must be at least a week and not more than a user lock period.")] - DelegationPeriodError {}, - - #[error("New expiration date must be greater than previously set and less than or equal to user's end of voting power lock.")] - DelegationExtendPeriodError {}, - - #[error("Not enough voting power to proceed")] - NotEnoughVotingPower {}, - - #[error("The percentage range must be from 0 to 100.")] - PercentageError {}, - - #[error("A delegation with a token {0} already exists.")] - DelegationTokenAlreadyExists(String), - - #[error("New delegated voting power can not be less than it was previously.")] - DecreasedDelegatedVotingPower {}, - - #[error("Basic points conversion error. The basic points must be from 1 to 10000: {0}")] - BPSConversionError(u16), -} diff --git a/contracts/voting_escrow_delegation/src/helpers.rs b/contracts/voting_escrow_delegation/src/helpers.rs deleted file mode 100644 index 0abd2629..00000000 --- a/contracts/voting_escrow_delegation/src/helpers.rs +++ /dev/null @@ -1,160 +0,0 @@ -use crate::state::DELEGATED; -use crate::ContractError; -use astroport_governance::utils::calc_voting_power; -use astroport_governance::voting_escrow::get_lock_info; -use astroport_governance::voting_escrow_delegation::{Config, Token}; -use cosmwasm_std::{Addr, Deps, Order, QuerierWrapper, StdError, StdResult, Uint128}; - -const MAX_BPS_AMOUNT: u16 = 10000u16; -const MIN_BPS_AMOUNT: u16 = 1u16; - -/// Adjusting voting power according to the slope by specified percentage. -pub fn calc_delegation( - not_delegated_vp: Uint128, - block_period: u64, - exp_period: u64, - bps: u16, -) -> Result { - let vp_to_delegate = not_delegated_vp.multiply_ratio(bps, MAX_BPS_AMOUNT); - - let dt = Uint128::from(exp_period - block_period); - let slope = vp_to_delegate - .checked_div(dt) - .map_err(|e| ContractError::Std(e.into()))?; - let power = slope * dt; - - if power.is_zero() { - return Err(ContractError::NotEnoughVotingPower {}); - } - - Ok(Token { - power, - slope, - start: block_period, - expire_period: exp_period, - }) -} - -/// Calculates the total delegated voting power for specified account. -pub(crate) fn calc_total_delegated_vp( - deps: Deps, - delegator: &Addr, - block_period: u64, -) -> StdResult { - let delegates = DELEGATED - .prefix(delegator) - .range(deps.storage, None, None, Order::Ascending) - .filter_map(|pair| { - let (_, token) = match pair { - Ok(v) => v, - Err(e) => return Some(Err(e)), - }; - if token.start <= block_period && token.expire_period > block_period { - Some(Ok(token)) - } else { - None - } - }) - .collect::, StdError>>()?; - - let mut total_delegated_vp = Uint128::zero(); - for delegate in delegates { - total_delegated_vp += - calc_voting_power(delegate.slope, delegate.power, delegate.start, block_period); - } - - Ok(total_delegated_vp) -} - -/// Validates input parameters to create or extend a delegation. -pub fn validate_parameters( - querier: &QuerierWrapper, - cfg: &Config, - delegator: &Addr, - block_period: u64, - exp_period: u64, - bps: u16, - old_delegate: Option<&Token>, -) -> Result<(), ContractError> { - let user_lock = get_lock_info(querier, &cfg.voting_escrow_addr, delegator)?; - - // vxASTRO delegation must be at least WEEK and no more then lock end period - if (exp_period <= block_period) || (exp_period > user_lock.end) { - return Err(ContractError::DelegationPeriodError {}); - } - - if !(MIN_BPS_AMOUNT..=MAX_BPS_AMOUNT).contains(&bps) { - return Err(ContractError::BPSConversionError(bps)); - } - - if let Some(old_token) = old_delegate { - if exp_period <= old_token.expire_period { - return Err(ContractError::DelegationExtendPeriodError {}); - } - } - - Ok(()) -} - -/// Calculates available balance for a new delegation. -pub fn calc_not_delegated_vp( - deps: Deps, - delegator: &Addr, - vp: Uint128, - block_period: u64, -) -> Result { - let total_delegated_vp = calc_total_delegated_vp(deps, delegator, block_period)?; - - if vp <= total_delegated_vp { - return Err(ContractError::AllVotingPowerIsDelegated {}); - } - - Ok(vp - total_delegated_vp) -} - -/// Calculates the available balance for the specified delegation. -pub fn calc_extend_delegation( - deps: Deps, - delegator: &Addr, - vp: Uint128, - old_delegation: &Token, - block_period: u64, - exp_period: u64, - bps: u16, -) -> Result { - let not_delegated_vp = calc_not_delegated_vp(deps, delegator, vp, block_period)?; - - // we should deduct the previous delegation balance and assign a new delegation data - let new_delegation = if old_delegation.expire_period > block_period { - let old_delegation_vp = calc_voting_power( - old_delegation.slope, - old_delegation.power, - old_delegation.start, - block_period, - ); - - let new_delegation = calc_delegation( - not_delegated_vp + old_delegation_vp, - block_period, - exp_period, - bps, - )?; - - let new_delegation_vp = calc_voting_power( - new_delegation.slope, - new_delegation.power, - new_delegation.start, - block_period, - ); - - if old_delegation_vp > new_delegation_vp { - return Err(ContractError::DecreasedDelegatedVotingPower {}); - } - - new_delegation - } else { - calc_delegation(not_delegated_vp, block_period, exp_period, bps)? - }; - - Ok(new_delegation) -} diff --git a/contracts/voting_escrow_delegation/src/lib.rs b/contracts/voting_escrow_delegation/src/lib.rs deleted file mode 100644 index f59274b5..00000000 --- a/contracts/voting_escrow_delegation/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod contract; -mod error; -mod helpers; -pub mod state; -pub use crate::error::ContractError; diff --git a/contracts/voting_escrow_delegation/src/state.rs b/contracts/voting_escrow_delegation/src/state.rs deleted file mode 100644 index 968a1329..00000000 --- a/contracts/voting_escrow_delegation/src/state.rs +++ /dev/null @@ -1,16 +0,0 @@ -use astroport_governance::astroport::common::OwnershipProposal; -use astroport_governance::voting_escrow_delegation::{Config, Token}; -use cosmwasm_std::Addr; -use cw_storage_plus::{Item, Map}; - -/// Contains a proposal to change contract ownership -pub const OWNERSHIP_PROPOSAL: Item = Item::new("ownership_proposal"); - -/// Stores the contract config at the given key -pub const CONFIG: Item = Item::new("config"); - -/// Delegated voting power are stored using a (contract_addr => token_ID) key -pub const DELEGATED: Map<(&Addr, String), Token> = Map::new("delegated"); - -/// Delegated token history is stored using a token ID key -pub const TOKENS: Map = Map::new("tokens"); diff --git a/contracts/voting_escrow_delegation/tests/integration.rs b/contracts/voting_escrow_delegation/tests/integration.rs deleted file mode 100644 index b9b6147c..00000000 --- a/contracts/voting_escrow_delegation/tests/integration.rs +++ /dev/null @@ -1,914 +0,0 @@ -use astroport_governance::utils::WEEK; -use astroport_governance::voting_escrow_delegation::Config; -use astroport_governance::voting_escrow_delegation::QueryMsg; -use cosmwasm_std::{to_json_binary, Addr, Empty, QueryRequest, Uint128, WasmQuery}; -use cw721_base::{ExecuteMsg as ExecuteMsgNFT, Extension, MintMsg, QueryMsg as QueryMsgNFT}; -use cw_multi_test::Executor; - -use cw721::{ContractInfoResponse, Cw721ExecuteMsg, NumTokensResponse, TokensResponse}; - -use crate::test_helper::{mock_app, Helper}; - -mod test_helper; - -const EMPTY_TOKENS: Vec = vec![]; -const USER: &str = "user"; - -#[test] -fn config() { - let mut router = mock_app(); - let router_ref = &mut router; - let owner = Addr::unchecked("owner"); - let helper = Helper::init(router_ref, owner); - - let res = router - .wrap() - .query::(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: helper.delegation_instance.to_string(), - msg: to_json_binary(&QueryMsg::Config {}).unwrap(), - })) - .unwrap(); - - assert_eq!("owner", res.owner.to_string()); -} - -#[test] -fn mint() { - let mut router = mock_app(); - let router_ref = &mut router; - let owner = Addr::unchecked("owner"); - let helper = Helper::init(router_ref, owner); - - let resp = router_ref - .wrap() - .query::(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: helper.nft_instance.to_string(), - msg: to_json_binary(&QueryMsgNFT::::ContractInfo {}).unwrap(), - })) - .unwrap(); - assert_eq!("Delegated VP NFT", resp.name); - assert_eq!("VP-NFT", resp.symbol); - - // try to mint from random - let err = router_ref - .execute_contract( - Addr::unchecked("random"), - helper.nft_instance.clone(), - &ExecuteMsgNFT::::Mint(MintMsg:: { - token_id: "token_1".to_string(), - owner: USER.to_string(), - token_uri: None, - extension: None, - }), - &[], - ) - .unwrap_err(); - assert_eq!("Unauthorized", err.root_cause().to_string()); - - // try to mint from owner - router_ref - .execute_contract( - helper.delegation_instance.clone(), - helper.nft_instance.clone(), - &ExecuteMsgNFT::::Mint(MintMsg:: { - token_id: "token_1".to_string(), - owner: USER.to_string(), - token_uri: None, - extension: None, - }), - &[], - ) - .unwrap(); - - let resp = router_ref - .wrap() - .query::(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: helper.nft_instance.to_string(), - msg: to_json_binary(&QueryMsgNFT::::NumTokens {}).unwrap(), - })) - .unwrap(); - assert_eq!(1, resp.count); - - let resp = router_ref - .wrap() - .query::(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: helper.nft_instance.to_string(), - msg: to_json_binary(&QueryMsgNFT::::Tokens { - owner: USER.to_string(), - start_after: None, - limit: None, - }) - .unwrap(), - })) - .unwrap(); - assert_eq!(vec!["token_1",], resp.tokens); - - // try to mint from owner for the same token ID - let err = router_ref - .execute_contract( - helper.delegation_instance.clone(), - helper.nft_instance.clone(), - &ExecuteMsgNFT::::Mint(MintMsg:: { - token_id: "token_1".to_string(), - owner: USER.to_string(), - token_uri: None, - extension: None, - }), - &[], - ) - .unwrap_err(); - assert_eq!("token_id already claimed", err.root_cause().to_string()); - - // try to burn nft by token ID - let err = router_ref - .execute_contract( - helper.delegation_instance.clone(), - helper.nft_instance.clone(), - &ExecuteMsgNFT::::Burn { - token_id: "token_1".to_string(), - }, - &[], - ) - .unwrap_err(); - assert_eq!("Unauthorized", err.root_cause().to_string()); - - // check if token exists - let resp = router_ref - .wrap() - .query::(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: helper.nft_instance.to_string(), - msg: to_json_binary(&QueryMsgNFT::::Tokens { - owner: USER.to_string(), - start_after: None, - limit: None, - }) - .unwrap(), - })) - .unwrap(); - assert_eq!(vec!["token_1",], resp.tokens); -} - -#[test] -fn create_delegation() { - let mut router = mock_app(); - let router_ref = &mut router; - let owner = Addr::unchecked("owner"); - let delegator_helper = Helper::init(router_ref, owner); - - // try to create delegation from user with zero voting power - let err = delegator_helper - .create_delegation( - router_ref, - "user", - 5000, - WEEK, - "token_1".to_string(), - "user2".to_string(), - ) - .unwrap_err(); - assert_eq!( - "You can't delegate with zero voting power", - err.root_cause().to_string() - ); - - // Mint ASTRO, stake it and mint xASTRO - delegator_helper - .escrow_helper - .mint_xastro(router_ref, "user", 200); - delegator_helper - .escrow_helper - .check_xastro_balance(router_ref, "user", 200); - - // Create valid voting escrow lock - delegator_helper - .escrow_helper - .create_lock(router_ref, "user", WEEK * 2, 100f32) - .unwrap(); - // Check that 100 xASTRO were actually debited - delegator_helper - .escrow_helper - .check_xastro_balance(router_ref, "user", 100); - delegator_helper.escrow_helper.check_xastro_balance( - router_ref, - delegator_helper.escrow_helper.escrow_instance.as_str(), - 100, - ); - - // Mint ASTRO, stake it and mint xASTRO - delegator_helper - .escrow_helper - .mint_xastro(router_ref, "user2", 200); - delegator_helper - .escrow_helper - .check_xastro_balance(router_ref, "user2", 200); - - // Create valid voting escrow lock - delegator_helper - .escrow_helper - .create_lock(router_ref, "user2", WEEK * 2, 100f32) - .unwrap(); - // Check that 100 xASTRO were actually debited - delegator_helper - .escrow_helper - .check_xastro_balance(router_ref, "user2", 100); - delegator_helper.escrow_helper.check_xastro_balance( - router_ref, - delegator_helper.escrow_helper.escrow_instance.as_str(), - 200, - ); - - // check user's adjusted balance before create a delegation - let resp = delegator_helper - .adjusted_balance(router_ref, "user", None) - .unwrap(); - assert_eq!(Uint128::new(102_884_614), resp); - - // check user's nft tokens before create a delegation - let resp = delegator_helper - .nft_helper - .tokens(&router_ref.wrap().into(), "user", None, None) - .unwrap(); - assert_eq!(EMPTY_TOKENS, resp.tokens); - - // check user2's adjusted balance before create a delegation - let user2_vp_before_delegation = delegator_helper - .adjusted_balance(router_ref, "user2", None) - .unwrap(); - assert_eq!(Uint128::new(102_884_614), user2_vp_before_delegation); - - // check user2's nft tokens before create a delegation - let resp = delegator_helper - .nft_helper - .tokens(&router_ref.wrap().into(), "user2", None, None) - .unwrap(); - assert_eq!(EMPTY_TOKENS, resp.tokens); - - // try to create delegation with the 10_001 bps - let err = delegator_helper - .create_delegation( - router_ref, - "user", - 10001, - WEEK, - "token_1".to_string(), - "user2".to_string(), - ) - .unwrap_err(); - assert_eq!( - "Basic points conversion error. The basic points must be from 1 to 10000: 10001", - err.root_cause().to_string() - ); - - // try to create delegation with the 10_001 bps - let err = delegator_helper - .create_delegation( - router_ref, - "user", - 0, - WEEK, - "token_1".to_string(), - "user2".to_string(), - ) - .unwrap_err(); - assert_eq!( - "Basic points conversion error. The basic points must be from 1 to 10000: 0", - err.root_cause().to_string() - ); - - // create delegation for one week - delegator_helper - .create_delegation( - router_ref, - "user", - 10000, - WEEK, - "token_1".to_string(), - "user2".to_string(), - ) - .unwrap(); - - // try to create delegation with the same token ID - let err = delegator_helper - .create_delegation( - router_ref, - "user", - 10000, - WEEK, - "token_1".to_string(), - "user2".to_string(), - ) - .unwrap_err(); - assert_eq!( - "A delegation with a token token_1 already exists.", - err.root_cause().to_string() - ); - - // try create delegation without free voting power - let err = delegator_helper - .create_delegation( - router_ref, - "user", - 3000, - WEEK, - "token_2".to_string(), - "user2".to_string(), - ) - .unwrap_err(); - - assert_eq!( - "You have already delegated all the voting power.", - err.root_cause().to_string() - ); - - // check user's nft tokens - let resp = delegator_helper - .nft_helper - .tokens(&router_ref.wrap().into(), "user", None, None) - .unwrap(); - assert_eq!(EMPTY_TOKENS, resp.tokens); - - // check user2's nft tokens - let resp = delegator_helper - .nft_helper - .tokens(&router_ref.wrap().into(), "user2", None, None) - .unwrap(); - assert_eq!(vec!["token_1"], resp.tokens); - - // check user's balance after the delegation - let resp = delegator_helper - .adjusted_balance(router_ref, "user", None) - .unwrap(); - assert_eq!(Uint128::new(0), resp); - - // check user2's balance after the delegation - let user2_vp_after_delegation = delegator_helper - .adjusted_balance(router_ref, "user2", None) - .unwrap(); - assert_eq!(Uint128::new(205_769_228), user2_vp_after_delegation); - - let user_delegated_vp = delegator_helper - .delegated_balance(router_ref, "user", None) - .unwrap(); - - // check user's user_vp_after_delegation + user_delegated_vp = user_vp_before_delegation - assert_eq!( - user2_vp_after_delegation, - user_delegated_vp + user2_vp_before_delegation - ); - - router_ref.update_block(|block_info| { - block_info.time = block_info.time.plus_seconds(WEEK); - block_info.height += 1; - }); - - // check user's adjusted balance when delegation expired - let resp = delegator_helper - .adjusted_balance(router_ref, "user", None) - .unwrap(); - assert_eq!(Uint128::new(51_442_307), resp); - - // check user2's adjusted balance when delegation expired - let resp = delegator_helper - .adjusted_balance(router_ref, "user2", None) - .unwrap(); - assert_eq!(Uint128::new(51_442_307), resp); - - // try to transfer NFT to user2 - let err = router_ref - .execute_contract( - Addr::unchecked("user"), - delegator_helper.nft_instance.clone(), - &Cw721ExecuteMsg::TransferNft { - recipient: "user2".to_string(), - token_id: "token_1".to_string(), - }, - &[], - ) - .unwrap_err(); - assert_eq!("Unauthorized", err.root_cause().to_string()); -} - -#[test] -fn create_multiple_delegation() { - let mut router = mock_app(); - let router_ref = &mut router; - let owner = Addr::unchecked("owner"); - let delegator_helper = Helper::init(router_ref, owner); - - // Mint ASTRO, stake it and mint xASTRO - delegator_helper - .escrow_helper - .mint_xastro(router_ref, "user", 200); - delegator_helper - .escrow_helper - .check_xastro_balance(router_ref, "user", 200); - - // Create valid voting escrow lock - delegator_helper - .escrow_helper - .create_lock(router_ref, "user", WEEK * 10, 100f32) - .unwrap(); - - // Check that 100 xASTRO were actually debited - delegator_helper - .escrow_helper - .check_xastro_balance(router_ref, "user", 100); - delegator_helper.escrow_helper.check_xastro_balance( - router_ref, - delegator_helper.escrow_helper.escrow_instance.as_str(), - 100, - ); - - // Mint ASTRO, stake it and mint xASTRO - delegator_helper - .escrow_helper - .mint_xastro(router_ref, "user2", 200); - delegator_helper - .escrow_helper - .check_xastro_balance(router_ref, "user2", 200); - - // Create valid voting escrow lock - delegator_helper - .escrow_helper - .create_lock(router_ref, "user2", WEEK * 5, 100f32) - .unwrap(); - // Check that 100 xASTRO were actually debited - delegator_helper - .escrow_helper - .check_xastro_balance(router_ref, "user2", 100); - delegator_helper.escrow_helper.check_xastro_balance( - router_ref, - delegator_helper.escrow_helper.escrow_instance.as_str(), - 200, - ); - - // Mint ASTRO, stake it and mint xASTRO - delegator_helper - .escrow_helper - .mint_xastro(router_ref, "user3", 200); - delegator_helper - .escrow_helper - .check_xastro_balance(router_ref, "user3", 200); - - // Create valid voting escrow lock - delegator_helper - .escrow_helper - .create_lock(router_ref, "user3", WEEK, 100f32) - .unwrap(); - - // Check that 100 xASTRO were actually debited - delegator_helper - .escrow_helper - .check_xastro_balance(router_ref, "user3", 100); - delegator_helper.escrow_helper.check_xastro_balance( - router_ref, - delegator_helper.escrow_helper.escrow_instance.as_str(), - 300, - ); - - // try to create delegation for 1 week for user2 - delegator_helper - .create_delegation( - router_ref, - "user", - 3000, - WEEK, - "token_1".to_string(), - "user2".to_string(), - ) - .unwrap(); - - // try to create delegation for 3 weeks for user3 - delegator_helper - .create_delegation( - router_ref, - "user", - 3000, - WEEK * 3, - "token_2".to_string(), - "user3".to_string(), - ) - .unwrap(); - - // try to create delegation for 2 weeks for user1 - let err = delegator_helper - .create_delegation( - router_ref, - "user3", - 3000, - WEEK * 2, - "token_3".to_string(), - "user".to_string(), - ) - .unwrap_err(); - - assert_eq!( - "The delegation period must be at least a week and not more than a user lock period.", - err.root_cause().to_string() - ); - - // try to create delegation for 1 week for user1 - delegator_helper - .create_delegation( - router_ref, - "user3", - 3000, - WEEK, - "token_3".to_string(), - "user".to_string(), - ) - .unwrap(); - - // check the user's NFT. - let resp = delegator_helper - .nft_helper - .tokens(&router_ref.wrap().into(), "user", None, None) - .unwrap(); - assert_eq!(vec!["token_3"], resp.tokens); - - // check user's adjusted balance - let resp = delegator_helper - .adjusted_balance(router_ref, "user", None) - .unwrap(); - assert_eq!(Uint128::new(86_499_999), resp); - - // check the user2's NFT. - let resp = delegator_helper - .nft_helper - .tokens(&router_ref.wrap().into(), "user2", None, None) - .unwrap(); - assert_eq!(vec!["token_1"], resp.tokens); - - // check user2's adjusted balance - let resp = delegator_helper - .adjusted_balance(router_ref, "user2", None) - .unwrap(); - assert_eq!(Uint128::new(141_538_456), resp); - - // check user3's nft tokens - let resp = delegator_helper - .nft_helper - .tokens(&router_ref.wrap().into(), "user3", None, None) - .unwrap(); - assert_eq!(vec!["token_2"], resp.tokens); - - // check user3's adjusted balance - let resp = delegator_helper - .adjusted_balance(router_ref, "user3", None) - .unwrap(); - assert_eq!(Uint128::new(95_038_457), resp); - - router_ref.update_block(|block_info| { - block_info.time = block_info.time.plus_seconds(WEEK); - block_info.height += 1; - }); - - // try to create delegation without free voting power - let err = delegator_helper - .create_delegation( - router_ref, - "user3", - 3000, - WEEK, - "token_4".to_string(), - "user2".to_string(), - ) - .unwrap_err(); - - assert_eq!( - "You can't delegate with zero voting power", - err.root_cause().to_string() - ); - - // check user's adjusted balance when one delegation is expired - let resp = delegator_helper - .adjusted_balance(router_ref, "user", None) - .unwrap(); - assert_eq!(Uint128::new(86_961_535), resp); - - // check user2's adjusted balance when delegation expired - let resp = delegator_helper - .adjusted_balance(router_ref, "user2", None) - .unwrap(); - assert_eq!(Uint128::new(85_769_228), resp); - - // check user3's adjusted balance when lock is expired - let resp = delegator_helper - .adjusted_balance(router_ref, "user3", None) - .unwrap(); - assert_eq!(Uint128::new(16_019_228), resp); - - // try to transfer NFT with ID `token_1` from user1 to user3 - let err = router_ref - .execute_contract( - Addr::unchecked("user1"), - delegator_helper.nft_instance.clone(), - &Cw721ExecuteMsg::TransferNft { - recipient: "user3".to_string(), - token_id: "token_1".to_string(), - }, - &[], - ) - .unwrap_err(); - assert_eq!("Unauthorized", err.root_cause().to_string()); - - // try to transfer NFT with ID `token_1` from user2 to user3 - router_ref - .execute_contract( - Addr::unchecked("user2"), - delegator_helper.nft_instance.clone(), - &Cw721ExecuteMsg::TransferNft { - recipient: "user3".to_string(), - token_id: "token_1".to_string(), - }, - &[], - ) - .unwrap(); - - // check the user's NFT. - let resp = delegator_helper - .nft_helper - .tokens(&router_ref.wrap().into(), "user", None, None) - .unwrap(); - assert_eq!(vec!["token_3"], resp.tokens); - - // check the user2's NFT. - let resp = delegator_helper - .nft_helper - .tokens(&router_ref.wrap().into(), "user2", None, None) - .unwrap(); - assert_eq!(EMPTY_TOKENS, resp.tokens); - - // check the user3's NFT. - let resp = delegator_helper - .nft_helper - .tokens(&router_ref.wrap().into(), "user3", None, None) - .unwrap(); - assert_eq!(vec!["token_1", "token_2"], resp.tokens); - - // check user's adjusted balance after transferred token - let resp = delegator_helper - .adjusted_balance(router_ref, "user", None) - .unwrap(); - assert_eq!(Uint128::new(86_961_535), resp); - - // check user2's adjusted balance when delegation expired - let resp = delegator_helper - .adjusted_balance(router_ref, "user2", None) - .unwrap(); - assert_eq!(Uint128::new(85_769_228), resp); - - // check user3's adjusted balance when lock is expired and token_1 is expired - let resp = delegator_helper - .adjusted_balance(router_ref, "user3", None) - .unwrap(); - assert_eq!(Uint128::new(16_019_228), resp); - - router_ref.update_block(|block_info| { - block_info.time = block_info.time.plus_seconds(WEEK * 8); - block_info.height += 1; - }); - - // check the user's NFT. - let resp = delegator_helper - .nft_helper - .tokens(&router_ref.wrap().into(), "user", None, None) - .unwrap(); - assert_eq!(vec!["token_3"], resp.tokens); - - // check the user2's NFT. - let resp = delegator_helper - .nft_helper - .tokens(&router_ref.wrap().into(), "user2", None, None) - .unwrap(); - assert_eq!(EMPTY_TOKENS, resp.tokens); - - // check the user3's NFT. - let resp = delegator_helper - .nft_helper - .tokens(&router_ref.wrap().into(), "user3", None, None) - .unwrap(); - assert_eq!(vec!["token_1", "token_2"], resp.tokens); - - // check user's adjusted balance after transferred token - let resp = delegator_helper - .adjusted_balance(router_ref, "user", None) - .unwrap(); - assert_eq!(Uint128::new(11_442_307), resp); - - // check user2's adjusted balance when user2's lock and tokens are expired - let resp = delegator_helper - .adjusted_balance(router_ref, "user2", None) - .unwrap(); - assert_eq!(Uint128::new(0), resp); - - // check user3's adjusted balance when user3's lock and tokens are expired - let resp = delegator_helper - .adjusted_balance(router_ref, "user3", None) - .unwrap(); - assert_eq!(Uint128::new(0), resp); -} - -#[test] -fn extend_delegation() { - let mut router = mock_app(); - let router_ref = &mut router; - let owner = Addr::unchecked("owner"); - let delegator_helper = Helper::init(router_ref, owner); - - // Mint ASTRO, stake it and mint xASTRO - delegator_helper - .escrow_helper - .mint_xastro(router_ref, "user", 100); - delegator_helper - .escrow_helper - .check_xastro_balance(router_ref, "user", 100); - - // Create valid voting escrow lock - delegator_helper - .escrow_helper - .create_lock(router_ref, "user", WEEK * 5, 100f32) - .unwrap(); - - // Check that 90 xASTRO were actually debited - delegator_helper - .escrow_helper - .check_xastro_balance(router_ref, "user", 0); - delegator_helper.escrow_helper.check_xastro_balance( - router_ref, - delegator_helper.escrow_helper.escrow_instance.as_str(), - 100, - ); - - // Mint ASTRO, stake it and mint xASTRO - delegator_helper - .escrow_helper - .mint_xastro(router_ref, "user2", 100); - delegator_helper - .escrow_helper - .check_xastro_balance(router_ref, "user2", 100); - - // Create valid voting escrow lock - delegator_helper - .escrow_helper - .create_lock(router_ref, "user2", WEEK * 2, 100f32) - .unwrap(); - // Check that 90 xASTRO were actually debited - delegator_helper - .escrow_helper - .check_xastro_balance(router_ref, "user2", 0); - delegator_helper.escrow_helper.check_xastro_balance( - router_ref, - delegator_helper.escrow_helper.escrow_instance.as_str(), - 200, - ); - - // try to create delegation to user2 - delegator_helper - .create_delegation( - router_ref, - "user", - 5000, - WEEK * 3, - "token_1".to_string(), - "user2".to_string(), - ) - .unwrap(); - - // check user's nft token - let resp = delegator_helper - .nft_helper - .tokens(&router_ref.wrap().into(), "user", None, None) - .unwrap(); - assert_eq!(EMPTY_TOKENS, resp.tokens); - - // check user2's nft token - let resp = delegator_helper - .nft_helper - .tokens(&router_ref.wrap().into(), "user2", None, None) - .unwrap(); - assert_eq!(vec!["token_1"], resp.tokens); - - // check user's adjusted balance - let resp = delegator_helper - .adjusted_balance(router_ref, "user", None) - .unwrap(); - assert_eq!(Uint128::new(53_605_768), resp); - - // check user2's adjusted balance - let resp = delegator_helper - .adjusted_balance(router_ref, "user2", None) - .unwrap(); - assert_eq!(Uint128::new(156_490_381), resp); - - router_ref.update_block(|block_info| { - block_info.time = block_info.time.plus_seconds(WEEK); - block_info.height += 1; - }); - - // check user's nft token - let resp = delegator_helper - .nft_helper - .tokens(&router_ref.wrap().into(), "user", None, None) - .unwrap(); - assert_eq!(EMPTY_TOKENS, resp.tokens); - - // check user2's nft token - let resp = delegator_helper - .nft_helper - .tokens(&router_ref.wrap().into(), "user2", None, None) - .unwrap(); - assert_eq!(vec!["token_1"], resp.tokens); - - // check user's adjusted balance - let resp = delegator_helper - .adjusted_balance(router_ref, "user", None) - .unwrap(); - assert_eq!(Uint128::new(50_032_050), resp); - - // check user2's adjusted balance - let resp = delegator_helper - .adjusted_balance(router_ref, "user2", None) - .unwrap(); - assert_eq!(Uint128::new(87_179_485), resp); - - // check's that we cannot create a delegation for a smaller amount - let err = delegator_helper - .extend_delegation(router_ref, "user", 4000, WEEK * 3, "token_1".to_string()) - .unwrap_err(); - assert_eq!( - "New delegated voting power can not be less than it was previously.", - err.root_cause().to_string() - ); - - // try to extend delegation period - delegator_helper - .extend_delegation(router_ref, "user", 6000, WEEK * 3, "token_1".to_string()) - .unwrap(); - - // check user's nft token - let resp = delegator_helper - .nft_helper - .tokens(&router_ref.wrap().into(), "user", None, None) - .unwrap(); - assert_eq!(EMPTY_TOKENS, resp.tokens); - - // check user2's nft token - let resp = delegator_helper - .nft_helper - .tokens(&router_ref.wrap().into(), "user2", None, None) - .unwrap(); - assert_eq!(vec!["token_1"], resp.tokens); - - // check user's adjusted balance - let resp = delegator_helper - .adjusted_balance(router_ref, "user", None) - .unwrap(); - assert_eq!(Uint128::new(34_307_693), resp); - - // check user2's adjusted balance - let resp = delegator_helper - .adjusted_balance(router_ref, "user2", None) - .unwrap(); - assert_eq!(Uint128::new(102_903_842), resp); - - router_ref.update_block(|block_info| { - block_info.time = block_info.time.plus_seconds(WEEK * 3); - block_info.height += 1; - }); - - // check user's nft token - let resp = delegator_helper - .nft_helper - .tokens(&router_ref.wrap().into(), "user", None, None) - .unwrap(); - assert_eq!(EMPTY_TOKENS, resp.tokens); - - // check user2's nft token - let resp = delegator_helper - .nft_helper - .tokens(&router_ref.wrap().into(), "user2", None, None) - .unwrap(); - assert_eq!(vec!["token_1"], resp.tokens); - - // check user's adjusted balance - let resp = delegator_helper - .adjusted_balance(router_ref, "user", None) - .unwrap(); - assert_eq!(Uint128::new(21_442_307), resp); - - // check user2's adjusted balance - let resp = delegator_helper - .adjusted_balance(router_ref, "user2", None) - .unwrap(); - assert_eq!(Uint128::new(0), resp); - - // try to extend delegation period - let err = delegator_helper - .extend_delegation(router_ref, "user", 9000, WEEK * 3, "token_1".to_string()) - .unwrap_err(); - assert_eq!( - "The delegation period must be at least a week and not more than a user lock period.", - err.root_cause().to_string() - ); -} diff --git a/contracts/voting_escrow_delegation/tests/simulation.rs b/contracts/voting_escrow_delegation/tests/simulation.rs deleted file mode 100644 index 522ef146..00000000 --- a/contracts/voting_escrow_delegation/tests/simulation.rs +++ /dev/null @@ -1,371 +0,0 @@ -use anyhow::Result; -use astroport_governance::utils::WEEK; - -use crate::test_helper::{mock_app, Helper}; -use cosmwasm_std::Addr; -use cw_multi_test::{next_block, App, AppResponse}; - -mod test_helper; - -#[derive(Clone, Debug)] -enum Event { - CreateDelegation(u16, u64, String, String), - ExtendDelegation(u16, u64, String), -} - -use Event::*; - -struct Simulator { - helper: Helper, - router: App, -} - -impl Simulator { - fn new() -> Self { - let mut router = mock_app(); - Self { - helper: Helper::init(&mut router, Addr::unchecked("owner")), - router, - } - } - - fn mint(&mut self, user: &str, amount: u128) { - self.helper - .escrow_helper - .mint_xastro(&mut self.router, user, amount as u64) - } - - fn create_lock(&mut self, user: &str, amount: f64, interval: u64) -> Result { - self.helper - .escrow_helper - .create_lock(&mut self.router, user, interval, amount as f32) - } - - fn app_next_period(&mut self) { - self.router.update_block(next_block); - self.router - .update_block(|block| block.time = block.time.plus_seconds(WEEK)); - } - - fn create_delegation( - &mut self, - user: &str, - bps: u16, - expire_time: u64, - token_id: String, - recipient: String, - ) -> Result { - self.helper.create_delegation( - &mut self.router, - user, - bps, - expire_time, - token_id, - recipient, - ) - } - - fn extend_delegation( - &mut self, - user: &str, - bps: u16, - expire_time: u64, - token_id: String, - ) -> Result { - self.helper - .extend_delegation(&mut self.router, user, bps, expire_time, token_id) - } - - fn event_router(&mut self, user: &str, event: Event) { - println!("User {} Event {:?}", user, event); - match event { - Event::CreateDelegation(bps, expire_time, token_id, recipient) => { - if let Err(err) = - self.create_delegation(user, bps, expire_time, token_id, recipient) - { - dbg!(err); - } - } - Event::ExtendDelegation(percentage, expire_time, token_id) => { - if let Err(err) = self.extend_delegation(user, percentage, expire_time, token_id) { - dbg!(err); - } - } - } - } -} - -use proptest::prelude::*; - -const MAX_PERIOD: usize = 10; -const MAX_USERS: usize = 6; -const MAX_TOKENS: usize = 10; -const MAX_EVENTS: usize = 100; - -fn events_strategy() -> impl Strategy { - prop_oneof![ - ( - 1u16..=100u16, - 1..MAX_PERIOD, - prop::collection::vec("[a-z]{4,32}", 1..MAX_USERS), - prop::collection::vec("[t-z]{6,32}", 1..MAX_TOKENS) - ) - .prop_map(|(a, b, c, d)| { - Event::CreateDelegation( - a, - WEEK * b as u64, - c.iter().next().unwrap().to_string(), - d.iter().next().unwrap().to_string(), - ) - }), - ( - 1u16..=100u16, - 1..MAX_PERIOD, - prop::collection::vec("[t-z]{2,32}", 1..MAX_TOKENS) - ) - .prop_map(|(a, b, c)| { - Event::ExtendDelegation(a, WEEK * b as u64, c.iter().next().unwrap().to_string()) - }), - ] -} - -fn generate_cases() -> impl Strategy, Vec<(usize, String, String, Event)>)> { - let users_strategy = prop::collection::vec("[a-z]{4,32}", 1..MAX_USERS); - - users_strategy.prop_flat_map(|users| { - ( - Just(users.clone()), - prop::collection::vec( - ( - 1..=MAX_PERIOD, - prop::sample::select(users.clone()), - prop::sample::select(users.clone()), - events_strategy(), - ), - 0..MAX_EVENTS, - ), - ) - }) -} - -proptest! { - #[test] - fn run_simulations - ( - case in generate_cases() - ) { - let mut events: Vec> = vec![vec![]; MAX_PERIOD + 1]; - let (users, events_tuples) = case; - for (period, user, recipient, event) in events_tuples { - events[period].push((user.to_string(), recipient.to_string(), event)); - } - - let mut simulator = Simulator::new(); - for user in users { - simulator.mint(user.as_str(), 10000); - simulator.create_lock(user.as_str(), 500 as f64, WEEK * 11).unwrap(); - } - - for period in 1..=MAX_PERIOD { - if let Some(period_events) = events.get(period) { - if !period_events.is_empty() { - println!("Period {}:", period); - } - for (user, recipient, event) in period_events { - // check user's balance before the delegation - let user_balance_before = simulator - .helper - .adjusted_balance(&mut simulator.router, user, None) - .unwrap(); - - // check user's delegated balance before the delegation - let user_delegated_balance_before = simulator - .helper - .delegated_balance(&mut simulator.router, user, None) - .unwrap(); - - // check recipient's balance before the delegation - let recipient_balance_before = simulator - .helper - .adjusted_balance(&mut simulator.router, recipient, None) - .unwrap(); - - // check recipient's delegated balance before the delegation - let recipient_delegated_balance_before = simulator - .helper - .delegated_balance(&mut simulator.router, recipient, None) - .unwrap(); - - // try to execute user's event - simulator.event_router(user, event.clone()); - - // check user's balance after the delegation - let user_balance_after = simulator - .helper - .adjusted_balance(&mut simulator.router, user, None) - .unwrap(); - - // check user's delegated balance - let user_delegated_balance_after = simulator - .helper - .delegated_balance(&mut simulator.router, user, None) - .unwrap(); - - // check recipient's balance after the delegation - let recipient_balance_after = simulator - .helper - .adjusted_balance(&mut simulator.router, recipient, None) - .unwrap(); - - // check recipient's delegated balance after the delegation - let recipient_delegated_balance_after = simulator - .helper - .delegated_balance(&mut simulator.router, recipient, None) - .unwrap(); - - // check user's balance - assert_eq!( - user_balance_after, - user_balance_before - - (user_delegated_balance_after - user_delegated_balance_before) - ); - - // check recipient's balance - assert_eq!( - recipient_balance_after, - recipient_balance_before - - (recipient_delegated_balance_after - recipient_delegated_balance_before) - ); - } - } - - simulator.app_next_period() - } - } -} - -#[test] -fn exact_simulation() { - let case = ( - ["user1", "user2"], - [ - ( - 1, - "user1", - "user2", - CreateDelegation(10000, WEEK * 2, "token_1".to_string(), "user2".to_string()), - ), - ( - 1, - "user2", - "user1", - CreateDelegation(5000, WEEK * 2, "token_2".to_string(), "user1".to_string()), - ), - ( - 2, - "user2", - "user1", - CreateDelegation(3000, WEEK * 2, "token_3".to_string(), "user1".to_string()), - ), - ( - 3, - "user2", - "user1", - ExtendDelegation(7000, WEEK * 5, "token_2".to_string()), - ), - ( - 4, - "user1", - "user2", - ExtendDelegation(6000, WEEK * 4, "token_1".to_string()), - ), - ( - 5, - "user1", - "user3", - CreateDelegation(10000, WEEK * 4, "token_4".to_string(), "user3".to_string()), - ), - ( - 6, - "user2", - "user1", - CreateDelegation(10000, WEEK * 4, "token_5".to_string(), "user1".to_string()), - ), - ], - ); - - let mut events: Vec> = vec![vec![]; MAX_PERIOD + 1]; - let (users, events_tuples) = case; - for (period, user, recipient, event) in events_tuples { - events[period].push((user.to_string(), recipient.to_string(), event)); - } - - let mut simulator = Simulator::new(); - for user in users { - simulator.mint(user, 10000); - simulator.create_lock(user, 500 as f64, WEEK * 10).unwrap(); - } - - for period in 1..=MAX_PERIOD { - if let Some(period_events) = events.get(period) { - if !period_events.is_empty() { - println!("Period {}:", period); - } - for (user, recipient, event) in period_events { - // check user's balance before the delegation - let user_balance_before = simulator - .helper - .adjusted_balance(&mut simulator.router, user, None) - .unwrap(); - - let user_delegated_balance_before = simulator - .helper - .delegated_balance(&mut simulator.router, user, None) - .unwrap(); - - // check recipient's balance before the delegation - let recipient_balance_before = simulator - .helper - .adjusted_balance(&mut simulator.router, recipient, None) - .unwrap(); - - // try to execute user's event - simulator.event_router(user, event.clone()); - - // check user's balance after the delegation - let user_balance_after = simulator - .helper - .adjusted_balance(&mut simulator.router, user, None) - .unwrap(); - - // check user's delegated balance - let user_delegated_balance_after = simulator - .helper - .delegated_balance(&mut simulator.router, user, None) - .unwrap(); - - // check recipient's balance after the delegation - let recipient_balance_after = simulator - .helper - .adjusted_balance(&mut simulator.router, recipient, None) - .unwrap(); - - // check user's balance - assert_eq!( - user_balance_after, - user_balance_before - - (user_delegated_balance_after - user_delegated_balance_before) - ); - - // check recipient's balance - assert_eq!( - recipient_balance_after, - recipient_balance_before - + (user_delegated_balance_after - user_delegated_balance_before) - ); - } - } - - simulator.app_next_period() - } -} diff --git a/contracts/voting_escrow_delegation/tests/test_helper.rs b/contracts/voting_escrow_delegation/tests/test_helper.rs deleted file mode 100644 index 32da8d92..00000000 --- a/contracts/voting_escrow_delegation/tests/test_helper.rs +++ /dev/null @@ -1,190 +0,0 @@ -use anyhow::Result; -use astroport_governance::utils::EPOCH_START; -use astroport_governance::voting_escrow_delegation::Config; -use astroport_governance::voting_escrow_delegation::{InstantiateMsg, QueryMsg}; -use astroport_tests::escrow_helper::EscrowHelper; -use cosmwasm_std::{to_json_binary, Addr, Empty, QueryRequest, StdResult, Uint128, WasmQuery}; -use cw_multi_test::{App, AppResponse, Contract, ContractWrapper, Executor}; - -use astroport_governance::voting_escrow_delegation::ExecuteMsg; -use cw721_base::helpers::Cw721Contract; - -pub struct Helper { - pub escrow_helper: EscrowHelper, - pub delegation_instance: Addr, - pub nft_instance: Addr, - pub nft_helper: Cw721Contract, -} - -impl Helper { - pub fn contract_escrow_delegation_template() -> Box> { - let contract = ContractWrapper::new_with_empty( - voting_escrow_delegation::contract::execute, - voting_escrow_delegation::contract::instantiate, - voting_escrow_delegation::contract::query, - ) - .with_reply_empty(voting_escrow_delegation::contract::reply); - Box::new(contract) - } - - pub fn contract_nft_template() -> Box> { - let contract = ContractWrapper::new( - astroport_nft::contract::execute, - astroport_nft::contract::instantiate, - astroport_nft::contract::query, - ); - Box::new(contract) - } - - fn instantiate_delegation( - router: &mut App, - owner: Addr, - escrow_addr: Addr, - delegation_id: u64, - nft_id: u64, - ) -> (Addr, Addr) { - let delegation_addr = router - .instantiate_contract( - delegation_id, - owner.clone(), - &InstantiateMsg { - owner: owner.to_string(), - nft_code_id: nft_id, - voting_escrow_addr: escrow_addr.to_string(), - }, - &[], - String::from("Astroport Escrow Delegation"), - None, - ) - .unwrap(); - - let res = router - .wrap() - .query::(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: delegation_addr.to_string(), - msg: to_json_binary(&QueryMsg::Config {}).unwrap(), - })) - .unwrap(); - - (delegation_addr, res.nft_addr) - } - - pub fn init(router: &mut App, owner: Addr) -> Self { - let escrow_helper = EscrowHelper::init(router, owner.clone()); - - let delegation_id = router.store_code(Helper::contract_escrow_delegation_template()); - let nft_id = router.store_code(Helper::contract_nft_template()); - - let (delegation_addr, nft_addr) = Helper::instantiate_delegation( - router, - owner, - escrow_helper.escrow_instance.clone(), - delegation_id, - nft_id, - ); - - let nft_helper = cw721_base::helpers::Cw721Contract( - nft_addr.clone(), - Default::default(), - Default::default(), - ); - - escrow_helper.mint_xastro(router, "owner", 1001); - - Helper { - escrow_helper, - delegation_instance: delegation_addr, - nft_instance: nft_addr, - nft_helper, - } - } - - pub fn create_delegation( - &self, - router: &mut App, - user: &str, - bps: u16, - expire_time: u64, - token_id: String, - recipient: String, - ) -> Result { - router.execute_contract( - Addr::unchecked(user), - self.delegation_instance.clone(), - &ExecuteMsg::CreateDelegation { - bps, - expire_time, - token_id, - recipient, - }, - &[], - ) - } - - pub fn extend_delegation( - &self, - router: &mut App, - user: &str, - bps: u16, - expire_time: u64, - token_id: String, - ) -> Result { - router.execute_contract( - Addr::unchecked(user), - self.delegation_instance.clone(), - &ExecuteMsg::ExtendDelegation { - bps, - expire_time, - token_id, - }, - &[], - ) - } - - pub fn adjusted_balance( - &self, - router: &mut App, - user: &str, - timestamp: Option, - ) -> StdResult { - router - .wrap() - .query::(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: self.delegation_instance.to_string(), - msg: to_json_binary(&QueryMsg::AdjustedBalance { - account: user.to_string(), - timestamp, - }) - .unwrap(), - })) - } - - pub fn delegated_balance( - &self, - router: &mut App, - user: &str, - timestamp: Option, - ) -> StdResult { - router - .wrap() - .query::(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: self.delegation_instance.to_string(), - msg: to_json_binary(&QueryMsg::DelegatedVotingPower { - account: user.to_string(), - timestamp, - }) - .unwrap(), - })) - } -} - -pub fn mock_app() -> App { - let mut app = App::default(); - - app.update_block(|bi| { - bi.time = bi.time.plus_seconds(EPOCH_START); - bi.height += 1; - }); - - app -} diff --git a/packages/astroport-governance/src/lib.rs b/packages/astroport-governance/src/lib.rs index b973465c..69787fcd 100644 --- a/packages/astroport-governance/src/lib.rs +++ b/packages/astroport-governance/src/lib.rs @@ -1,3 +1,5 @@ +pub use astroport; + pub mod assembly; pub mod builder_unlock; pub mod escrow_fee_distributor; @@ -5,15 +7,11 @@ pub mod generator_controller; pub mod generator_controller_lite; pub mod hub; pub mod interchain; -pub mod nft; pub mod outpost; pub mod utils; pub mod voting_escrow; -pub mod voting_escrow_delegation; pub mod voting_escrow_lite; -pub use astroport; - // Default pagination constants -pub const DEFAULT_LIMIT: u32 = 10; -pub const MAX_LIMIT: u32 = 30; +pub const DEFAULT_LIMIT: u32 = 30; +pub const MAX_LIMIT: u32 = 100; diff --git a/packages/astroport-governance/src/nft.rs b/packages/astroport-governance/src/nft.rs deleted file mode 100644 index c57a7f94..00000000 --- a/packages/astroport-governance/src/nft.rs +++ /dev/null @@ -1,5 +0,0 @@ -use cosmwasm_schema::cw_serde; - -/// This structure describes a migration message. -#[cw_serde] -pub struct MigrateMsg {} diff --git a/packages/astroport-governance/src/utils.rs b/packages/astroport-governance/src/utils.rs index ec0f261f..4f099e3a 100644 --- a/packages/astroport-governance/src/utils.rs +++ b/packages/astroport-governance/src/utils.rs @@ -1,6 +1,5 @@ use cosmwasm_std::{ - Addr, ChannelResponse, Decimal, Fraction, IbcQuery, OverflowError, QuerierWrapper, StdError, - StdResult, Uint128, Uint256, Uint64, + Addr, ChannelResponse, IbcQuery, QuerierWrapper, StdError, StdResult, Uint128, Uint64, }; use crate::hub::HubBalance; @@ -53,68 +52,6 @@ pub fn get_lite_periods_count(interval: u64) -> u64 { interval / LITE_VOTING_PERIOD } -/// This trait was implemented to eliminate Decimal rounding problems. -trait DecimalRoundedCheckedMul { - fn checked_mul(self, other: Uint128) -> Result; -} - -pub trait DecimalCheckedOps { - fn checked_add(self, other: Decimal) -> Result; - fn checked_mul_uint128(self, other: Uint128) -> Result; -} - -impl DecimalRoundedCheckedMul for Decimal { - fn checked_mul(self, other: Uint128) -> Result { - if self.is_zero() || other.is_zero() { - return Ok(Uint128::zero()); - } - let numerator = other.full_mul(self.numerator()); - let multiply_ratio = numerator / Uint256::from(self.denominator()); - if multiply_ratio > Uint256::from(Uint128::MAX) { - Err(OverflowError::new( - cosmwasm_std::OverflowOperation::Mul, - self, - other, - )) - } else { - let mut result: Uint128 = multiply_ratio.try_into().unwrap(); - let rem: Uint128 = numerator - .checked_rem(Uint256::from(self.denominator())) - .unwrap() - .try_into() - .unwrap(); - // 0.5 in Decimal - if rem.u128() >= 500000000000000000_u128 { - result += Uint128::from(1_u128); - } - Ok(result) - } - } -} - -impl DecimalCheckedOps for Decimal { - fn checked_add(self, other: Decimal) -> Result { - self.numerator() - .checked_add(other.numerator()) - .map(|_| self + other) - } - fn checked_mul_uint128(self, other: Uint128) -> Result { - if self.is_zero() || other.is_zero() { - return Ok(Uint128::zero()); - } - let multiply_ratio = other.full_mul(self.numerator()) / Uint256::from(self.denominator()); - if multiply_ratio > Uint256::from(Uint128::MAX) { - Err(OverflowError::new( - cosmwasm_std::OverflowOperation::Mul, - self, - other, - )) - } else { - Ok(multiply_ratio.try_into().unwrap()) - } - } -} - /// Main function used to calculate a user's voting power at a specific period as: previous_power - slope*(x - previous_x). pub fn calc_voting_power( slope: Uint128, diff --git a/packages/astroport-governance/src/voting_escrow.rs b/packages/astroport-governance/src/voting_escrow.rs index e92e8e49..f304c8b0 100644 --- a/packages/astroport-governance/src/voting_escrow.rs +++ b/packages/astroport-governance/src/voting_escrow.rs @@ -1,22 +1,6 @@ -use crate::voting_escrow::QueryMsg::{ - LockInfo, TotalVotingPower, TotalVotingPowerAt, UserVotingPower, UserVotingPowerAt, -}; use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Binary, Decimal, QuerierWrapper, StdResult, Uint128}; -use cw20::{ - BalanceResponse, Cw20ReceiveMsg, DownloadLogoResponse, Logo, MarketingInfoResponse, - TokenInfoResponse, -}; -use std::fmt; - -/// ## Pagination settings -/// The maximum amount of items that can be read at once from -pub const MAX_LIMIT: u32 = 30; - -/// The default amount of items to read from -pub const DEFAULT_LIMIT: u32 = 10; - -pub const DEFAULT_PERIODS_LIMIT: u64 = 20; +use cosmwasm_std::Uint128; +use cw20::{BalanceResponse, DownloadLogoResponse, Logo, MarketingInfoResponse, TokenInfoResponse}; /// This structure stores marketing information for vxASTRO. #[cw_serde] @@ -34,12 +18,8 @@ pub struct UpdateMarketingInfo { /// This structure stores general parameters for the vxASTRO contract. #[cw_serde] pub struct InstantiateMsg { - /// The vxASTRO contract owner - pub owner: String, - /// Address that's allowed to black or whitelist contracts - pub guardian_addr: Option, - /// xASTRO token address - pub deposit_token_addr: String, + /// xASTRO denom + pub deposit_denom: String, /// Marketing info for vxASTRO pub marketing: Option, /// The list of whitelisted logo urls prefixes @@ -49,24 +29,14 @@ pub struct InstantiateMsg { /// This structure describes the execute functions in the contract. #[cw_serde] pub enum ExecuteMsg { - /// Extend the lockup time for your staked xASTRO - ExtendLockTime { time: u64 }, - /// Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received - /// template. - Receive(Cw20ReceiveMsg), + /// Create a vxASTRO position and lock xASTRO + Lock { receiver: Option }, + /// Unlock xASTRO from the vxASTRO contract + Unlock {}, + /// Cancel unlocking + Relock {}, /// Withdraw xASTRO from the vxASTRO contract Withdraw {}, - /// Propose a new owner for the contract - ProposeNewOwner { new_owner: String, expires_in: u64 }, - /// Remove the ownership transfer proposal - DropOwnershipProposal {}, - /// Claim contract ownership - ClaimOwnership {}, - /// Add or remove accounts from the blacklist - UpdateBlacklist { - append_addrs: Option>, - remove_addrs: Option>, - }, /// Update the marketing info for the vxASTRO contract UpdateMarketing { /// A URL pointing to the project behind this token @@ -78,56 +48,14 @@ pub enum ExecuteMsg { }, /// Upload a logo for vxASTRO UploadLogo(Logo), - /// Update config - UpdateConfig { new_guardian: Option }, /// Set whitelisted logo urls SetLogoUrlsWhitelist { whitelist: Vec }, } -/// This structure describes a CW20 hook message. -#[cw_serde] -pub enum Cw20HookMsg { - /// Create a vxASTRO position and lock xASTRO for `time` amount of time - CreateLock { time: u64 }, - /// Deposit xASTRO in another user's vxASTRO position - DepositFor { user: String }, - /// Add more xASTRO to your vxASTRO position - ExtendLockAmount {}, -} - -/// This enum describes voters status. -#[cw_serde] -pub enum BlacklistedVotersResponse { - /// Voters are blacklisted - VotersBlacklisted {}, - /// Returns a voter that is not blacklisted. - VotersNotBlacklisted { voter: String }, -} - -impl fmt::Display for BlacklistedVotersResponse { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - BlacklistedVotersResponse::VotersBlacklisted {} => write!(f, "Voters are blacklisted!"), - BlacklistedVotersResponse::VotersNotBlacklisted { voter } => { - write!(f, "Voter is not blacklisted: {voter}") - } - } - } -} - /// This structure describes the query messages available in the contract. #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { - /// Checks if specified addresses are blacklisted - #[returns(BlacklistedVotersResponse)] - CheckVotersAreBlacklisted { voters: Vec }, - /// Return the blacklisted voters - #[returns(Vec)] - BlacklistedVoters { - start_after: Option, - limit: Option, - }, /// Return the user's vxASTRO balance #[returns(BalanceResponse)] Balance { address: String }, @@ -141,147 +69,32 @@ pub enum QueryMsg { #[returns(DownloadLogoResponse)] DownloadLogo {}, /// Return the current total amount of vxASTRO - #[returns(VotingPowerResponse)] - TotalVotingPower {}, - /// Return the total amount of vxASTRO at some point in the past - #[returns(VotingPowerResponse)] - TotalVotingPowerAt { time: u64 }, - /// Return the total voting power at a specific period - #[returns(VotingPowerResponse)] - TotalVotingPowerAtPeriod { period: u64 }, + #[returns(Uint128)] + TotalVotingPower { time: Option }, /// Return the user's current voting power (vxASTRO balance) - #[returns(VotingPowerResponse)] - UserVotingPower { user: String }, - /// Return the user's vxASTRO balance at some point in the past - #[returns(VotingPowerResponse)] - UserVotingPowerAt { user: String, time: u64 }, - /// Return the user's voting power at a specific period - #[returns(VotingPowerResponse)] - UserVotingPowerAtPeriod { user: String, period: u64 }, - /// Return information about a user's lock position + #[returns(Uint128)] + UserVotingPower { user: String, time: Option }, + /// Fetch a user's lock information #[returns(LockInfoResponse)] LockInfo { user: String }, - /// Return user's locked xASTRO balance at the given block height - #[returns(Uint128)] - UserDepositAtHeight { user: String, height: u64 }, /// Return the vxASTRO contract configuration - #[returns(ConfigResponse)] + #[returns(Config)] Config {}, } -/// This structure is used to return a user's amount of vxASTRO. +/// This structure stores the main parameters for the voting escrow contract. #[cw_serde] -pub struct VotingPowerResponse { - /// The vxASTRO balance - pub voting_power: Uint128, -} - -/// This structure is used to return the lock information for a vxASTRO position. -#[cw_serde] -pub struct LockInfoResponse { - /// The amount of xASTRO locked in the position - pub amount: Uint128, - /// This is the initial boost for the lock position - pub coefficient: Decimal, - /// Start time for the vxASTRO position decay - pub start: u64, - /// End time for the vxASTRO position decay - pub end: u64, - /// Slope at which a staker's vxASTRO balance decreases over time - pub slope: Uint128, -} - -/// This structure stores the parameters returned when querying for a contract's configuration. -#[cw_serde] -pub struct ConfigResponse { - /// Address that's allowed to change contract parameters - pub owner: String, - /// Address that can only blacklist vxASTRO stakers and remove their governance power - pub guardian_addr: Option, - /// The xASTRO token contract address - pub deposit_token_addr: String, - /// The address of $ASTRO - pub astro_addr: String, - /// The address of $xASTRO staking contract - pub xastro_staking_addr: String, +pub struct Config { + /// The xASTRO denom + pub deposit_denom: String, /// The list of whitelisted logo urls prefixes pub logo_urls_whitelist: Vec, } -/// This structure describes a Migration message. #[cw_serde] -pub struct MigrateMsg { - pub params: Binary, -} - -/// Queries current user's voting power from the voting escrow contract. -/// -/// * **user** staker for which we calculate the latest vxASTRO voting power. -pub fn get_voting_power( - querier: &QuerierWrapper, - escrow_addr: impl Into, - user: impl Into, -) -> StdResult { - let vp: VotingPowerResponse = - querier.query_wasm_smart(escrow_addr, &UserVotingPower { user: user.into() })?; - Ok(vp.voting_power) -} - -/// Queries current user's voting power from the voting escrow contract by timestamp. -/// -/// * **user** staker for which we calculate the voting power at a specific time. -/// -/// * **timestamp** timestamp at which we calculate the staker's voting power. -pub fn get_voting_power_at( - querier: &QuerierWrapper, - escrow_addr: impl Into, - user: impl Into, - timestamp: u64, -) -> StdResult { - let vp: VotingPowerResponse = querier.query_wasm_smart( - escrow_addr, - &UserVotingPowerAt { - user: user.into(), - time: timestamp, - }, - )?; - - Ok(vp.voting_power) -} - -/// Queries current total voting power from the voting escrow contract. -pub fn get_total_voting_power( - querier: &QuerierWrapper, - escrow_addr: impl Into, -) -> StdResult { - let vp: VotingPowerResponse = querier.query_wasm_smart(escrow_addr, &TotalVotingPower {})?; - - Ok(vp.voting_power) -} - -/// Queries total voting power from the voting escrow contract by timestamp. -/// -/// * **timestamp** time at which we fetch the total voting power. -pub fn get_total_voting_power_at( - querier: &QuerierWrapper, - escrow_addr: impl Into, - timestamp: u64, -) -> StdResult { - let vp: VotingPowerResponse = - querier.query_wasm_smart(escrow_addr, &TotalVotingPowerAt { time: timestamp })?; - - Ok(vp.voting_power) -} - -/// Queries user's lockup information from the voting escrow contract. -/// -/// * **user** staker for which we return lock position information. -pub fn get_lock_info( - querier: &QuerierWrapper, - escrow_addr: impl Into, - user: impl Into, -) -> StdResult { - let lock_info: LockInfoResponse = - querier.query_wasm_smart(escrow_addr, &LockInfo { user: user.into() })?; - Ok(lock_info) +pub struct LockInfoResponse { + /// The total amount of xASTRO tokens that were deposited in the vxASTRO position + pub amount: Uint128, + /// The timestamp when a lock will be unlocked. None for positions in Locked state + pub end: Option, } diff --git a/packages/astroport-governance/src/voting_escrow_delegation.rs b/packages/astroport-governance/src/voting_escrow_delegation.rs deleted file mode 100644 index 5f498e55..00000000 --- a/packages/astroport-governance/src/voting_escrow_delegation.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::voting_escrow_delegation::QueryMsg::AdjustedBalance; -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, QuerierWrapper, StdResult, Uint128}; - -/// This structure stores the main parameters for the voting escrow delegation contract. -#[cw_serde] -pub struct Config { - /// Address that's allowed to change contract parameters - pub owner: Addr, - /// Astroport NFT contract address - pub nft_addr: Addr, - /// vxASTRO contract address - pub voting_escrow_addr: Addr, -} - -#[cw_serde] -pub struct Token { - /// The amount of voting power to be delegated - pub power: Uint128, - /// Weekly voting power decay - pub slope: Uint128, - /// The start period when the delegated voting power start to decrease - pub start: u64, - /// The period when the delegated voting power should expire - pub expire_period: u64, -} - -#[cw_serde] -pub struct InstantiateMsg { - /// The contract owner address - pub owner: String, - /// Astroport NFT code identifier - pub nft_code_id: u64, - /// vxASTRO contract address - pub voting_escrow_addr: String, -} - -#[cw_serde] -pub enum ExecuteMsg { - CreateDelegation { - /// The share of voting power (in bps) that will be delegated to the recipient - bps: u16, - expire_time: u64, - token_id: String, - recipient: String, - }, - ExtendDelegation { - /// The share of voting power (in bps) that will be delegated to the recipient - bps: u16, - expire_time: u64, - token_id: String, - }, - UpdateConfig { - /// vxASTRO contract address - new_voting_escrow: Option, - }, - /// Propose a new owner for the contract - ProposeNewOwner { new_owner: String, expires_in: u64 }, - /// Remove the ownership transfer proposal - DropOwnershipProposal {}, - /// Claim contract ownership - ClaimOwnership {}, -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - #[returns(Config)] - Config {}, - #[returns(Uint128)] - AdjustedBalance { - account: String, - timestamp: Option, - }, - #[returns(Uint128)] - DelegatedVotingPower { - account: String, - timestamp: Option, - }, -} - -/// Queries current user's adjusted voting power from the voting escrow delegation contract. -pub fn get_adjusted_balance( - querier: &QuerierWrapper, - escrow_delegation_addr: String, - account: String, - timestamp: Option, -) -> StdResult { - querier.query_wasm_smart( - escrow_delegation_addr, - &AdjustedBalance { account, timestamp }, - ) -} From 060090d1041586f0823ff02a3c6a35c9d03fd6cc Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Tue, 14 May 2024 17:37:24 +0400 Subject: [PATCH 04/32] remove unused contracts; add vxastro tests --- .../escrow_fee_distributor/.cargo/config | 6 - contracts/escrow_fee_distributor/Cargo.toml | 29 - contracts/escrow_fee_distributor/README.md | 143 --- .../examples/escrow_fee_distributor_schema.rs | 13 - .../escrow_fee_distributor/src/contract.rs | 387 ------ contracts/escrow_fee_distributor/src/error.rs | 28 - contracts/escrow_fee_distributor/src/lib.rs | 11 - contracts/escrow_fee_distributor/src/state.rs | 30 - .../escrow_fee_distributor/src/testing.rs | 34 - contracts/escrow_fee_distributor/src/utils.rs | 135 -- .../tests/integration.rs | 1141 ----------------- contracts/voting_escrow/Cargo.toml | 5 +- contracts/voting_escrow/src/contract.rs | 11 +- contracts/voting_escrow/src/error.rs | 2 +- .../voting_escrow/src/marketing_validation.rs | 43 +- contracts/voting_escrow/src/state.rs | 36 +- contracts/voting_escrow/tests/helper.rs | 134 ++ .../tests/voting_escrow_integration.rs | 247 ++++ .../tests/vxastro_integration.rs | 1 - .../src/escrow_fee_distributor.rs | 94 -- packages/astroport-governance/src/lib.rs | 1 - packages/astroport-tests/Cargo.toml | 33 - packages/astroport-tests/src/base.rs | 371 ------ .../astroport-tests/src/controller_helper.rs | 365 ------ packages/astroport-tests/src/escrow_helper.rs | 345 ----- packages/astroport-tests/src/lib.rs | 46 - scripts/coverage.sh | 6 +- 27 files changed, 441 insertions(+), 3256 deletions(-) delete mode 100644 contracts/escrow_fee_distributor/.cargo/config delete mode 100644 contracts/escrow_fee_distributor/Cargo.toml delete mode 100644 contracts/escrow_fee_distributor/README.md delete mode 100644 contracts/escrow_fee_distributor/examples/escrow_fee_distributor_schema.rs delete mode 100644 contracts/escrow_fee_distributor/src/contract.rs delete mode 100644 contracts/escrow_fee_distributor/src/error.rs delete mode 100644 contracts/escrow_fee_distributor/src/lib.rs delete mode 100644 contracts/escrow_fee_distributor/src/state.rs delete mode 100644 contracts/escrow_fee_distributor/src/testing.rs delete mode 100644 contracts/escrow_fee_distributor/src/utils.rs delete mode 100644 contracts/escrow_fee_distributor/tests/integration.rs create mode 100644 contracts/voting_escrow/tests/helper.rs create mode 100644 contracts/voting_escrow/tests/voting_escrow_integration.rs delete mode 100644 contracts/voting_escrow/tests/vxastro_integration.rs delete mode 100644 packages/astroport-governance/src/escrow_fee_distributor.rs delete mode 100644 packages/astroport-tests/Cargo.toml delete mode 100644 packages/astroport-tests/src/base.rs delete mode 100644 packages/astroport-tests/src/controller_helper.rs delete mode 100644 packages/astroport-tests/src/escrow_helper.rs delete mode 100644 packages/astroport-tests/src/lib.rs diff --git a/contracts/escrow_fee_distributor/.cargo/config b/contracts/escrow_fee_distributor/.cargo/config deleted file mode 100644 index 62f88868..00000000 --- a/contracts/escrow_fee_distributor/.cargo/config +++ /dev/null @@ -1,6 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib" -integration-test = "test --test integration" -schema = "run --example escrow_fee_distributor_schema" diff --git a/contracts/escrow_fee_distributor/Cargo.toml b/contracts/escrow_fee_distributor/Cargo.toml deleted file mode 100644 index 389add17..00000000 --- a/contracts/escrow_fee_distributor/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "astroport-escrow-fee-distributor" -version = "1.0.2" -authors = ["Astroport"] -edition = "2021" -repository = "https://github.com/astroport-fi/astroport-governance" -homepage = "https://astroport.fi" - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -backtraces = ["cosmwasm-std/backtraces"] -# use library feature to disable all init/handle/query exports -library = [] - -[dependencies] -cw2 = "0.15" -cw20 = "0.15" -cosmwasm-std = "1.1" -cw-storage-plus = "0.15" -thiserror = { version = "1.0" } -astroport-governance = { path = "../../packages/astroport-governance" } -cosmwasm-schema = "1.1" - -[dev-dependencies] -cw-multi-test = "0.15" -astroport-token = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -astroport-tests = { path = "../../packages/astroport-tests" } diff --git a/contracts/escrow_fee_distributor/README.md b/contracts/escrow_fee_distributor/README.md deleted file mode 100644 index 8c678d86..00000000 --- a/contracts/escrow_fee_distributor/README.md +++ /dev/null @@ -1,143 +0,0 @@ -# vxASTRO Escrow Fee Distributor - -Distribute ASTRO staking rewards every period to vxASTRO stakers. - -## InstantiateMsg - -Instantiate the fee distributor contract with the ASTRO and vxASTRO token contract addresses as well as claim related parameters. - -```json -{ - "owner": "terra...", - "astro_token": "terra...", - "voting_escrow": "terra...", - "claim_many_limit": 7, - "is_claim_disabled": false -} -``` - -## ExecuteMsg - -### `claim` - -Claims ASTRO rewards for one period and sends them to the recipient. - -```json -{ - "claim": { - "recipient": "terra..." - } -} -``` - -### `claim_many` - -Claims ASTRO rewards from multiple periods and sends them to the recipient. - -```json -{ - "claim": { - "receivers": ["terra...", "terra..."] - } -} -``` - -### `update_config` - -Update the contract configuration. - -```json -{ - "claim": { - "claim_many_limit": 2, - "is_claim_disabled": false - } -} -``` - -### `receive` - -Receive ASTRO fees (from the Maker) and prepares them to be distributed pro-rata to current stakers. - -```json -{ - "receive": { - "sender": "terra...", - "amount": "123", - "msg": "" - } -} -``` - -### `propose_new_owner` - -Creates a proposal to change the contract owner. The validity period for the offer is set in the `expires_in` variable. - -```json -{ - "propose_new_owner": { - "owner": "terra...", - "expires_in": 1234567 - } -} -``` - -### `drop_ownership_proposal` - -Removes the existing proposal to change contract ownership. - -```json -{ - "drop_ownership_proposal": {} -} -``` - -### `claim_ownership` - -Claim contract ownership. - -```json -{ - "claim_ownership": {} -} -``` - -## QueryMsg - -All query messages are described below. A custom struct is defined for each query response. - -### `config` - -Returns the contract configuration. - -```json -{ - "config": {} -} -``` - -### `user_reward` - -Returns the amount of ASTRO rewards a user can claim at a specific timestamp. `timestamp` is in seconds. - -```json -{ - "user_reward": { - "user": "user1", - "timestamp": 1645113644 - } -} -``` - -### `available_reward_per_week` - -Returns a vector with total amounts of ASTRO distributed as rewards every week to stakers. `start_after` is a timestamp in seconds. `limit` is the amount of entries to return. - -```json -{ - "available_reward_per_week": { - "start_after": 1645015524, - "limit": 3 - } -} -``` diff --git a/contracts/escrow_fee_distributor/examples/escrow_fee_distributor_schema.rs b/contracts/escrow_fee_distributor/examples/escrow_fee_distributor_schema.rs deleted file mode 100644 index ba0c02c6..00000000 --- a/contracts/escrow_fee_distributor/examples/escrow_fee_distributor_schema.rs +++ /dev/null @@ -1,13 +0,0 @@ -use astroport_governance::escrow_fee_distributor::{ - ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, -}; -use cosmwasm_schema::write_api; - -fn main() { - write_api! { - instantiate: InstantiateMsg, - query: QueryMsg, - execute: ExecuteMsg, - migrate: MigrateMsg - } -} diff --git a/contracts/escrow_fee_distributor/src/contract.rs b/contracts/escrow_fee_distributor/src/contract.rs deleted file mode 100644 index 6163f650..00000000 --- a/contracts/escrow_fee_distributor/src/contract.rs +++ /dev/null @@ -1,387 +0,0 @@ -use astroport::common::{claim_ownership, drop_ownership_proposal, propose_new_owner}; -use cosmwasm_std::{ - attr, entry_point, to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Order, Response, - StdError, StdResult, Uint128, -}; -use cw2::set_contract_version; -use cw20::Cw20ReceiveMsg; -use cw_storage_plus::Bound; - -use astroport_governance::escrow_fee_distributor::{ - ConfigResponse, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, -}; -use astroport_governance::utils::{get_period, CLAIM_LIMIT, MIN_CLAIM_LIMIT}; -use astroport_governance::voting_escrow::{get_total_voting_power_at, get_voting_power_at}; - -use crate::astroport; -use crate::astroport::asset::addr_opt_validate; -use crate::error::ContractError; -use crate::state::{Config, CONFIG, OWNERSHIP_PROPOSAL, REWARDS_PER_WEEK}; -use crate::utils::{calc_claim_amount, calculate_reward, transfer_token_amount}; - -/// Contract name that is used for migration. -const CONTRACT_NAME: &str = "astroport-escrow-fee-distributor"; -/// Contract version that is used for migration. -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -/// Creates a new contract with the specified parameters in the [`InstantiateMsg`]. -#[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)?; - - if let Some(claim_many_limit) = msg.claim_many_limit { - if claim_many_limit < MIN_CLAIM_LIMIT { - return Err(StdError::generic_err(format!( - "Accounts limit for claim operation cannot be less than {MIN_CLAIM_LIMIT} !" - ))); - } - } - - CONFIG.save( - deps.storage, - &Config { - owner: deps.api.addr_validate(&msg.owner)?, - astro_token: deps.api.addr_validate(&msg.astro_token)?, - voting_escrow_addr: deps.api.addr_validate(&msg.voting_escrow_addr)?, - is_claim_disabled: msg.is_claim_disabled.unwrap_or(false), - claim_many_limit: msg.claim_many_limit.unwrap_or(CLAIM_LIMIT), - }, - )?; - - Ok(Response::new()) -} - -/// Exposes all the execute functions available in the contract. -/// -/// ## Execute messages -/// * **ExecuteMsg::ProposeNewOwner { owner, expires_in }** Creates a request to change contract ownership. -/// -/// * **ExecuteMsg::DropOwnershipProposal {}** Removes a request to change contract ownership. -/// -/// * **ExecuteMsg::ClaimOwnership {}** Claims contract ownership. -/// -/// * **ExecuteMsg::Claim { recipient }** Claims ASTRO fees from the distributor and sends them to the recipient. -/// -/// * **ExecuteMsg::ClaimMany { receivers }** Perform multiple fee claims in a single transaction. -/// -/// * **ExecuteMsg::Receive(msg)** Parse incoming messages from the ASTRO token. -/// -/// * **ExecuteMsg::UpdateConfig { claim_many_limit, is_claim_disabled}** Updates general settings. -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - let config: Config = CONFIG.load(deps.storage)?; - match msg { - ExecuteMsg::ProposeNewOwner { owner, expires_in } => propose_new_owner( - deps, - info, - env, - owner, - expires_in, - config.owner, - OWNERSHIP_PROPOSAL, - ) - .map_err(Into::into), - ExecuteMsg::DropOwnershipProposal {} => { - drop_ownership_proposal(deps, info, config.owner, OWNERSHIP_PROPOSAL) - .map_err(Into::into) - } - ExecuteMsg::ClaimOwnership {} => { - claim_ownership(deps, info, env, OWNERSHIP_PROPOSAL, |deps, new_owner| { - CONFIG - .update::<_, StdError>(deps.storage, |mut v| { - v.owner = new_owner; - Ok(v) - }) - .map(|_| ()) - }) - .map_err(Into::into) - } - ExecuteMsg::Claim { - recipient, - max_periods, - } => claim(deps, env, info, recipient, max_periods), - ExecuteMsg::ClaimMany { receivers } => claim_many(deps, env, receivers), - ExecuteMsg::UpdateConfig { - claim_many_limit, - is_claim_disabled, - } => update_config(deps, info, claim_many_limit, is_claim_disabled), - ExecuteMsg::Receive(msg) => receive_cw20(deps, env, info, msg), - } -} - -/// Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template. -/// -/// * **cw20_msg** CW20 message to process. -fn receive_cw20( - deps: DepsMut, - env: Env, - info: MessageInfo, - cw20_msg: Cw20ReceiveMsg, -) -> Result { - let config: Config = CONFIG.load(deps.storage)?; - if info.sender != config.astro_token { - return Err(ContractError::Unauthorized {}); - } - - let curr_period = get_period(env.block.time.seconds())?; - - REWARDS_PER_WEEK.update(deps.storage, curr_period, |period| -> StdResult<_> { - if let Some(tokens_amount) = period { - Ok(tokens_amount.checked_add(cw20_msg.amount)?) - } else { - Ok(cw20_msg.amount) - } - })?; - - Ok(Response::new()) -} - -/// Claims ASTRO staking rewards from this contract and sends them to the `recipient`. -/// -/// * **recipient** address that will receive the ASTRO staking rewards. -pub fn claim( - deps: DepsMut, - env: Env, - info: MessageInfo, - recipient: Option, - max_periods: Option, -) -> Result { - let config = CONFIG.load(deps.storage)?; - - if config.is_claim_disabled { - return Err(ContractError::ClaimDisabled {}); - } - - let recipient_addr = - addr_opt_validate(deps.api, &recipient)?.unwrap_or_else(|| info.sender.clone()); - let current_period = get_period(env.block.time.seconds())?; - - let claim_amount = calc_claim_amount( - deps, - current_period, - &info.sender, - &config.voting_escrow_addr, - max_periods, - )?; - - let transfer_msg = transfer_token_amount(&config.astro_token, &recipient_addr, claim_amount)?; - - let response = Response::new() - .add_attributes(vec![ - attr("action", "claim"), - attr("address", recipient_addr.to_string()), - attr("amount", claim_amount.to_string()), - ]) - .add_messages(transfer_msg); - - Ok(response) -} - -/// Make multiple ASTRO fee claims in a single call. -/// -/// * **receivers** list of addresses that will receive the claimed ASTRO. -fn claim_many( - mut deps: DepsMut, - env: Env, - receivers: Vec, -) -> Result { - let config = CONFIG.load(deps.storage)?; - - if config.is_claim_disabled { - return Err(ContractError::ClaimDisabled {}); - } - - if receivers.len() > config.claim_many_limit as usize { - return Err(ContractError::ClaimLimitExceeded {}); - } - - let mut claim_total_amount = Uint128::zero(); - let mut transfer_msg = vec![]; - let current_period = get_period(env.block.time.seconds())?; - - for receiver in receivers { - let receiver_addr = deps.api.addr_validate(&receiver)?; - let claim_amount = calc_claim_amount( - deps.branch(), - current_period, - &receiver_addr, - &config.voting_escrow_addr, - None, - )?; - - if !claim_amount.is_zero() { - transfer_msg.extend(transfer_token_amount( - &config.astro_token, - &receiver_addr, - claim_amount, - )?); - claim_total_amount = claim_total_amount.checked_add(claim_amount)?; - }; - } - - let response = Response::new() - .add_attributes(vec![ - attr("action", "claim_many"), - attr("amount", claim_total_amount.to_string()), - ]) - .add_messages(transfer_msg); - - Ok(response) -} - -/// Updates general contract settings. -/// -/// * **claim_many_limit** max amount of rewards slots to claim in one transaction. -/// -/// * **is_claim_disabled** whether reward claims are disabled or not. -fn update_config( - deps: DepsMut, - info: MessageInfo, - claim_many_limit: Option, - is_claim_disabled: Option, -) -> Result { - let mut config = CONFIG.load(deps.storage)?; - - if info.sender != config.owner { - return Err(ContractError::Unauthorized {}); - } - - let mut attributes = vec![attr("action", "update_config")]; - - if let Some(is_claim_disabled) = is_claim_disabled { - if config.is_claim_disabled == is_claim_disabled { - return Err(ContractError::Std(StdError::generic_err(format!( - "Parameter is_claim_disabled is already {}!", - config.is_claim_disabled - )))); - } - config.is_claim_disabled = is_claim_disabled; - attributes.push(attr("is_claim_disabled", is_claim_disabled.to_string())); - }; - - if let Some(claim_many_limit) = claim_many_limit { - if claim_many_limit < MIN_CLAIM_LIMIT { - return Err(StdError::generic_err(format!( - "Accounts limit for claim operation cannot be less than {MIN_CLAIM_LIMIT} !" - )) - .into()); - } - - config.claim_many_limit = claim_many_limit; - attributes.push(attr("claim_many_limit", claim_many_limit.to_string())); - }; - - CONFIG.save(deps.storage, &config)?; - - Ok(Response::new().add_attributes(attributes)) -} - -/// Expose available contract queries. -/// -/// ## Queries -/// * **QueryMsg::UserReward { user, timestamp }** Returns the amount of ASTRO rewards a user can claim at a specific timestamp. -/// -/// * **QueryMsg::Config {}** Returns the contract configuration. -/// -/// * **QueryMsg::AvailableRewardPerWeek { start_after, limit }** Returns a vector with total amounts -/// of ASTRO distributed as rewards every week to stakers. -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::UserReward { user, timestamp } => { - to_json_binary(&query_user_reward(deps, user, timestamp)?) - } - QueryMsg::Config {} => to_json_binary(&query_config(deps)?), - QueryMsg::AvailableRewardPerWeek { start_after, limit } => { - to_json_binary(&query_available_reward_per_week(deps, start_after, limit)?) - } - } -} - -/// Pagination settings -/// The maximum limit for reading pairs from [`PAIRS`]. -const MAX_LIMIT: u64 = 30; - -/// The default limit for reading pairs from [`PAIRS`]. -const DEFAULT_LIMIT: u64 = 10; - -/// Returns a vector of weekly rewards for current vxASTRO stakers. -/// -/// * **start_after** timestamp from which to start querying. -/// -/// * **limit** max amount of entries to return. -fn query_available_reward_per_week( - deps: Deps, - start_after: Option, - limit: Option, -) -> StdResult> { - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let start = if let Some(timestamp) = start_after { - Some(Bound::exclusive(get_period(timestamp)?)) - } else { - None - }; - - REWARDS_PER_WEEK - .range(deps.storage, start, None, Order::Ascending) - .take(limit) - .map(|week| Ok(week?.1)) - .collect() -} - -/// Returns the amount of rewards a user accrued at a specific timestamp. -/// -/// * **user** user for which we return the amount of rewards. -/// -/// * **timestamp** timestamp at which we fetch the user's reward amount. -fn query_user_reward(deps: Deps, user: String, timestamp: u64) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - let user_voting_power = - get_voting_power_at(&deps.querier, &config.voting_escrow_addr, user, timestamp)?; - let total_voting_power = - get_total_voting_power_at(&deps.querier, &config.voting_escrow_addr, timestamp)?; - - if !total_voting_power.is_zero() { - let current_period = get_period(timestamp)?; - calculate_reward( - deps.storage, - current_period, - user_voting_power, - total_voting_power, - ) - } else { - Ok(Uint128::zero()) - } -} - -/// Returns the contract configuration. -pub fn query_config(deps: Deps) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - let resp = ConfigResponse { - owner: config.owner, - astro_token: config.astro_token, - voting_escrow_addr: config.voting_escrow_addr, - is_claim_disabled: config.is_claim_disabled, - claim_many_limit: config.claim_many_limit, - }; - - Ok(resp) -} - -/// Manages contract migration. -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { - Err(ContractError::MigrationError {}) -} diff --git a/contracts/escrow_fee_distributor/src/error.rs b/contracts/escrow_fee_distributor/src/error.rs deleted file mode 100644 index e10642f0..00000000 --- a/contracts/escrow_fee_distributor/src/error.rs +++ /dev/null @@ -1,28 +0,0 @@ -use cosmwasm_std::{OverflowError, StdError}; -use thiserror::Error; - -/// ## Description -/// This enum describes fee distributor contract errors! -#[derive(Error, Debug, PartialEq)] -pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("Unauthorized")] - Unauthorized {}, - - #[error("Exceeded account limit for the claim operation!")] - ClaimLimitExceeded {}, - - #[error("Claiming is disabled!")] - ClaimDisabled {}, - - #[error("Contract can't be migrated!")] - MigrationError {}, -} - -impl From for ContractError { - fn from(o: OverflowError) -> Self { - StdError::from(o).into() - } -} diff --git a/contracts/escrow_fee_distributor/src/lib.rs b/contracts/escrow_fee_distributor/src/lib.rs deleted file mode 100644 index 410f0ed6..00000000 --- a/contracts/escrow_fee_distributor/src/lib.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub mod contract; -mod error; -pub mod state; -mod utils; - -// During development this import could be replaced with another astroport version. -// However, in production, the astroport version should be the same for all contracts. -use astroport_governance::astroport; - -#[cfg(test)] -mod testing; diff --git a/contracts/escrow_fee_distributor/src/state.rs b/contracts/escrow_fee_distributor/src/state.rs deleted file mode 100644 index 0ac2c942..00000000 --- a/contracts/escrow_fee_distributor/src/state.rs +++ /dev/null @@ -1,30 +0,0 @@ -use cosmwasm_schema::cw_serde; - -use crate::astroport::common::OwnershipProposal; - -use cosmwasm_std::{Addr, Uint128}; -use cw_storage_plus::{Item, Map}; - -/// This structure stores the main parameters for the distributor contract. -#[cw_serde] -pub struct Config { - /// Address that's allowed to change contract parameters - pub owner: Addr, - /// ASTRO token address - pub astro_token: Addr, - /// vxASTRO contract address - pub voting_escrow_addr: Addr, - /// Max limit of addresses that can claim rewards in a single call - pub claim_many_limit: u64, - /// Whether reward claiming is disabled - pub is_claim_disabled: bool, -} - -/// Stores the contract config at the given key. -pub const CONFIG: Item = Item::new("config"); -/// Contains information about weekly distributed rewards. -pub const REWARDS_PER_WEEK: Map = Map::new("rewards_per_week"); -/// Contains information about the last week of reward issuance. -pub const LAST_CLAIM_PERIOD: Map<&Addr, u64> = Map::new("last_claim_period"); -/// Contains the proposal to change contract ownership. -pub const OWNERSHIP_PROPOSAL: Item = Item::new("ownership_proposal"); diff --git a/contracts/escrow_fee_distributor/src/testing.rs b/contracts/escrow_fee_distributor/src/testing.rs deleted file mode 100644 index 9311d424..00000000 --- a/contracts/escrow_fee_distributor/src/testing.rs +++ /dev/null @@ -1,34 +0,0 @@ -use crate::contract::{instantiate, query}; -use astroport_governance::escrow_fee_distributor::{ConfigResponse, InstantiateMsg, QueryMsg}; - -use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; -use cosmwasm_std::{from_json, Addr}; - -#[test] -fn proper_initialization() { - let mut deps = mock_dependencies(); - - let msg = InstantiateMsg { - owner: "owner".to_string(), - astro_token: "token".to_string(), - voting_escrow_addr: "voting_escrow".to_string(), - claim_many_limit: None, - is_claim_disabled: None, - }; - - let env = mock_env(); - let info = mock_info("addr0000", &vec![]); - let _res = instantiate(deps.as_mut(), env.clone(), info, msg).unwrap(); - - assert_eq!( - from_json::(&query(deps.as_ref(), env, QueryMsg::Config {}).unwrap()) - .unwrap(), - ConfigResponse { - owner: Addr::unchecked("owner"), - astro_token: Addr::unchecked("token"), - voting_escrow_addr: Addr::unchecked("voting_escrow"), - claim_many_limit: 10, - is_claim_disabled: false - } - ); -} diff --git a/contracts/escrow_fee_distributor/src/utils.rs b/contracts/escrow_fee_distributor/src/utils.rs deleted file mode 100644 index e3ee9128..00000000 --- a/contracts/escrow_fee_distributor/src/utils.rs +++ /dev/null @@ -1,135 +0,0 @@ -use std::cmp::min; - -use cosmwasm_std::{ - to_json_binary, Addr, CosmosMsg, DepsMut, StdError, StdResult, Storage, Uint128, WasmMsg, -}; -use cw20::Cw20ExecuteMsg; - -use astroport_governance::voting_escrow::{ - get_lock_info, QueryMsg as VotingQueryMsg, VotingPowerResponse, DEFAULT_PERIODS_LIMIT, -}; - -use crate::error::ContractError; -use crate::state::{LAST_CLAIM_PERIOD, REWARDS_PER_WEEK}; - -/// Transfer tokens to another address. -/// -/// * **contract_addr** address of the token contract. -/// -/// * **recipient** address of the token recipient. -/// -/// * **amount** token amount to transfer. -pub(crate) fn transfer_token_amount( - contract_addr: &Addr, - recipient: &Addr, - amount: Uint128, -) -> Result, ContractError> { - let messages = if !amount.is_zero() { - vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: contract_addr.to_string(), - msg: to_json_binary(&Cw20ExecuteMsg::Transfer { - recipient: recipient.to_string(), - amount, - })?, - funds: vec![], - })] - } else { - vec![] - }; - - Ok(messages) -} - -/// Returns the amount of rewards distributed to a user for a specific period. -/// -/// * **period** period for which we calculate the user's reward. -/// -/// * **user_vp** user's voting power for the specified period. -/// -/// * **total_vp** total voting power for the specified period. -pub(crate) fn calculate_reward( - storage: &dyn Storage, - period: u64, - user_vp: Uint128, - total_vp: Uint128, -) -> StdResult { - let rewards_per_week = REWARDS_PER_WEEK - .may_load(storage, period)? - .unwrap_or_default(); - - user_vp - .checked_multiply_ratio(rewards_per_week, total_vp) - .map_err(|e| StdError::generic_err(format!("{e:?}"))) -} - -/// Calculates the amount of ASTRO available to claim by a specific address. -/// -/// * **current_period** current epoch number. -/// -/// * **account** account for which we calculate the amount of ASTRO rewards available to claim. -/// -/// * **voting_escrow_addr** vxASTRO contract address. -/// -/// * **max_periods** maximum number of periods to claim. -pub(crate) fn calc_claim_amount( - deps: DepsMut, - current_period: u64, - account: &Addr, - voting_escrow_addr: &Addr, - max_periods: Option, -) -> StdResult { - let user_lock_info = get_lock_info(&deps.querier, voting_escrow_addr, account)?; - - let mut claim_period = LAST_CLAIM_PERIOD - .may_load(deps.storage, account)? - .unwrap_or(user_lock_info.start); - - let lock_end_period = user_lock_info.end; - let mut claim_amount: Uint128 = Default::default(); - let max_period = min( - max_periods.unwrap_or(DEFAULT_PERIODS_LIMIT) + claim_period, - current_period, - ); - - loop { - // User cannot claim for the current period/ - if claim_period >= max_period { - break; - } - - // User cannot claim after their max lock period - if claim_period > lock_end_period { - break; - } - - let user_voting_power: VotingPowerResponse = deps.querier.query_wasm_smart( - voting_escrow_addr, - &VotingQueryMsg::UserVotingPowerAtPeriod { - user: account.to_string(), - period: claim_period, - }, - )?; - - let total_voting_power: VotingPowerResponse = deps.querier.query_wasm_smart( - voting_escrow_addr, - &VotingQueryMsg::TotalVotingPowerAtPeriod { - period: claim_period, - }, - )?; - - if !user_voting_power.voting_power.is_zero() && !total_voting_power.voting_power.is_zero() { - claim_amount = claim_amount.checked_add(calculate_reward( - deps.storage, - claim_period, - user_voting_power.voting_power, - total_voting_power.voting_power, - )?)?; - } - - claim_period += 1; - } - - LAST_CLAIM_PERIOD.save(deps.storage, account, &claim_period)?; - - Ok(claim_amount) -} diff --git a/contracts/escrow_fee_distributor/tests/integration.rs b/contracts/escrow_fee_distributor/tests/integration.rs deleted file mode 100644 index af3b37d2..00000000 --- a/contracts/escrow_fee_distributor/tests/integration.rs +++ /dev/null @@ -1,1141 +0,0 @@ -use cosmwasm_std::testing::{mock_env, MockApi, MockStorage}; -use cosmwasm_std::{attr, to_json_binary, Addr, StdResult, Timestamp, Uint128}; - -use astroport_governance::utils::{get_period, EPOCH_START, WEEK}; - -use astroport_governance::escrow_fee_distributor::{ - ConfigResponse, Cw20HookMsg, ExecuteMsg, QueryMsg, -}; -use astroport_governance::voting_escrow::{ - LockInfoResponse, QueryMsg as VotingEscrowQueryMsg, VotingPowerResponse, -}; - -use astroport_tests::base::{ - check_balance, mint, BaseAstroportTestInitMessage, BaseAstroportTestPackage, MULTIPLIER, -}; -use cw20::Cw20ExecuteMsg; -use cw_multi_test::{next_block, App, AppBuilder, BankKeeper, Executor}; - -const OWNER: &str = "owner"; -const USER1: &str = "user1"; -const USER2: &str = "user2"; -const USER3: &str = "user3"; -const USER4: &str = "user4"; -const USER5: &str = "user5"; -const MAKER: &str = "maker"; - -fn mock_app() -> App { - let mut env = mock_env(); - env.block.time = Timestamp::from_seconds(EPOCH_START); - let api = MockApi::default(); - let bank = BankKeeper::new(); - let storage = MockStorage::new(); - - AppBuilder::new() - .with_api(api) - .with_block(env.block) - .with_bank(bank) - .with_storage(storage) - .build(|_, _, _| {}) -} - -fn init_astroport_test_package(router: &mut App) -> StdResult { - let base_msg = BaseAstroportTestInitMessage { - owner: Addr::unchecked(OWNER), - }; - - Ok(BaseAstroportTestPackage::init_all(router, base_msg)) -} - -#[test] -fn instantiation() { - let mut router = mock_app(); - let router_ref = &mut router; - let owner = Addr::unchecked(OWNER); - - let base_pack = init_astroport_test_package(router_ref).unwrap(); - - let resp: ConfigResponse = router - .wrap() - .query_wasm_smart( - &base_pack.escrow_fee_distributor.clone().unwrap().address, - &QueryMsg::Config {}, - ) - .unwrap(); - - assert_eq!(owner, resp.owner); - assert_eq!(base_pack.astro_token.unwrap().address, resp.astro_token); - assert_eq!( - base_pack.voting_escrow.unwrap().address, - resp.voting_escrow_addr - ); - assert_eq!(false, resp.is_claim_disabled); - assert_eq!(10u64, resp.claim_many_limit); -} - -#[test] -fn test_receive_tokens() { - let mut router = mock_app(); - let router_ref = &mut router; - let owner = Addr::unchecked(OWNER.clone()); - let maker = Addr::unchecked(MAKER.clone()); - - let base_pack = init_astroport_test_package(router_ref).unwrap(); - - // Mint 1000_000_000 ASTRO for the Maker - mint( - router_ref, - owner.clone(), - base_pack.astro_token.clone().unwrap().address, - &maker, - 1000, - ); - - // Check if Maker's ASTRO balance is 1000_000_000 - check_balance( - router_ref, - &base_pack.astro_token.clone().unwrap().address, - &maker, - 1000 * MULTIPLIER as u128, - ); - - // Check if escrow_fee_distributor ASTRO balance is 0 - check_balance( - router_ref, - &base_pack.astro_token.clone().unwrap().address, - &base_pack.escrow_fee_distributor.clone().unwrap().address, - 0u128, - ); - - // Try to send 100_000_000 ASTRO from Maker to distributor - let msg = Cw20ExecuteMsg::Send { - contract: base_pack - .escrow_fee_distributor - .clone() - .unwrap() - .address - .to_string(), - msg: to_json_binary(&Cw20HookMsg::ReceiveTokens {}).unwrap(), - amount: Uint128::from(100 * MULTIPLIER as u128), - }; - - router_ref - .execute_contract( - maker.clone(), - base_pack.astro_token.clone().unwrap().address, - &msg, - &[], - ) - .unwrap(); - - // Sends 100_000_000 ASTRO from Maker to distributor for the next 5 weeks - for _i in 0..5 { - router_ref - .execute_contract( - maker.clone(), - base_pack.astro_token.clone().unwrap().address, - &msg, - &[], - ) - .unwrap(); - - // Going to the next week - router_ref.update_block(next_block); - router_ref.update_block(|b| b.time = b.time.plus_seconds(WEEK)); - } - - // Check if escrow_fee_distributor's ASTRO balance is equal to 600_000_000 - check_balance( - router_ref, - &base_pack.astro_token.clone().unwrap().address, - &base_pack.escrow_fee_distributor.clone().unwrap().address, - 600 * MULTIPLIER as u128, - ); - - // Check if Maker's ASTRO balance is equal to 400_000_000 - check_balance( - router_ref, - &base_pack.astro_token.clone().unwrap().address, - &maker.clone(), - 400 * MULTIPLIER as u128, - ); - - // Checks rewards per week - let resp: Vec = router_ref - .wrap() - .query_wasm_smart( - &base_pack.escrow_fee_distributor.clone().unwrap().address, - &QueryMsg::AvailableRewardPerWeek { - start_after: None, - limit: None, - }, - ) - .unwrap(); - assert_eq!( - vec![ - Uint128::new(200_000_000), - Uint128::new(100_000_000), - Uint128::new(100_000_000), - Uint128::new(100_000_000), - Uint128::new(100_000_000), - ], - resp - ); -} - -#[test] -fn update_config() { - let mut router = mock_app(); - let router_ref = &mut router; - let owner = Addr::unchecked(OWNER.clone()); - let user1 = Addr::unchecked(USER1.clone()); - - let base_pack = init_astroport_test_package(router_ref).unwrap(); - let escrow_fee_distributor = base_pack.escrow_fee_distributor.unwrap().address; - - let resp: ConfigResponse = router_ref - .wrap() - .query_wasm_smart(&escrow_fee_distributor.clone(), &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(10u64, resp.claim_many_limit); - assert_eq!(false, resp.is_claim_disabled); - - // Check if a random address can update the config - let err = router_ref - .execute_contract( - user1.clone(), - escrow_fee_distributor.clone(), - &ExecuteMsg::UpdateConfig { - claim_many_limit: Some(20u64), - is_claim_disabled: Some(true), - }, - &[], - ) - .unwrap_err(); - assert_eq!("Unauthorized", err.root_cause().to_string()); - - // Check that the owner can update the config - let resp = router_ref - .execute_contract( - owner.clone(), - escrow_fee_distributor.clone(), - &ExecuteMsg::UpdateConfig { - claim_many_limit: Some(20u64), - is_claim_disabled: Some(true), - }, - &[], - ) - .unwrap(); - - let resp_config: ConfigResponse = router_ref - .wrap() - .query_wasm_smart(&escrow_fee_distributor.clone(), &QueryMsg::Config {}) - .unwrap(); - - assert_eq!(20u64, resp_config.claim_many_limit); - assert_eq!(true, resp_config.is_claim_disabled); - - assert_eq!( - vec![ - attr("action", "update_config"), - attr("is_claim_disabled", "true"), - attr("claim_many_limit", "20"), - ], - vec![ - resp.events[1].attributes[1].clone(), - resp.events[1].attributes[2].clone(), - resp.events[1].attributes[3].clone(), - ] - ); -} - -#[test] -fn check_if_user_exists_after_withdraw() { - let mut router = mock_app(); - let router_ref = &mut router; - let user1 = Addr::unchecked(USER1.clone()); - - let base_pack = init_astroport_test_package(router_ref).unwrap(); - let xastro_token = base_pack.get_staking_xastro(router_ref); - - // Send 200_000_000 xASTRO tokens to user1 - mint( - router_ref, - base_pack.staking.clone().unwrap().address, - xastro_token.clone(), - &user1, - 200, - ); - - // Create lock for user1 for WEEK - base_pack - .create_lock(router_ref, user1.clone(), WEEK, 200) - .unwrap(); - - // Going to the last week - router_ref.update_block(next_block); - router_ref.update_block(|b| b.time = b.time.plus_seconds(WEEK)); - - let resp: LockInfoResponse = router_ref - .wrap() - .query_wasm_smart( - &base_pack.voting_escrow.clone().unwrap().address, - &VotingEscrowQueryMsg::LockInfo { - user: user1.to_string(), - }, - ) - .unwrap(); - - assert_eq!(Uint128::new(200_000_000), resp.amount); - assert_eq!( - get_period(router_ref.block_info().time.seconds() - WEEK).unwrap(), - resp.start - ); - assert_eq!( - get_period(router_ref.block_info().time.seconds()).unwrap(), - resp.end - ); - - base_pack.withdraw(router_ref, user1.as_str()).unwrap(); - - let resp: LockInfoResponse = router_ref - .wrap() - .query_wasm_smart( - &base_pack.voting_escrow.clone().unwrap().address, - &VotingEscrowQueryMsg::LockInfo { - user: user1.to_string(), - }, - ) - .unwrap(); - assert_eq!(resp.amount, Uint128::zero()); - assert_eq!( - resp.start, - get_period(router_ref.block_info().time.minus_seconds(WEEK).seconds()).unwrap() - ); - assert_eq!( - resp.end, - get_period(router_ref.block_info().time.seconds()).unwrap() - ); -} - -#[test] -fn claim_without_fee_on_distributor() { - let mut router = mock_app(); - let router_ref = &mut router; - let user1 = Addr::unchecked(USER1.clone()); - let user2 = Addr::unchecked(USER2.clone()); - - let base_pack = init_astroport_test_package(router_ref).unwrap(); - - let xastro_token = base_pack.get_staking_xastro(router_ref); - - // Sets 200_000_000 xASTRO tokens to user1 - mint( - router_ref, - base_pack.staking.clone().unwrap().address, - xastro_token.clone(), - &user1, - 200, - ); - - // Send 200_000_000 xASTRO tokens to user2 - mint( - router_ref, - base_pack.staking.clone().unwrap().address, - xastro_token.clone(), - &user2, - 200, - ); - - // Create lock for user1 for WEEK * 104 - base_pack - .create_lock(router_ref, user1.clone(), WEEK * 104, 200) - .unwrap(); - - // Create lock for user2 for WEEK * 104 - base_pack - .create_lock(router_ref, user2.clone(), WEEK * 104, 200) - .unwrap(); - - // Going to the last week - router_ref.update_block(next_block); - router_ref.update_block(|b| b.time = b.time.plus_seconds(WEEK * 103)); - - // Try to claim fees for user1 - router_ref - .execute_contract( - user1.clone(), - base_pack.escrow_fee_distributor.clone().unwrap().address, - &ExecuteMsg::Claim { - recipient: None, - max_periods: None, - }, - &[], - ) - .unwrap(); - - // Try to claim fees for user2 - router_ref - .execute_contract( - user2.clone(), - base_pack.escrow_fee_distributor.clone().unwrap().address, - &ExecuteMsg::Claim { - recipient: None, - max_periods: None, - }, - &[], - ) - .unwrap(); - - // Check if user1's ASTRO balance is equal to 0 - check_balance( - router_ref, - &base_pack.astro_token.clone().unwrap().address, - &user1, - 0, - ); - - // Check if user2's ASTRO balance is equal to 0 - check_balance( - router_ref, - &base_pack.astro_token.clone().unwrap().address, - &user2, - 0, - ); -} - -#[test] -fn claim_max_period() { - let mut router = mock_app(); - let router_ref = &mut router; - let owner = Addr::unchecked(OWNER.clone()); - let maker = Addr::unchecked(MAKER.clone()); - let user1 = Addr::unchecked(USER1.clone()); - let user2 = Addr::unchecked(USER2.clone()); - - let base_pack = init_astroport_test_package(router_ref).unwrap(); - - let xastro_token = base_pack.get_staking_xastro(router_ref); - - // Send 200_000_000 xASTRO tokens to user1 - mint( - router_ref, - base_pack.staking.clone().unwrap().address, - xastro_token.clone(), - &user1, - 200, - ); - - // Send 200_000_000 xASTRO tokens to user2 - mint( - router_ref, - base_pack.staking.clone().unwrap().address, - xastro_token.clone(), - &user2, - 200, - ); - - // Create lock for user1 for WEEK * 104 - base_pack - .create_lock(router_ref, user1.clone(), WEEK * 104, 200) - .unwrap(); - - // Create lock for user2 for WEEK * 104 - base_pack - .create_lock(router_ref, user2.clone(), WEEK * 104, 200) - .unwrap(); - - // Mint 100_000_000 ASTRO for the Maker - mint( - router_ref, - owner.clone(), - base_pack.astro_token.clone().unwrap().address, - &maker, - 100, - ); - - // Try to send 100_000_000 ASTRO from Maker to distributor for the first period - let msg = Cw20ExecuteMsg::Send { - contract: base_pack - .escrow_fee_distributor - .clone() - .unwrap() - .address - .to_string(), - msg: to_json_binary(&Cw20HookMsg::ReceiveTokens {}).unwrap(), - amount: Uint128::from(100 * MULTIPLIER as u128), - }; - - router_ref - .execute_contract( - maker.clone(), - base_pack.astro_token.clone().unwrap().address, - &msg, - &[], - ) - .unwrap(); - - // Going to the next week - router_ref.update_block(next_block); - router_ref.update_block(|b| b.time = b.time.plus_seconds(WEEK)); - - // Mint 100_000_000 ASTRO for the Maker - mint( - router_ref, - owner.clone(), - base_pack.astro_token.clone().unwrap().address, - &maker, - 100, - ); - - // Try to send 100_000_000 ASTRO from Maker to distributor for the second period - let msg = Cw20ExecuteMsg::Send { - contract: base_pack - .escrow_fee_distributor - .clone() - .unwrap() - .address - .to_string(), - msg: to_json_binary(&Cw20HookMsg::ReceiveTokens {}).unwrap(), - amount: Uint128::from(100 * MULTIPLIER as u128), - }; - - router_ref - .execute_contract( - maker.clone(), - base_pack.astro_token.clone().unwrap().address, - &msg, - &[], - ) - .unwrap(); - - // Warping to the week after user's lock period ends - router_ref.update_block(next_block); - router_ref.update_block(|b| b.time = b.time.plus_seconds(WEEK * 105)); - - // Check if rewards for the first and the second weeks equal 100_000_000 ASTRO - let resp: Vec = router_ref - .wrap() - .query_wasm_smart( - &base_pack.escrow_fee_distributor.clone().unwrap().address, - &QueryMsg::AvailableRewardPerWeek { - start_after: None, - limit: Some(2), - }, - ) - .unwrap(); - assert_eq!( - vec![Uint128::new(100_000_000), Uint128::new(100_000_000)], - resp - ); - - // Claim fees for max period for user1(firstly 1 period) - router_ref - .execute_contract( - user1.clone(), - base_pack.escrow_fee_distributor.clone().unwrap().address, - &ExecuteMsg::Claim { - recipient: None, - max_periods: Some(1), - }, - &[], - ) - .unwrap(); - - check_balance( - router_ref, - &base_pack.astro_token.clone().unwrap().address, - &user1, - 50_000_000, - ); - - router_ref - .execute_contract( - user1.clone(), - base_pack.escrow_fee_distributor.clone().unwrap().address, - &ExecuteMsg::Claim { - recipient: None, - max_periods: None, - }, - &[], - ) - .unwrap(); - - // Claim fees for max period for user2 - router_ref - .execute_contract( - user2.clone(), - base_pack.escrow_fee_distributor.clone().unwrap().address, - &ExecuteMsg::Claim { - recipient: None, - max_periods: None, - }, - &[], - ) - .unwrap(); - - // Check if user1's ASTRO balance is equal to 100_000_000 ASTRO - check_balance( - router_ref, - &base_pack.astro_token.clone().unwrap().address, - &user1, - 100_000_000, - ); - - // Check if user2's ASTRO balance equal to 100_000_000 ASTRO - check_balance( - router_ref, - &base_pack.astro_token.clone().unwrap().address, - &user2, - 100_000_000, - ); - - // Check if distributor's ASTRO balance equal to 0 - check_balance( - router_ref, - &base_pack.astro_token.clone().unwrap().address, - &base_pack.escrow_fee_distributor.clone().unwrap().address, - 0, - ); -} - -#[test] -fn claim_multiple_users() { - let mut router = mock_app(); - let router_ref = &mut router; - let owner = Addr::unchecked(OWNER.clone()); - let maker = Addr::unchecked(MAKER.clone()); - let user1 = Addr::unchecked(USER1.clone()); - let user2 = Addr::unchecked(USER2.clone()); - let user3 = Addr::unchecked(USER3.clone()); - let user4 = Addr::unchecked(USER4.clone()); - let user5 = Addr::unchecked(USER5.clone()); - - let base_pack = init_astroport_test_package(router_ref).unwrap(); - - let xastro_token = base_pack.get_staking_xastro(router_ref); - - for user in [user1.clone(), user2.clone(), user3.clone(), user4.clone()] { - // Sends 200_000_000 xASTRO tokens to users - mint( - router_ref, - base_pack.staking.clone().unwrap().address, - xastro_token.clone(), - &user, - 200, - ); - - // Checks if user's xASTRO balance is equal to 200 * 1000_000 ASTRO - check_balance( - router_ref, - &xastro_token.clone(), - &user, - 200 * MULTIPLIER as u128, - ); - - // Create lock for user for WEEK * 2 - base_pack - .create_lock(router_ref, user.clone(), WEEK * 2, 100) - .unwrap(); - } - - mint( - router_ref, - owner.clone(), - base_pack.astro_token.clone().unwrap().address, - &maker, - 1000, - ); - - // Sends 100_000_000 ASTRO from Maker to distributor for the first period - let msg = Cw20ExecuteMsg::Send { - contract: base_pack - .escrow_fee_distributor - .clone() - .unwrap() - .address - .to_string(), - msg: to_json_binary(&Cw20HookMsg::ReceiveTokens {}).unwrap(), - amount: Uint128::from(100 * MULTIPLIER as u128), - }; - - router_ref - .execute_contract( - maker.clone(), - base_pack.astro_token.clone().unwrap().address, - &msg, - &[], - ) - .unwrap(); - - // Checks if distributor's ASTRO balance is equal to 100_000_000 ASTRO - check_balance( - router_ref, - &base_pack.astro_token.clone().unwrap().address, - &base_pack.escrow_fee_distributor.clone().unwrap().address, - 100 * MULTIPLIER as u128, - ); - - // Check if rewards per week are set to 100_000_000 ASTRO - let resp: Vec = router_ref - .wrap() - .query_wasm_smart( - &base_pack.escrow_fee_distributor.clone().unwrap().address, - &QueryMsg::AvailableRewardPerWeek { - start_after: None, - limit: None, - }, - ) - .unwrap(); - assert_eq!(vec![Uint128::new(100_000_000)], resp); - - // Check if weekly voting supply can be queried - let resp: VotingPowerResponse = router_ref - .wrap() - .query_wasm_smart( - &base_pack.voting_escrow.clone().unwrap().address, - &VotingEscrowQueryMsg::TotalVotingPowerAt { - time: router_ref.block_info().time.seconds(), - }, - ) - .unwrap(); - assert_eq!(Uint128::new(411_538_456), resp.voting_power); - - // Go to the next week - router_ref.update_block(next_block); - router_ref.update_block(|b| b.time = b.time.plus_seconds(WEEK)); - - // Perform an operation for an unlimited number of users - let err = router_ref - .execute_contract( - user1.clone(), - base_pack.escrow_fee_distributor.clone().unwrap().address, - &ExecuteMsg::ClaimMany { - receivers: vec![ - user1.to_string(), - user2.to_string(), - user3.to_string(), - user4.to_string(), - Addr::unchecked("user5").to_string(), - Addr::unchecked("user6").to_string(), - Addr::unchecked("user7").to_string(), - Addr::unchecked("user8").to_string(), - Addr::unchecked("user9").to_string(), - Addr::unchecked("user10").to_string(), - Addr::unchecked("user11").to_string(), - ], - }, - &[], - ) - .unwrap_err(); - - assert_eq!( - "Exceeded account limit for the claim operation!", - err.root_cause().to_string() - ); - - mint( - router_ref, - base_pack.staking.clone().unwrap().address, - xastro_token.clone(), - &user5, - 200, - ); - - // Check if user5's xASTRO balance is equal to 200 * 1000_000 - check_balance( - router_ref, - &xastro_token.clone(), - &user5, - 200 * MULTIPLIER as u128, - ); - - // Create lock for user5 for WEEK * 2 - base_pack - .create_lock(router_ref, user5.clone(), WEEK * 2, 100) - .unwrap(); - - // Claim for all users - router_ref - .execute_contract( - user1.clone(), - base_pack.escrow_fee_distributor.clone().unwrap().address, - &ExecuteMsg::ClaimMany { - receivers: vec![ - user1.to_string(), - user2.to_string(), - user3.to_string(), - user4.to_string(), - user5.to_string(), - ], - }, - &[], - ) - .unwrap(); - - // Checks if user's ASTRO balance is equal to 100 / 4 = 25 * 1_000_000 - for user in [user1.clone(), user2.clone(), user3.clone(), user4.clone()] { - check_balance( - router_ref, - &base_pack.astro_token.clone().unwrap().address, - &user, - 25 * MULTIPLIER as u128, - ); - } - - // Checks if user5's ASTRO balance is equal to 0. Cannot claim for the current period - check_balance( - router_ref, - &base_pack.astro_token.clone().unwrap().address, - &user5, - 0, - ); - - // Check if distributor's ASTRO balance equal to 0 - check_balance( - router_ref, - &base_pack.astro_token.clone().unwrap().address, - &base_pack.escrow_fee_distributor.clone().unwrap().address, - 0, - ); - - // Going to next week - router_ref.update_block(next_block); - router_ref.update_block(|b| b.time = b.time.plus_seconds(WEEK)); - - // Sends 900_000_000 ASTRO from the Maker to the distributor for the third period - let msg = Cw20ExecuteMsg::Send { - contract: base_pack - .escrow_fee_distributor - .clone() - .unwrap() - .address - .to_string(), - msg: to_json_binary(&Cw20HookMsg::ReceiveTokens {}).unwrap(), - amount: Uint128::from(900 * MULTIPLIER as u128), - }; - - router_ref - .execute_contract( - maker.clone(), - base_pack.astro_token.clone().unwrap().address, - &msg, - &[], - ) - .unwrap(); - - // Check if rewards per week are set to 900_000_000 ASTRO - let resp: Vec = router_ref - .wrap() - .query_wasm_smart( - &base_pack.escrow_fee_distributor.clone().unwrap().address, - &QueryMsg::AvailableRewardPerWeek { - start_after: None, - limit: None, - }, - ) - .unwrap(); - assert_eq!( - vec![Uint128::new(100_000_000), Uint128::new(900_000_000),], - resp - ); - - // Try to claim for all users for the current period - router_ref - .execute_contract( - user1.clone(), - base_pack.escrow_fee_distributor.clone().unwrap().address, - &ExecuteMsg::ClaimMany { - receivers: vec![ - user1.to_string(), - user2.to_string(), - user3.to_string(), - user4.to_string(), - user5.to_string(), - ], - }, - &[], - ) - .unwrap(); - - // Checks if the user's ASTRO token balance is still equal to 100 / 4 = 25 * 1_000_000 - for user in [user1.clone(), user2.clone(), user3.clone(), user4.clone()] { - check_balance( - router_ref, - &base_pack.astro_token.clone().unwrap().address, - &user, - 25 * MULTIPLIER as u128, - ); - } - - // Check if user5's ASTRO balance is 0 for the first lock week - check_balance( - router_ref, - &base_pack.astro_token.clone().unwrap().address, - &user5, - 0, - ); - - // Check if the distributor's ASTRO balance is still equal to 900_000_000 - check_balance( - router_ref, - &base_pack.astro_token.clone().unwrap().address, - &base_pack.escrow_fee_distributor.clone().unwrap().address, - 900_000_000, - ); - - // Going to next week - router_ref.update_block(next_block); - router_ref.update_block(|b| b.time = b.time.plus_seconds(WEEK)); - - // Try to claim for all users - router_ref - .execute_contract( - user1.clone(), - base_pack.escrow_fee_distributor.clone().unwrap().address, - &ExecuteMsg::ClaimMany { - receivers: vec![ - user1.to_string(), - user2.to_string(), - user3.to_string(), - user4.to_string(), - user5.to_string(), - ], - }, - &[], - ) - .unwrap(); - - // Checks if the user's ASTRO balance is still equal to 25 * 100_000_000. - for user in [user1.clone(), user2.clone(), user3.clone(), user4.clone()] { - check_balance( - router_ref, - &base_pack.astro_token.clone().unwrap().address, - &user, - 25 * MULTIPLIER as u128, - ); - } - - // Checks if user5's ASTRO balance equal to 900_000_000 for the second week of lock - check_balance( - router_ref, - &base_pack.astro_token.clone().unwrap().address, - &user5, - 900_000_000, - ); - - // Check if distributor's ASTRO balance still equal to 0 - check_balance( - router_ref, - &base_pack.astro_token.clone().unwrap().address, - &base_pack.escrow_fee_distributor.clone().unwrap().address, - 0, - ); -} - -#[test] -fn is_claim_enabled() { - let mut router = mock_app(); - let router_ref = &mut router; - let owner = Addr::unchecked(OWNER.clone()); - let maker = Addr::unchecked(MAKER.clone()); - let user1 = Addr::unchecked(USER1.clone()); - let user2 = Addr::unchecked(USER2.clone()); - - let base_pack = init_astroport_test_package(router_ref).unwrap(); - - let xastro_token = base_pack.get_staking_xastro(router_ref); - - // Sends 200_000_000 xASTRO tokens to users - for user in [user1.clone(), user2.clone()] { - mint( - router_ref, - base_pack.staking.clone().unwrap().address, - xastro_token.clone(), - &user, - 200, - ); - - // Checks if user's xASTRO token balance is equal to 200 * 1000_000 - check_balance( - router_ref, - &xastro_token.clone(), - &user, - 200 * MULTIPLIER as u128, - ); - - // Create a lock for user for WEEK * 3 - base_pack - .create_lock(router_ref, user.clone(), WEEK * 3, 100) - .unwrap(); - } - - // Send 1000_000_000 ASTRO tokens to the Maker - mint( - router_ref, - owner.clone(), - base_pack.astro_token.clone().unwrap().address, - &maker, - 1000, - ); - - // Send 100_000_000 ASTRO from the Maker to the distributor for the first period - let msg = Cw20ExecuteMsg::Send { - contract: base_pack - .escrow_fee_distributor - .clone() - .unwrap() - .address - .to_string(), - msg: to_json_binary(&Cw20HookMsg::ReceiveTokens {}).unwrap(), - amount: Uint128::from(100 * MULTIPLIER as u128), - }; - - router_ref - .execute_contract( - maker.clone(), - base_pack.astro_token.clone().unwrap().address, - &msg, - &[], - ) - .unwrap(); - - // Check if distributor's ASTRO balance is equal to 100_000_000 ASTRO - check_balance( - router_ref, - &base_pack.astro_token.clone().unwrap().address, - &base_pack.escrow_fee_distributor.clone().unwrap().address, - 100 * MULTIPLIER as u128, - ); - - // Checl if rewards are set to 100_000_000 ASTRO - let resp: Vec = router_ref - .wrap() - .query_wasm_smart( - &base_pack.escrow_fee_distributor.clone().unwrap().address, - &QueryMsg::AvailableRewardPerWeek { - start_after: None, - limit: None, - }, - ) - .unwrap(); - assert_eq!(vec![Uint128::new(100_000_000)], resp); - - // Going to the next week - router_ref.update_block(next_block); - router_ref.update_block(|b| b.time = b.time.plus_seconds(WEEK)); - - // Disable claiming - router_ref - .execute_contract( - owner.clone(), - base_pack.escrow_fee_distributor.clone().unwrap().address, - &ExecuteMsg::UpdateConfig { - claim_many_limit: None, - is_claim_disabled: Some(true), - }, - &[], - ) - .unwrap(); - - // Try to claim fees for all users for the first week - let err = router_ref - .execute_contract( - user1.clone(), - base_pack.escrow_fee_distributor.clone().unwrap().address, - &ExecuteMsg::ClaimMany { - receivers: vec![user1.to_string(), user2.to_string()], - }, - &[], - ) - .unwrap_err(); - - assert_eq!("Claiming is disabled!", err.root_cause().to_string()); - - // Send 100_000_000 ASTRO from the Maker to the distributor for the first period - let msg = Cw20ExecuteMsg::Send { - contract: base_pack - .escrow_fee_distributor - .clone() - .unwrap() - .address - .to_string(), - msg: to_json_binary(&Cw20HookMsg::ReceiveTokens {}).unwrap(), - amount: Uint128::from(100 * MULTIPLIER as u128), - }; - - router_ref - .execute_contract( - maker.clone(), - base_pack.astro_token.clone().unwrap().address, - &msg, - &[], - ) - .unwrap(); - - // Going to the next week - router_ref.update_block(next_block); - router_ref.update_block(|b| b.time = b.time.plus_seconds(WEEK)); - - // Try to claim fees for all users - let err = router_ref - .execute_contract( - user1.clone(), - base_pack.escrow_fee_distributor.clone().unwrap().address, - &ExecuteMsg::ClaimMany { - receivers: vec![user1.to_string(), user2.to_string()], - }, - &[], - ) - .unwrap_err(); - - assert_eq!("Claiming is disabled!", err.root_cause().to_string()); - - // Going to the next week - router_ref.update_block(next_block); - router_ref.update_block(|b| b.time = b.time.plus_seconds(WEEK)); - - // Enable claiming - router_ref - .execute_contract( - owner.clone(), - base_pack.escrow_fee_distributor.clone().unwrap().address, - &ExecuteMsg::UpdateConfig { - claim_many_limit: None, - is_claim_disabled: Some(false), - }, - &[], - ) - .unwrap(); - - // Try to claim fees for all users - router_ref - .execute_contract( - user1.clone(), - base_pack.escrow_fee_distributor.clone().unwrap().address, - &ExecuteMsg::ClaimMany { - receivers: vec![user1.to_string(), user2.to_string()], - }, - &[], - ) - .unwrap(); - - // Check if the user's ASTRO token balance is equal to 25 * 1_000_000 - for user in [user1.clone(), user2.clone()] { - check_balance( - router_ref, - &base_pack.astro_token.clone().unwrap().address, - &user, - 100 * MULTIPLIER as u128, - ); - } - - // Check if the distributor's ASTRO balance is 0 - check_balance( - router_ref, - &base_pack.astro_token.clone().unwrap().address, - &base_pack.escrow_fee_distributor.clone().unwrap().address, - 0, - ); -} diff --git a/contracts/voting_escrow/Cargo.toml b/contracts/voting_escrow/Cargo.toml index 647ce413..86d2aa4a 100644 --- a/contracts/voting_escrow/Cargo.toml +++ b/contracts/voting_escrow/Cargo.toml @@ -28,7 +28,4 @@ astroport-governance = { path = "../../packages/astroport-governance", version = astroport = { git = "https://github.com/astroport-fi/hidden_astroport_core", version = "4" } [dev-dependencies] -cw-multi-test = { git = "https://github.com/astroport-fi/cw-multi-test", branch = "feat/bank_with_send_hooks", features = ["cosmwasm_1_1"] } -astroport-staking = { git = "https://github.com/astroport-fi/hidden_astroport_core", version = "2" } -anyhow = "1" -proptest = "1.0" +cw-multi-test = "1" diff --git a/contracts/voting_escrow/src/contract.rs b/contracts/voting_escrow/src/contract.rs index b442ba79..6a6016fb 100644 --- a/contracts/voting_escrow/src/contract.rs +++ b/contracts/voting_escrow/src/contract.rs @@ -6,7 +6,7 @@ use cosmwasm_std::{ Response, StdError, StdResult, Uint128, }; use cw2::set_contract_version; -use cw20::{Logo, LogoInfo, MarketingInfoResponse, TokenInfoResponse}; +use cw20::{BalanceResponse, Logo, LogoInfo, MarketingInfoResponse, TokenInfoResponse}; use cw20_base::contract::{ execute_update_marketing, execute_upload_logo, query_download_logo, query_marketing_info, }; @@ -230,7 +230,8 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { } QueryMsg::Config {} => to_json_binary(&CONFIG.load(deps.storage)?), QueryMsg::Balance { address } => { - to_json_binary(&query_user_voting_power(deps, env, address, None)?) + let user_vp = query_user_voting_power(deps, env, address, None)?; + to_json_binary(&BalanceResponse { balance: user_vp }) } QueryMsg::TokenInfo {} => to_json_binary(&query_token_info(deps, env)?), QueryMsg::MarketingInfo {} => to_json_binary(&query_marketing_info(deps)?), @@ -257,6 +258,8 @@ pub fn query_user_voting_power( timestamp: Option, ) -> StdResult { let address = deps.api.addr_validate(&address)?; - let position = Lock::load_at_ts(deps.storage, env.block.time.seconds(), &address, timestamp)?; - Ok(position.amount) + let voting_power = + Lock::load_at_ts(deps.storage, env.block.time.seconds(), &address, timestamp)? + .get_voting_power(); + Ok(voting_power) } diff --git a/contracts/voting_escrow/src/error.rs b/contracts/voting_escrow/src/error.rs index e3115810..f66e4acf 100644 --- a/contracts/voting_escrow/src/error.rs +++ b/contracts/voting_escrow/src/error.rs @@ -4,7 +4,7 @@ use cw_utils::PaymentError; use thiserror::Error; /// This enum describes vxASTRO contract errors -#[derive(Error, Debug)] +#[derive(Error, Debug, PartialEq)] pub enum ContractError { #[error("{0}")] Std(#[from] StdError), diff --git a/contracts/voting_escrow/src/marketing_validation.rs b/contracts/voting_escrow/src/marketing_validation.rs index ab700981..c9acf73c 100644 --- a/contracts/voting_escrow/src/marketing_validation.rs +++ b/contracts/voting_escrow/src/marketing_validation.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::StdError; +use cosmwasm_std::ensure; use cw20::Logo; use crate::error::ContractError; @@ -19,33 +19,32 @@ pub fn validate_text(text: &str, name: &str) -> Result<(), ContractError> { } pub fn validate_whitelist_links(links: &[String]) -> Result<(), ContractError> { - links.iter().try_for_each(|link| { - if !link.ends_with('/') { - return Err(ContractError::MarketingInfoValidationError(format!( - "Whitelist link should end with '/': {link}" - ))); - } - validate_link(link) - }) + links.iter().try_for_each(validate_link) } pub fn validate_link(link: &String) -> Result<(), ContractError> { - if link - .chars() - .any(|c| !c.is_ascii_alphanumeric() && !SAFE_LINK_CHARS.contains(c)) - { - Err(StdError::generic_err(format!("Link contains invalid characters: {link}")).into()) - } else { - Ok(()) - } + ensure!( + link.ends_with('/'), + ContractError::MarketingInfoValidationError(format!( + "Whitelist link should end with '/': {link}" + )) + ); + + ensure!( + link.chars() + .all(|c| c.is_ascii_alphanumeric() || SAFE_LINK_CHARS.contains(c)), + ContractError::MarketingInfoValidationError(format!( + "Link contains invalid characters: {link}" + )) + ); + + Ok(()) } pub fn check_link(link: &String, whitelisted_links: &[String]) -> Result<(), ContractError> { - if validate_link(link).is_err() { - Err(ContractError::MarketingInfoValidationError(format!( - "Logo link is invalid: {link}" - ))) - } else if !whitelisted_links.iter().any(|wl| link.starts_with(wl)) { + validate_link(link)?; + + if !whitelisted_links.iter().any(|wl| link.starts_with(wl)) { Err(ContractError::MarketingInfoValidationError(format!( "Logo link is not whitelisted: {link}" ))) diff --git a/contracts/voting_escrow/src/state.rs b/contracts/voting_escrow/src/state.rs index fa99c719..6203da44 100644 --- a/contracts/voting_escrow/src/state.rs +++ b/contracts/voting_escrow/src/state.rs @@ -30,6 +30,17 @@ pub struct Lock { pub block_time: u64, } +impl Default for Lock { + fn default() -> Self { + Lock { + amount: Uint128::zero(), + end: None, + user: default_addr(), + block_time: 0, + } + } +} + impl Lock { pub fn load_at_ts( storage: &dyn Storage, @@ -37,17 +48,16 @@ impl Lock { user: &Addr, timestamp: Option, ) -> StdResult { - match timestamp.unwrap_or(block_time) { + let lock = match timestamp.unwrap_or(block_time) { timestamp if timestamp == block_time => LOCKED.may_load(storage, &user), timestamp => LOCKED.may_load_at_height(storage, &user, timestamp), - } - .map(|lock| { - lock.unwrap_or_else(|| Lock { - amount: Uint128::zero(), - end: None, - user: user.clone(), - block_time, - }) + }? + .unwrap_or_default(); + + Ok(Lock { + user: user.clone(), + block_time, + ..lock }) } @@ -115,6 +125,14 @@ impl Lock { Err(ContractError::NotInUnlockingState {}) } } + + pub fn get_voting_power(&self) -> Uint128 { + if self.end.is_some() { + Uint128::zero() + } else { + self.amount + } + } } impl From for LockInfoResponse { diff --git a/contracts/voting_escrow/tests/helper.rs b/contracts/voting_escrow/tests/helper.rs new file mode 100644 index 00000000..54120313 --- /dev/null +++ b/contracts/voting_escrow/tests/helper.rs @@ -0,0 +1,134 @@ +use cosmwasm_std::{Addr, Coin, Empty, StdResult, Uint128}; +use cw_multi_test::error::AnyResult; +use cw_multi_test::{AppResponse, BankSudo, BasicApp, Contract, ContractWrapper, Executor}; + +use astroport_governance::voting_escrow::{ + ExecuteMsg, InstantiateMsg, LockInfoResponse, QueryMsg, UpdateMarketingInfo, +}; + +fn vxastro_contract() -> Box> { + Box::new(ContractWrapper::new_with_empty( + astroport_voting_escrow::contract::execute, + astroport_voting_escrow::contract::instantiate, + astroport_voting_escrow::contract::query, + )) +} + +pub struct EscrowHelper { + pub app: BasicApp, + pub owner: Addr, + pub xastro_denom: String, + pub vxastro_contract: Addr, +} + +impl EscrowHelper { + pub fn new(xastro_denom: &str) -> Self { + let mut app = BasicApp::default(); + let owner = Addr::unchecked("owner"); + + let vxastro_code_id = app.store_code(vxastro_contract()); + let vxastro_contract = app + .instantiate_contract( + vxastro_code_id, + owner.clone(), + &InstantiateMsg { + deposit_denom: xastro_denom.to_string(), + marketing: Some(UpdateMarketingInfo { + project: None, + description: None, + marketing: Some(owner.to_string()), + logo: None, + }), + logo_urls_whitelist: vec!["https://astroport.fi/".to_string()], + }, + &[], + "label", + None, + ) + .unwrap(); + + Self { + app, + owner, + xastro_denom: xastro_denom.to_string(), + vxastro_contract, + } + } + + pub fn mint_tokens(&mut self, user: &Addr, coins: &[Coin]) -> AnyResult { + self.app.sudo( + BankSudo::Mint { + to_address: user.to_string(), + amount: coins.to_vec(), + } + .into(), + ) + } + + pub fn lock(&mut self, user: &Addr, coins: &[Coin]) -> AnyResult { + self.app.execute_contract( + user.clone(), + self.vxastro_contract.clone(), + &ExecuteMsg::Lock { receiver: None }, + coins, + ) + } + + pub fn unlock(&mut self, user: &Addr) -> AnyResult { + self.app.execute_contract( + user.clone(), + self.vxastro_contract.clone(), + &ExecuteMsg::Unlock {}, + &[], + ) + } + + pub fn relock(&mut self, user: &Addr) -> AnyResult { + self.app.execute_contract( + user.clone(), + self.vxastro_contract.clone(), + &ExecuteMsg::Relock {}, + &[], + ) + } + + pub fn withdraw(&mut self, user: &Addr) -> AnyResult { + self.app.execute_contract( + user.clone(), + self.vxastro_contract.clone(), + &ExecuteMsg::Withdraw {}, + &[], + ) + } + + pub fn timetravel(&mut self, time: u64) { + self.app.update_block(|block| { + block.time = block.time.plus_seconds(time); + }) + } + + pub fn user_vp(&self, user: &Addr, time: Option) -> StdResult { + self.app.wrap().query_wasm_smart( + &self.vxastro_contract, + &QueryMsg::UserVotingPower { + user: user.to_string(), + time, + }, + ) + } + + pub fn total_vp(&self, time: Option) -> StdResult { + self.app + .wrap() + .query_wasm_smart(&self.vxastro_contract, &QueryMsg::TotalVotingPower { time }) + } + + pub fn lock_info(&self, user: &Addr) -> StdResult { + self.app.wrap().query_wasm_smart( + &self.vxastro_contract, + &QueryMsg::LockInfo { + user: user.to_string(), + }, + ) + } +} diff --git a/contracts/voting_escrow/tests/voting_escrow_integration.rs b/contracts/voting_escrow/tests/voting_escrow_integration.rs new file mode 100644 index 00000000..98b4a85f --- /dev/null +++ b/contracts/voting_escrow/tests/voting_escrow_integration.rs @@ -0,0 +1,247 @@ +use cosmwasm_std::{coin, Addr}; +use cw20::{BalanceResponse, MarketingInfoResponse, TokenInfoResponse}; +use cw_utils::PaymentError; + +use astroport_governance::voting_escrow::{Config, LockInfoResponse, QueryMsg}; +use astroport_voting_escrow::error::ContractError; +use astroport_voting_escrow::state::UNLOCK_PERIOD; + +use crate::helper::EscrowHelper; + +mod helper; +#[test] +fn test_lock() { + let xastro_denom = "xastro"; + let mut helper = EscrowHelper::new(xastro_denom); + + let user1 = Addr::unchecked("user1"); + + // Non existing user has 0 voting power + assert_eq!(0, helper.user_vp(&user1, None).unwrap().u128()); + + // Try to lock non xASTRO tokens + let non_xastro = coin(100, "non_xastro"); + helper.mint_tokens(&user1, &[non_xastro.clone()]).unwrap(); + let err = helper.lock(&user1, &[non_xastro.clone()]).unwrap_err(); + assert_eq!( + ContractError::PaymentError(PaymentError::MissingDenom(xastro_denom.to_string())), + err.downcast().unwrap(), + ); + + // Try to mix xASTRO and non xASTRO tokens + let xastro_coin = coin(100, xastro_denom); + helper.mint_tokens(&user1, &[xastro_coin.clone()]).unwrap(); + + let err = helper + .lock(&user1, &[xastro_coin.clone(), non_xastro]) + .unwrap_err(); + assert_eq!( + ContractError::PaymentError(PaymentError::MultipleDenoms {}), + err.downcast().unwrap(), + ); + + // Correct lock + helper.lock(&user1, &[xastro_coin]).unwrap(); + + let user_vp = helper.user_vp(&user1, None).unwrap(); + assert_eq!(100, user_vp.u128()); + + // Lock more + let xastro_coin = coin(200, xastro_denom); + helper.mint_tokens(&user1, &[xastro_coin.clone()]).unwrap(); + helper.lock(&user1, &[xastro_coin]).unwrap(); + + let user_vp = helper.user_vp(&user1, None).unwrap(); + assert_eq!(300, user_vp.u128()); + + // Voting power 1 second before is 0 + let user_vp = helper + .user_vp(&user1, Some(helper.app.block_info().time.seconds() - 1)) + .unwrap(); + assert_eq!(0, user_vp.u128()); + + helper.timetravel(10000); + + // lock more + let xastro_coin = coin(1000, xastro_denom); + helper.mint_tokens(&user1, &[xastro_coin.clone()]).unwrap(); + helper.lock(&user1, &[xastro_coin]).unwrap(); + assert_eq!(1300, helper.user_vp(&user1, None).unwrap().u128()); +} + +#[test] +fn test_unlok() { + let xastro_denom = "xastro"; + let mut helper = EscrowHelper::new(xastro_denom); + + let user1 = Addr::unchecked("user1"); + + // Try to unlock without locking + let err = helper.unlock(&user1).unwrap_err(); + assert_eq!(ContractError::ZeroBalance {}, err.downcast().unwrap()); + + // Try to relock without locking + let err = helper.relock(&user1).unwrap_err(); + assert_eq!( + ContractError::NotInUnlockingState {}, + err.downcast().unwrap() + ); + + // Try to withdraw without locking + let err = helper.withdraw(&user1).unwrap_err(); + assert_eq!( + ContractError::NotInUnlockingState {}, + err.downcast().unwrap() + ); + + // Create lock + let xastro_coin = coin(100, xastro_denom); + helper.mint_tokens(&user1, &[xastro_coin.clone()]).unwrap(); + helper.lock(&user1, &[xastro_coin.clone()]).unwrap(); + assert_eq!(100, helper.user_vp(&user1, None).unwrap().u128()); + assert_eq!(100, helper.total_vp(None).unwrap().u128()); + + // Try to relock not unlocked position + let err = helper.relock(&user1).unwrap_err(); + assert_eq!( + ContractError::NotInUnlockingState {}, + err.downcast().unwrap() + ); + // Withdraw still does nothing + let err = helper.withdraw(&user1).unwrap_err(); + assert_eq!( + ContractError::NotInUnlockingState {}, + err.downcast().unwrap() + ); + + // Start unlocking + let start_ts = helper.app.block_info().time.seconds(); + helper.unlock(&user1).unwrap(); + + // User lost voting power immediately. His contribution is removed from total vp + assert_eq!(0, helper.user_vp(&user1, None).unwrap().u128()); + assert_eq!(0, helper.total_vp(None).unwrap().u128()); + + helper.timetravel(10000); + + // Try to unlock again + let err = helper.unlock(&user1).unwrap_err(); + assert_eq!(ContractError::PositionUnlocking {}, err.downcast().unwrap()); + + let lock = helper.lock_info(&user1).unwrap(); + assert_eq!( + lock, + LockInfoResponse { + amount: xastro_coin.amount, + end: Some(start_ts + UNLOCK_PERIOD) + } + ); + + // Try to withdraw before unlock + let err = helper.withdraw(&user1).unwrap_err(); + assert_eq!( + ContractError::UnlockPeriodNotExpired(start_ts + UNLOCK_PERIOD), + err.downcast().unwrap() + ); + + // Cant lock while in unlocking state + helper.mint_tokens(&user1, &[xastro_coin.clone()]).unwrap(); + let err = helper.lock(&user1, &[xastro_coin.clone()]).unwrap_err(); + assert_eq!(ContractError::PositionUnlocking {}, err.downcast().unwrap()); + + // Relock works + helper.relock(&user1).unwrap(); + // Voting power is recovered + assert_eq!(100, helper.user_vp(&user1, None).unwrap().u128()); + assert_eq!(100, helper.total_vp(None).unwrap().u128()); + + let lock = helper.lock_info(&user1).unwrap(); + assert_eq!( + lock, + LockInfoResponse { + amount: xastro_coin.amount, + end: None, + } + ); + + // Normal unlocking flow + let bal_before = helper + .app + .wrap() + .query_balance(&user1, xastro_denom) + .unwrap() + .amount; + helper.unlock(&user1).unwrap(); + helper.timetravel(UNLOCK_PERIOD); + helper.withdraw(&user1).unwrap(); + + assert_eq!(0, helper.user_vp(&user1, None).unwrap().u128()); + let bal_after = helper + .app + .wrap() + .query_balance(&user1, xastro_denom) + .unwrap() + .amount; + assert_eq!(xastro_coin.amount, bal_after - bal_before); +} + +#[test] +fn test_general_queries() { + let xastro_denom = "xastro"; + let mut helper = EscrowHelper::new(xastro_denom); + + let user1 = Addr::unchecked("user1"); + let xastro_coin = coin(100, xastro_denom); + helper.mint_tokens(&user1, &[xastro_coin.clone()]).unwrap(); + helper.lock(&user1, &[xastro_coin.clone()]).unwrap(); + + let config: Config = helper + .app + .wrap() + .query_wasm_smart(&helper.vxastro_contract, &QueryMsg::Config {}) + .unwrap(); + assert_eq!( + config, + Config { + deposit_denom: xastro_denom.to_string(), + logo_urls_whitelist: vec!["https://astroport.fi/".to_string()] + } + ); + + let token_info: TokenInfoResponse = helper + .app + .wrap() + .query_wasm_smart(&helper.vxastro_contract, &QueryMsg::TokenInfo {}) + .unwrap(); + let total_vp = helper.total_vp(None).unwrap(); + + assert_eq!( + token_info, + TokenInfoResponse { + name: "Vote Escrowed xASTRO".to_string(), + symbol: "vxASTRO".to_string(), + decimals: 6, + total_supply: total_vp, + } + ); + + let cw20_bal_resp: BalanceResponse = helper + .app + .wrap() + .query_wasm_smart( + &helper.vxastro_contract, + &QueryMsg::Balance { + address: user1.to_string(), + }, + ) + .unwrap(); + let user_vp = helper.user_vp(&user1, None).unwrap(); + assert_eq!(user_vp, cw20_bal_resp.balance); + + let marketing_info: MarketingInfoResponse = helper + .app + .wrap() + .query_wasm_smart(&helper.vxastro_contract, &QueryMsg::MarketingInfo {}) + .unwrap(); + assert_eq!(marketing_info.marketing, Some(helper.owner.clone())); +} diff --git a/contracts/voting_escrow/tests/vxastro_integration.rs b/contracts/voting_escrow/tests/vxastro_integration.rs deleted file mode 100644 index 8b137891..00000000 --- a/contracts/voting_escrow/tests/vxastro_integration.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/astroport-governance/src/escrow_fee_distributor.rs b/packages/astroport-governance/src/escrow_fee_distributor.rs deleted file mode 100644 index f71782d0..00000000 --- a/packages/astroport-governance/src/escrow_fee_distributor.rs +++ /dev/null @@ -1,94 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Uint128}; -use cw20::Cw20ReceiveMsg; - -/// This structure describes the basic settings for creating a contract. -#[cw_serde] -pub struct InstantiateMsg { - /// Admin address - pub owner: String, - /// Fee token address - pub astro_token: String, - /// Voting escrow contract address - pub voting_escrow_addr: String, - /// Max limit of addresses to claim rewards for in a single call - pub claim_many_limit: Option, - /// Whether reward claiming is disabled - pub is_claim_disabled: Option, -} - -/// This structure describes the execute messages available in the contract. -#[cw_serde] -pub enum ExecuteMsg { - /// ProposeNewOwner creates a request to change contract ownership - ProposeNewOwner { - /// The newly proposed owner - owner: String, - /// The validity period of the offer to change the contract owner - expires_in: u64, - }, - /// DropOwnershipProposal removes the request to change contract ownership - DropOwnershipProposal {}, - /// ClaimOwnership claims contract ownership - ClaimOwnership {}, - /// Claim claims staking rewards for a single staker and sends them to the specified recipient - Claim { - recipient: Option, - max_periods: Option, - }, - /// ClaimMany claims staking rewards for multiple addresses in a single call - ClaimMany { receivers: Vec }, - /// UpdateConfig updates the contract configuration - UpdateConfig { - /// Max limit of addresses to claim rewards for in a single call - claim_many_limit: Option, - /// Whether reward claiming is disabled - is_claim_disabled: Option, - }, - /// Receive receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template - Receive(Cw20ReceiveMsg), -} - -/// This structure describes query messages available in the contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Config returns control settings using a custom [`ConfigResponse`] structure - #[returns(ConfigResponse)] - Config {}, - /// UserReward returns the reward amount that can be claimed by a staker in the form of ASTRO at a specified timestamp - #[returns(Uint128)] - UserReward { user: String, timestamp: u64 }, - /// AvailableRewardPerWeek returns a vector that contains the total reward amount per week distributed to vxASTRO stakers - #[returns(Vec)] - AvailableRewardPerWeek { - start_after: Option, - limit: Option, - }, -} - -/// This structure describes the parameters returned when querying for the contract configuration. -#[cw_serde] -pub struct ConfigResponse { - /// Address that's allowed to change contract parameters - pub owner: Addr, - /// Fee token address (ASTRO token) - pub astro_token: Addr, - /// Voting escrow contract address - pub voting_escrow_addr: Addr, - /// Max limit of addresses to claim rewards for in a single call - pub claim_many_limit: u64, - /// Wthether reward claiming is disabled - pub is_claim_disabled: bool, -} - -/// This structure describes a migration message. -#[cw_serde] -pub struct MigrateMsg {} - -/// This structure describes custom hooks for a CW20. -#[cw_serde] -pub enum Cw20HookMsg { - /// ReceiveTokens receives tokens into the contract and triggers a vxASTRO checkpoint. - ReceiveTokens {}, -} diff --git a/packages/astroport-governance/src/lib.rs b/packages/astroport-governance/src/lib.rs index 69787fcd..8d1b8010 100644 --- a/packages/astroport-governance/src/lib.rs +++ b/packages/astroport-governance/src/lib.rs @@ -2,7 +2,6 @@ pub use astroport; pub mod assembly; pub mod builder_unlock; -pub mod escrow_fee_distributor; pub mod generator_controller; pub mod generator_controller_lite; pub mod hub; diff --git a/packages/astroport-tests/Cargo.toml b/packages/astroport-tests/Cargo.toml deleted file mode 100644 index 823d7d6d..00000000 --- a/packages/astroport-tests/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "astroport-tests" -version = "1.0.0" -authors = ["Astroport"] -edition = "2021" -repository = "https://github.com/astroport-fi/astroport-governance" -homepage = "https://astroport.fi" - -[features] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] - -[dependencies] -cw2 = "0.15" -cw20 = "0.15" -cosmwasm-std = "1.1" - -cosmwasm-schema = "1.1" -cw-multi-test = "0.15" -astroport = { git = "https://github.com/astroport-fi/hidden_astroport_core" } - -astroport-escrow-fee-distributor = { path = "../../contracts/escrow_fee_distributor" } -astroport-governance = { path = "../astroport-governance" } -voting-escrow = { path = "../../contracts/voting_escrow" } -generator-controller = { path = "../../contracts/generator_controller" } -astroport-generator = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -astroport-pair = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -astroport-factory = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -astroport-token = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -astroport-staking = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -astroport-whitelist = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -anyhow = "1" diff --git a/packages/astroport-tests/src/base.rs b/packages/astroport-tests/src/base.rs deleted file mode 100644 index 3403be3c..00000000 --- a/packages/astroport-tests/src/base.rs +++ /dev/null @@ -1,371 +0,0 @@ -use cosmwasm_schema::cw_serde; - -use astroport::staking; -use astroport::token::InstantiateMsg as AstroTokenInstantiateMsg; -use astroport_governance::escrow_fee_distributor::InstantiateMsg as EscrowFeeDistributorInstantiateMsg; -use astroport_governance::voting_escrow::{ - Cw20HookMsg, ExecuteMsg, InstantiateMsg as AstroVotingEscrowInstantiateMsg, QueryMsg, - VotingPowerResponse, -}; -use cosmwasm_std::{attr, to_json_binary, Addr, QueryRequest, StdResult, Uint128, WasmQuery}; -use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg, MinterResponse}; - -use anyhow::Result; -use cw_multi_test::{App, AppResponse, ContractWrapper, Executor}; - -pub const MULTIPLIER: u64 = 1_000_000; - -#[cw_serde] -pub struct ContractInfo { - pub address: Addr, - pub code_id: u64, -} - -#[cw_serde] -pub struct BaseAstroportTestPackage { - pub owner: Addr, - pub astro_token: Option, - pub escrow_fee_distributor: Option, - pub staking: Option, - pub voting_escrow: Option, -} - -#[cw_serde] -pub struct BaseAstroportTestInitMessage { - pub owner: Addr, -} - -impl BaseAstroportTestPackage { - pub fn init_all(router: &mut App, msg: BaseAstroportTestInitMessage) -> Self { - let mut base_pack = BaseAstroportTestPackage { - owner: msg.owner.clone(), - astro_token: None, - escrow_fee_distributor: None, - staking: None, - voting_escrow: None, - }; - - base_pack.init_astro_token(router, msg.owner.clone()); - base_pack.init_staking(router, msg.owner.clone()); - base_pack.init_voting_escrow(router, msg.owner.clone()); - base_pack.init_escrow_fee_distributor(router, msg.owner); - base_pack - } - - fn init_astro_token(&mut self, router: &mut App, owner: Addr) { - let astro_token_contract = Box::new(ContractWrapper::new_with_empty( - astroport_token::contract::execute, - astroport_token::contract::instantiate, - astroport_token::contract::query, - )); - - let astro_token_code_id = router.store_code(astro_token_contract); - - let init_msg = AstroTokenInstantiateMsg { - name: String::from("Astro token"), - symbol: String::from("ASTRO"), - decimals: 6, - initial_balances: vec![], - mint: Some(MinterResponse { - minter: owner.to_string(), - cap: None, - }), - marketing: None, - }; - - let astro_token_instance = router - .instantiate_contract( - astro_token_code_id, - owner, - &init_msg, - &[], - "Astro token", - None, - ) - .unwrap(); - - self.astro_token = Some(ContractInfo { - address: astro_token_instance, - code_id: astro_token_code_id, - }) - } - - fn init_staking(&mut self, router: &mut App, owner: Addr) { - let staking_contract = Box::new( - ContractWrapper::new_with_empty( - astroport_staking::contract::execute, - astroport_staking::contract::instantiate, - astroport_staking::contract::query, - ) - .with_reply_empty(astroport_staking::contract::reply), - ); - - let staking_code_id = router.store_code(staking_contract); - - let msg = staking::InstantiateMsg { - owner: owner.to_string(), - token_code_id: self.astro_token.clone().unwrap().code_id, - deposit_token_addr: self.astro_token.clone().unwrap().address.to_string(), - marketing: None, - }; - - let staking_instance = router - .instantiate_contract( - staking_code_id, - owner, - &msg, - &[], - String::from("xASTRO"), - None, - ) - .unwrap(); - - self.staking = Some(ContractInfo { - address: staking_instance, - code_id: staking_code_id, - }) - } - - pub fn get_staking_xastro(&self, router: &App) -> Addr { - let res = router - .wrap() - .query::(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: self.staking.clone().unwrap().address.to_string(), - msg: to_json_binary(&staking::QueryMsg::Config {}).unwrap(), - })) - .unwrap(); - - res.share_token_addr - } - - fn init_voting_escrow(&mut self, router: &mut App, owner: Addr) { - let voting_contract = Box::new(ContractWrapper::new_with_empty( - voting_escrow::contract::execute, - voting_escrow::contract::instantiate, - voting_escrow::contract::query, - )); - - let voting_code_id = router.store_code(voting_contract); - - let msg = AstroVotingEscrowInstantiateMsg { - guardian_addr: Some("guardian".to_string()), - marketing: None, - owner: owner.to_string(), - deposit_token_addr: self.get_staking_xastro(router).to_string(), - logo_urls_whitelist: vec![], - }; - - let voting_instance = router - .instantiate_contract( - voting_code_id, - owner, - &msg, - &[], - String::from("vxASTRO"), - None, - ) - .unwrap(); - - self.voting_escrow = Some(ContractInfo { - address: voting_instance, - code_id: voting_code_id, - }) - } - - pub fn init_escrow_fee_distributor(&mut self, router: &mut App, owner: Addr) { - let escrow_fee_distributor_contract = Box::new(ContractWrapper::new_with_empty( - astroport_escrow_fee_distributor::contract::execute, - astroport_escrow_fee_distributor::contract::instantiate, - astroport_escrow_fee_distributor::contract::query, - )); - - let escrow_fee_distributor_code_id = router.store_code(escrow_fee_distributor_contract); - - let init_msg = EscrowFeeDistributorInstantiateMsg { - owner: owner.to_string(), - astro_token: self.astro_token.clone().unwrap().address.to_string(), - voting_escrow_addr: self.voting_escrow.clone().unwrap().address.to_string(), - claim_many_limit: None, - is_claim_disabled: None, - }; - - let escrow_fee_distributor_instance = router - .instantiate_contract( - escrow_fee_distributor_code_id, - owner, - &init_msg, - &[], - "Astroport escrow fee distributor", - None, - ) - .unwrap(); - - self.escrow_fee_distributor = Some(ContractInfo { - address: escrow_fee_distributor_instance, - code_id: escrow_fee_distributor_code_id, - }) - } - - pub fn create_lock( - &self, - router: &mut App, - user: Addr, - time: u64, - amount: u64, - ) -> Result { - let amount = amount * MULTIPLIER; - let cw20msg = Cw20ExecuteMsg::Send { - contract: self.voting_escrow.clone().unwrap().address.to_string(), - amount: Uint128::from(amount), - msg: to_json_binary(&Cw20HookMsg::CreateLock { time }).unwrap(), - }; - - router.execute_contract(user, self.get_staking_xastro(router), &cw20msg, &[]) - } - - pub fn extend_lock_amount( - &mut self, - router: &mut App, - user: &str, - amount: u64, - ) -> Result { - let amount = amount * MULTIPLIER; - let cw20msg = Cw20ExecuteMsg::Send { - contract: self.voting_escrow.clone().unwrap().address.to_string(), - amount: Uint128::from(amount), - msg: to_json_binary(&Cw20HookMsg::ExtendLockAmount {}).unwrap(), - }; - router.execute_contract( - Addr::unchecked(user), - self.get_staking_xastro(router), - &cw20msg, - &[], - ) - } - - pub fn extend_lock_time( - &mut self, - router: &mut App, - user: &str, - time: u64, - ) -> Result { - router.execute_contract( - Addr::unchecked(user), - self.voting_escrow.clone().unwrap().address, - &ExecuteMsg::ExtendLockTime { time }, - &[], - ) - } - - pub fn withdraw(&self, router: &mut App, user: &str) -> Result { - router.execute_contract( - Addr::unchecked(user), - self.voting_escrow.clone().unwrap().address, - &ExecuteMsg::Withdraw {}, - &[], - ) - } - - pub fn query_user_vp(&self, router: &mut App, user: Addr) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.voting_escrow.clone().unwrap().address, - &QueryMsg::UserVotingPower { - user: user.to_string(), - }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_user_vp_at(&self, router: &mut App, user: Addr, time: u64) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.voting_escrow.clone().unwrap().address, - &QueryMsg::UserVotingPowerAt { - user: user.to_string(), - time, - }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_total_vp(&self, router: &mut App) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.voting_escrow.clone().unwrap().address, - &QueryMsg::TotalVotingPower {}, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_total_vp_at(&self, router: &mut App, time: u64) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.voting_escrow.clone().unwrap().address, - &QueryMsg::TotalVotingPowerAt { time }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } -} - -pub fn mint(router: &mut App, owner: Addr, token_instance: Addr, to: &Addr, amount: u128) { - let amount = amount * MULTIPLIER as u128; - let msg = cw20::Cw20ExecuteMsg::Mint { - recipient: to.to_string(), - amount: Uint128::from(amount), - }; - - let res = router - .execute_contract(owner, token_instance, &msg, &[]) - .unwrap(); - assert_eq!(res.events[1].attributes[1], attr("action", "mint")); - assert_eq!(res.events[1].attributes[2], attr("to", String::from(to))); - assert_eq!( - res.events[1].attributes[3], - attr("amount", Uint128::from(amount)) - ); -} - -pub fn check_balance(app: &mut App, token_addr: &Addr, contract_addr: &Addr, expected: u128) { - let msg = Cw20QueryMsg::Balance { - address: contract_addr.to_string(), - }; - let res: StdResult = app.wrap().query_wasm_smart(token_addr, &msg); - assert_eq!(res.unwrap().balance, Uint128::from(expected)); -} - -pub fn increase_allowance( - router: &mut App, - owner: Addr, - spender: Addr, - token: Addr, - amount: Uint128, -) { - let msg = cw20::Cw20ExecuteMsg::IncreaseAllowance { - spender: spender.to_string(), - amount, - expires: None, - }; - - let res = router - .execute_contract(owner.clone(), token, &msg, &[]) - .unwrap(); - - assert_eq!( - res.events[1].attributes[1], - attr("action", "increase_allowance") - ); - assert_eq!( - res.events[1].attributes[2], - attr("owner", owner.to_string()) - ); - assert_eq!( - res.events[1].attributes[3], - attr("spender", spender.to_string()) - ); - assert_eq!(res.events[1].attributes[4], attr("amount", amount)); -} diff --git a/packages/astroport-tests/src/controller_helper.rs b/packages/astroport-tests/src/controller_helper.rs deleted file mode 100644 index 2878ddfd..00000000 --- a/packages/astroport-tests/src/controller_helper.rs +++ /dev/null @@ -1,365 +0,0 @@ -use crate::escrow_helper::EscrowHelper; -use anyhow::Result as AnyResult; -use astroport::asset::{AssetInfo, PairInfo}; -use astroport::factory::{PairConfig, PairType}; - -use astroport_governance::generator_controller::{ConfigResponse, ExecuteMsg, QueryMsg}; -use cosmwasm_std::{Addr, Decimal, StdResult}; -use cw_multi_test::{App, AppResponse, ContractWrapper, Executor}; -use generator_controller::state::{UserInfo, VotedPoolInfo}; - -pub struct ControllerHelper { - pub owner: String, - pub generator: Addr, - pub controller: Addr, - pub factory: Addr, - pub escrow_helper: EscrowHelper, -} - -impl ControllerHelper { - pub fn init(router: &mut App, owner: &Addr) -> Self { - let escrow_helper = EscrowHelper::init(router, owner.clone()); - - let pair_contract = Box::new( - ContractWrapper::new_with_empty( - astroport_pair::contract::execute, - astroport_pair::contract::instantiate, - astroport_pair::contract::query, - ) - .with_reply_empty(astroport_pair::contract::reply), - ); - - let pair_code_id = router.store_code(pair_contract); - - let factory_contract = Box::new( - ContractWrapper::new_with_empty( - astroport_factory::contract::execute, - astroport_factory::contract::instantiate, - astroport_factory::contract::query, - ) - .with_reply_empty(astroport_factory::contract::reply), - ); - - let factory_code_id = router.store_code(factory_contract); - - let whitelist_code_id = store_whitelist_code(router); - - let msg = astroport::factory::InstantiateMsg { - pair_configs: vec![PairConfig { - code_id: pair_code_id, - pair_type: PairType::Xyk {}, - total_fee_bps: 100, - maker_fee_bps: 10, - is_disabled: false, - is_generator_disabled: false, - permissioned: false, - }], - token_code_id: escrow_helper.astro_token_code_id, - fee_address: None, - generator_address: None, - owner: owner.to_string(), - whitelist_code_id, - coin_registry_address: Addr::unchecked("coin_registry").to_string(), - }; - - let factory = router - .instantiate_contract(factory_code_id, owner.clone(), &msg, &[], "Factory", None) - .unwrap(); - - let generator_contract = Box::new( - ContractWrapper::new_with_empty( - astroport_generator::contract::execute, - astroport_generator::contract::instantiate, - astroport_generator::contract::query, - ) - .with_reply_empty(astroport_generator::contract::reply), - ); - - let generator_code_id = router.store_code(generator_contract); - let init_msg = astroport::generator::InstantiateMsg { - owner: owner.to_string(), - factory: factory.to_string(), - generator_controller: None, - guardian: None, - astro_token: AssetInfo::NativeToken { - denom: escrow_helper.astro_token.to_string(), - }, - tokens_per_block: Default::default(), - start_block: Default::default(), - vesting_contract: "vesting_placeholder".to_string(), - whitelist_code_id, - voting_escrow: None, - voting_escrow_delegation: None, - }; - - let generator = router - .instantiate_contract( - generator_code_id, - owner.clone(), - &init_msg, - &[], - String::from("Generator"), - None, - ) - .unwrap(); - - let controller_contract = Box::new(ContractWrapper::new_with_empty( - generator_controller::contract::execute, - generator_controller::contract::instantiate, - generator_controller::contract::query, - )); - - let controller_code_id = router.store_code(controller_contract); - let init_msg = astroport_governance::generator_controller::InstantiateMsg { - owner: owner.to_string(), - escrow_addr: escrow_helper.escrow_instance.to_string(), - generator_addr: generator.to_string(), - factory_addr: factory.to_string(), - pools_limit: 5, - whitelisted_pools: vec![], - }; - - let controller = router - .instantiate_contract( - controller_code_id, - owner.clone(), - &init_msg, - &[], - String::from("Controller"), - None, - ) - .unwrap(); - - // Setup controller in generator contract - router - .execute_contract( - owner.clone(), - generator.clone(), - &astroport::generator::ExecuteMsg::UpdateConfig { - vesting_contract: None, - generator_controller: Some(controller.to_string()), - guardian: None, - checkpoint_generator_limit: None, - voting_escrow: None, - voting_escrow_delegation: None, - }, - &[], - ) - .unwrap(); - - Self { - owner: owner.to_string(), - generator, - controller, - factory, - escrow_helper, - } - } - - pub fn init_cw20_token(&self, router: &mut App, name: &str) -> AnyResult { - let msg = astroport::token::InstantiateMsg { - name: name.to_string(), - symbol: name.to_string(), - decimals: 6, - initial_balances: vec![], - mint: None, - marketing: None, - }; - - router.instantiate_contract( - self.escrow_helper.astro_token_code_id, - Addr::unchecked(self.owner.clone()), - &msg, - &[], - name.to_string(), - None, - ) - } - - pub fn create_pool(&self, router: &mut App, token1: &Addr, token2: &Addr) -> AnyResult { - let asset_infos = vec![ - AssetInfo::Token { - contract_addr: token1.clone(), - }, - AssetInfo::Token { - contract_addr: token2.clone(), - }, - ]; - - router.execute_contract( - Addr::unchecked(self.owner.clone()), - self.factory.clone(), - &astroport::factory::ExecuteMsg::CreatePair { - pair_type: PairType::Xyk {}, - asset_infos: asset_infos.to_vec(), - init_params: None, - }, - &[], - )?; - - let res: PairInfo = router.wrap().query_wasm_smart( - self.factory.clone(), - &astroport::factory::QueryMsg::Pair { - asset_infos: asset_infos.to_vec(), - }, - )?; - - Ok(res.liquidity_token) - } - - pub fn create_pool_with_tokens( - &self, - router: &mut App, - name1: &str, - name2: &str, - ) -> AnyResult { - let token1 = self.init_cw20_token(router, name1).unwrap(); - let token2 = self.init_cw20_token(router, name2).unwrap(); - - self.create_pool(router, &token1, &token2) - } - - pub fn vote( - &self, - router: &mut App, - user: &str, - votes: Vec<(impl Into, u16)>, - ) -> AnyResult { - let msg = ExecuteMsg::Vote { - votes: votes - .into_iter() - .map(|(pool, apoints)| (pool.into(), apoints)) - .collect(), - }; - - router.execute_contract(Addr::unchecked(user), self.controller.clone(), &msg, &[]) - } - - pub fn tune(&self, router: &mut App) -> AnyResult { - router.execute_contract( - Addr::unchecked("anyone"), - self.controller.clone(), - &ExecuteMsg::TunePools {}, - &[], - ) - } - - pub fn kick_holders( - &self, - router: &mut App, - user: &str, - blacklisted_voters: Vec, - ) -> AnyResult { - router.execute_contract( - Addr::unchecked(user), - self.controller.clone(), - &ExecuteMsg::KickBlacklistedVoters { blacklisted_voters }, - &[], - ) - } - - pub fn update_blacklisted_limit( - &self, - router: &mut App, - user: &str, - blacklisted_voters_limit: Option, - ) -> AnyResult { - router.execute_contract( - Addr::unchecked(user), - self.controller.clone(), - &ExecuteMsg::UpdateConfig { - blacklisted_voters_limit, - main_pool: None, - main_pool_min_alloc: None, - remove_main_pool: None, - }, - &[], - ) - } - - pub fn update_main_pool( - &self, - router: &mut App, - user: &str, - main_pool: Option<&Addr>, - main_pool_min_alloc: Option, - remove_main_pool: bool, - ) -> AnyResult { - let remove_main_pool = if remove_main_pool { Some(true) } else { None }; - router.execute_contract( - Addr::unchecked(user), - self.controller.clone(), - &ExecuteMsg::UpdateConfig { - blacklisted_voters_limit: None, - main_pool: main_pool.map(|p| p.to_string()), - main_pool_min_alloc, - remove_main_pool, - }, - &[], - ) - } - - pub fn update_whitelist( - &self, - router: &mut App, - user: &str, - add_pools: Option>, - remove_pools: Option>, - ) -> AnyResult { - let msg = ExecuteMsg::UpdateWhitelist { - add: add_pools, - remove: remove_pools, - }; - - router.execute_contract(Addr::unchecked(user), self.controller.clone(), &msg, &[]) - } - - pub fn query_user_info(&self, router: &mut App, user: &str) -> StdResult { - router.wrap().query_wasm_smart( - self.controller.clone(), - &QueryMsg::UserInfo { - user: user.to_string(), - }, - ) - } - - pub fn query_voted_pool_info(&self, router: &mut App, pool: &str) -> StdResult { - router.wrap().query_wasm_smart( - self.controller.clone(), - &QueryMsg::PoolInfo { - pool_addr: pool.to_string(), - }, - ) - } - - pub fn query_voted_pool_info_at_period( - &self, - router: &mut App, - pool: &str, - period: u64, - ) -> StdResult { - router.wrap().query_wasm_smart( - self.controller.clone(), - &QueryMsg::PoolInfoAtPeriod { - pool_addr: pool.to_string(), - period, - }, - ) - } - - pub fn query_config(&self, router: &mut App) -> StdResult { - router - .wrap() - .query_wasm_smart(self.controller.clone(), &QueryMsg::Config {}) - } -} - -fn store_whitelist_code(app: &mut App) -> u64 { - let whitelist_contract = Box::new(ContractWrapper::new_with_empty( - astroport_whitelist::contract::execute, - astroport_whitelist::contract::instantiate, - astroport_whitelist::contract::query, - )); - - app.store_code(whitelist_contract) -} diff --git a/packages/astroport-tests/src/escrow_helper.rs b/packages/astroport-tests/src/escrow_helper.rs deleted file mode 100644 index 36d38d90..00000000 --- a/packages/astroport-tests/src/escrow_helper.rs +++ /dev/null @@ -1,345 +0,0 @@ -use anyhow::Result; -use astroport::{staking as xastro, token as astro}; -use astroport_governance::voting_escrow::{ - Cw20HookMsg, ExecuteMsg, InstantiateMsg, LockInfoResponse, QueryMsg, VotingPowerResponse, -}; -use cosmwasm_std::{attr, to_json_binary, Addr, QueryRequest, StdResult, Uint128, WasmQuery}; -use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg, MinterResponse}; -use cw_multi_test::{App, AppResponse, ContractWrapper, Executor}; - -pub const MULTIPLIER: u64 = 1000000; - -pub struct EscrowHelper { - pub owner: Addr, - pub astro_token: Addr, - pub staking_instance: Addr, - pub xastro_token: Addr, - pub escrow_instance: Addr, - pub astro_token_code_id: u64, -} - -impl EscrowHelper { - pub fn init(router: &mut App, owner: Addr) -> Self { - let astro_token_contract = Box::new(ContractWrapper::new_with_empty( - astroport_token::contract::execute, - astroport_token::contract::instantiate, - astroport_token::contract::query, - )); - - let astro_token_code_id = router.store_code(astro_token_contract); - - let msg = astro::InstantiateMsg { - name: String::from("Astro token"), - symbol: String::from("ASTRO"), - decimals: 6, - initial_balances: vec![], - mint: Some(MinterResponse { - minter: owner.to_string(), - cap: None, - }), - marketing: None, - }; - - let astro_token = router - .instantiate_contract( - astro_token_code_id, - owner.clone(), - &msg, - &[], - String::from("ASTRO"), - None, - ) - .unwrap(); - - let staking_contract = Box::new( - ContractWrapper::new_with_empty( - astroport_staking::contract::execute, - astroport_staking::contract::instantiate, - astroport_staking::contract::query, - ) - .with_reply_empty(astroport_staking::contract::reply), - ); - - let staking_code_id = router.store_code(staking_contract); - - let msg = xastro::InstantiateMsg { - owner: owner.to_string(), - token_code_id: astro_token_code_id, - deposit_token_addr: astro_token.to_string(), - marketing: None, - }; - let staking_instance = router - .instantiate_contract( - staking_code_id, - owner.clone(), - &msg, - &[], - String::from("xASTRO"), - None, - ) - .unwrap(); - - let res = router - .wrap() - .query::(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: staking_instance.to_string(), - msg: to_json_binary(&xastro::QueryMsg::Config {}).unwrap(), - })) - .unwrap(); - - let voting_contract = Box::new(ContractWrapper::new_with_empty( - voting_escrow::contract::execute, - voting_escrow::contract::instantiate, - voting_escrow::contract::query, - )); - - let voting_code_id = router.store_code(voting_contract); - - let msg = InstantiateMsg { - owner: owner.to_string(), - guardian_addr: Some("guardian".to_string()), - deposit_token_addr: res.share_token_addr.to_string(), - marketing: None, - logo_urls_whitelist: vec![], - }; - let voting_instance = router - .instantiate_contract( - voting_code_id, - owner.clone(), - &msg, - &[], - String::from("vxASTRO"), - None, - ) - .unwrap(); - - Self { - owner, - xastro_token: res.share_token_addr, - astro_token, - staking_instance, - escrow_instance: voting_instance, - astro_token_code_id, - } - } - - pub fn mint_xastro(&self, router: &mut App, to: &str, amount: u64) { - let amount = amount * MULTIPLIER; - let msg = Cw20ExecuteMsg::Mint { - recipient: String::from(to), - amount: Uint128::from(amount), - }; - let res = router - .execute_contract(self.owner.clone(), self.astro_token.clone(), &msg, &[]) - .unwrap(); - assert_eq!(res.events[1].attributes[1], attr("action", "mint")); - assert_eq!(res.events[1].attributes[2], attr("to", String::from(to))); - assert_eq!( - res.events[1].attributes[3], - attr("amount", Uint128::from(amount)) - ); - - let to_addr = Addr::unchecked(to); - let msg = Cw20ExecuteMsg::Send { - contract: self.staking_instance.to_string(), - msg: to_json_binary(&xastro::Cw20HookMsg::Enter {}).unwrap(), - amount: Uint128::from(amount), - }; - router - .execute_contract(to_addr, self.astro_token.clone(), &msg, &[]) - .unwrap(); - } - - pub fn check_xastro_balance(&self, router: &mut App, user: &str, amount: u64) { - let amount = amount * MULTIPLIER; - let res: BalanceResponse = router - .wrap() - .query_wasm_smart( - self.xastro_token.clone(), - &Cw20QueryMsg::Balance { - address: user.to_string(), - }, - ) - .unwrap(); - assert_eq!(res.balance.u128(), amount as u128); - } - - pub fn create_lock( - &self, - router: &mut App, - user: &str, - time: u64, - amount: f32, - ) -> Result { - let amount = (amount * MULTIPLIER as f32) as u64; - let cw20msg = Cw20ExecuteMsg::Send { - contract: self.escrow_instance.to_string(), - amount: Uint128::from(amount), - msg: to_json_binary(&Cw20HookMsg::CreateLock { time }).unwrap(), - }; - router.execute_contract( - Addr::unchecked(user), - self.xastro_token.clone(), - &cw20msg, - &[], - ) - } - - pub fn extend_lock_amount( - &self, - router: &mut App, - user: &str, - amount: f32, - ) -> Result { - let amount = (amount * MULTIPLIER as f32) as u64; - let cw20msg = Cw20ExecuteMsg::Send { - contract: self.escrow_instance.to_string(), - amount: Uint128::from(amount), - msg: to_json_binary(&Cw20HookMsg::ExtendLockAmount {}).unwrap(), - }; - router.execute_contract( - Addr::unchecked(user), - self.xastro_token.clone(), - &cw20msg, - &[], - ) - } - - pub fn deposit_for( - &self, - router: &mut App, - from: &str, - to: &str, - amount: f32, - ) -> Result { - let amount = (amount * MULTIPLIER as f32) as u64; - let cw20msg = Cw20ExecuteMsg::Send { - contract: self.escrow_instance.to_string(), - amount: Uint128::from(amount), - msg: to_json_binary(&Cw20HookMsg::DepositFor { - user: to.to_string(), - }) - .unwrap(), - }; - router.execute_contract( - Addr::unchecked(from), - self.xastro_token.clone(), - &cw20msg, - &[], - ) - } - - pub fn extend_lock_time(&self, router: &mut App, user: &str, time: u64) -> Result { - router.execute_contract( - Addr::unchecked(user), - self.escrow_instance.clone(), - &ExecuteMsg::ExtendLockTime { time }, - &[], - ) - } - - pub fn withdraw(&self, router: &mut App, user: &str) -> Result { - router.execute_contract( - Addr::unchecked(user), - self.escrow_instance.clone(), - &ExecuteMsg::Withdraw {}, - &[], - ) - } - - pub fn update_blacklist( - &self, - router: &mut App, - append_addrs: Option>, - remove_addrs: Option>, - ) -> Result { - router.execute_contract( - Addr::unchecked("owner"), - self.escrow_instance.clone(), - &ExecuteMsg::UpdateBlacklist { - append_addrs, - remove_addrs, - }, - &[], - ) - } - - pub fn query_user_vp(&self, router: &mut App, user: &str) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.escrow_instance.clone(), - &QueryMsg::UserVotingPower { - user: user.to_string(), - }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_user_vp_at(&self, router: &mut App, user: &str, time: u64) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.escrow_instance.clone(), - &QueryMsg::UserVotingPowerAt { - user: user.to_string(), - time, - }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_user_vp_at_period( - &self, - router: &mut App, - user: &str, - period: u64, - ) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.escrow_instance.clone(), - &QueryMsg::UserVotingPowerAtPeriod { - user: user.to_string(), - period, - }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_total_vp(&self, router: &mut App) -> StdResult { - router - .wrap() - .query_wasm_smart(self.escrow_instance.clone(), &QueryMsg::TotalVotingPower {}) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_total_vp_at(&self, router: &mut App, time: u64) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.escrow_instance.clone(), - &QueryMsg::TotalVotingPowerAt { time }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_total_vp_at_period(&self, router: &mut App, period: u64) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.escrow_instance.clone(), - &QueryMsg::TotalVotingPowerAtPeriod { period }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_lock_info(&self, router: &mut App, user: &str) -> StdResult { - router.wrap().query_wasm_smart( - self.escrow_instance.clone(), - &QueryMsg::LockInfo { - user: user.to_string(), - }, - ) - } -} diff --git a/packages/astroport-tests/src/lib.rs b/packages/astroport-tests/src/lib.rs deleted file mode 100644 index 49b2fead..00000000 --- a/packages/astroport-tests/src/lib.rs +++ /dev/null @@ -1,46 +0,0 @@ -pub mod base; -use astroport_governance::utils::{get_period, EPOCH_START}; -use cosmwasm_std::testing::{mock_env, MockApi, MockStorage}; -use cosmwasm_std::Timestamp; -use cw_multi_test::{App, BankKeeper, BasicAppBuilder}; - -#[allow(clippy::all)] -#[allow(dead_code)] -pub mod controller_helper; - -#[allow(clippy::all)] -#[allow(dead_code)] -pub mod escrow_helper; - -pub fn mock_app() -> App { - let mut env = mock_env(); - env.block.time = Timestamp::from_seconds(EPOCH_START); - let api = MockApi::default(); - let bank = BankKeeper::new(); - let storage = MockStorage::new(); - - BasicAppBuilder::new() - .with_api(api) - .with_block(env.block) - .with_bank(bank) - .with_storage(storage) - .build(|_, _, _| {}) -} - -pub trait TerraAppExtension { - fn next_block(&mut self, time: u64); - fn block_period(&self) -> u64; -} - -impl TerraAppExtension for App { - fn next_block(&mut self, time: u64) { - self.update_block(|block| { - block.time = block.time.plus_seconds(time); - block.height += 1 - }); - } - - fn block_period(&self) -> u64 { - get_period(self.block_info().time.seconds()).unwrap() - } -} diff --git a/scripts/coverage.sh b/scripts/coverage.sh index 9b031d04..0a3a43da 100755 --- a/scripts/coverage.sh +++ b/scripts/coverage.sh @@ -1,7 +1,7 @@ -#!/usr/src/env bash +#!/usr/bin/env bash # Usage: ./scripts/coverage.sh # Example: ./scripts/coverage.sh astroport-pair -cargo tarpaulin --target-dir target/tarpaulin_build --skip-clean --exclude-files *tests*.rs --exclude-files target*.rs \ - -p "$1" --out Html +cargo tarpaulin --target-dir target/tarpaulin_build --skip-clean --exclude-files *tests*.rs \ + --exclude-files *examples*.rs --exclude-files target*.rs -p "$1" --out Html From 0c21af19bd42f513a22dd2f508b31b036591a471 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Tue, 14 May 2024 18:17:58 +0400 Subject: [PATCH 05/32] remove needless marketing validation; increase coverage --- contracts/voting_escrow/src/contract.rs | 70 ++++-------------- contracts/voting_escrow/src/error.rs | 3 - contracts/voting_escrow/src/lib.rs | 1 - .../voting_escrow/src/marketing_validation.rs | 73 ------------------- contracts/voting_escrow/src/state.rs | 10 +-- contracts/voting_escrow/tests/helper.rs | 4 +- .../tests/voting_escrow_integration.rs | 50 ++++++++++++- .../astroport-governance/src/voting_escrow.rs | 13 +--- 8 files changed, 71 insertions(+), 153 deletions(-) delete mode 100644 contracts/voting_escrow/src/marketing_validation.rs diff --git a/contracts/voting_escrow/src/contract.rs b/contracts/voting_escrow/src/contract.rs index 6a6016fb..7eeac809 100644 --- a/contracts/voting_escrow/src/contract.rs +++ b/contracts/voting_escrow/src/contract.rs @@ -2,14 +2,12 @@ use astroport::asset::{addr_opt_validate, validate_native_denom}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - attr, coins, ensure, to_json_binary, BankMsg, Binary, Deps, DepsMut, Env, MessageInfo, - Response, StdError, StdResult, Uint128, + attr, coins, to_json_binary, BankMsg, Binary, Deps, DepsMut, Env, MessageInfo, Response, + StdError, StdResult, Uint128, }; use cw2::set_contract_version; use cw20::{BalanceResponse, Logo, LogoInfo, MarketingInfoResponse, TokenInfoResponse}; -use cw20_base::contract::{ - execute_update_marketing, execute_upload_logo, query_download_logo, query_marketing_info, -}; +use cw20_base::contract::{execute_update_marketing, query_marketing_info}; use cw20_base::state::{MinterData, TokenInfo, LOGO, MARKETING_INFO, TOKEN_INFO}; use cw_utils::must_pay; @@ -18,7 +16,6 @@ use astroport_governance::voting_escrow::{ }; use crate::error::ContractError; -use crate::marketing_validation::{validate_marketing_info, validate_whitelist_links}; use crate::state::{get_total_vp, Lock, CONFIG}; /// Contract name that is used for migration. @@ -37,35 +34,21 @@ pub fn instantiate( set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; validate_native_denom(&msg.deposit_denom)?; - validate_whitelist_links(&msg.logo_urls_whitelist)?; let config = Config { deposit_denom: msg.deposit_denom.clone(), - logo_urls_whitelist: msg.logo_urls_whitelist.clone(), }; CONFIG.save(deps.storage, &config)?; if let Some(marketing) = msg.marketing { - if msg.logo_urls_whitelist.is_empty() { - return Err(StdError::generic_err("Logo URLs whitelist can not be empty").into()); - } - - validate_marketing_info( - marketing.project.as_ref(), - marketing.description.as_ref(), - marketing.logo.as_ref(), - &config.logo_urls_whitelist, - )?; - - let logo = if let Some(logo) = marketing.logo { - LOGO.save(deps.storage, &logo)?; - - match logo { - Logo::Url(url) => Some(LogoInfo::Url(url)), - Logo::Embedded(_) => Some(LogoInfo::Embedded), + let logo = match &marketing.logo { + Some(Logo::Url(url)) => { + LOGO.save(deps.storage, &marketing.logo.clone().unwrap())?; + Some(LogoInfo::Url(url.clone())) + } + _ => { + return Err(StdError::generic_err("Logo url must be set").into()); } - } else { - None }; let data = MarketingInfoResponse { @@ -75,6 +58,8 @@ pub fn instantiate( logo, }; MARKETING_INFO.save(deps.storage, &data)?; + } else { + return Err(StdError::generic_err("Marketing info is required").into()); } // Store token info @@ -159,7 +144,7 @@ pub fn execute( let send_msg = BankMsg::Send { to_address: info.sender.to_string(), - amount: coins(amount.u128(), &config.deposit_denom), + amount: coins(amount.u128(), config.deposit_denom), }; Ok(Response::new().add_message(send_msg).add_attributes([ @@ -172,32 +157,8 @@ pub fn execute( project, description, marketing, - } => { - validate_marketing_info(project.as_ref(), description.as_ref(), None, &[])?; - execute_update_marketing(deps, env, info, project, description, marketing) - .map_err(Into::into) - } - ExecuteMsg::UploadLogo(logo) => { - let config = CONFIG.load(deps.storage)?; - validate_marketing_info(None, None, Some(&logo), &config.logo_urls_whitelist)?; - execute_upload_logo(deps, env, info, logo).map_err(Into::into) - } - ExecuteMsg::SetLogoUrlsWhitelist { whitelist } => { - let marketing_info = MARKETING_INFO.load(deps.storage)?; - - ensure!( - Some(info.sender) == marketing_info.marketing, - ContractError::Unauthorized {} - ); - - CONFIG.update::<_, ContractError>(deps.storage, |mut config| { - validate_whitelist_links(&whitelist)?; - config.logo_urls_whitelist = whitelist; - Ok(config) - })?; - - Ok(Response::default().add_attribute("action", "set_logo_urls_whitelist")) - } + } => execute_update_marketing(deps, env, info, project, description, marketing) + .map_err(Into::into), } } @@ -235,7 +196,6 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { } QueryMsg::TokenInfo {} => to_json_binary(&query_token_info(deps, env)?), QueryMsg::MarketingInfo {} => to_json_binary(&query_marketing_info(deps)?), - QueryMsg::DownloadLogo {} => to_json_binary(&query_download_logo(deps)?), } } diff --git a/contracts/voting_escrow/src/error.rs b/contracts/voting_escrow/src/error.rs index f66e4acf..a9a29c3b 100644 --- a/contracts/voting_escrow/src/error.rs +++ b/contracts/voting_escrow/src/error.rs @@ -21,9 +21,6 @@ pub enum ContractError { #[error("Unauthorized")] Unauthorized {}, - #[error("Marketing info validation error: {0}")] - MarketingInfoValidationError(String), - #[error("No withdrawal balance available")] ZeroBalance {}, diff --git a/contracts/voting_escrow/src/lib.rs b/contracts/voting_escrow/src/lib.rs index 6e591cfd..4fd6c769 100644 --- a/contracts/voting_escrow/src/lib.rs +++ b/contracts/voting_escrow/src/lib.rs @@ -2,4 +2,3 @@ pub mod contract; pub mod state; pub mod error; -pub mod marketing_validation; diff --git a/contracts/voting_escrow/src/marketing_validation.rs b/contracts/voting_escrow/src/marketing_validation.rs deleted file mode 100644 index c9acf73c..00000000 --- a/contracts/voting_escrow/src/marketing_validation.rs +++ /dev/null @@ -1,73 +0,0 @@ -use cosmwasm_std::ensure; -use cw20::Logo; - -use crate::error::ContractError; - -const SAFE_TEXT_CHARS: &str = "!&?#()*+'-.,/\""; -const SAFE_LINK_CHARS: &str = "-_:/?#@!$&()*+,;=.~[]'%"; - -pub fn validate_text(text: &str, name: &str) -> Result<(), ContractError> { - if text.chars().any(|c| { - !c.is_ascii_alphanumeric() && !c.is_ascii_whitespace() && !SAFE_TEXT_CHARS.contains(c) - }) { - Err(ContractError::MarketingInfoValidationError(format!( - "{name} contains invalid characters: {text}" - ))) - } else { - Ok(()) - } -} - -pub fn validate_whitelist_links(links: &[String]) -> Result<(), ContractError> { - links.iter().try_for_each(validate_link) -} - -pub fn validate_link(link: &String) -> Result<(), ContractError> { - ensure!( - link.ends_with('/'), - ContractError::MarketingInfoValidationError(format!( - "Whitelist link should end with '/': {link}" - )) - ); - - ensure!( - link.chars() - .all(|c| c.is_ascii_alphanumeric() || SAFE_LINK_CHARS.contains(c)), - ContractError::MarketingInfoValidationError(format!( - "Link contains invalid characters: {link}" - )) - ); - - Ok(()) -} - -pub fn check_link(link: &String, whitelisted_links: &[String]) -> Result<(), ContractError> { - validate_link(link)?; - - if !whitelisted_links.iter().any(|wl| link.starts_with(wl)) { - Err(ContractError::MarketingInfoValidationError(format!( - "Logo link is not whitelisted: {link}" - ))) - } else { - Ok(()) - } -} - -pub fn validate_marketing_info( - project: Option<&String>, - description: Option<&String>, - logo: Option<&Logo>, - whitelisted_links: &[String], -) -> Result<(), ContractError> { - if let Some(description) = description { - validate_text(description, "description")?; - } - if let Some(project) = project { - validate_text(project, "project")?; - } - if let Some(Logo::Url(url)) = logo { - check_link(url, whitelisted_links)?; - } - - Ok(()) -} diff --git a/contracts/voting_escrow/src/state.rs b/contracts/voting_escrow/src/state.rs index 6203da44..f4ca3e11 100644 --- a/contracts/voting_escrow/src/state.rs +++ b/contracts/voting_escrow/src/state.rs @@ -49,8 +49,8 @@ impl Lock { timestamp: Option, ) -> StdResult { let lock = match timestamp.unwrap_or(block_time) { - timestamp if timestamp == block_time => LOCKED.may_load(storage, &user), - timestamp => LOCKED.may_load_at_height(storage, &user, timestamp), + timestamp if timestamp == block_time => LOCKED.may_load(storage, user), + timestamp => LOCKED.may_load_at_height(storage, user, timestamp), }? .unwrap_or_default(); @@ -73,7 +73,7 @@ impl Lock { ensure!(self.end.is_none(), ContractError::PositionUnlocking {}); self.amount += amount; - LOCKED.save(storage, &self.user, &self, self.block_time)?; + LOCKED.save(storage, &self.user, self, self.block_time)?; TOTAL_POWER .update(storage, self.block_time, |total| { Ok(total.unwrap_or_default() + amount) @@ -87,7 +87,7 @@ impl Lock { let end = self.block_time + UNLOCK_PERIOD; self.end = Some(end); - LOCKED.save(storage, &self.user, &self, self.block_time)?; + LOCKED.save(storage, &self.user, self, self.block_time)?; // Remove user's voting power from the total TOTAL_POWER.update(storage, self.block_time, |total| -> StdResult<_> { @@ -101,7 +101,7 @@ impl Lock { ensure!(self.end.is_some(), ContractError::NotInUnlockingState {}); self.end = None; - LOCKED.save(storage, &self.user, &self, self.block_time)?; + LOCKED.save(storage, &self.user, self, self.block_time)?; // Add user's voting power back to the total TOTAL_POWER diff --git a/contracts/voting_escrow/tests/helper.rs b/contracts/voting_escrow/tests/helper.rs index 54120313..808a0e14 100644 --- a/contracts/voting_escrow/tests/helper.rs +++ b/contracts/voting_escrow/tests/helper.rs @@ -1,4 +1,5 @@ use cosmwasm_std::{Addr, Coin, Empty, StdResult, Uint128}; +use cw20::Logo; use cw_multi_test::error::AnyResult; use cw_multi_test::{AppResponse, BankSudo, BasicApp, Contract, ContractWrapper, Executor}; @@ -37,9 +38,8 @@ impl EscrowHelper { project: None, description: None, marketing: Some(owner.to_string()), - logo: None, + logo: Some(Logo::Url("https://example.com".to_string())), }), - logo_urls_whitelist: vec!["https://astroport.fi/".to_string()], }, &[], "label", diff --git a/contracts/voting_escrow/tests/voting_escrow_integration.rs b/contracts/voting_escrow/tests/voting_escrow_integration.rs index 98b4a85f..8e6ac4eb 100644 --- a/contracts/voting_escrow/tests/voting_escrow_integration.rs +++ b/contracts/voting_escrow/tests/voting_escrow_integration.rs @@ -1,5 +1,6 @@ use cosmwasm_std::{coin, Addr}; -use cw20::{BalanceResponse, MarketingInfoResponse, TokenInfoResponse}; +use cw20::{BalanceResponse, LogoInfo, MarketingInfoResponse, TokenInfoResponse}; +use cw_multi_test::Executor; use cw_utils::PaymentError; use astroport_governance::voting_escrow::{Config, LockInfoResponse, QueryMsg}; @@ -59,6 +60,10 @@ fn test_lock() { .user_vp(&user1, Some(helper.app.block_info().time.seconds() - 1)) .unwrap(); assert_eq!(0, user_vp.u128()); + let total_vp = helper + .total_vp(Some(helper.app.block_info().time.seconds() - 1)) + .unwrap(); + assert_eq!(0, total_vp.u128()); helper.timetravel(10000); @@ -204,7 +209,6 @@ fn test_general_queries() { config, Config { deposit_denom: xastro_denom.to_string(), - logo_urls_whitelist: vec!["https://astroport.fi/".to_string()] } ); @@ -244,4 +248,46 @@ fn test_general_queries() { .query_wasm_smart(&helper.vxastro_contract, &QueryMsg::MarketingInfo {}) .unwrap(); assert_eq!(marketing_info.marketing, Some(helper.owner.clone())); + + // Update marketing + let new_marketing = Addr::unchecked("new_marketing"); + let update_msg = astroport_governance::voting_escrow::ExecuteMsg::UpdateMarketing { + project: Some("new_project".to_string()), + description: Some("new_description".to_string()), + marketing: Some(new_marketing.to_string()), + }; + let err = helper + .app + .execute_contract( + Addr::unchecked("random"), + helper.vxastro_contract.clone(), + &update_msg, + &[], + ) + .unwrap_err(); + assert_eq!( + ContractError::Cw20Base(cw20_base::ContractError::Unauthorized {}), + err.downcast().unwrap(), + ); + + let owner = helper.owner.clone(); + helper + .app + .execute_contract(owner, helper.vxastro_contract.clone(), &update_msg, &[]) + .unwrap(); + + let marketing_info: MarketingInfoResponse = helper + .app + .wrap() + .query_wasm_smart(&helper.vxastro_contract, &QueryMsg::MarketingInfo {}) + .unwrap(); + assert_eq!( + marketing_info, + MarketingInfoResponse { + project: Some("new_project".to_string()), + description: Some("new_description".to_string()), + logo: Some(LogoInfo::Url("https://example.com".to_string())), + marketing: Some(new_marketing), + } + ); } diff --git a/packages/astroport-governance/src/voting_escrow.rs b/packages/astroport-governance/src/voting_escrow.rs index f304c8b0..5a99e7c6 100644 --- a/packages/astroport-governance/src/voting_escrow.rs +++ b/packages/astroport-governance/src/voting_escrow.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::Uint128; -use cw20::{BalanceResponse, DownloadLogoResponse, Logo, MarketingInfoResponse, TokenInfoResponse}; +use cw20::{BalanceResponse, Logo, MarketingInfoResponse, TokenInfoResponse}; /// This structure stores marketing information for vxASTRO. #[cw_serde] @@ -22,8 +22,6 @@ pub struct InstantiateMsg { pub deposit_denom: String, /// Marketing info for vxASTRO pub marketing: Option, - /// The list of whitelisted logo urls prefixes - pub logo_urls_whitelist: Vec, } /// This structure describes the execute functions in the contract. @@ -46,10 +44,6 @@ pub enum ExecuteMsg { /// The address (if any) that can update this data structure marketing: Option, }, - /// Upload a logo for vxASTRO - UploadLogo(Logo), - /// Set whitelisted logo urls - SetLogoUrlsWhitelist { whitelist: Vec }, } /// This structure describes the query messages available in the contract. @@ -65,9 +59,6 @@ pub enum QueryMsg { /// Fetch vxASTRO's marketing information #[returns(MarketingInfoResponse)] MarketingInfo {}, - /// Download the vxASTRO logo - #[returns(DownloadLogoResponse)] - DownloadLogo {}, /// Return the current total amount of vxASTRO #[returns(Uint128)] TotalVotingPower { time: Option }, @@ -87,8 +78,6 @@ pub enum QueryMsg { pub struct Config { /// The xASTRO denom pub deposit_denom: String, - /// The list of whitelisted logo urls prefixes - pub logo_urls_whitelist: Vec, } #[cw_serde] From d1f43793f2d75754dab362bff6ee72e97826e2aa Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Tue, 14 May 2024 18:22:44 +0400 Subject: [PATCH 06/32] remove vxastro_lite --- Cargo.toml | 5 +- .../assembly/tests/assembly_integration.rs | 1 - contracts/voting_escrow_lite/.cargo/config | 6 - contracts/voting_escrow_lite/Cargo.toml | 42 - contracts/voting_escrow_lite/README.md | 348 ------ .../voting_escrow_lite/examples/schema.rs | 11 - contracts/voting_escrow_lite/src/contract.rs | 107 -- contracts/voting_escrow_lite/src/error.rs | 47 - contracts/voting_escrow_lite/src/execute.rs | 590 ---------- contracts/voting_escrow_lite/src/lib.rs | 12 - .../src/marketing_validation.rs | 75 -- contracts/voting_escrow_lite/src/query.rs | 273 ----- contracts/voting_escrow_lite/src/state.rs | 35 - contracts/voting_escrow_lite/src/utils.rs | 34 - .../voting_escrow_lite/tests/simulation.todo | 394 ------- .../voting_escrow_lite/tests/test_utils.rs | 440 ------- .../tests/vxastro_lite_integration.rs | 1026 ----------------- packages/astroport-governance/src/lib.rs | 1 - .../src/voting_escrow_lite.rs | 333 ------ packages/astroport-tests-lite/Cargo.toml | 34 - .../src/address_generator.rs | 19 - packages/astroport-tests-lite/src/base.rs | 359 ------ .../src/controller_helper.rs | 494 -------- .../astroport-tests-lite/src/escrow_helper.rs | 397 ------- packages/astroport-tests-lite/src/lib.rs | 54 - 25 files changed, 1 insertion(+), 5136 deletions(-) delete mode 100644 contracts/voting_escrow_lite/.cargo/config delete mode 100644 contracts/voting_escrow_lite/Cargo.toml delete mode 100644 contracts/voting_escrow_lite/README.md delete mode 100644 contracts/voting_escrow_lite/examples/schema.rs delete mode 100644 contracts/voting_escrow_lite/src/contract.rs delete mode 100644 contracts/voting_escrow_lite/src/error.rs delete mode 100644 contracts/voting_escrow_lite/src/execute.rs delete mode 100644 contracts/voting_escrow_lite/src/lib.rs delete mode 100644 contracts/voting_escrow_lite/src/marketing_validation.rs delete mode 100644 contracts/voting_escrow_lite/src/query.rs delete mode 100644 contracts/voting_escrow_lite/src/state.rs delete mode 100644 contracts/voting_escrow_lite/src/utils.rs delete mode 100644 contracts/voting_escrow_lite/tests/simulation.todo delete mode 100644 contracts/voting_escrow_lite/tests/test_utils.rs delete mode 100644 contracts/voting_escrow_lite/tests/vxastro_lite_integration.rs delete mode 100644 packages/astroport-governance/src/voting_escrow_lite.rs delete mode 100644 packages/astroport-tests-lite/Cargo.toml delete mode 100644 packages/astroport-tests-lite/src/address_generator.rs delete mode 100644 packages/astroport-tests-lite/src/base.rs delete mode 100644 packages/astroport-tests-lite/src/controller_helper.rs delete mode 100644 packages/astroport-tests-lite/src/escrow_helper.rs delete mode 100644 packages/astroport-tests-lite/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index a5794493..0e3819e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,13 @@ [workspace] resolver = "2" members = [ - "packages/astroport-governance", - # "packages/astroport-tests", - # "packages/astroport-tests-lite", + "packages/*", "contracts/assembly", "contracts/voting_escrow", "contracts/builder_unlock", # "contracts/generator_controller_lite", # "contracts/hub", # "contracts/outpost", - # "contracts/voting_escrow_lite", ] [workspace.dependencies] diff --git a/contracts/assembly/tests/assembly_integration.rs b/contracts/assembly/tests/assembly_integration.rs index 324638ca..6c05df5b 100644 --- a/contracts/assembly/tests/assembly_integration.rs +++ b/contracts/assembly/tests/assembly_integration.rs @@ -935,7 +935,6 @@ fn test_execute_multisig() { .unwrap(); let messages: Vec<_> = (0..5) - .into_iter() .map(|_| wasm_execute(&noop_addr, &Empty {}, vec![]).unwrap().into()) .collect(); diff --git a/contracts/voting_escrow_lite/.cargo/config b/contracts/voting_escrow_lite/.cargo/config deleted file mode 100644 index 8d4bc738..00000000 --- a/contracts/voting_escrow_lite/.cargo/config +++ /dev/null @@ -1,6 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib" -integration-test = "test --test integration" -schema = "run --example schema" diff --git a/contracts/voting_escrow_lite/Cargo.toml b/contracts/voting_escrow_lite/Cargo.toml deleted file mode 100644 index 8e2e7447..00000000 --- a/contracts/voting_escrow_lite/Cargo.toml +++ /dev/null @@ -1,42 +0,0 @@ -[package] -name = "astroport-voting-escrow-lite" -version = "1.0.0" -authors = ["Astroport"] -edition = "2021" -repository = "https://github.com/astroport-fi/astroport-governance" -homepage = "https://astroport.fi" - -exclude = [ - # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. - "contract.wasm", - "hash.txt", -] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] -library = [] - -[dependencies] -cw2 = "1.1" -cw20 = "1.1" -cw20-base = { version = "1.1", features = ["library"] } -cw-utils = "1" -cosmwasm-std = "1.5" -cw-storage-plus = "0.15" -thiserror = "1" -astroport-governance = { path = "../../packages/astroport-governance" } -cosmwasm-schema = "1.5" - -[dev-dependencies] -cw-multi-test = "0.20" -astroport-generator-controller = { path = "../../contracts/generator_controller_lite", package = "generator-controller-lite" } -astroport = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/neutron-migration" } -anyhow = "1" -proptest = "1.0" diff --git a/contracts/voting_escrow_lite/README.md b/contracts/voting_escrow_lite/README.md deleted file mode 100644 index 51a6ff4c..00000000 --- a/contracts/voting_escrow_lite/README.md +++ /dev/null @@ -1,348 +0,0 @@ -# Vote Escrowed Staked ASTRO Lite - -The vxASTRO lite contract allows xASTRO token holders to lock their tokens in order to gain emissions voting power that -is used in voting which pools should be receiving ASTRO emissions. - -The xASTRO is lock forever, or until a holder decides to unlock the position. Unlocking is subject to a 2 week (default) -waiting period until withdrawal is allowed. Once an unlocking period starts, the holder will lose the emissions voting power -immediately. - -## InstantiateMsg - -Initialize the contract with the initial owner and the address of the xASTRO token. - -```json -{ - "owner": "terra...", - "deposit_token_addr": "terra..." -} -``` - -## ExecuteMsg - -### `receive` - -Create new lock/vxASTRO position, deposit more xASTRO in the user's vxASTRO position or deposit on behalf of another address. - -```json -{ - "receive": { - "sender": "terra...", - "amount": "123", - "msg": "" - } -} -``` - -### `unlock` - -Unlock the whole position in vxASTRO, subject to a waiting period until `withdraw` is possible - -```json -{ - "unlock": {} -} -``` - -### `withdraw` - -Withdraw the whole amount of xASTRO if the lock for a vxASTRO position has been unlocked and the waiting period has passed. - -```json -{ - "withdraw": {} -} -``` - -### `propose_new_owner` - -Create a request to change contract ownership. The validity period of the offer is set by the `expires_in` variable. -Only the current contract owner can execute this method. - -```json -{ - "propose_new_owner": { - "owner": "terra...", - "expires_in": 1234567 - } -} -``` - -### `drop_ownership_proposal` - -Delete the contract ownership transfer proposal. Only the current contract owner can execute this method. - -```json -{ - "drop_ownership_proposal": {} -} -``` - -### `claim_ownership` - -Used to claim contract ownership. Only the newly proposed contract owner can execute this method. - -```json -{ - "claim_ownership": {} -} -``` - -### `update_blacklist` - -Updates the list of addresses that are prohibited from staking in vxASTRO or if they are already staked, from voting with their vxASTRO in the Astral Assembly. Only the contract owner can execute this method. - -```json -{ - "append_addrs": ["terra...", "terra...", "terra..."], - "remove_addrs": ["terra...", "terra..."] -} -``` - -### `update_config` - -Updates contract parameters. - -```json -{ - "new_guardian": "terra..." -} -``` - -## QueryMsg - -All query messages are described below. A custom struct is defined for each query response. - -### `total_voting_power` - -Returns the total supply of vxASTRO at the current block, for this version, will always return 0. - -```json -{ - "voting_power_response": { - "voting_power": 0 - } -} -``` - -### `user_voting_power` - -Returns a user's vxASTRO balance at the current block, for this version, will always return 0. - -Request: - -```json -{ - "user_voting_power": { - "user": "terra..." - } -} -``` - -Response: - -```json -{ - "voting_power_response": { - "voting_power": 0 - } -} -``` - -### `total_voting_power_at` - -Returns the total vxASTRO supply at a specific timestamp (in seconds), for this version, will always return 0. - -Request: - -```json -{ - "total_voting_power_at": { - "time": 1234567 - } -} -``` - -Response: - -```json -{ - "voting_power_response": { - "voting_power": 0 - } -} -``` - -### `user_voting_power_at` - -Returns the user's vxASTRO balance at a specific timestamp (in seconds), for this version, will always return 0. - -Request: - -```json -{ - "user_voting_power_at": { - "user": "terra...", - "time": 1234567 - } -} -``` - -Response: - -```json -{ - "voting_power_response": { - "voting_power": 0 - } -} -``` - -### `total_emissions_voting_power` - -Returns the total emissions voting power of vxASTRO at the current block. - -```json -{ - "voting_power_response": { - "voting_power": 0 - } -} -``` - -### `user_emissions_voting_power` - -Returns a user's emissions voting power at the current block. - -Request: - -```json -{ - "user_emissions_voting_power": { - "user": "terra..." - } -} -``` - -Response: - -```json -{ - "voting_power_response": { - "voting_power": 0 - } -} -``` - -### `total_emissions_voting_power_at` - -Returns the total emissions voting power at a specific timestamp (in seconds), for this version, will always return 0. - -Request: - -```json -{ - "total_emissions_voting_power_at": { - "time": 1234567 - } -} -``` - -Response: - -```json -{ - "voting_power_response": { - "voting_power": 0 - } -} -``` - -### `user_emissions_voting_power_at` - -Returns a user's emissions voting power at a specific timestamp (in seconds), for this version, will always return 0. - -Request: - -```json -{ - "user_emissions_voting_power_at": { - "user": "terra...", - "time": 1234567 - } -} -``` - -Response: - -```json -{ - "voting_power_response": { - "voting_power": 0 - } -} -``` -### `lock_info` - -Returns the information about a user's vxASTRO position. - -Request: - -```json -{ - "lock_info": { - "user": "terra..." - } -} -``` - -Response: - -```json -{ - "lock_info_response": { - "amount": 10, - "coefficient": 2.5, - "start": 2600, - "end": 2704 - } -} -``` - -### `config` - -Returns the contract's config. - -```json -{ - "config_response": { - "owner": "terra...", - "deposit_token_addr" : "terra..." - } -} -``` - -### `blacklisted_voters` - -Returns blacklisted voters. - -```json -{ - "blacklisted_voters": { - "start_after": "terra...", - "limit": 5 - } -} -``` - -### `check_voters_are_blacklisted` - -Checks if specified addresses are blacklisted - -```json -{ - "check_voters_are_blacklisted": { - "voters": ["terra...", "terra..."] - } -} -``` \ No newline at end of file diff --git a/contracts/voting_escrow_lite/examples/schema.rs b/contracts/voting_escrow_lite/examples/schema.rs deleted file mode 100644 index 3d98f61a..00000000 --- a/contracts/voting_escrow_lite/examples/schema.rs +++ /dev/null @@ -1,11 +0,0 @@ -use astroport_governance::voting_escrow_lite::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; -use cosmwasm_schema::write_api; - -fn main() { - write_api! { - instantiate: InstantiateMsg, - query: QueryMsg, - execute: ExecuteMsg, - migrate: MigrateMsg, - } -} diff --git a/contracts/voting_escrow_lite/src/contract.rs b/contracts/voting_escrow_lite/src/contract.rs deleted file mode 100644 index 6c59863b..00000000 --- a/contracts/voting_escrow_lite/src/contract.rs +++ /dev/null @@ -1,107 +0,0 @@ -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{DepsMut, Env, MessageInfo, Response, StdError, Uint128}; -use cw2::set_contract_version; -use cw20::{Logo, LogoInfo, MarketingInfoResponse}; -use cw20_base::state::{TokenInfo, LOGO, MARKETING_INFO, TOKEN_INFO}; - -use astroport_governance::utils::DEFAULT_UNLOCK_PERIOD; -use astroport_governance::voting_escrow_lite::{Config, InstantiateMsg}; - -use crate::astroport::asset::{addr_opt_validate, validate_native_denom}; -use crate::error::ContractError; -use crate::marketing_validation::{validate_marketing_info, validate_whitelist_links}; -use crate::state::{BLACKLIST, CONFIG, VOTING_POWER_HISTORY}; - -/// Contract name that is used for migration. -const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); -/// Contract version that is used for migration. -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -/// Creates a new contract with the specified parameters in [`InstantiateMsg`]. -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> Result { - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - validate_native_denom(&msg.deposit_denom)?; - - validate_whitelist_links(&msg.logo_urls_whitelist)?; - let guardian_addr = addr_opt_validate(deps.api, &msg.guardian_addr)?; - - // We only allow either generator controller *or* the outpost to be set - // If we're on the Hub generator controller should be set - // If we're on an outpost, then outpost should be set - if msg.generator_controller_addr.is_some() && msg.outpost_addr.is_some() { - return Err(StdError::generic_err( - "Only one of Generator Controller or Outpost can be set", - ) - .into()); - } - - let config = Config { - owner: deps.api.addr_validate(&msg.owner)?, - guardian_addr, - deposit_denom: msg.deposit_denom, - logo_urls_whitelist: msg.logo_urls_whitelist.clone(), - unlock_period: DEFAULT_UNLOCK_PERIOD, - generator_controller_addr: addr_opt_validate(deps.api, &msg.generator_controller_addr)?, - outpost_addr: addr_opt_validate(deps.api, &msg.outpost_addr)?, - }; - CONFIG.save(deps.storage, &config)?; - - VOTING_POWER_HISTORY.save( - deps.storage, - (env.contract.address, env.block.time.seconds()), - &Uint128::zero(), - )?; - BLACKLIST.save(deps.storage, &vec![])?; - - if let Some(marketing) = msg.marketing { - if msg.logo_urls_whitelist.is_empty() { - return Err(StdError::generic_err("Logo URLs whitelist can not be empty").into()); - } - - validate_marketing_info( - marketing.project.as_ref(), - marketing.description.as_ref(), - marketing.logo.as_ref(), - &config.logo_urls_whitelist, - )?; - - let logo = if let Some(logo) = marketing.logo { - LOGO.save(deps.storage, &logo)?; - - match logo { - Logo::Url(url) => Some(LogoInfo::Url(url)), - Logo::Embedded(_) => Some(LogoInfo::Embedded), - } - } else { - None - }; - - let data = MarketingInfoResponse { - project: marketing.project, - description: marketing.description, - marketing: addr_opt_validate(deps.api, &marketing.marketing)?, - logo, - }; - MARKETING_INFO.save(deps.storage, &data)?; - } - - // Store token info - let data = TokenInfo { - name: "Vote Escrowed xASTRO lite".to_string(), - symbol: "vxASTRO".to_string(), - decimals: 6, - total_supply: Uint128::zero(), - mint: None, - }; - - TOKEN_INFO.save(deps.storage, &data)?; - - Ok(Response::default()) -} diff --git a/contracts/voting_escrow_lite/src/error.rs b/contracts/voting_escrow_lite/src/error.rs deleted file mode 100644 index 55baa4c4..00000000 --- a/contracts/voting_escrow_lite/src/error.rs +++ /dev/null @@ -1,47 +0,0 @@ -use cosmwasm_std::{OverflowError, StdError}; -use cw20_base::ContractError as cw20baseError; -use cw_utils::PaymentError; -use thiserror::Error; - -/// This enum describes vxASTRO contract errors -#[derive(Error, Debug)] -pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("{0}")] - Cw20Base(#[from] cw20baseError), - - #[error("{0}")] - OverflowError(#[from] OverflowError), - - #[error("{0}")] - PaymentError(#[from] PaymentError), - - #[error("Unauthorized")] - Unauthorized {}, - - #[error("Lock already exists, either unlock and withdraw or extend_lock to add to the lock")] - LockAlreadyExists {}, - - #[error("Lock does not exist")] - LockDoesNotExist {}, - - #[error("The lock time has not yet expired")] - LockHasNotExpired {}, - - #[error("The lock expired. Withdraw and create new lock")] - LockExpired {}, - - #[error("The {0} address is blacklisted")] - AddressBlacklisted(String), - - #[error("Marketing info validation error: {0}")] - MarketingInfoValidationError(String), - - #[error("Already unlocking")] - Unlocking {}, - - #[error("The lock has not been unlocked, call unlock first")] - NotUnlocked {}, -} diff --git a/contracts/voting_escrow_lite/src/execute.rs b/contracts/voting_escrow_lite/src/execute.rs deleted file mode 100644 index 78c84f3b..00000000 --- a/contracts/voting_escrow_lite/src/execute.rs +++ /dev/null @@ -1,590 +0,0 @@ -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{ - attr, coins, to_json_binary, Addr, BankMsg, CosmosMsg, DepsMut, Env, MessageInfo, Response, - StdError, StdResult, Storage, Uint128, WasmMsg, -}; -use cw20_base::contract::{execute_update_marketing, execute_upload_logo}; -use cw20_base::state::MARKETING_INFO; -use cw_utils::must_pay; - -use astroport_governance::voting_escrow_lite::{Config, ExecuteMsg}; -use astroport_governance::{generator_controller_lite, outpost}; - -use crate::astroport::common::{ - claim_ownership, drop_ownership_proposal, propose_new_owner, validate_addresses, -}; -use crate::error::ContractError; -use crate::marketing_validation::{validate_marketing_info, validate_whitelist_links}; -use crate::state::{Lock, BLACKLIST, CONFIG, LOCKED, OWNERSHIP_PROPOSAL, VOTING_POWER_HISTORY}; -use crate::utils::{blacklist_check, fetch_last_checkpoint}; - -/// Exposes all the execute functions available in the contract. -/// -/// ## Execute messages -/// * **ExecuteMsg::Unlock {}** Unlock all xASTRO from a lock position, subject to a waiting period until withdrawal is possible. -/// -/// * **ExecuteMsg::Relock {}** Relock all xASTRO from an unlocking position if the Hub could not be notified -/// -/// * **ExecuteMsg::Withdraw {}** Withdraw all xASTRO from an lock position if the unlock time has expired. -/// -/// * **ExecuteMsg::ProposeNewOwner { owner, expires_in }** Creates a new request to change contract ownership. -/// -/// * **ExecuteMsg::DropOwnershipProposal {}** Removes a request to change contract ownership. -/// -/// * **ExecuteMsg::ClaimOwnership {}** Claims contract ownership. -/// -/// * **ExecuteMsg::UpdateBlacklist { append_addrs, remove_addrs }** Updates the contract's blacklist. -/// -/// * **ExecuteMsg::UpdateMarketing { project, description, marketing }** Updates the contract's marketing information. -/// -/// * **ExecuteMsg::UploadLogo { logo }** Uploads a new logo to the contract. -/// -/// * **ExecuteMsg::SetLogoUrlsWhitelist { whitelist }** Sets the contract's logo whitelist. -/// -/// * **ExecuteMsg::UpdateConfig { new_guardian }** Updates the contract's guardian. -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - match msg { - ExecuteMsg::CreateLock {} => { - blacklist_check(deps.storage, &info.sender)?; - - let config = CONFIG.load(deps.storage)?; - let amount = must_pay(&info, &config.deposit_denom)?; - - create_lock(deps, env, info.sender, amount) - } - ExecuteMsg::DepositFor { user } => { - blacklist_check(deps.storage, &info.sender)?; - - let addr = deps.api.addr_validate(&user)?; - blacklist_check(deps.storage, &addr)?; - - let config = CONFIG.load(deps.storage)?; - let amount = must_pay(&info, &config.deposit_denom)?; - - deposit_for(deps, env, amount, addr) - } - ExecuteMsg::ExtendLockAmount {} => { - blacklist_check(deps.storage, &info.sender)?; - - let config = CONFIG.load(deps.storage)?; - let amount = must_pay(&info, &config.deposit_denom)?; - - deposit_for(deps, env, amount, info.sender) - } - ExecuteMsg::Unlock {} => unlock(deps, env, info), - ExecuteMsg::Relock { user } => relock(deps, env, info, user), - ExecuteMsg::Withdraw {} => withdraw(deps, env, info), - ExecuteMsg::ProposeNewOwner { - new_owner, - expires_in, - } => { - let config = CONFIG.load(deps.storage)?; - propose_new_owner( - deps, - info, - env, - new_owner, - expires_in, - config.owner, - OWNERSHIP_PROPOSAL, - ) - .map_err(Into::into) - } - ExecuteMsg::DropOwnershipProposal {} => { - let config: Config = CONFIG.load(deps.storage)?; - - drop_ownership_proposal(deps, info, config.owner, OWNERSHIP_PROPOSAL) - .map_err(Into::into) - } - ExecuteMsg::ClaimOwnership {} => { - claim_ownership(deps, info, env, OWNERSHIP_PROPOSAL, |deps, new_owner| { - CONFIG - .update::<_, StdError>(deps.storage, |mut v| { - v.owner = new_owner; - Ok(v) - }) - .map(|_| ()) - }) - .map_err(Into::into) - } - ExecuteMsg::UpdateBlacklist { - append_addrs, - remove_addrs, - } => update_blacklist(deps, env, info, append_addrs, remove_addrs), - ExecuteMsg::UpdateMarketing { - project, - description, - marketing, - } => { - validate_marketing_info(project.as_ref(), description.as_ref(), None, &[])?; - execute_update_marketing(deps, env, info, project, description, marketing) - .map_err(Into::into) - } - ExecuteMsg::UploadLogo(logo) => { - let config = CONFIG.load(deps.storage)?; - validate_marketing_info(None, None, Some(&logo), &config.logo_urls_whitelist)?; - execute_upload_logo(deps, env, info, logo).map_err(Into::into) - } - ExecuteMsg::SetLogoUrlsWhitelist { whitelist } => { - let mut config = CONFIG.load(deps.storage)?; - let marketing_info = MARKETING_INFO.load(deps.storage)?; - if info.sender != config.owner && Some(info.sender) != marketing_info.marketing { - Err(ContractError::Unauthorized {}) - } else { - validate_whitelist_links(&whitelist)?; - config.logo_urls_whitelist = whitelist; - CONFIG.save(deps.storage, &config)?; - Ok(Response::default().add_attribute("action", "set_logo_urls_whitelist")) - } - } - ExecuteMsg::UpdateConfig { - new_guardian, - generator_controller, - outpost, - } => execute_update_config(deps, info, new_guardian, generator_controller, outpost), - } -} - -/// Creates a lock for the user that lasts until Unlock is called -/// Creates a lock if it doesn't exist and triggers a [`checkpoint`] for the staker. -/// If a lock already exists, then a [`ContractError`] is returned. -/// -/// * **user** staker for which we create a lock position. -/// -/// * **amount** amount of xASTRO deposited in the lock position. -fn create_lock( - deps: DepsMut, - env: Env, - user: Addr, - amount: Uint128, -) -> Result { - LOCKED.update( - deps.storage, - user.clone(), - env.block.time.seconds(), - |lock_opt| { - if lock_opt.is_some() && !lock_opt.unwrap().amount.is_zero() { - return Err(ContractError::LockAlreadyExists {}); - } - Ok(Lock { amount, end: None }) - }, - )?; - checkpoint(deps, env, user, Some(amount))?; - - Ok(Response::default().add_attribute("action", "create_lock")) -} - -/// Deposits an 'amount' of xASTRO tokens into 'user''s lock. -/// Triggers a [`checkpoint`] for the user. -/// If the user does not have a lock, then a lock is created. -/// -/// * **amount** amount of xASTRO to deposit. -/// -/// * **user** user who's lock amount will increase. -fn deposit_for( - deps: DepsMut, - env: Env, - amount: Uint128, - user: Addr, -) -> Result { - LOCKED.update( - deps.storage, - user.clone(), - env.block.time.seconds(), - |lock_opt| { - match lock_opt { - Some(mut lock) if !lock.amount.is_zero() => match lock.end { - // This lock is still locked - None => { - lock.amount += amount; - Ok(lock) - } - // This lock is expired or being unlocked, thus reject the deposit - Some(end) => { - if end <= env.block.time.seconds() { - return Err(ContractError::LockExpired {}); - } - Err(ContractError::Unlocking {}) - } - }, - // If no lock exists, create a new one - _ => Ok(Lock { amount, end: None }), - } - }, - )?; - checkpoint(deps, env, user, Some(amount))?; - - Ok(Response::default().add_attribute("action", "deposit_for")) -} - -/// Starts the unlock of the whole amount of locked xASTRO from a specific user lock. -/// If the user lock doesn't exist or if it has been unlocked, then a [`ContractError`] is returned. -/// -/// Note: When a user unlocks, they lose their emission voting power immediately -fn unlock(deps: DepsMut, env: Env, info: MessageInfo) -> Result { - let sender = info.sender; - - // 'LockDoesNotExist' is thrown either when a lock does not exist in LOCKED or when a lock exists but lock.amount == 0 - let lock = LOCKED - .may_load(deps.storage, sender.clone())? - .filter(|lock| !lock.amount.is_zero()) - .ok_or(ContractError::LockDoesNotExist {})?; - - match lock.end { - // This lock is still locked, we can unlock - None => { - let config = CONFIG.load(deps.storage)?; - let response = Response::default().add_attribute("action", "unlock_initiated"); - - // Start the unlock for this address - start_unlock(lock, deps, env, sender.clone())?; - - // We only allow either the generator controller _or_ the Outpost to be set at any time - let kick_msg = match (&config.generator_controller_addr, &config.outpost_addr) { - (Some(generator_controller), None) => { - // On the Hub we kick the user from the Generator Controller directly - // Voting power is removed immediately after a user unlocks - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: generator_controller.to_string(), - msg: to_json_binary( - &generator_controller_lite::ExecuteMsg::KickUnlockedVoters { - unlocked_voters: vec![sender.to_string()], - }, - )?, - funds: vec![], - }) - } - (None, Some(outpost)) => { - // If this vxASTRO contract is deployed on an Outpost we need to - // forward the unlock to the Hub, if the notification fails - // the funds will be locked again - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: outpost.to_string(), - msg: to_json_binary(&outpost::ExecuteMsg::KickUnlocked { user: sender })?, - funds: vec![], - }) - } - _ => { - return Err(StdError::generic_err( - "Either Generator Controller or Outpost must be set", - ) - .into()); - } - }; - - Ok(response.add_message(kick_msg)) - } - // This lock is expired or being unlocked, can't unlock again - Some(end) => { - if end <= env.block.time.seconds() { - return Err(ContractError::LockExpired {}); - } - Err(ContractError::Unlocking {}) - } - } -} - -/// Locks the given user's xASTRO lock again if the Hub could not be notified -/// -/// When a user unlocks, the Hub needs to be notified so that the user's votes -/// can be kicked from the Generator Controller. If the notification to the Hub -/// fails, then the position must be locked again -/// If the user lock doesn't exist or if it has been completely unlocked, -/// then a [`ContractError`] is returned. -fn relock( - deps: DepsMut, - env: Env, - info: MessageInfo, - user: String, -) -> Result { - let config = CONFIG.load(deps.storage)?; - - // Check that the caller is the Outpost contract - if Some(info.sender) != config.outpost_addr { - return Err(ContractError::Unauthorized {}); - } - - let sender = Addr::unchecked(user); - // 'LockDoesNotExist' is thrown either when a lock does not exist in LOCKED or when a lock exists but lock.amount == 0 - let mut lock = LOCKED - .may_load(deps.storage, sender.clone())? - .filter(|lock| !lock.amount.is_zero()) - .ok_or(ContractError::LockDoesNotExist {})?; - - // If the lock has been unlocked - if lock.end.is_some() { - lock.end = None; - LOCKED.save( - deps.storage, - sender.clone(), - &lock, - env.block.time.seconds(), - )?; - // Relock needs to add back the user's voting power - VOTING_POWER_HISTORY.save( - deps.storage, - (sender.clone(), env.block.time.seconds()), - &lock.amount, - )?; - checkpoint_total(deps.storage, env, Some(lock.amount), None)?; - } - - Ok(Response::new() - .add_attribute("action", "relock") - .add_attribute("user", sender)) -} - -/// Withdraws the whole amount of locked xASTRO from a specific user lock. -/// If the user lock doesn't exist or if it has not yet expired, then a [`ContractError`] is returned. -fn withdraw(deps: DepsMut, env: Env, info: MessageInfo) -> Result { - let sender = info.sender; - // 'LockDoesNotExist' is thrown either when a lock does not exist in LOCKED or when a lock exists but lock.amount == 0 - let mut lock = LOCKED - .may_load(deps.storage, sender.clone())? - .filter(|lock| !lock.amount.is_zero()) - .ok_or(ContractError::LockDoesNotExist {})?; - - match lock.end { - // This lock is still locked, withdrawal not possible - None => Err(ContractError::NotUnlocked {}), - // This lock is expired or being unlocked - Some(end) => { - // Still unlocking, can't withdraw - if end > env.block.time.seconds() { - return Err(ContractError::LockHasNotExpired {}); - } - // Unlocked, withdrawal is now allowed - let config = CONFIG.load(deps.storage)?; - - let transfer_msg = BankMsg::Send { - to_address: sender.to_string(), - amount: coins(lock.amount.u128(), &config.deposit_denom), - }; - lock.amount = Uint128::zero(); - LOCKED.save(deps.storage, sender, &lock, env.block.time.seconds())?; - - Ok(Response::default() - .add_message(transfer_msg) - .add_attribute("action", "withdraw")) - } - } -} - -/// Update the staker blacklist. Whitelists addresses specified in 'remove_addrs' -/// and blacklists new addresses specified in 'append_addrs'. Nullifies staker voting power and -/// cancels their contribution in the total voting power (total vxASTRO supply). -/// -/// * **append_addrs** array of addresses to blacklist. -/// -/// * **remove_addrs** array of addresses to whitelist. -fn update_blacklist( - mut deps: DepsMut, - env: Env, - info: MessageInfo, - append_addrs: Vec, - remove_addrs: Vec, -) -> Result { - if append_addrs.is_empty() && remove_addrs.is_empty() { - return Err(StdError::generic_err("Append and remove arrays are empty").into()); - } - - let config = CONFIG.load(deps.storage)?; - // Permission check - if info.sender != config.owner && Some(info.sender) != config.guardian_addr { - return Err(ContractError::Unauthorized {}); - } - let blacklist = BLACKLIST.load(deps.storage)?; - let append: Vec<_> = validate_addresses(deps.api, &append_addrs)? - .into_iter() - .filter(|addr| !blacklist.contains(addr)) - .collect(); - let remove: Vec<_> = validate_addresses(deps.api, &remove_addrs)? - .into_iter() - .filter(|addr| blacklist.contains(addr)) - .collect(); - - let timestamp = env.block.time.seconds(); - let mut reduce_total_vp = Uint128::zero(); // accumulator for decreasing total voting power - - for addr in append.iter() { - let last_checkpoint = fetch_last_checkpoint(deps.storage, addr, timestamp)?; - if let Some((_, emissions_power)) = last_checkpoint { - // We need to checkpoint with zero power and zero slope - VOTING_POWER_HISTORY.save(deps.storage, (addr.clone(), timestamp), &Uint128::zero())?; - - let cur_power = emissions_power; - // User's contribution is already zero. Skipping them - if cur_power.is_zero() { - continue; - } - - // User's contribution in the total voting power calculation - reduce_total_vp += cur_power; - } - } - - if !reduce_total_vp.is_zero() { - // Trigger a total voting power recalculation - checkpoint_total(deps.storage, env.clone(), None, Some(reduce_total_vp))?; - } - - for addr in remove.iter() { - let lock_opt = LOCKED.may_load(deps.storage, addr.clone())?; - if let Some(Lock { amount, end, .. }) = lock_opt { - match end { - // Only checkpoint the amount if the lock if still active - None => checkpoint(deps.branch(), env.clone(), addr.clone(), Some(amount))?, - // This lock is expired or being unlocked and has already been set to zero - Some(_) => checkpoint(deps.branch(), env.clone(), addr.clone(), None)?, - } - } - } - - BLACKLIST.update(deps.storage, |blacklist| -> StdResult> { - let mut updated_blacklist: Vec<_> = blacklist - .into_iter() - .filter(|addr| !remove.contains(addr)) - .collect(); - updated_blacklist.extend(append); - Ok(updated_blacklist) - })?; - - let mut attrs = vec![attr("action", "update_blacklist")]; - if !append_addrs.is_empty() { - attrs.push(attr("added_addresses", append_addrs.join(","))) - } - if !remove_addrs.is_empty() { - attrs.push(attr("removed_addresses", remove_addrs.join(","))) - } - - // TODO: Submit update blacklist immediately - - Ok(Response::default().add_attributes(attrs)) -} - -/// Updates contracts' guardian address. -fn execute_update_config( - deps: DepsMut, - info: MessageInfo, - new_guardian: Option, - generator_controller: Option, - outpost: Option, -) -> Result { - let mut config = CONFIG.load(deps.storage)?; - - if config.owner != info.sender { - return Err(ContractError::Unauthorized {}); - } - - if let Some(new_guardian) = new_guardian { - config.guardian_addr = Some(deps.api.addr_validate(&new_guardian)?); - } - - if let Some(generator_controller) = generator_controller { - if config.outpost_addr.is_some() { - return Err(StdError::generic_err( - "Only one of Generator Controller or Outpost can be set", - ) - .into()); - } - config.generator_controller_addr = Some(deps.api.addr_validate(&generator_controller)?); - } - - if let Some(outpost) = outpost { - if config.generator_controller_addr.is_some() { - return Err(StdError::generic_err( - "Only one of Generator Controller or Outpost can be set", - ) - .into()); - } - config.outpost_addr = Some(deps.api.addr_validate(&outpost)?); - } - - CONFIG.save(deps.storage, &config)?; - - Ok(Response::default().add_attribute("action", "execute_update_config")) -} - -/// Start the unlock of a user's Lock -/// -/// The unlocking time is based on the current block time + configured unlock period -fn start_unlock(mut lock: Lock, deps: DepsMut, env: Env, sender: Addr) -> StdResult<()> { - let config = CONFIG.load(deps.storage)?; - let unlock_time = env.block.time.seconds() + config.unlock_period; - lock.end = Some(unlock_time); - LOCKED.save( - deps.storage, - sender.clone(), - &lock, - env.block.time.seconds(), - )?; - // Update user's voting power - VOTING_POWER_HISTORY.save( - deps.storage, - (sender, env.block.time.seconds()), - &Uint128::zero(), - )?; - // Update total voting power - checkpoint_total(deps.storage, env, None, Some(lock.amount)) -} - -/// Checkpoint a user's voting power (vxASTRO balance). -/// This function fetches the user's last available checkpoint, calculates the user's current voting power -/// and saves the new checkpoint for the current period in [`HISTORY`] (using the user's address). -/// If a user already checkpointed themselves for the current period, then this function uses the current checkpoint as the latest -/// available one. -/// -/// * **addr** staker for which we checkpoint the voting power. -/// -/// * **add_amount** amount of vxASTRO to add to the staker's balance. -fn checkpoint(deps: DepsMut, env: Env, addr: Addr, add_amount: Option) -> StdResult<()> { - let timestamp = env.block.time.seconds(); - let add_amount = add_amount.unwrap_or_default(); - - // Get the last user checkpoint - let last_checkpoint = fetch_last_checkpoint(deps.storage, &addr, timestamp)?; - let new_power = if let Some((_, emissions_power)) = last_checkpoint { - emissions_power.checked_add(add_amount)? - } else { - add_amount - }; - - VOTING_POWER_HISTORY.save(deps.storage, (addr, timestamp), &new_power)?; - checkpoint_total(deps.storage, env, Some(add_amount), None) -} - -/// Checkpoint the total voting power (total supply of vxASTRO). -/// This function fetches the last available vxASTRO checkpoint -/// saves all recalculated periods in [`HISTORY`]. -/// -/// * **add_voting_power** amount of vxASTRO to add to the total. -/// -/// * **reduce_power** amount of vxASTRO to subtract from the total. -fn checkpoint_total( - storage: &mut dyn Storage, - env: Env, - add_voting_power: Option, - reduce_power: Option, -) -> StdResult<()> { - let timestamp = env.block.time.seconds(); - let contract_addr = env.contract.address; - let add_voting_power = add_voting_power.unwrap_or_default(); - - // Get last checkpoint - let last_checkpoint = fetch_last_checkpoint(storage, &contract_addr, timestamp)?; - let new_point = if let Some((_, emissions_power)) = last_checkpoint { - let mut new_power = emissions_power.saturating_add(add_voting_power); - new_power = new_power.saturating_sub(reduce_power.unwrap_or_default()); - new_power - } else { - add_voting_power - }; - VOTING_POWER_HISTORY.save(storage, (contract_addr, timestamp), &new_point) -} diff --git a/contracts/voting_escrow_lite/src/lib.rs b/contracts/voting_escrow_lite/src/lib.rs deleted file mode 100644 index 183e6145..00000000 --- a/contracts/voting_escrow_lite/src/lib.rs +++ /dev/null @@ -1,12 +0,0 @@ -pub mod contract; -pub mod state; - -// During development this import could be replaced with another astroport version. -// However, in production, the astroport version should be the same for all contracts. -pub use astroport_governance::astroport; - -pub mod error; -pub mod execute; -mod marketing_validation; -pub mod query; -mod utils; diff --git a/contracts/voting_escrow_lite/src/marketing_validation.rs b/contracts/voting_escrow_lite/src/marketing_validation.rs deleted file mode 100644 index aea35004..00000000 --- a/contracts/voting_escrow_lite/src/marketing_validation.rs +++ /dev/null @@ -1,75 +0,0 @@ -use crate::error::ContractError; -use crate::error::ContractError::MarketingInfoValidationError; - -use cosmwasm_std::StdError; -use cw20::Logo; - -const SAFE_TEXT_CHARS: &str = "!&?#()*+'-.,/\""; -const SAFE_LINK_CHARS: &str = "-_:/?#@!$&()*+,;=.~[]'%"; - -fn validate_text(text: &str, name: &str) -> Result<(), ContractError> { - if text.chars().any(|c| { - !c.is_ascii_alphanumeric() && !c.is_ascii_whitespace() && !SAFE_TEXT_CHARS.contains(c) - }) { - Err(MarketingInfoValidationError(format!( - "{name} contains invalid characters: {text}" - ))) - } else { - Ok(()) - } -} - -pub fn validate_whitelist_links(links: &[String]) -> Result<(), ContractError> { - links.iter().try_for_each(|link| { - if !link.ends_with('/') { - return Err(MarketingInfoValidationError(format!( - "Whitelist link should end with '/': {link}" - ))); - } - validate_link(link) - }) -} - -pub fn validate_link(link: &String) -> Result<(), ContractError> { - if link - .chars() - .any(|c| !c.is_ascii_alphanumeric() && !SAFE_LINK_CHARS.contains(c)) - { - Err(StdError::generic_err(format!("Link contains invalid characters: {link}")).into()) - } else { - Ok(()) - } -} - -fn check_link(link: &String, whitelisted_links: &[String]) -> Result<(), ContractError> { - if validate_link(link).is_err() { - Err(MarketingInfoValidationError(format!( - "Logo link is invalid: {link}" - ))) - } else if !whitelisted_links.iter().any(|wl| link.starts_with(wl)) { - Err(MarketingInfoValidationError(format!( - "Logo link is not whitelisted: {link}" - ))) - } else { - Ok(()) - } -} - -pub(crate) fn validate_marketing_info( - project: Option<&String>, - description: Option<&String>, - logo: Option<&Logo>, - whitelisted_links: &[String], -) -> Result<(), ContractError> { - if let Some(description) = description { - validate_text(description, "description")?; - } - if let Some(project) = project { - validate_text(project, "project")?; - } - if let Some(Logo::Url(url)) = logo { - check_link(url, whitelisted_links)?; - } - - Ok(()) -} diff --git a/contracts/voting_escrow_lite/src/query.rs b/contracts/voting_escrow_lite/src/query.rs deleted file mode 100644 index 1a386a47..00000000 --- a/contracts/voting_escrow_lite/src/query.rs +++ /dev/null @@ -1,273 +0,0 @@ -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{to_json_binary, Addr, Binary, Deps, Env, StdError, StdResult, Uint128, Uint64}; - -use cw20::{BalanceResponse, TokenInfoResponse}; -use cw20_base::contract::{query_download_logo, query_marketing_info}; -use cw20_base::state::TOKEN_INFO; - -use astroport_governance::voting_escrow_lite::{ - BlacklistedVotersResponse, LockInfoResponse, QueryMsg, VotingPowerResponse, DEFAULT_LIMIT, - MAX_LIMIT, -}; - -use crate::state::{BLACKLIST, CONFIG, LOCKED}; -use crate::utils::fetch_last_checkpoint; - -/// Expose available contract queries. -/// -/// ## Queries -/// * **QueryMsg::CheckVotersAreBlacklisted { voters }** Check if the provided voters are blacklisted. -/// -/// * **QueryMsg::BlacklistedVoters { start_after, limit }** Fetch all blacklisted voters. -/// -/// * **QueryMsg::TotalVotingPower {}** Fetch the total voting power (vxASTRO supply) at the current block. Always returns 0 in this version. -/// -/// * **QueryMsg::TotalVotingPowerAt { .. }** Fetch the total voting power (vxASTRO supply) at a specified timestamp. Always returns 0 in this version. -/// -/// * **QueryMsg::TotalVotingPowerAtPeriod { .. }** Fetch the total voting power (vxASTRO supply) at a specified period. Always returns 0 in this version. -/// -/// * **QueryMsg::UserVotingPower{ .. }** Fetch the user's voting power (vxASTRO balance) at the current block. Always returns 0 in this version. -/// -/// * **QueryMsg::UserVotingPowerAt { .. }** Fetch the user's voting power (vxASTRO balance) at a specified timestamp. Always returns 0 in this version. -/// -/// * **QueryMsg::UserVotingPowerAtPeriod { .. }** Fetch the user's voting power (vxASTRO balance) at a specified period. Always returns 0 in this version. -/// -/// * **QueryMsg::TotalEmissionsVotingPower {}** Fetch the total emissions voting power at the current block. -/// -/// * **QueryMsg::TotalEmissionsVotingPowerAt { time }** Fetch the total emissions voting power at a specified timestamp. -/// -/// * **QueryMsg::UserEmissionsVotingPower { user }** Fetch a user's emissions voting power at the current block. -/// -/// * **QueryMsg::UserEmissionsVotingPowerAt { user, time }** Fetch a user's emissions voting power at a specified timestamp. -/// -/// * **QueryMsg::LockInfo { user }** Fetch a user's lock information. -/// -/// * **QueryMsg::UserDepositAt { user, timestamp }** Fetch a user's deposit at a specified timestamp. -/// -/// * **QueryMsg::Config {}** Fetch the contract's config. -/// -/// * **QueryMsg::Balance { address: _ }** Fetch the user's balance. Always returns 0 in this version. -/// -/// * **QueryMsg::TokenInfo {}** Fetch the token's information. -/// -/// * **QueryMsg::MarketingInfo {}** Fetch the token's marketing information. -/// -/// * **QueryMsg::DownloadLogo {}** Fetch the token's logo. -/// -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::CheckVotersAreBlacklisted { voters } => { - to_json_binary(&check_voters_are_blacklisted(deps, voters)?) - } - QueryMsg::BlacklistedVoters { start_after, limit } => { - to_json_binary(&get_blacklisted_voters(deps, start_after, limit)?) - } - QueryMsg::TotalVotingPower {} => to_json_binary(&VotingPowerResponse { - voting_power: Uint128::zero(), - }), - QueryMsg::TotalVotingPowerAt { .. } => to_json_binary(&VotingPowerResponse { - voting_power: Uint128::zero(), - }), - QueryMsg::TotalVotingPowerAtPeriod { .. } => to_json_binary(&VotingPowerResponse { - voting_power: Uint128::zero(), - }), - QueryMsg::UserVotingPower { .. } => to_json_binary(&VotingPowerResponse { - voting_power: Uint128::zero(), - }), - QueryMsg::UserVotingPowerAt { .. } => to_json_binary(&VotingPowerResponse { - voting_power: Uint128::zero(), - }), - QueryMsg::UserVotingPowerAtPeriod { .. } => to_json_binary(&VotingPowerResponse { - voting_power: Uint128::zero(), - }), - QueryMsg::TotalEmissionsVotingPower {} => { - to_json_binary(&get_total_emissions_voting_power(deps, env, None)?) - } - QueryMsg::TotalEmissionsVotingPowerAt { time } => { - to_json_binary(&get_total_emissions_voting_power(deps, env, Some(time))?) - } - QueryMsg::UserEmissionsVotingPower { user } => { - to_json_binary(&get_user_emissions_voting_power(deps, env, user, None)?) - } - QueryMsg::UserEmissionsVotingPowerAt { user, time } => to_json_binary( - &get_user_emissions_voting_power(deps, env, user, Some(time))?, - ), - QueryMsg::LockInfo { user } => to_json_binary(&get_user_lock_info(deps, env, user)?), - QueryMsg::UserDepositAt { user, timestamp } => { - to_json_binary(&get_user_deposit_at_time(deps, user, timestamp)?) - } - QueryMsg::Config {} => { - let config = CONFIG.load(deps.storage)?; - to_json_binary(&config) - } - QueryMsg::Balance { address } => to_json_binary(&get_user_balance(deps, env, address)?), - QueryMsg::TokenInfo {} => to_json_binary(&query_token_info(deps, env)?), - QueryMsg::MarketingInfo {} => to_json_binary(&query_marketing_info(deps)?), - QueryMsg::DownloadLogo {} => to_json_binary(&query_download_logo(deps)?), - } -} - -/// Checks if specified addresses are blacklisted. -/// -/// * **voters** addresses to check if they are blacklisted. -pub fn check_voters_are_blacklisted( - deps: Deps, - voters: Vec, -) -> StdResult { - let black_list = BLACKLIST.load(deps.storage)?; - - for voter in voters { - let voter_addr = deps.api.addr_validate(voter.as_str())?; - if !black_list.contains(&voter_addr) { - return Ok(BlacklistedVotersResponse::VotersNotBlacklisted { voter }); - } - } - - Ok(BlacklistedVotersResponse::VotersBlacklisted {}) -} - -/// Returns a list of blacklisted voters. -/// -/// * **start_after** is an optional field that specifies whether the function should return -/// a list of voters starting from a specific address onward. -/// -/// * **limit** max amount of voters addresses to return. -pub fn get_blacklisted_voters( - deps: Deps, - start_after: Option, - limit: Option, -) -> StdResult> { - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let mut black_list = BLACKLIST.load(deps.storage)?; - - if black_list.is_empty() { - return Ok(vec![]); - } - - black_list.sort(); - - let mut start_index = Default::default(); - if let Some(start_after) = start_after { - let start_addr = deps.api.addr_validate(start_after.as_str())?; - start_index = black_list - .iter() - .position(|addr| *addr == start_addr) - .ok_or_else(|| { - StdError::generic_err(format!( - "The {} address is not blacklisted", - start_addr.as_str() - )) - })? - + 1; // start from the next element of the slice - } - - // validate end index of the slice - let end_index = (start_index + limit).min(black_list.len()); - - Ok(black_list[start_index..end_index].to_vec()) -} - -/// Return a user's lock information. -/// -/// * **user** user for which we return lock information. -fn get_user_lock_info(deps: Deps, _env: Env, user: String) -> StdResult { - let addr = deps.api.addr_validate(&user)?; - if let Some(lock) = LOCKED.may_load(deps.storage, addr)? { - let resp = LockInfoResponse { - amount: lock.amount, - end: lock.end, - }; - Ok(resp) - } else { - Err(StdError::generic_err("User is not found")) - } -} - -/// Fetches a user's emissions voting power at the current block and uses that -/// as the balance -/// -/// * **user** user/staker for which we fetch the current voting power (vxASTRO balance). -fn get_user_balance(deps: Deps, env: Env, user: String) -> StdResult { - let vp_response = get_user_emissions_voting_power(deps, env, user, None)?; - Ok(BalanceResponse { - balance: vp_response.voting_power, - }) -} - -/// Return a user's staked xASTRO amount at a given timestamp. -/// -/// * **user** user for which we return lock information. -/// -/// * **timestamp** timestamp at which we return the staked xASTRO amount. -fn get_user_deposit_at_time(deps: Deps, user: String, timestamp: Uint64) -> StdResult { - let addr = deps.api.addr_validate(&user)?; - let locked_opt = LOCKED.may_load_at_height(deps.storage, addr, timestamp.u64())?; - if let Some(lock) = locked_opt { - Ok(lock.amount) - } else { - Ok(Uint128::zero()) - } -} - -/// Fetch a user's emissions voting power at the current block if no time -/// is specified, else uses the given time. If a user is blacklisted, this will -/// return 0 -/// -/// * **user** user/staker for which we fetch the current emissions voting power. -/// -/// * **time** optional time at which to fetch the user's emissions voting power. -fn get_user_emissions_voting_power( - deps: Deps, - env: Env, - user: String, - time: Option, -) -> StdResult { - let user = deps.api.addr_validate(&user)?; - let timestamp = time.unwrap_or_else(|| env.block.time.seconds()); - let last_checkpoint = fetch_last_checkpoint(deps.storage, &user, timestamp)?; - - if let Some(emissions_power) = last_checkpoint.map(|(_, emissions_power)| emissions_power) { - // The voting power point at the specified `time` was found - Ok(VotingPowerResponse { - voting_power: emissions_power, - }) - } else { - // User not found - Ok(VotingPowerResponse { - voting_power: Uint128::zero(), - }) - } -} - -/// Fetch the total emissions voting power at the current block if no time -/// is specified, else uses the given time. -/// -/// * **time** optional time at which to fetch the user's emissions voting power. -fn get_total_emissions_voting_power( - deps: Deps, - env: Env, - time: Option, -) -> StdResult { - let timestamp = time.unwrap_or_else(|| env.block.time.seconds()); - let last_checkpoint = fetch_last_checkpoint(deps.storage, &env.contract.address, timestamp)?; - - let emissions_power = - last_checkpoint.map_or(Uint128::zero(), |(_, emissions_power)| emissions_power); - Ok(VotingPowerResponse { - voting_power: emissions_power, - }) -} - -/// Fetch the vxASTRO token information, such as the token name, symbol, decimals and total supply (total voting power). -fn query_token_info(deps: Deps, _env: Env) -> StdResult { - let info = TOKEN_INFO.load(deps.storage)?; - let res = TokenInfoResponse { - name: info.name, - symbol: info.symbol, - decimals: info.decimals, - total_supply: Uint128::zero(), - }; - Ok(res) -} diff --git a/contracts/voting_escrow_lite/src/state.rs b/contracts/voting_escrow_lite/src/state.rs deleted file mode 100644 index 87dc4aad..00000000 --- a/contracts/voting_escrow_lite/src/state.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::astroport::common::OwnershipProposal; -use astroport_governance::voting_escrow_lite::Config; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Uint128}; -use cw_storage_plus::{Item, Map, SnapshotMap, Strategy}; - -/// This structure stores data about the lockup position for a specific vxASTRO staker. -#[cw_serde] -pub struct Lock { - /// The total amount of xASTRO tokens that were deposited in the vxASTRO position - pub amount: Uint128, - /// The timestamp when a lock will be unlocked. None for positions in Locked state - pub end: Option, -} - -/// Stores the contract config at the given key -pub const CONFIG: Item = Item::new("config"); - -/// Stores all user lock history by timestamp -pub const LOCKED: SnapshotMap = SnapshotMap::new( - "locked_timestamp", - "locked_timestamp__checkpoints", - "locked_timestamp__changelog", - Strategy::EveryBlock, -); - -/// Stores the voting power history for every staker (addr => timestamp) -/// Total voting power checkpoints are stored using a (contract_addr => timestamp) key -pub const VOTING_POWER_HISTORY: Map<(Addr, u64), Uint128> = Map::new("voting_power_history"); - -/// Contains a proposal to change contract ownership -pub const OWNERSHIP_PROPOSAL: Item = Item::new("ownership_proposal"); - -/// Contains blacklisted staker addresses -pub const BLACKLIST: Item> = Item::new("blacklist"); diff --git a/contracts/voting_escrow_lite/src/utils.rs b/contracts/voting_escrow_lite/src/utils.rs deleted file mode 100644 index 5d02a1cc..00000000 --- a/contracts/voting_escrow_lite/src/utils.rs +++ /dev/null @@ -1,34 +0,0 @@ -use cosmwasm_std::{Addr, Order, StdResult, Storage, Uint128}; -use cw_storage_plus::Bound; - -use crate::state::BLACKLIST; -use crate::{error::ContractError, state::VOTING_POWER_HISTORY}; - -/// Checks if the blacklist contains a specific address. -pub(crate) fn blacklist_check(storage: &dyn Storage, addr: &Addr) -> Result<(), ContractError> { - // TODO: use Map instead of raw array which could be potentially hit gas limit - let blacklist = BLACKLIST.load(storage)?; - if blacklist.contains(addr) { - Err(ContractError::AddressBlacklisted(addr.to_string())) - } else { - Ok(()) - } -} - -/// Fetches the last known voting power in [`VOTING_POWER_HISTORY`] for the given address. -pub(crate) fn fetch_last_checkpoint( - storage: &dyn Storage, - addr: &Addr, - timestamp: u64, -) -> StdResult> { - VOTING_POWER_HISTORY - .prefix(addr.clone()) - .range( - storage, - None, - Some(Bound::inclusive(timestamp)), - Order::Descending, - ) - .next() - .transpose() -} diff --git a/contracts/voting_escrow_lite/tests/simulation.todo b/contracts/voting_escrow_lite/tests/simulation.todo deleted file mode 100644 index 63fba753..00000000 --- a/contracts/voting_escrow_lite/tests/simulation.todo +++ /dev/null @@ -1,394 +0,0 @@ -use crate::test_utils::{mock_app, Helper, MULTIPLIER}; -use anyhow::Result; -use astroport_governance::utils::{ - get_lite_period, get_lite_periods_count, EPOCH_START, LITE_VOTING_PERIOD, MAX_LOCK_TIME, -}; -use cosmwasm_std::Addr; -use cw_multi_test::{next_block, App, AppResponse}; -use std::collections::hash_map::Entry; -use std::collections::HashMap; - -mod test_utils; - -#[derive(Clone, Default, Debug)] -struct Point { - amount: f64, - end: u64, -} - -#[derive(Clone, Debug)] -enum Event { - CreateLock(f64, u64), - ExtendLock(f64), - Withdraw, - Blacklist, - Recover, -} - -use Event::*; - -struct Simulator { - // Point history (history[period][user] = point) - points: Vec>, - // Current user's lock (amount, end) - locked: HashMap, - users: Vec, - helper: Helper, - router: App, -} - -fn apply_coefficient(amount: f64) -> f64 { - // No coefficient in lite version - (amount * MULTIPLIER as f64).trunc() / MULTIPLIER as f64 -} - -impl Simulator { - fn new>(users: &[T]) -> Self { - let mut router = mock_app(); - Self { - points: vec![HashMap::new(); 10000], - locked: Default::default(), - users: users.iter().cloned().map(|user| user.into()).collect(), - helper: Helper::init(&mut router, Addr::unchecked("owner")), - router, - } - } - - fn mint(&mut self, user: &str, amount: u128) { - self.helper - .mint_xastro(&mut self.router, user, amount as u64) - } - - fn block_period(&self) -> u64 { - get_lite_period(self.router.block_info().time.seconds()).unwrap() - } - - fn app_next_period(&mut self) { - self.router.update_block(next_block); - self.router - .update_block(|block| block.time = block.time.plus_seconds(LITE_VOTING_PERIOD)); - } - - fn create_lock(&mut self, user: &str, amount: f64, interval: u64) -> Result { - let block_period = self.block_period(); - let periods_interval = get_lite_periods_count(interval); - self.helper - .create_lock(&mut self.router, user, interval, amount as f32) - .map(|response| { - self.add_point( - block_period as usize, - user, - apply_coefficient(amount), - block_period + periods_interval, - ); - self.locked.extend(vec![( - user.to_string(), - (amount, block_period + periods_interval), - )]); - response - }) - } - - fn extend_lock(&mut self, user: &str, amount: f64) -> Result { - self.helper - .extend_lock_amount(&mut self.router, user, amount as f32) - .map(|response| { - let cur_period = self.block_period() as usize; - let (user_balance, end) = - if let Some(point) = self.get_user_point_at(cur_period, user) { - (point.amount, point.end) - } else { - let prev_point = self - .get_prev_point(user) - .expect("We always need previous point!"); - (self.calc_user_balance_at(cur_period, user), prev_point.end) - }; - let vp = apply_coefficient(amount); - self.add_point(cur_period, user, user_balance + vp, end); - let mut lock = self.locked.get_mut(user).unwrap(); - lock.0 += amount; - response - }) - } - - fn withdraw(&mut self, user: &str) -> Result { - self.helper - .withdraw(&mut self.router, user) - .map(|response| { - let cur_period = self.block_period(); - self.add_point(cur_period as usize, user, 0.0, cur_period); - self.locked.remove(user); - response - }) - } - - fn append2blacklist(&mut self, user: &str) -> Result { - self.helper - .update_blacklist(&mut self.router, Some(vec![user.to_string()]), None) - .map(|response| { - let cur_period = self.block_period(); - self.add_point(cur_period as usize, user, 0.0, cur_period); - response - }) - } - - fn remove_from_blacklist(&mut self, user: &str) -> Result { - self.helper - .update_blacklist(&mut self.router, None, Some(vec![user.to_string()])) - .map(|response| { - let cur_period = self.block_period() as usize; - if let Some((amount, end)) = self.locked.get(user).copied() { - // Amount stays constant, no need to recalculate based on period - self.add_point(cur_period, user, apply_coefficient(amount), end); - } - response - }) - } - - fn event_router(&mut self, user: &str, event: Event) { - match event { - Event::CreateLock(amount, interval) => { - if let Err(err) = self.create_lock(user, amount, interval) { - dbg!(err); - } - } - Event::ExtendLock(amount) => { - if let Err(err) = self.extend_lock(user, amount) { - dbg!(err); - } - } - Event::Withdraw => { - if let Err(err) = self.withdraw(user) { - dbg!(err); - } - } - Event::Blacklist => { - if let Err(err) = self.append2blacklist(user) { - dbg!(err); - } - } - Event::Recover => { - if let Err(err) = self.remove_from_blacklist(user) { - dbg!(err); - } - } - } - let real_balance = self - .get_user_point_at(self.block_period() as usize, user) - .map(|point| point.amount) - .unwrap_or_else(|| self.calc_user_balance_at(self.block_period() as usize, user)); - let contract_balance = self - .helper - .query_user_emissions_vp(&mut self.router, user) - .unwrap_or(0.0) as f64; - if (real_balance - contract_balance).abs() >= 10e-3 { - assert_eq!(real_balance, contract_balance) - }; - } - - fn checkpoint_all_users(&mut self) { - let cur_period = self.block_period() as usize; - self.users.clone().iter().for_each(|user| { - // we need to calc point only if it was not calculated yet - if self.get_user_point_at(cur_period, user).is_none() { - self.checkpoint_user(user) - } - }) - } - - fn add_point>(&mut self, period: usize, user: T, amount: f64, end: u64) { - let map = &mut self.points[period]; - map.extend(vec![(user.into(), Point { amount, end })]); - } - - fn get_prev_point(&mut self, user: &str) -> Option { - let prev_period = (self.block_period() - 1) as usize; - self.get_user_point_at(prev_period, user) - } - - fn checkpoint_user(&mut self, user: &str) { - let cur_period = self.block_period() as usize; - let user_balance = self.calc_user_balance_at(cur_period, user); - let prev_point = self - .get_prev_point(user) - .expect("We always need previous point!"); - self.add_point(cur_period, user, user_balance, prev_point.end); - } - - fn get_user_point_at>(&mut self, period: usize, user: T) -> Option { - let points_map = &mut self.points[period]; - match points_map.entry(user.into()) { - Entry::Occupied(value) => Some(value.get().clone()), - Entry::Vacant(_) => None, - } - } - - fn calc_user_balance_at(&mut self, period: usize, user: &str) -> f64 { - match self.get_user_point_at(period, user) { - Some(point) => point.amount, - None => { - let prev_point = self - .get_user_point_at(period - 1, user) - .expect("We always need previous point!"); - - // No calculations needed as nothing decays - prev_point.amount - } - } - } - - fn calc_total_balance_at(&mut self, period: usize) -> f64 { - self.users.clone().iter().fold(0.0, |acc, user| { - acc + self.get_user_point_at(period, user).unwrap().amount - }) - } -} - -use proptest::prelude::*; - -const MAX_PERIOD: usize = 10; -const MAX_USERS: usize = 6; -const MAX_EVENTS: usize = 100; - -fn amount_strategy() -> impl Strategy { - // (1f64..=100f64).prop_map(|val| (val * MULTIPLIER as f64).trunc() / MULTIPLIER as f64) - (1f64..=2f64).prop_map(|val| (val * MULTIPLIER as f64).trunc() / MULTIPLIER as f64) -} - -fn events_strategy() -> impl Strategy { - prop_oneof![ - Just(Event::Withdraw), - Just(Event::Blacklist), - Just(Event::Recover), - amount_strategy().prop_map(Event::ExtendLock), - (amount_strategy(), 0..MAX_LOCK_TIME).prop_map(|(a, b)| Event::CreateLock(a, b)), - ] -} - -fn generate_cases() -> impl Strategy, Vec<(usize, String, Event)>)> { - let users_strategy = prop::collection::vec("[a-z]{4,32}", 1..MAX_USERS); - users_strategy.prop_flat_map(|users| { - ( - Just(users.clone()), - prop::collection::vec( - ( - 1..=MAX_PERIOD, - prop::sample::select(users), - events_strategy(), - ), - 0..MAX_EVENTS, - ), - ) - }) -} - -proptest! { - #[test] - fn run_simulations - ( - case in generate_cases() - ) { - let mut events: Vec> = vec![vec![]; MAX_PERIOD + 1]; - let (users, events_tuples) = case; - for (period, user, event) in events_tuples { - events[period].push((user, event)); - }; - - let mut simulator = Simulator::new(&users); - for user in users { - simulator.mint(&user, 10000); - simulator.add_point(0, user, 0.0, 104); - } - simulator.app_next_period(); - - for period in 1..=MAX_PERIOD { - if let Some(period_events) = events.get(period) { - for (user, event) in period_events { - simulator.event_router(user, event.clone()) - } - } - simulator.checkpoint_all_users(); - let real_balance = simulator.calc_total_balance_at(period); - let contract_balance = simulator - .helper - .query_total_emissions_vp(&mut simulator.router) - .unwrap_or(0.0) as f64; - if (real_balance - contract_balance).abs() >= 10e-3 { - assert_eq!(real_balance, contract_balance) - }; - // Evaluate historical periods - for check_period in 1..period { - let real_balance = simulator.calc_total_balance_at(check_period); - let contract_balance = simulator - .helper - .query_total_emissions_vp_at(&mut simulator.router, EPOCH_START + check_period as u64 * LITE_VOTING_PERIOD) - .unwrap_or(0.0) as f64; - if (real_balance - contract_balance).abs() >= 10e-3 { - assert_eq!(real_balance, contract_balance) - }; - } - simulator.app_next_period() - } - } -} - -#[test] -fn exact_simulation() { - let case = ( - ["bpcy"], - [ - (1, "bpcy", CreateLock(100.0, 3024000)), - (3, "bpcy", Blacklist), - (3, "bpcy", Recover), - ], - ); - - let mut events: Vec> = vec![vec![]; MAX_PERIOD + 1]; - let (users, events_tuples) = case; - for (period, user, event) in events_tuples { - events[period].push((user.to_string(), event)); - } - - let mut simulator = Simulator::new(&users); - for user in users { - simulator.mint(user, 10000); - simulator.add_point(0, user, 0.0, 104); - } - simulator.app_next_period(); - - for period in 1..=MAX_PERIOD { - if let Some(period_events) = events.get(period) { - if !period_events.is_empty() { - println!("Period {}:", period); - } - for (user, event) in period_events { - simulator.event_router(user, event.clone()) - } - } - simulator.checkpoint_all_users(); - let real_balance = simulator.calc_total_balance_at(period); - let contract_balance = simulator - .helper - .query_total_emissions_vp(&mut simulator.router) - .unwrap_or(0.0) as f64; - if (real_balance - contract_balance).abs() >= 10e-3 { - println!("Assert failed at period {}", period); - assert_eq!(real_balance, contract_balance) - }; - // Evaluate historical periods - for check_period in 1..period { - let real_balance = simulator.calc_total_balance_at(check_period); - let contract_balance = simulator - .helper - .query_total_emissions_vp_at( - &mut simulator.router, - EPOCH_START + check_period as u64 * LITE_VOTING_PERIOD, - ) - .unwrap_or(0.0) as f64; - if (real_balance - contract_balance).abs() >= 10e-3 { - assert_eq!(real_balance, contract_balance) - }; - } - simulator.app_next_period() - } -} diff --git a/contracts/voting_escrow_lite/tests/test_utils.rs b/contracts/voting_escrow_lite/tests/test_utils.rs deleted file mode 100644 index 9137ad90..00000000 --- a/contracts/voting_escrow_lite/tests/test_utils.rs +++ /dev/null @@ -1,440 +0,0 @@ -#![allow(dead_code)] - -use anyhow::Result; -use cosmwasm_std::{coins, Addr, BlockInfo, StdResult, Timestamp, Uint128, Uint64}; -use cw20::Logo; -use cw_multi_test::{App, AppBuilder, AppResponse, ContractWrapper, Executor}; - -use astroport_governance::utils::EPOCH_START; -use astroport_governance::voting_escrow_lite::{ - BlacklistedVotersResponse, ExecuteMsg, InstantiateMsg, QueryMsg, UpdateMarketingInfo, - VotingPowerResponse, -}; - -pub const MULTIPLIER: u128 = 1_000000; - -pub const XASTRO_DENOM: &str = "factory/assembly/xASTRO"; - -pub const OWNER: &str = "owner"; - -pub struct Helper { - pub app: App, - pub owner: Addr, - pub vxastro: Addr, - pub generator_controller: Addr, -} - -impl Helper { - pub fn init() -> Self { - let owner = Addr::unchecked(OWNER); - - let mut app = AppBuilder::new() - .with_block(BlockInfo { - height: 1000, - time: Timestamp::from_seconds(EPOCH_START), - chain_id: "cw-multitest-1".to_string(), - }) - .build(|router, _, storage| { - router - .bank - .init_balance(storage, &owner, coins(u128::MAX, XASTRO_DENOM)) - .unwrap() - }); - - let voting_contract = Box::new(ContractWrapper::new_with_empty( - astroport_voting_escrow_lite::execute::execute, - astroport_voting_escrow_lite::contract::instantiate, - astroport_voting_escrow_lite::query::query, - )); - - let voting_code_id = app.store_code(voting_contract); - - let marketing_info = UpdateMarketingInfo { - project: Some("Astroport".to_string()), - description: Some("Astroport is a decentralized application for managing the supply of space resources.".to_string()), - marketing: Some(owner.to_string()), - logo: Some(Logo::Url("https://astroport.com/logo.png".to_string())), - }; - - let msg = InstantiateMsg { - owner: owner.to_string(), - guardian_addr: Some("guardian".to_string()), - deposit_denom: XASTRO_DENOM.to_string(), - marketing: Some(marketing_info), - logo_urls_whitelist: vec!["https://astroport.com/".to_string()], - generator_controller_addr: None, - outpost_addr: None, - }; - let vxastro = app - .instantiate_contract( - voting_code_id, - owner.clone(), - &msg, - &[], - String::from("vxASTRO"), - None, - ) - .unwrap(); - - let generator_controller = Box::new(ContractWrapper::new_with_empty( - astroport_generator_controller::contract::execute, - astroport_generator_controller::contract::instantiate, - astroport_generator_controller::contract::query, - )); - - let generator_controller_id = app.store_code(generator_controller); - - let msg = astroport_governance::generator_controller_lite::InstantiateMsg { - owner: owner.to_string(), - assembly_addr: "assembly".to_string(), - escrow_addr: vxastro.to_string(), - factory_addr: "factory".to_string(), - generator_addr: "generator".to_string(), - hub_addr: None, - pools_limit: 10, - whitelisted_pools: vec![], - }; - let generator_controller = app - .instantiate_contract( - generator_controller_id, - owner.clone(), - &msg, - &[], - String::from("Generator Controller Lite"), - None, - ) - .unwrap(); - - app.execute_contract( - owner.clone(), - vxastro.clone(), - &ExecuteMsg::UpdateConfig { - new_guardian: None, - generator_controller: Some(generator_controller.to_string()), - outpost: None, - }, - &[], - ) - .unwrap(); - - Self { - app, - owner, - vxastro, - generator_controller, - } - } - - pub fn mint_xastro(&mut self, to: &str, amount: u128) { - let amount = amount * MULTIPLIER; - self.app - .send_tokens( - self.owner.clone(), - Addr::unchecked(to), - &coins(amount, XASTRO_DENOM), - ) - .unwrap(); - } - - pub fn check_xastro_balance(&self, user: &str, amount: u128) { - let amount = amount * MULTIPLIER; - let balance = self - .app - .wrap() - .query_balance(user, XASTRO_DENOM) - .unwrap() - .amount; - assert_eq!(balance.u128(), amount); - } - - pub fn create_lock(&mut self, user: &str, amount: f32) -> Result { - let amount = (amount * MULTIPLIER as f32) as u128; - self.app.execute_contract( - Addr::unchecked(user), - self.vxastro.clone(), - &ExecuteMsg::CreateLock {}, - &coins(amount, XASTRO_DENOM), - ) - } - - pub fn create_lock_u128(&mut self, user: &str, amount: u128) -> Result { - self.app.execute_contract( - Addr::unchecked(user), - self.vxastro.clone(), - &ExecuteMsg::CreateLock {}, - &coins(amount, XASTRO_DENOM), - ) - } - - pub fn extend_lock_amount(&mut self, user: &str, amount: f32) -> Result { - let amount = (amount * MULTIPLIER as f32) as u128; - self.app.execute_contract( - Addr::unchecked(user), - self.vxastro.clone(), - &ExecuteMsg::ExtendLockAmount {}, - &coins(amount, XASTRO_DENOM), - ) - } - - pub fn relock(&mut self, user: &str) -> Result { - self.app.execute_contract( - Addr::unchecked("outpost"), - self.vxastro.clone(), - &ExecuteMsg::Relock { - user: user.to_string(), - }, - &[], - ) - } - - pub fn deposit_for(&mut self, from: &str, to: &str, amount: f32) -> Result { - let amount = (amount * MULTIPLIER as f32) as u128; - self.app.execute_contract( - Addr::unchecked(from), - self.vxastro.clone(), - &ExecuteMsg::DepositFor { - user: to.to_string(), - }, - &coins(amount, XASTRO_DENOM), - ) - } - - pub fn unlock(&mut self, user: &str) -> Result { - self.app.execute_contract( - Addr::unchecked(user), - self.vxastro.clone(), - &ExecuteMsg::Unlock {}, - &[], - ) - } - - pub fn withdraw(&mut self, user: &str) -> Result { - self.app.execute_contract( - Addr::unchecked(user), - self.vxastro.clone(), - &ExecuteMsg::Withdraw {}, - &[], - ) - } - - pub fn update_blacklist( - &mut self, - append_addrs: Vec, - remove_addrs: Vec, - ) -> Result { - self.app.execute_contract( - Addr::unchecked("owner"), - self.vxastro.clone(), - &ExecuteMsg::UpdateBlacklist { - append_addrs, - remove_addrs, - }, - &[], - ) - } - - pub fn update_outpost_address(&mut self, new_address: String) -> Result { - self.app.execute_contract( - Addr::unchecked("owner"), - self.vxastro.clone(), - &ExecuteMsg::UpdateConfig { - new_guardian: None, - generator_controller: None, - outpost: Some(new_address), - }, - &[], - ) - } - - pub fn query_user_vp(&self, user: &str) -> StdResult { - self.app - .wrap() - .query_wasm_smart( - self.vxastro.clone(), - &QueryMsg::UserVotingPower { - user: user.to_string(), - }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_user_emissions_vp(&self, user: &str) -> StdResult { - self.app - .wrap() - .query_wasm_smart( - self.vxastro.clone(), - &QueryMsg::UserEmissionsVotingPower { - user: user.to_string(), - }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_exact_user_vp(&self, user: &str) -> StdResult { - self.app - .wrap() - .query_wasm_smart( - self.vxastro.clone(), - &QueryMsg::UserVotingPower { - user: user.to_string(), - }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128()) - } - - pub fn query_exact_user_emissions_vp(&self, user: &str) -> StdResult { - self.app - .wrap() - .query_wasm_smart( - self.vxastro.clone(), - &QueryMsg::UserEmissionsVotingPower { - user: user.to_string(), - }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128()) - } - - pub fn query_user_vp_at(&self, user: &str, time: u64) -> StdResult { - self.app - .wrap() - .query_wasm_smart( - self.vxastro.clone(), - &QueryMsg::UserVotingPowerAt { - user: user.to_string(), - time, - }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_user_emissions_vp_at(&self, user: &str, time: u64) -> StdResult { - self.app - .wrap() - .query_wasm_smart( - self.vxastro.clone(), - &QueryMsg::UserEmissionsVotingPowerAt { - user: user.to_string(), - time, - }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_user_vp_at_period(&self, user: &str, period: u64) -> StdResult { - self.app - .wrap() - .query_wasm_smart( - self.vxastro.clone(), - &QueryMsg::UserVotingPowerAtPeriod { - user: user.to_string(), - period, - }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_total_vp(&self) -> StdResult { - self.app - .wrap() - .query_wasm_smart(self.vxastro.clone(), &QueryMsg::TotalVotingPower {}) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_total_emissions_vp(&self) -> StdResult { - self.app - .wrap() - .query_wasm_smart( - self.vxastro.clone(), - &QueryMsg::TotalEmissionsVotingPower {}, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_exact_total_vp(&self) -> StdResult { - self.app - .wrap() - .query_wasm_smart(self.vxastro.clone(), &QueryMsg::TotalVotingPower {}) - .map(|vp: VotingPowerResponse| vp.voting_power.u128()) - } - - pub fn query_exact_total_emissions_vp(&self) -> StdResult { - self.app - .wrap() - .query_wasm_smart( - self.vxastro.clone(), - &QueryMsg::TotalEmissionsVotingPower {}, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128()) - } - - pub fn query_total_vp_at(&self, time: u64) -> StdResult { - self.app - .wrap() - .query_wasm_smart(self.vxastro.clone(), &QueryMsg::TotalVotingPowerAt { time }) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_total_emissions_vp_at(&self, time: u64) -> StdResult { - self.app - .wrap() - .query_wasm_smart( - self.vxastro.clone(), - &QueryMsg::TotalEmissionsVotingPowerAt { time }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_total_vp_at_period(&self, period: u64) -> StdResult { - self.app - .wrap() - .query_wasm_smart( - self.vxastro.clone(), - &QueryMsg::TotalVotingPowerAtPeriod { period }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_total_emissions_vp_at_period(&self, timestamp: u64) -> StdResult { - self.app - .wrap() - .query_wasm_smart( - self.vxastro.clone(), - &QueryMsg::TotalEmissionsVotingPowerAt { time: timestamp }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_locked_balance_at(&self, user: &str, timestamp: Uint64) -> StdResult { - self.app - .wrap() - .query_wasm_smart( - self.vxastro.clone(), - &QueryMsg::UserDepositAt { - user: user.to_string(), - timestamp, - }, - ) - .map(|vp: Uint128| vp.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_blacklisted_voters( - &self, - start_after: Option, - limit: Option, - ) -> StdResult> { - self.app.wrap().query_wasm_smart( - self.vxastro.clone(), - &QueryMsg::BlacklistedVoters { start_after, limit }, - ) - } - - pub fn check_voters_are_blacklisted( - &self, - voters: Vec, - ) -> StdResult { - self.app.wrap().query_wasm_smart( - self.vxastro.clone(), - &QueryMsg::CheckVotersAreBlacklisted { voters }, - ) - } -} diff --git a/contracts/voting_escrow_lite/tests/vxastro_lite_integration.rs b/contracts/voting_escrow_lite/tests/vxastro_lite_integration.rs deleted file mode 100644 index 3c1d0743..00000000 --- a/contracts/voting_escrow_lite/tests/vxastro_lite_integration.rs +++ /dev/null @@ -1,1026 +0,0 @@ -use cosmwasm_std::{attr, Addr, StdError, Uint64}; -use cw20::{Logo, LogoInfo, MarketingInfoResponse}; -use cw_multi_test::{next_block, Executor}; - -use astroport_governance::utils::{get_lite_period, WEEK}; -use astroport_governance::voting_escrow_lite::{Config, ExecuteMsg, LockInfoResponse, QueryMsg}; - -use crate::test_utils::{Helper, MULTIPLIER}; - -mod test_utils; - -#[test] -fn lock_unlock_logic() { - let mut helper = Helper::init(); - - helper.mint_xastro("owner", 100); - - // Mint ASTRO, stake it and mint xASTRO - helper.mint_xastro("user", 100); - helper.check_xastro_balance("user", 100); - - // Try to withdraw from a non-existent lock - let err = helper.withdraw("user").unwrap_err(); - assert_eq!(err.root_cause().to_string(), "Lock does not exist"); - - // Try to deposit more xASTRO in a position that does not already exist - // This should create a new lock - helper.extend_lock_amount("user", 1f32).unwrap(); - helper.check_xastro_balance("user", 99); - helper.check_xastro_balance(helper.vxastro.as_str(), 1); - - // Current total voting power is 0 - let vp = helper.query_total_vp().unwrap(); - assert_eq!(vp, 0.0); - let vp = helper.query_total_emissions_vp().unwrap(); - assert_eq!(vp, 1.0); - - // Try to create another voting escrow lock - let err = helper.create_lock("user", 90f32).unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Lock already exists, either unlock and withdraw or extend_lock to add to the lock" - ); - - // Check that 90 xASTRO were not debited - helper.check_xastro_balance("user", 99); - helper.check_xastro_balance(helper.vxastro.as_str(), 1); - - // Add more xASTRO to the existing position - helper.extend_lock_amount("user", 9f32).unwrap(); - helper.check_xastro_balance("user", 90); - helper.check_xastro_balance(helper.vxastro.as_str(), 10); - - // Try to withdraw from a non-unlocked lock - let err = helper.withdraw("user").unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "The lock has not been unlocked, call unlock first" - ); - - helper.unlock("user").unwrap(); - - // Go in the future - helper.app.update_block(next_block); - helper - .app - .update_block(|block| block.time = block.time.plus_seconds(WEEK)); - - // The lock has not yet expired since unlocking has a 2 week waiting time - let err = helper.withdraw("user").unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "The lock time has not yet expired" - ); - - // Go to the future again - helper.app.update_block(next_block); - helper - .app - .update_block(|block| block.time = block.time.plus_seconds(WEEK)); - - // Try to add more xASTRO to an expired position - let err = helper.extend_lock_amount("user", 1f32).unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "The lock expired. Withdraw and create new lock" - ); - - // Imagine the user will withdraw their expired lock in 5 weeks - helper.app.update_block(next_block); - helper - .app - .update_block(|block| block.time = block.time.plus_seconds(5 * WEEK)); - - // Time has passed so we can withdraw - helper.withdraw("user").unwrap(); - helper.check_xastro_balance("user", 100); - helper.check_xastro_balance(helper.vxastro.as_str(), 0); - - // Create a new lock - helper.extend_lock_amount("user", 50f32).unwrap(); - - let vp = helper.query_total_emissions_vp().unwrap(); - assert_eq!(vp, 50.0); - - let vp = helper.query_user_emissions_vp("user").unwrap(); - assert_eq!(vp, 50.0); - - // Unlock the lock - helper.unlock("user").unwrap(); - - let vp = helper.query_total_emissions_vp().unwrap(); - assert_eq!(vp, 0.0); - - let vp = helper.query_user_emissions_vp("user").unwrap(); - assert_eq!(vp, 0.0); - - // Relock -} - -#[test] -fn new_lock_after_unlock() { - let mut helper = Helper::init(); - helper.mint_xastro("owner", 100); - - // Mint ASTRO, stake it and mint xASTRO - helper.mint_xastro("user", 100); - - helper.create_lock("user", 50f32).unwrap(); - - let vp = helper.query_user_vp("user").unwrap(); - assert_eq!(vp, 0.0); - let vp = helper.query_total_vp().unwrap(); - assert_eq!(vp, 0.0); - - let evp = helper.query_user_emissions_vp("user").unwrap(); - assert_eq!(evp, 50.0); - let evp = helper.query_total_emissions_vp().unwrap(); - assert_eq!(evp, 50.0); - - // Go to the future - helper.app.update_block(next_block); - - helper.unlock("user").unwrap(); - helper - .app - .update_block(|block| block.time = block.time.plus_seconds(WEEK * 2)); - - helper.withdraw("user").unwrap(); - helper.check_xastro_balance("user", 100); - - let vp = helper.query_user_vp("user").unwrap(); - assert_eq!(vp, 0.0); - let vp = helper.query_total_vp().unwrap(); - assert_eq!(vp, 0.0); - - // Create a new lock in 3 weeks from now - helper.app.update_block(next_block); - helper - .app - .update_block(|block| block.time = block.time.plus_seconds(WEEK * 3)); - - helper.create_lock("user", 100f32).unwrap(); - - let vp = helper.query_user_vp("user").unwrap(); - assert_eq!(vp, 0.0); - let vp = helper.query_total_vp().unwrap(); - assert_eq!(vp, 0.0); - - let evp = helper.query_user_emissions_vp("user").unwrap(); - assert_eq!(evp, 100.0); - let evp = helper.query_total_emissions_vp().unwrap(); - assert_eq!(evp, 100.0); -} - -/// Plot for this test case is generated at tests/plots/variable_decay.png -#[test] -fn emissions_voting_no_decay() { - let mut helper = Helper::init(); - helper.mint_xastro("owner", 100); - - // Mint ASTRO, stake it and mint xASTRO - helper.mint_xastro("user", 100); - helper.mint_xastro("user2", 100); - - helper.create_lock("user", 30f32).unwrap(); - - // Go to the future - helper.app.update_block(next_block); - helper - .app - .update_block(|block| block.time = block.time.plus_seconds(WEEK * 5)); - - // Create lock for user2 - helper.create_lock("user2", 50f32).unwrap(); - let vp = helper.query_total_vp().unwrap(); - assert_eq!(vp, 0.0); - - let vp = helper.query_total_emissions_vp().unwrap(); - assert_eq!(vp, 80.0); - - // Go to the future - helper.app.update_block(next_block); - helper - .app - .update_block(|block| block.time = block.time.plus_seconds(WEEK * 4)); - - helper.extend_lock_amount("user", 70f32).unwrap(); - - let vp = helper.query_user_vp("user").unwrap(); - assert_eq!(vp, 0.0); - let vp = helper.query_user_vp("user2").unwrap(); - assert_eq!(vp, 0.0); - let vp = helper.query_total_vp().unwrap(); - assert_eq!(vp, 0.0); - let vp = helper.query_user_emissions_vp("user").unwrap(); - assert_eq!(vp, 100.0); - let vp = helper.query_user_emissions_vp("user2").unwrap(); - assert_eq!(vp, 50.0); - let vp = helper.query_total_emissions_vp().unwrap(); - assert_eq!(vp, 150.0); - - let res = helper - .query_user_vp_at("user2", helper.app.block_info().time.seconds() + 4 * WEEK) - .unwrap(); - assert_eq!(res, 0.0); - let res = helper - .query_total_vp_at(helper.app.block_info().time.seconds() + WEEK) - .unwrap(); - assert_eq!(res, 0.0); - - let res = helper - .query_user_emissions_vp_at("user2", helper.app.block_info().time.seconds() + 4 * WEEK) - .unwrap(); - assert_eq!(res, 50.0); - let res = helper - .query_total_emissions_vp_at(helper.app.block_info().time.seconds() + WEEK) - .unwrap(); - assert_eq!(res, 150.0); - - // Go to the future - helper.app.update_block(next_block); - helper - .app - .update_block(|block| block.time = block.time.plus_seconds(WEEK)); - let vp = helper.query_user_vp("user").unwrap(); - assert_eq!(vp, 0.0); - let vp = helper.query_user_vp("user2").unwrap(); - assert_eq!(vp, 0.0); - let vp = helper.query_total_vp().unwrap(); - assert_eq!(vp, 0.0); - let vp = helper.query_user_emissions_vp("user").unwrap(); - assert_eq!(vp, 100.0); - let vp = helper.query_user_emissions_vp("user2").unwrap(); - assert_eq!(vp, 50.0); - let vp = helper.query_total_emissions_vp().unwrap(); - assert_eq!(vp, 150.0); -} - -#[test] -fn check_queries() { - let mut helper = Helper::init(); - helper.mint_xastro("owner", 100); - - // Mint ASTRO, stake it and mint xASTRO - helper.mint_xastro("user", 100); - helper.check_xastro_balance("user", 100); - - // Create valid voting escrow lock - helper.create_lock("user", 90f32).unwrap(); - // Check that 90 xASTRO were actually debited - helper.check_xastro_balance("user", 10); - helper.check_xastro_balance(helper.vxastro.as_str(), 90); - - // Validate user's lock - let user_lock: LockInfoResponse = helper - .app - .wrap() - .query_wasm_smart( - helper.vxastro.clone(), - &QueryMsg::LockInfo { - user: "user".to_string(), - }, - ) - .unwrap(); - assert_eq!(user_lock.amount.u128(), 90_u128 * MULTIPLIER as u128); - // New locks must not have an end time - assert_eq!(user_lock.end, None); - - // Voting power must be 0 - let total_vp_at_ts = helper - .query_total_vp_at(helper.app.block_info().time.seconds()) - .unwrap(); - assert_eq!(total_vp_at_ts, 0.0); - - // Must always be 0 - let period = get_lite_period(helper.app.block_info().time.seconds()).unwrap(); - let total_vp_at_period = helper.query_total_vp_at_period(period).unwrap(); - assert_eq!(total_vp_at_period, 0.0); - - // Must always be 0 - let user_vp = helper - .query_user_vp_at("user", helper.app.block_info().time.seconds()) - .unwrap(); - assert_eq!(user_vp, 0.0); - - // Must always be 0 - let user_vp = helper.query_user_vp_at_period("user", period).unwrap(); - assert_eq!(user_vp, 0.0); - - // Emissions voting power must be 90 - let total_emissions_vp_at_ts = helper - .query_total_emissions_vp_at(helper.app.block_info().time.seconds()) - .unwrap(); - assert_eq!(total_emissions_vp_at_ts, 90.0); - - let user_emissions_vp = helper.query_user_emissions_vp("user").unwrap(); - assert_eq!(user_emissions_vp, 90.0); - - let user_emissions_vp = helper - .query_user_emissions_vp_at("user", helper.app.block_info().time.seconds()) - .unwrap(); - assert_eq!(user_emissions_vp, 90.0); - - // Check users' locked xASTRO balance history - helper.mint_xastro("user", 90); - // SnapshotMap checkpoints the data at the next block - let start_time = Uint64::from(helper.app.block_info().time.seconds() + 1); - - let balance_timestamp = helper.query_locked_balance_at("user", start_time).unwrap(); - assert_eq!(balance_timestamp, 90f32); - - helper.app.update_block(next_block); - helper.extend_lock_amount("user", 100f32).unwrap(); - - let balance_timestamp = helper.query_locked_balance_at("user", start_time).unwrap(); - assert_eq!(balance_timestamp, 90f32); - - helper.app.update_block(|bi| { - bi.height += 100000; - bi.time = bi.time.plus_seconds(500000); - }); - - let balance_timestamp = helper.query_locked_balance_at("user", start_time).unwrap(); - assert_eq!(balance_timestamp, 90f32); - - let balance_timestamp = helper - .query_locked_balance_at( - "user", - start_time.saturating_add(Uint64::from(10u64)), // Next block adds 5 seconds - ) - .unwrap(); - assert_eq!(balance_timestamp, 190f32); - - // The user still has 190 xASTRO locked - let balance_timestamp = helper - .query_locked_balance_at( - "user", - Uint64::from(helper.app.block_info().time.seconds()), // Next block adds 5 seconds - ) - .unwrap(); - assert_eq!(balance_timestamp, 190f32); - - helper.app.update_block(|bi| { - bi.height += 1; - bi.time = bi.time.plus_seconds(WEEK * 102); - }); - helper.unlock("user").unwrap(); - - // Ensure emissions voting power is 0 after unlock - let user_emissions_vp = helper - .query_user_emissions_vp_at("user", helper.app.block_info().time.seconds()) - .unwrap(); - assert_eq!(user_emissions_vp, 0.0); - - // Forward until after unlock period ends - helper.app.update_block(|bi| { - bi.height += 1; - bi.time = bi.time.plus_seconds(WEEK * 102); - }); - // Withdraw - helper.withdraw("user").unwrap(); - - // Now the users' balance is zero - // But one block before it had 190 xASTRO locked - let balance_timestamp = helper - .query_locked_balance_at( - "user", - Uint64::from(helper.app.block_info().time.seconds() + 5), // Next block adds 5 seconds - ) - .unwrap(); - assert_eq!(balance_timestamp, 0f32); - - let balance_timestamp = helper - .query_locked_balance_at( - "user", - Uint64::from(helper.app.block_info().time.seconds() - 5), // Next block adds 5 seconds - ) - .unwrap(); - assert_eq!(balance_timestamp, 190f32); - - // add users to the blacklist - helper - .update_blacklist( - vec![ - "voter1".to_string(), - "voter2".to_string(), - "voter3".to_string(), - "voter4".to_string(), - "voter5".to_string(), - "voter6".to_string(), - "voter7".to_string(), - "voter8".to_string(), - ], - vec![], - ) - .unwrap(); - - // query all blacklisted voters - let blacklisted_voters = helper.query_blacklisted_voters(None, None).unwrap(); - assert_eq!( - blacklisted_voters, - vec![ - Addr::unchecked("voter1"), - Addr::unchecked("voter2"), - Addr::unchecked("voter3"), - Addr::unchecked("voter4"), - Addr::unchecked("voter5"), - Addr::unchecked("voter6"), - Addr::unchecked("voter7"), - Addr::unchecked("voter8"), - ] - ); - - // query not blacklisted voter - let err = helper - .query_blacklisted_voters(Some("voter9".to_string()), Some(10u32)) - .unwrap_err(); - assert_eq!( - StdError::generic_err( - "Querier contract error: Generic error: The voter9 address is not blacklisted" - ), - err - ); - - // query voters by specified parameters - let blacklisted_voters = helper - .query_blacklisted_voters(Some("voter2".to_string()), Some(2u32)) - .unwrap(); - assert_eq!( - blacklisted_voters, - vec![Addr::unchecked("voter3"), Addr::unchecked("voter4")] - ); - - // add users to the blacklist - helper - .update_blacklist(vec!["voter0".to_string(), "voter33".to_string()], vec![]) - .unwrap(); - - // query voters by specified parameters - let blacklisted_voters = helper - .query_blacklisted_voters(Some("voter2".to_string()), Some(2u32)) - .unwrap(); - assert_eq!( - blacklisted_voters, - vec![Addr::unchecked("voter3"), Addr::unchecked("voter33")] - ); - - let blacklisted_voters = helper - .query_blacklisted_voters(Some("voter4".to_string()), Some(10u32)) - .unwrap(); - assert_eq!( - blacklisted_voters, - vec![ - Addr::unchecked("voter5"), - Addr::unchecked("voter6"), - Addr::unchecked("voter7"), - Addr::unchecked("voter8"), - ] - ); - - let empty_blacklist: Vec = vec![]; - let blacklisted_voters = helper - .query_blacklisted_voters(Some("voter8".to_string()), Some(10u32)) - .unwrap(); - assert_eq!(blacklisted_voters, empty_blacklist); - - // check if voters are blacklisted - let res = helper - .check_voters_are_blacklisted(vec!["voter1".to_string(), "voter9".to_string()]) - .unwrap(); - assert_eq!("Voter is not blacklisted: voter9", res.to_string()); - - let res = helper - .check_voters_are_blacklisted(vec!["voter1".to_string(), "voter8".to_string()]) - .unwrap(); - assert_eq!("Voters are blacklisted!", res.to_string()); -} - -#[test] -fn check_deposit_for() { - let mut helper = Helper::init(); - helper.mint_xastro("owner", 100); - - // Mint ASTRO, stake it and mint xASTRO - helper.mint_xastro("user1", 100); - helper.check_xastro_balance("user1", 100); - helper.mint_xastro("user2", 100); - helper.check_xastro_balance("user2", 100); - - // 104 weeks ~ 2 years - helper.create_lock("user1", 50f32).unwrap(); - let vp = helper.query_user_vp("user1").unwrap(); - assert_eq!(0.0, vp); - let vp = helper.query_user_emissions_vp("user1").unwrap(); - assert_eq!(50.0, vp); - - helper.deposit_for("user2", "user1", 50f32).unwrap(); - let vp = helper.query_user_vp("user1").unwrap(); - assert_eq!(0.0, vp); - let vp = helper.query_user_emissions_vp("user1").unwrap(); - assert_eq!(100.0, vp); - helper.check_xastro_balance("user1", 50); - helper.check_xastro_balance("user2", 50); -} - -#[test] -fn check_update_owner() { - let mut helper = Helper::init(); - - let new_owner = String::from("new_owner"); - - // New owner - let msg = ExecuteMsg::ProposeNewOwner { - new_owner: new_owner.clone(), - expires_in: 100, // seconds - }; - - // Unauthed check - let err = helper - .app - .execute_contract( - Addr::unchecked("not_owner"), - helper.vxastro.clone(), - &msg, - &[], - ) - .unwrap_err(); - assert_eq!(err.root_cause().to_string(), "Generic error: Unauthorized"); - - // Claim before proposal - let err = helper - .app - .execute_contract( - Addr::unchecked(new_owner.clone()), - helper.vxastro.clone(), - &ExecuteMsg::ClaimOwnership {}, - &[], - ) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Generic error: Ownership proposal not found" - ); - - // Propose new owner - helper - .app - .execute_contract(Addr::unchecked("owner"), helper.vxastro.clone(), &msg, &[]) - .unwrap(); - - // Claim from invalid addr - let err = helper - .app - .execute_contract( - Addr::unchecked("invalid_addr"), - helper.vxastro.clone(), - &ExecuteMsg::ClaimOwnership {}, - &[], - ) - .unwrap_err(); - assert_eq!(err.root_cause().to_string(), "Generic error: Unauthorized"); - - // Claim ownership - helper - .app - .execute_contract( - Addr::unchecked(new_owner.clone()), - helper.vxastro.clone(), - &ExecuteMsg::ClaimOwnership {}, - &[], - ) - .unwrap(); - - // Let's query the contract state - let msg = QueryMsg::Config {}; - let res: Config = helper - .app - .wrap() - .query_wasm_smart(&helper.vxastro, &msg) - .unwrap(); - - assert_eq!(res.owner, new_owner) -} - -#[test] -fn check_blacklist() { - let mut helper = Helper::init(); - - // Mint ASTRO, stake it and mint xASTRO - helper.mint_xastro("user1", 100); - helper.mint_xastro("user2", 100); - helper.mint_xastro("user3", 100); - - // Try to execute with empty arrays - let err = helper.update_blacklist(vec![], vec![]).unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Generic error: Append and remove arrays are empty" - ); - - // Blacklisting user2 - let res = helper - .update_blacklist(vec!["user2".to_string()], vec![]) - .unwrap(); - assert_eq!( - res.events[1].attributes[1], - attr("action", "update_blacklist") - ); - assert_eq!( - res.events[1].attributes[2], - attr("added_addresses", "user2") - ); - - helper.create_lock("user1", 50f32).unwrap(); - // Try to create lock from a blacklisted address - let err = helper.create_lock("user2", 100f32).unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "The user2 address is blacklisted" - ); - let err = helper.deposit_for("user2", "user3", 50f32).unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "The user2 address is blacklisted" - ); - - // Since user2 is blacklisted, their xASTRO balance was left unchanged - helper.check_xastro_balance("user2", 100); - // And they did not create a lock, thus we have no information to query - let vp = helper.query_user_vp("user2").unwrap(); - assert_eq!(vp, 0.0); - let vp = helper.query_user_emissions_vp("user2").unwrap(); - assert_eq!(vp, 0.0); - - // Go to the future - helper.app.update_block(next_block); - helper - .app - .update_block(|block| block.time = block.time.plus_seconds(2 * WEEK)); - - // user2 is still blacklisted - let err = helper.create_lock("user2", 100f32).unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "The user2 address is blacklisted" - ); - - // Blacklisting user1 using the guardian - let msg = ExecuteMsg::UpdateBlacklist { - append_addrs: vec!["user1".to_string()], - remove_addrs: vec![], - }; - let res = helper - .app - .execute_contract( - Addr::unchecked("guardian"), - helper.vxastro.clone(), - &msg, - &[], - ) - .unwrap(); - assert_eq!( - res.events[1].attributes[1], - attr("action", "update_blacklist") - ); - assert_eq!( - res.events[1].attributes[2], - attr("added_addresses", "user1") - ); - - let err = helper.extend_lock_amount("user1", 10f32).unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "The user1 address is blacklisted" - ); - let err = helper.deposit_for("user2", "user1", 50f32).unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "The user2 address is blacklisted" - ); - let err = helper.deposit_for("user3", "user1", 50f32).unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "The user1 address is blacklisted" - ); - // user1 doesn't have voting power now - let vp = helper.query_user_vp("user1").unwrap(); - assert_eq!(vp, 0.0); - let vp = helper.query_user_emissions_vp("user1").unwrap(); - assert_eq!(vp, 0.0); - // Voting - let vp = helper - .query_user_vp_at("user1", helper.app.block_info().time.seconds() - WEEK) - .unwrap(); - assert_eq!(vp, 0f32); - // Total voting power should be zero as well since there was only one vxASTRO position created by user1 - let vp = helper.query_total_vp().unwrap(); - assert_eq!(vp, 0.0); - // Total emissions voting power should be zero as well since there was only one vxASTRO position created by user1 - let vp = helper.query_total_emissions_vp().unwrap(); - assert_eq!(vp, 0.0); - - // The only option available for a blacklisted user is to unlock and withdraw their funds - helper.unlock("user1").unwrap(); - - // Go to the future - helper.app.update_block(next_block); - helper - .app - .update_block(|block| block.time = block.time.plus_seconds(20 * WEEK)); - - // The only option available for a blacklisted user is to withdraw their funds - helper.withdraw("user1").unwrap(); - - // Remove user1 from the blacklist - let res = helper - .update_blacklist(vec![], vec!["user1".to_string()]) - .unwrap(); - assert_eq!( - res.events[1].attributes[1], - attr("action", "update_blacklist") - ); - assert_eq!( - res.events[1].attributes[2], - attr("removed_addresses", "user1") - ); - - // Now user1 can create a new lock - helper.create_lock("user1", 10f32).unwrap(); -} - -#[test] -fn check_residual() { - let mut helper = Helper::init(); - let users_num = 1000; - let lock_amount = 100_000_000; - - helper.mint_xastro("owner", 100); - - for i in 1..(users_num / 2) { - let user = &format!("user{}", i); - helper.mint_xastro(user, 100); - helper.create_lock_u128(user, lock_amount).unwrap(); - } - - let mut sum = 0; - for i in 1..=users_num { - let user = &format!("user{}", i); - sum += helper.query_exact_user_vp(user).unwrap(); - } - - assert_eq!(sum, helper.query_exact_total_vp().unwrap()); - - let mut sum = 0; - for i in 1..=users_num { - let user = &format!("user{}", i); - sum += helper.query_exact_user_emissions_vp(user).unwrap(); - } - - assert_eq!(sum, helper.query_exact_total_emissions_vp().unwrap()); - - helper.app.update_block(|bi| { - bi.height += 1; - bi.time = bi.time.plus_seconds(WEEK); - }); - - for i in (users_num / 2)..users_num { - let user = &format!("user{}", i); - helper.mint_xastro(user, 1000000); - helper.create_lock_u128(user, lock_amount).unwrap(); - } - - for _ in 1..104 { - sum = 0; - for i in 1..=users_num { - let user = &format!("user{}", i); - sum += helper.query_exact_user_vp(user).unwrap(); - } - - let ve_vp = helper.query_exact_total_vp().unwrap(); - let diff = (sum as f64 - ve_vp as f64).abs(); - assert_eq!(diff, 0.0, "diff: {}, sum: {}, ve_vp: {}", diff, sum, ve_vp); - - helper.app.update_block(|bi| { - bi.height += 1; - bi.time = bi.time.plus_seconds(WEEK); - }); - } - - for _ in 1..104 { - sum = 0; - for i in 1..=users_num { - let user = &format!("user{}", i); - sum += helper.query_exact_user_emissions_vp(user).unwrap(); - } - - let ve_vp = helper.query_exact_total_emissions_vp().unwrap(); - let diff = (sum as f64 - ve_vp as f64).abs(); - assert_eq!(diff, 0.0, "diff: {}, sum: {}, ve_vp: {}", diff, sum, ve_vp); - - helper.app.update_block(|bi| { - bi.height += 1; - bi.time = bi.time.plus_seconds(WEEK); - }); - } -} - -#[test] -fn total_vp_multiple_slope_subtraction() { - let mut helper = Helper::init(); - - helper.mint_xastro("user1", 1000); - helper.create_lock("user1", 100f32).unwrap(); - let total = helper.query_total_vp().unwrap(); - assert_eq!(total, 0.0); - let total = helper.query_total_emissions_vp().unwrap(); - assert_eq!(total, 100.0); - - helper - .app - .update_block(|bi| bi.time = bi.time.plus_seconds(2 * WEEK)); - // Slope changes have been applied - let total = helper.query_total_vp().unwrap(); - assert_eq!(total, 0.0); - let total = helper.query_total_emissions_vp().unwrap(); - assert_eq!(total, 100.0); - - helper.unlock("user1").unwrap(); - - // Try to manipulate over expired lock 3 weeks later - helper - .app - .update_block(|bi| bi.time = bi.time.plus_seconds(3 * WEEK)); - - let err = helper.extend_lock_amount("user1", 100f32).unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "The lock expired. Withdraw and create new lock" - ); - - let err = helper.create_lock("user1", 100f32).unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Lock already exists, either unlock and withdraw or extend_lock to add to the lock" - ); - - let total = helper.query_total_vp().unwrap(); - assert_eq!(total, 0f32); - let total = helper.query_total_emissions_vp().unwrap(); - assert_eq!(total, 0f32); -} - -#[test] -fn marketing_info() { - let mut helper = Helper::init(); - - let err = helper - .app - .execute_contract( - helper.owner.clone(), - helper.vxastro.clone(), - &ExecuteMsg::SetLogoUrlsWhitelist { - whitelist: vec![ - "@hello-test-url .com/".to_string(), - "example.com/".to_string(), - ], - }, - &[], - ) - .unwrap_err(); - assert_eq!( - &err.root_cause().to_string(), - "Generic error: Link contains invalid characters: @hello-test-url .com/" - ); - - let err = helper - .app - .execute_contract( - helper.owner.clone(), - helper.vxastro.clone(), - &ExecuteMsg::SetLogoUrlsWhitelist { - whitelist: vec!["example.com".to_string()], - }, - &[], - ) - .unwrap_err(); - assert_eq!( - &err.root_cause().to_string(), - "Marketing info validation error: Whitelist link should end with '/': example.com" - ); - - helper - .app - .execute_contract( - helper.owner.clone(), - helper.vxastro.clone(), - &ExecuteMsg::SetLogoUrlsWhitelist { - whitelist: vec!["example.com/".to_string()], - }, - &[], - ) - .unwrap(); - - let err = helper - .app - .execute_contract( - helper.owner.clone(), - helper.vxastro.clone(), - &ExecuteMsg::UpdateMarketing { - project: Some("".to_string()), - description: None, - marketing: None, - }, - &[], - ) - .unwrap_err(); - - assert_eq!( - &err.root_cause().to_string(), - "Marketing info validation error: project contains invalid characters: " - ); - - let err = helper - .app - .execute_contract( - helper.owner.clone(), - helper.vxastro.clone(), - &ExecuteMsg::UpdateMarketing { - project: None, - description: Some("".to_string()), - marketing: None, - }, - &[], - ) - .unwrap_err(); - assert_eq!( - &err.root_cause().to_string(), - "Marketing info validation error: description contains invalid characters: " - ); - - helper - .app - .execute_contract( - helper.owner.clone(), - helper.vxastro.clone(), - &ExecuteMsg::UpdateMarketing { - project: Some("Some project".to_string()), - description: Some("Some description".to_string()), - marketing: None, - }, - &[], - ) - .unwrap(); - - let config: Config = helper - .app - .wrap() - .query_wasm_smart(&helper.vxastro, &QueryMsg::Config {}) - .unwrap(); - assert_eq!(config.logo_urls_whitelist, vec!["example.com/".to_string()]); - let marketing_info: MarketingInfoResponse = helper - .app - .wrap() - .query_wasm_smart(&helper.vxastro, &QueryMsg::MarketingInfo {}) - .unwrap(); - assert_eq!(marketing_info.project, Some("Some project".to_string())); - assert_eq!( - marketing_info.description, - Some("Some description".to_string()) - ); - - let err = helper - .app - .execute_contract( - helper.owner.clone(), - helper.vxastro.clone(), - &ExecuteMsg::UploadLogo(Logo::Url("https://some-website.com/logo.svg".to_string())), - &[], - ) - .unwrap_err(); - assert_eq!( - &err.root_cause().to_string(), - "Marketing info validation error: Logo link is not whitelisted: https://some-website.com/logo.svg", - ); - - helper - .app - .execute_contract( - helper.owner.clone(), - helper.vxastro.clone(), - &ExecuteMsg::UploadLogo(Logo::Url("example.com/logo.svg".to_string())), - &[], - ) - .unwrap(); - - let marketing_info: MarketingInfoResponse = helper - .app - .wrap() - .query_wasm_smart(&helper.vxastro, &QueryMsg::MarketingInfo {}) - .unwrap(); - assert_eq!( - marketing_info.logo.unwrap(), - LogoInfo::Url("example.com/logo.svg".to_string()) - ); -} diff --git a/packages/astroport-governance/src/lib.rs b/packages/astroport-governance/src/lib.rs index 8d1b8010..0467b164 100644 --- a/packages/astroport-governance/src/lib.rs +++ b/packages/astroport-governance/src/lib.rs @@ -9,7 +9,6 @@ pub mod interchain; pub mod outpost; pub mod utils; pub mod voting_escrow; -pub mod voting_escrow_lite; // Default pagination constants pub const DEFAULT_LIMIT: u32 = 30; diff --git a/packages/astroport-governance/src/voting_escrow_lite.rs b/packages/astroport-governance/src/voting_escrow_lite.rs deleted file mode 100644 index e66a8fc7..00000000 --- a/packages/astroport-governance/src/voting_escrow_lite.rs +++ /dev/null @@ -1,333 +0,0 @@ -use std::fmt; - -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Binary, QuerierWrapper, StdResult, Uint128, Uint64}; -use cw20::{BalanceResponse, DownloadLogoResponse, Logo, MarketingInfoResponse, TokenInfoResponse}; - -use crate::voting_escrow_lite::QueryMsg::{ - LockInfo, TotalVotingPower, TotalVotingPowerAt, UserDepositAt, UserEmissionsVotingPower, - UserVotingPower, UserVotingPowerAt, -}; - -/// ## Pagination settings -/// The maximum amount of items that can be read at once from -pub const MAX_LIMIT: u32 = 30; - -/// The default amount of items to read from -pub const DEFAULT_LIMIT: u32 = 10; - -pub const DEFAULT_PERIODS_LIMIT: u64 = 20; - -/// This structure stores marketing information for vxASTRO. -#[cw_serde] -pub struct UpdateMarketingInfo { - /// Project URL - pub project: Option, - /// Token description - pub description: Option, - /// Token marketing information - pub marketing: Option, - /// Token logo - pub logo: Option, -} - -/// This structure stores general parameters for the vxASTRO contract. -#[cw_serde] -pub struct InstantiateMsg { - /// The vxASTRO contract owner - pub owner: String, - /// Address that's allowed to black or whitelist contracts - pub guardian_addr: Option, - /// xASTRO token address - pub deposit_denom: String, - /// Marketing info for vxASTRO - pub marketing: Option, - /// The list of whitelisted logo urls prefixes - pub logo_urls_whitelist: Vec, - /// Address of the Generator controller to kick unlocked users - pub generator_controller_addr: Option, - /// Address of the Outpost to handle unlock remotely - pub outpost_addr: Option, -} - -/// This structure describes the execute functions in the contract. -#[cw_serde] -pub enum ExecuteMsg { - /// Create a vxASTRO position and lock xASTRO for `time` amount of time - CreateLock {}, - /// Deposit xASTRO in another user's vxASTRO position - DepositFor { user: String }, - /// Add more xASTRO to your vxASTRO position - ExtendLockAmount {}, - /// Unlock xASTRO from the vxASTRO contract - Unlock {}, - /// Relock all xASTRO from an unlocking position if the Hub could not be notified - Relock { user: String }, - /// Withdraw xASTRO from the vxASTRO contract - Withdraw {}, - /// Propose a new owner for the contract - ProposeNewOwner { new_owner: String, expires_in: u64 }, - /// Remove the ownership transfer proposal - DropOwnershipProposal {}, - /// Claim contract ownership - ClaimOwnership {}, - /// Add or remove accounts from the blacklist - UpdateBlacklist { - #[serde(default)] - append_addrs: Vec, - #[serde(default)] - remove_addrs: Vec, - }, - /// Update the marketing info for the vxASTRO contract - UpdateMarketing { - /// A URL pointing to the project behind this token - project: Option, - /// A longer description of the token and its utility. Designed for tooltips or such - description: Option, - /// The address (if any) that can update this data structure - marketing: Option, - }, - /// Upload a logo for vxASTRO - UploadLogo(Logo), - /// Update config - UpdateConfig { - new_guardian: Option, - generator_controller: Option, - outpost: Option, - }, - /// Set whitelisted logo urls - SetLogoUrlsWhitelist { whitelist: Vec }, -} - -/// This enum describes voters status. -#[cw_serde] -pub enum BlacklistedVotersResponse { - /// Voters are blacklisted - VotersBlacklisted {}, - /// Returns a voter that is not blacklisted. - VotersNotBlacklisted { voter: String }, -} - -impl fmt::Display for BlacklistedVotersResponse { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - BlacklistedVotersResponse::VotersBlacklisted {} => write!(f, "Voters are blacklisted!"), - BlacklistedVotersResponse::VotersNotBlacklisted { voter } => { - write!(f, "Voter is not blacklisted: {voter}") - } - } - } -} - -/// This structure describes the query messages available in the contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Checks if specified addresses are blacklisted - #[returns(BlacklistedVotersResponse)] - CheckVotersAreBlacklisted { voters: Vec }, - /// Return the blacklisted voters - #[returns(Vec)] - BlacklistedVoters { - start_after: Option, - limit: Option, - }, - /// Return the user's vxASTRO balance - #[returns(BalanceResponse)] - Balance { address: String }, - /// Fetch the vxASTRO token information - #[returns(TokenInfoResponse)] - TokenInfo {}, - /// Fetch vxASTRO's marketing information - #[returns(MarketingInfoResponse)] - MarketingInfo {}, - /// Download the vxASTRO logo - #[returns(DownloadLogoResponse)] - DownloadLogo {}, - /// Return the current total amount of vxASTRO - #[returns(VotingPowerResponse)] - TotalVotingPower {}, - /// Return the total amount of vxASTRO at some point in the past - #[returns(VotingPowerResponse)] - TotalVotingPowerAt { time: u64 }, - /// Return the total voting power at a specific period - #[returns(VotingPowerResponse)] - TotalVotingPowerAtPeriod { period: u64 }, - /// Return the user's current voting power (vxASTRO balance) - #[returns(VotingPowerResponse)] - UserVotingPower { user: String }, - /// Return the user's vxASTRO balance at some point in the past - #[returns(VotingPowerResponse)] - UserVotingPowerAt { user: String, time: u64 }, - /// Return the user's voting power at a specific period - #[returns(VotingPowerResponse)] - UserVotingPowerAtPeriod { user: String, period: u64 }, - - #[returns(VotingPowerResponse)] - TotalEmissionsVotingPower {}, - /// Return the total amount of vxASTRO at some point in the past - #[returns(VotingPowerResponse)] - TotalEmissionsVotingPowerAt { time: u64 }, - /// Return the user's current emission voting power - #[returns(VotingPowerResponse)] - UserEmissionsVotingPower { user: String }, - /// Return the user's emission voting power at some point in the past - #[returns(VotingPowerResponse)] - UserEmissionsVotingPowerAt { user: String, time: u64 }, - - #[returns(LockInfoResponse)] - LockInfo { user: String }, - /// Return user's locked xASTRO balance at the given timestamp - #[returns(Uint128)] - UserDepositAt { user: String, timestamp: Uint64 }, - /// Return the vxASTRO contract configuration - #[returns(Config)] - Config {}, -} - -/// This structure is used to return a user's amount of vxASTRO. -#[cw_serde] -pub struct VotingPowerResponse { - /// The vxASTRO balance - pub voting_power: Uint128, -} - -/// This structure is used to return the lock information for a vxASTRO position. -#[cw_serde] -pub struct LockInfoResponse { - /// The amount of xASTRO locked in the position - pub amount: Uint128, - /// Indicates the end of a lock period, if None the position is locked - pub end: Option, -} - -/// This structure stores the main parameters for the voting escrow contract. -#[cw_serde] -pub struct Config { - /// Address that's allowed to change contract parameters - pub owner: Addr, - /// Address that can only blacklist vxASTRO stakers and remove their governance power - pub guardian_addr: Option, - /// The xASTRO token contract address - pub deposit_denom: String, - /// The list of whitelisted logo urls prefixes - pub logo_urls_whitelist: Vec, - /// Minimum unlock wait time in seconds - pub unlock_period: u64, - /// Address of the Generator controller to kick unlocked users - pub generator_controller_addr: Option, - /// Address of the Outpost to handle unlock remotely - pub outpost_addr: Option, -} - -/// This structure describes a Migration message. -#[cw_serde] -pub struct MigrateMsg { - pub params: Binary, -} - -/// Queries current user's deposit from the voting escrow contract. -/// -/// * **user** staker for which we fetch the latest xASTRO deposits. -/// -/// * **timestamp** timestamp to fetch deposits at. -pub fn get_user_deposit_at_time( - querier: &QuerierWrapper, - escrow_addr: impl Into, - user: impl Into, - timestamp: u64, -) -> StdResult { - let balance = querier.query_wasm_smart( - escrow_addr, - &UserDepositAt { - user: user.into(), - timestamp: Uint64::from(timestamp), - }, - )?; - Ok(balance) -} - -/// Queries current user's voting power from the voting escrow contract. -/// -/// * **user** staker for which we calculate the latest vxASTRO voting power. -pub fn get_voting_power( - querier: &QuerierWrapper, - escrow_addr: impl Into, - user: impl Into, -) -> StdResult { - let vp: VotingPowerResponse = - querier.query_wasm_smart(escrow_addr, &UserVotingPower { user: user.into() })?; - Ok(vp.voting_power) -} - -/// Queries current user's emissions voting power from the voting escrow contract. -/// -/// * **user** staker for which we calculate the latest vxASTRO voting power. -pub fn get_emissions_voting_power( - querier: &QuerierWrapper, - escrow_addr: impl Into, - user: impl Into, -) -> StdResult { - let vp: VotingPowerResponse = - querier.query_wasm_smart(escrow_addr, &UserEmissionsVotingPower { user: user.into() })?; - Ok(vp.voting_power) -} - -/// Queries current user's voting power from the voting escrow contract by timestamp. -/// -/// * **user** staker for which we calculate the voting power at a specific time. -/// -/// * **timestamp** timestamp at which we calculate the staker's voting power. -pub fn get_voting_power_at( - querier: &QuerierWrapper, - escrow_addr: impl Into, - user: impl Into, - timestamp: u64, -) -> StdResult { - let vp: VotingPowerResponse = querier.query_wasm_smart( - escrow_addr, - &UserVotingPowerAt { - user: user.into(), - time: timestamp, - }, - )?; - - Ok(vp.voting_power) -} - -/// Queries current total voting power from the voting escrow contract. -pub fn get_total_voting_power( - querier: &QuerierWrapper, - escrow_addr: impl Into, -) -> StdResult { - let vp: VotingPowerResponse = querier.query_wasm_smart(escrow_addr, &TotalVotingPower {})?; - - Ok(vp.voting_power) -} - -/// Queries total voting power from the voting escrow contract by timestamp. -/// -/// * **timestamp** time at which we fetch the total voting power. -pub fn get_total_voting_power_at( - querier: &QuerierWrapper, - escrow_addr: impl Into, - timestamp: u64, -) -> StdResult { - let vp: VotingPowerResponse = - querier.query_wasm_smart(escrow_addr, &TotalVotingPowerAt { time: timestamp })?; - - Ok(vp.voting_power) -} - -/// Queries user's lockup information from the voting escrow contract. -/// -/// * **user** staker for which we return lock position information. -pub fn get_lock_info( - querier: &QuerierWrapper, - escrow_addr: impl Into, - user: impl Into, -) -> StdResult { - let lock_info: LockInfoResponse = - querier.query_wasm_smart(escrow_addr, &LockInfo { user: user.into() })?; - Ok(lock_info) -} diff --git a/packages/astroport-tests-lite/Cargo.toml b/packages/astroport-tests-lite/Cargo.toml deleted file mode 100644 index 6c27a081..00000000 --- a/packages/astroport-tests-lite/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "astroport-tests-lite" -version = "1.0.0" -authors = ["Astroport"] -edition = "2021" -repository = "https://github.com/astroport-fi/astroport-governance" -homepage = "https://astroport.fi" - -[features] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] - -[dependencies] -cw2 = "0.15" -cw20 = "0.15" -cosmwasm-std = "1.1" - -cosmwasm-schema = "1.1" -cw-multi-test = "0.16" -astroport = { git = "https://github.com/astroport-fi/hidden_astroport_core" } - -astroport-escrow-fee-distributor = { path = "../../contracts/escrow_fee_distributor" } -astroport-governance = { path = "../astroport-governance" } -voting-escrow-lite = { package = "astroport-voting-escrow-lite", path = "../../contracts/voting_escrow_lite" } -generator-controller-lite = { path = "../../contracts/generator_controller_lite" } -astro-assembly = { path = "../../contracts/assembly" } -astroport-generator = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -astroport-pair = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -astroport-factory = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -astroport-token = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -astroport-staking = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -astroport-whitelist = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -anyhow = "1" diff --git a/packages/astroport-tests-lite/src/address_generator.rs b/packages/astroport-tests-lite/src/address_generator.rs deleted file mode 100644 index 1ea21b9b..00000000 --- a/packages/astroport-tests-lite/src/address_generator.rs +++ /dev/null @@ -1,19 +0,0 @@ -use std::cell::Cell; - -use cosmwasm_std::{Addr, Storage}; -use cw_multi_test::AddressGenerator; - -/// Defines a custom address generator that creates simple addresses that -/// always use the format wasm1xxxxx to conform to Cosmos address formats -#[derive(Default)] -pub struct WasmAddressGenerator { - address_counter: Cell, -} - -impl AddressGenerator for WasmAddressGenerator { - fn next_address(&self, _: &mut dyn Storage) -> Addr { - let contract_number = self.address_counter.get() + 1; - self.address_counter.set(contract_number); - Addr::unchecked(format!("wasm1contract{}", contract_number)) - } -} diff --git a/packages/astroport-tests-lite/src/base.rs b/packages/astroport-tests-lite/src/base.rs deleted file mode 100644 index adfdd7cd..00000000 --- a/packages/astroport-tests-lite/src/base.rs +++ /dev/null @@ -1,359 +0,0 @@ -use cosmwasm_schema::cw_serde; - -use astroport::staking; -use astroport::token::InstantiateMsg as AstroTokenInstantiateMsg; -use astroport_governance::escrow_fee_distributor::InstantiateMsg as EscrowFeeDistributorInstantiateMsg; -use astroport_governance::voting_escrow_lite::{ - Cw20HookMsg, ExecuteMsg, InstantiateMsg as AstroVotingEscrowInstantiateMsg, QueryMsg, - VotingPowerResponse, -}; -use cosmwasm_std::{attr, to_json_binary, Addr, QueryRequest, StdResult, Uint128, WasmQuery}; -use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg, MinterResponse}; - -use anyhow::Result; -use cw_multi_test::{App, AppResponse, ContractWrapper, Executor}; - -pub const MULTIPLIER: u64 = 1_000_000; - -#[cw_serde] -pub struct ContractInfo { - pub address: Addr, - pub code_id: u64, -} - -#[cw_serde] -pub struct BaseAstroportTestPackage { - pub owner: Addr, - pub astro_token: Option, - pub escrow_fee_distributor: Option, - pub staking: Option, - pub voting_escrow: Option, -} - -#[cw_serde] -pub struct BaseAstroportTestInitMessage { - pub owner: Addr, -} - -impl BaseAstroportTestPackage { - pub fn init_all(router: &mut App, msg: BaseAstroportTestInitMessage) -> Self { - let mut base_pack = BaseAstroportTestPackage { - owner: msg.owner.clone(), - astro_token: None, - escrow_fee_distributor: None, - staking: None, - voting_escrow: None, - }; - - base_pack.init_astro_token(router, msg.owner.clone()); - base_pack.init_staking(router, msg.owner.clone()); - base_pack.init_voting_escrow(router, msg.owner.clone()); - base_pack.init_escrow_fee_distributor(router, msg.owner); - base_pack - } - - fn init_astro_token(&mut self, router: &mut App, owner: Addr) { - let astro_token_contract = Box::new(ContractWrapper::new_with_empty( - astroport_token::contract::execute, - astroport_token::contract::instantiate, - astroport_token::contract::query, - )); - - let astro_token_code_id = router.store_code(astro_token_contract); - - let init_msg = AstroTokenInstantiateMsg { - name: String::from("Astro token"), - symbol: String::from("ASTRO"), - decimals: 6, - initial_balances: vec![], - mint: Some(MinterResponse { - minter: owner.to_string(), - cap: None, - }), - marketing: None, - }; - - let astro_token_instance = router - .instantiate_contract( - astro_token_code_id, - owner, - &init_msg, - &[], - "Astro token", - None, - ) - .unwrap(); - - self.astro_token = Some(ContractInfo { - address: astro_token_instance, - code_id: astro_token_code_id, - }) - } - - fn init_staking(&mut self, router: &mut App, owner: Addr) { - let staking_contract = Box::new( - ContractWrapper::new_with_empty( - astroport_staking::contract::execute, - astroport_staking::contract::instantiate, - astroport_staking::contract::query, - ) - .with_reply_empty(astroport_staking::contract::reply), - ); - - let staking_code_id = router.store_code(staking_contract); - - let msg = staking::InstantiateMsg { - owner: owner.to_string(), - token_code_id: self.astro_token.clone().unwrap().code_id, - deposit_token_addr: self.astro_token.clone().unwrap().address.to_string(), - marketing: None, - }; - - let staking_instance = router - .instantiate_contract( - staking_code_id, - owner, - &msg, - &[], - String::from("xASTRO"), - None, - ) - .unwrap(); - - self.staking = Some(ContractInfo { - address: staking_instance, - code_id: staking_code_id, - }) - } - - pub fn get_staking_xastro(&self, router: &App) -> Addr { - let res = router - .wrap() - .query::(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: self.staking.clone().unwrap().address.to_string(), - msg: to_json_binary(&staking::QueryMsg::Config {}).unwrap(), - })) - .unwrap(); - - res.share_token_addr - } - - fn init_voting_escrow(&mut self, router: &mut App, owner: Addr) { - let voting_contract = Box::new(ContractWrapper::new_with_empty( - voting_escrow_lite::execute::execute, - voting_escrow_lite::contract::instantiate, - voting_escrow_lite::query::query, - )); - - let voting_code_id = router.store_code(voting_contract); - - let msg = AstroVotingEscrowInstantiateMsg { - guardian_addr: Some("guardian".to_string()), - marketing: None, - owner: owner.to_string(), - deposit_denom: self.get_staking_xastro(router).to_string(), - logo_urls_whitelist: vec![], - generator_controller_addr: None, - outpost_addr: None, - }; - - let voting_instance = router - .instantiate_contract( - voting_code_id, - owner, - &msg, - &[], - String::from("vxASTRO"), - None, - ) - .unwrap(); - - self.voting_escrow = Some(ContractInfo { - address: voting_instance, - code_id: voting_code_id, - }) - } - - pub fn init_escrow_fee_distributor(&mut self, router: &mut App, owner: Addr) { - let escrow_fee_distributor_contract = Box::new(ContractWrapper::new_with_empty( - astroport_escrow_fee_distributor::contract::execute, - astroport_escrow_fee_distributor::contract::instantiate, - astroport_escrow_fee_distributor::contract::query, - )); - - let escrow_fee_distributor_code_id = router.store_code(escrow_fee_distributor_contract); - - let init_msg = EscrowFeeDistributorInstantiateMsg { - owner: owner.to_string(), - astro_token: self.astro_token.clone().unwrap().address.to_string(), - voting_escrow_addr: self.voting_escrow.clone().unwrap().address.to_string(), - claim_many_limit: None, - is_claim_disabled: None, - }; - - let escrow_fee_distributor_instance = router - .instantiate_contract( - escrow_fee_distributor_code_id, - owner, - &init_msg, - &[], - "Astroport escrow fee distributor", - None, - ) - .unwrap(); - - self.escrow_fee_distributor = Some(ContractInfo { - address: escrow_fee_distributor_instance, - code_id: escrow_fee_distributor_code_id, - }) - } - - pub fn create_lock( - &self, - router: &mut App, - user: Addr, - time: u64, - amount: u64, - ) -> Result { - let amount = amount * MULTIPLIER; - let cw20msg = Cw20ExecuteMsg::Send { - contract: self.voting_escrow.clone().unwrap().address.to_string(), - amount: Uint128::from(amount), - msg: to_json_binary(&Cw20HookMsg::CreateLock { time }).unwrap(), - }; - - router.execute_contract(user, self.get_staking_xastro(router), &cw20msg, &[]) - } - - pub fn extend_lock_amount( - &mut self, - router: &mut App, - user: &str, - amount: u64, - ) -> Result { - let amount = amount * MULTIPLIER; - let cw20msg = Cw20ExecuteMsg::Send { - contract: self.voting_escrow.clone().unwrap().address.to_string(), - amount: Uint128::from(amount), - msg: to_json_binary(&Cw20HookMsg::ExtendLockAmount {}).unwrap(), - }; - router.execute_contract( - Addr::unchecked(user), - self.get_staking_xastro(router), - &cw20msg, - &[], - ) - } - - pub fn withdraw(&self, router: &mut App, user: &str) -> Result { - router.execute_contract( - Addr::unchecked(user), - self.voting_escrow.clone().unwrap().address, - &ExecuteMsg::Withdraw {}, - &[], - ) - } - - pub fn query_user_vp(&self, router: &mut App, user: Addr) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.voting_escrow.clone().unwrap().address, - &QueryMsg::UserVotingPower { - user: user.to_string(), - }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_user_vp_at(&self, router: &mut App, user: Addr, time: u64) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.voting_escrow.clone().unwrap().address, - &QueryMsg::UserVotingPowerAt { - user: user.to_string(), - time, - }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_total_vp(&self, router: &mut App) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.voting_escrow.clone().unwrap().address, - &QueryMsg::TotalVotingPower {}, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_total_vp_at(&self, router: &mut App, time: u64) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.voting_escrow.clone().unwrap().address, - &QueryMsg::TotalVotingPowerAt { time }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } -} - -pub fn mint(router: &mut App, owner: Addr, token_instance: Addr, to: &Addr, amount: u128) { - let amount = amount * MULTIPLIER as u128; - let msg = cw20::Cw20ExecuteMsg::Mint { - recipient: to.to_string(), - amount: Uint128::from(amount), - }; - - let res = router - .execute_contract(owner, token_instance, &msg, &[]) - .unwrap(); - assert_eq!(res.events[1].attributes[1], attr("action", "mint")); - assert_eq!(res.events[1].attributes[2], attr("to", String::from(to))); - assert_eq!( - res.events[1].attributes[3], - attr("amount", Uint128::from(amount)) - ); -} - -pub fn check_balance(app: &mut App, token_addr: &Addr, contract_addr: &Addr, expected: u128) { - let msg = Cw20QueryMsg::Balance { - address: contract_addr.to_string(), - }; - let res: StdResult = app.wrap().query_wasm_smart(token_addr, &msg); - assert_eq!(res.unwrap().balance, Uint128::from(expected)); -} - -pub fn increase_allowance( - router: &mut App, - owner: Addr, - spender: Addr, - token: Addr, - amount: Uint128, -) { - let msg = cw20::Cw20ExecuteMsg::IncreaseAllowance { - spender: spender.to_string(), - amount, - expires: None, - }; - - let res = router - .execute_contract(owner.clone(), token, &msg, &[]) - .unwrap(); - - assert_eq!( - res.events[1].attributes[1], - attr("action", "increase_allowance") - ); - assert_eq!( - res.events[1].attributes[2], - attr("owner", owner.to_string()) - ); - assert_eq!( - res.events[1].attributes[3], - attr("spender", spender.to_string()) - ); - assert_eq!(res.events[1].attributes[4], attr("amount", amount)); -} diff --git a/packages/astroport-tests-lite/src/controller_helper.rs b/packages/astroport-tests-lite/src/controller_helper.rs deleted file mode 100644 index bc202b61..00000000 --- a/packages/astroport-tests-lite/src/controller_helper.rs +++ /dev/null @@ -1,494 +0,0 @@ -use crate::escrow_helper::EscrowHelper; -use anyhow::Result as AnyResult; -use astroport::asset::{AssetInfo, PairInfo}; -use astroport::factory::{PairConfig, PairType}; - -use astroport_governance::assembly::{DEPOSIT_INTERVAL, VOTING_PERIOD_INTERVAL}; -use astroport_governance::generator_controller_lite::{ - ConfigResponse, ExecuteMsg, NetworkInfo, QueryMsg, -}; -use cosmwasm_std::{Addr, Decimal, StdResult, Uint128}; -use cw_multi_test::{App, AppResponse, ContractWrapper, Executor}; -use generator_controller_lite::state::{UserInfo, VotedPoolInfo}; - -const PROPOSAL_VOTING_PERIOD: u64 = *VOTING_PERIOD_INTERVAL.start(); -const PROPOSAL_EFFECTIVE_DELAY: u64 = 12_342; -const PROPOSAL_EXPIRATION_PERIOD: u64 = 86_399; -const PROPOSAL_REQUIRED_DEPOSIT: u128 = *DEPOSIT_INTERVAL.start(); -const PROPOSAL_REQUIRED_QUORUM: &str = "0.50"; -const PROPOSAL_REQUIRED_THRESHOLD: &str = "0.60"; - -pub struct ControllerHelper { - pub owner: String, - pub generator: Addr, - pub controller: Addr, - pub factory: Addr, - pub escrow_helper: EscrowHelper, -} - -impl ControllerHelper { - pub fn init(router: &mut App, owner: &Addr, hub_addr: Option) -> Self { - let escrow_helper = EscrowHelper::init(router, owner.clone()); - - let pair_contract = Box::new( - ContractWrapper::new_with_empty( - astroport_pair::contract::execute, - astroport_pair::contract::instantiate, - astroport_pair::contract::query, - ) - .with_reply_empty(astroport_pair::contract::reply), - ); - - let pair_code_id = router.store_code(pair_contract); - - let factory_contract = Box::new( - ContractWrapper::new_with_empty( - astroport_factory::contract::execute, - astroport_factory::contract::instantiate, - astroport_factory::contract::query, - ) - .with_reply_empty(astroport_factory::contract::reply), - ); - - let factory_code_id = router.store_code(factory_contract); - - let whitelist_code_id = store_whitelist_code(router); - - let msg = astroport::factory::InstantiateMsg { - pair_configs: vec![PairConfig { - code_id: pair_code_id, - pair_type: PairType::Xyk {}, - total_fee_bps: 100, - maker_fee_bps: 10, - is_disabled: false, - is_generator_disabled: false, - permissioned: false, - }], - token_code_id: escrow_helper.astro_token_code_id, - fee_address: None, - generator_address: None, - owner: owner.to_string(), - whitelist_code_id, - coin_registry_address: Addr::unchecked("coin_registry").to_string(), - }; - - let factory = router - .instantiate_contract(factory_code_id, owner.clone(), &msg, &[], "Factory", None) - .unwrap(); - - let generator_contract = Box::new( - ContractWrapper::new_with_empty( - astroport_generator::contract::execute, - astroport_generator::contract::instantiate, - astroport_generator::contract::query, - ) - .with_reply_empty(astroport_generator::contract::reply), - ); - - let generator_code_id = router.store_code(generator_contract); - let init_msg = astroport::generator::InstantiateMsg { - owner: owner.to_string(), - factory: factory.to_string(), - generator_controller: None, - guardian: None, - astro_token: AssetInfo::NativeToken { - denom: escrow_helper.astro_token.to_string(), - }, - tokens_per_block: Default::default(), - start_block: Default::default(), - vesting_contract: "vesting_placeholder".to_string(), - whitelist_code_id, - voting_escrow: None, - voting_escrow_delegation: None, - }; - - let generator = router - .instantiate_contract( - generator_code_id, - owner.clone(), - &init_msg, - &[], - String::from("Generator"), - None, - ) - .unwrap(); - - let assembly_contract = Box::new(ContractWrapper::new_with_empty( - astro_assembly::contract::execute, - astro_assembly::contract::instantiate, - astro_assembly::queries::query, - )); - - let assembly_code = router.store_code(assembly_contract); - - let assembly_default_instantiate_msg = astroport_governance::assembly::InstantiateMsg { - staking_addr: escrow_helper.staking_instance.to_string(), - vxastro_token_addr: None, - voting_escrow_delegator_addr: None, - ibc_controller: None, - generator_controller_addr: None, - hub_addr: None, - builder_unlock_addr: "nocontract".to_string(), - proposal_voting_period: PROPOSAL_VOTING_PERIOD, - proposal_effective_delay: PROPOSAL_EFFECTIVE_DELAY, - proposal_expiration_period: PROPOSAL_EXPIRATION_PERIOD, - proposal_required_deposit: Uint128::from(PROPOSAL_REQUIRED_DEPOSIT), - proposal_required_quorum: String::from(PROPOSAL_REQUIRED_QUORUM), - proposal_required_threshold: String::from(PROPOSAL_REQUIRED_THRESHOLD), - whitelisted_links: vec!["https://some.link/".to_string()], - }; - - let assembly_instance = router - .instantiate_contract( - assembly_code, - owner.clone(), - &assembly_default_instantiate_msg, - &[], - "Assembly".to_string(), - Some(owner.to_string()), - ) - .unwrap(); - - let controller_contract = Box::new(ContractWrapper::new_with_empty( - generator_controller_lite::contract::execute, - generator_controller_lite::contract::instantiate, - generator_controller_lite::contract::query, - )); - - let controller_code_id = router.store_code(controller_contract); - let init_msg = astroport_governance::generator_controller_lite::InstantiateMsg { - owner: owner.to_string(), - escrow_addr: escrow_helper.escrow_instance.to_string(), - generator_addr: generator.to_string(), - factory_addr: factory.to_string(), - pools_limit: 5, - whitelisted_pools: vec![], - assembly_addr: assembly_instance.to_string(), - hub_addr, - }; - - let controller = router - .instantiate_contract( - controller_code_id, - owner.clone(), - &init_msg, - &[], - String::from("Controller"), - None, - ) - .unwrap(); - - // Update the vxASTRO instance to include the controller - router - .execute_contract( - owner.clone(), - escrow_helper.escrow_instance.clone(), - &astroport_governance::voting_escrow_lite::ExecuteMsg::UpdateConfig { - new_guardian: None, - generator_controller: Some(controller.to_string()), - outpost: None, - }, - &[], - ) - .unwrap(); - - // Setup controller in generator contract - router - .execute_contract( - owner.clone(), - generator.clone(), - &astroport::generator::ExecuteMsg::UpdateConfig { - vesting_contract: None, - generator_controller: Some(controller.to_string()), - guardian: None, - checkpoint_generator_limit: None, - voting_escrow: None, - voting_escrow_delegation: None, - }, - &[], - ) - .unwrap(); - - Self { - owner: owner.to_string(), - generator, - controller, - factory, - escrow_helper, - } - } - - pub fn init_cw20_token(&self, router: &mut App, name: &str) -> AnyResult { - let msg = astroport::token::InstantiateMsg { - name: name.to_string(), - symbol: name.to_string(), - decimals: 6, - initial_balances: vec![], - mint: None, - marketing: None, - }; - - router.instantiate_contract( - self.escrow_helper.astro_token_code_id, - Addr::unchecked(self.owner.clone()), - &msg, - &[], - name.to_string(), - None, - ) - } - - pub fn create_pool(&self, router: &mut App, token1: &Addr, token2: &Addr) -> AnyResult { - let asset_infos = vec![ - AssetInfo::Token { - contract_addr: token1.clone(), - }, - AssetInfo::Token { - contract_addr: token2.clone(), - }, - ]; - - router.execute_contract( - Addr::unchecked(self.owner.clone()), - self.factory.clone(), - &astroport::factory::ExecuteMsg::CreatePair { - pair_type: PairType::Xyk {}, - asset_infos: asset_infos.to_vec(), - init_params: None, - }, - &[], - )?; - - let res: PairInfo = router.wrap().query_wasm_smart( - self.factory.clone(), - &astroport::factory::QueryMsg::Pair { - asset_infos: asset_infos.to_vec(), - }, - )?; - - Ok(res.liquidity_token) - } - - pub fn create_pool_with_tokens( - &self, - router: &mut App, - name1: &str, - name2: &str, - ) -> AnyResult { - let token1 = self.init_cw20_token(router, name1).unwrap(); - let token2 = self.init_cw20_token(router, name2).unwrap(); - - self.create_pool(router, &token1, &token2) - } - - pub fn vote( - &self, - router: &mut App, - user: &str, - votes: Vec<(impl Into, u16)>, - ) -> AnyResult { - let msg = ExecuteMsg::Vote { - votes: votes - .into_iter() - .map(|(pool, apoints)| (pool.into(), apoints)) - .collect(), - }; - - router.execute_contract(Addr::unchecked(user), self.controller.clone(), &msg, &[]) - } - - pub fn outpost_vote( - &self, - router: &mut App, - sender: &str, - voter: String, - voting_power: Uint128, - votes: Vec<(impl Into, u16)>, - ) -> AnyResult { - let msg = ExecuteMsg::OutpostVote { - voter, - voting_power, - votes: votes - .into_iter() - .map(|(pool, apoints)| (pool.into(), apoints)) - .collect(), - }; - - router.execute_contract(Addr::unchecked(sender), self.controller.clone(), &msg, &[]) - } - - pub fn tune(&self, router: &mut App) -> AnyResult { - router.execute_contract( - Addr::unchecked("anyone"), - self.controller.clone(), - &ExecuteMsg::TunePools {}, - &[], - ) - } - - pub fn kick_holders( - &self, - router: &mut App, - user: &str, - blacklisted_voters: Vec, - ) -> AnyResult { - router.execute_contract( - Addr::unchecked(user), - self.controller.clone(), - &ExecuteMsg::KickBlacklistedVoters { blacklisted_voters }, - &[], - ) - } - - pub fn kick_unlocked_holders( - &self, - router: &mut App, - user: &str, - unlocked_voters: Vec, - ) -> AnyResult { - router.execute_contract( - Addr::unchecked(user), - self.controller.clone(), - &ExecuteMsg::KickUnlockedVoters { unlocked_voters }, - &[], - ) - } - - pub fn kick_unlocked_outpost_holders( - &self, - router: &mut App, - user: &str, - unlocked_voter: String, - ) -> AnyResult { - router.execute_contract( - Addr::unchecked(user), - self.controller.clone(), - &ExecuteMsg::KickUnlockedOutpostVoter { unlocked_voter }, - &[], - ) - } - - pub fn update_blacklisted_limit( - &self, - router: &mut App, - user: &str, - kick_voters_limit: Option, - ) -> AnyResult { - router.execute_contract( - Addr::unchecked(user), - self.controller.clone(), - &ExecuteMsg::UpdateConfig { - kick_voters_limit, - main_pool: None, - main_pool_min_alloc: None, - remove_main_pool: None, - assembly_addr: None, - hub_addr: None, - }, - &[], - ) - } - - pub fn update_main_pool( - &self, - router: &mut App, - user: &str, - main_pool: Option<&Addr>, - main_pool_min_alloc: Option, - remove_main_pool: bool, - ) -> AnyResult { - let remove_main_pool = if remove_main_pool { Some(true) } else { None }; - router.execute_contract( - Addr::unchecked(user), - self.controller.clone(), - &ExecuteMsg::UpdateConfig { - kick_voters_limit: None, - main_pool: main_pool.map(|p| p.to_string()), - main_pool_min_alloc, - remove_main_pool, - assembly_addr: None, - hub_addr: None, - }, - &[], - ) - } - - pub fn update_whitelist( - &self, - router: &mut App, - user: &str, - add_pools: Option>, - remove_pools: Option>, - ) -> AnyResult { - let msg = ExecuteMsg::UpdateWhitelist { - add: add_pools, - remove: remove_pools, - }; - - router.execute_contract(Addr::unchecked(user), self.controller.clone(), &msg, &[]) - } - - pub fn update_networks( - &self, - router: &mut App, - user: &str, - add_networks: Option>, - remove_networks: Option>, - ) -> AnyResult { - let msg = ExecuteMsg::UpdateNetworks { - add: add_networks, - remove: remove_networks, - }; - - router.execute_contract(Addr::unchecked(user), self.controller.clone(), &msg, &[]) - } - - pub fn query_user_info(&self, router: &mut App, user: &str) -> StdResult { - router.wrap().query_wasm_smart( - self.controller.clone(), - &QueryMsg::UserInfo { - user: user.to_string(), - }, - ) - } - - pub fn query_voted_pool_info(&self, router: &mut App, pool: &str) -> StdResult { - router.wrap().query_wasm_smart( - self.controller.clone(), - &QueryMsg::PoolInfo { - pool_addr: pool.to_string(), - }, - ) - } - - pub fn query_voted_pool_info_at_period( - &self, - router: &mut App, - pool: &str, - period: u64, - ) -> StdResult { - router.wrap().query_wasm_smart( - self.controller.clone(), - &QueryMsg::PoolInfoAtPeriod { - pool_addr: pool.to_string(), - period, - }, - ) - } - - pub fn query_config(&self, router: &mut App) -> StdResult { - router - .wrap() - .query_wasm_smart(self.controller.clone(), &QueryMsg::Config {}) - } -} - -fn store_whitelist_code(app: &mut App) -> u64 { - let whitelist_contract = Box::new(ContractWrapper::new_with_empty( - astroport_whitelist::contract::execute, - astroport_whitelist::contract::instantiate, - astroport_whitelist::contract::query, - )); - - app.store_code(whitelist_contract) -} diff --git a/packages/astroport-tests-lite/src/escrow_helper.rs b/packages/astroport-tests-lite/src/escrow_helper.rs deleted file mode 100644 index a22ded4b..00000000 --- a/packages/astroport-tests-lite/src/escrow_helper.rs +++ /dev/null @@ -1,397 +0,0 @@ -use anyhow::Result; -use astroport::{staking as xastro, token as astro}; -use astroport_governance::voting_escrow_lite::{ - Cw20HookMsg, ExecuteMsg, InstantiateMsg, LockInfoResponse, QueryMsg, VotingPowerResponse, -}; -use cosmwasm_std::{attr, to_json_binary, Addr, QueryRequest, StdResult, Uint128, WasmQuery}; -use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg, MinterResponse}; -use cw_multi_test::{App, AppResponse, ContractWrapper, Executor}; - -pub const MULTIPLIER: u64 = 1000000; - -pub struct EscrowHelper { - pub owner: Addr, - pub astro_token: Addr, - pub staking_instance: Addr, - pub xastro_token: Addr, - pub escrow_instance: Addr, - pub astro_token_code_id: u64, -} - -impl EscrowHelper { - pub fn init(router: &mut App, owner: Addr) -> Self { - let astro_token_contract = Box::new(ContractWrapper::new_with_empty( - astroport_token::contract::execute, - astroport_token::contract::instantiate, - astroport_token::contract::query, - )); - - let astro_token_code_id = router.store_code(astro_token_contract); - - let msg = astro::InstantiateMsg { - name: String::from("Astro token"), - symbol: String::from("ASTRO"), - decimals: 6, - initial_balances: vec![], - mint: Some(MinterResponse { - minter: owner.to_string(), - cap: None, - }), - marketing: None, - }; - - let astro_token = router - .instantiate_contract( - astro_token_code_id, - owner.clone(), - &msg, - &[], - String::from("ASTRO"), - None, - ) - .unwrap(); - - let staking_contract = Box::new( - ContractWrapper::new_with_empty( - astroport_staking::contract::execute, - astroport_staking::contract::instantiate, - astroport_staking::contract::query, - ) - .with_reply_empty(astroport_staking::contract::reply), - ); - - let staking_code_id = router.store_code(staking_contract); - - let msg = xastro::InstantiateMsg { - owner: owner.to_string(), - token_code_id: astro_token_code_id, - deposit_token_addr: astro_token.to_string(), - marketing: None, - }; - let staking_instance = router - .instantiate_contract( - staking_code_id, - owner.clone(), - &msg, - &[], - String::from("xASTRO"), - None, - ) - .unwrap(); - - let res = router - .wrap() - .query::(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: staking_instance.to_string(), - msg: to_json_binary(&xastro::QueryMsg::Config {}).unwrap(), - })) - .unwrap(); - - let voting_contract = Box::new(ContractWrapper::new_with_empty( - voting_escrow_lite::execute::execute, - voting_escrow_lite::contract::instantiate, - voting_escrow_lite::query::query, - )); - - let voting_code_id = router.store_code(voting_contract); - - let msg = InstantiateMsg { - owner: owner.to_string(), - guardian_addr: Some("guardian".to_string()), - deposit_denom: res.share_token_addr.to_string(), - marketing: None, - logo_urls_whitelist: vec![], - generator_controller_addr: None, - outpost_addr: None, - }; - let voting_instance = router - .instantiate_contract( - voting_code_id, - owner.clone(), - &msg, - &[], - String::from("vxASTRO"), - None, - ) - .unwrap(); - - Self { - owner, - xastro_token: res.share_token_addr, - astro_token, - staking_instance, - escrow_instance: voting_instance, - astro_token_code_id, - } - } - - pub fn mint_xastro(&self, router: &mut App, to: &str, amount: u64) { - let amount = amount * MULTIPLIER; - let msg = Cw20ExecuteMsg::Mint { - recipient: String::from(to), - amount: Uint128::from(amount), - }; - let res = router - .execute_contract(self.owner.clone(), self.astro_token.clone(), &msg, &[]) - .unwrap(); - assert_eq!(res.events[1].attributes[1], attr("action", "mint")); - assert_eq!(res.events[1].attributes[2], attr("to", String::from(to))); - assert_eq!( - res.events[1].attributes[3], - attr("amount", Uint128::from(amount)) - ); - - let to_addr = Addr::unchecked(to); - let msg = Cw20ExecuteMsg::Send { - contract: self.staking_instance.to_string(), - msg: to_json_binary(&xastro::Cw20HookMsg::Enter {}).unwrap(), - amount: Uint128::from(amount), - }; - router - .execute_contract(to_addr, self.astro_token.clone(), &msg, &[]) - .unwrap(); - } - - pub fn check_xastro_balance(&self, router: &mut App, user: &str, amount: u64) { - let amount = amount * MULTIPLIER; - let res: BalanceResponse = router - .wrap() - .query_wasm_smart( - self.xastro_token.clone(), - &Cw20QueryMsg::Balance { - address: user.to_string(), - }, - ) - .unwrap(); - assert_eq!(res.balance.u128(), amount as u128); - } - - pub fn create_lock( - &self, - router: &mut App, - user: &str, - time: u64, - amount: f32, - ) -> Result { - let amount = (amount * MULTIPLIER as f32) as u64; - let cw20msg = Cw20ExecuteMsg::Send { - contract: self.escrow_instance.to_string(), - amount: Uint128::from(amount), - msg: to_json_binary(&Cw20HookMsg::CreateLock { time }).unwrap(), - }; - router.execute_contract( - Addr::unchecked(user), - self.xastro_token.clone(), - &cw20msg, - &[], - ) - } - - pub fn extend_lock_amount( - &self, - router: &mut App, - user: &str, - amount: f32, - ) -> Result { - let amount = (amount * MULTIPLIER as f32) as u64; - let cw20msg = Cw20ExecuteMsg::Send { - contract: self.escrow_instance.to_string(), - amount: Uint128::from(amount), - msg: to_json_binary(&Cw20HookMsg::ExtendLockAmount {}).unwrap(), - }; - router.execute_contract( - Addr::unchecked(user), - self.xastro_token.clone(), - &cw20msg, - &[], - ) - } - - pub fn unlock(&self, router: &mut App, user: &str) -> Result { - router.execute_contract( - Addr::unchecked(user), - self.escrow_instance.clone(), - &ExecuteMsg::Unlock {}, - &[], - ) - } - - pub fn deposit_for( - &self, - router: &mut App, - from: &str, - to: &str, - amount: f32, - ) -> Result { - let amount = (amount * MULTIPLIER as f32) as u64; - let cw20msg = Cw20ExecuteMsg::Send { - contract: self.escrow_instance.to_string(), - amount: Uint128::from(amount), - msg: to_json_binary(&Cw20HookMsg::DepositFor { - user: to.to_string(), - }) - .unwrap(), - }; - router.execute_contract( - Addr::unchecked(from), - self.xastro_token.clone(), - &cw20msg, - &[], - ) - } - - pub fn withdraw(&self, router: &mut App, user: &str) -> Result { - router.execute_contract( - Addr::unchecked(user), - self.escrow_instance.clone(), - &ExecuteMsg::Withdraw {}, - &[], - ) - } - - pub fn update_blacklist( - &self, - router: &mut App, - append_addrs: Option>, - remove_addrs: Option>, - ) -> Result { - router.execute_contract( - Addr::unchecked("owner"), - self.escrow_instance.clone(), - &ExecuteMsg::UpdateBlacklist { - append_addrs, - remove_addrs, - }, - &[], - ) - } - - pub fn query_user_vp(&self, router: &mut App, user: &str) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.escrow_instance.clone(), - &QueryMsg::UserVotingPower { - user: user.to_string(), - }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_user_vp_at(&self, router: &mut App, user: &str, time: u64) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.escrow_instance.clone(), - &QueryMsg::UserVotingPowerAt { - user: user.to_string(), - time, - }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_user_vp_at_period( - &self, - router: &mut App, - user: &str, - period: u64, - ) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.escrow_instance.clone(), - &QueryMsg::UserVotingPowerAtPeriod { - user: user.to_string(), - period, - }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_total_vp(&self, router: &mut App) -> StdResult { - router - .wrap() - .query_wasm_smart(self.escrow_instance.clone(), &QueryMsg::TotalVotingPower {}) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_total_vp_at(&self, router: &mut App, time: u64) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.escrow_instance.clone(), - &QueryMsg::TotalVotingPowerAt { time }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_total_vp_at_period(&self, router: &mut App, period: u64) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.escrow_instance.clone(), - &QueryMsg::TotalVotingPowerAtPeriod { period }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_user_emissions_vp(&self, router: &mut App, user: &str) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.escrow_instance.clone(), - &QueryMsg::UserEmissionsVotingPower { - user: user.to_string(), - }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_user_emissions_vp_at( - &self, - router: &mut App, - user: &str, - time: u64, - ) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.escrow_instance.clone(), - &QueryMsg::UserEmissionsVotingPowerAt { - user: user.to_string(), - time, - }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_total_emissions_vp(&self, router: &mut App) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.escrow_instance.clone(), - &QueryMsg::TotalEmissionsVotingPower {}, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_total_emissions_vp_at(&self, router: &mut App, time: u64) -> StdResult { - router - .wrap() - .query_wasm_smart( - self.escrow_instance.clone(), - &QueryMsg::TotalEmissionsVotingPowerAt { time }, - ) - .map(|vp: VotingPowerResponse| vp.voting_power.u128() as f32 / MULTIPLIER as f32) - } - - pub fn query_lock_info(&self, router: &mut App, user: &str) -> StdResult { - router.wrap().query_wasm_smart( - self.escrow_instance.clone(), - &QueryMsg::LockInfo { - user: user.to_string(), - }, - ) - } -} diff --git a/packages/astroport-tests-lite/src/lib.rs b/packages/astroport-tests-lite/src/lib.rs deleted file mode 100644 index abd8f49c..00000000 --- a/packages/astroport-tests-lite/src/lib.rs +++ /dev/null @@ -1,54 +0,0 @@ -#![cfg(not(tarpaulin_include))] - -pub mod address_generator; -pub mod base; - -use address_generator::WasmAddressGenerator; -use astroport_governance::utils::{get_lite_period, EPOCH_START}; -use cosmwasm_std::testing::{mock_env, MockApi, MockStorage}; -use cosmwasm_std::{Empty, Timestamp}; -use cw_multi_test::{App, BankKeeper, BasicAppBuilder, FailingModule, WasmKeeper}; - -#[allow(clippy::all)] -#[allow(dead_code)] -pub mod controller_helper; - -#[allow(clippy::all)] -#[allow(dead_code)] -pub mod escrow_helper; - -pub fn mock_app() -> App { - let mut env = mock_env(); - env.block.time = Timestamp::from_seconds(EPOCH_START); - let api = MockApi::default(); - let bank = BankKeeper::new(); - let storage = MockStorage::new(); - - BasicAppBuilder::new() - .with_api(api) - .with_block(env.block) - .with_bank(bank) - .with_storage(storage) - .with_wasm::, WasmKeeper>( - WasmKeeper::new_with_custom_address_generator(WasmAddressGenerator::default()), - ) - .build(|_, _, _| {}) -} - -pub trait TerraAppExtension { - fn next_block(&mut self, time: u64); - fn block_period(&self) -> u64; -} - -impl TerraAppExtension for App { - fn next_block(&mut self, time: u64) { - self.update_block(|block| { - block.time = block.time.plus_seconds(time); - block.height += 1 - }); - } - - fn block_period(&self) -> u64 { - get_lite_period(self.block_info().time.seconds()).unwrap() - } -} From 7bfde856349b655537cac664f36e04b334885d96 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Tue, 14 May 2024 18:23:51 +0400 Subject: [PATCH 07/32] add vxastro schemas --- .../astroport-voting-escrow.json | 588 ++++++++++++++++++ .../astroport-voting-escrow/raw/execute.json | 108 ++++ .../raw/instantiate.json | 134 ++++ .../astroport-voting-escrow/raw/query.json | 147 +++++ .../raw/response_to_balance.json | 20 + .../raw/response_to_config.json | 16 + .../raw/response_to_lock_info.json | 34 + .../raw/response_to_marketing_info.json | 74 +++ .../raw/response_to_token_info.json | 34 + .../raw/response_to_total_voting_power.json | 6 + .../raw/response_to_user_voting_power.json | 6 + 11 files changed, 1167 insertions(+) create mode 100644 schemas/astroport-voting-escrow/astroport-voting-escrow.json create mode 100644 schemas/astroport-voting-escrow/raw/execute.json create mode 100644 schemas/astroport-voting-escrow/raw/instantiate.json create mode 100644 schemas/astroport-voting-escrow/raw/query.json create mode 100644 schemas/astroport-voting-escrow/raw/response_to_balance.json create mode 100644 schemas/astroport-voting-escrow/raw/response_to_config.json create mode 100644 schemas/astroport-voting-escrow/raw/response_to_lock_info.json create mode 100644 schemas/astroport-voting-escrow/raw/response_to_marketing_info.json create mode 100644 schemas/astroport-voting-escrow/raw/response_to_token_info.json create mode 100644 schemas/astroport-voting-escrow/raw/response_to_total_voting_power.json create mode 100644 schemas/astroport-voting-escrow/raw/response_to_user_voting_power.json diff --git a/schemas/astroport-voting-escrow/astroport-voting-escrow.json b/schemas/astroport-voting-escrow/astroport-voting-escrow.json new file mode 100644 index 00000000..37735849 --- /dev/null +++ b/schemas/astroport-voting-escrow/astroport-voting-escrow.json @@ -0,0 +1,588 @@ +{ + "contract_name": "astroport-voting-escrow", + "contract_version": "1.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "description": "This structure stores general parameters for the vxASTRO contract.", + "type": "object", + "required": [ + "deposit_denom" + ], + "properties": { + "deposit_denom": { + "description": "xASTRO denom", + "type": "string" + }, + "marketing": { + "description": "Marketing info for vxASTRO", + "anyOf": [ + { + "$ref": "#/definitions/UpdateMarketingInfo" + }, + { + "type": "null" + } + ] + } + }, + "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" + }, + "EmbeddedLogo": { + "description": "This is used to store the logo on the blockchain in an accepted format. Enforce maximum size of 5KB on all variants.", + "oneOf": [ + { + "description": "Store the Logo as an SVG file. The content must conform to the spec at https://en.wikipedia.org/wiki/Scalable_Vector_Graphics (The contract should do some light-weight sanity-check validation)", + "type": "object", + "required": [ + "svg" + ], + "properties": { + "svg": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + }, + { + "description": "Store the Logo as a PNG file. This will likely only support up to 64x64 or so within the 5KB limit.", + "type": "object", + "required": [ + "png" + ], + "properties": { + "png": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + } + ] + }, + "Logo": { + "description": "This is used for uploading logo data, or setting it in InstantiateData", + "oneOf": [ + { + "description": "A reference to an externally hosted logo. Must be a valid HTTP or HTTPS URL.", + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "description": "Logo content stored on the blockchain. Enforce maximum size of 5KB on all variants", + "type": "object", + "required": [ + "embedded" + ], + "properties": { + "embedded": { + "$ref": "#/definitions/EmbeddedLogo" + } + }, + "additionalProperties": false + } + ] + }, + "UpdateMarketingInfo": { + "description": "This structure stores marketing information for vxASTRO.", + "type": "object", + "properties": { + "description": { + "description": "Token description", + "type": [ + "string", + "null" + ] + }, + "logo": { + "description": "Token logo", + "anyOf": [ + { + "$ref": "#/definitions/Logo" + }, + { + "type": "null" + } + ] + }, + "marketing": { + "description": "Token marketing information", + "type": [ + "string", + "null" + ] + }, + "project": { + "description": "Project URL", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "description": "This structure describes the execute functions in the contract.", + "oneOf": [ + { + "description": "Create a vxASTRO position and lock xASTRO", + "type": "object", + "required": [ + "lock" + ], + "properties": { + "lock": { + "type": "object", + "properties": { + "receiver": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Unlock xASTRO from the vxASTRO contract", + "type": "object", + "required": [ + "unlock" + ], + "properties": { + "unlock": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Cancel unlocking", + "type": "object", + "required": [ + "relock" + ], + "properties": { + "relock": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Withdraw xASTRO from the vxASTRO contract", + "type": "object", + "required": [ + "withdraw" + ], + "properties": { + "withdraw": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Update the marketing info for the vxASTRO contract", + "type": "object", + "required": [ + "update_marketing" + ], + "properties": { + "update_marketing": { + "type": "object", + "properties": { + "description": { + "description": "A longer description of the token and its utility. Designed for tooltips or such", + "type": [ + "string", + "null" + ] + }, + "marketing": { + "description": "The address (if any) that can update this data structure", + "type": [ + "string", + "null" + ] + }, + "project": { + "description": "A URL pointing to the project behind this token", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "description": "This structure describes the query messages available in the contract.", + "oneOf": [ + { + "description": "Return the user's vxASTRO balance", + "type": "object", + "required": [ + "balance" + ], + "properties": { + "balance": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Fetch the vxASTRO token information", + "type": "object", + "required": [ + "token_info" + ], + "properties": { + "token_info": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Fetch vxASTRO's marketing information", + "type": "object", + "required": [ + "marketing_info" + ], + "properties": { + "marketing_info": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return the current total amount of vxASTRO", + "type": "object", + "required": [ + "total_voting_power" + ], + "properties": { + "total_voting_power": { + "type": "object", + "properties": { + "time": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return the user's current voting power (vxASTRO balance)", + "type": "object", + "required": [ + "user_voting_power" + ], + "properties": { + "user_voting_power": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "time": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Fetch a user's lock information", + "type": "object", + "required": [ + "lock_info" + ], + "properties": { + "lock_info": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return the vxASTRO contract configuration", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": null, + "sudo": null, + "responses": { + "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" + } + } + }, + "config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config", + "description": "This structure stores the main parameters for the voting escrow contract.", + "type": "object", + "required": [ + "deposit_denom" + ], + "properties": { + "deposit_denom": { + "description": "The xASTRO denom", + "type": "string" + } + }, + "additionalProperties": false + }, + "lock_info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "LockInfoResponse", + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "description": "The total amount of xASTRO tokens that were deposited in the vxASTRO position", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "end": { + "description": "The timestamp when a lock will be unlocked. None for positions in Locked state", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "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" + } + } + }, + "marketing_info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MarketingInfoResponse", + "type": "object", + "properties": { + "description": { + "description": "A longer description of the token and it's utility. Designed for tooltips or such", + "type": [ + "string", + "null" + ] + }, + "logo": { + "description": "A link to the logo, or a comment there is an on-chain logo stored", + "anyOf": [ + { + "$ref": "#/definitions/LogoInfo" + }, + { + "type": "null" + } + ] + }, + "marketing": { + "description": "The address (if any) who can update this data structure", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "project": { + "description": "A URL pointing to the project behind this token.", + "type": [ + "string", + "null" + ] + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "LogoInfo": { + "description": "This is used to display logo info, provide a link or inform there is one that can be downloaded from the blockchain itself", + "oneOf": [ + { + "description": "A reference to an externally hosted logo. Must be a valid HTTP or HTTPS URL.", + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "description": "There is an embedded logo on the chain, make another call to download it.", + "type": "string", + "enum": [ + "embedded" + ] + } + ] + } + } + }, + "token_info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TokenInfoResponse", + "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" + } + }, + "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" + } + } + }, + "total_voting_power": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "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" + }, + "user_voting_power": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "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/schemas/astroport-voting-escrow/raw/execute.json b/schemas/astroport-voting-escrow/raw/execute.json new file mode 100644 index 00000000..88c6a3c1 --- /dev/null +++ b/schemas/astroport-voting-escrow/raw/execute.json @@ -0,0 +1,108 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "description": "This structure describes the execute functions in the contract.", + "oneOf": [ + { + "description": "Create a vxASTRO position and lock xASTRO", + "type": "object", + "required": [ + "lock" + ], + "properties": { + "lock": { + "type": "object", + "properties": { + "receiver": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Unlock xASTRO from the vxASTRO contract", + "type": "object", + "required": [ + "unlock" + ], + "properties": { + "unlock": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Cancel unlocking", + "type": "object", + "required": [ + "relock" + ], + "properties": { + "relock": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Withdraw xASTRO from the vxASTRO contract", + "type": "object", + "required": [ + "withdraw" + ], + "properties": { + "withdraw": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Update the marketing info for the vxASTRO contract", + "type": "object", + "required": [ + "update_marketing" + ], + "properties": { + "update_marketing": { + "type": "object", + "properties": { + "description": { + "description": "A longer description of the token and its utility. Designed for tooltips or such", + "type": [ + "string", + "null" + ] + }, + "marketing": { + "description": "The address (if any) that can update this data structure", + "type": [ + "string", + "null" + ] + }, + "project": { + "description": "A URL pointing to the project behind this token", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/schemas/astroport-voting-escrow/raw/instantiate.json b/schemas/astroport-voting-escrow/raw/instantiate.json new file mode 100644 index 00000000..1bd521af --- /dev/null +++ b/schemas/astroport-voting-escrow/raw/instantiate.json @@ -0,0 +1,134 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "description": "This structure stores general parameters for the vxASTRO contract.", + "type": "object", + "required": [ + "deposit_denom" + ], + "properties": { + "deposit_denom": { + "description": "xASTRO denom", + "type": "string" + }, + "marketing": { + "description": "Marketing info for vxASTRO", + "anyOf": [ + { + "$ref": "#/definitions/UpdateMarketingInfo" + }, + { + "type": "null" + } + ] + } + }, + "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" + }, + "EmbeddedLogo": { + "description": "This is used to store the logo on the blockchain in an accepted format. Enforce maximum size of 5KB on all variants.", + "oneOf": [ + { + "description": "Store the Logo as an SVG file. The content must conform to the spec at https://en.wikipedia.org/wiki/Scalable_Vector_Graphics (The contract should do some light-weight sanity-check validation)", + "type": "object", + "required": [ + "svg" + ], + "properties": { + "svg": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + }, + { + "description": "Store the Logo as a PNG file. This will likely only support up to 64x64 or so within the 5KB limit.", + "type": "object", + "required": [ + "png" + ], + "properties": { + "png": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + } + ] + }, + "Logo": { + "description": "This is used for uploading logo data, or setting it in InstantiateData", + "oneOf": [ + { + "description": "A reference to an externally hosted logo. Must be a valid HTTP or HTTPS URL.", + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "description": "Logo content stored on the blockchain. Enforce maximum size of 5KB on all variants", + "type": "object", + "required": [ + "embedded" + ], + "properties": { + "embedded": { + "$ref": "#/definitions/EmbeddedLogo" + } + }, + "additionalProperties": false + } + ] + }, + "UpdateMarketingInfo": { + "description": "This structure stores marketing information for vxASTRO.", + "type": "object", + "properties": { + "description": { + "description": "Token description", + "type": [ + "string", + "null" + ] + }, + "logo": { + "description": "Token logo", + "anyOf": [ + { + "$ref": "#/definitions/Logo" + }, + { + "type": "null" + } + ] + }, + "marketing": { + "description": "Token marketing information", + "type": [ + "string", + "null" + ] + }, + "project": { + "description": "Project URL", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + } +} diff --git a/schemas/astroport-voting-escrow/raw/query.json b/schemas/astroport-voting-escrow/raw/query.json new file mode 100644 index 00000000..86839735 --- /dev/null +++ b/schemas/astroport-voting-escrow/raw/query.json @@ -0,0 +1,147 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "description": "This structure describes the query messages available in the contract.", + "oneOf": [ + { + "description": "Return the user's vxASTRO balance", + "type": "object", + "required": [ + "balance" + ], + "properties": { + "balance": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Fetch the vxASTRO token information", + "type": "object", + "required": [ + "token_info" + ], + "properties": { + "token_info": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Fetch vxASTRO's marketing information", + "type": "object", + "required": [ + "marketing_info" + ], + "properties": { + "marketing_info": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return the current total amount of vxASTRO", + "type": "object", + "required": [ + "total_voting_power" + ], + "properties": { + "total_voting_power": { + "type": "object", + "properties": { + "time": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return the user's current voting power (vxASTRO balance)", + "type": "object", + "required": [ + "user_voting_power" + ], + "properties": { + "user_voting_power": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "time": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Fetch a user's lock information", + "type": "object", + "required": [ + "lock_info" + ], + "properties": { + "lock_info": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Return the vxASTRO contract configuration", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/schemas/astroport-voting-escrow/raw/response_to_balance.json b/schemas/astroport-voting-escrow/raw/response_to_balance.json new file mode 100644 index 00000000..7dcf4d4a --- /dev/null +++ b/schemas/astroport-voting-escrow/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/schemas/astroport-voting-escrow/raw/response_to_config.json b/schemas/astroport-voting-escrow/raw/response_to_config.json new file mode 100644 index 00000000..82583b5e --- /dev/null +++ b/schemas/astroport-voting-escrow/raw/response_to_config.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config", + "description": "This structure stores the main parameters for the voting escrow contract.", + "type": "object", + "required": [ + "deposit_denom" + ], + "properties": { + "deposit_denom": { + "description": "The xASTRO denom", + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/schemas/astroport-voting-escrow/raw/response_to_lock_info.json b/schemas/astroport-voting-escrow/raw/response_to_lock_info.json new file mode 100644 index 00000000..06b854ef --- /dev/null +++ b/schemas/astroport-voting-escrow/raw/response_to_lock_info.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "LockInfoResponse", + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "description": "The total amount of xASTRO tokens that were deposited in the vxASTRO position", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "end": { + "description": "The timestamp when a lock will be unlocked. None for positions in Locked state", + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "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/schemas/astroport-voting-escrow/raw/response_to_marketing_info.json b/schemas/astroport-voting-escrow/raw/response_to_marketing_info.json new file mode 100644 index 00000000..c36ee5f9 --- /dev/null +++ b/schemas/astroport-voting-escrow/raw/response_to_marketing_info.json @@ -0,0 +1,74 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MarketingInfoResponse", + "type": "object", + "properties": { + "description": { + "description": "A longer description of the token and it's utility. Designed for tooltips or such", + "type": [ + "string", + "null" + ] + }, + "logo": { + "description": "A link to the logo, or a comment there is an on-chain logo stored", + "anyOf": [ + { + "$ref": "#/definitions/LogoInfo" + }, + { + "type": "null" + } + ] + }, + "marketing": { + "description": "The address (if any) who can update this data structure", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "project": { + "description": "A URL pointing to the project behind this token.", + "type": [ + "string", + "null" + ] + } + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "LogoInfo": { + "description": "This is used to display logo info, provide a link or inform there is one that can be downloaded from the blockchain itself", + "oneOf": [ + { + "description": "A reference to an externally hosted logo. Must be a valid HTTP or HTTPS URL.", + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "description": "There is an embedded logo on the chain, make another call to download it.", + "type": "string", + "enum": [ + "embedded" + ] + } + ] + } + } +} diff --git a/schemas/astroport-voting-escrow/raw/response_to_token_info.json b/schemas/astroport-voting-escrow/raw/response_to_token_info.json new file mode 100644 index 00000000..0e84d125 --- /dev/null +++ b/schemas/astroport-voting-escrow/raw/response_to_token_info.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TokenInfoResponse", + "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" + } + }, + "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/schemas/astroport-voting-escrow/raw/response_to_total_voting_power.json b/schemas/astroport-voting-escrow/raw/response_to_total_voting_power.json new file mode 100644 index 00000000..25b73e8f --- /dev/null +++ b/schemas/astroport-voting-escrow/raw/response_to_total_voting_power.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "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/schemas/astroport-voting-escrow/raw/response_to_user_voting_power.json b/schemas/astroport-voting-escrow/raw/response_to_user_voting_power.json new file mode 100644 index 00000000..25b73e8f --- /dev/null +++ b/schemas/astroport-voting-escrow/raw/response_to_user_voting_power.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "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" +} From ea0de090f8d6ec51a83cdad8aeaff9b0072ee214 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Mon, 3 Jun 2024 12:24:05 +0400 Subject: [PATCH 08/32] feat(emissions controller): add hub and outpost contracts --- .github/workflows/check_artifacts.yml | 4 +- Cargo.lock | 1726 +++++++++++++++++ Cargo.toml | 8 +- INTERCHAIN.md | 216 --- README.md | 25 +- contracts/assembly/Cargo.toml | 4 +- contracts/builder_unlock/Cargo.toml | 2 +- contracts/emissions_controller/Cargo.toml | 38 + .../README.md | 60 +- .../examples/emissions_controller_schema.rs | 12 + contracts/emissions_controller/src/error.rs | 82 + contracts/emissions_controller/src/execute.rs | 773 ++++++++ contracts/emissions_controller/src/ibc.rs | 429 ++++ .../emissions_controller/src/instantiate.rs | 115 ++ contracts/emissions_controller/src/lib.rs | 9 + contracts/emissions_controller/src/query.rs | 97 + contracts/emissions_controller/src/state.rs | 37 + contracts/emissions_controller/src/sudo.rs | 61 + contracts/emissions_controller/src/utils.rs | 249 +++ .../tests/common/contracts.rs | 85 + .../tests/common/helper.rs | 424 ++++ .../tests/common/ibc_module.rs | 82 + .../emissions_controller/tests/common/mod.rs | 4 + .../tests/common/neutron_module.rs | 98 + .../tests/emissions_controller_integration.rs | 1333 +++++++++++++ .../emissions_controller_outpost/Cargo.toml | 37 + .../README.md | 63 +- .../emissions_controller_outpost_schema.rs | 14 + .../emissions_controller_outpost/src/error.rs | 48 + .../src/execute.rs | 379 ++++ .../emissions_controller_outpost/src/ibc.rs | 298 +++ .../src/instantiate.rs | 98 + .../emissions_controller_outpost/src/lib.rs | 7 + .../emissions_controller_outpost/src/query.rs | 19 + .../emissions_controller_outpost/src/state.rs | 17 + .../tests/common/contracts.rs | 83 + .../tests/common/helper.rs | 447 +++++ .../tests/common/ibc_module.rs | 171 ++ .../tests/common/mod.rs | 3 + ...missions_controller_outpost_integration.rs | 715 +++++++ contracts/generator_controller/Cargo.toml | 48 - .../assets/generator_controller_timeline.png | Bin 53717 -> 0 bytes contracts/generator_controller/src/bps.rs | 89 - .../generator_controller/src/contract.rs | 640 ------ contracts/generator_controller/src/error.rs | 63 - contracts/generator_controller/src/lib.rs | 10 - contracts/generator_controller/src/state.rs | 71 - contracts/generator_controller/src/utils.rs | 378 ---- .../generator_controller/tests/integration.rs | 997 ---------- .../generator_controller/tests/math_test.rs | 411 ---- .../generator_controller_lite/.cargo/config | 6 - .../generator_controller_lite/Cargo.toml | 48 - .../generator_controller_lite/src/bps.rs | 89 - .../generator_controller_lite/src/contract.rs | 937 --------- .../generator_controller_lite/src/error.rs | 69 - .../generator_controller_lite/src/lib.rs | 10 - .../generator_controller_lite/src/state.rs | 67 - .../generator_controller_lite/src/utils.rs | 295 --- .../tests/integration.rs | 1621 ---------------- .../tests/math_test.rs | 394 ---- contracts/hub/.cargo/config | 6 - contracts/hub/Cargo.toml | 41 - contracts/hub/README.md | 139 -- contracts/hub/src/contract.rs | 133 -- contracts/hub/src/error.rs | 70 - contracts/hub/src/execute.rs | 1602 --------------- contracts/hub/src/ibc.rs | 678 ------- contracts/hub/src/ibc_governance.rs | 727 ------- contracts/hub/src/ibc_misc.rs | 230 --- contracts/hub/src/ibc_query.rs | 170 -- contracts/hub/src/ibc_staking.rs | 330 ---- contracts/hub/src/lib.rs | 14 - contracts/hub/src/mock.rs | 296 --- contracts/hub/src/query.rs | 72 - contracts/hub/src/reply.rs | 162 -- contracts/hub/src/state.rs | 169 -- contracts/outpost/.cargo/config | 6 - contracts/outpost/Cargo.toml | 44 - contracts/outpost/README.md | 131 -- contracts/outpost/src/contract.rs | 123 -- contracts/outpost/src/error.rs | 51 - contracts/outpost/src/execute.rs | 1324 ------------- contracts/outpost/src/ibc.rs | 662 ------- contracts/outpost/src/ibc_failure.rs | 656 ------- contracts/outpost/src/ibc_mint.rs | 180 -- contracts/outpost/src/lib.rs | 11 - contracts/outpost/src/mock.rs | 217 --- contracts/outpost/src/query.rs | 174 -- contracts/outpost/src/state.rs | 34 - contracts/voting_escrow/Cargo.toml | 2 +- contracts/voting_escrow/src/contract.rs | 119 +- contracts/voting_escrow/src/error.rs | 3 + contracts/voting_escrow/src/state.rs | 58 +- contracts/voting_escrow/tests/helper.rs | 55 +- .../tests/voting_escrow_integration.rs | 43 +- packages/astroport-governance/.cargo/config | 4 - packages/astroport-governance/Cargo.toml | 3 +- .../src/emissions_controller/consts.rs | 28 + .../src/emissions_controller/hub.rs | 305 +++ .../src/emissions_controller/mod.rs | 5 + .../src/emissions_controller/msg.rs | 70 + .../src/emissions_controller/outpost.rs | 96 + .../src/emissions_controller/utils.rs | 83 + .../src/generator_controller.rs | 146 -- .../src/generator_controller_lite.rs | 184 -- packages/astroport-governance/src/hub.rs | 145 -- .../astroport-governance/src/interchain.rs | 164 -- packages/astroport-governance/src/lib.rs | 8 +- packages/astroport-governance/src/outpost.rs | 107 - packages/astroport-governance/src/utils.rs | 88 +- .../astroport-governance/src/voting_escrow.rs | 30 +- ...stroport-emissions-controller-outpost.json | 786 ++++++++ .../raw/execute.json | 370 ++++ .../raw/instantiate.json | 166 ++ .../raw/query.json | 43 + .../raw/response_to_config.json | 64 + .../response_to_query_user_ibc_status.json | 134 ++ .../astroport-emissions-controller.json | 1179 +++++++++++ .../raw/execute.json | 388 ++++ .../raw/instantiate.json | 209 ++ .../raw/query.json | 163 ++ .../raw/response_to_config.json | 106 + .../raw/response_to_list_outposts.json | 104 + .../raw/response_to_query_whitelist.json | 8 + .../raw/response_to_tune_info.json | 60 + .../raw/response_to_user_info.json | 52 + .../raw/response_to_voted_pool.json | 32 + .../raw/response_to_voted_pools_list.json | 48 + .../astroport-voting-escrow.json | 110 +- .../astroport-voting-escrow/raw/execute.json | 44 + .../raw/instantiate.json | 7 +- .../raw/response_to_config.json | 19 +- .../raw/response_to_lock_info.json | 40 +- 133 files changed, 12999 insertions(+), 15946 deletions(-) create mode 100644 Cargo.lock delete mode 100644 INTERCHAIN.md create mode 100644 contracts/emissions_controller/Cargo.toml rename contracts/{generator_controller_lite => emissions_controller}/README.md (76%) create mode 100644 contracts/emissions_controller/examples/emissions_controller_schema.rs create mode 100644 contracts/emissions_controller/src/error.rs create mode 100644 contracts/emissions_controller/src/execute.rs create mode 100644 contracts/emissions_controller/src/ibc.rs create mode 100644 contracts/emissions_controller/src/instantiate.rs create mode 100644 contracts/emissions_controller/src/lib.rs create mode 100644 contracts/emissions_controller/src/query.rs create mode 100644 contracts/emissions_controller/src/state.rs create mode 100644 contracts/emissions_controller/src/sudo.rs create mode 100644 contracts/emissions_controller/src/utils.rs create mode 100644 contracts/emissions_controller/tests/common/contracts.rs create mode 100644 contracts/emissions_controller/tests/common/helper.rs create mode 100644 contracts/emissions_controller/tests/common/ibc_module.rs create mode 100644 contracts/emissions_controller/tests/common/mod.rs create mode 100644 contracts/emissions_controller/tests/common/neutron_module.rs create mode 100644 contracts/emissions_controller/tests/emissions_controller_integration.rs create mode 100644 contracts/emissions_controller_outpost/Cargo.toml rename contracts/{generator_controller => emissions_controller_outpost}/README.md (76%) create mode 100644 contracts/emissions_controller_outpost/examples/emissions_controller_outpost_schema.rs create mode 100644 contracts/emissions_controller_outpost/src/error.rs create mode 100644 contracts/emissions_controller_outpost/src/execute.rs create mode 100644 contracts/emissions_controller_outpost/src/ibc.rs create mode 100644 contracts/emissions_controller_outpost/src/instantiate.rs create mode 100644 contracts/emissions_controller_outpost/src/lib.rs create mode 100644 contracts/emissions_controller_outpost/src/query.rs create mode 100644 contracts/emissions_controller_outpost/src/state.rs create mode 100644 contracts/emissions_controller_outpost/tests/common/contracts.rs create mode 100644 contracts/emissions_controller_outpost/tests/common/helper.rs create mode 100644 contracts/emissions_controller_outpost/tests/common/ibc_module.rs create mode 100644 contracts/emissions_controller_outpost/tests/common/mod.rs create mode 100644 contracts/emissions_controller_outpost/tests/emissions_controller_outpost_integration.rs delete mode 100644 contracts/generator_controller/Cargo.toml delete mode 100644 contracts/generator_controller/assets/generator_controller_timeline.png delete mode 100644 contracts/generator_controller/src/bps.rs delete mode 100644 contracts/generator_controller/src/contract.rs delete mode 100644 contracts/generator_controller/src/error.rs delete mode 100644 contracts/generator_controller/src/lib.rs delete mode 100644 contracts/generator_controller/src/state.rs delete mode 100644 contracts/generator_controller/src/utils.rs delete mode 100644 contracts/generator_controller/tests/integration.rs delete mode 100644 contracts/generator_controller/tests/math_test.rs delete mode 100644 contracts/generator_controller_lite/.cargo/config delete mode 100644 contracts/generator_controller_lite/Cargo.toml delete mode 100644 contracts/generator_controller_lite/src/bps.rs delete mode 100644 contracts/generator_controller_lite/src/contract.rs delete mode 100644 contracts/generator_controller_lite/src/error.rs delete mode 100644 contracts/generator_controller_lite/src/lib.rs delete mode 100644 contracts/generator_controller_lite/src/state.rs delete mode 100644 contracts/generator_controller_lite/src/utils.rs delete mode 100644 contracts/generator_controller_lite/tests/integration.rs delete mode 100644 contracts/generator_controller_lite/tests/math_test.rs delete mode 100644 contracts/hub/.cargo/config delete mode 100644 contracts/hub/Cargo.toml delete mode 100644 contracts/hub/README.md delete mode 100644 contracts/hub/src/contract.rs delete mode 100644 contracts/hub/src/error.rs delete mode 100644 contracts/hub/src/execute.rs delete mode 100644 contracts/hub/src/ibc.rs delete mode 100644 contracts/hub/src/ibc_governance.rs delete mode 100644 contracts/hub/src/ibc_misc.rs delete mode 100644 contracts/hub/src/ibc_query.rs delete mode 100644 contracts/hub/src/ibc_staking.rs delete mode 100644 contracts/hub/src/lib.rs delete mode 100644 contracts/hub/src/mock.rs delete mode 100644 contracts/hub/src/query.rs delete mode 100644 contracts/hub/src/reply.rs delete mode 100644 contracts/hub/src/state.rs delete mode 100644 contracts/outpost/.cargo/config delete mode 100644 contracts/outpost/Cargo.toml delete mode 100644 contracts/outpost/README.md delete mode 100644 contracts/outpost/src/contract.rs delete mode 100644 contracts/outpost/src/error.rs delete mode 100644 contracts/outpost/src/execute.rs delete mode 100644 contracts/outpost/src/ibc.rs delete mode 100644 contracts/outpost/src/ibc_failure.rs delete mode 100644 contracts/outpost/src/ibc_mint.rs delete mode 100644 contracts/outpost/src/lib.rs delete mode 100644 contracts/outpost/src/mock.rs delete mode 100644 contracts/outpost/src/query.rs delete mode 100644 contracts/outpost/src/state.rs delete mode 100644 packages/astroport-governance/.cargo/config create mode 100644 packages/astroport-governance/src/emissions_controller/consts.rs create mode 100644 packages/astroport-governance/src/emissions_controller/hub.rs create mode 100644 packages/astroport-governance/src/emissions_controller/mod.rs create mode 100644 packages/astroport-governance/src/emissions_controller/msg.rs create mode 100644 packages/astroport-governance/src/emissions_controller/outpost.rs create mode 100644 packages/astroport-governance/src/emissions_controller/utils.rs delete mode 100644 packages/astroport-governance/src/generator_controller.rs delete mode 100644 packages/astroport-governance/src/generator_controller_lite.rs delete mode 100644 packages/astroport-governance/src/hub.rs delete mode 100644 packages/astroport-governance/src/interchain.rs delete mode 100644 packages/astroport-governance/src/outpost.rs create mode 100644 schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json create mode 100644 schemas/astroport-emissions-controller-outpost/raw/execute.json create mode 100644 schemas/astroport-emissions-controller-outpost/raw/instantiate.json create mode 100644 schemas/astroport-emissions-controller-outpost/raw/query.json create mode 100644 schemas/astroport-emissions-controller-outpost/raw/response_to_config.json create mode 100644 schemas/astroport-emissions-controller-outpost/raw/response_to_query_user_ibc_status.json create mode 100644 schemas/astroport-emissions-controller/astroport-emissions-controller.json create mode 100644 schemas/astroport-emissions-controller/raw/execute.json create mode 100644 schemas/astroport-emissions-controller/raw/instantiate.json create mode 100644 schemas/astroport-emissions-controller/raw/query.json create mode 100644 schemas/astroport-emissions-controller/raw/response_to_config.json create mode 100644 schemas/astroport-emissions-controller/raw/response_to_list_outposts.json create mode 100644 schemas/astroport-emissions-controller/raw/response_to_query_whitelist.json create mode 100644 schemas/astroport-emissions-controller/raw/response_to_tune_info.json create mode 100644 schemas/astroport-emissions-controller/raw/response_to_user_info.json create mode 100644 schemas/astroport-emissions-controller/raw/response_to_voted_pool.json create mode 100644 schemas/astroport-emissions-controller/raw/response_to_voted_pools_list.json diff --git a/.github/workflows/check_artifacts.yml b/.github/workflows/check_artifacts.yml index f311595e..464217b3 100644 --- a/.github/workflows/check_artifacts.yml +++ b/.github/workflows/check_artifacts.yml @@ -82,7 +82,7 @@ jobs: -v "$GITHUB_WORKSPACE":/code \ -v ~/.cargo/registry:/usr/local/cargo/registry \ -v ~/.cargo/git:/usr/local/cargo/git \ - cosmwasm/workspace-optimizer:0.12.13 + cosmwasm/rust-optimizer:0.15.1 - name: Save artifacts cache uses: actions/cache/save@v3 @@ -113,4 +113,4 @@ jobs: run: cargo install --debug --version 1.4.0 cosmwasm-check - name: Cosmwasm check run: | - cosmwasm-check $GITHUB_WORKSPACE/artifacts/*.wasm --available-capabilities staking,iterator,stargate,cosmwasm_1_1 + cosmwasm-check $GITHUB_WORKSPACE/artifacts/*.wasm --available-capabilities staking,iterator,stargate,cosmwasm_1_1,neutron diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..b2eeec92 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1726 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "anyhow" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" + +[[package]] +name = "astro-assembly" +version = "2.0.0" +dependencies = [ + "anyhow", + "astro-satellite", + "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "astroport-governance 3.0.0", + "astroport-staking", + "astroport-tokenfactory-tracker", + "builder-unlock", + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test 0.20.0 (git+https://github.com/astroport-fi/cw-multi-test?branch=feat/bank_with_send_hooks)", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "ibc-controller-package", + "osmosis-std", + "test-case", + "thiserror", +] + +[[package]] +name = "astro-satellite" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e6e7c9f6e9ef66b5b0bb93a69530f012ddcf9c8a5a2d17ad598deae54024919" +dependencies = [ + "astro-satellite-package", + "astroport-ibc", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus 0.15.1", + "cw-utils 0.15.1", + "cw2 0.15.1", + "ibc-controller-package", + "itertools 0.10.5", + "thiserror", +] + +[[package]] +name = "astro-satellite-package" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "893363819104a2a4685d99f19e35e5d81102fb782e622619ac643e54ff65d638" +dependencies = [ + "astroport-governance 1.2.0", + "cosmwasm-schema", + "cosmwasm-std", +] + +[[package]] +name = "astroport" +version = "2.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d102b618016b3c1f1ebb5750617a73dbd294a3c941e54b12deabc931d771bc6e" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw-utils 0.15.1", + "cw20 0.15.1", + "itertools 0.10.5", + "uint", +] + +[[package]] +name = "astroport" +version = "3.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdebdf96895f363e121710cb84bbbfa659cea1bb1470260d4976d1a7206b3b16" +dependencies = [ + "astroport-circular-buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cosmwasm-schema", + "cosmwasm-std", + "cw-asset", + "cw-storage-plus 0.15.1", + "cw-utils 1.0.3", + "cw20 0.15.1", + "cw3", + "itertools 0.10.5", + "uint", +] + +[[package]] +name = "astroport" +version = "4.0.2" +source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#ada68324a4cca143008646d3c81baa715cba12d2" +dependencies = [ + "astroport-circular-buffer 0.2.0 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "cosmwasm-schema", + "cosmwasm-std", + "cw-asset", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw20 1.1.2", + "itertools 0.12.1", + "uint", +] + +[[package]] +name = "astroport" +version = "4.0.2" +source = "git+https://github.com/astroport-fi/hidden_astroport_core#02eafc677d9c402a55e45e956fd185d67ac3a8a6" +dependencies = [ + "astroport-circular-buffer 0.2.0 (git+https://github.com/astroport-fi/hidden_astroport_core)", + "cosmwasm-schema", + "cosmwasm-std", + "cw-asset", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw20 1.1.2", + "itertools 0.12.1", + "uint", +] + +[[package]] +name = "astroport-circular-buffer" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c7369d3c4126804f861620db2221c15b5fa7b7718f12180e265b087c933fb6" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "thiserror", +] + +[[package]] +name = "astroport-circular-buffer" +version = "0.2.0" +source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#ada68324a4cca143008646d3c81baa715cba12d2" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "thiserror", +] + +[[package]] +name = "astroport-circular-buffer" +version = "0.2.0" +source = "git+https://github.com/astroport-fi/hidden_astroport_core#02eafc677d9c402a55e45e956fd185d67ac3a8a6" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "thiserror", +] + +[[package]] +name = "astroport-emissions-controller" +version = "1.0.0" +dependencies = [ + "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "astroport-factory", + "astroport-governance 3.0.0", + "astroport-incentives", + "astroport-pair", + "astroport-voting-escrow", + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test 1.1.0", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw20-base", + "derivative", + "itertools 0.12.1", + "neutron-sdk", + "serde_json", + "thiserror", +] + +[[package]] +name = "astroport-emissions-controller-outpost" +version = "1.0.0" +dependencies = [ + "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "astroport-factory", + "astroport-governance 3.0.0", + "astroport-incentives", + "astroport-pair", + "astroport-voting-escrow", + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test 1.1.0", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw20-base", + "derivative", + "itertools 0.12.1", + "serde_json", + "thiserror", +] + +[[package]] +name = "astroport-factory" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc4250f45bd4697476921a3533f151a73f7352c271b68ff411321006d5a4977" +dependencies = [ + "astroport 3.12.2", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw-utils 1.0.3", + "cw2 0.15.1", + "itertools 0.10.5", + "protobuf 2.28.0", + "thiserror", +] + +[[package]] +name = "astroport-governance" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72806ace350e81c4e1cab7e275ef91f05bad830275d697d67ad1bd4acc6f016d" +dependencies = [ + "astroport 2.9.5", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw20 0.15.1", +] + +[[package]] +name = "astroport-governance" +version = "3.0.0" +dependencies = [ + "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw20 1.1.2", + "itertools 0.12.1", + "thiserror", +] + +[[package]] +name = "astroport-ibc" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93c0ce66970f190873b30f862b0cd39fb0d8499678a1860446aa60d9618671f4" +dependencies = [ + "cosmwasm-schema", +] + +[[package]] +name = "astroport-incentives" +version = "1.2.0" +source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#ada68324a4cca143008646d3c81baa715cba12d2" +dependencies = [ + "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw20 1.1.2", + "itertools 0.12.1", + "thiserror", +] + +[[package]] +name = "astroport-pair" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4336db82506bc3aaf52e194711ed0785896ad92825eb18d870d535023e33b666" +dependencies = [ + "astroport 3.12.2", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw-utils 1.0.3", + "cw2 0.15.1", + "cw20 0.15.1", + "integer-sqrt", + "protobuf 2.28.0", + "thiserror", +] + +[[package]] +name = "astroport-staking" +version = "2.1.0" +source = "git+https://github.com/astroport-fi/hidden_astroport_core#02eafc677d9c402a55e45e956fd185d67ac3a8a6" +dependencies = [ + "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core)", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "osmosis-std", + "thiserror", +] + +[[package]] +name = "astroport-tokenfactory-tracker" +version = "1.0.0" +source = "git+https://github.com/astroport-fi/hidden_astroport_core#02eafc677d9c402a55e45e956fd185d67ac3a8a6" +dependencies = [ + "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core)", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw2 1.1.2", + "thiserror", +] + +[[package]] +name = "astroport-voting-escrow" +version = "1.0.0" +dependencies = [ + "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "astroport-governance 3.0.0", + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test 1.1.0", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "cw20 1.1.2", + "cw20-base", + "thiserror", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bnum" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56953345e39537a3e18bdaeba4cb0c58a78c1f61f361dc0fa7c5c7340ae87c5f" + +[[package]] +name = "builder-unlock" +version = "3.0.0" +dependencies = [ + "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "astroport-governance 3.0.0", + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "thiserror", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +dependencies = [ + "serde", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "num-traits", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cosmos-sdk-proto" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32560304ab4c365791fd307282f76637213d8083c1a98490c35159cd67852237" +dependencies = [ + "prost 0.12.4", + "prost-types 0.12.4", + "tendermint-proto", +] + +[[package]] +name = "cosmwasm-crypto" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd50718a2b6830ce9eb5d465de5a018a12e71729d66b70807ce97e6dd14f931d" +dependencies = [ + "digest 0.10.7", + "ecdsa", + "ed25519-zebra", + "k256", + "rand_core 0.6.4", + "thiserror", +] + +[[package]] +name = "cosmwasm-derive" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "242e98e7a231c122e08f300d9db3262d1007b51758a8732cd6210b3e9faa4f3a" +dependencies = [ + "syn 1.0.109", +] + +[[package]] +name = "cosmwasm-schema" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8467874827d384c131955ff6f4d47d02e72a956a08eb3c0ff24f8c903a5517b4" +dependencies = [ + "cosmwasm-schema-derive", + "schemars", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cosmwasm-schema-derive" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6db85d98ac80922aef465e564d5b21fa9cfac5058cb62df7f116c3682337393" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "cosmwasm-std" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c1556156fdf892a55cced6115968b961eaaadd6f724a2c2cb7d1e168e32dd3" +dependencies = [ + "base64", + "bech32 0.9.1", + "bnum", + "cosmwasm-crypto", + "cosmwasm-derive", + "derivative", + "forward_ref", + "hex", + "schemars", + "serde", + "serde-json-wasm 0.5.2", + "sha2 0.10.8", + "static_assertions", + "thiserror", +] + +[[package]] +name = "cosmwasm-storage" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66de2ab9db04757bcedef2b5984fbe536903ada4a8a9766717a4a71197ef34f6" +dependencies = [ + "cosmwasm-std", + "serde", +] + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "cw-address-like" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451a4691083a88a3c0630a8a88799e9d4cd6679b7ce8ff22b8da2873ff31d380" +dependencies = [ + "cosmwasm-std", +] + +[[package]] +name = "cw-asset" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c999a12f8cd8736f6f86e9a4ede5905530cb23cfdef946b9da1c506ad1b70799" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-address-like", + "cw-storage-plus 1.2.0", + "cw20 1.1.2", + "thiserror", +] + +[[package]] +name = "cw-multi-test" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67fff029689ae89127cf6d7655809a68d712f3edbdb9686c70b018ba438b26ca" +dependencies = [ + "anyhow", + "bech32 0.9.1", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "derivative", + "itertools 0.12.1", + "prost 0.12.4", + "schemars", + "serde", + "sha2 0.10.8", + "thiserror", +] + +[[package]] +name = "cw-multi-test" +version = "0.20.0" +source = "git+https://github.com/astroport-fi/cw-multi-test?branch=feat/bank_with_send_hooks#3220f4cd126b5a8da2ce8b00152afef046a9b391" +dependencies = [ + "anyhow", + "bech32 0.9.1", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "derivative", + "itertools 0.12.1", + "prost 0.12.4", + "schemars", + "serde", + "sha2 0.10.8", + "thiserror", +] + +[[package]] +name = "cw-multi-test" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ffa9e3bae206540c084198e5be5aea2ecb1f2597f79dc09263b528ea0604788" +dependencies = [ + "anyhow", + "bech32 0.11.0", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "derivative", + "itertools 0.12.1", + "prost 0.12.4", + "schemars", + "serde", + "sha2 0.10.8", + "thiserror", +] + +[[package]] +name = "cw-storage-plus" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6cf70ef7686e2da9ad7b067c5942cd3e88dd9453f7af42f54557f8af300fb0" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "cw-storage-plus" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5ff29294ee99373e2cd5fd21786a3c0ced99a52fec2ca347d565489c61b723c" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "cw-utils" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae0b69fa7679de78825b4edeeec045066aa2b2c4b6e063d80042e565bb4da5c" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw2 0.15.1", + "schemars", + "semver", + "serde", + "thiserror", +] + +[[package]] +name = "cw-utils" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c4a657e5caacc3a0d00ee96ca8618745d050b8f757c709babafb81208d4239c" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw2 1.1.2", + "schemars", + "semver", + "serde", + "thiserror", +] + +[[package]] +name = "cw2" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5abb8ecea72e09afff830252963cb60faf945ce6cef2c20a43814516082653da" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "schemars", + "serde", +] + +[[package]] +name = "cw2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6c120b24fbbf5c3bedebb97f2cc85fbfa1c3287e09223428e7e597b5293c1fa" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "schemars", + "semver", + "serde", + "thiserror", +] + +[[package]] +name = "cw20" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6025276fb6e603e974c21f3e4606982cdc646080e8fba3198816605505e1d9a" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 0.15.1", + "schemars", + "serde", +] + +[[package]] +name = "cw20" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "526e39bb20534e25a1cd0386727f0038f4da294e5e535729ba3ef54055246abd" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 1.0.3", + "schemars", + "serde", +] + +[[package]] +name = "cw20-base" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ad79e86ea3707229bf78df94e08732e8f713207b4a77b2699755596725e7d9" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw2 1.1.2", + "cw20 1.1.2", + "schemars", + "semver", + "serde", + "thiserror", +] + +[[package]] +name = "cw3" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2967fbd073d4b626dd9e7148e05a84a3bebd9794e71342e12351110ffbb12395" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 1.0.3", + "cw20 1.1.2", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519-zebra" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" +dependencies = [ + "curve25519-dalek", + "hashbrown", + "hex", + "rand_core 0.6.4", + "serde", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "either" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "flex-error" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b" +dependencies = [ + "paste", +] + +[[package]] +name = "forward_ref" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "ibc-controller-package" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfcf94f5691716bfecb45e6bb6a82a5c11a392d501c2a695589c5087671f7c33" +dependencies = [ + "astroport-governance 1.2.0", + "astroport-ibc", + "cosmwasm-schema", + "cosmwasm-std", +] + +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "k256" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2 0.10.8", + "signature", +] + +[[package]] +name = "libc" +version = "0.2.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" + +[[package]] +name = "neutron-sdk" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26fb513aae0c82b3185228e96664d8312e79c3aa763f6ebdc70cf4b8d513d533" +dependencies = [ + "bech32 0.9.1", + "cosmos-sdk-proto", + "cosmwasm-schema", + "cosmwasm-std", + "prost 0.12.4", + "prost-types 0.12.4", + "protobuf 3.4.0", + "schemars", + "serde", + "serde-json-wasm 1.0.1", + "serde_json", + "speedate", + "tendermint-proto", + "thiserror", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "osmosis-std" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e87adf61f03306474ce79ab322d52dfff6b0bcf3aed1e12d8864ac0400dec1bf" +dependencies = [ + "chrono", + "cosmwasm-std", + "osmosis-std-derive", + "prost 0.12.4", + "prost-types 0.12.4", + "schemars", + "serde", + "serde-cw-value", +] + +[[package]] +name = "osmosis-std-derive" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5ebdfd1bc8ed04db596e110c6baa9b174b04f6ed1ec22c666ddc5cb3fa91bd7" +dependencies = [ + "itertools 0.10.5", + "proc-macro2", + "prost-types 0.11.9", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +dependencies = [ + "bytes", + "prost-derive 0.11.9", +] + +[[package]] +name = "prost" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f5d036824e4761737860779c906171497f6d55681139d8312388f8fe398922" +dependencies = [ + "bytes", + "prost-derive 0.12.5", +] + +[[package]] +name = "prost-derive" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +dependencies = [ + "anyhow", + "itertools 0.10.5", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "prost-derive" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9554e3ab233f0a932403704f1a1d08c30d5ccd931adfdfa1e8b5a19b52c1d55a" +dependencies = [ + "anyhow", + "itertools 0.10.5", + "proc-macro2", + "quote", + "syn 2.0.61", +] + +[[package]] +name = "prost-types" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +dependencies = [ + "prost 0.11.9", +] + +[[package]] +name = "prost-types" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3235c33eb02c1f1e212abdbe34c78b264b038fb58ca612664343271e36e55ffe" +dependencies = [ + "prost 0.12.4", +] + +[[package]] +name = "protobuf" +version = "2.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" +dependencies = [ + "bytes", +] + +[[package]] +name = "protobuf" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58678a64de2fced2bdec6bca052a6716a0efe692d6e3f53d1bda6a1def64cfc0" +dependencies = [ + "once_cell", + "protobuf-support", + "thiserror", +] + +[[package]] +name = "protobuf-support" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1ed294a835b0f30810e13616b1cd34943c6d1e84a8f3b0dcfe466d256c3e7e7" +dependencies = [ + "thiserror", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schemars" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6e7ed6919cb46507fb01ff1654309219f62b4d603822501b0b80d42f6f21ef" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185f2b7aa7e02d418e453790dde16890256bbd2bcd04b7dc5348811052b53f49" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.61", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.201" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-cw-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75d32da6b8ed758b7d850b6c3c08f1d7df51a4df3cb201296e63e34a78e99d4" +dependencies = [ + "serde", +] + +[[package]] +name = "serde-json-wasm" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e9213a07d53faa0b8dd81e767a54a8188a242fdb9be99ab75ec576a774bfdd7" +dependencies = [ + "serde", +] + +[[package]] +name = "serde-json-wasm" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05da0d153dd4595bdffd5099dc0e9ce425b205ee648eb93437ff7302af8c9a5" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_bytes" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.201" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.61", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.61", +] + +[[package]] +name = "serde_json" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "speedate" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "242f76c50fd18cbf098607090ade73a08d39cfd84ea835f3796a2c855223b19b" +dependencies = [ + "strum", + "strum_macros", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.61", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "subtle-encoding" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945" +dependencies = [ + "zeroize", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tendermint-proto" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b797dd3d2beaaee91d2f065e7bdf239dc8d80bba4a183a288bc1279dd5a69a1e" +dependencies = [ + "bytes", + "flex-error", + "num-derive", + "num-traits", + "prost 0.12.4", + "prost-types 0.12.4", + "serde", + "serde_bytes", + "subtle-encoding", + "time", +] + +[[package]] +name = "test-case" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 2.0.61", +] + +[[package]] +name = "test-case-macros" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.61", + "test-case-core", +] + +[[package]] +name = "thiserror" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.61", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/Cargo.toml b/Cargo.toml index 0e3819e9..2a32324a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,12 +2,7 @@ resolver = "2" members = [ "packages/*", - "contracts/assembly", - "contracts/voting_escrow", - "contracts/builder_unlock", - # "contracts/generator_controller_lite", - # "contracts/hub", - # "contracts/outpost", + "contracts/*" ] [workspace.dependencies] @@ -18,6 +13,7 @@ thiserror = "1.0" itertools = "0.12" cosmwasm-schema = "1.5" cw-utils = "1" +astroport = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "4" } [profile.release] opt-level = "z" diff --git a/INTERCHAIN.md b/INTERCHAIN.md deleted file mode 100644 index ca158b4d..00000000 --- a/INTERCHAIN.md +++ /dev/null @@ -1,216 +0,0 @@ -# Interchain governance - -To enable interchain governance from any Outpost, we deploy two contracts. The Hub contract on the Hub chain and the Outpost contract on the Outpost chain. The Hub chain is defined as the chain where Assembly and the ASTRO token is deployed. - -This enables the following actions: - -1. Stake ASTRO -2. Unstake xASTRO -3. Vote in governance -4. Vote on emissions through vxASTRO - -This document covers the flow of messages, permissioned IBC channels and how failures are handled to make this as safe as possible for a user as well as the protocol itself. - -## Architecture - -To enable interchain governance, the following contracts need to be deployed: - -### Hub - -1. Hub -2. CW20-ICS20 with memo handler support -3. Assembly -4. ASTRO token -5. Staking -6. xASTRO token -7. Generator Controller -8. vxASTRO - -Technical details on the Hub is available [in this document](contracts/hub/README.md). - -### Outpost - -1. Outpost -2. Outpost xASTRO with timestamp balance tracking -3. vxASTRO - -Technical details on the Outpost is available [in this document](contracts/outpost/README.md). - -### IBC - -The following diagram shows the IBC connections between these contracts: - -![IBC connections](assets/interchain-ibc-channels.png) -*IBC connections diagram* - -The CW20-ICS20 contract allows CW20 tokens to be transferred to other chains where they end up as standard IBC tokens. This requires a contract-to-transfer channel, that is, a channel between the contract port `wasm.wasm12345...` and the counterparty chain's `transfer` port. - -The Hub and Outpost requires a contract-to-contract channel, that is, a channel between the Hub contract port `wasm.wasm123hub45...` and the Outpost's contract port `wasm.wasm123outpost45...`. Both contracts contain configuration parameters that will **only** allow these two contracts to communicate - it is not possible to send messages to either contract outside of this secured channel. - -Do note that the Hub may have multiple Outposts configured at any time, but the Outpost may only have a single Hub configured. - -## Flow of messages - -### Prerequisite IBC knowledge - -1. In an IBC transfer (or sending of a message) the initial transaction might succeed on the source chain but never make it to the destination resulting in a timeout. - -2. When an IBC message is received by the destination, they reply with an acknowledgement message. This may indicate success or failure on the destination side and may be handled by the source. - - -### Staking ASTRO from an Outpost - -> xASTRO on an Outpost is using a custom CW20 contract that tracks balances by timestamp instead of sending the tokens over IBC. We require this to verify xASTRO holdings at the time a proposal was created to vote in governance. - -In order to stake ASTRO from an Outpost, the user must meet the following conditions: - -1. ASTRO tokens transferred over the official CW20-ICS20 channel - -![Stake ASTRO from an Outpost](assets/interchain-stake-astro.png) -*Stake ASTRO from an Outpost* - -The flow is as follows: - -1. A user sends IBC ASTRO to the chain's IBC `transfer` channel with a memo indicating they want to stake the tokens. See [the Hub's messages for details](contracts/hub/README.md) -2. The CW20-ICS20 contract forwards the ASTRO and memo to the Hub contract -3. For staking, sends the ASTRO to the staking contract -4. xASTRO is minted -5. xASTRO is sent back to the Hub contract -6. Issue an Outpost IBC message to mint the corresponding amount of xASTRO on the Outpost -7. The Outpost contract mints the xASTRO to the original sender's address - -Failure scenarios and how they are handled: - -1. Initial IBC transfer fails or experiences a timeout - - The funds are returned to the user by the CW20-ICS20 contract - -2. Handling the memo, ASTRO staking, xASTRO minting or Outpost minting message fails - - The entire flow is reverted and the ASTRO is sent back to the user by the CW20-ICS20 contract - -3. Minting of xASTRO on Outpost fails or experiences a timeout - - The staked xASTRO is unstaked and the resulting ASTRO is sent back to the initial staker. - -4. IBC transfer of ASTRO back to the user succeeds, but experiences a timeout or transfer fails - - The ASTRO is sent back to the Hub contract together with the intended recipient of the funds. The Hub will hold these funds on behalf of the user, but exposes a path to allow the retry/withdrawal of the funds. The funds are **not** lost. - - -### Voting in governance from an Outpost - -In order to vote in governance from an Outpost, the user must meet the following conditions: - -1. xASTRO tokens on the Outpost -2. The xASTRO tokens must have been held at the time the proposal was created - -![Vote in governance from an Outpost](assets/interchain-governance-voting.png) -*Vote in governance from an Outpost* - -The flow is as follows: - -1. A user submits a vote to the Outpost contract. See [the Outpost's messages for details](contracts/outpost/README.md) -2. The Outpost checks the voting power of the user at the time of proposal creation by doing three things - * If the proposal is cached already, use the timestamp. If not, query the Hub for the proposal information - * Query the xASTRO contract for the user's holdings at the proposal creation time - * Query the vxASTRO contract for the user's xASTRO deposits at the proposal creation time -3. Submit vote with voting power to the Hub -4. Hub casts the vote in the Assembly on behalf of the user - -Failure scenarios and how they are handled: - -1. Initial vote fails or experiences a timeout - - An error is returned in the contract and written as attributes - -2. Casting of vote on Hub or Assembly fails - - An error is returned in the contract and written as attributes - -### Voting on emissions from an Outpost (vxASTRO) - -In order to vote on emissions from an Outpost, the user must meet the following conditions: - -1. xASTRO tokens locked in the vxASTRO contract on the Outpost - -![Vote on emissions from an Outpost](assets/interchain-emissions-voting.png) -*Vote on emissions from an Outpost* - -The flow is as follows: - -1. A user submits a vote to the Outpost contract. See [the Outpost's messages for details](contracts/outpost/README.md) -2. The Outpost checks the current voting power of the user by querying the vxASTRO contract -3. Submit vote with voting power to the Hub -4. Hub casts the vote in the Generator Controller on behalf of the user - -Failure scenarios and how they are handled: - -1. Initial vote fails or experiences a timeout - - An error is returned in the contract and written as attributes - -2. Casting of vote on Hub or Generator Controller fails - - An error is returned in the contract and written as attributes - - - -### Withdraw funds from the Hub - -In order to withdraw funds from the Hub, the user must meet the following conditions: - -1. The user must have ASTRO stuck on the Hub - -![Withdraw funds from the Hub](assets/interchain-withdraw-funds.png) -*Withdraw funds from the Hub* - -The flow is as follows: - -1. A user submits a request for withdrawal to the Outpost contract. See [the Outpost's messages for details](contracts/outpost/README.md) -2. The Outpost sends the request to the Hub over IBC -3. Hub checks if the original sender on the Outpost has funds -4. If the user has funds, send _everything_ to the user through the CW20-ICS20 contract -5. User receives IBC funds - -Failure scenarios and how they are handled: - -1. Initial request fails or experiences a timeout - - An error is returned in the contract and written as attributes - -2. Checking for funds fails or the user has no funds on the Hub - - An error is returned in the contract and written as attributes - -2. The transfer of funds fail or experiences a timeout - - The funds are returned to the Hub contract and captured against the original sender's address again - - -### Unlock vxASTRO on Outpost - -When vxASTRO is unlocked, the votes cast by the user previously need to be removed from the Generator controller on the Hub. - -![Unlock on Outpost](assets/interchain-unlock.png) -*Unlock on Outpost* - -The flow is as follows: - -1. Submit the unlock to the vxASTRO contract -2. The unlock will be processed and the kick message sent to the Outpost -3. The Outpost will forward the unlock the the Hub -4. The Hub will execute the kick on the Generator controller -5. When the votes have been removed, the confirmation is returned to the Hub -6. The Hub writes back the IBC acknowledgement containing the success -7. The Outpost receives the IBC acknowledgement and starts the actual unlock period on vxASTRO - -Failure scenarios and how they are handled: - -1. Initial request fails or experiences a timeout - - An error is returned in the contract and written as attributes - -2. Failing to remove votes - - An error IBC acknowledgement is returned and the vxASTRO does not start unlocking diff --git a/README.md b/README.md index 92769fcb..03d0c7fe 100644 --- a/README.md +++ b/README.md @@ -10,21 +10,22 @@ This repo contains Astroport Governance related contracts. ## Contracts -| Name | Description | -| ------------------------------ | -------------------------------- | -| [`assembly`](contracts/assembly) | The Astral Assembly governance contract | -| [`builder_unlock`](contracts/builder_unlock) | ASTRO unlock/vesting contract for Initial Builders | -| [`escrow_fee_distributor`](contracts/escrow_fee_distributor) | vxASTRO fee distributor | -| [`generator_controller`](contracts/generator_controller) | Generator Controller used to vote on directing ASTRO emissions | -| [`voting_escrow`](contracts/voting_escrow) | vxASTRO contract | +| Name | Description | +|--------------------------------------------------------------|----------------------------------------------------------------| +| [`assembly`](contracts/assembly) | The Astral Assembly governance contract | +| [`builder_unlock`](contracts/builder_unlock) | ASTRO unlock/vesting contract for Initial Builders | +| [`escrow_fee_distributor`](contracts/escrow_fee_distributor) | vxASTRO fee distributor | +| [`generator_controller`](contracts/emissions_controller) | Generator Controller used to vote on directing ASTRO emissions | +| [`voting_escrow`](contracts/voting_escrow) | vxASTRO contract | ## Building Contracts You will need Rust 1.64.0+ with wasm32-unknown-unknown target installed. ### You can compile each contract: -Go to contract directory and run - + +Go to contract directory and run + ``` cargo wasm cp ../../target/wasm32-unknown-unknown/release/astroport_token.wasm . @@ -33,6 +34,7 @@ sha256sum astroport_token.wasm ``` ### You can run tests for all contracts + Run the following from the repository root ``` @@ -40,6 +42,7 @@ cargo test ``` ### For a production-ready (compressed) build: + Run the following from the repository root ``` @@ -50,8 +53,8 @@ The optimized contracts are generated in the artifacts/ directory. ## Deployment -You can find versions and commits for actually deployed contracts [here](https://github.com/astroport-fi/astroport-changelog). - +You can find versions and commits for actually deployed +contracts [here](https://github.com/astroport-fi/astroport-changelog). ## Docs diff --git a/contracts/assembly/Cargo.toml b/contracts/assembly/Cargo.toml index f4b89842..e63fd9f7 100644 --- a/contracts/assembly/Cargo.toml +++ b/contracts/assembly/Cargo.toml @@ -24,8 +24,8 @@ thiserror.workspace = true cosmwasm-schema.workspace = true cw-utils.workspace = true astroport-governance = { path = "../../packages/astroport-governance", version = "3" } -astroport = { git = "https://github.com/astroport-fi/hidden_astroport_core", version = "4" } -astro-satellite = "1.1.0" +astroport.workspace = true +astro-satellite = { version = "1.1.0", features = ["library"] } ibc-controller-package = "1.0.0" [dev-dependencies] diff --git a/contracts/builder_unlock/Cargo.toml b/contracts/builder_unlock/Cargo.toml index 3e8f1206..7deaeb6b 100644 --- a/contracts/builder_unlock/Cargo.toml +++ b/contracts/builder_unlock/Cargo.toml @@ -23,7 +23,7 @@ cw-storage-plus.workspace = true cosmwasm-schema.workspace = true thiserror.workspace = true astroport-governance = { path = "../../packages/astroport-governance", version = "3" } -astroport = { git = "https://github.com/astroport-fi/hidden_astroport_core", version = "4" } +astroport.workspace = true [dev-dependencies] cw-multi-test = "0.20" \ No newline at end of file diff --git a/contracts/emissions_controller/Cargo.toml b/contracts/emissions_controller/Cargo.toml new file mode 100644 index 00000000..7f6f5858 --- /dev/null +++ b/contracts/emissions_controller/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "astroport-emissions-controller" +version = "1.0.0" +authors = ["Astroport"] +edition = "2021" +description = "Astroport vxASTRO Emissions Voting Contract" +license = "GPL-3.0-only" +repository = "https://github.com/astroport-fi/astroport-governance" +homepage = "https://astroport.fi" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# use library feature to disable all init/handle/query exports +library = [] + +[dependencies] +cw2.workspace = true +cw-utils.workspace = true +cosmwasm-std = { workspace = true, features = ["ibc3"] } +cw-storage-plus.workspace = true +cosmwasm-schema.workspace = true +thiserror.workspace = true +itertools.workspace = true +astroport-governance = { path = "../../packages/astroport-governance", version = "3" } +astroport = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "4" } +neutron-sdk = "0.10.0" +serde_json = "1" + +[dev-dependencies] +cw-multi-test = "1" +astroport-voting-escrow = { path = "../voting_escrow", version = "1", features = ["library"] } +astroport-factory = { version = "1.7", features = ["library"] } +astroport-pair = { version = "1.5", features = ["library"] } +cw20-base = { version = "1", features = ["library"] } +astroport-incentives = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "1.2", features = ["library"] } +derivative = "2.2" diff --git a/contracts/generator_controller_lite/README.md b/contracts/emissions_controller/README.md similarity index 76% rename from contracts/generator_controller_lite/README.md rename to contracts/emissions_controller/README.md index 95be0ce1..bc3d804b 100644 --- a/contracts/generator_controller_lite/README.md +++ b/contracts/emissions_controller/README.md @@ -1,6 +1,7 @@ -# Generator Controller +# Emissions Controller -The Generator Controller allows vxASTRO holders to vote on changing `alloc_point`s in the Generator contract every 2 weeks. Note that the Controller contract uses the word "pool" when referring to LP tokens (generators) available in the Generator contract. +The Emissions Controller allows vxASTRO holders to vote on changing `alloc_point`s in the Generator contract every 2 +weeks. Note that the Controller contract uses the word "pool" when referring to LP tokens. ## InstantiateMsg @@ -26,19 +27,10 @@ Remove votes of voters that are blacklisted. ```json { "kick_blacklisted_voters": { - "blacklisted_voters": ["wasm...", "wasm..."] - } -} -``` - -### `kick_unlocked_voters` - -Remove votes of voters that have unlocked their vxASTRO. - -```json -{ - "kick_unlocked_voters": { - "unlocked_voters": ["wasm...", "wasm..."] + "blacklisted_voters": [ + "wasm...", + "wasm..." + ] } } ``` @@ -59,8 +51,9 @@ Sets various configuration parameters. Any of them can be omitted. ### `vote` -Vote on pools that will start to get an ASTRO distribution in the current period. For example, assume an address has voting -power `100`. Then, following the example below, pools will receive voting power 10, 50, 40 respectively. Note that all values are scaled so they sum to 10,000. +Vote on pools that will start to get an ASTRO distribution in the next period. For example, assume an address has voting +power `100`. Then, following the example below, pools will receive voting power 10, 50, 40 respectively. Note that all +values are scaled so they sum to 10,000. ```json { @@ -147,33 +140,12 @@ Adds or removes lp tokens which are eligible to receive votes. { "update_whitelist": { "add": [ - "wasm...", - "wasm..." - ], - "remove": [ "wasm...", "wasm..." - ] - } -} -``` - -### `update_networks` - -Adds or removes network mappings for tuning pools on remote chains. - -```json -{ - "update_networks": { - "add": [ - { - "address_prefix": "wasm", - "generator_address": "wasm124tapgv8wsn5t3rv2cvywhxxxxxxxxx", - "ibc_channel": "channel-1" - } ], "remove": [ - "wasm", + "wasm...", + "wasm..." ] } } @@ -202,8 +174,8 @@ Returns last user's voting parameters. "user_info_response": { "vote_ts": 1234567, "voting_power": 100, - "slope": 0, - "lock_end": 0, + "slope": 15.45, + "lock_end": 10, "votes": [ [ "wasm...", @@ -264,7 +236,7 @@ Response: { "voted_pool_info_response": { "vxastro_amount": 1000, - "slope": 0 + "slope": 10.2 } } ``` @@ -290,7 +262,7 @@ Response: { "voted_pool_info_response": { "vxastro_amount": 1000, - "slope": 0 + "slope": 10.2 } } ``` diff --git a/contracts/emissions_controller/examples/emissions_controller_schema.rs b/contracts/emissions_controller/examples/emissions_controller_schema.rs new file mode 100644 index 00000000..f16328e0 --- /dev/null +++ b/contracts/emissions_controller/examples/emissions_controller_schema.rs @@ -0,0 +1,12 @@ +use cosmwasm_schema::write_api; + +use astroport_governance::emissions_controller::hub::{HubInstantiateMsg, HubMsg, QueryMsg}; +use astroport_governance::emissions_controller::msg::ExecuteMsg; + +fn main() { + write_api! { + instantiate: HubInstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg + } +} diff --git a/contracts/emissions_controller/src/error.rs b/contracts/emissions_controller/src/error.rs new file mode 100644 index 00000000..684ec66f --- /dev/null +++ b/contracts/emissions_controller/src/error.rs @@ -0,0 +1,82 @@ +use cosmwasm_std::{Coin, StdError}; +use cw_utils::{ParseReplyError, PaymentError}; +use neutron_sdk::NeutronError; +use thiserror::Error; + +use astroport_governance::emissions_controller::consts::MAX_POOLS_TO_VOTE; + +/// This enum describes contract errors +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + PaymentError(#[from] PaymentError), + + #[error("{0}")] + NeutronError(#[from] NeutronError), + + #[error("{0}")] + ParseReplyError(#[from] ParseReplyError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("You can't vote with zero voting power")] + ZeroVotingPower {}, + + #[error("Next time you can change your vote is at {0}")] + VoteCooldown(u64), + + #[error("Next tuning will be available at {0}")] + TuneCooldown(u64), + + #[error("Pool {0} is not whitelisted")] + PoolIsNotWhitelisted(String), + + #[error("Incorrect whitelist fee. Expected {0}")] + IncorrectWhitelistFee(Coin), + + #[error("Pool {0} is already whitelisted")] + PoolAlreadyWhitelisted(String), + + #[error("Invalid total votes weight. Must be 1.")] + InvalidTotalWeight {}, + + #[error("Failed to parse reply")] + FailedToParseReply {}, + + #[error("Invalid outpost prefix for {0}")] + InvalidOutpostPrefix(String), + + #[error("Invalid ASTRO denom for outpost. Must start with ibc/")] + InvalidOutpostAstroDenom {}, + + #[error("Invalid ASTRO denom on the Hub. Must be {0}")] + InvalidHubAstroDenom(String), + + #[error("Invalid ics20 channel. Must start with channel-")] + InvalidOutpostIcs20Channel {}, + + #[error("Failed to determine outpost for pool {0}")] + NoOutpostForPool(String), + + #[error("Unknown IBC packet data")] + UnknownIbcPacketData {}, + + #[error("You can vote maximum for {MAX_POOLS_TO_VOTE} pools")] + ExceededMaxPoolsToVote {}, + + #[error("Message contains duplicated pools")] + DuplicatedVotes {}, + + #[error("Astro pool can't be whitelisted")] + IsAstroPool {}, + + #[error("No failed outposts to retry")] + NoFailedOutpostsToRetry {}, + + #[error("Can't set zero emissions for astro pool")] + ZeroAstroEmissions {}, +} diff --git a/contracts/emissions_controller/src/execute.rs b/contracts/emissions_controller/src/execute.rs new file mode 100644 index 00000000..02d30d63 --- /dev/null +++ b/contracts/emissions_controller/src/execute.rs @@ -0,0 +1,773 @@ +use std::collections::{HashMap, HashSet}; + +use astroport::asset::{determine_asset_info, validate_native_denom}; +use astroport::common::{claim_ownership, drop_ownership_proposal, propose_new_owner}; +use astroport::incentives; +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + attr, ensure, wasm_execute, BankMsg, Coin, CosmosMsg, Decimal, DepsMut, Env, Fraction, + MessageInfo, Order, Response, StdError, StdResult, Storage, Uint128, +}; +use cw_utils::{must_pay, nonpayable}; +use itertools::Itertools; +use neutron_sdk::bindings::msg::NeutronMsg; +use neutron_sdk::bindings::query::NeutronQuery; + +use astroport_governance::emissions_controller::consts::{ + EPOCH_LENGTH, MAX_POOLS_TO_VOTE, VOTE_COOLDOWN, +}; +use astroport_governance::emissions_controller::hub::{ + AstroPoolConfig, HubMsg, OutpostInfo, OutpostParams, OutpostStatus, TuneInfo, UserInfo, + VotedPoolInfo, +}; +use astroport_governance::emissions_controller::msg::ExecuteMsg; +use astroport_governance::emissions_controller::utils::{ + check_lp_token, get_voting_power, query_incentives_addr, +}; +use astroport_governance::utils::check_contract_supports_channel; +use astroport_governance::voting_escrow; + +use crate::error::ContractError; +use crate::state::{ + CONFIG, OUTPOSTS, OWNERSHIP_PROPOSAL, POOLS_WHITELIST, TUNE_INFO, USER_INFO, VOTED_POOLS, +}; +use crate::utils::{ + astro_emissions_curve, build_emission_ibc_msg, determine_outpost_prefix, get_epoch_start, + get_outpost_prefix, min_ntrn_ibc_fee, raw_emissions_to_schedules, validate_outpost_prefix, +}; + +/// Exposes all the execute functions available in the contract. +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result, ContractError> { + match msg { + ExecuteMsg::Vote { votes } => { + nonpayable(&info)?; + let votes_map: HashMap<_, _> = votes.iter().cloned().collect(); + ensure!( + votes.len() == votes_map.len(), + ContractError::DuplicatedVotes {} + ); + ensure!( + votes_map.len() <= MAX_POOLS_TO_VOTE, + ContractError::ExceededMaxPoolsToVote {} + ); + let deps = deps.into_empty(); + let config = CONFIG.load(deps.storage)?; + let voting_power = get_voting_power(deps.querier, &config.vxastro, &info.sender, None)?; + ensure!(!voting_power.is_zero(), ContractError::ZeroVotingPower {}); + + handle_vote(deps, env, info.sender.as_str(), voting_power, votes_map) + } + ExecuteMsg::UpdateUserVotes { user, is_unlock } => { + let config = CONFIG.load(deps.storage)?; + ensure!( + info.sender == config.vxastro, + ContractError::Unauthorized {} + ); + let voter = deps.api.addr_validate(&user)?; + let deps = deps.into_empty(); + + let voting_power = get_voting_power(deps.querier, &config.vxastro, &voter, None)?; + handle_update_user(deps.storage, env, voter.as_str(), voting_power).and_then( + |response| { + if is_unlock { + let confirm_unlock_msg = wasm_execute( + config.vxastro, + &voting_escrow::ExecuteMsg::ConfirmUnlock { + user: voter.to_string(), + }, + vec![], + )?; + Ok(response.add_message(confirm_unlock_msg)) + } else { + Ok(response) + } + }, + ) + } + ExecuteMsg::RefreshUserVotes {} => { + nonpayable(&info)?; + let config = CONFIG.load(deps.storage)?; + let deps = deps.into_empty(); + + let voting_power = get_voting_power(deps.querier, &config.vxastro, &info.sender, None)?; + + ensure!(!voting_power.is_zero(), ContractError::ZeroVotingPower {}); + handle_update_user(deps.storage, env, info.sender.as_str(), voting_power) + } + ExecuteMsg::ProposeNewOwner { + new_owner, + expires_in, + } => { + nonpayable(&info)?; + let config = CONFIG.load(deps.storage)?; + + propose_new_owner( + deps, + info, + env, + new_owner, + expires_in, + config.owner, + OWNERSHIP_PROPOSAL, + ) + .map_err(Into::into) + } + ExecuteMsg::DropOwnershipProposal {} => { + nonpayable(&info)?; + let config = CONFIG.load(deps.storage)?; + + drop_ownership_proposal(deps, info, config.owner, OWNERSHIP_PROPOSAL) + .map_err(Into::into) + } + ExecuteMsg::ClaimOwnership {} => { + nonpayable(&info)?; + claim_ownership(deps, info, env, OWNERSHIP_PROPOSAL, |deps, new_owner| { + CONFIG + .update::<_, StdError>(deps.storage, |mut v| { + v.owner = new_owner; + Ok(v) + }) + .map(|_| ()) + }) + .map_err(Into::into) + } + ExecuteMsg::Custom(hub_msg) => match hub_msg { + HubMsg::WhitelistPool { pool } => whitelist_pool(deps, env, info, pool), + HubMsg::UpdateOutpost { + prefix, + astro_denom, + outpost_params, + astro_pool_config, + } => update_outpost( + deps, + env, + info, + prefix, + astro_denom, + outpost_params, + astro_pool_config, + ), + HubMsg::RemoveOutpost { prefix } => remove_outpost(deps, env, info, prefix), + HubMsg::TunePools {} => tune_pools(deps, env), + HubMsg::RetryFailedOutposts {} => retry_failed_outposts(deps, info, env), + HubMsg::UpdateConfig { + pools_per_outpost: pools_limit, + whitelisting_fee, + fee_receiver, + } => update_config( + deps.into_empty(), + info, + pools_limit, + whitelisting_fee, + fee_receiver, + ), + }, + } +} + +/// Permissionless endpoint to whitelist a pool. +/// Requires a fee to be paid. +/// This endpoint is meant to be executed by users from the Hub or from other outposts via IBC hooks. +pub fn whitelist_pool( + deps: DepsMut, + env: Env, + info: MessageInfo, + pool: String, +) -> Result, ContractError> { + let deps = deps.into_empty(); + let config = CONFIG.load(deps.storage)?; + let amount = must_pay(&info, &config.whitelisting_fee.denom)?; + ensure!( + amount == config.whitelisting_fee.amount, + ContractError::IncorrectWhitelistFee(config.whitelisting_fee) + ); + + // Perform basic LP token validation. Ensure the outpost exists. + let outposts = OUTPOSTS + .range(deps.storage, None, None, Order::Ascending) + .collect::>>()?; + if let Some(prefix) = get_outpost_prefix(&pool, &outposts) { + if outposts.get(&prefix).unwrap().params.is_none() { + // Validate LP token on the Hub + determine_asset_info(&pool, deps.api) + .and_then(|maybe_lp| check_lp_token(deps.querier, &config.factory, &maybe_lp))? + } + } else { + return Err(ContractError::NoOutpostForPool(pool)); + } + + // Astro pools receive flat emissions hence we don't allow people to vote for them + ensure!( + outposts.values().all(|outpost_info| { + outpost_info + .astro_pool_config + .as_ref() + .map(|conf| conf.astro_pool != pool) + .unwrap_or(true) + }), + ContractError::IsAstroPool {} + ); + + POOLS_WHITELIST.update(deps.storage, |v| { + let mut pools: HashSet<_> = v.into_iter().collect(); + if !pools.insert(pool.clone()) { + return Err(ContractError::PoolAlreadyWhitelisted(pool.clone())); + }; + Ok(pools.into_iter().collect()) + })?; + + // Starting the voting process from scratch for this pool + VOTED_POOLS.save( + deps.storage, + &pool, + &VotedPoolInfo { + init_ts: env.block.time.seconds(), + voting_power: Uint128::zero(), + }, + env.block.time.seconds(), + )?; + + let send_fee_msg = BankMsg::Send { + to_address: config.fee_receiver.to_string(), + amount: info.funds, + }; + + Ok(Response::default() + .add_message(send_fee_msg) + .add_attributes([attr("action", "whitelist_pool"), attr("pool", &pool)])) +} + +/// Permissioned endpoint to add or update outpost. +/// Performs several simple checks to cut off possible human errors. +pub fn update_outpost( + deps: DepsMut, + env: Env, + info: MessageInfo, + prefix: String, + astro_denom: String, + outpost_params: Option, + astro_pool_config: Option, +) -> Result, ContractError> { + nonpayable(&info)?; + let config = CONFIG.load(deps.storage)?; + + ensure!(info.sender == config.owner, ContractError::Unauthorized {}); + + validate_native_denom(&astro_denom)?; + if let Some(conf) = &astro_pool_config { + validate_outpost_prefix(&conf.astro_pool, &prefix)?; + ensure!( + !conf.constant_emissions.is_zero(), + ContractError::ZeroAstroEmissions {} + ) + } + + if let Some(params) = &outpost_params { + validate_outpost_prefix(¶ms.emissions_controller, &prefix)?; + ensure!( + astro_denom.starts_with("ibc/") && astro_denom.len() == 68, + ContractError::InvalidOutpostAstroDenom {} + ); + check_contract_supports_channel( + deps.as_ref().into_empty().querier, + &env.contract.address, + ¶ms.voting_channel, + )?; + ensure!( + params.ics20_channel.starts_with("channel-"), + ContractError::InvalidOutpostIcs20Channel {} + ); + } else { + if let Some(conf) = &astro_pool_config { + deps.api.addr_validate(&conf.astro_pool)?; + } + ensure!( + astro_denom == config.astro_denom, + ContractError::InvalidHubAstroDenom(config.astro_denom) + ); + } + + OUTPOSTS.save( + deps.storage, + &prefix, + &OutpostInfo { + params: outpost_params, + astro_denom, + astro_pool_config, + }, + )?; + + Ok(Response::default().add_attributes([("action", "update_outpost"), ("prefix", &prefix)])) +} + +/// Removes outpost from the contract as well as all whitelisted +/// and being voted pools related to this outpost. +pub fn remove_outpost( + deps: DepsMut, + env: Env, + info: MessageInfo, + prefix: String, +) -> Result, ContractError> { + nonpayable(&info)?; + let config = CONFIG.load(deps.storage)?; + ensure!(info.sender == config.owner, ContractError::Unauthorized {}); + + // Remove all votable pools related to this outpost + let voted_pools = VOTED_POOLS + .keys(deps.storage, None, None, Order::Ascending) + .collect::>>()?; + let prefix_some = Some(prefix.clone()); + voted_pools + .iter() + .filter(|pool| determine_outpost_prefix(pool) == prefix_some) + .try_for_each(|pool| VOTED_POOLS.remove(deps.storage, pool, env.block.time.seconds()))?; + + // And clear whitelist + POOLS_WHITELIST.update::<_, StdError>(deps.storage, |mut whitelist| { + whitelist.retain(|pool| determine_outpost_prefix(pool) != prefix_some); + Ok(whitelist) + })?; + + OUTPOSTS.remove(deps.storage, &prefix); + + Ok(Response::default().add_attributes([("action", "remove_outpost"), ("prefix", &prefix)])) +} + +/// This permissionless function retries failed emission IBC messages. +pub fn retry_failed_outposts( + deps: DepsMut, + info: MessageInfo, + env: Env, +) -> Result, ContractError> { + nonpayable(&info)?; + let mut tune_info = TUNE_INFO.load(deps.storage)?; + let outposts = OUTPOSTS + .range(deps.storage, None, None, Order::Ascending) + .collect::>>()?; + + let mut attrs = vec![attr("action", "retry_failed_outposts")]; + let ibc_fee = min_ntrn_ibc_fee(deps.as_ref())?; + let config = CONFIG.load(deps.storage)?; + + let retry_msgs = tune_info + .outpost_emissions_statuses + .iter_mut() + .filter_map(|(outpost, status)| { + let outpost_info = outposts.get(outpost)?; + outpost_info.params.as_ref().and_then(|params| { + if *status == OutpostStatus::Failed { + let raw_schedules = tune_info.pools_grouped.get(outpost)?; + let (schedules, astro_funds) = raw_emissions_to_schedules( + &env, + raw_schedules, + &outpost_info.astro_denom, + &config.astro_denom, + ); + // Ignoring this outpost if it failed to serialize IbcHook msg for some reason + let msg = + build_emission_ibc_msg(&env, params, &ibc_fee, astro_funds, &schedules) + .ok()?; + + *status = OutpostStatus::InProgress; + attrs.push(attr("outpost", outpost)); + + Some(msg) + } else { + None + } + }) + }) + .collect_vec(); + + ensure!( + !retry_msgs.is_empty(), + ContractError::NoFailedOutpostsToRetry {} + ); + + TUNE_INFO.save(deps.storage, &tune_info, env.block.time.seconds())?; + + Ok(Response::new() + .add_messages(retry_msgs) + .add_attributes(attrs)) +} + +/// The function checks that: +/// * user didn't vote for the last 10 days, +/// * sum of all percentage values <= 1. +/// User can direct his voting power partially. +/// +/// The function cancels changes applied by previous votes and applies new votes for the next epoch. +/// New vote parameters are saved in [`USER_INFO`]. +/// +/// * **voter** is a voter address. +/// * **voting_power** is a user's voting power reported from the outpost. +/// * **votes** is a map LP token -> percentage of user's voting power to direct to this pool. +pub fn handle_vote( + deps: DepsMut, + env: Env, + voter: &str, + voting_power: Uint128, + votes: HashMap, +) -> Result, ContractError> { + let user_info = USER_INFO.may_load(deps.storage, voter)?.unwrap_or_default(); + let block_ts = env.block.time.seconds(); + // Is the user eligible to vote again? + ensure!( + user_info.vote_ts + VOTE_COOLDOWN <= block_ts, + ContractError::VoteCooldown(user_info.vote_ts + VOTE_COOLDOWN) + ); + + let mut total_weight = Decimal::zero(); + let whitelist: HashSet<_> = POOLS_WHITELIST.load(deps.storage)?.into_iter().collect(); + for (pool, weight) in &votes { + ensure!( + whitelist.contains(pool), + ContractError::PoolIsNotWhitelisted(pool.clone()) + ); + + total_weight += weight; + + ensure!( + total_weight <= Decimal::one(), + ContractError::InvalidTotalWeight {} + ); + } + + // Cancel previous user votes. Filter non-whitelisted pools. + let cache = user_info + .votes + .into_iter() + .filter(|(pool, _)| whitelist.contains(pool)) + .map(|(pool, weight)| { + let pool_info = VOTED_POOLS.load(deps.storage, &pool)?; + // Subtract old vote from pool voting power if pool wasn't reset to 0 + let pool_dedicated_vp = if pool_info.init_ts <= user_info.vote_ts { + user_info + .voting_power + .multiply_ratio(weight.numerator(), weight.denominator()) + } else { + Uint128::zero() + }; + Ok((pool, pool_info.with_sub_vp(pool_dedicated_vp))) + }) + .collect::>>()?; + + // Apply new votes with fresh user voting power. + votes + .iter() + .try_for_each(|(pool, weight)| -> StdResult<()> { + let pool_dedicated_vp = + voting_power.multiply_ratio(weight.numerator(), weight.denominator()); + + let pool_info = if let Some(pool_info) = cache.get(pool).cloned() { + pool_info + } else { + VOTED_POOLS.load(deps.storage, pool)? + }; + + VOTED_POOLS.save( + deps.storage, + pool, + &pool_info.with_add_vp(pool_dedicated_vp), + block_ts, + ) + })?; + + USER_INFO.save( + deps.storage, + voter, + &UserInfo { + vote_ts: block_ts, + voting_power, + votes, + }, + block_ts, + )?; + + Ok(Response::default() + .add_attributes([attr("action", "vote"), attr("voting_power", voting_power)])) +} + +/// This function updates existing user's voting power contribution in pool votes. +/// Is used to reflect user's vxASTRO balance changes in the emissions controller contract. +pub fn handle_update_user( + store: &mut dyn Storage, + env: Env, + voter: &str, + new_voting_power: Uint128, +) -> Result, ContractError> { + if let Some(user_info) = USER_INFO.may_load(store, voter)? { + let block_ts = env.block.time.seconds(); + + let whitelist: HashSet<_> = POOLS_WHITELIST.load(store)?.into_iter().collect(); + user_info + .votes + .iter() + .filter(|(pool, _)| whitelist.contains(pool.as_str())) + .try_for_each(|(pool, weight)| { + let pool_info = VOTED_POOLS.load(store, pool)?; + // Subtract old vote from pool voting power if pool wasn't reset to 0 + let pool_dedicated_vp = if pool_info.init_ts <= user_info.vote_ts { + user_info + .voting_power + .multiply_ratio(weight.numerator(), weight.denominator()) + } else { + Uint128::zero() + }; + let add_vp = + new_voting_power.multiply_ratio(weight.numerator(), weight.denominator()); + + let new_pool_info = pool_info.with_sub_vp(pool_dedicated_vp).with_add_vp(add_vp); + VOTED_POOLS.save(store, pool, &new_pool_info, block_ts) + })?; + + // Updating only voting power + USER_INFO.save( + store, + voter, + &UserInfo { + voting_power: new_voting_power, + ..user_info + }, + block_ts, + )?; + + Ok(Response::default().add_attributes([ + attr("action", "update_user_votes"), + attr("voter", voter), + attr("old_voting_power", user_info.voting_power), + attr("new_voting_power", new_voting_power), + ])) + } else { + Ok(Response::default()) + } +} + +/// The function checks that the last pools tuning happened >= 14 days ago. +/// Then it calculates voting power per each pool. +/// Filters all non-whitelisted pools, +/// takes top X pools by voting power, where X is +/// 'config.pools_per_outpost' * number of outposts, +/// calculates total ASTRO emission amount for upcoming epoch, +/// distributes it between selected pools +/// and sends emission messages to each outpost. +pub fn tune_pools( + deps: DepsMut, + env: Env, +) -> Result, ContractError> { + let tune_info = TUNE_INFO.load(deps.storage)?; + let block_ts = env.block.time.seconds(); + + ensure!( + tune_info.tune_ts + EPOCH_LENGTH <= block_ts, + ContractError::TuneCooldown(tune_info.tune_ts + EPOCH_LENGTH) + ); + + let voted_pools = VOTED_POOLS + .keys(deps.storage, None, None, Order::Ascending) + .collect::>>()?; + + let outposts = OUTPOSTS + .range(deps.storage, None, None, Order::Ascending) + .collect::>>()?; + + let epoch_start = get_epoch_start(block_ts); + + // Determine outpost prefix and filter out non-outpost pools. + let mut candidates = voted_pools + .iter() + .filter_map(|pool| get_outpost_prefix(pool, &outposts).map(|prefix| (prefix, pool))) + .map(|(prefix, pool)| { + let pool_vp = VOTED_POOLS + .may_load_at_height( + deps.storage, + pool, + epoch_start, // We need to get the VP at the begging of the epoch + )? + .map(|info| info.voting_power) + .unwrap_or_default(); + Ok((prefix, (pool, pool_vp))) + }) + .collect::>>()?; + + candidates.sort_by( + |(_, (_, a)), (_, (_, b))| b.cmp(a), // Sort in descending order + ); + + let config = CONFIG.load(deps.storage)?; + let total_pool_limit = config.pools_per_outpost as usize * outposts.len(); + + // If candidates list size is more than the total pool number limit, + // we need to whitelist all candidates + // and those which have more than the threshold voting power. + // Otherwise, keep the current whitelist. + if candidates.len() > total_pool_limit { + let total_vp = candidates + .iter() + .fold(Uint128::zero(), |acc, (_, (_, vp))| acc + vp); + + let new_whitelist: HashSet<_> = candidates + .iter() + .skip(total_pool_limit) + .filter(|(_, (_, pool_vp))| { + let threshold_vp = total_vp.multiply_ratio( + config.whitelist_threshold.numerator(), + config.whitelist_threshold.denominator(), + ); + *pool_vp >= threshold_vp + }) + .chain(candidates.iter().take(total_pool_limit)) + .map(|(_, (pool, _))| (*pool).clone()) + .collect(); + + // Remove all non-whitelisted pools + voted_pools + .difference(&new_whitelist) + .try_for_each(|pool| VOTED_POOLS.remove(deps.storage, pool, block_ts))?; + + POOLS_WHITELIST.save(deps.storage, &new_whitelist.into_iter().collect())?; + } + + let astro_for_the_next_period = astro_emissions_curve(); + + // Total voting power of all selected pools + let total_selected_vp = candidates + .iter() + .take(total_pool_limit) + .fold(Uint128::zero(), |acc, (_, (_, vp))| acc + vp); + let mut next_pools = candidates + .into_iter() + .take(total_pool_limit) + .map(|(prefix, (pool, pool_vp))| { + let astro_for_pool = + astro_for_the_next_period.multiply_ratio(pool_vp, total_selected_vp); + (prefix, (pool.clone(), astro_for_pool)) + }) + .collect_vec(); + + // Add astro pools for each registered outpost + next_pools.extend(outposts.iter().filter_map(|(prefix, outpost)| { + outpost.astro_pool_config.as_ref().map(|astro_pool_config| { + ( + prefix.clone(), + ( + astro_pool_config.astro_pool.clone(), + astro_pool_config.constant_emissions, + ), + ) + }) + })); + + let ibc_fee = min_ntrn_ibc_fee(deps.as_ref())?; + + let next_pools_grouped: HashMap<_, _> = next_pools + .into_iter() + .into_group_map() + .into_iter() + .collect(); + + let mut attrs = vec![attr("action", "tune_pools")]; + + let deps = deps.into_empty(); + let mut outpost_emissions_statuses = HashMap::new(); + let setup_pools_msgs = next_pools_grouped + .iter() + .map(|(prefix, raw_schedules)| { + let outpost_info = outposts.get(prefix).unwrap(); + + let (schedules, astro_funds) = raw_emissions_to_schedules( + &env, + raw_schedules, + &outpost_info.astro_denom, + &config.astro_denom, + ); + + let msg = if let Some(params) = &outpost_info.params { + outpost_emissions_statuses.insert(prefix.clone(), OutpostStatus::InProgress); + build_emission_ibc_msg(&env, params, &ibc_fee, astro_funds, &schedules)? + } else { + let incentives_contract = query_incentives_addr(deps.querier, &config.factory)?; + // Ensure on the Hub that all LP tokens are valid. + // Otherwise, keep ASTRO directed to invalid pools on the emissions controller. + let schedules = schedules + .into_iter() + .filter(|(pool, _)| { + determine_asset_info(pool, deps.api) + .and_then(|maybe_lp| { + check_lp_token(deps.querier, &config.factory, &maybe_lp) + }) + .is_ok() + }) + .collect_vec(); + let incentives_msg = incentives::ExecuteMsg::IncentivizeMany(schedules); + wasm_execute(incentives_contract, &incentives_msg, vec![astro_funds])?.into() + }; + + attrs.push(attr("outpost", prefix)); + attrs.push(attr( + "pools", + serde_json::to_string(&raw_schedules) + .map_err(|err| StdError::generic_err(err.to_string()))?, + )); + + Ok(msg) + }) + .collect::>>>()?; + + TUNE_INFO.save( + deps.storage, + &TuneInfo { + tune_ts: epoch_start, + pools_grouped: next_pools_grouped, + outpost_emissions_statuses, + }, + block_ts, + )?; + + Ok(Response::new() + .add_messages(setup_pools_msgs) + .add_attributes(attrs)) +} + +/// Permissioned to the contract owner. +/// Updates the contract configuration. +pub fn update_config( + deps: DepsMut, + info: MessageInfo, + pools_limit: Option, + whitelisting_fee: Option, + fee_receiver: Option, +) -> Result, ContractError> { + nonpayable(&info)?; + let mut config = CONFIG.load(deps.storage)?; + + ensure!(info.sender == config.owner, ContractError::Unauthorized {}); + + let mut attrs = vec![attr("action", "update_config")]; + + if let Some(pools_limit) = pools_limit { + attrs.push(attr("new_pools_limit", pools_limit.to_string())); + config.pools_per_outpost = pools_limit; + } + + if let Some(whitelisting_fee) = whitelisting_fee { + attrs.push(attr("new_whitelisting_fee", whitelisting_fee.to_string())); + config.whitelisting_fee = whitelisting_fee; + } + + if let Some(fee_receiver) = fee_receiver { + attrs.push(attr("new_fee_receiver", &fee_receiver)); + config.fee_receiver = deps.api.addr_validate(&fee_receiver)?; + } + + CONFIG.save(deps.storage, &config)?; + + Ok(Response::default().add_attributes(attrs)) +} diff --git a/contracts/emissions_controller/src/ibc.rs b/contracts/emissions_controller/src/ibc.rs new file mode 100644 index 00000000..ed5831be --- /dev/null +++ b/contracts/emissions_controller/src/ibc.rs @@ -0,0 +1,429 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + ensure, from_json, DepsMut, Env, Ibc3ChannelOpenResponse, IbcBasicResponse, IbcChannelCloseMsg, + IbcChannelConnectMsg, IbcChannelOpenMsg, IbcPacketAckMsg, IbcPacketReceiveMsg, + IbcPacketTimeoutMsg, IbcReceiveResponse, Never, Order, StdError, StdResult, +}; + +use astroport_governance::emissions_controller::consts::{IBC_APP_VERSION, IBC_ORDERING}; +use astroport_governance::emissions_controller::msg::{ack_fail, ack_ok, VxAstroIbcMsg}; + +use crate::error::ContractError; +use crate::execute::{handle_update_user, handle_vote}; +use crate::state::OUTPOSTS; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn ibc_channel_open( + _deps: DepsMut, + _env: Env, + msg: IbcChannelOpenMsg, +) -> StdResult> { + let channel = msg.channel(); + + ensure!( + channel.order == IBC_ORDERING, + StdError::generic_err("Ordering is invalid. The channel must be unordered",) + ); + ensure!( + channel.version == IBC_APP_VERSION, + StdError::generic_err(format!("Must set version to `{IBC_APP_VERSION}`",)) + ); + if let Some(counter_version) = msg.counterparty_version() { + if counter_version != IBC_APP_VERSION { + return Err(StdError::generic_err(format!( + "Counterparty version must be `{IBC_APP_VERSION}`" + ))); + } + } + + Ok(Some(Ibc3ChannelOpenResponse { + version: IBC_APP_VERSION.to_string(), + })) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn ibc_channel_connect( + _deps: DepsMut, + _env: Env, + msg: IbcChannelConnectMsg, +) -> StdResult { + if let Some(counter_version) = msg.counterparty_version() { + if counter_version != IBC_APP_VERSION { + return Err(StdError::generic_err(format!( + "Counterparty version must be `{IBC_APP_VERSION}`" + ))); + } + } + + let channel = msg.channel(); + + Ok(IbcBasicResponse::new() + .add_attribute("action", "ibc_connect") + .add_attribute("channel_id", &channel.endpoint.channel_id)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn ibc_packet_receive( + deps: DepsMut, + env: Env, + msg: IbcPacketReceiveMsg, +) -> Result { + do_packet_receive(deps, env, msg).or_else(|err| { + Ok(IbcReceiveResponse::new() + .add_attribute("action", "ibc_packet_receive") + .set_ack(ack_fail(err))) + }) +} + +pub fn do_packet_receive( + deps: DepsMut, + env: Env, + msg: IbcPacketReceiveMsg, +) -> Result { + // Ensure this outpost is registered + OUTPOSTS + .range(deps.storage, None, None, Order::Ascending) + .find_map(|data| { + let (outpost_prefix, outpost) = data.ok()?; + outpost.params.as_ref().and_then(|params| { + if msg.packet.dest.channel_id == params.voting_channel { + Some(outpost_prefix.clone()) + } else { + None + } + }) + }) + .ok_or_else(|| { + StdError::generic_err(format!( + "Unknown outpost with {} voting channel", + msg.packet.dest.channel_id + )) + })?; + + match from_json(&msg.packet.data)? { + VxAstroIbcMsg::Vote { + voter, + voting_power, + votes, + } => handle_vote(deps, env, &voter, voting_power, votes).map(|orig_response| { + IbcReceiveResponse::new() + .add_attributes(orig_response.attributes) + .set_ack(ack_ok()) + }), + VxAstroIbcMsg::UpdateUserVotes { + voter, + voting_power, + .. + } => handle_update_user(deps.storage, env, voter.as_str(), voting_power).map( + |orig_response| { + IbcReceiveResponse::new() + .add_attributes(orig_response.attributes) + .set_ack(ack_ok()) + }, + ), + } +} + +#[cfg(not(tarpaulin_include))] +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn ibc_packet_ack( + _deps: DepsMut, + _env: Env, + _msg: IbcPacketAckMsg, +) -> StdResult { + unimplemented!("This contract is only receiving IBC messages") +} + +#[cfg(not(tarpaulin_include))] +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn ibc_packet_timeout( + _deps: DepsMut, + _env: Env, + _msg: IbcPacketTimeoutMsg, +) -> StdResult { + unimplemented!("This contract is only receiving IBC messages") +} + +#[cfg(not(tarpaulin_include))] +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn ibc_channel_close( + _deps: DepsMut, + _env: Env, + _channel: IbcChannelCloseMsg, +) -> StdResult { + unimplemented!() +} + +#[cfg(test)] +mod unit_tests { + use std::collections::HashMap; + use std::marker::PhantomData; + + use cosmwasm_std::testing::{mock_dependencies, mock_env, MockApi, MockQuerier, MockStorage}; + use cosmwasm_std::{ + to_json_binary, Addr, Decimal, IbcChannel, IbcEndpoint, IbcOrder, IbcPacket, IbcTimeout, + OwnedDeps, Timestamp, + }; + use neutron_sdk::bindings::query::NeutronQuery; + + use astroport_governance::emissions_controller::hub::{ + OutpostInfo, OutpostParams, VotedPoolInfo, + }; + use astroport_governance::emissions_controller::msg::IbcAckResult; + + use crate::state::{POOLS_WHITELIST, VOTED_POOLS}; + + use super::*; + + pub fn mock_custom_dependencies() -> OwnedDeps + { + OwnedDeps { + storage: MockStorage::default(), + api: MockApi::default(), + querier: MockQuerier::default(), + custom_query_type: PhantomData, + } + } + + #[test] + fn test_channel_open() { + let mut deps = mock_dependencies(); + + let mut ibc_channel = IbcChannel::new( + IbcEndpoint { + port_id: "doesnt matter".to_string(), + channel_id: "doesnt matter".to_string(), + }, + IbcEndpoint { + port_id: "doesnt matter".to_string(), + channel_id: "doesnt matter".to_string(), + }, + IbcOrder::Unordered, + IBC_APP_VERSION, + "doesnt matter", + ); + let res = ibc_channel_open( + deps.as_mut(), + mock_env(), + IbcChannelOpenMsg::new_init(ibc_channel.clone()), + ) + .unwrap() + .unwrap(); + + assert_eq!(res.version, IBC_APP_VERSION); + + ibc_channel.order = IbcOrder::Ordered; + + let res = ibc_channel_open( + deps.as_mut(), + mock_env(), + IbcChannelOpenMsg::new_init(ibc_channel.clone()), + ) + .unwrap_err(); + assert_eq!( + res, + StdError::generic_err("Ordering is invalid. The channel must be unordered") + ); + + ibc_channel.order = IbcOrder::Unordered; + ibc_channel.version = "wrong_version".to_string(); + + let res = ibc_channel_open( + deps.as_mut(), + mock_env(), + IbcChannelOpenMsg::new_init(ibc_channel.clone()), + ) + .unwrap_err(); + assert_eq!( + res, + StdError::generic_err(format!("Must set version to `{IBC_APP_VERSION}`")) + ); + + ibc_channel.version = IBC_APP_VERSION.to_string(); + + let res = ibc_channel_open( + deps.as_mut(), + mock_env(), + IbcChannelOpenMsg::new_try(ibc_channel.clone(), "wrong_version"), + ) + .unwrap_err(); + assert_eq!( + res, + StdError::generic_err(format!("Counterparty version must be `{IBC_APP_VERSION}`")) + ); + + ibc_channel_open( + deps.as_mut(), + mock_env(), + IbcChannelOpenMsg::new_try(ibc_channel.clone(), IBC_APP_VERSION), + ) + .unwrap() + .unwrap(); + } + + #[test] + fn test_channel_connect() { + let mut deps = mock_dependencies(); + + let ibc_channel = IbcChannel::new( + IbcEndpoint { + port_id: "doesnt matter".to_string(), + channel_id: "doesnt matter".to_string(), + }, + IbcEndpoint { + port_id: "doesnt matter".to_string(), + channel_id: "doesnt matter".to_string(), + }, + IbcOrder::Unordered, + IBC_APP_VERSION, + "doesnt matter", + ); + + ibc_channel_connect( + deps.as_mut(), + mock_env(), + IbcChannelConnectMsg::new_ack(ibc_channel.clone(), IBC_APP_VERSION), + ) + .unwrap(); + + let err = ibc_channel_connect( + deps.as_mut(), + mock_env(), + IbcChannelConnectMsg::new_ack(ibc_channel.clone(), "wrong version"), + ) + .unwrap_err(); + assert_eq!( + err, + StdError::generic_err(format!("Counterparty version must be `{IBC_APP_VERSION}`")) + ); + } + + #[test] + fn test_packet_receive() { + let mut deps = mock_custom_dependencies(); + + let voting_msg = VxAstroIbcMsg::Vote { + voter: "osmo1voter".to_string(), + voting_power: 1000u128.into(), + votes: HashMap::from([("osmo1pool1".to_string(), Decimal::one())]), + }; + let packet = IbcPacket::new( + to_json_binary(&voting_msg).unwrap(), + IbcEndpoint { + port_id: "".to_string(), + channel_id: "".to_string(), + }, + IbcEndpoint { + port_id: "".to_string(), + channel_id: "channel-2".to_string(), + }, + 1, + IbcTimeout::with_timestamp(Timestamp::from_seconds(100)), + ); + let ibc_msg = IbcPacketReceiveMsg::new(packet, Addr::unchecked("doesnt matter")); + + let resp = + ibc_packet_receive(deps.as_mut().into_empty(), mock_env(), ibc_msg.clone()).unwrap(); + let ack_err: IbcAckResult = from_json(resp.acknowledgement).unwrap(); + assert_eq!( + ack_err, + IbcAckResult::Error( + "Generic error: Unknown outpost with channel-2 voting channel".to_string() + ) + ); + + // Mock added outpost and whitelist + OUTPOSTS + .save( + deps.as_mut().storage, + "osmo", + &OutpostInfo { + params: Some(OutpostParams { + emissions_controller: "".to_string(), + voting_channel: "channel-2".to_string(), + ics20_channel: "".to_string(), + }), + astro_denom: "".to_string(), + astro_pool_config: None, + }, + ) + .unwrap(); + POOLS_WHITELIST + .save(deps.as_mut().storage, &vec!["osmo1pool1".to_string()]) + .unwrap(); + let env = mock_env(); + VOTED_POOLS + .save( + deps.as_mut().storage, + "osmo1pool1", + &VotedPoolInfo { + init_ts: env.block.time.seconds(), + voting_power: 0u128.into(), + }, + env.block.time.seconds(), + ) + .unwrap(); + + let resp = + ibc_packet_receive(deps.as_mut().into_empty(), env.clone(), ibc_msg.clone()).unwrap(); + let ack_err: IbcAckResult = from_json(resp.acknowledgement).unwrap(); + assert_eq!(ack_err, IbcAckResult::Ok(b"ok".into())); + + // The same user has voting cooldown for 10 days + let resp = ibc_packet_receive(deps.as_mut().into_empty(), env.clone(), ibc_msg).unwrap(); + let ack_err: IbcAckResult = from_json(resp.acknowledgement).unwrap(); + assert_eq!( + ack_err, + IbcAckResult::Error("Next time you can change your vote is at 1572661419".to_string()) + ); + + // Voting from random channel is not possible + let packet = IbcPacket::new( + to_json_binary(&voting_msg).unwrap(), + IbcEndpoint { + port_id: "".to_string(), + channel_id: "".to_string(), + }, + IbcEndpoint { + port_id: "".to_string(), + channel_id: "channel-3".to_string(), + }, + 1, + IbcTimeout::with_timestamp(Timestamp::from_seconds(100)), + ); + let ibc_msg = IbcPacketReceiveMsg::new(packet, Addr::unchecked("doesnt matter")); + let resp = ibc_packet_receive(deps.as_mut().into_empty(), env.clone(), ibc_msg).unwrap(); + let ack_err: IbcAckResult = from_json(resp.acknowledgement).unwrap(); + assert_eq!( + ack_err, + IbcAckResult::Error( + "Generic error: Unknown outpost with channel-3 voting channel".to_string() + ) + ); + + // However, his voting power can be updated any time + let update_msg = VxAstroIbcMsg::UpdateUserVotes { + voter: "osmo1voter".to_string(), + voting_power: 2000u128.into(), + is_unlock: false, + }; + let packet = IbcPacket::new( + to_json_binary(&update_msg).unwrap(), + IbcEndpoint { + port_id: "".to_string(), + channel_id: "".to_string(), + }, + IbcEndpoint { + port_id: "".to_string(), + channel_id: "channel-2".to_string(), + }, + 1, + IbcTimeout::with_timestamp(Timestamp::from_seconds(100)), + ); + let ibc_msg = IbcPacketReceiveMsg::new(packet, Addr::unchecked("doesnt matter")); + let resp = ibc_packet_receive(deps.as_mut().into_empty(), env.clone(), ibc_msg).unwrap(); + let ack_err: IbcAckResult = from_json(resp.acknowledgement).unwrap(); + assert_eq!(ack_err, IbcAckResult::Ok(b"ok".into())); + } +} diff --git a/contracts/emissions_controller/src/instantiate.rs b/contracts/emissions_controller/src/instantiate.rs new file mode 100644 index 00000000..a3fcbf03 --- /dev/null +++ b/contracts/emissions_controller/src/instantiate.rs @@ -0,0 +1,115 @@ +use astroport::asset::validate_native_denom; +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + ensure, to_json_binary, Addr, DepsMut, Env, MessageInfo, Reply, Response, StdError, SubMsg, + SubMsgResponse, SubMsgResult, WasmMsg, +}; +use cw2::set_contract_version; +use cw_utils::parse_instantiate_response_data; +use neutron_sdk::bindings::msg::NeutronMsg; +use neutron_sdk::bindings::query::NeutronQuery; + +use astroport_governance::emissions_controller::hub::HubInstantiateMsg; +use astroport_governance::emissions_controller::hub::{Config, TuneInfo}; +use astroport_governance::voting_escrow; + +use crate::error::ContractError; +use crate::state::{CONFIG, POOLS_WHITELIST, TUNE_INFO}; +use crate::utils::get_epoch_start; + +/// Contract name that is used for migration. +pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +/// Contract version that is used for migration. +pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); +/// ID for the vxastro contract instantiate reply +pub const INSTANTIATE_VXASTRO_REPLY_ID: u64 = 1; + +/// Creates a new contract with the specified parameters in the [`InstantiateMsg`]. +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + env: Env, + _info: MessageInfo, + msg: HubInstantiateMsg, +) -> Result, ContractError> { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + validate_native_denom(&msg.astro_denom)?; + + let config = Config { + owner: deps.api.addr_validate(&msg.owner)?, + vxastro: Addr::unchecked(""), + factory: deps.api.addr_validate(&msg.factory)?, + astro_denom: msg.astro_denom.to_string(), + pools_per_outpost: msg.pools_per_outpost, + whitelisting_fee: msg.whitelisting_fee, + fee_receiver: deps.api.addr_validate(&msg.fee_receiver)?, + whitelist_threshold: msg.whitelist_threshold, + }; + config.validate()?; + + CONFIG.save(deps.storage, &config)?; + + // Set tune_ts just for safety so the first tuning could happen in 2 weeks + TUNE_INFO.save( + deps.storage, + &TuneInfo { + tune_ts: get_epoch_start(env.block.time.seconds()), + pools_grouped: Default::default(), + outpost_emissions_statuses: Default::default(), + }, + env.block.time.seconds(), + )?; + + // Instantiate vxASTRO contract + validate_native_denom(&msg.vxastro_deposit_denom)?; + let init_vxastro_msg = WasmMsg::Instantiate { + admin: Some(msg.owner), + code_id: msg.vxastro_code_id, + msg: to_json_binary(&voting_escrow::InstantiateMsg { + deposit_denom: msg.vxastro_deposit_denom.to_string(), + emissions_controller: env.contract.address.to_string(), + marketing: msg.vxastro_marketing_info, + })?, + funds: vec![], + label: "Vote Escrowed xASTRO".to_string(), + }; + + POOLS_WHITELIST.save(deps.storage, &vec![])?; + + Ok(Response::default() + .add_attribute("action", "instantiate_emissions_controller") + .add_submessage(SubMsg::reply_on_success( + init_vxastro_msg, + INSTANTIATE_VXASTRO_REPLY_ID, + ))) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result { + match msg { + Reply { + id: INSTANTIATE_VXASTRO_REPLY_ID, + result: + SubMsgResult::Ok(SubMsgResponse { + data: Some(data), .. + }), + } => { + let vxastro_contract = parse_instantiate_response_data(&data)?.contract_address; + + CONFIG.update::<_, StdError>(deps.storage, |mut config| { + ensure!( + config.vxastro == Addr::unchecked(""), + StdError::generic_err("vxASTRO contract is already set") + ); + + config.vxastro = Addr::unchecked(&vxastro_contract); + Ok(config) + })?; + + Ok(Response::new().add_attribute("vxastro", vxastro_contract)) + } + _ => Err(ContractError::FailedToParseReply {}), + } +} diff --git a/contracts/emissions_controller/src/lib.rs b/contracts/emissions_controller/src/lib.rs new file mode 100644 index 00000000..0f1541fa --- /dev/null +++ b/contracts/emissions_controller/src/lib.rs @@ -0,0 +1,9 @@ +pub mod execute; +pub mod state; + +pub mod error; +pub mod ibc; +pub mod instantiate; +pub mod query; +pub mod sudo; +pub mod utils; diff --git a/contracts/emissions_controller/src/query.rs b/contracts/emissions_controller/src/query.rs new file mode 100644 index 00000000..033b540e --- /dev/null +++ b/contracts/emissions_controller/src/query.rs @@ -0,0 +1,97 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{to_json_binary, Binary, Deps, Env, Order, StdError, StdResult}; +use cw_storage_plus::Bound; +use itertools::Itertools; +use neutron_sdk::bindings::query::NeutronQuery; + +use astroport_governance::emissions_controller::consts::MAX_PAGE_LIMIT; +use astroport_governance::emissions_controller::hub::{QueryMsg, UserInfoResponse}; + +use crate::state::{CONFIG, OUTPOSTS, POOLS_WHITELIST, TUNE_INFO, USER_INFO, VOTED_POOLS}; + +/// Expose available contract queries. +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::UserInfo { user, timestamp } => { + let block_time = env.block.time.seconds(); + let timestamp = timestamp.unwrap_or(block_time); + let user_info = match timestamp { + timestamp if timestamp == block_time => USER_INFO.may_load(deps.storage, &user), + timestamp => USER_INFO.may_load_at_height(deps.storage, &user, timestamp), + }? + .unwrap_or_default(); + + let applied_votes = user_info + .votes + .iter() + .filter_map(|(pool, weight)| { + let data = if timestamp == block_time { + VOTED_POOLS.may_load(deps.storage, pool) + } else { + VOTED_POOLS.may_load_at_height(deps.storage, pool, timestamp) + }; + + match data { + Ok(Some(pool_info)) if pool_info.init_ts <= user_info.vote_ts => { + Some(Ok((pool.clone(), *weight))) + } + Err(err) => Some(Err(err)), + _ => None, + } + }) + .try_collect()?; + + let response = UserInfoResponse { + vote_ts: user_info.vote_ts, + voting_power: user_info.voting_power, + votes: user_info.votes, + applied_votes, + }; + + to_json_binary(&response) + } + QueryMsg::TuneInfo { timestamp } => { + let block_time = env.block.time.seconds(); + let timestamp = timestamp.unwrap_or(block_time); + let tune_info = match timestamp { + timestamp if timestamp == block_time => TUNE_INFO.may_load(deps.storage), + timestamp => TUNE_INFO.may_load_at_height(deps.storage, timestamp), + }? + .ok_or_else(|| StdError::generic_err(format!("Tune info not found at {timestamp}")))?; + to_json_binary(&tune_info) + } + QueryMsg::Config {} => to_json_binary(&CONFIG.load(deps.storage)?), + QueryMsg::VotedPool { pool, timestamp } => { + let block_time = env.block.time.seconds(); + let timestamp = timestamp.unwrap_or(block_time); + let voted_pool = match timestamp { + timestamp if timestamp == block_time => VOTED_POOLS.may_load(deps.storage, &pool), + timestamp => VOTED_POOLS.may_load_at_height(deps.storage, &pool, timestamp), + }? + .ok_or_else(|| StdError::generic_err(format!("Voted pool not found at {timestamp}")))?; + to_json_binary(&voted_pool) + } + QueryMsg::VotedPoolsList { limit, start_after } => { + let limit = limit.unwrap_or(MAX_PAGE_LIMIT) as usize; + let voted_pools = VOTED_POOLS + .range( + deps.storage, + start_after.as_ref().map(|s| Bound::exclusive(s.as_str())), + None, + Order::Ascending, + ) + .take(limit) + .collect::>>()?; + to_json_binary(&voted_pools) + } + QueryMsg::ListOutposts {} => { + let outposts = OUTPOSTS + .range(deps.storage, None, None, Order::Ascending) + .collect::>>()?; + to_json_binary(&outposts) + } + QueryMsg::QueryWhitelist {} => to_json_binary(&POOLS_WHITELIST.load(deps.storage)?), + } +} diff --git a/contracts/emissions_controller/src/state.rs b/contracts/emissions_controller/src/state.rs new file mode 100644 index 00000000..ef927c1c --- /dev/null +++ b/contracts/emissions_controller/src/state.rs @@ -0,0 +1,37 @@ +use astroport::common::OwnershipProposal; +use cw_storage_plus::{Item, Map, SnapshotItem, SnapshotMap, Strategy}; + +use astroport_governance::emissions_controller::hub::{ + Config, OutpostInfo, TuneInfo, UserInfo, VotedPoolInfo, +}; + +/// Stores config at the given key. +pub const CONFIG: Item = Item::new("config"); + +/// Contains a proposal to change contract ownership +pub const OWNERSHIP_PROPOSAL: Item = Item::new("ownership_proposal"); +/// Array of pools eligible for voting. +pub const POOLS_WHITELIST: Item> = Item::new("pools_whitelist"); +/// Registered Astroport outposts with respective parameters. +pub const OUTPOSTS: Map<&str, OutpostInfo> = Map::new("outposts"); +/// Historical user's voting information. +pub const USER_INFO: SnapshotMap<&str, UserInfo> = SnapshotMap::new( + "user_info", + "user_info____checkpoints", + "user_info__changelog", + Strategy::EveryBlock, +); +/// Historical pools voting power and the time when they were whitelisted. +pub const VOTED_POOLS: SnapshotMap<&str, VotedPoolInfo> = SnapshotMap::new( + "voted_pools", + "voted_pools____checkpoints", + "voted_pools__changelog", + Strategy::EveryBlock, +); +/// Historical tune information. +pub const TUNE_INFO: SnapshotItem = SnapshotItem::new( + "tune_info", + "tune_info____checkpoints", + "tune_info__changelog", + Strategy::EveryBlock, +); diff --git a/contracts/emissions_controller/src/sudo.rs b/contracts/emissions_controller/src/sudo.rs new file mode 100644 index 00000000..81b46ebe --- /dev/null +++ b/contracts/emissions_controller/src/sudo.rs @@ -0,0 +1,61 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ensure, DepsMut, Env, Response, StdError, StdResult, Storage}; +use neutron_sdk::sudo::msg::{RequestPacket, TransferSudoMsg}; + +use astroport_governance::emissions_controller::hub::OutpostStatus; + +use crate::state::TUNE_INFO; +use crate::utils::get_outpost_from_hub_ics20_channel; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn sudo(deps: DepsMut, env: Env, msg: TransferSudoMsg) -> StdResult { + match msg { + TransferSudoMsg::Response { request, .. } => { + process_ibc_reply(deps.storage, env, request, false) + } + TransferSudoMsg::Error { request, .. } | TransferSudoMsg::Timeout { request } => { + process_ibc_reply(deps.storage, env, request, true) + } + } +} + +/// Process outcome of an ics20 IBC packet with IBC hook. +/// If a packet was successful, it marks the outpost as done. +/// If a packet failed or timed out, it marks the outpost as failed, so it can be retried. +pub fn process_ibc_reply( + storage: &mut dyn Storage, + env: Env, + packet: RequestPacket, + failed: bool, +) -> StdResult { + let outpost = get_outpost_from_hub_ics20_channel(storage, packet.source_channel)?; + + let mut tune_info = TUNE_INFO.load(storage)?; + tune_info + .outpost_emissions_statuses + .get_mut(&outpost) + .ok_or_else(|| StdError::generic_err("Outpost status for {outpost} not found")) + .and_then(|status| { + ensure!( + *status == OutpostStatus::InProgress, + StdError::generic_err(format!("Outpost {outpost} is not in progress")) + ); + *status = if failed { + OutpostStatus::Failed + } else { + OutpostStatus::Done + }; + Ok(()) + })?; + TUNE_INFO.save(storage, &tune_info, env.block.time.seconds())?; + + let mut attrs = if failed { + vec![("action", "ibc_failed")] + } else { + vec![("action", "ibc_transfer_ack")] + }; + attrs.push(("outpost", &outpost)); + + Ok(Response::default().add_attributes(attrs)) +} diff --git a/contracts/emissions_controller/src/utils.rs b/contracts/emissions_controller/src/utils.rs new file mode 100644 index 00000000..024f0370 --- /dev/null +++ b/contracts/emissions_controller/src/utils.rs @@ -0,0 +1,249 @@ +use std::collections::HashMap; + +use astroport::asset::Asset; +use astroport::incentives::{IncentivesSchedule, InputSchedule}; +use cosmwasm_schema::cw_serde; +use cosmwasm_schema::serde::Serialize; +use cosmwasm_std::{ + coin, Coin, CosmosMsg, Deps, Env, Order, StdError, StdResult, Storage, Uint128, +}; +use itertools::Itertools; +use neutron_sdk::bindings::msg::{IbcFee, NeutronMsg}; +use neutron_sdk::bindings::query::NeutronQuery; +use neutron_sdk::query::min_ibc_fee::query_min_ibc_fee; +use neutron_sdk::sudo::msg::RequestPacketTimeoutHeight; + +use astroport_governance::emissions_controller::consts::{ + EPOCHS_START, EPOCH_LENGTH, FEE_DENOM, IBC_TIMEOUT, LP_SUBDENOM, +}; +use astroport_governance::emissions_controller::hub::{OutpostInfo, OutpostParams}; +use astroport_governance::emissions_controller::outpost::OutpostMsg; + +use crate::error::ContractError; +use crate::state::OUTPOSTS; + +/// Determine outpost prefix from address or denom. +pub fn determine_outpost_prefix(value: &str) -> Option { + let mut maybe_addr = Some(value); + + if value.starts_with("factory/") && value.ends_with(LP_SUBDENOM) { + maybe_addr = value.split('/').nth(1); + } + + maybe_addr.and_then(|value| { + value.find('1').and_then(|delim_ind| { + if delim_ind > 0 && value.chars().all(char::is_alphanumeric) { + Some(value[..delim_ind].to_string()) + } else { + None + } + }) + }) +} + +/// Determine outpost prefix for the pool LP token and validate +/// that this outpost exists. +pub fn get_outpost_prefix( + pool: &str, + outpost_prefixes: &HashMap, +) -> Option { + determine_outpost_prefix(pool).and_then(|maybe_prefix| { + if outpost_prefixes.contains_key(&maybe_prefix) { + Some(maybe_prefix) + } else { + None + } + }) +} + +/// Validate LP token denom or address matches outpost prefix. +pub fn validate_outpost_prefix(value: &str, prefix: &str) -> Result<(), ContractError> { + determine_outpost_prefix(value) + .and_then(|maybe_prefix| { + if maybe_prefix == prefix { + Some(maybe_prefix) + } else { + None + } + }) + .ok_or_else(|| ContractError::InvalidOutpostPrefix(value.to_string())) + .map(|_| ()) +} + +/// Helper function to get outpost prefix from the ICS20 IBC packet. +pub fn get_outpost_from_hub_ics20_channel( + store: &dyn Storage, + source_channel: Option, +) -> StdResult { + let source_channel = source_channel + .ok_or_else(|| StdError::generic_err("Missing source_channel in IBC ack packet"))?; + // Find outpost by ics20 channel + OUTPOSTS + .range(store, None, None, Order::Ascending) + .find_map(|data| { + let (outpost_prefix, outpost) = data.ok()?; + outpost.params.as_ref().and_then(|params| { + if source_channel == params.ics20_channel { + Some(outpost_prefix.clone()) + } else { + None + } + }) + }) + .ok_or_else(|| { + StdError::generic_err(format!( + "Unknown outpost with {source_channel} ics20 channel" + )) + }) +} + +#[cw_serde] +pub enum IbcHookMemo { + Wasm { contract: String, msg: T }, +} + +impl IbcHookMemo { + pub fn build(contract: &str, msg: T) -> StdResult { + serde_json::to_string(&IbcHookMemo::Wasm { + contract: contract.to_string(), + msg, + }) + .map_err(|err| StdError::generic_err(err.to_string())) + } +} + +pub fn min_ntrn_ibc_fee(deps: Deps) -> Result { + let fee = query_min_ibc_fee(deps)?.min_fee; + + Ok(IbcFee { + recv_fee: fee.recv_fee, + ack_fee: fee + .ack_fee + .into_iter() + .filter(|a| a.denom == FEE_DENOM) + .collect(), + timeout_fee: fee + .timeout_fee + .into_iter() + .filter(|a| a.denom == FEE_DENOM) + .collect(), + }) +} + +/// Compose ics20 message with IBC hook memo for outpost emissions controller. +pub fn build_emission_ibc_msg( + env: &Env, + params: &OutpostParams, + ibc_fee: &IbcFee, + astro_funds: Coin, + schedules: &[(String, InputSchedule)], +) -> StdResult> { + let outpost_controller_msg = + astroport_governance::emissions_controller::msg::ExecuteMsg::Custom( + OutpostMsg::SetEmissions { + schedules: schedules.to_vec(), + }, + ); + Ok(NeutronMsg::IbcTransfer { + source_port: "transfer".to_string(), + source_channel: params.ics20_channel.clone(), + token: astro_funds, + sender: env.contract.address.to_string(), + receiver: params.emissions_controller.clone(), + timeout_height: RequestPacketTimeoutHeight { + revision_number: None, + revision_height: None, + }, + timeout_timestamp: env.block.time.plus_seconds(IBC_TIMEOUT).nanos(), + memo: IbcHookMemo::build(¶ms.emissions_controller, outpost_controller_msg)?, + fee: ibc_fee.clone(), + } + .into()) +} + +/// This function converts schedule pairs (lp_token, ASTRO amount) +/// into the incentives contract executable message. +/// It also calculates total ASTRO funds required for the emissions. +pub fn raw_emissions_to_schedules( + env: &Env, + raw_schedules: &[(String, Uint128)], + schedule_denom: &str, + hub_denom: &str, +) -> (Vec<(String, InputSchedule)>, Coin) { + let mut total_astro = Uint128::zero(); + // Ensure emissions >=1 uASTRO per second. + // >= 1 uASTRO per second is the requirement in the incentives contract. + let schedules = raw_schedules + .iter() + .filter_map(|(pool, astro_amount)| { + let schedule = InputSchedule { + reward: Asset::native(schedule_denom, *astro_amount), + duration_periods: 1, + }; + // Schedule validation imported from the incentives contract + IncentivesSchedule::from_input(env, &schedule).ok()?; + + total_astro += astro_amount; + Some((pool.clone(), schedule)) + }) + .collect_vec(); + + let astro_funds = coin(total_astro.u128(), hub_denom); + + (schedules, astro_funds) +} + +/// Normalize current timestamp to the beginning of the current epoch (Monday). +pub fn get_epoch_start(timestamp: u64) -> u64 { + let rem = timestamp % EPOCHS_START; + if rem % EPOCH_LENGTH == 0 { + // Hit at the beginning of the current epoch + timestamp + } else { + // Hit somewhere in the middle + EPOCHS_START + rem / EPOCH_LENGTH * EPOCH_LENGTH + } +} + +// TODO: Implement dynamic emissions curve +pub fn astro_emissions_curve() -> Uint128 { + Uint128::new(100_000_000_000) +} + +#[cfg(test)] +mod unit_tests { + use super::*; + + #[test] + fn test_determine_outpost_prefix() { + assert_eq!( + determine_outpost_prefix(&format!("factory/wasm1addr{LP_SUBDENOM}")).unwrap(), + "wasm" + ); + assert_eq!(determine_outpost_prefix("wasm1addr").unwrap(), "wasm"); + assert_eq!(determine_outpost_prefix("1addr"), None); + assert_eq!( + determine_outpost_prefix(&format!("factory/1addr{LP_SUBDENOM}")), + None + ); + assert_eq!(determine_outpost_prefix("factory/wasm1addr/random"), None); + assert_eq!( + determine_outpost_prefix(&format!("factory{LP_SUBDENOM}")), + None + ); + } + + #[test] + fn test_epoch_start() { + assert_eq!(get_epoch_start(1716163200), 1716163200); + assert_eq!(get_epoch_start(1716163200 + 1), 1716163200); + assert_eq!( + get_epoch_start(1716163200 + EPOCH_LENGTH), + 1716163200 + EPOCH_LENGTH + ); + assert_eq!( + get_epoch_start(1716163200 + EPOCH_LENGTH + 1), + 1716163200 + EPOCH_LENGTH + ); + } +} diff --git a/contracts/emissions_controller/tests/common/contracts.rs b/contracts/emissions_controller/tests/common/contracts.rs new file mode 100644 index 00000000..dc82a3f7 --- /dev/null +++ b/contracts/emissions_controller/tests/common/contracts.rs @@ -0,0 +1,85 @@ +use std::fmt::Debug; + +use cosmwasm_schema::schemars::JsonSchema; +use cosmwasm_std::{CustomMsg, CustomQuery}; +use cw_multi_test::{Contract, ContractWrapper}; +use neutron_sdk::bindings::msg::NeutronMsg; +use neutron_sdk::bindings::query::NeutronQuery; + +pub fn token_contract() -> Box> +where + T: CustomMsg + Clone + Debug + PartialEq + JsonSchema + 'static, + C: CustomQuery + for<'de> cosmwasm_schema::serde::Deserialize<'de> + 'static, +{ + Box::new(ContractWrapper::new_with_empty( + cw20_base::contract::execute, + cw20_base::contract::instantiate, + cw20_base::contract::query, + )) +} + +pub fn pair_contract() -> Box> +where + T: CustomMsg + Clone + Debug + PartialEq + JsonSchema + 'static, + C: CustomQuery + for<'de> cosmwasm_schema::serde::Deserialize<'de> + 'static, +{ + Box::new( + ContractWrapper::new_with_empty( + astroport_pair::contract::execute, + astroport_pair::contract::instantiate, + astroport_pair::contract::query, + ) + .with_reply_empty(astroport_pair::contract::reply), + ) +} + +pub fn vxastro_contract() -> Box> +where + T: CustomMsg + Clone + Debug + PartialEq + JsonSchema + 'static, + C: CustomQuery + for<'de> cosmwasm_schema::serde::Deserialize<'de> + 'static, +{ + Box::new(ContractWrapper::new_with_empty( + astroport_voting_escrow::contract::execute, + astroport_voting_escrow::contract::instantiate, + astroport_voting_escrow::contract::query, + )) +} + +pub fn incentives_contract() -> Box> +where + T: CustomMsg + Clone + Debug + PartialEq + JsonSchema + 'static, + C: CustomQuery + for<'de> cosmwasm_schema::serde::Deserialize<'de> + 'static, +{ + Box::new(ContractWrapper::new_with_empty( + astroport_incentives::execute::execute, + astroport_incentives::instantiate::instantiate, + astroport_incentives::query::query, + )) +} + +pub fn factory_contract() -> Box> +where + T: CustomMsg + Clone + Debug + PartialEq + JsonSchema + 'static, + C: CustomQuery + for<'de> cosmwasm_schema::serde::Deserialize<'de> + 'static, +{ + Box::new( + ContractWrapper::new_with_empty( + astroport_factory::contract::execute, + astroport_factory::contract::instantiate, + astroport_factory::contract::query, + ) + .with_reply_empty(astroport_factory::contract::reply), + ) +} + +pub fn emissions_controller() -> Box> { + Box::new( + ContractWrapper::new( + astroport_emissions_controller::execute::execute, + astroport_emissions_controller::instantiate::instantiate, + astroport_emissions_controller::query::query, + ) + .with_sudo_empty(astroport_emissions_controller::sudo::sudo) + .with_reply_empty(astroport_emissions_controller::instantiate::reply), + ) +} diff --git a/contracts/emissions_controller/tests/common/helper.rs b/contracts/emissions_controller/tests/common/helper.rs new file mode 100644 index 00000000..8dc66235 --- /dev/null +++ b/contracts/emissions_controller/tests/common/helper.rs @@ -0,0 +1,424 @@ +use astroport::asset::{AssetInfo, PairInfo}; +use astroport::factory::{PairConfig, PairType}; +use astroport::incentives::RewardInfo; +use astroport::token::Logo; +use astroport::{factory, incentives}; +use cosmwasm_std::{ + coin, coins, Addr, BlockInfo, Coin, Decimal, Empty, MemoryStorage, StdResult, Timestamp, + Uint128, +}; +use cw_multi_test::error::AnyResult; +use cw_multi_test::{ + no_init, App, AppBuilder, AppResponse, BankKeeper, BankSudo, DistributionKeeper, Executor, + MockAddressGenerator, MockApiBech32, StakeKeeper, WasmKeeper, +}; +use derivative::Derivative; +use itertools::Itertools; +use neutron_sdk::bindings::msg::NeutronMsg; +use neutron_sdk::bindings::query::NeutronQuery; + +use astroport_governance::emissions_controller::consts::EPOCHS_START; +use astroport_governance::emissions_controller::hub::{ + HubInstantiateMsg, HubMsg, OutpostInfo, UserInfoResponse, VotedPoolInfo, +}; +use astroport_governance::voting_escrow::UpdateMarketingInfo; +use astroport_governance::{emissions_controller, voting_escrow}; + +use crate::common::contracts::*; +use crate::common::ibc_module::IbcMockModule; +use crate::common::neutron_module::MockNeutronModule; + +pub type NeutronApp = App< + BankKeeper, + MockApiBech32, + MemoryStorage, + MockNeutronModule, + WasmKeeper, + StakeKeeper, + DistributionKeeper, + IbcMockModule, +>; + +fn mock_ntrn_app() -> NeutronApp { + let api = MockApiBech32::new("neutron"); + AppBuilder::new_custom() + .with_custom(MockNeutronModule::new(&api)) + .with_api(api) + .with_wasm(WasmKeeper::new().with_address_generator(MockAddressGenerator)) + .with_ibc(IbcMockModule) + .with_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(EPOCHS_START), + chain_id: "cw-multitest-1".to_string(), + }) + .build(no_init) +} + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct ControllerHelper { + #[derivative(Debug = "ignore")] + pub app: NeutronApp, + pub owner: Addr, + pub astro: String, + pub xastro: String, + pub factory: Addr, + pub vxastro: Addr, + pub whitelisting_fee: Coin, + pub emission_controller: Addr, + pub incentives: Addr, +} + +impl ControllerHelper { + pub fn new() -> Self { + let mut app = mock_ntrn_app(); + let owner = app.api().addr_make("owner"); + let astro_denom = "astro"; + let xastro_denom = "xastro"; + + let vxastro_code_id = app.store_code(vxastro_contract()); + let emissions_controller_code_id = app.store_code(emissions_controller()); + let token_code_id = app.store_code(token_contract()); + let xyk_code_id = app.store_code(pair_contract()); + let factory_code_id = app.store_code(factory_contract()); + let incentives_code_id = app.store_code(incentives_contract()); + + let factory = app + .instantiate_contract( + factory_code_id, + owner.clone(), + &factory::InstantiateMsg { + pair_configs: vec![PairConfig { + code_id: xyk_code_id, + pair_type: PairType::Xyk {}, + total_fee_bps: 0, + maker_fee_bps: 0, + is_disabled: false, + is_generator_disabled: false, + permissioned: false, + }], + token_code_id, + fee_address: None, + generator_address: None, + owner: owner.to_string(), + whitelist_code_id: 0, + coin_registry_address: app.api().addr_make("coin_registry").to_string(), + }, + &[], + "label", + None, + ) + .unwrap(); + + let incentives = app + .instantiate_contract( + incentives_code_id, + owner.clone(), + &incentives::InstantiateMsg { + owner: owner.to_string(), + factory: factory.to_string(), + astro_token: AssetInfo::native(astro_denom), + vesting_contract: app.api().addr_make("vesting").to_string(), + incentivization_fee_info: None, + guardian: None, + }, + &[], + "label", + None, + ) + .unwrap(); + + app.execute_contract( + owner.clone(), + factory.clone(), + &factory::ExecuteMsg::UpdateConfig { + token_code_id: None, + fee_address: None, + generator_address: Some(incentives.to_string()), + whitelist_code_id: None, + coin_registry_address: None, + }, + &[], + ) + .unwrap(); + + let whitelisting_fee = coin(1_000_000, astro_denom); + let emission_controller = app + .instantiate_contract( + emissions_controller_code_id, + owner.clone(), + &HubInstantiateMsg { + owner: owner.to_string(), + vxastro_code_id, + vxastro_marketing_info: Some(UpdateMarketingInfo { + project: None, + description: None, + marketing: None, + logo: Some(Logo::Url("".to_string())), + }), + vxastro_deposit_denom: xastro_denom.to_string(), + factory: factory.to_string(), + astro_denom: astro_denom.to_string(), + pools_per_outpost: 5, + whitelisting_fee: whitelisting_fee.clone(), + fee_receiver: app.api().addr_make("fee_receiver").to_string(), + whitelist_threshold: Decimal::percent(1), + }, + &[], + "label", + None, + ) + .unwrap(); + + let vxastro = app + .wrap() + .query_wasm_smart::( + &emission_controller, + &emissions_controller::hub::QueryMsg::Config {}, + ) + .unwrap() + .vxastro; + + let helper = Self { + app, + owner, + xastro: xastro_denom.to_string(), + astro: astro_denom.to_string(), + factory, + vxastro, + whitelisting_fee, + emission_controller, + incentives, + }; + dbg!(&helper); + + helper + } + + pub fn mint_tokens(&mut self, user: &Addr, coins: &[Coin]) -> AnyResult { + self.app.sudo( + BankSudo::Mint { + to_address: user.to_string(), + amount: coins.to_vec(), + } + .into(), + ) + } + + pub fn lock(&mut self, user: &Addr, amount: u128) -> AnyResult { + let funds = coins(amount, &self.xastro); + self.mint_tokens(user, &funds).unwrap(); + self.app.execute_contract( + user.clone(), + self.vxastro.clone(), + &voting_escrow::ExecuteMsg::Lock { receiver: None }, + &funds, + ) + } + + pub fn withdraw(&mut self, user: &Addr) -> AnyResult { + self.app.execute_contract( + user.clone(), + self.vxastro.clone(), + &voting_escrow::ExecuteMsg::Withdraw {}, + &[], + ) + } + + pub fn unlock(&mut self, user: &Addr) -> AnyResult { + self.app.execute_contract( + user.clone(), + self.vxastro.clone(), + &voting_escrow::ExecuteMsg::Unlock {}, + &[], + ) + } + + pub fn relock(&mut self, user: &Addr) -> AnyResult { + self.app.execute_contract( + user.clone(), + self.vxastro.clone(), + &voting_escrow::ExecuteMsg::Relock {}, + &[], + ) + } + + pub fn timetravel(&mut self, time: u64) { + self.app.update_block(|block| { + block.time = block.time.plus_seconds(time); + }) + } + + pub fn user_vp(&self, user: &Addr, time: Option) -> StdResult { + self.app.wrap().query_wasm_smart( + &self.vxastro, + &voting_escrow::QueryMsg::UserVotingPower { + user: user.to_string(), + time, + }, + ) + } + + pub fn user_info(&self, user: &Addr, timestamp: Option) -> StdResult { + self.app.wrap().query_wasm_smart( + &self.emission_controller, + &emissions_controller::hub::QueryMsg::UserInfo { + user: user.to_string(), + timestamp, + }, + ) + } + + pub fn create_pair(&mut self, denom1: &str, denom2: &str) -> Addr { + let asset_infos = vec![AssetInfo::native(denom1), AssetInfo::native(denom2)]; + self.app + .execute_contract( + self.owner.clone(), + self.factory.clone(), + &factory::ExecuteMsg::CreatePair { + pair_type: PairType::Xyk {}, + asset_infos: asset_infos.clone(), + init_params: None, + }, + &[], + ) + .unwrap(); + + self.app + .wrap() + .query_wasm_smart::(&self.factory, &factory::QueryMsg::Pair { asset_infos }) + .unwrap() + .liquidity_token + } + + pub fn vote(&mut self, user: &Addr, votes: &[(String, Decimal)]) -> AnyResult { + self.app.execute_contract( + user.clone(), + self.emission_controller.clone(), + &emissions_controller::msg::ExecuteMsg::::Vote { + votes: votes.to_vec(), + }, + &[], + ) + } + + pub fn whitelist( + &mut self, + user: &Addr, + pool: impl Into, + fees: &[Coin], + ) -> AnyResult { + self.app.execute_contract( + user.clone(), + self.emission_controller.clone(), + &emissions_controller::msg::ExecuteMsg::Custom(HubMsg::WhitelistPool { + pool: pool.into(), + }), + fees, + ) + } + + pub fn add_outpost(&mut self, prefix: &str, outpost: OutpostInfo) -> AnyResult { + self.app.execute_contract( + self.owner.clone(), + self.emission_controller.clone(), + &emissions_controller::msg::ExecuteMsg::Custom(HubMsg::UpdateOutpost { + prefix: prefix.to_string(), + astro_denom: outpost.astro_denom, + outpost_params: outpost.params, + astro_pool_config: outpost.astro_pool_config, + }), + &[], + ) + } + + pub fn query_voted_pool(&self, pool: &str, timestamp: Option) -> StdResult { + self.app.wrap().query_wasm_smart( + &self.emission_controller, + &emissions_controller::hub::QueryMsg::VotedPool { + pool: pool.to_string(), + timestamp, + }, + ) + } + + pub fn query_pool_vp(&self, pool: &str, timestamp: Option) -> StdResult { + self.query_voted_pool(pool, timestamp) + .map(|x| x.voting_power) + } + + pub fn query_voted_pools(&self, limit: Option) -> StdResult> { + self.app.wrap().query_wasm_smart( + &self.emission_controller, + &emissions_controller::hub::QueryMsg::VotedPoolsList { + limit, + start_after: None, + }, + ) + } + + pub fn query_pools_vp(&self, limit: Option) -> StdResult> { + self.query_voted_pools(limit).map(|res| { + res.into_iter() + .sorted_by(|a, b| a.0.cmp(&b.0)) + .map(|(pool, info)| (pool, info.voting_power)) + .collect_vec() + }) + } + + pub fn query_config(&self) -> StdResult { + self.app.wrap().query_wasm_smart( + &self.emission_controller, + &emissions_controller::hub::QueryMsg::Config {}, + ) + } + + pub fn tune(&mut self, sender: &Addr) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.emission_controller.clone(), + &emissions_controller::msg::ExecuteMsg::Custom(HubMsg::TunePools {}), + &[], + ) + } + + pub fn refresh_user_votes(&mut self, sender: &Addr) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.emission_controller.clone(), + &emissions_controller::msg::ExecuteMsg::::RefreshUserVotes {}, + &[], + ) + } + + pub fn retry_failed_outposts(&mut self, sender: &Addr) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.emission_controller.clone(), + &emissions_controller::msg::ExecuteMsg::Custom(HubMsg::RetryFailedOutposts {}), + &[], + ) + } + + pub fn query_tune_info( + &self, + timestamp: Option, + ) -> StdResult { + self.app.wrap().query_wasm_smart( + &self.emission_controller, + &emissions_controller::hub::QueryMsg::TuneInfo { timestamp }, + ) + } + + pub fn query_rewards(&self, pool: impl Into) -> StdResult> { + self.app + .wrap() + .query_wasm_smart::( + &self.incentives, + &incentives::QueryMsg::PoolInfo { + lp_token: pool.into(), + }, + ) + .map(|x| x.rewards) + } +} diff --git a/contracts/emissions_controller/tests/common/ibc_module.rs b/contracts/emissions_controller/tests/common/ibc_module.rs new file mode 100644 index 00000000..c84a9a82 --- /dev/null +++ b/contracts/emissions_controller/tests/common/ibc_module.rs @@ -0,0 +1,82 @@ +use cosmwasm_schema::serde::de::DeserializeOwned; +use cosmwasm_std::{ + to_json_binary, Addr, Api, Binary, BlockInfo, ChannelResponse, CustomMsg, CustomQuery, Empty, + IbcChannel, IbcEndpoint, IbcMsg, IbcOrder, IbcQuery, Querier, Storage, +}; +use cw_multi_test::error::{anyhow, AnyResult}; +use cw_multi_test::{AppResponse, CosmosRouter, Ibc, Module}; + +pub struct IbcMockModule; + +impl Ibc for IbcMockModule {} + +impl Module for IbcMockModule { + type ExecT = IbcMsg; + type QueryT = IbcQuery; + type SudoT = Empty; + + fn execute( + &self, + _api: &dyn Api, + _storage: &mut dyn Storage, + _router: &dyn CosmosRouter, + _block: &BlockInfo, + _sender: Addr, + _msg: Self::ExecT, + ) -> AnyResult + where + ExecC: CustomMsg + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, + { + unimplemented!() + } + + fn query( + &self, + _api: &dyn Api, + _storage: &dyn Storage, + _querier: &dyn Querier, + _block: &BlockInfo, + request: Self::QueryT, + ) -> AnyResult { + match &request { + IbcQuery::Channel { channel_id, .. } if channel_id == "channel-1" => { + to_json_binary(&ChannelResponse { + channel: Some(IbcChannel::new( + IbcEndpoint { + port_id: "".to_string(), + channel_id: "channel-1".to_string(), + }, + IbcEndpoint { + port_id: "".to_string(), + channel_id: "".to_string(), + }, + IbcOrder::Unordered, + "", + "", + )), + }) + .map_err(Into::into) + } + IbcQuery::Channel { .. } => { + to_json_binary(&ChannelResponse { channel: None }).map_err(Into::into) + } + _ => Err(anyhow!("Query {request:?} not supported")), + } + } + + fn sudo( + &self, + _api: &dyn Api, + _storage: &mut dyn Storage, + _router: &dyn CosmosRouter, + _block: &BlockInfo, + _msg: Self::SudoT, + ) -> AnyResult + where + ExecC: CustomMsg + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, + { + unimplemented!() + } +} diff --git a/contracts/emissions_controller/tests/common/mod.rs b/contracts/emissions_controller/tests/common/mod.rs new file mode 100644 index 00000000..782eead8 --- /dev/null +++ b/contracts/emissions_controller/tests/common/mod.rs @@ -0,0 +1,4 @@ +pub mod contracts; +pub mod helper; +mod ibc_module; +mod neutron_module; diff --git a/contracts/emissions_controller/tests/common/neutron_module.rs b/contracts/emissions_controller/tests/common/neutron_module.rs new file mode 100644 index 00000000..0d22ef66 --- /dev/null +++ b/contracts/emissions_controller/tests/common/neutron_module.rs @@ -0,0 +1,98 @@ +use astroport_governance::emissions_controller::consts::FEE_DENOM; +use cosmwasm_schema::serde::de::DeserializeOwned; +use cosmwasm_std::{ + coins, to_json_binary, Addr, Api, BankMsg, Binary, BlockInfo, CustomMsg, CustomQuery, Empty, + Querier, Storage, +}; +use cw_multi_test::error::{anyhow, AnyResult}; +use cw_multi_test::{AppResponse, CosmosRouter, MockApiBech32, Module}; +use neutron_sdk::bindings::msg::{IbcFee, NeutronMsg}; +use neutron_sdk::bindings::query::NeutronQuery; +use neutron_sdk::query::min_ibc_fee::MinIbcFeeResponse; + +pub struct MockNeutronModule { + ibc_escrow: Addr, +} + +impl MockNeutronModule { + pub fn new(api: &MockApiBech32) -> Self { + Self { + ibc_escrow: api.addr_make("ibc_escrow"), + } + } +} + +impl Module for MockNeutronModule { + type ExecT = NeutronMsg; + type QueryT = NeutronQuery; + type SudoT = Empty; + + fn execute( + &self, + api: &dyn Api, + storage: &mut dyn Storage, + router: &dyn CosmosRouter, + block: &BlockInfo, + sender: Addr, + msg: Self::ExecT, + ) -> AnyResult + where + ExecC: CustomMsg + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, + { + match msg { + NeutronMsg::IbcTransfer { token, .. } => { + router.execute( + api, + storage, + block, + sender, + BankMsg::Send { + to_address: self.ibc_escrow.to_string(), + amount: vec![token], + } + .into(), + )?; + } + _ => {} + } + + Ok(AppResponse::default()) + } + + fn query( + &self, + _api: &dyn Api, + _storage: &dyn Storage, + _querier: &dyn Querier, + _block: &BlockInfo, + request: Self::QueryT, + ) -> AnyResult { + match &request { + NeutronQuery::MinIbcFee {} => to_json_binary(&MinIbcFeeResponse { + min_fee: IbcFee { + recv_fee: vec![], + ack_fee: coins(100000, FEE_DENOM), + timeout_fee: coins(100000, FEE_DENOM), + }, + }) + .map_err(Into::into), + _ => Err(anyhow!("Unknown query: {request:?}")), + } + } + + fn sudo( + &self, + _api: &dyn Api, + _storage: &mut dyn Storage, + _router: &dyn CosmosRouter, + _block: &BlockInfo, + _msg: Self::SudoT, + ) -> AnyResult + where + ExecC: CustomMsg + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, + { + unimplemented!() + } +} diff --git a/contracts/emissions_controller/tests/emissions_controller_integration.rs b/contracts/emissions_controller/tests/emissions_controller_integration.rs new file mode 100644 index 00000000..aea085a8 --- /dev/null +++ b/contracts/emissions_controller/tests/emissions_controller_integration.rs @@ -0,0 +1,1333 @@ +use std::collections::HashMap; + +use astroport::asset::AssetInfo; +use astroport::incentives::RewardType; +use cosmwasm_std::{coin, coins, Decimal, Decimal256, Empty, Uint128}; +use cw_multi_test::Executor; +use cw_utils::PaymentError; +use itertools::Itertools; +use neutron_sdk::sudo::msg::{RequestPacket, TransferSudoMsg}; + +use astroport_emissions_controller::error::ContractError; +use astroport_emissions_controller::utils::get_epoch_start; +use astroport_governance::emissions_controller; +use astroport_governance::emissions_controller::consts::{DAY, EPOCH_LENGTH, VOTE_COOLDOWN}; +use astroport_governance::emissions_controller::hub::{ + AstroPoolConfig, HubMsg, OutpostInfo, OutpostParams, OutpostStatus, TuneInfo, UserInfoResponse, +}; +use astroport_governance::emissions_controller::msg::ExecuteMsg; +use astroport_voting_escrow::state::UNLOCK_PERIOD; + +use crate::common::helper::ControllerHelper; + +mod common; + +#[test] +pub fn voting_test() { + let mut helper = ControllerHelper::new(); + + let user = helper.app.api().addr_make("user"); + + let err = helper + .vote(&user, &[("lp_token".to_string(), Decimal::one())]) + .unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::ZeroVotingPower {} + ); + + helper.lock(&user, 1000).unwrap(); + + let lp_token1 = helper.create_pair("token1", "token2"); + let lp_token2 = helper.create_pair("token1", "token3"); + + let neutron = OutpostInfo { + astro_denom: helper.astro.clone(), + params: None, + astro_pool_config: None, + }; + helper.add_outpost("neutron", neutron).unwrap(); + + let whitelist_fee = helper.whitelisting_fee.clone(); + for pool in &[lp_token1.clone(), lp_token2.clone()] { + helper.mint_tokens(&user, &[whitelist_fee.clone()]).unwrap(); + helper + .whitelist(&user, pool, &[whitelist_fee.clone()]) + .unwrap(); + } + + let err = helper + .vote( + &user, + &[ + (lp_token1.to_string(), Decimal::one()), + (lp_token1.to_string(), Decimal::one()), + ], + ) + .unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::DuplicatedVotes {} + ); + + let err = helper + .vote( + &user, + &[ + (lp_token1.to_string(), Decimal::one()), + (lp_token2.to_string(), Decimal::one()), + ], + ) + .unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::InvalidTotalWeight {} + ); + + let err = helper + .vote( + &user, + &[ + (lp_token1.to_string(), Decimal::raw(1)), + (lp_token2.to_string(), Decimal::raw(1)), + ("lp_token3".to_string(), Decimal::raw(1)), + ("lp_token4".to_string(), Decimal::raw(1)), + ("lp_token5".to_string(), Decimal::raw(1)), + ("lp_token6".to_string(), Decimal::raw(1)), + ], + ) + .unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::ExceededMaxPoolsToVote {} + ); + + helper + .vote(&user, &[(lp_token1.to_string(), Decimal::one())]) + .unwrap(); + + let pool_vp = helper.query_pool_vp(lp_token1.as_str(), None).unwrap(); + let user_vp = helper.user_vp(&user, None).unwrap(); + assert_eq!(pool_vp, user_vp); + + let err = helper + .vote(&user, &[(lp_token1.to_string(), Decimal::one())]) + .unwrap_err(); + + helper.timetravel(1); + let block_time = helper.app.block_info().time.seconds(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::VoteCooldown(block_time + VOTE_COOLDOWN - 1) + ); + + helper.timetravel(VOTE_COOLDOWN + 1); + helper + .vote( + &user, + &[ + (lp_token1.to_string(), Decimal::percent(50)), + (lp_token2.to_string(), Decimal::percent(50)), + ], + ) + .unwrap(); + + let old_pool_vp = helper + .query_pool_vp(lp_token1.as_str(), Some(block_time)) + .unwrap(); + assert_eq!(old_pool_vp, pool_vp); + + // Check new voting power + let pool1_vp = helper.query_pool_vp(lp_token1.as_str(), None).unwrap(); + let pool2_vp = helper.query_pool_vp(lp_token2.as_str(), None).unwrap(); + + assert_eq!(pool1_vp.u128(), user_vp.u128() / 2); + assert_eq!(pool1_vp, pool2_vp); +} + +#[test] +fn test_whitelist() { + let mut helper = ControllerHelper::new(); + let owner = helper.owner.clone(); + let whitelist_fee = helper.whitelisting_fee.clone(); + + let lp_token = helper.create_pair("token1", "token2"); + + let err = helper.whitelist(&owner, &lp_token, &[]).unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::PaymentError(PaymentError::NoFunds {}) + ); + + helper + .mint_tokens(&owner, &[whitelist_fee.clone()]) + .unwrap(); + let err = helper + .whitelist(&owner, &lp_token, &coins(1, &whitelist_fee.denom)) + .unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::IncorrectWhitelistFee(whitelist_fee.clone()) + ); + + let err = helper + .whitelist(&owner, &lp_token, &[whitelist_fee.clone()]) + .unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::NoOutpostForPool(lp_token.to_string()) + ); + + let astro_pool = helper + .create_pair(helper.astro.clone().as_str(), "uusd") + .to_string(); + let neutron = OutpostInfo { + astro_denom: helper.astro.clone(), + params: None, + astro_pool_config: Some(AstroPoolConfig { + astro_pool: astro_pool.clone(), + constant_emissions: Uint128::one(), + }), + }; + helper.add_outpost("neutron", neutron).unwrap(); + + let err = helper + .whitelist(&owner, &astro_pool, &[whitelist_fee.clone()]) + .unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::IsAstroPool {} + ); + + // Try to whitelist non-existent pool + let err = helper + .whitelist( + &owner, + "factory/neutron1invalidaddr/astroport/share", + &[whitelist_fee.clone()], + ) + .unwrap_err(); + assert_eq!( + err.root_cause().to_string(), + "Generic error: Querier contract error: Generic error: Invalid input" + ); // cosmwasm tried to query invalid 'neutron1invalidaddr' address + + helper + .whitelist(&owner, &lp_token, &[whitelist_fee.clone()]) + .unwrap(); + + let fee_receiver = helper.query_config().unwrap().fee_receiver; + let fee_balance = helper + .app + .wrap() + .query_balance(fee_receiver, &whitelist_fee.denom) + .unwrap(); + assert_eq!(fee_balance, whitelist_fee); + + helper + .mint_tokens(&owner, &[whitelist_fee.clone()]) + .unwrap(); + let err = helper + .whitelist(&owner, &lp_token, &[whitelist_fee.clone()]) + .unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::PoolAlreadyWhitelisted(lp_token.to_string()) + ); + + let whitelist = helper + .app + .wrap() + .query_wasm_smart::>( + helper.emission_controller.clone(), + &emissions_controller::hub::QueryMsg::QueryWhitelist {}, + ) + .unwrap(); + assert_eq!(whitelist, vec![lp_token.to_string()]); +} + +#[test] +fn test_outpost_management() { + let mut helper = ControllerHelper::new(); + + let mut neutron = OutpostInfo { + astro_denom: helper.astro.clone(), + params: None, + astro_pool_config: Some(AstroPoolConfig { + astro_pool: "wasm1pool".to_string(), + constant_emissions: Uint128::one(), + }), + }; + + let err = helper + .app + .execute_contract( + helper.app.api().addr_make("random"), + helper.emission_controller.clone(), + &ExecuteMsg::Custom(HubMsg::UpdateOutpost { + prefix: "neutron".to_string(), + astro_denom: neutron.astro_denom.clone(), + outpost_params: neutron.params.clone(), + astro_pool_config: neutron.astro_pool_config.clone(), + }), + &[], + ) + .unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::Unauthorized {} + ); + + let err = helper.add_outpost("neutron", neutron.clone()).unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::InvalidOutpostPrefix("wasm1pool".to_string()) + ); + + neutron.astro_pool_config.as_mut().unwrap().astro_pool = + helper.create_pair("token1", "token2").to_string(); + neutron.astro_denom = "aa".to_string(); + + let err = helper.add_outpost("neutron", neutron.clone()).unwrap_err(); + assert_eq!( + err.root_cause().to_string(), + "Generic error: Invalid denom length [3,128]: aa" + ); + + neutron.astro_denom = "osmo1addr".to_string(); + let err = helper.add_outpost("neutron", neutron.clone()).unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::InvalidHubAstroDenom(helper.astro.clone()) + ); + + neutron.astro_denom.clone_from(&helper.astro); + neutron + .astro_pool_config + .as_mut() + .unwrap() + .constant_emissions = Uint128::zero(); + let err = helper.add_outpost("neutron", neutron.clone()).unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::ZeroAstroEmissions {} + ); + + neutron + .astro_pool_config + .as_mut() + .unwrap() + .constant_emissions = Uint128::one(); + helper.add_outpost("neutron", neutron.clone()).unwrap(); + + let mut osmosis = OutpostInfo { + astro_denom: "uastro".to_string(), + params: Some(OutpostParams { + emissions_controller: "osmo1controller".to_string(), + voting_channel: "channel-1".to_string(), + ics20_channel: "channel-2".to_string(), + }), + astro_pool_config: None, + }; + + let err = helper.add_outpost("osmo", osmosis.clone()).unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::InvalidOutpostAstroDenom {} + ); + + osmosis.astro_denom = + "ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9".to_string(); + osmosis.params.as_mut().unwrap().ics20_channel = "ch-2".to_string(); + + let err = helper.add_outpost("osmo", osmosis.clone()).unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::InvalidOutpostIcs20Channel {} + ); + + osmosis.params.as_mut().unwrap().ics20_channel = "channel-2".to_string(); + osmosis.params.as_mut().unwrap().voting_channel = "channel-200".to_string(); + + let err = helper.add_outpost("osmo", osmosis.clone()).unwrap_err(); + assert_eq!( + err.root_cause().to_string(), + "Generic error: The contract does not have channel channel-200" + ); + + osmosis.params.as_mut().unwrap().voting_channel = "channel-1".to_string(); + osmosis.params.as_mut().unwrap().emissions_controller = "terra1controller".to_string(); + + let err = helper.add_outpost("osmo", osmosis.clone()).unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::InvalidOutpostPrefix("terra1controller".to_string()) + ); + + osmosis.params.as_mut().unwrap().emissions_controller = "osmo1controller".to_string(); + helper.add_outpost("osmo", osmosis.clone()).unwrap(); + + let outposts = helper + .app + .wrap() + .query_wasm_smart::>( + helper.emission_controller.clone(), + &emissions_controller::hub::QueryMsg::ListOutposts {}, + ) + .unwrap(); + assert_eq!( + outposts, + vec![ + ("neutron".to_string(), neutron), + ("osmo".to_string(), osmosis) + ] + ); + + // Whitelist and vote for neutron pool before removing outpost + let user = helper.app.api().addr_make("user"); + helper + .mint_tokens(&user, &[helper.whitelisting_fee.clone()]) + .unwrap(); + let lp_token = helper.create_pair("token1", "token3"); + helper + .whitelist(&user, &lp_token, &[helper.whitelisting_fee.clone()]) + .unwrap(); + helper.lock(&user, 1000).unwrap(); + helper + .vote(&user, &[(lp_token.to_string(), Decimal::one())]) + .unwrap(); + + // Remove neutron outpost + let rand_user = helper.app.api().addr_make("random"); + let err = helper + .app + .execute_contract( + rand_user, + helper.emission_controller.clone(), + &ExecuteMsg::Custom(HubMsg::RemoveOutpost { + prefix: "neutron".to_string(), + }), + &[], + ) + .unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::Unauthorized {} + ); + + helper + .app + .execute_contract( + helper.owner.clone(), + helper.emission_controller.clone(), + &ExecuteMsg::Custom(HubMsg::RemoveOutpost { + prefix: "neutron".to_string(), + }), + &[], + ) + .unwrap(); + + // Cant vote for neutron pools anymore + let user = helper.app.api().addr_make("user2"); + helper.lock(&user, 1000).unwrap(); + let err = helper + .vote(&user, &[(lp_token.to_string(), Decimal::one())]) + .unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::PoolIsNotWhitelisted(lp_token.to_string()) + ); + + // Ensure neutron pool was removed from votable pools + let voted_pools = helper.query_pools_vp(None).unwrap(); + assert_eq!(voted_pools, vec![]); +} + +#[test] +fn test_tune_only_hub() { + let mut helper = ControllerHelper::new(); + let owner = helper.owner.clone(); + + let epoch_start = get_epoch_start(helper.app.block_info().time.seconds()); + + let err = helper.tune(&owner).unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::TuneCooldown(epoch_start + EPOCH_LENGTH) + ); + + let lp_token1 = helper.create_pair("token1", "token2"); + let lp_token2 = helper.create_pair("token1", "token3"); + let astro_pool = helper + .create_pair(helper.astro.clone().as_str(), "uusd") + .to_string(); + + let neutron = OutpostInfo { + astro_denom: helper.astro.clone(), + params: None, + astro_pool_config: Some(AstroPoolConfig { + astro_pool: astro_pool.clone(), + constant_emissions: 1_000_000_000u128.into(), + }), + }; + helper.add_outpost("neutron", neutron.clone()).unwrap(); + + let user = helper.app.api().addr_make("user"); + + let whitelist_fee = helper.whitelisting_fee.clone(); + for pool in &[lp_token1.clone(), lp_token2.clone()] { + helper.mint_tokens(&user, &[whitelist_fee.clone()]).unwrap(); + helper + .whitelist(&user, pool, &[whitelist_fee.clone()]) + .unwrap(); + } + + helper.lock(&user, 1000).unwrap(); + + helper + .vote( + &user, + &[ + (lp_token1.to_string(), Decimal::percent(50)), + (lp_token2.to_string(), Decimal::percent(50)), + ], + ) + .unwrap(); + + helper.timetravel(EPOCH_LENGTH - 1); + let err = helper.tune(&owner).unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::TuneCooldown(epoch_start + EPOCH_LENGTH) + ); + + helper.timetravel(1); + // Top up ASTRO for emissions + helper + .mint_tokens( + &helper.emission_controller.clone(), + &coins(50_000_000_000_000, helper.astro.clone()), + ) + .unwrap(); + helper.tune(&owner).unwrap(); + + let expected_rps = Decimal256::from_ratio(100_000_000_000u128 / 2, EPOCH_LENGTH); + let rewards = helper.query_rewards(&lp_token1).unwrap(); + let epoch_start = get_epoch_start(helper.app.block_info().time.seconds()); + let first_epoch_start = epoch_start; + assert_eq!(rewards.len(), 1); + assert_eq!(rewards[0].rps, expected_rps); + assert_eq!( + rewards[0].reward, + RewardType::Ext { + info: AssetInfo::native(&helper.astro), + next_update_ts: epoch_start + EPOCH_LENGTH + } + ); + // Check astro pool + let rewards = helper.query_rewards(&astro_pool).unwrap(); + let expected_rps = Decimal256::from_ratio( + neutron + .astro_pool_config + .as_ref() + .unwrap() + .constant_emissions, + EPOCH_LENGTH, + ); + assert_eq!(rewards.len(), 1); + assert_eq!(rewards[0].rps, expected_rps); + assert_eq!( + rewards[0].reward, + RewardType::Ext { + info: AssetInfo::native(&helper.astro), + next_update_ts: epoch_start + EPOCH_LENGTH + } + ); + + let epoch_start = get_epoch_start(helper.app.block_info().time.seconds()); + let err = helper.tune(&owner).unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::TuneCooldown(epoch_start + EPOCH_LENGTH) + ); + + // Imagine bot executed the tune late + helper.timetravel(EPOCH_LENGTH + 3 * DAY); + helper.tune(&owner).unwrap(); + + // User didn't change his votes. Emissions were 3 days late, thus their duration is 11 days. + let expected_rps = Decimal256::from_ratio(100_000_000_000u128 / 2, EPOCH_LENGTH - 3 * DAY); + let rewards = helper.query_rewards(&lp_token1).unwrap(); + let epoch_start = get_epoch_start(helper.app.block_info().time.seconds()); + assert_eq!(rewards.len(), 1); + assert_eq!(rewards[0].rps, expected_rps); + assert_eq!( + rewards[0].reward, + RewardType::Ext { + info: AssetInfo::native(&helper.astro), + next_update_ts: epoch_start + EPOCH_LENGTH + } + ); + // Check astro pool + let rewards = helper.query_rewards(&astro_pool).unwrap(); + let expected_rps = Decimal256::from_ratio( + neutron + .astro_pool_config + .as_ref() + .unwrap() + .constant_emissions, + EPOCH_LENGTH - 3 * DAY, + ); + assert_eq!(rewards.len(), 1); + assert_eq!(rewards[0].rps, expected_rps); + assert_eq!( + rewards[0].reward, + RewardType::Ext { + info: AssetInfo::native(&helper.astro), + next_update_ts: epoch_start + EPOCH_LENGTH + } + ); + + let mut tune_info = helper.query_tune_info(None).unwrap(); + tune_info + .pools_grouped + .iter_mut() + .for_each(|(_, pools)| pools.sort()); + let expected_tune_info = TuneInfo { + tune_ts: epoch_start, + pools_grouped: HashMap::from([( + "neutron".to_string(), + vec![ + (lp_token1.to_string(), Uint128::new(50000000000)), + (lp_token2.to_string(), Uint128::new(50000000000)), + (astro_pool.to_string(), Uint128::new(1000000000)), + ] + .into_iter() + .sorted() + .collect(), + )]), + outpost_emissions_statuses: Default::default(), + }; + assert_eq!(tune_info, expected_tune_info); + + // Check historical tune info + let mut tune_info = helper.query_tune_info(Some(first_epoch_start + 1)).unwrap(); + tune_info + .pools_grouped + .iter_mut() + .for_each(|(_, pools)| pools.sort()); + let expected_tune_info = TuneInfo { + tune_ts: first_epoch_start, + pools_grouped: HashMap::from([( + "neutron".to_string(), + vec![ + (lp_token1.to_string(), Uint128::new(50000000000)), + (lp_token2.to_string(), Uint128::new(50000000000)), + (astro_pool.to_string(), Uint128::new(1000000000)), + ] + .into_iter() + .sorted() + .collect(), + )]), + outpost_emissions_statuses: Default::default(), + }; + assert_eq!(tune_info, expected_tune_info); +} + +#[test] +fn test_tune_outpost() { + let mut helper = ControllerHelper::new(); + let owner = helper.owner.clone(); + + let lp_token1 = "factory/osmo1pool1/astroport/share"; + let lp_token2 = "factory/osmo1pool2/astroport/share"; + let astro_pool = "factory/osmo1astropool/astroport/share"; + + let osmosis = OutpostInfo { + astro_denom: "ibc/6569E05DEE32B339D9286A52BE33DFCEFC97267F23EF9CFDE0C055140967A9A5" + .to_string(), + params: Some(OutpostParams { + emissions_controller: "osmo1emissionscontroller".to_string(), + voting_channel: "channel-1".to_string(), + ics20_channel: "channel-2".to_string(), + }), + astro_pool_config: Some(AstroPoolConfig { + astro_pool: astro_pool.to_string(), + constant_emissions: 1_000_000_000u128.into(), + }), + }; + helper.add_outpost("osmo", osmosis.clone()).unwrap(); + + let whitelist_fee = helper.whitelisting_fee.clone(); + for pool in [lp_token1, lp_token2] { + helper + .mint_tokens(&owner, &[whitelist_fee.clone()]) + .unwrap(); + helper + .whitelist(&owner, pool, &[whitelist_fee.clone()]) + .unwrap(); + } + + let user = helper.app.api().addr_make("user"); + helper.lock(&user, 1000).unwrap(); + + helper + .vote( + &user, + &[ + (lp_token1.to_string(), Decimal::percent(50)), + (lp_token2.to_string(), Decimal::percent(50)), + ], + ) + .unwrap(); + + helper + .mint_tokens( + &helper.emission_controller.clone(), + &coins(50_000_000_000_000, helper.astro.clone()), + ) + .unwrap(); + + helper.timetravel(EPOCH_LENGTH); + helper.tune(&owner).unwrap(); + + let epoch_start = get_epoch_start(helper.app.block_info().time.seconds()); + let mut tune_info = helper.query_tune_info(None).unwrap(); + tune_info + .pools_grouped + .iter_mut() + .for_each(|(_, pools)| pools.sort()); + let expected_tune_info = TuneInfo { + tune_ts: epoch_start, + pools_grouped: HashMap::from([( + "osmo".to_string(), + vec![ + (lp_token1.to_string(), Uint128::new(50000000000)), + (lp_token2.to_string(), Uint128::new(50000000000)), + (astro_pool.to_string(), Uint128::new(1000000000)), + ] + .into_iter() + .sorted() + .collect(), + )]), + outpost_emissions_statuses: HashMap::from([( + "osmo".to_string(), + OutpostStatus::InProgress, + )]), + }; + assert_eq!(tune_info, expected_tune_info); + + // Try to retry outposts which are being in progress + let err = helper.retry_failed_outposts(&owner).unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::NoFailedOutpostsToRetry {} + ); + + // Mock ics20 IBC timeout + helper + .app + .wasm_sudo( + helper.emission_controller.clone(), + &TransferSudoMsg::Timeout { + request: RequestPacket { + sequence: None, + source_port: None, + source_channel: Some("channel-2".to_string()), + destination_port: None, + destination_channel: None, + data: None, + timeout_height: None, + timeout_timestamp: None, + }, + }, + ) + .unwrap(); + + // Try to mock ics20 message failure right after timeout even tho this must be impossible + let err = helper + .app + .wasm_sudo( + helper.emission_controller.clone(), + &TransferSudoMsg::Error { + request: RequestPacket { + sequence: None, + source_port: None, + source_channel: Some("channel-2".to_string()), + destination_port: None, + destination_channel: None, + data: None, + timeout_height: None, + timeout_timestamp: None, + }, + details: "".to_string(), + }, + ) + .unwrap_err(); + assert_eq!( + err.root_cause().to_string(), + "Generic error: Outpost osmo is not in progress" + ); + + // Retry failed outposts and mock IBC acknowledgment packet + helper.retry_failed_outposts(&owner).unwrap(); + helper + .app + .wasm_sudo( + helper.emission_controller.clone(), + &TransferSudoMsg::Response { + request: RequestPacket { + sequence: None, + source_port: None, + source_channel: Some("channel-2".to_string()), + destination_port: None, + destination_channel: None, + data: None, + timeout_height: None, + timeout_timestamp: None, + }, + data: Default::default(), + }, + ) + .unwrap(); + + helper.timetravel(10000); + + let mut tune_info = helper.query_tune_info(None).unwrap(); + tune_info + .pools_grouped + .iter_mut() + .for_each(|(_, pools)| pools.sort()); + let expected_tune_info = TuneInfo { + tune_ts: epoch_start, + pools_grouped: HashMap::from([( + "osmo".to_string(), + vec![ + (lp_token1.to_string(), Uint128::new(50000000000)), + (lp_token2.to_string(), Uint128::new(50000000000)), + (astro_pool.to_string(), Uint128::new(1000000000)), + ] + .into_iter() + .sorted() + .collect(), + )]), + outpost_emissions_statuses: HashMap::from([("osmo".to_string(), OutpostStatus::Done)]), + }; + assert_eq!(tune_info, expected_tune_info); + + // Confirm there is no outposts to retry + let err = helper.retry_failed_outposts(&owner).unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::NoFailedOutpostsToRetry {} + ); +} + +#[test] +fn test_lock_unlock_vxastro() { + let mut helper = ControllerHelper::new(); + + // Ensure nobody but vxASTRO can call UpdateUserVotes endpoint + let err = helper + .app + .execute_contract( + helper.app.api().addr_make("random"), + helper.emission_controller.clone(), + &ExecuteMsg::::UpdateUserVotes { + user: "user".to_string(), + is_unlock: false, + }, + &[], + ) + .unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::Unauthorized {} + ); + helper + .app + .execute_contract( + helper.vxastro.clone(), + helper.emission_controller.clone(), + &ExecuteMsg::::UpdateUserVotes { + user: helper.app.api().addr_make("random").to_string(), + is_unlock: false, + }, + &[], + ) + .unwrap(); + + let owner = helper.owner.clone(); + helper + .mint_tokens(&owner, &[coin(1000_000000, helper.astro.clone())]) + .unwrap(); + let whitelisting_fee = helper.whitelisting_fee.clone(); + + helper + .add_outpost( + "neutron", + OutpostInfo { + astro_denom: helper.astro.clone(), + params: None, + astro_pool_config: None, + }, + ) + .unwrap(); + + let pool1 = helper.create_pair("token1", "token2"); + helper + .whitelist(&owner, &pool1, &[whitelisting_fee.clone()]) + .unwrap(); + let pool2 = helper.create_pair("token1", "token3"); + helper + .whitelist(&owner, &pool2, &[whitelisting_fee.clone()]) + .unwrap(); + + let alice = helper.app.api().addr_make("alice"); + helper.lock(&alice, 1_000000).unwrap(); + + let bob = helper.app.api().addr_make("bob"); + helper.lock(&bob, 1_000000).unwrap(); + + let voting_block_ts = helper.app.block_info().time.seconds(); + // Alice and Bob vote 50:50 for two existing pools + for user in [&alice, &bob] { + helper + .vote( + user, + &[ + (pool1.to_string(), Decimal::percent(50)), + (pool2.to_string(), Decimal::percent(50)), + ], + ) + .unwrap(); + + let user_info = helper.user_info(user, None).unwrap(); + let user_info_historical = helper.user_info(user, Some(voting_block_ts)).unwrap(); + + assert_eq!(user_info, user_info_historical); + assert_eq!( + user_info, + UserInfoResponse { + vote_ts: voting_block_ts, + voting_power: 1_000000u128.into(), + votes: HashMap::from([ + (pool1.to_string(), Decimal::percent(50)), + (pool2.to_string(), Decimal::percent(50)), + ]), + applied_votes: HashMap::from([ + (pool1.to_string(), Decimal::percent(50)), + (pool2.to_string(), Decimal::percent(50)), + ]) + } + ); + } + + // Assert pools voting power + for pool in [&pool1, &pool2] { + let pool_vp = helper.query_pool_vp(pool.as_str(), None).unwrap(); + assert_eq!(pool_vp.u128(), 1_000000); + } + + helper.timetravel(3 * DAY); + + // Alice locks more astro + helper.lock(&alice, 1_000000).unwrap(); + + // Ensure pool voting power is updated + for pool in [&pool1, &pool2] { + let pool_vp = helper.query_pool_vp(pool.as_str(), None).unwrap(); + assert_eq!(pool_vp.u128(), 1_500000); + } + + // Bob starts unlocking + helper.unlock(&bob).unwrap(); + + // Ensure pool voting power is updated + for pool in [&pool1, &pool2] { + let pool_vp = helper.query_pool_vp(pool.as_str(), None).unwrap(); + assert_eq!(pool_vp.u128(), 1_000000); + } + + helper.timetravel(2 * DAY); + + // Bob relocks + helper.relock(&bob).unwrap(); + + // Ensure pool voting power is updated + for pool in [&pool1, &pool2] { + let pool_vp = helper.query_pool_vp(pool.as_str(), None).unwrap(); + assert_eq!(pool_vp.u128(), 1_500000); + } + + // Check historical queries + for user in [&alice, &bob] { + // Contract state is finalized at the end of the voting block, + // thus we are querying the next block + let user_info = helper.user_info(user, Some(voting_block_ts + 1)).unwrap(); + + assert_eq!( + user_info, + UserInfoResponse { + vote_ts: voting_block_ts, + voting_power: 1_000000u128.into(), + votes: HashMap::from([ + (pool1.to_string(), Decimal::percent(50)), + (pool2.to_string(), Decimal::percent(50)), + ]), + applied_votes: HashMap::from([ + (pool1.to_string(), Decimal::percent(50)), + (pool2.to_string(), Decimal::percent(50)), + ]) + } + ); + } + + let voted_pools = helper.query_pools_vp(Some(5)).unwrap(); + let mut expected_pools = vec![ + (pool1.to_string(), 1_500000u128.into()), + (pool2.to_string(), 1_500000u128.into()), + ]; + expected_pools.sort(); + assert_eq!(voted_pools, expected_pools); + + // Unlock and withdraw + helper.unlock(&alice).unwrap(); + helper.unlock(&bob).unwrap(); + + helper.timetravel(UNLOCK_PERIOD); + + helper.withdraw(&alice).unwrap(); + helper.withdraw(&bob).unwrap(); + + let alice_balance = helper + .app + .wrap() + .query_balance(alice, &helper.xastro) + .unwrap(); + let bob_balance = helper + .app + .wrap() + .query_balance(bob, &helper.xastro) + .unwrap(); + assert_eq!(alice_balance, coin(2_000000, "xastro")); + assert_eq!(bob_balance, coin(1_000000, "xastro")); +} + +#[test] +fn test_some_epochs() { + let mut helper = ControllerHelper::new(); + let owner = helper.owner.clone(); + let whitelisting_fee = helper.whitelisting_fee.clone(); + + helper + .add_outpost( + "osmo", + OutpostInfo { + astro_denom: "ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9" + .to_string(), + params: Some(OutpostParams { + emissions_controller: "osmo1controller".to_string(), + voting_channel: "channel-1".to_string(), + ics20_channel: "channel-2".to_string(), + }), + astro_pool_config: None, + }, + ) + .unwrap(); + let pool1 = "osmo1pool1"; + let pool2 = "osmo1pool2"; + helper + .mint_tokens(&owner, &coins(100000000, helper.astro.clone())) + .unwrap(); + helper + .whitelist(&owner, pool1, &[whitelisting_fee.clone()]) + .unwrap(); + helper + .whitelist(&owner, pool2, &[whitelisting_fee.clone()]) + .unwrap(); + + let user1 = helper.app.api().addr_make("user1"); + helper.lock(&user1, 1_000000).unwrap(); + let user2 = helper.app.api().addr_make("user2"); + helper.lock(&user2, 1_000000).unwrap(); + + helper + .vote(&user1, &[(pool1.to_string(), Decimal::one())]) + .unwrap(); + helper + .vote(&user2, &[(pool2.to_string(), Decimal::one())]) + .unwrap(); + + // Preparing controller balance for tuning + helper + .mint_tokens( + &helper.emission_controller.clone(), + &coins(1000000000000, helper.astro.clone()), + ) + .unwrap(); + + helper.timetravel(EPOCH_LENGTH); + + helper.tune(&user1).unwrap(); + let voted_pools = helper.query_pools_vp(None).unwrap(); + assert_eq!( + voted_pools, + [ + (pool1.to_string(), 1_000000u128.into()), + (pool2.to_string(), 1_000000u128.into()), + ] + ); + + helper.unlock(&user1).unwrap(); + + helper.timetravel(EPOCH_LENGTH); + helper.tune(&user1).unwrap(); + + // User1 unlocked, user2 still keeps his votes + let voted_pools = helper.query_pools_vp(None).unwrap(); + let expected_pools = vec![ + (pool1.to_string(), 0u128.into()), + (pool2.to_string(), 1_000000u128.into()), + ]; + assert_eq!(voted_pools, expected_pools); + + helper.relock(&user1).unwrap(); + helper.unlock(&user2).unwrap(); + + helper.timetravel(EPOCH_LENGTH); + helper.tune(&user1).unwrap(); + + // User1 relocked, user2 unlocked + let voted_pools = helper.query_pools_vp(None).unwrap(); + assert_eq!( + voted_pools, + [ + (pool1.to_string(), 1_000000u128.into()), + (pool2.to_string(), 0u128.into()), + ] + ); + + // Allow only 1 pool for tuning + helper + .app + .execute_contract( + owner.clone(), + helper.emission_controller.clone(), + &ExecuteMsg::Custom(HubMsg::UpdateConfig { + pools_per_outpost: Some(1), + whitelisting_fee: None, + fee_receiver: None, + }), + &[], + ) + .unwrap(); + + helper.timetravel(EPOCH_LENGTH); + helper.tune(&user1).unwrap(); + + // pool2 was removed from votable pools + let voted_pools = helper.query_pools_vp(None).unwrap(); + assert_eq!(voted_pools, [(pool1.to_string(), 1_000000u128.into())]); + + // And from whitelist + let whitelist = helper + .app + .wrap() + .query_wasm_smart::>( + helper.emission_controller.clone(), + &emissions_controller::hub::QueryMsg::QueryWhitelist {}, + ) + .unwrap(); + assert_eq!(whitelist, vec![pool1.to_string()]); + + // If user2 relocks his votes won't be restored as pool2 must be whitelisted again + helper.relock(&user2).unwrap(); + let voted_pools = helper.query_pools_vp(None).unwrap(); + assert_eq!(voted_pools, [(pool1.to_string(), 1_000000u128.into())]); + + // Whitelist pool2 again + helper + .whitelist(&owner, pool2, &[whitelisting_fee.clone()]) + .unwrap(); + + // Ensure that user2 votes are not applied + let user2_info = helper.user_info(&user2, None).unwrap(); + assert_eq!(user2_info.applied_votes, HashMap::new()); + + // User2 must refresh his votes + helper.refresh_user_votes(&user2).unwrap(); + + // His contribution must be restored + let voted_pools = helper.query_pools_vp(None).unwrap(); + assert_eq!( + voted_pools, + [ + (pool1.to_string(), 1_000000u128.into()), + (pool2.to_string(), 1_000000u128.into()), + ] + ); +} + +#[test] +fn test_change_ownership() { + let mut helper = ControllerHelper::new(); + + let new_owner = helper.app.api().addr_make("new_owner"); + + // New owner + let msg = ExecuteMsg::::ProposeNewOwner { + new_owner: new_owner.to_string(), + expires_in: 100, // seconds + }; + + // Unauthorized check + let err = helper + .app + .execute_contract( + helper.app.api().addr_make("not_owner"), + helper.emission_controller.clone(), + &msg, + &[], + ) + .unwrap_err(); + assert_eq!(err.root_cause().to_string(), "Generic error: Unauthorized"); + + // Claim before proposal + let err = helper + .app + .execute_contract( + new_owner.clone(), + helper.emission_controller.clone(), + &ExecuteMsg::::ClaimOwnership {}, + &[], + ) + .unwrap_err(); + assert_eq!( + err.root_cause().to_string(), + "Generic error: Ownership proposal not found" + ); + + // Propose a new owner + helper + .app + .execute_contract( + helper.owner.clone(), + helper.emission_controller.clone(), + &msg, + &[], + ) + .unwrap(); + + // Claim from invalid addr + let err = helper + .app + .execute_contract( + helper.app.api().addr_make("invalid_addr"), + helper.emission_controller.clone(), + &ExecuteMsg::::ClaimOwnership {}, + &[], + ) + .unwrap_err(); + assert_eq!(err.root_cause().to_string(), "Generic error: Unauthorized"); + + // Drop the ownership proposal + helper + .app + .execute_contract( + helper.owner.clone(), + helper.emission_controller.clone(), + &ExecuteMsg::::DropOwnershipProposal {}, + &[], + ) + .unwrap(); + + // Claim ownership + let err = helper + .app + .execute_contract( + new_owner.clone(), + helper.emission_controller.clone(), + &ExecuteMsg::::ClaimOwnership {}, + &[], + ) + .unwrap_err(); + assert_eq!( + err.root_cause().to_string(), + "Generic error: Ownership proposal not found" + ); + + // Propose a new owner again + helper + .app + .execute_contract( + helper.owner.clone(), + helper.emission_controller.clone(), + &msg, + &[], + ) + .unwrap(); + helper + .app + .execute_contract( + new_owner.clone(), + helper.emission_controller.clone(), + &ExecuteMsg::::ClaimOwnership {}, + &[], + ) + .unwrap(); + + assert_eq!(helper.query_config().unwrap().owner.to_string(), new_owner) +} + +#[test] +fn test_update_config() { + let mut helper = ControllerHelper::new(); + + let fee_receiver = helper.app.api().addr_make("fee_receiver"); + let msg = ExecuteMsg::Custom(HubMsg::UpdateConfig { + pools_per_outpost: Some(8), + whitelisting_fee: Some(coin(100, "astro")), + fee_receiver: Some(fee_receiver.to_string()), + }); + + let err = helper + .app + .execute_contract( + helper.app.api().addr_make("random"), + helper.emission_controller.clone(), + &msg, + &[], + ) + .unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::Unauthorized {} + ); + + helper + .app + .execute_contract( + helper.owner.clone(), + helper.emission_controller.clone(), + &msg, + &[], + ) + .unwrap(); + + let config = helper.query_config().unwrap(); + + assert_eq!( + config, + emissions_controller::hub::Config { + owner: helper.owner.clone(), + vxastro: helper.vxastro.clone(), + factory: helper.factory.clone(), + astro_denom: helper.astro.clone(), + pools_per_outpost: 8, + whitelisting_fee: coin(100, "astro"), + fee_receiver, + whitelist_threshold: Decimal::percent(1), + } + ); +} diff --git a/contracts/emissions_controller_outpost/Cargo.toml b/contracts/emissions_controller_outpost/Cargo.toml new file mode 100644 index 00000000..f1837e24 --- /dev/null +++ b/contracts/emissions_controller_outpost/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "astroport-emissions-controller-outpost" +version = "1.0.0" +authors = ["Astroport"] +edition = "2021" +description = "Astroport vxASTRO Emissions Voting Contract. Outpost version" +license = "GPL-3.0-only" +repository = "https://github.com/astroport-fi/astroport-governance" +homepage = "https://astroport.fi" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# use library feature to disable all init/handle/query exports +library = [] + +[dependencies] +cw2.workspace = true +cw-utils.workspace = true +cosmwasm-std = { workspace = true, features = ["ibc3"] } +cw-storage-plus.workspace = true +cosmwasm-schema.workspace = true +thiserror.workspace = true +itertools.workspace = true +astroport-governance = { path = "../../packages/astroport-governance", version = "3" } +astroport.workspace = true +serde_json = "1" + +[dev-dependencies] +cw-multi-test = "1" +astroport-voting-escrow = { path = "../voting_escrow", version = "1", features = ["library"] } +astroport-factory = { version = "1.7", features = ["library"] } +astroport-pair = { version = "1.5", features = ["library"] } +cw20-base = { version = "1", features = ["library"] } +astroport-incentives = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "1.2" } +derivative = "2.2" diff --git a/contracts/generator_controller/README.md b/contracts/emissions_controller_outpost/README.md similarity index 76% rename from contracts/generator_controller/README.md rename to contracts/emissions_controller_outpost/README.md index c5deb691..bc3d804b 100644 --- a/contracts/generator_controller/README.md +++ b/contracts/emissions_controller_outpost/README.md @@ -1,6 +1,7 @@ -# Generator Controller +# Emissions Controller -The Generator Controller allows vxASTRO holders to vote on changing `alloc_point`s in the Generator contract every 2 weeks. Note that the Controller contract uses the word "pool" when referring to LP tokens (generators) available in the Generator contract. +The Emissions Controller allows vxASTRO holders to vote on changing `alloc_point`s in the Generator contract every 2 +weeks. Note that the Controller contract uses the word "pool" when referring to LP tokens. ## InstantiateMsg @@ -9,10 +10,10 @@ and the max amount of pools that can receive ASTRO emissions at the same time. ```json { - "owner": "terra...", - "escrow_addr": "terra...", - "generator_addr": "terra...", - "factory_addr": "terra...", + "owner": "wasm...", + "escrow_addr": "wasm...", + "generator_addr": "wasm...", + "factory_addr": "wasm...", "pools_limit": 5 } ``` @@ -26,7 +27,10 @@ Remove votes of voters that are blacklisted. ```json { "kick_blacklisted_voters": { - "blacklisted_voters": ["terra...", "terra..."] + "blacklisted_voters": [ + "wasm...", + "wasm..." + ] } } ``` @@ -39,7 +43,7 @@ Sets various configuration parameters. Any of them can be omitted. { "update_config": { "blacklisted_voters_limit": 22, - "main_pool": "terra...", + "main_pool": "wasm...", "main_pool_min_alloc": "0.3" } } @@ -48,22 +52,23 @@ Sets various configuration parameters. Any of them can be omitted. ### `vote` Vote on pools that will start to get an ASTRO distribution in the next period. For example, assume an address has voting -power `100`. Then, following the example below, pools will receive voting power 10, 50, 40 respectively. Note that all values are scaled so they sum to 10,000. +power `100`. Then, following the example below, pools will receive voting power 10, 50, 40 respectively. Note that all +values are scaled so they sum to 10,000. ```json { "vote": { "votes": [ [ - "terra...", + "wasm...", 1000 ], [ - "terra...", + "wasm...", 5000 ], [ - "terra...", + "wasm...", 4000 ] ] @@ -101,7 +106,7 @@ Only the current contract owner can execute this method. ```json { "propose_new_owner": { - "owner": "terra...", + "owner": "wasm...", "expires_in": 1234567 } } @@ -135,12 +140,12 @@ Adds or removes lp tokens which are eligible to receive votes. { "update_whitelist": { "add": [ - "terra...", - "terra..." + "wasm...", + "wasm..." ], "remove": [ - "terra...", - "terra..." + "wasm...", + "wasm..." ] } } @@ -157,7 +162,7 @@ Request: ```json { "user_info": { - "user": "terra..." + "user": "wasm..." } } ``` @@ -173,15 +178,15 @@ Returns last user's voting parameters. "lock_end": 10, "votes": [ [ - "terra...", + "wasm...", 1000 ], [ - "terra...", + "wasm...", 5000 ], [ - "terra...", + "wasm...", 4000 ] ] @@ -199,11 +204,11 @@ Returns last tune information. "tune_ts": 1234567, "pool_alloc_points": [ [ - "terra...", + "wasm...", 4000 ], [ - "terra...", + "wasm...", 6000 ] ] @@ -220,7 +225,7 @@ Request: ```json { "pool_info": { - "pool_addr": "terra..." + "pool_addr": "wasm..." } } ``` @@ -245,7 +250,7 @@ Request: ```json { "pool_info_at_period": { - "pool_addr": "terra...", + "pool_addr": "wasm...", "period": 10 } } @@ -268,10 +273,10 @@ Returns the contract's config. ```json { - "owner": "terra...", - "escrow_addr": "terra...", - "generator_addr": "terra...", - "factory_addr": "terra...", + "owner": "wasm...", + "escrow_addr": "wasm...", + "generator_addr": "wasm...", + "factory_addr": "wasm...", "pools_limit": 5 } ``` diff --git a/contracts/emissions_controller_outpost/examples/emissions_controller_outpost_schema.rs b/contracts/emissions_controller_outpost/examples/emissions_controller_outpost_schema.rs new file mode 100644 index 00000000..9ed68a16 --- /dev/null +++ b/contracts/emissions_controller_outpost/examples/emissions_controller_outpost_schema.rs @@ -0,0 +1,14 @@ +use cosmwasm_schema::write_api; + +use astroport_governance::emissions_controller::msg::ExecuteMsg; +use astroport_governance::emissions_controller::outpost::{ + OutpostInstantiateMsg, OutpostMsg, QueryMsg, +}; + +fn main() { + write_api! { + instantiate: OutpostInstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg + } +} diff --git a/contracts/emissions_controller_outpost/src/error.rs b/contracts/emissions_controller_outpost/src/error.rs new file mode 100644 index 00000000..d5bdd750 --- /dev/null +++ b/contracts/emissions_controller_outpost/src/error.rs @@ -0,0 +1,48 @@ +use cosmwasm_std::{OverflowError, StdError, Uint128}; +use cw_utils::{ParseReplyError, PaymentError}; +use thiserror::Error; + +use astroport_governance::emissions_controller::consts::MAX_POOLS_TO_VOTE; + +/// This enum describes contract errors +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + PaymentError(#[from] PaymentError), + + #[error("{0}")] + OverflowError(#[from] OverflowError), + + #[error("{0}")] + ParseReplyError(#[from] ParseReplyError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("You can't vote with zero voting power")] + ZeroVotingPower {}, + + #[error("Invalid total votes weight. Must be 1.")] + InvalidTotalWeight {}, + + #[error("Failed to parse reply")] + FailedToParseReply {}, + + #[error("You can vote maximum for {MAX_POOLS_TO_VOTE} pools")] + ExceededMaxPoolsToVote {}, + + #[error("User {0} has pending IBC transaction. Wait until it is resolved by relayer")] + PendingUser(String), + + #[error("Message contains duplicated pools")] + DuplicatedVotes {}, + + #[error("Invalid astro amount. Expected: {expected}, actual: {actual}")] + InvalidAstroAmount { expected: Uint128, actual: Uint128 }, + + #[error("No valid schedules found")] + NoValidSchedules {}, +} diff --git a/contracts/emissions_controller_outpost/src/execute.rs b/contracts/emissions_controller_outpost/src/execute.rs new file mode 100644 index 00000000..39be8a76 --- /dev/null +++ b/contracts/emissions_controller_outpost/src/execute.rs @@ -0,0 +1,379 @@ +use std::collections::HashMap; + +use astroport::asset::determine_asset_info; +use astroport::common::{claim_ownership, drop_ownership_proposal, propose_new_owner}; +use astroport::incentives; +use astroport::incentives::{IncentivesSchedule, InputSchedule}; +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + attr, coin, coins, ensure, to_json_binary, wasm_execute, Addr, Coin, Decimal, DepsMut, Env, + IbcMsg, MessageInfo, Response, StdError, Uint128, +}; +use cw_utils::{may_pay, nonpayable}; +use itertools::Itertools; + +use astroport_governance::emissions_controller::consts::{IBC_TIMEOUT, MAX_POOLS_TO_VOTE}; +use astroport_governance::emissions_controller::msg::ExecuteMsg; +use astroport_governance::emissions_controller::msg::VxAstroIbcMsg; +use astroport_governance::emissions_controller::outpost::{Config, OutpostMsg}; +use astroport_governance::emissions_controller::utils::{ + check_lp_token, get_voting_power, query_incentives_addr, +}; +use astroport_governance::utils::check_contract_supports_channel; + +use crate::error::ContractError; +use crate::state::{CONFIG, OWNERSHIP_PROPOSAL, PENDING_MESSAGES}; + +/// Exposes all execute endpoints available in the contract. +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::Vote { votes } => handle_vote(deps, env, info, votes), + ExecuteMsg::UpdateUserVotes { user, is_unlock } => { + let config = CONFIG.load(deps.storage)?; + ensure!( + info.sender == config.vxastro, + ContractError::Unauthorized {} + ); + let voting_power = get_voting_power(deps.querier, &config.vxastro, &user, None)?; + handle_update_user( + deps, + env, + Addr::unchecked(user), + voting_power, + is_unlock, + config, + ) + } + ExecuteMsg::RefreshUserVotes {} => { + nonpayable(&info)?; + let config = CONFIG.load(deps.storage)?; + let voting_power = get_voting_power(deps.querier, &config.vxastro, &info.sender, None)?; + + // Blocking updates if this is not unlocking and new_voting_power is zero. + // Potentially reduces IBC spam attack vector + ensure!(!voting_power.is_zero(), ContractError::ZeroVotingPower {}); + handle_update_user(deps, env, info.sender, voting_power, false, config) + } + ExecuteMsg::ProposeNewOwner { + new_owner, + expires_in, + } => { + nonpayable(&info)?; + let config = CONFIG.load(deps.storage)?; + + propose_new_owner( + deps, + info, + env, + new_owner, + expires_in, + config.owner, + OWNERSHIP_PROPOSAL, + ) + .map_err(Into::into) + } + ExecuteMsg::DropOwnershipProposal {} => { + nonpayable(&info)?; + let config = CONFIG.load(deps.storage)?; + + drop_ownership_proposal(deps, info, config.owner, OWNERSHIP_PROPOSAL) + .map_err(Into::into) + } + ExecuteMsg::ClaimOwnership {} => { + nonpayable(&info)?; + claim_ownership(deps, info, env, OWNERSHIP_PROPOSAL, |deps, new_owner| { + CONFIG + .update::<_, StdError>(deps.storage, |mut v| { + v.owner = new_owner; + Ok(v) + }) + .map(|_| ()) + }) + .map_err(Into::into) + } + ExecuteMsg::Custom(outpost_msg) => match outpost_msg { + OutpostMsg::SetEmissions { schedules } => set_emissions(deps, env, info, schedules), + OutpostMsg::PermissionedSetEmissions { schedules } => { + permissioned_set_emissions(deps, env, info, schedules) + } + OutpostMsg::UpdateConfig { + voting_ibc_channel, + hub_emissions_controller, + ics20_channel, + } => update_config( + deps, + env, + info, + voting_ibc_channel, + hub_emissions_controller, + ics20_channel, + ), + }, + } +} + +/// Permissionless endpoint to set emissions for given pools. +/// Caller must send exact amount of ASTRO to cover all emissions. +pub fn set_emissions( + deps: DepsMut, + env: Env, + info: MessageInfo, + schedules: Vec<(String, InputSchedule)>, +) -> Result { + let config = CONFIG.load(deps.storage)?; + let amount = may_pay(&info, &config.astro_denom)?; + + // Ensure we received exact amount of ASTRO + let schedules_total: Uint128 = schedules + .iter() + .map(|(_, schedule)| schedule.reward.amount) + .sum(); + ensure!( + amount == schedules_total, + ContractError::InvalidAstroAmount { + expected: schedules_total, + actual: amount + } + ); + + let funds = coin(amount.u128(), &config.astro_denom); + execute_emissions(deps, env, funds, config, schedules) +} + +/// Permissioned endpoint to set emissions for given pools. +/// Only contract owner can call this function. +/// Caller may or may not send ASTRO to cover all emissions. +/// Contract uses whole available ASTRO balance. +pub fn permissioned_set_emissions( + deps: DepsMut, + env: Env, + info: MessageInfo, + schedules: Vec<(String, InputSchedule)>, +) -> Result { + let config = CONFIG.load(deps.storage)?; + ensure!(info.sender == config.owner, ContractError::Unauthorized {}); + + let balance = deps + .querier + .query_balance(&env.contract.address, &config.astro_denom)?; + + // Ensure we have enough ASTRO in balance + let schedules_total: Uint128 = schedules + .iter() + .map(|(_, schedule)| schedule.reward.amount) + .sum(); + ensure!( + balance.amount >= schedules_total, + ContractError::InvalidAstroAmount { + expected: schedules_total, + actual: balance.amount + } + ); + + execute_emissions(deps, env, balance, config, schedules) +} + +/// Main function to set emissions for given pools. +/// Filters out not eligible pools and sends leftover funds back to the Hub. +pub fn execute_emissions( + deps: DepsMut, + env: Env, + astro_balance: Coin, + config: Config, + schedules: Vec<(String, InputSchedule)>, +) -> Result { + // Filter not eligible pools and send leftover funds back to the Hub + let mut expected_amount = 0u128; + let schedules = schedules + .into_iter() + .filter(|(pool, schedule)| { + determine_asset_info(pool, deps.api) + .and_then(|maybe_lp| check_lp_token(deps.querier, &config.factory, &maybe_lp)) + .and_then(|_| IncentivesSchedule::from_input(&env, schedule)) + .map(|_| { + expected_amount += schedule.reward.amount.u128(); + }) + .is_ok() + }) + .collect_vec(); + + ensure!(!schedules.is_empty(), ContractError::NoValidSchedules {}); + + let incentives_contract = query_incentives_addr(deps.querier, &config.factory)?; + let incentives_msg = wasm_execute( + incentives_contract, + &incentives::ExecuteMsg::IncentivizeMany(schedules), + coins(expected_amount, &config.astro_denom), + )?; + + let excess_amount = astro_balance.amount.checked_sub(expected_amount.into())?; + + let mut response = Response::default() + .add_message(incentives_msg) + .add_attribute("action", "set_emissions"); + if !excess_amount.is_zero() { + // Send excess funds back to the Hub + let ibc_transfer_msg = IbcMsg::Transfer { + channel_id: config.ics20_channel, + to_address: config.hub_emissions_controller, + amount: coin(excess_amount.u128(), &config.astro_denom), + timeout: env.block.time.plus_seconds(IBC_TIMEOUT).into(), + }; + response = response + .add_message(ibc_transfer_msg) + .add_attribute("excess_amount", excess_amount); + } + + Ok(response) +} + +/// This function performs vote basic validation and sends an IBC packet to the Hub. +/// Emissions Controller on the Hub is responsible for checking whether user is eligible to vote again +/// as well as validates pools are whitelisted and correspond to a specific outpost. +pub fn handle_vote( + deps: DepsMut, + env: Env, + info: MessageInfo, + votes: Vec<(String, Decimal)>, +) -> Result { + nonpayable(&info)?; + + let votes_map: HashMap<_, _> = votes.iter().cloned().collect(); + ensure!( + votes.len() == votes_map.len(), + ContractError::DuplicatedVotes {} + ); + + ensure!( + votes_map.len() <= MAX_POOLS_TO_VOTE, + ContractError::ExceededMaxPoolsToVote {} + ); + + let mut total_weight = Decimal::zero(); + for weight in votes_map.values() { + total_weight += weight; + ensure!( + total_weight <= Decimal::one(), + ContractError::InvalidTotalWeight {} + ); + } + + let config = CONFIG.load(deps.storage)?; + let voting_power = get_voting_power(deps.querier, &config.vxastro, &info.sender, None)?; + ensure!(!voting_power.is_zero(), ContractError::ZeroVotingPower {}); + + let vote_payload = VxAstroIbcMsg::Vote { + voter: info.sender.to_string(), + voting_power, + votes: votes_map, + }; + + // Blocks any new IBC messages for users with pending IBC requests + // until the previous one is acknowledged, failed or timed out. + PENDING_MESSAGES.update(deps.storage, info.sender.as_ref(), |v| match v { + Some(_) => Err(ContractError::PendingUser(info.sender.to_string())), + None => Ok(vote_payload.clone()), + })?; + + let config = CONFIG.load(deps.storage)?; + let vote_ibc_msg = IbcMsg::SendPacket { + channel_id: config.voting_ibc_channel, + data: to_json_binary(&vote_payload)?, + timeout: env.block.time.plus_seconds(IBC_TIMEOUT).into(), + }; + + Ok(Response::default() + .add_attributes([("action", "vote")]) + .add_message(vote_ibc_msg)) +} + +/// This function sends an IBC packet to the Hub to update user's contribution to emissions voting. +/// The 'is_unlock' flag is used to force relock user in case of IBC error. +pub fn handle_update_user( + deps: DepsMut, + env: Env, + voter: Addr, + voting_power: Uint128, + is_unlock: bool, + config: Config, +) -> Result { + let attrs = vec![ + attr("action", "update_user_votes"), + attr("voter", &voter), + attr("new_voting_power", voting_power), + ]; + + let payload = VxAstroIbcMsg::UpdateUserVotes { + voter: voter.to_string(), + voting_power, + is_unlock, + }; + + // Blocks any new IBC messages for users with pending IBC requests + // until the previous one is acknowledged, failed or timed out. + PENDING_MESSAGES.update(deps.storage, voter.as_str(), |v| match v { + Some(_) => Err(ContractError::PendingUser(voter.to_string())), + None => Ok(payload.clone()), + })?; + + let ibc_msg = IbcMsg::SendPacket { + channel_id: config.voting_ibc_channel, + data: to_json_binary(&payload)?, + timeout: env.block.time.plus_seconds(IBC_TIMEOUT).into(), + }; + + Ok(Response::default() + .add_attributes(attrs) + .add_message(ibc_msg)) +} + +/// Only contract owner can call this function. +/// * voting_ibc_channel: new IBC channel to send votes to the Hub. +/// The contract must be connected to this channel. +/// * hub_emissions_controller: new address of the Hub Emissions Controller contract. +/// * ics20_channel: new ICS20 channel to send ASTRO tokens to the Hub. +fn update_config( + deps: DepsMut, + env: Env, + info: MessageInfo, + voting_ibc_channel: Option, + hub_emissions_controller: Option, + ics20_channel: Option, +) -> Result { + nonpayable(&info)?; + let mut config = CONFIG.load(deps.storage)?; + + ensure!(info.sender == config.owner, ContractError::Unauthorized {}); + + let mut attrs = vec![attr("action", "update_config")]; + + if let Some(voting_ibc_channel) = voting_ibc_channel { + check_contract_supports_channel(deps.querier, &env.contract.address, &voting_ibc_channel)?; + attrs.push(attr("new_voting_ibc_channel", &voting_ibc_channel)); + config.voting_ibc_channel = voting_ibc_channel; + } + + if let Some(hub_emissions_controller) = hub_emissions_controller { + attrs.push(attr( + "new_hub_emissions_controller", + &hub_emissions_controller, + )); + config.hub_emissions_controller = hub_emissions_controller; + } + + if let Some(ics20_channel) = ics20_channel { + attrs.push(attr("new_ics20_channel", &ics20_channel)); + config.ics20_channel = ics20_channel; + } + + CONFIG.save(deps.storage, &config)?; + + Ok(Response::default().add_attributes(attrs)) +} diff --git a/contracts/emissions_controller_outpost/src/ibc.rs b/contracts/emissions_controller_outpost/src/ibc.rs new file mode 100644 index 00000000..f5020a8e --- /dev/null +++ b/contracts/emissions_controller_outpost/src/ibc.rs @@ -0,0 +1,298 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + ensure, from_json, wasm_execute, DepsMut, Env, Ibc3ChannelOpenResponse, IbcBasicResponse, + IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg, IbcPacketAckMsg, + IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse, Never, StdError, StdResult, + Storage, +}; + +use astroport_governance::emissions_controller::consts::{IBC_APP_VERSION, IBC_ORDERING}; +use astroport_governance::emissions_controller::msg::{IbcAckResult, VxAstroIbcMsg}; +use astroport_governance::emissions_controller::outpost::UserIbcError; +use astroport_governance::voting_escrow; + +use crate::state::{CONFIG, PENDING_MESSAGES, USER_IBC_ERROR}; + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn ibc_channel_open( + _deps: DepsMut, + _env: Env, + msg: IbcChannelOpenMsg, +) -> StdResult> { + let channel = msg.channel(); + + ensure!( + channel.order == IBC_ORDERING, + StdError::generic_err("Ordering is invalid. The channel must be unordered",) + ); + ensure!( + channel.version == IBC_APP_VERSION, + StdError::generic_err(format!("Must set version to `{IBC_APP_VERSION}`",)) + ); + if let Some(counter_version) = msg.counterparty_version() { + if counter_version != IBC_APP_VERSION { + return Err(StdError::generic_err(format!( + "Counterparty version must be `{IBC_APP_VERSION}`" + ))); + } + } + + Ok(Some(Ibc3ChannelOpenResponse { + version: IBC_APP_VERSION.to_string(), + })) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn ibc_channel_connect( + _deps: DepsMut, + _env: Env, + msg: IbcChannelConnectMsg, +) -> StdResult { + let channel = msg.channel(); + + if let Some(counter_version) = msg.counterparty_version() { + if counter_version != IBC_APP_VERSION { + return Err(StdError::generic_err(format!( + "Counterparty version must be `{IBC_APP_VERSION}`" + ))); + } + } + + Ok(IbcBasicResponse::new() + .add_attribute("action", "ibc_connect") + .add_attribute("channel_id", &channel.endpoint.channel_id)) +} + +#[cfg(not(tarpaulin_include))] +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn ibc_packet_receive( + _deps: DepsMut, + _env: Env, + _msg: IbcPacketReceiveMsg, +) -> Result { + unimplemented!("This contract is only sending IBC messages") +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn ibc_packet_ack( + deps: DepsMut, + _env: Env, + msg: IbcPacketAckMsg, +) -> StdResult { + let orig_msg: VxAstroIbcMsg = from_json(&msg.original_packet.data)?; + match from_json(&msg.acknowledgement.data)? { + IbcAckResult::Ok(_) => { + let mut response = IbcBasicResponse::new().add_attribute("action", "ibc_packet_ack"); + let voter = match &orig_msg { + VxAstroIbcMsg::UpdateUserVotes { + voter, + is_unlock: true, + .. + } => { + let config = CONFIG.load(deps.storage)?; + let relock_msg = wasm_execute( + config.vxastro, + &voting_escrow::ExecuteMsg::ConfirmUnlock { + user: voter.to_string(), + }, + vec![], + )?; + response = response + .add_attribute("action", "confirm_vxastro_unlock") + .add_message(relock_msg); + + voter + } + VxAstroIbcMsg::UpdateUserVotes { voter, .. } + | VxAstroIbcMsg::Vote { voter, .. } => voter, + }; + USER_IBC_ERROR.remove(deps.storage, voter); + PENDING_MESSAGES.remove(deps.storage, voter); + + Ok(response) + } + IbcAckResult::Error(err) => process_ibc_error(deps.storage, orig_msg, err), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn ibc_packet_timeout( + deps: DepsMut, + _env: Env, + msg: IbcPacketTimeoutMsg, +) -> StdResult { + process_ibc_error( + deps.storage, + from_json(msg.packet.data)?, + "IBC packet timeout".to_string(), + ) +} + +#[cfg(not(tarpaulin_include))] +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn ibc_channel_close( + _deps: DepsMut, + _env: Env, + _channel: IbcChannelCloseMsg, +) -> StdResult { + unimplemented!() +} + +pub fn process_ibc_error( + storage: &mut dyn Storage, + msg: VxAstroIbcMsg, + err: String, +) -> StdResult { + let mut response = IbcBasicResponse::default().add_attribute("action", "process_ibc_error"); + let voter = match &msg { + VxAstroIbcMsg::UpdateUserVotes { + voter, + is_unlock: true, + .. + } => { + // Relock user vxASTRO in case IBC failed + let config = CONFIG.load(storage)?; + let relock_msg = wasm_execute( + config.vxastro, + &voting_escrow::ExecuteMsg::ForceRelock { + user: voter.to_string(), + }, + vec![], + )?; + response = response + .add_attribute("action", "relock_user_vxastro") + .add_message(relock_msg); + voter.clone() + } + VxAstroIbcMsg::Vote { voter, .. } | VxAstroIbcMsg::UpdateUserVotes { voter, .. } => { + voter.clone() + } + }; + + USER_IBC_ERROR.save(storage, &voter, &UserIbcError { msg, err })?; + PENDING_MESSAGES.remove(storage, &voter); + + Ok(response) +} + +#[cfg(test)] +mod unit_tests { + use cosmwasm_std::testing::{mock_dependencies, mock_env}; + use cosmwasm_std::{IbcChannel, IbcEndpoint, IbcOrder}; + + use super::*; + + #[test] + fn test_channel_open() { + let mut deps = mock_dependencies(); + + let mut ibc_channel = IbcChannel::new( + IbcEndpoint { + port_id: "doesnt matter".to_string(), + channel_id: "doesnt matter".to_string(), + }, + IbcEndpoint { + port_id: "doesnt matter".to_string(), + channel_id: "doesnt matter".to_string(), + }, + IbcOrder::Unordered, + IBC_APP_VERSION, + "doesnt matter", + ); + let res = ibc_channel_open( + deps.as_mut(), + mock_env(), + IbcChannelOpenMsg::new_init(ibc_channel.clone()), + ) + .unwrap() + .unwrap(); + + assert_eq!(res.version, IBC_APP_VERSION); + + ibc_channel.order = IbcOrder::Ordered; + + let res = ibc_channel_open( + deps.as_mut(), + mock_env(), + IbcChannelOpenMsg::new_init(ibc_channel.clone()), + ) + .unwrap_err(); + assert_eq!( + res, + StdError::generic_err("Ordering is invalid. The channel must be unordered") + ); + + ibc_channel.order = IbcOrder::Unordered; + ibc_channel.version = "wrong_version".to_string(); + + let res = ibc_channel_open( + deps.as_mut(), + mock_env(), + IbcChannelOpenMsg::new_init(ibc_channel.clone()), + ) + .unwrap_err(); + assert_eq!( + res, + StdError::generic_err(format!("Must set version to `{IBC_APP_VERSION}`")) + ); + + ibc_channel.version = IBC_APP_VERSION.to_string(); + + let res = ibc_channel_open( + deps.as_mut(), + mock_env(), + IbcChannelOpenMsg::new_try(ibc_channel.clone(), "wrong_version"), + ) + .unwrap_err(); + assert_eq!( + res, + StdError::generic_err(format!("Counterparty version must be `{IBC_APP_VERSION}`")) + ); + + ibc_channel_open( + deps.as_mut(), + mock_env(), + IbcChannelOpenMsg::new_try(ibc_channel.clone(), IBC_APP_VERSION), + ) + .unwrap() + .unwrap(); + } + + #[test] + fn test_channel_connect() { + let mut deps = mock_dependencies(); + + let ibc_channel = IbcChannel::new( + IbcEndpoint { + port_id: "doesnt matter".to_string(), + channel_id: "doesnt matter".to_string(), + }, + IbcEndpoint { + port_id: "doesnt matter".to_string(), + channel_id: "doesnt matter".to_string(), + }, + IbcOrder::Unordered, + IBC_APP_VERSION, + "doesnt matter", + ); + + ibc_channel_connect( + deps.as_mut(), + mock_env(), + IbcChannelConnectMsg::new_ack(ibc_channel.clone(), IBC_APP_VERSION), + ) + .unwrap(); + + let err = ibc_channel_connect( + deps.as_mut(), + mock_env(), + IbcChannelConnectMsg::new_ack(ibc_channel.clone(), "wrong version"), + ) + .unwrap_err(); + assert_eq!( + err, + StdError::generic_err(format!("Counterparty version must be `{IBC_APP_VERSION}`")) + ); + } +} diff --git a/contracts/emissions_controller_outpost/src/instantiate.rs b/contracts/emissions_controller_outpost/src/instantiate.rs new file mode 100644 index 00000000..394fabfb --- /dev/null +++ b/contracts/emissions_controller_outpost/src/instantiate.rs @@ -0,0 +1,98 @@ +use astroport::asset::validate_native_denom; +use astroport_governance::emissions_controller::outpost::OutpostInstantiateMsg; +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + ensure, to_json_binary, Addr, DepsMut, Env, MessageInfo, Reply, Response, StdError, SubMsg, + SubMsgResponse, SubMsgResult, WasmMsg, +}; +use cw2::set_contract_version; +use cw_utils::parse_instantiate_response_data; + +use astroport_governance::emissions_controller::outpost::Config; +use astroport_governance::voting_escrow; + +use crate::error::ContractError; +use crate::state::CONFIG; + +/// Contract name that is used for migration. +pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +/// Contract version that is used for migration. +pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); +/// ID for the vxastro contract instantiate reply +pub const INSTANTIATE_VXASTRO_REPLY_ID: u64 = 1; + +/// Creates a new contract with the specified parameters in the [`InstantiateMsg`]. +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + env: Env, + _info: MessageInfo, + msg: OutpostInstantiateMsg, +) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + validate_native_denom(&msg.astro_denom)?; + + let config = Config { + owner: deps.api.addr_validate(&msg.owner)?, + vxastro: Addr::unchecked(""), + astro_denom: msg.astro_denom, + factory: deps.api.addr_validate(&msg.factory)?, + // Contract owner is responsible for setting a channel via UpdateConfig + voting_ibc_channel: "".to_string(), + hub_emissions_controller: msg.hub_emissions_controller, + ics20_channel: msg.ics20_channel, + }; + + CONFIG.save(deps.storage, &config)?; + + // Instantiate vxASTRO contract + validate_native_denom(&msg.vxastro_deposit_denom)?; + let init_vxastro_msg = WasmMsg::Instantiate { + admin: Some(msg.owner), + code_id: msg.vxastro_code_id, + msg: to_json_binary(&voting_escrow::InstantiateMsg { + deposit_denom: msg.vxastro_deposit_denom.to_string(), + emissions_controller: env.contract.address.to_string(), + marketing: msg.vxastro_marketing_info, + })?, + funds: vec![], + label: "Vote Escrowed xASTRO".to_string(), + }; + + Ok(Response::default() + .add_attribute("action", "instantiate_emissions_controller") + .add_submessage(SubMsg::reply_on_success( + init_vxastro_msg, + INSTANTIATE_VXASTRO_REPLY_ID, + ))) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result { + match msg { + Reply { + id: INSTANTIATE_VXASTRO_REPLY_ID, + result: + SubMsgResult::Ok(SubMsgResponse { + data: Some(data), .. + }), + } => { + let vxastro_contract = parse_instantiate_response_data(&data)?.contract_address; + + CONFIG.update::<_, StdError>(deps.storage, |mut config| { + ensure!( + config.vxastro == Addr::unchecked(""), + StdError::generic_err("vxASTRO contract is already set") + ); + + config.vxastro = Addr::unchecked(&vxastro_contract); + Ok(config) + })?; + + Ok(Response::new().add_attribute("vxastro", vxastro_contract)) + } + _ => Err(ContractError::FailedToParseReply {}), + } +} diff --git a/contracts/emissions_controller_outpost/src/lib.rs b/contracts/emissions_controller_outpost/src/lib.rs new file mode 100644 index 00000000..28edd73c --- /dev/null +++ b/contracts/emissions_controller_outpost/src/lib.rs @@ -0,0 +1,7 @@ +pub mod execute; +pub mod state; + +pub mod error; +pub mod ibc; +pub mod instantiate; +pub mod query; diff --git a/contracts/emissions_controller_outpost/src/query.rs b/contracts/emissions_controller_outpost/src/query.rs new file mode 100644 index 00000000..55afa152 --- /dev/null +++ b/contracts/emissions_controller_outpost/src/query.rs @@ -0,0 +1,19 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{to_json_binary, Binary, Deps, Env, StdResult}; + +use astroport_governance::emissions_controller::outpost::{QueryMsg, UserIbcStatus}; + +use crate::state::{CONFIG, PENDING_MESSAGES, USER_IBC_ERROR}; + +/// Expose available contract queries. +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Config {} => to_json_binary(&CONFIG.load(deps.storage)?), + QueryMsg::QueryUserIbcStatus { user } => to_json_binary(&UserIbcStatus { + pending_msg: PENDING_MESSAGES.may_load(deps.storage, &user)?, + error: USER_IBC_ERROR.may_load(deps.storage, &user)?, + }), + } +} diff --git a/contracts/emissions_controller_outpost/src/state.rs b/contracts/emissions_controller_outpost/src/state.rs new file mode 100644 index 00000000..2cf73075 --- /dev/null +++ b/contracts/emissions_controller_outpost/src/state.rs @@ -0,0 +1,17 @@ +use astroport::common::OwnershipProposal; +use astroport_governance::emissions_controller::msg::VxAstroIbcMsg; +use cw_storage_plus::{Item, Map}; + +use astroport_governance::emissions_controller::outpost::{Config, UserIbcError}; + +/// Stores config at the given key. +pub const CONFIG: Item = Item::new("config"); + +/// Contains a proposal to change contract ownership +pub const OWNERSHIP_PROPOSAL: Item = Item::new("ownership_proposal"); +/// Stores the latest IBC error and message. +pub const USER_IBC_ERROR: Map<&str, UserIbcError> = Map::new("user_ibc_error"); +/// Keeps the list of users with pending IBC requests. +/// The contract blocks any new IBC messages for these users +/// until the previous one is acknowledged, failed or timed out. +pub const PENDING_MESSAGES: Map<&str, VxAstroIbcMsg> = Map::new("pending_messages"); diff --git a/contracts/emissions_controller_outpost/tests/common/contracts.rs b/contracts/emissions_controller_outpost/tests/common/contracts.rs new file mode 100644 index 00000000..7c32c09e --- /dev/null +++ b/contracts/emissions_controller_outpost/tests/common/contracts.rs @@ -0,0 +1,83 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{ + DepsMut, Empty, Env, IbcPacketAckMsg, IbcPacketTimeoutMsg, Response, StdResult, +}; +use cw_multi_test::{Contract, ContractWrapper}; + +use astroport_emissions_controller_outpost::ibc::{ibc_packet_ack, ibc_packet_timeout}; + +pub fn token_contract() -> Box> { + Box::new(ContractWrapper::new_with_empty( + cw20_base::contract::execute, + cw20_base::contract::instantiate, + cw20_base::contract::query, + )) +} + +pub fn pair_contract() -> Box> { + Box::new( + ContractWrapper::new_with_empty( + astroport_pair::contract::execute, + astroport_pair::contract::instantiate, + astroport_pair::contract::query, + ) + .with_reply_empty(astroport_pair::contract::reply), + ) +} + +pub fn vxastro_contract() -> Box> { + Box::new(ContractWrapper::new_with_empty( + astroport_voting_escrow::contract::execute, + astroport_voting_escrow::contract::instantiate, + astroport_voting_escrow::contract::query, + )) +} + +pub fn incentives_contract() -> Box> { + Box::new(ContractWrapper::new_with_empty( + astroport_incentives::execute::execute, + astroport_incentives::instantiate::instantiate, + astroport_incentives::query::query, + )) +} + +pub fn factory_contract() -> Box> { + Box::new( + ContractWrapper::new_with_empty( + astroport_factory::contract::execute, + astroport_factory::contract::instantiate, + astroport_factory::contract::query, + ) + .with_reply_empty(astroport_factory::contract::reply), + ) +} + +#[cw_serde] +pub enum TestSudoMsg { + Ack(IbcPacketAckMsg), + Timeout(IbcPacketTimeoutMsg), +} + +fn sudo(deps: DepsMut, env: Env, msg: TestSudoMsg) -> StdResult { + match msg { + TestSudoMsg::Ack(packet) => ibc_packet_ack(deps, env, packet), + TestSudoMsg::Timeout(packet) => ibc_packet_timeout(deps, env, packet), + } + .map(|ibc_response| { + Response::default() + .add_attributes(ibc_response.attributes) + .add_submessages(ibc_response.messages) + }) +} + +pub fn emissions_controller() -> Box> { + Box::new( + ContractWrapper::new( + astroport_emissions_controller_outpost::execute::execute, + astroport_emissions_controller_outpost::instantiate::instantiate, + astroport_emissions_controller_outpost::query::query, + ) + .with_reply_empty(astroport_emissions_controller_outpost::instantiate::reply) + .with_sudo_empty(sudo), + ) +} diff --git a/contracts/emissions_controller_outpost/tests/common/helper.rs b/contracts/emissions_controller_outpost/tests/common/helper.rs new file mode 100644 index 00000000..b3b12bc8 --- /dev/null +++ b/contracts/emissions_controller_outpost/tests/common/helper.rs @@ -0,0 +1,447 @@ +use astroport::asset::{AssetInfo, PairInfo}; +use astroport::factory::{PairConfig, PairType}; +use astroport::incentives::{InputSchedule, RewardInfo}; +use astroport::token::Logo; +use astroport::{factory, incentives}; +use cosmwasm_std::{ + coin, coins, to_json_binary, Addr, BlockInfo, Coin, Decimal, Empty, IbcAcknowledgement, + IbcEndpoint, IbcPacket, IbcPacketAckMsg, IbcPacketTimeoutMsg, MemoryStorage, StdResult, + Timestamp, Uint128, +}; +use cw_multi_test::error::AnyResult; +use cw_multi_test::{ + no_init, App, AppBuilder, AppResponse, BankKeeper, BankSudo, DistributionKeeper, Executor, + FailingModule, MockAddressGenerator, MockApiBech32, StakeKeeper, WasmKeeper, +}; +use derivative::Derivative; + +use astroport_governance::emissions_controller::consts::{EPOCHS_START, EPOCH_LENGTH}; +use astroport_governance::emissions_controller::msg::{ExecuteMsg, IbcAckResult, VxAstroIbcMsg}; +use astroport_governance::emissions_controller::outpost::{OutpostInstantiateMsg, OutpostMsg}; +use astroport_governance::voting_escrow::{LockInfoResponse, UpdateMarketingInfo}; +use astroport_governance::{emissions_controller, voting_escrow}; + +use crate::common::contracts::*; +use crate::common::ibc_module::IbcMockModule; + +pub type OutpostApp = App< + BankKeeper, + MockApiBech32, + MemoryStorage, + FailingModule, + WasmKeeper, + StakeKeeper, + DistributionKeeper, + IbcMockModule, +>; + +fn mock_app() -> OutpostApp { + AppBuilder::new() + .with_ibc(IbcMockModule) + .with_api(MockApiBech32::new("osmo")) + .with_wasm(WasmKeeper::new().with_address_generator(MockAddressGenerator)) + .with_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds(EPOCHS_START), + chain_id: "cw-multitest-1".to_string(), + }) + .build(no_init) +} + +/// Normalize current timestamp to the beginning of the current epoch (Monday). +pub fn get_epoch_start(timestamp: u64) -> u64 { + let rem = timestamp % EPOCHS_START; + if rem % EPOCH_LENGTH == 0 { + // Hit at the beginning of the current epoch + timestamp + } else { + // Hit somewhere in the middle + EPOCHS_START + rem / EPOCH_LENGTH * EPOCH_LENGTH + } +} + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct ControllerHelper { + #[derivative(Debug = "ignore")] + pub app: OutpostApp, + pub owner: Addr, + pub astro: String, + pub xastro: String, + pub factory: Addr, + pub vxastro: Addr, + pub emission_controller: Addr, + pub incentives: Addr, +} + +impl ControllerHelper { + pub fn new() -> Self { + let mut app = mock_app(); + let owner = app.api().addr_make("owner"); + let astro_denom = "astro"; + let xastro_denom = "xastro"; + + let vxastro_code_id = app.store_code(vxastro_contract()); + let emissions_controller_code_id = app.store_code(emissions_controller()); + let token_code_id = app.store_code(token_contract()); + let xyk_code_id = app.store_code(pair_contract()); + let factory_code_id = app.store_code(factory_contract()); + let incentives_code_id = app.store_code(incentives_contract()); + + let factory = app + .instantiate_contract( + factory_code_id, + owner.clone(), + &factory::InstantiateMsg { + pair_configs: vec![PairConfig { + code_id: xyk_code_id, + pair_type: PairType::Xyk {}, + total_fee_bps: 0, + maker_fee_bps: 0, + is_disabled: false, + is_generator_disabled: false, + permissioned: false, + }], + token_code_id, + fee_address: None, + generator_address: None, + owner: owner.to_string(), + whitelist_code_id: 0, + coin_registry_address: app.api().addr_make("coin_registry").to_string(), + }, + &[], + "label", + None, + ) + .unwrap(); + + let incentives = app + .instantiate_contract( + incentives_code_id, + owner.clone(), + &incentives::InstantiateMsg { + owner: owner.to_string(), + factory: factory.to_string(), + astro_token: AssetInfo::native(astro_denom), + vesting_contract: app.api().addr_make("vesting").to_string(), + incentivization_fee_info: Some(incentives::IncentivizationFeeInfo { + fee_receiver: app.api().addr_make("maker"), + fee: coin(250_000000, astro_denom), + }), + guardian: None, + }, + &[], + "label", + None, + ) + .unwrap(); + + app.execute_contract( + owner.clone(), + factory.clone(), + &factory::ExecuteMsg::UpdateConfig { + token_code_id: None, + fee_address: None, + generator_address: Some(incentives.to_string()), + whitelist_code_id: None, + coin_registry_address: None, + }, + &[], + ) + .unwrap(); + + let emission_controller = app + .instantiate_contract( + emissions_controller_code_id, + owner.clone(), + &OutpostInstantiateMsg { + owner: owner.to_string(), + astro_denom: astro_denom.to_string(), + vxastro_code_id, + vxastro_marketing_info: Some(UpdateMarketingInfo { + project: None, + description: None, + marketing: None, + logo: Some(Logo::Url("".to_string())), + }), + vxastro_deposit_denom: xastro_denom.to_string(), + factory: factory.to_string(), + hub_emissions_controller: "emissions_controller".to_string(), + ics20_channel: "channel-2".to_string(), + }, + &[], + "label", + None, + ) + .unwrap(); + + let vxastro = app + .wrap() + .query_wasm_smart::( + &emission_controller, + &emissions_controller::outpost::QueryMsg::Config {}, + ) + .unwrap() + .vxastro; + + Self { + app, + owner, + xastro: xastro_denom.to_string(), + astro: astro_denom.to_string(), + factory, + vxastro, + emission_controller, + incentives, + } + } + + pub fn mint_tokens(&mut self, user: &Addr, coins: &[Coin]) -> AnyResult { + self.app.sudo( + BankSudo::Mint { + to_address: user.to_string(), + amount: coins.to_vec(), + } + .into(), + ) + } + + pub fn lock(&mut self, user: &Addr, amount: u128) -> AnyResult { + let funds = coins(amount, &self.xastro); + self.mint_tokens(user, &funds).unwrap(); + self.app.execute_contract( + user.clone(), + self.vxastro.clone(), + &voting_escrow::ExecuteMsg::Lock { receiver: None }, + &funds, + ) + } + + pub fn unlock(&mut self, user: &Addr) -> AnyResult { + self.app.execute_contract( + user.clone(), + self.vxastro.clone(), + &voting_escrow::ExecuteMsg::Unlock {}, + &[], + ) + } + + pub fn user_vp(&self, user: &Addr, time: Option) -> StdResult { + self.app.wrap().query_wasm_smart( + &self.vxastro, + &voting_escrow::QueryMsg::UserVotingPower { + user: user.to_string(), + time, + }, + ) + } + + pub fn create_pair(&mut self, denom1: &str, denom2: &str) -> Addr { + let asset_infos = vec![AssetInfo::native(denom1), AssetInfo::native(denom2)]; + self.app + .execute_contract( + self.owner.clone(), + self.factory.clone(), + &factory::ExecuteMsg::CreatePair { + pair_type: PairType::Xyk {}, + asset_infos: asset_infos.clone(), + init_params: None, + }, + &[], + ) + .unwrap(); + + self.app + .wrap() + .query_wasm_smart::(&self.factory, &factory::QueryMsg::Pair { asset_infos }) + .unwrap() + .liquidity_token + } + + pub fn vote(&mut self, user: &Addr, votes: &[(String, Decimal)]) -> AnyResult { + self.app.execute_contract( + user.clone(), + self.emission_controller.clone(), + &ExecuteMsg::::Vote { + votes: votes.to_vec(), + }, + &[], + ) + } + + pub fn set_emissions( + &mut self, + sender: &Addr, + schedules: &[(&str, InputSchedule)], + funds: &[Coin], + ) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.emission_controller.clone(), + &emissions_controller::msg::ExecuteMsg::::Custom( + OutpostMsg::SetEmissions { + schedules: schedules + .iter() + .map(|(pool, schedule)| (pool.to_string(), schedule.clone())) + .collect(), + }, + ), + funds, + ) + } + + pub fn permissioned_set_emissions( + &mut self, + user: &Addr, + schedules: &[(&str, InputSchedule)], + funds: &[Coin], + ) -> AnyResult { + self.app.execute_contract( + user.clone(), + self.emission_controller.clone(), + &emissions_controller::msg::ExecuteMsg::::Custom( + OutpostMsg::PermissionedSetEmissions { + schedules: schedules + .iter() + .map(|(pool, schedule)| (pool.to_string(), schedule.clone())) + .collect(), + }, + ), + funds, + ) + } + + pub fn query_config(&self) -> StdResult { + self.app.wrap().query_wasm_smart( + &self.emission_controller, + &emissions_controller::outpost::QueryMsg::Config {}, + ) + } + + pub fn timetravel(&mut self, time: u64) { + self.app.update_block(|block| { + block.time = block.time.plus_seconds(time); + }) + } + + pub fn withdraw(&mut self, user: &Addr) -> AnyResult { + self.app.execute_contract( + user.clone(), + self.vxastro.clone(), + &voting_escrow::ExecuteMsg::Withdraw {}, + &[], + ) + } + + pub fn query_rewards(&self, pool: impl Into) -> StdResult> { + self.app + .wrap() + .query_wasm_smart::( + &self.incentives, + &incentives::QueryMsg::PoolInfo { + lp_token: pool.into(), + }, + ) + .map(|x| x.rewards) + } + + pub fn lock_info(&self, user: &Addr) -> StdResult { + self.app.wrap().query_wasm_smart( + &self.vxastro, + &voting_escrow::QueryMsg::LockInfo { + user: user.to_string(), + }, + ) + } + + pub fn refresh_user(&mut self, user: &Addr) -> AnyResult { + self.app.execute_contract( + user.clone(), + self.emission_controller.clone(), + &ExecuteMsg::::RefreshUserVotes {}, + &[], + ) + } + + pub fn query_ibc_status( + &self, + user: &Addr, + ) -> StdResult { + self.app.wrap().query_wasm_smart( + &self.emission_controller, + &emissions_controller::outpost::QueryMsg::QueryUserIbcStatus { + user: user.to_string(), + }, + ) + } + + pub fn set_voting_channel(&mut self) { + self.app + .execute_contract( + self.owner.clone(), + self.emission_controller.clone(), + &ExecuteMsg::Custom(OutpostMsg::UpdateConfig { + // channel-1 is hardcoded in the mocked ibc module + voting_ibc_channel: Some("channel-1".to_string()), + hub_emissions_controller: None, + ics20_channel: None, + }), + &[], + ) + .unwrap(); + } + + pub fn mock_ibc_ack( + &mut self, + ibc_msg: VxAstroIbcMsg, + error: Option<&str>, + ) -> AnyResult { + let ack_result = if let Some(err) = error { + IbcAckResult::Error(err.to_string()) + } else { + IbcAckResult::Ok(b"ok".into()) + }; + let packet = IbcPacketAckMsg::new( + IbcAcknowledgement::encode_json(&ack_result).unwrap(), + IbcPacket::new( + to_json_binary(&ibc_msg).unwrap(), + IbcEndpoint { + port_id: "".to_string(), + channel_id: "".to_string(), + }, + IbcEndpoint { + port_id: "".to_string(), + channel_id: "".to_string(), + }, + 0, + Timestamp::from_seconds(0).into(), + ), + Addr::unchecked("relayer"), + ); + self.app + .wasm_sudo(self.emission_controller.clone(), &TestSudoMsg::Ack(packet)) + } + + pub fn mock_ibc_timeout(&mut self, ibc_msg: VxAstroIbcMsg) -> AnyResult { + let packet = IbcPacketTimeoutMsg::new( + IbcPacket::new( + to_json_binary(&ibc_msg).unwrap(), + IbcEndpoint { + port_id: "".to_string(), + channel_id: "".to_string(), + }, + IbcEndpoint { + port_id: "".to_string(), + channel_id: "".to_string(), + }, + 0, + Timestamp::from_seconds(0).into(), + ), + Addr::unchecked("relayer"), + ); + self.app.wasm_sudo( + self.emission_controller.clone(), + &TestSudoMsg::Timeout(packet), + ) + } +} diff --git a/contracts/emissions_controller_outpost/tests/common/ibc_module.rs b/contracts/emissions_controller_outpost/tests/common/ibc_module.rs new file mode 100644 index 00000000..1f2e01cc --- /dev/null +++ b/contracts/emissions_controller_outpost/tests/common/ibc_module.rs @@ -0,0 +1,171 @@ +use cosmwasm_schema::serde::de::DeserializeOwned; +use cosmwasm_std::{ + attr, from_json, to_json_binary, Addr, Api, BankMsg, Binary, BlockInfo, ChannelResponse, + ContractResult, CustomMsg, CustomQuery, Empty, Event, IbcChannel, IbcEndpoint, IbcMsg, + IbcOrder, IbcQuery, Querier, QuerierResult, QuerierWrapper, QueryRequest, Storage, SystemError, + SystemResult, +}; +use cw_multi_test::error::{anyhow, AnyResult}; +use cw_multi_test::{AppResponse, CosmosRouter, Ibc, Module}; + +use astroport_governance::utils::check_contract_supports_channel; + +pub struct RouterQuerier<'a, ExecC, QueryC> { + router: &'a dyn CosmosRouter, + api: &'a dyn Api, + storage: &'a dyn Storage, + block_info: &'a BlockInfo, +} + +impl<'a, ExecC, QueryC> RouterQuerier<'a, ExecC, QueryC> { + pub fn new( + router: &'a dyn CosmosRouter, + api: &'a dyn Api, + storage: &'a dyn Storage, + block_info: &'a BlockInfo, + ) -> Self { + Self { + router, + api, + storage, + block_info, + } + } +} + +impl<'a, ExecC, QueryC> Querier for RouterQuerier<'a, ExecC, QueryC> +where + ExecC: CustomMsg + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, +{ + fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { + let request: QueryRequest = match from_json(bin_request) { + Ok(v) => v, + Err(e) => { + return SystemResult::Err(SystemError::InvalidRequest { + error: format!("Parsing query request: {}", e), + request: bin_request.into(), + }) + } + }; + let contract_result: ContractResult = self + .router + .query(self.api, self.storage, self.block_info, request) + .into(); + SystemResult::Ok(contract_result) + } +} + +pub struct IbcMockModule; + +impl Ibc for IbcMockModule {} + +impl Module for IbcMockModule { + type ExecT = IbcMsg; + type QueryT = IbcQuery; + type SudoT = Empty; + + fn execute( + &self, + api: &dyn Api, + storage: &mut dyn Storage, + router: &dyn CosmosRouter, + block: &BlockInfo, + sender: Addr, + msg: Self::ExecT, + ) -> AnyResult + where + ExecC: CustomMsg + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, + { + match msg { + IbcMsg::SendPacket { channel_id, .. } => { + let querier = RouterQuerier::new(router, api, storage, block); + let querier = QuerierWrapper::new(&querier); + check_contract_supports_channel(querier, &sender, &channel_id) + .map(|_| AppResponse::default()) + .map_err(Into::into) + } + IbcMsg::Transfer { + channel_id, + to_address, + amount, + timeout, + } => { + // Very simplified IBC transfer processing given cosmwasm-multitest constraints + let ibc_event = Event::new("transfer").add_attributes([ + attr( + "packet_timeout_timestamp", + timeout.timestamp().unwrap().seconds().to_string(), + ), + attr("packet_src_port", "transfer"), + attr("packet_src_channel", channel_id), + attr("to_address", to_address), + attr("amount", amount.to_string()), + ]); + let mut response = router.execute( + api, + storage, + block, + sender.clone(), + BankMsg::Burn { + amount: vec![amount], + } + .into(), + )?; + response.events.push(ibc_event); + Ok(response) + } + _ => unimplemented!("Execute {msg:?} not supported"), + } + } + + fn query( + &self, + _api: &dyn Api, + _storage: &dyn Storage, + _querier: &dyn Querier, + _block: &BlockInfo, + request: Self::QueryT, + ) -> AnyResult { + match &request { + IbcQuery::Channel { channel_id, .. } if channel_id == "channel-1" => { + to_json_binary(&ChannelResponse { + channel: Some(IbcChannel::new( + IbcEndpoint { + port_id: "".to_string(), + channel_id: "channel-1".to_string(), + }, + IbcEndpoint { + port_id: "".to_string(), + channel_id: "".to_string(), + }, + IbcOrder::Unordered, + "", + "", + )), + }) + .map_err(Into::into) + } + IbcQuery::Channel { .. } => { + to_json_binary(&ChannelResponse { channel: None }).map_err(Into::into) + } + _ => Err(anyhow!("Query {request:?} not supported")), + } + } + + fn sudo( + &self, + _api: &dyn Api, + _storage: &mut dyn Storage, + _router: &dyn CosmosRouter, + _block: &BlockInfo, + _msg: Self::SudoT, + ) -> AnyResult + where + ExecC: CustomMsg + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, + { + unimplemented!() + } +} diff --git a/contracts/emissions_controller_outpost/tests/common/mod.rs b/contracts/emissions_controller_outpost/tests/common/mod.rs new file mode 100644 index 00000000..6074b843 --- /dev/null +++ b/contracts/emissions_controller_outpost/tests/common/mod.rs @@ -0,0 +1,3 @@ +pub mod contracts; +pub mod helper; +pub mod ibc_module; diff --git a/contracts/emissions_controller_outpost/tests/emissions_controller_outpost_integration.rs b/contracts/emissions_controller_outpost/tests/emissions_controller_outpost_integration.rs new file mode 100644 index 00000000..add455f4 --- /dev/null +++ b/contracts/emissions_controller_outpost/tests/emissions_controller_outpost_integration.rs @@ -0,0 +1,715 @@ +use astroport::asset::{Asset, AssetInfo}; +use astroport::incentives::{InputSchedule, RewardType}; +use cosmwasm_std::{attr, coin, coins, Decimal, Decimal256, Empty, Event}; +use cw_multi_test::Executor; +use cw_utils::PaymentError; + +use astroport_emissions_controller_outpost::error::ContractError; +use astroport_governance::emissions_controller::consts::{EPOCH_LENGTH, IBC_TIMEOUT}; +use astroport_governance::emissions_controller::msg::{ExecuteMsg, VxAstroIbcMsg}; +use astroport_governance::emissions_controller::outpost::{OutpostMsg, UserIbcError}; +use astroport_governance::voting_escrow::LockInfoResponse; +use astroport_voting_escrow::state::UNLOCK_PERIOD; + +use crate::common::helper::{get_epoch_start, ControllerHelper}; + +mod common; + +#[test] +fn set_emissions_test() { + let mut helper = ControllerHelper::new(); + let astro = helper.astro.clone(); + + let pool1 = helper.create_pair("token1", "token2"); + let user = helper.app.api().addr_make("permissionless"); + + // Incentivizing with any token other than astro should fail + let funds = [coin(100_000000, "token1")]; + helper.mint_tokens(&user, &funds).unwrap(); + let schedules = [( + pool1.as_str(), + InputSchedule { + reward: Asset::native("token1", 100_000000u64), + duration_periods: 1, + }, + )]; + let err = helper.set_emissions(&user, &schedules, &funds).unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::PaymentError(PaymentError::ExtraDenom("token1".to_string())) + ); + + // Trying to bypass payment error by sending astro. + // Still should fail due to incentive fee absence + let funds = [coin(100_000000, &astro)]; + helper.mint_tokens(&user, &funds).unwrap(); + let schedules = [( + pool1.as_str(), + InputSchedule { + reward: Asset::native("token1", 100_000000u64), + duration_periods: 1, + }, + )]; + let err = helper.set_emissions(&user, &schedules, &funds).unwrap_err(); + assert_eq!( + err.downcast::() + .unwrap(), + astroport_incentives::error::ContractError::IncentivizationFeeExpected { + fee: coin(250_000000, &astro).to_string(), + lp_token: pool1.to_string(), + new_reward_token: "token1".to_string(), + } + ); + + let mut schedules = vec![ + ( + pool1.as_str(), + InputSchedule { + reward: Asset::native(&astro, 100_000000u64), + duration_periods: 1, + }, + ), + ( + "random", // <--- invalid pool + InputSchedule { + reward: Asset::native(&astro, 100_000000u64), + duration_periods: 1, + }, + ), + ]; + + // Try to incentivize with wrong funds + let funds = coins(100_000000, &astro); + helper.mint_tokens(&user, &funds).unwrap(); + let err = helper + .set_emissions(&user, &schedules, &coins(100_000000, &astro)) + .unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::InvalidAstroAmount { + expected: 200_000000u128.into(), + actual: 100_000000u128.into() + } + ); + + // Try schedule with <1 uASTRO reward per second + let invalid_schedules = [( + pool1.as_str(), + InputSchedule { + reward: Asset::native(&astro, 1000u64), + duration_periods: 1, + }, + )]; + let err = helper + .set_emissions(&user, &invalid_schedules, &coins(1000, &astro)) + .unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::NoValidSchedules {} + ); + + // However, if we mix this invalid schedule with valid ones, it will be filtered out + schedules.push(( + pool1.as_str(), + InputSchedule { + reward: Asset::native(&astro, 1000u64), + duration_periods: 1, + }, + )); + let funds = coins(200_001000, &astro); + helper.mint_tokens(&user, &funds).unwrap(); + + let resp = helper.set_emissions(&user, &schedules, &funds).unwrap(); + // Assert mocked ibc event + let has_event = resp.has_event( + &Event::new("transfer").add_attributes([ + attr( + "packet_timeout_timestamp", + helper + .app + .block_info() + .time + .plus_seconds(IBC_TIMEOUT) + .seconds() + .to_string(), + ), + attr("packet_src_port", "transfer"), + attr("packet_src_channel", "channel-2"), + attr("to_address", "emissions_controller"), + attr("amount", coin(100_001000, &astro).to_string()), + ]), + ); + assert!( + has_event, + "Expected IBC transfer event. Actual {:?}", + resp.events + ); + + // Check schedule in the incentives contract + let expected_rps = Decimal256::from_ratio(100_000000u64, EPOCH_LENGTH); + let rewards = helper.query_rewards(&pool1).unwrap(); + let epoch_start = get_epoch_start(helper.app.block_info().time.seconds()); + assert_eq!(rewards.len(), 1); + assert_eq!(rewards[0].rps, expected_rps); + assert_eq!( + rewards[0].reward, + RewardType::Ext { + info: AssetInfo::native(&astro), + next_update_ts: epoch_start + EPOCH_LENGTH + } + ); +} + +#[test] +fn permissioned_set_emissions_test() { + let mut helper = ControllerHelper::new(); + let astro = helper.astro.clone(); + let owner = helper.owner.clone(); + + let pool1 = helper.create_pair("token1", "token2"); + + // Unauthorized check + let random = helper.app.api().addr_make("random"); + let err = helper + .permissioned_set_emissions(&random, &[], &[]) + .unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::Unauthorized {} + ); + + // Incentivizing with any token other than astro should fail due to fee absence + let funds = [coin(100_000000, &astro)]; + helper.mint_tokens(&owner, &funds).unwrap(); + let schedules = [( + pool1.as_str(), + InputSchedule { + reward: Asset::native("token1", 100_000000u64), + duration_periods: 1, + }, + )]; + let err = helper + .permissioned_set_emissions(&owner, &schedules, &funds) + .unwrap_err(); + assert_eq!( + err.downcast::() + .unwrap(), + astroport_incentives::error::ContractError::IncentivizationFeeExpected { + fee: coin(250_000000, &astro).to_string(), + lp_token: pool1.to_string(), + new_reward_token: "token1".to_string(), + } + ); + + let schedules = [ + ( + pool1.as_str(), + InputSchedule { + reward: Asset::native(&astro, 100_000000u64), + duration_periods: 1, + }, + ), + ( + "random", // <--- invalid pool + InputSchedule { + reward: Asset::native(&astro, 100_000000u64), + duration_periods: 1, + }, + ), + ]; + + // Try to incentivize with zero funds in balance. + // Error happens on dispatch from emissions controller to incentives contract + let err = helper + .permissioned_set_emissions(&owner, &schedules, &[]) + .unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::InvalidAstroAmount { + expected: 200_000000u128.into(), + actual: 0u128.into() + } + ); + + // Mint funds to the emissions controller + let funds = coins(200_000000, &astro); + helper + .mint_tokens(&helper.emission_controller.clone(), &funds) + .unwrap(); + + let resp = helper + .permissioned_set_emissions(&owner, &schedules, &[]) + .unwrap(); + // Assert mocked ibc event + let has_event = resp.has_event( + &Event::new("transfer").add_attributes([ + attr( + "packet_timeout_timestamp", + helper + .app + .block_info() + .time + .plus_seconds(IBC_TIMEOUT) + .seconds() + .to_string(), + ), + attr("packet_src_port", "transfer"), + attr("packet_src_channel", "channel-2"), + attr("to_address", "emissions_controller"), + attr("amount", coin(100_000000, &astro).to_string()), + ]), + ); + assert!( + has_event, + "Expected IBC transfer event. Actual {:?}", + resp.events + ); + + // Check schedule in the incentives contract + let expected_rps = Decimal256::from_ratio(100_000000u64, EPOCH_LENGTH); + let rewards = helper.query_rewards(&pool1).unwrap(); + let epoch_start = get_epoch_start(helper.app.block_info().time.seconds()); + assert_eq!(rewards.len(), 1); + assert_eq!(rewards[0].rps, expected_rps); + assert_eq!( + rewards[0].reward, + RewardType::Ext { + info: AssetInfo::native(&astro), + next_update_ts: epoch_start + EPOCH_LENGTH + } + ); +} + +#[test] +fn test_voting() { + let mut helper = ControllerHelper::new(); + + let user = helper.app.api().addr_make("user"); + + let err = helper + .vote( + &user, + &[ + ("pool1".to_string(), Decimal::percent(1)), + ("pool1".to_string(), Decimal::percent(1)), + ], + ) + .unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::DuplicatedVotes {} + ); + + let err = helper + .vote( + &user, + &[ + ("pool1".to_string(), Decimal::percent(1)), + ("pool2".to_string(), Decimal::percent(1)), + ("pool3".to_string(), Decimal::percent(1)), + ("pool4".to_string(), Decimal::percent(1)), + ("pool5".to_string(), Decimal::percent(1)), + ("pool6".to_string(), Decimal::percent(1)), + ], + ) + .unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::ExceededMaxPoolsToVote {} + ); + + let err = helper + .vote(&user, &[("pool1".to_string(), Decimal::percent(1))]) + .unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::ZeroVotingPower {} + ); + + let err = helper + .vote( + &user, + &[ + ("pool1".to_string(), Decimal::one()), + ("pool2".to_string(), Decimal::one()), + ], + ) + .unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::InvalidTotalWeight {} + ); + + // Until voting channel set by the owner, any vxASTRO interactions should fail + let err = helper.lock(&user, 1000u64.into()).unwrap_err(); + assert_eq!( + err.root_cause().to_string(), + "Generic error: The contract does not have channel " + ); + + helper.set_voting_channel(); + helper.lock(&user, 1000u64.into()).unwrap(); + + // Can't lock more until the hub acknowledges a previous message + let err = helper.lock(&user, 1000u64.into()).unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::PendingUser(user.to_string()) + ); + + // Mock ibc ack + helper + .mock_ibc_ack( + VxAstroIbcMsg::UpdateUserVotes { + voter: user.to_string(), + voting_power: Default::default(), + is_unlock: false, + }, + None, + ) + .unwrap(); + + helper + .vote(&user, &[("pool1".to_string(), Decimal::one())]) + .unwrap(); + + // Cant do anything until the hub acknowledges the vote + let err = helper + .vote(&user, &[("pool1".to_string(), Decimal::one())]) + .unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::PendingUser(user.to_string()) + ); + let err = helper.unlock(&user).unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::PendingUser(user.to_string()) + ); + let err = helper.refresh_user(&user).unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::PendingUser(user.to_string()) + ); + + // Time out IBC packet + let mock_packet = VxAstroIbcMsg::Vote { + voter: user.to_string(), + voting_power: Default::default(), + votes: Default::default(), + }; + helper.mock_ibc_timeout(mock_packet.clone()).unwrap(); + + let ibc_status = helper.query_ibc_status(&user).unwrap(); + assert_eq!(ibc_status.pending_msg, None); + assert_eq!( + ibc_status.error, + Some(UserIbcError { + msg: mock_packet, + err: "IBC packet timeout".to_string() + }) + ); + + helper + .vote(&user, &[("pool1".to_string(), Decimal::one())]) + .unwrap(); + + // Refreshing user with 0 voting power should fail + let random = helper.app.api().addr_make("random"); + let err = helper.refresh_user(&random).unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::ZeroVotingPower {} + ); + + helper + .mock_ibc_ack( + VxAstroIbcMsg::Vote { + voter: user.to_string(), + voting_power: Default::default(), + votes: Default::default(), + }, + None, + ) + .unwrap(); + + // Check failed unlock + + helper.unlock(&user).unwrap(); + // Check user VP became 0 + let user_vp = helper.user_vp(&user, None).unwrap(); + assert_eq!(user_vp.u128(), 0); + + let mock_packet = VxAstroIbcMsg::UpdateUserVotes { + voter: user.to_string(), + voting_power: Default::default(), + is_unlock: true, + }; + helper + .mock_ibc_ack(mock_packet.clone(), Some("error")) + .unwrap(); + let ibc_status = helper.query_ibc_status(&user).unwrap(); + assert_eq!(ibc_status.pending_msg, None); + assert_eq!( + ibc_status.error, + Some(UserIbcError { + msg: mock_packet, + err: "error".to_string() + }) + ); + let lock_info = helper.lock_info(&user).unwrap(); + assert_eq!( + lock_info, + LockInfoResponse { + amount: 1000u128.into(), + unlock_status: None, + } + ); + // Ensure user VP was recovered + let user_vp = helper.user_vp(&user, None).unwrap(); + assert_eq!(user_vp.u128(), 1000); + + // Ensure nobody but vxASTRO can call UpdateUserVotes + let err = helper + .app + .execute_contract( + user.clone(), + helper.emission_controller.clone(), + &ExecuteMsg::::UpdateUserVotes { + user: user.to_string(), + is_unlock: true, + }, + &[], + ) + .unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::Unauthorized {} + ); +} + +#[test] +fn test_unlock_and_withdraw() { + let mut helper = ControllerHelper::new(); + let user = helper.app.api().addr_make("user"); + + helper.set_voting_channel(); + helper.lock(&user, 1000u64.into()).unwrap(); + + // Mock ibc ack + helper + .mock_ibc_ack( + VxAstroIbcMsg::UpdateUserVotes { + voter: user.to_string(), + voting_power: Default::default(), + is_unlock: false, + }, + None, + ) + .unwrap(); + + helper.unlock(&user).unwrap(); + helper.timetravel(UNLOCK_PERIOD); + + let err = helper.withdraw(&user).unwrap_err(); + assert_eq!( + err.downcast::() + .unwrap(), + astroport_voting_escrow::error::ContractError::HubNotConfirmed {} + ); + + // Mock hub confirmation + helper + .mock_ibc_ack( + VxAstroIbcMsg::UpdateUserVotes { + voter: user.to_string(), + voting_power: Default::default(), + is_unlock: true, + }, + None, + ) + .unwrap(); + + helper.withdraw(&user).unwrap(); + let user_bal = helper + .app + .wrap() + .query_balance(&user, &helper.xastro) + .unwrap() + .amount + .u128(); + assert_eq!(user_bal, 1000); +} + +#[test] +fn test_update_config() { + let mut helper = ControllerHelper::new(); + let owner = helper.owner.clone(); + + // Unauthorized check + let random = helper.app.api().addr_make("random"); + let err = helper + .app + .execute_contract( + random.clone(), + helper.emission_controller.clone(), + &ExecuteMsg::Custom(OutpostMsg::UpdateConfig { + voting_ibc_channel: None, + hub_emissions_controller: None, + ics20_channel: None, + }), + &[], + ) + .unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::Unauthorized {} + ); + + let err = helper + .app + .execute_contract( + owner.clone(), + helper.emission_controller.clone(), + &ExecuteMsg::Custom(OutpostMsg::UpdateConfig { + voting_ibc_channel: Some("channel-100".to_string()), + hub_emissions_controller: None, + ics20_channel: None, + }), + &[], + ) + .unwrap_err(); + assert_eq!( + err.root_cause().to_string(), + "Generic error: The contract does not have channel channel-100" + ); + + helper + .app + .execute_contract( + owner.clone(), + helper.emission_controller.clone(), + &ExecuteMsg::Custom(OutpostMsg::UpdateConfig { + voting_ibc_channel: Some("channel-1".to_string()), + hub_emissions_controller: Some("hub_emissions_controller".to_string()), + ics20_channel: Some("channel-10".to_string()), + }), + &[], + ) + .unwrap(); + let config = helper.query_config().unwrap(); + assert_eq!(config.voting_ibc_channel, "channel-1"); + assert_eq!(config.hub_emissions_controller, "hub_emissions_controller"); + assert_eq!(config.ics20_channel, "channel-10"); +} + +#[test] +fn test_change_ownership() { + let mut helper = ControllerHelper::new(); + + let new_owner = helper.app.api().addr_make("new_owner"); + + // New owner + let msg = ExecuteMsg::::ProposeNewOwner { + new_owner: new_owner.to_string(), + expires_in: 100, // seconds + }; + + // Unauthorized check + let err = helper + .app + .execute_contract( + helper.app.api().addr_make("not_owner"), + helper.emission_controller.clone(), + &msg, + &[], + ) + .unwrap_err(); + assert_eq!(err.root_cause().to_string(), "Generic error: Unauthorized"); + + // Claim before proposal + let err = helper + .app + .execute_contract( + new_owner.clone(), + helper.emission_controller.clone(), + &ExecuteMsg::::ClaimOwnership {}, + &[], + ) + .unwrap_err(); + assert_eq!( + err.root_cause().to_string(), + "Generic error: Ownership proposal not found" + ); + + // Propose a new owner + helper + .app + .execute_contract( + helper.owner.clone(), + helper.emission_controller.clone(), + &msg, + &[], + ) + .unwrap(); + + // Claim from invalid addr + let err = helper + .app + .execute_contract( + helper.app.api().addr_make("invalid_addr"), + helper.emission_controller.clone(), + &ExecuteMsg::::ClaimOwnership {}, + &[], + ) + .unwrap_err(); + assert_eq!(err.root_cause().to_string(), "Generic error: Unauthorized"); + + // Drop the ownership proposal + helper + .app + .execute_contract( + helper.owner.clone(), + helper.emission_controller.clone(), + &ExecuteMsg::::DropOwnershipProposal {}, + &[], + ) + .unwrap(); + + // Claim ownership + let err = helper + .app + .execute_contract( + new_owner.clone(), + helper.emission_controller.clone(), + &ExecuteMsg::::ClaimOwnership {}, + &[], + ) + .unwrap_err(); + assert_eq!( + err.root_cause().to_string(), + "Generic error: Ownership proposal not found" + ); + + // Propose a new owner again + helper + .app + .execute_contract( + helper.owner.clone(), + helper.emission_controller.clone(), + &msg, + &[], + ) + .unwrap(); + helper + .app + .execute_contract( + new_owner.clone(), + helper.emission_controller.clone(), + &ExecuteMsg::::ClaimOwnership {}, + &[], + ) + .unwrap(); + + assert_eq!(helper.query_config().unwrap().owner.to_string(), new_owner) +} diff --git a/contracts/generator_controller/Cargo.toml b/contracts/generator_controller/Cargo.toml deleted file mode 100644 index caac7412..00000000 --- a/contracts/generator_controller/Cargo.toml +++ /dev/null @@ -1,48 +0,0 @@ -[package] -name = "generator-controller" -version = "1.3.0" -authors = ["Astroport"] -edition = "2021" -repository = "https://github.com/astroport-fi/astroport-governance" -homepage = "https://astroport.fi" - -exclude = [ - # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. - "contract.wasm", - "hash.txt", -] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] - -[dependencies] -cw2 = "0.15" -cw20 = "0.15" -cosmwasm-std = "1.1" -cw-storage-plus = "0.15" -thiserror = { version = "1.0" } -itertools = "0.10" -astroport-governance = { path = "../../packages/astroport-governance" } -cosmwasm-schema = "1.1" - -[dev-dependencies] -cw-multi-test = "0.15" -astroport-tests = { path = "../../packages/astroport-tests" } - -astroport-generator = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -astroport-pair = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -astroport-factory = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -astroport-token = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -astroport-staking = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -astroport-whitelist = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -cw20 = "0.15" -voting-escrow = { path = "../voting_escrow" } -anyhow = "1" -proptest = "1.0" diff --git a/contracts/generator_controller/assets/generator_controller_timeline.png b/contracts/generator_controller/assets/generator_controller_timeline.png deleted file mode 100644 index 5355ddcdd4f21e2e72c12dcff1a33820000de887..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53717 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 diff --git a/contracts/generator_controller/src/bps.rs b/contracts/generator_controller/src/bps.rs deleted file mode 100644 index 63799710..00000000 --- a/contracts/generator_controller/src/bps.rs +++ /dev/null @@ -1,89 +0,0 @@ -use crate::error::ContractError; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Decimal, Fraction, StdError, Uint128}; -use std::convert::{TryFrom, TryInto}; -use std::ops::Mul; - -/// BasicPoints struct implementation. BasicPoints value is within [0, 10000] interval. -/// Technically BasicPoints is wrapper over [`u16`] with additional limit checks and -/// several implementations of math functions so BasicPoints object -/// can be used in formulas along with [`Uint128`] and [`Decimal`]. -#[cw_serde] -#[derive(Default, Copy)] -pub struct BasicPoints(u16); - -impl BasicPoints { - pub const MAX: u16 = 10000; - - pub fn checked_add(self, rhs: Self) -> Result { - let next_value = self.0 + rhs.0; - if next_value > Self::MAX { - Err(ContractError::BPSLimitError {}) - } else { - Ok(Self(next_value)) - } - } - - pub fn from_ratio(numerator: Uint128, denominator: Uint128) -> Result { - numerator - .checked_multiply_ratio(Self::MAX, denominator) - .map_err(|_| StdError::generic_err("Checked multiply ratio error!"))? - .u128() - .try_into() - } -} - -impl TryFrom for BasicPoints { - type Error = ContractError; - - fn try_from(value: u16) -> Result { - if value <= Self::MAX { - Ok(Self(value)) - } else { - Err(ContractError::BPSConverstionError(value as u128)) - } - } -} - -impl TryFrom for BasicPoints { - type Error = ContractError; - - fn try_from(value: u128) -> Result { - if value <= Self::MAX as u128 { - Ok(Self(value as u16)) - } else { - Err(ContractError::BPSConverstionError(value)) - } - } -} - -impl From for u16 { - fn from(value: BasicPoints) -> Self { - value.0 - } -} - -impl From for Uint128 { - fn from(value: BasicPoints) -> Self { - Uint128::from(u16::from(value)) - } -} - -impl Mul for BasicPoints { - type Output = Uint128; - - fn mul(self, rhs: Uint128) -> Self::Output { - rhs.multiply_ratio(self.0, Self::MAX) - } -} - -impl Mul for BasicPoints { - type Output = Decimal; - - fn mul(self, rhs: Decimal) -> Self::Output { - Decimal::from_ratio( - rhs.numerator() * Uint128::from(self.0), - rhs.denominator() * Uint128::from(Self::MAX), - ) - } -} diff --git a/contracts/generator_controller/src/contract.rs b/contracts/generator_controller/src/contract.rs deleted file mode 100644 index 7c4dfeea..00000000 --- a/contracts/generator_controller/src/contract.rs +++ /dev/null @@ -1,640 +0,0 @@ -use std::collections::HashSet; -use std::convert::TryInto; - -use crate::astroport; -use astroport::common::{claim_ownership, drop_ownership_proposal, propose_new_owner}; -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{ - to_json_binary, Addr, Binary, CosmosMsg, Decimal, Deps, DepsMut, Env, Fraction, MessageInfo, - Order, Response, StdError, StdResult, Uint128, WasmMsg, -}; -use cw2::set_contract_version; -use itertools::Itertools; - -use astroport_governance::generator_controller::{ - ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, UserInfoResponse, VOTERS_MAX_LIMIT, -}; -use astroport_governance::utils::{calc_voting_power, get_period, WEEK}; -use astroport_governance::voting_escrow::QueryMsg::CheckVotersAreBlacklisted; -use astroport_governance::voting_escrow::{ - get_lock_info, get_voting_power, BlacklistedVotersResponse, -}; - -use crate::bps::BasicPoints; -use crate::error::ContractError; -use crate::state::{ - Config, TuneInfo, UserInfo, VotedPoolInfo, CONFIG, OWNERSHIP_PROPOSAL, POOLS, TUNE_INFO, - USER_INFO, -}; - -use crate::utils::{ - cancel_user_changes, check_duplicated, filter_pools, get_pool_info, update_pool_info, - validate_pool, validate_pools_limit, vote_for_pool, -}; - -/// Contract name that is used for migration. -const CONTRACT_NAME: &str = "generator-controller"; -/// Contract version that is used for migration. -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -const DAY: u64 = 86400; -/// The user can only vote once every 10 days -const VOTE_COOLDOWN: u64 = DAY * 10; -/// It is possible to tune pools once every 14 days -const TUNE_COOLDOWN: u64 = WEEK * 2; - -type ExecuteResult = Result; - -/// Creates a new contract with the specified parameters in the [`InstantiateMsg`]. -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> ExecuteResult { - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - - CONFIG.save( - deps.storage, - &Config { - owner: deps.api.addr_validate(&msg.owner)?, - escrow_addr: deps.api.addr_validate(&msg.escrow_addr)?, - generator_addr: deps.api.addr_validate(&msg.generator_addr)?, - factory_addr: deps.api.addr_validate(&msg.factory_addr)?, - pools_limit: validate_pools_limit(msg.pools_limit)?, - blacklisted_voters_limit: None, - main_pool: None, - main_pool_min_alloc: Decimal::zero(), - whitelisted_pools: vec![], - }, - )?; - - // Set tune_ts just for safety so the first tuning could happen in 2 weeks - TUNE_INFO.save( - deps.storage, - &TuneInfo { - tune_ts: env.block.time.seconds(), - pool_alloc_points: vec![], - }, - )?; - - Ok(Response::default()) -} - -/// Exposes all the execute functions available in the contract. -/// -/// ## Execute messages -/// * **ExecuteMsg::KickBlacklistedVoters { blacklisted_voters }** Removes all votes applied by -/// blacklisted voters -/// -/// * **ExecuteMsg::Vote { votes }** Casts votes for pools -/// -/// * **ExecuteMsg::TunePools** Launches pool tuning -/// -/// * **ExecuteMsg::ChangePoolsLimit { limit }** Changes the number of pools which are eligible -/// to receive allocation points -/// -/// * **ExecuteMsg::UpdateConfig { blacklisted_voters_limit }** Changes the number of blacklisted -/// voters that can be kicked at once -/// -/// * **ExecuteMsg::UpdateWhitelist { add, remove }** Adds or removes lp tokens which are eligible -/// to receive votes. -/// -/// * **ExecuteMsg::ProposeNewOwner { owner, expires_in }** Creates a new request to change -/// contract ownership. -/// -/// * **ExecuteMsg::DropOwnershipProposal {}** Removes a request to change contract ownership. -/// -/// * **ExecuteMsg::ClaimOwnership {}** Claims contract ownership. -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> ExecuteResult { - match msg { - ExecuteMsg::KickBlacklistedVoters { blacklisted_voters } => { - kick_blacklisted_voters(deps, env, blacklisted_voters) - } - ExecuteMsg::Vote { votes } => handle_vote(deps, env, info, votes), - ExecuteMsg::TunePools {} => tune_pools(deps, env), - ExecuteMsg::ChangePoolsLimit { limit } => change_pools_limit(deps, info, limit), - ExecuteMsg::UpdateConfig { - blacklisted_voters_limit, - main_pool, - main_pool_min_alloc, - remove_main_pool, - } => update_config( - deps, - info, - blacklisted_voters_limit, - main_pool, - main_pool_min_alloc, - remove_main_pool, - ), - ExecuteMsg::UpdateWhitelist { add, remove } => update_whitelist(deps, info, add, remove), - ExecuteMsg::ProposeNewOwner { - new_owner, - expires_in, - } => { - let config: Config = CONFIG.load(deps.storage)?; - - propose_new_owner( - deps, - info, - env, - new_owner, - expires_in, - config.owner, - OWNERSHIP_PROPOSAL, - ) - .map_err(Into::into) - } - ExecuteMsg::DropOwnershipProposal {} => { - let config: Config = CONFIG.load(deps.storage)?; - - drop_ownership_proposal(deps, info, config.owner, OWNERSHIP_PROPOSAL) - .map_err(Into::into) - } - ExecuteMsg::ClaimOwnership {} => { - claim_ownership(deps, info, env, OWNERSHIP_PROPOSAL, |deps, new_owner| { - CONFIG - .update::<_, StdError>(deps.storage, |mut v| { - v.owner = new_owner; - Ok(v) - }) - .map(|_| ()) - }) - .map_err(Into::into) - } - } -} - -/// Adds or removes lp tokens which are eligible to receive votes. -/// Returns a [`ContractError`] on failure. -fn update_whitelist( - deps: DepsMut, - info: MessageInfo, - add: Option>, - remove: Option>, -) -> Result { - let mut cfg = CONFIG.load(deps.storage)?; - - // Permission check - if info.sender != cfg.owner { - return Err(ContractError::Unauthorized {}); - } - - // Remove old LP tokens - if let Some(remove_lp_tokens) = remove { - cfg.whitelisted_pools - .retain(|pool| !remove_lp_tokens.contains(&pool.to_string())); - } - - // Add new lp tokens - if let Some(add_lp_tokens) = add { - cfg.whitelisted_pools.append( - &mut add_lp_tokens - .into_iter() - .map(|lp_token| { - let lp_token_addr = deps.api.addr_validate(lp_token.as_str())?; - validate_pool(deps.as_ref(), &cfg, &lp_token_addr)?; - Ok(lp_token_addr) - }) - .collect::, ContractError>>()?, - ); - check_duplicated(&cfg.whitelisted_pools).map_err(|_| - ContractError::Std(StdError::generic_err("The resulting whitelist contains duplicated pools. It's either provided 'add' list contains duplicated pools or some of the added pools are already whitelisted.")))?; - } - - CONFIG.save(deps.storage, &cfg)?; - Ok(Response::default().add_attribute("action", "update_whitelist")) -} - -/// This function removes all votes applied by blacklisted voters. -/// -/// * **holders** list with blacklisted holders whose votes will be removed. -fn kick_blacklisted_voters(deps: DepsMut, env: Env, voters: Vec) -> ExecuteResult { - let block_period = get_period(env.block.time.seconds())?; - let config = CONFIG.load(deps.storage)?; - - if voters.len() > config.blacklisted_voters_limit.unwrap_or(VOTERS_MAX_LIMIT) as usize { - return Err(ContractError::KickVotersLimitExceeded {}); - } - - // Check duplicated voters - let addrs_set = voters.iter().collect::>(); - if voters.len() != addrs_set.len() { - return Err(ContractError::DuplicatedVoters {}); - } - - // Check if voters are blacklisted - let res: BlacklistedVotersResponse = deps.querier.query_wasm_smart( - config.escrow_addr, - &CheckVotersAreBlacklisted { - voters: voters.clone(), - }, - )?; - - if !res.eq(&BlacklistedVotersResponse::VotersBlacklisted {}) { - return Err(ContractError::Std(StdError::generic_err(res.to_string()))); - } - - for voter in voters { - let voter_addr = deps.api.addr_validate(&voter)?; - if let Some(user_info) = USER_INFO.may_load(deps.storage, &voter_addr)? { - if user_info.lock_end > block_period { - let user_last_vote_period = get_period(user_info.vote_ts)?; - // Calculate voting power before changes - let old_vp_at_period = calc_voting_power( - user_info.slope, - user_info.voting_power, - user_last_vote_period, - block_period, - ); - - // Cancel changes applied by previous votes - user_info.votes.iter().try_for_each(|(pool_addr, bps)| { - cancel_user_changes( - deps.storage, - block_period + 1, - pool_addr, - *bps, - old_vp_at_period, - user_info.slope, - user_info.lock_end, - ) - })?; - - let user_info = UserInfo { - vote_ts: env.block.time.seconds(), - lock_end: block_period, - ..Default::default() - }; - - USER_INFO.save(deps.storage, &voter_addr, &user_info)?; - } - } - } - - Ok(Response::new().add_attribute("action", "kick_holders")) -} - -/// The function checks that: -/// * the user voting power is > 0, -/// * user didn't vote for last 10 days, -/// * all pool addresses are valid LP token addresses, -/// * 'votes' vector doesn't contain duplicated pool addresses, -/// * sum of all BPS values <= 10000. -/// -/// The function cancels changes applied by previous votes and apply new votes for the next period. -/// New vote parameters are saved in [`USER_INFO`]. -/// -/// The function returns [`Response`] in case of success or [`ContractError`] in case of errors. -/// -/// * **votes** is a vector of pairs ([`String`], [`u16`]). -/// Tuple consists of pool address and percentage of user's voting power for a given pool. -/// Percentage should be in BPS form. -fn handle_vote( - deps: DepsMut, - env: Env, - info: MessageInfo, - votes: Vec<(String, u16)>, -) -> ExecuteResult { - let user = info.sender; - let block_period = get_period(env.block.time.seconds())?; - let config = CONFIG.load(deps.storage)?; - let user_vp = get_voting_power(&deps.querier, &config.escrow_addr, &user)?; - - if user_vp.is_zero() { - return Err(ContractError::ZeroVotingPower {}); - } - - if config.whitelisted_pools.is_empty() { - return Err(ContractError::WhitelistEmpty {}); - } - - let user_info = USER_INFO.may_load(deps.storage, &user)?.unwrap_or_default(); - // Does the user eligible to vote again? - if env.block.time.seconds() - user_info.vote_ts < VOTE_COOLDOWN { - return Err(ContractError::CooldownError(VOTE_COOLDOWN / DAY)); - } - - check_duplicated( - &votes - .iter() - .map(|vote| { - let (lp_token, _) = vote; - Addr::unchecked(lp_token) - }) - .collect::>(), - )?; - - // Validating addrs and bps - let votes = votes - .into_iter() - .map(|(addr, bps)| { - let pool = deps.api.addr_validate(&addr)?; - - // Voting for the main pool is prohibited - if let Some(main_pool) = &config.main_pool { - if pool == main_pool { - return Err(ContractError::MainPoolVoteOrWhitelistingProhibited( - main_pool.to_string(), - )); - } - } - if !config.whitelisted_pools.contains(&pool) { - return Err(ContractError::PoolIsNotWhitelisted(pool.to_string())); - } - - validate_pool(deps.as_ref(), &config, &pool)?; - - let bps: BasicPoints = bps.try_into()?; - Ok((pool, bps)) - }) - .collect::, ContractError>>()?; - - // Check the bps sum is within the limit - votes - .iter() - .try_fold(BasicPoints::default(), |acc, (_, bps)| { - acc.checked_add(*bps) - })?; - - if user_info.lock_end > block_period { - let user_last_vote_period = get_period(user_info.vote_ts).unwrap_or(block_period); - // Calculate voting power before changes - let old_vp_at_period = calc_voting_power( - user_info.slope, - user_info.voting_power, - user_last_vote_period, - block_period, - ); - - // Cancel changes applied by previous votes - user_info.votes.iter().try_for_each(|(pool_addr, bps)| { - cancel_user_changes( - deps.storage, - block_period + 1, - pool_addr, - *bps, - old_vp_at_period, - user_info.slope, - user_info.lock_end, - ) - })?; - } - - let ve_lock_info = get_lock_info(&deps.querier, &config.escrow_addr, &user)?; - - // Votes are applied to the next period - votes.iter().try_for_each(|(pool_addr, bps)| { - vote_for_pool( - deps.storage, - block_period + 1, - pool_addr, - *bps, - user_vp, - ve_lock_info.slope, - ve_lock_info.end, - ) - })?; - - let user_info = UserInfo { - vote_ts: env.block.time.seconds(), - voting_power: user_vp, - slope: ve_lock_info.slope, - lock_end: ve_lock_info.end, - votes, - }; - - USER_INFO.save(deps.storage, &user, &user_info)?; - - Ok(Response::new().add_attribute("action", "vote")) -} - -/// The function checks that the last pools tuning happened >= 14 days ago. -/// Then it calculates voting power for each pool at the current period, filters all pools which -/// are not eligible to receive allocation points, -/// takes top X pools by voting power, where X is 'config.pools_limit', calculates allocation points -/// for these pools and applies allocation points in generator contract. -fn tune_pools(deps: DepsMut, env: Env) -> ExecuteResult { - let mut tune_info = TUNE_INFO.load(deps.storage)?; - let config = CONFIG.load(deps.storage)?; - let block_period = get_period(env.block.time.seconds())?; - - if env.block.time.seconds() - tune_info.tune_ts < TUNE_COOLDOWN { - return Err(ContractError::CooldownError(TUNE_COOLDOWN / DAY)); - } - - let pool_votes: Vec<_> = POOLS - .keys(deps.as_ref().storage, None, None, Order::Ascending) - .collect::>() - .into_iter() - .map(|pool_addr| { - let pool_addr = pool_addr?; - - let pool_info = update_pool_info(deps.storage, block_period, &pool_addr, None)?; - // Remove pools with zero voting power so we won't iterate over them in future - if pool_info.vxastro_amount.is_zero() { - POOLS.remove(deps.storage, &pool_addr) - } - Ok((pool_addr, pool_info.vxastro_amount)) - }) - .collect::>>()? - .into_iter() - .filter(|(_, vxastro_amount)| !vxastro_amount.is_zero()) - .sorted_by(|(_, a), (_, b)| b.cmp(a)) // Sort in descending order - .collect(); - - tune_info.pool_alloc_points = filter_pools( - &deps.querier, - &config.generator_addr, - &config.factory_addr, - pool_votes, - config.pools_limit + 1, // +1 additional pool if we will need to remove the main pool - )?; - - // Set allocation points for the main pool - match config.main_pool { - Some(main_pool) if !config.main_pool_min_alloc.is_zero() => { - // Main pool may appear in the pool list thus we need to eliminate its contribution in the total VP. - tune_info - .pool_alloc_points - .retain(|(pool, _)| pool != &main_pool.to_string()); - // If there is no main pool in the filtered list then we need to remove additional pool - tune_info.pool_alloc_points = tune_info - .pool_alloc_points - .iter() - .take(config.pools_limit as usize) - .cloned() - .collect(); - - let total_vp: Uint128 = tune_info - .pool_alloc_points - .iter() - .fold(Uint128::zero(), |acc, (_, vp)| acc + vp); - // Calculate main pool contribution. - // Example (30% for the main pool): VP + x = y, x = 0.3y => y = VP/0.7 => x = 0.3 * VP / 0.7, - // where VP - total VP, x - main pool's contribution, y - new total VP. - // x = 0.3 * VP * (1-0.3)^(-1) - let main_pool_contribution = config.main_pool_min_alloc - * total_vp - * (Decimal::one() - config.main_pool_min_alloc).inv().unwrap(); - tune_info - .pool_alloc_points - .push((main_pool.to_string(), main_pool_contribution)) - } - _ => { - // there is no main pool or min alloc is 0% - tune_info.pool_alloc_points = tune_info - .pool_alloc_points - .iter() - .take(config.pools_limit as usize) - .cloned() - .collect(); - } - } - - if tune_info.pool_alloc_points.is_empty() { - return Err(ContractError::TuneNoPools {}); - } - - tune_info.tune_ts = env.block.time.seconds(); - TUNE_INFO.save(deps.storage, &tune_info)?; - - // Set new alloc points - let setup_pools_msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: config.generator_addr.to_string(), - msg: to_json_binary(&astroport::generator::ExecuteMsg::SetupPools { - pools: tune_info.pool_alloc_points, - })?, - funds: vec![], - }); - - Ok(Response::new() - .add_message(setup_pools_msg) - .add_attribute("action", "tune_pools")) -} - -/// Only contract owner can call this function. -/// The function sets a new limit of blacklisted voters that can be kicked at once. -/// -/// * **blacklisted_voters_limit** is a new limit of blacklisted voters which can be kicked at once -/// -/// * **main_pool** is a main pool address -/// -/// * **main_pool_min_alloc** is a minimum percentage of ASTRO emissions that this pool should get every block -/// -/// * **remove_main_pool** should the main pool be removed or not -fn update_config( - deps: DepsMut, - info: MessageInfo, - blacklisted_voters_limit: Option, - main_pool: Option, - main_pool_min_alloc: Option, - remove_main_pool: Option, -) -> ExecuteResult { - let mut config = CONFIG.load(deps.storage)?; - - if info.sender != config.owner { - return Err(ContractError::Unauthorized {}); - } - - if let Some(blacklisted_voters_limit) = blacklisted_voters_limit { - config.blacklisted_voters_limit = Some(blacklisted_voters_limit); - } - - if let Some(main_pool_min_alloc) = main_pool_min_alloc { - if main_pool_min_alloc == Decimal::zero() || main_pool_min_alloc >= Decimal::one() { - return Err(ContractError::MainPoolMinAllocFailed {}); - } - config.main_pool_min_alloc = main_pool_min_alloc; - } - - if let Some(main_pool) = main_pool { - if config.main_pool_min_alloc.is_zero() { - return Err(StdError::generic_err("Main pool min alloc can not be zero").into()); - } - config.main_pool = Some(deps.api.addr_validate(&main_pool)?); - } - - if let Some(remove_main_pool) = remove_main_pool { - if remove_main_pool { - config.main_pool = None; - } - } - - CONFIG.save(deps.storage, &config)?; - - Ok(Response::default().add_attribute("action", "update_config")) -} - -/// Only contract owner can call this function. -/// The function sets new limit of pools which are eligible to receive allocation points. -/// -/// * **limit** is a new limit of pools which are eligible to receive allocation points. -fn change_pools_limit(deps: DepsMut, info: MessageInfo, limit: u64) -> ExecuteResult { - let mut config = CONFIG.load(deps.storage)?; - - if info.sender != config.owner { - return Err(ContractError::Unauthorized {}); - } - - config.pools_limit = validate_pools_limit(limit)?; - CONFIG.save(deps.storage, &config)?; - - Ok(Response::default().add_attribute("action", "change_pools_limit")) -} - -/// Expose available contract queries. -/// -/// ## Queries -/// * **QueryMsg::UserInfo { user }** Fetch user information -/// -/// * **QueryMsg::TuneInfo** Fetch last tuning information -/// -/// * **QueryMsg::Config** Fetch contract config -/// -/// * **QueryMsg::PoolInfo { pool_addr }** Fetch pool's voting information at the current period. -/// -/// * **QueryMsg::PoolInfoAtPeriod { pool_addr, period }** Fetch pool's voting information at a specified period. -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::UserInfo { user } => to_json_binary(&user_info(deps, user)?), - QueryMsg::TuneInfo {} => to_json_binary(&TUNE_INFO.load(deps.storage)?), - QueryMsg::Config {} => to_json_binary(&CONFIG.load(deps.storage)?), - QueryMsg::PoolInfo { pool_addr } => to_json_binary(&pool_info(deps, env, pool_addr, None)?), - QueryMsg::PoolInfoAtPeriod { pool_addr, period } => { - to_json_binary(&pool_info(deps, env, pool_addr, Some(period))?) - } - } -} - -/// Returns user information. -fn user_info(deps: Deps, user: String) -> StdResult { - let user_addr = deps.api.addr_validate(&user)?; - USER_INFO - .may_load(deps.storage, &user_addr)? - .map(UserInfo::into_response) - .ok_or_else(|| StdError::generic_err("User not found")) -} - -/// Returns pool's voting information at a specified period. -fn pool_info( - deps: Deps, - env: Env, - pool_addr: String, - period: Option, -) -> StdResult { - let pool_addr = deps.api.addr_validate(&pool_addr)?; - let block_period = get_period(env.block.time.seconds())?; - let period = period.unwrap_or(block_period); - get_pool_info(deps.storage, period, &pool_addr) -} - -/// Manages contract migration -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { - Err(ContractError::MigrationError {}) -} diff --git a/contracts/generator_controller/src/error.rs b/contracts/generator_controller/src/error.rs deleted file mode 100644 index 93a5768b..00000000 --- a/contracts/generator_controller/src/error.rs +++ /dev/null @@ -1,63 +0,0 @@ -use cosmwasm_std::StdError; -use thiserror::Error; - -/// This enum describes contract errors -#[derive(Error, Debug)] -pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("Unauthorized")] - Unauthorized {}, - - #[error("Basic points conversion error. {0} > 10000")] - BPSConverstionError(u128), - - #[error("Basic points sum exceeds limit")] - BPSLimitError {}, - - #[error("You can't vote with zero voting power")] - ZeroVotingPower {}, - - #[error("{0} is the main pool. Voting or whitelisting the main pool is prohibited.")] - MainPoolVoteOrWhitelistingProhibited(String), - - #[error("main_pool_min_alloc should be more than 0 and less than 1")] - MainPoolMinAllocFailed {}, - - #[error("You can only run this action every {0} days")] - CooldownError(u64), - - #[error("Invalid lp token address: {0}")] - InvalidLPTokenAddress(String), - - #[error("Votes contain duplicated pool addresses")] - DuplicatedPools {}, - - #[error("There are no pools to tune")] - TuneNoPools {}, - - #[error("Invalid pool number: {0}. Must be within [2, 100] range")] - InvalidPoolNumber(u64), - - #[error("The vector contains duplicated addresses")] - DuplicatedVoters {}, - - #[error("Exceeded voters limit for kick blacklisted voters operation!")] - KickVotersLimitExceeded {}, - - #[error("Contract can't be migrated!")] - MigrationError {}, - - #[error("Whitelist cannot be empty!")] - WhitelistEmpty {}, - - #[error("The pair aren't registered: {0}-{1}")] - PairNotRegistered(String, String), - - #[error("Pool is already whitelisted: {0}")] - PoolIsWhitelisted(String), - - #[error("Pool is not whitelisted: {0}")] - PoolIsNotWhitelisted(String), -} diff --git a/contracts/generator_controller/src/lib.rs b/contracts/generator_controller/src/lib.rs deleted file mode 100644 index 6764e51f..00000000 --- a/contracts/generator_controller/src/lib.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub mod bps; -pub mod contract; -pub mod state; - -// During development this import could be replaced with another astroport version. -// However, in production, the astroport version should be the same for all contracts. -pub use astroport_governance::astroport; - -mod error; -mod utils; diff --git a/contracts/generator_controller/src/state.rs b/contracts/generator_controller/src/state.rs deleted file mode 100644 index 346310fa..00000000 --- a/contracts/generator_controller/src/state.rs +++ /dev/null @@ -1,71 +0,0 @@ -use crate::astroport::common::OwnershipProposal; -use crate::bps::BasicPoints; - -use astroport_governance::generator_controller::{ - ConfigResponse, GaugeInfoResponse, UserInfoResponse, VotedPoolInfoResponse, -}; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Uint128}; -use cw_storage_plus::{Item, Map}; - -/// This structure describes the main control config of generator controller contract. -pub type Config = ConfigResponse; -/// This structure describes voting parameters for a specific pool. -pub type VotedPoolInfo = VotedPoolInfoResponse; -/// This structure describes last tuning parameters. -pub type TuneInfo = GaugeInfoResponse; - -/// The struct describes last user's votes parameters. -#[cw_serde] -#[derive(Default)] -pub struct UserInfo { - pub vote_ts: u64, - pub voting_power: Uint128, - pub slope: Uint128, - pub lock_end: u64, - pub votes: Vec<(Addr, BasicPoints)>, -} - -impl UserInfo { - /// The function converts [`UserInfo`] object into [`UserInfoResponse`]. - pub(crate) fn into_response(self) -> UserInfoResponse { - let votes = self - .votes - .iter() - .map(|(pool_addr, bps)| (pool_addr.clone(), u16::from(*bps))) - .collect(); - - UserInfoResponse { - vote_ts: self.vote_ts, - voting_power: self.voting_power, - slope: self.slope, - lock_end: self.lock_end, - votes, - } - } -} - -/// Stores config at the given key. -pub const CONFIG: Item = Item::new("config"); - -/// Stores voting parameters per pool at a specific period by key ( period -> pool_addr ). -pub const POOL_VOTES: Map<(u64, &Addr), VotedPoolInfo> = Map::new("pool_votes"); - -/// HashSet based on [`Map`]. It contains all pool addresses whose voting power > 0. -pub const POOLS: Map<&Addr, ()> = Map::new("pools"); - -/// Hashset based on [`Map`]. It stores null object by key ( pool_addr -> period ). -/// This hashset contains all periods which have saved result in [`POOL_VOTES`] for a specific pool address. -pub const POOL_PERIODS: Map<(&Addr, u64), ()> = Map::new("pool_periods"); - -/// Slope changes for a specific pool address by key ( pool_addr -> period ). -pub const POOL_SLOPE_CHANGES: Map<(&Addr, u64), Uint128> = Map::new("pool_slope_changes"); - -/// User's voting information. -pub const USER_INFO: Map<&Addr, UserInfo> = Map::new("user_info"); - -/// Last tuning information. -pub const TUNE_INFO: Item = Item::new("tune_info"); - -/// Contains a proposal to change contract ownership -pub const OWNERSHIP_PROPOSAL: Item = Item::new("ownership_proposal"); diff --git a/contracts/generator_controller/src/utils.rs b/contracts/generator_controller/src/utils.rs deleted file mode 100644 index 884c9cd9..00000000 --- a/contracts/generator_controller/src/utils.rs +++ /dev/null @@ -1,378 +0,0 @@ -use std::collections::HashSet; -use std::ops::RangeInclusive; - -use crate::astroport; -use astroport::asset::{pair_info_by_pool, AssetInfo}; -use astroport::factory::PairType; -use astroport_governance::generator_controller::ConfigResponse; -use cosmwasm_std::{Addr, Deps, Order, QuerierWrapper, StdError, StdResult, Storage, Uint128}; -use cw_storage_plus::Bound; - -use crate::astroport::querier::query_pair_info; -use astroport_governance::utils::calc_voting_power; - -use crate::bps::BasicPoints; -use crate::error::ContractError; -use crate::state::{VotedPoolInfo, POOLS, POOL_PERIODS, POOL_SLOPE_CHANGES, POOL_VOTES}; - -/// Pools limit should be within the range `[2, 100]` -const POOL_NUMBER_LIMIT: RangeInclusive = 2..=100; - -/// The enum defines math operations with voting power and slope. -#[derive(Debug)] -pub(crate) enum Operation { - Add, - Sub, -} - -impl Operation { - pub fn calc_slope(&self, cur_slope: Uint128, slope: Uint128, bps: BasicPoints) -> Uint128 { - match self { - Operation::Add => cur_slope + bps * slope, - Operation::Sub => cur_slope - bps * slope, - } - } - - pub fn calc_voting_power(&self, cur_vp: Uint128, vp: Uint128, bps: BasicPoints) -> Uint128 { - match self { - Operation::Add => cur_vp + bps * vp, - Operation::Sub => cur_vp.saturating_sub(bps * vp), - } - } -} - -/// Enum wraps [`VotedPoolInfo`] so the contract can leverage storage operations efficiently. -#[derive(Debug)] -pub(crate) enum VotedPoolInfoResult { - Unchanged(VotedPoolInfo), - New(VotedPoolInfo), -} - -/// Filters pairs (LP token address, voting parameters) by criteria: -/// * pool's pair is registered in Factory, -/// * pool's pair type is not in blocked list, -/// * any of pair's token is not listed in blocked tokens list. -pub(crate) fn filter_pools( - querier: &QuerierWrapper, - generator_addr: &Addr, - factory_addr: &Addr, - pools: Vec<(Addr, Uint128)>, - pools_limit: u64, -) -> StdResult> { - let blocked_tokens: Vec = querier.query_wasm_smart( - generator_addr.clone(), - &astroport::generator::QueryMsg::BlockedTokensList {}, - )?; - let blocklisted_pair_types: Vec = querier.query_wasm_smart( - factory_addr.clone(), - &astroport::factory::QueryMsg::BlacklistedPairTypes {}, - )?; - - let pools = pools - .into_iter() - .filter_map(|(pool_addr, vxastro_amount)| { - // Check the address is a LP token and retrieve a pair info - let pair_info = pair_info_by_pool(querier, pool_addr).ok()?; - // Check a pair is registered in factory - query_pair_info(querier, factory_addr.clone(), &pair_info.asset_infos).ok()?; - let condition = !blocklisted_pair_types.contains(&pair_info.pair_type) - && !blocked_tokens.contains(&pair_info.asset_infos[0]) - && !blocked_tokens.contains(&pair_info.asset_infos[1]); - if condition { - Some((pair_info.liquidity_token.to_string(), vxastro_amount)) - } else { - None - } - }) - .take(pools_limit as usize) - .collect(); - - Ok(pools) -} - -/// Cancels user changes using old voting parameters for a given pool. -/// Firstly, it removes slope change scheduled for previous lockup end period. -/// Secondly, it updates voting parameters for the given period, but without user's vote. -pub(crate) fn cancel_user_changes( - storage: &mut dyn Storage, - period: u64, - pool_addr: &Addr, - old_bps: BasicPoints, - old_vp: Uint128, - old_slope: Uint128, - old_lock_end: u64, -) -> StdResult<()> { - // Cancel scheduled slope changes - let last_pool_period = fetch_last_pool_period(storage, period, pool_addr)?.unwrap_or(period); - if last_pool_period < old_lock_end + 1 { - let end_period_key = old_lock_end + 1; - let old_scheduled_change = POOL_SLOPE_CHANGES.load(storage, (pool_addr, end_period_key))?; - let new_slope = old_scheduled_change - old_bps * old_slope; - if !new_slope.is_zero() { - POOL_SLOPE_CHANGES.save(storage, (pool_addr, end_period_key), &new_slope)? - } else { - POOL_SLOPE_CHANGES.remove(storage, (pool_addr, end_period_key)) - } - } - - update_pool_info( - storage, - period, - pool_addr, - Some((old_bps, old_vp, old_slope, Operation::Sub)), - ) - .map(|_| ()) -} - -/// Applies user's vote for a given pool. -/// Firstly, it schedules slope change for lockup end period. -/// Secondly, it updates voting parameters with applied user's vote. -pub(crate) fn vote_for_pool( - storage: &mut dyn Storage, - period: u64, - pool_addr: &Addr, - bps: BasicPoints, - vp: Uint128, - slope: Uint128, - lock_end: u64, -) -> StdResult<()> { - // Schedule slope changes - POOL_SLOPE_CHANGES.update::<_, StdError>(storage, (pool_addr, lock_end + 1), |slope_opt| { - if let Some(saved_slope) = slope_opt { - Ok(saved_slope + bps * slope) - } else { - Ok(bps * slope) - } - })?; - update_pool_info( - storage, - period, - pool_addr, - Some((bps, vp, slope, Operation::Add)), - ) - .map(|_| ()) -} - -/// Fetches voting parameters for a given pool at specific period, applies new changes, saves it in storage -/// and returns new voting parameters in [`VotedPoolInfo`] object. -/// If there are no changes in 'changes' parameter -/// and voting parameters were already calculated before the function just returns [`VotedPoolInfo`]. -pub(crate) fn update_pool_info( - storage: &mut dyn Storage, - period: u64, - pool_addr: &Addr, - changes: Option<(BasicPoints, Uint128, Uint128, Operation)>, -) -> StdResult { - if POOLS.may_load(storage, pool_addr)?.is_none() { - POOLS.save(storage, pool_addr, &())? - } - let period_key = period; - let pool_info = match get_pool_info_mut(storage, period, pool_addr)? { - VotedPoolInfoResult::Unchanged(mut pool_info) | VotedPoolInfoResult::New(mut pool_info) - if changes.is_some() => - { - if let Some((bps, vp, slope, op)) = changes { - pool_info.slope = op.calc_slope(pool_info.slope, slope, bps); - pool_info.vxastro_amount = op.calc_voting_power(pool_info.vxastro_amount, vp, bps); - } - POOL_PERIODS.save(storage, (pool_addr, period_key), &())?; - POOL_VOTES.save(storage, (period_key, pool_addr), &pool_info)?; - pool_info - } - VotedPoolInfoResult::New(pool_info) => { - POOL_PERIODS.save(storage, (pool_addr, period_key), &())?; - POOL_VOTES.save(storage, (period_key, pool_addr), &pool_info)?; - pool_info - } - VotedPoolInfoResult::Unchanged(pool_info) => pool_info, - }; - - Ok(pool_info) -} - -/// Returns pool info at specified period or calculates it. Saves intermediate results in storage. -pub(crate) fn get_pool_info_mut( - storage: &mut dyn Storage, - period: u64, - pool_addr: &Addr, -) -> StdResult { - let pool_info_result = if let Some(pool_info) = - POOL_VOTES.may_load(storage, (period, pool_addr))? - { - VotedPoolInfoResult::Unchanged(pool_info) - } else { - let pool_info_result = - if let Some(mut prev_period) = fetch_last_pool_period(storage, period, pool_addr)? { - let mut pool_info = POOL_VOTES.load(storage, (prev_period, pool_addr))?; - // Recalculating passed periods - let scheduled_slope_changes = - fetch_slope_changes(storage, pool_addr, prev_period, period)?; - for (recalc_period, scheduled_change) in scheduled_slope_changes { - pool_info = VotedPoolInfo { - vxastro_amount: calc_voting_power( - pool_info.slope, - pool_info.vxastro_amount, - prev_period, - recalc_period, - ), - slope: pool_info.slope - scheduled_change, - }; - // Save intermediate result - let recalc_period_key = recalc_period; - POOL_PERIODS.save(storage, (pool_addr, recalc_period_key), &())?; - POOL_VOTES.save(storage, (recalc_period_key, pool_addr), &pool_info)?; - prev_period = recalc_period - } - - VotedPoolInfo { - vxastro_amount: calc_voting_power( - pool_info.slope, - pool_info.vxastro_amount, - prev_period, - period, - ), - ..pool_info - } - } else { - VotedPoolInfo::default() - }; - - VotedPoolInfoResult::New(pool_info_result) - }; - - Ok(pool_info_result) -} - -/// Returns pool info at specified period or calculates it. -pub(crate) fn get_pool_info( - storage: &dyn Storage, - period: u64, - pool_addr: &Addr, -) -> StdResult { - let pool_info = if let Some(pool_info) = POOL_VOTES.may_load(storage, (period, pool_addr))? { - pool_info - } else if let Some(mut prev_period) = fetch_last_pool_period(storage, period, pool_addr)? { - let mut pool_info = POOL_VOTES.load(storage, (prev_period, pool_addr))?; - // Recalculating passed periods - let scheduled_slope_changes = fetch_slope_changes(storage, pool_addr, prev_period, period)?; - for (recalc_period, scheduled_change) in scheduled_slope_changes { - pool_info = VotedPoolInfo { - vxastro_amount: calc_voting_power( - pool_info.slope, - pool_info.vxastro_amount, - prev_period, - recalc_period, - ), - slope: pool_info.slope - scheduled_change, - }; - prev_period = recalc_period - } - - VotedPoolInfo { - vxastro_amount: calc_voting_power( - pool_info.slope, - pool_info.vxastro_amount, - prev_period, - period, - ), - ..pool_info - } - } else { - VotedPoolInfo::default() - }; - - Ok(pool_info) -} - -/// Fetches last period for specified pool which has saved result in [`POOL_PERIODS`]. -pub(crate) fn fetch_last_pool_period( - storage: &dyn Storage, - period: u64, - pool_addr: &Addr, -) -> StdResult> { - let period_opt = POOL_PERIODS - .prefix(pool_addr) - .range( - storage, - None, - Some(Bound::exclusive(period)), - Order::Descending, - ) - .next() - .transpose()? - .map(|(period, _)| period); - Ok(period_opt) -} - -/// Fetches all slope changes between `last_period` and `period` for specific pool. -pub(crate) fn fetch_slope_changes( - storage: &dyn Storage, - pool_addr: &Addr, - last_period: u64, - period: u64, -) -> StdResult> { - POOL_SLOPE_CHANGES - .prefix(pool_addr) - .range( - storage, - Some(Bound::exclusive(last_period)), - Some(Bound::inclusive(period)), - Order::Ascending, - ) - .collect() -} - -/// Input validation for pools limit. -pub(crate) fn validate_pools_limit(number: u64) -> Result { - if !POOL_NUMBER_LIMIT.contains(&number) { - Err(ContractError::InvalidPoolNumber(number)) - } else { - Ok(number) - } -} - -/// Check if a pool isn't the main pool. Check if a pool is an LP token. -/// Check if a pool is registered in the factory contract. -pub fn validate_pool( - deps: Deps, - config: &ConfigResponse, - pool: &Addr, -) -> Result<(), ContractError> { - // Voting for the main pool or updating it is prohibited - if let Some(main_pool) = &config.main_pool { - if pool == main_pool { - return Err(ContractError::MainPoolVoteOrWhitelistingProhibited( - main_pool.to_string(), - )); - } - } - - // Checks if a pool is an LP token - let pair_info = pair_info_by_pool(&deps.querier, pool.clone()) - .map_err(|_| ContractError::InvalidLPTokenAddress(pool.to_string()))?; - - // Check if a pair is registered in the factory - query_pair_info( - &deps.querier, - config.factory_addr.clone(), - &pair_info.asset_infos, - ) - .map_err(|_| { - ContractError::PairNotRegistered( - pair_info.asset_infos[0].to_string(), - pair_info.asset_infos[1].to_string(), - ) - })?; - - Ok(()) -} - -/// Checks for duplicate pools -pub fn check_duplicated(votes: &[Addr]) -> Result<(), ContractError> { - let mut uniq = HashSet::new(); - if !votes.iter().all(|lp_token| uniq.insert(lp_token)) { - return Err(ContractError::DuplicatedPools {}); - } - - Ok(()) -} diff --git a/contracts/generator_controller/tests/integration.rs b/contracts/generator_controller/tests/integration.rs deleted file mode 100644 index 7c512abc..00000000 --- a/contracts/generator_controller/tests/integration.rs +++ /dev/null @@ -1,997 +0,0 @@ -use astroport::asset::AssetInfo; -use astroport::generator::PoolInfoResponse; -use cosmwasm_std::{attr, Addr, Decimal, StdResult, Uint128}; -use cw_multi_test::{App, ContractWrapper, Executor}; -use generator_controller::astroport; -use std::str::FromStr; - -use crate::astroport::asset::PairInfo; -use astroport_governance::generator_controller::{ - ConfigResponse, ExecuteMsg, QueryMsg, VOTERS_MAX_LIMIT, -}; -use astroport_governance::utils::{get_period, MAX_LOCK_TIME, WEEK}; -use astroport_tests::{ - controller_helper::ControllerHelper, escrow_helper::MULTIPLIER, mock_app, TerraAppExtension, -}; -use generator_controller::state::TuneInfo; - -#[test] -fn update_configs() { - let mut router = mock_app(); - let owner = Addr::unchecked("owner"); - let helper = ControllerHelper::init(&mut router, &owner); - - let config = helper.query_config(&mut router).unwrap(); - assert_eq!(config.blacklisted_voters_limit, None); - - // check if user2 cannot update config - let err = helper - .update_blacklisted_limit(&mut router, "user2", Some(4u32)) - .unwrap_err(); - assert_eq!("Unauthorized", err.root_cause().to_string()); - - // successful update config by owner - helper - .update_blacklisted_limit(&mut router, "owner", Some(4u32)) - .unwrap(); - - let config = helper.query_config(&mut router).unwrap(); - assert_eq!(config.blacklisted_voters_limit, Some(4u32)); -} - -#[test] -fn check_kick_holders_works() { - let mut router = mock_app(); - let owner = Addr::unchecked("owner"); - let helper = ControllerHelper::init(&mut router, &owner); - let pools = vec![ - helper - .create_pool_with_tokens(&mut router, "FOO", "BAR") - .unwrap(), - helper - .create_pool_with_tokens(&mut router, "BAR", "ADN") - .unwrap(), - ]; - - let err = helper - .vote(&mut router, "user1", vec![(pools[0].as_str(), 1000)]) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "You can't vote with zero voting power" - ); - - helper.escrow_helper.mint_xastro(&mut router, "owner", 100); - helper.escrow_helper.mint_xastro(&mut router, "user1", 100); - // Create short lock - helper - .escrow_helper - .create_lock(&mut router, "user1", WEEK, 100f32) - .unwrap(); - - helper - .update_whitelist( - &mut router, - "owner", - Some(pools.iter().map(|el| el.to_string()).collect()), - None, - ) - .unwrap(); - - // Votes from user1 - helper - .vote(&mut router, "user1", vec![(pools[0].as_str(), 1000)]) - .unwrap(); - - helper.escrow_helper.mint_xastro(&mut router, "user2", 100); - helper - .escrow_helper - .create_lock(&mut router, "user2", 10 * WEEK, 100f32) - .unwrap(); - - // Votes from user2 - helper - .vote( - &mut router, - "user2", - vec![(pools[0].as_str(), 3000), (pools[1].as_str(), 7000)], - ) - .unwrap(); - - let ve_slope = helper - .escrow_helper - .query_lock_info(&mut router, "user2") - .unwrap() - .slope; - let ve_power = helper - .escrow_helper - .query_user_vp(&mut router, "user2") - .unwrap(); - let user_info = helper.query_user_info(&mut router, "user2").unwrap(); - assert_eq!(ve_slope, user_info.slope); - assert_eq!(router.block_info().time.seconds(), user_info.vote_ts); - assert_eq!( - ve_power, - user_info.voting_power.u128() as f32 / MULTIPLIER as f32 - ); - let resp_votes = user_info - .votes - .clone() - .into_iter() - .map(|(addr, bps)| (addr.to_string(), bps.into())) - .collect::>(); - assert_eq!( - vec![(pools[0].to_string(), 3000), (pools[1].to_string(), 7000)], - resp_votes - ); - - // Add user2 to the blacklist - let res = helper - .escrow_helper - .update_blacklist(&mut router, Some(vec!["user2".to_string()]), None) - .unwrap(); - assert_eq!( - res.events[1].attributes[1], - attr("action", "update_blacklist") - ); - - // Let's take the period for which the vote was applied. - let current_period = router.block_period() + 1u64; - - // Get pools info before kick holder - let res = helper - .query_voted_pool_info_at_period(&mut router, pools[0].as_str(), current_period) - .unwrap(); - assert_eq!(Uint128::new(13_576_922), res.slope); - assert_eq!(Uint128::new(44_471_151), res.vxastro_amount); - - let res = helper - .query_voted_pool_info_at_period(&mut router, pools[1].as_str(), current_period) - .unwrap(); - assert_eq!(Uint128::new(8_009_614), res.slope); - assert_eq!(Uint128::new(80_096_149), res.vxastro_amount); - - // check if blacklisted voters limit exceeded for kick operation - let err = helper - .kick_holders( - &mut router, - "user1", - vec!["user2".to_string(); (VOTERS_MAX_LIMIT + 1) as usize], - ) - .unwrap_err(); - assert_eq!( - "Exceeded voters limit for kick blacklisted voters operation!", - err.root_cause().to_string() - ); - - // Removes votes for user2 - helper - .kick_holders(&mut router, "user1", vec!["user2".to_string()]) - .unwrap(); - - let ve_slope = helper - .escrow_helper - .query_lock_info(&mut router, "user2") - .unwrap() - .slope; - let ve_power = helper - .escrow_helper - .query_user_vp(&mut router, "user2") - .unwrap(); - - let user_info = helper.query_user_info(&mut router, "user2").unwrap(); - assert_eq!(ve_slope, user_info.slope); - assert_eq!(router.block_info().time.seconds(), user_info.vote_ts); - assert_eq!( - ve_power, - user_info.voting_power.u128() as f32 / MULTIPLIER as f32 - ); - assert_eq!(user_info.votes, vec![]); - - // Get pool info after kick holder - let res = helper - .query_voted_pool_info_at_period(&mut router, pools[0].as_str(), current_period) - .unwrap(); - assert_eq!(Uint128::new(10_144_230), res.slope); - assert_eq!(Uint128::new(10_144_230), res.vxastro_amount); - - let res1 = helper - .query_voted_pool_info_at_period(&mut router, pools[1].as_str(), current_period) - .unwrap(); - assert_eq!(Uint128::new(0), res1.slope); - assert_eq!(Uint128::new(0), res1.vxastro_amount); -} - -#[test] -fn check_vote_works() { - let mut router = mock_app(); - let owner = Addr::unchecked("owner"); - let helper = ControllerHelper::init(&mut router, &owner); - let pools = vec![ - helper - .create_pool_with_tokens(&mut router, "FOO", "BAR") - .unwrap(), - helper - .create_pool_with_tokens(&mut router, "BAR", "ADN") - .unwrap(), - ]; - - let err = helper - .vote(&mut router, "user1", vec![(pools[0].as_str(), 1000)]) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "You can't vote with zero voting power" - ); - - helper.escrow_helper.mint_xastro(&mut router, "owner", 100); - helper.escrow_helper.mint_xastro(&mut router, "user1", 100); - // Create short lock - helper - .escrow_helper - .create_lock(&mut router, "user1", WEEK, 100f32) - .unwrap(); - let err = helper - .vote(&mut router, "user1", vec![(pools[0].as_str(), 1000)]) - .unwrap_err(); - assert_eq!("Whitelist cannot be empty!", err.root_cause().to_string()); - - let err = helper - .update_whitelist(&mut router, "user1", Some(vec![pools[0].to_string()]), None) - .unwrap_err(); - assert_eq!("Unauthorized", err.root_cause().to_string()); - - helper - .update_whitelist( - &mut router, - "owner", - Some(pools.iter().map(|el| el.to_string()).collect()), - None, - ) - .unwrap(); - - helper - .vote(&mut router, "user1", vec![(pools[0].as_str(), 1000)]) - .unwrap(); - - helper.escrow_helper.mint_xastro(&mut router, "user2", 100); - helper - .escrow_helper - .create_lock(&mut router, "user2", 10 * WEEK, 100f32) - .unwrap(); - - // Bps is > 10000 - let err = helper - .vote(&mut router, "user2", vec![(pools[1].as_str(), 10001)]) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Basic points conversion error. 10001 > 10000" - ); - - // Bps sum is > 10000 - let err = helper - .vote( - &mut router, - "user2", - vec![(pools[0].as_str(), 3000), (pools[1].as_str(), 8000)], - ) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Basic points sum exceeds limit" - ); - - // Duplicated pools - let err = helper - .vote( - &mut router, - "user2", - vec![(pools[0].as_str(), 3000), (pools[0].as_str(), 7000)], - ) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Votes contain duplicated pool addresses" - ); - - // Valid votes - helper - .vote( - &mut router, - "user2", - vec![(pools[0].as_str(), 3000), (pools[1].as_str(), 7000)], - ) - .unwrap(); - - let err = helper - .vote( - &mut router, - "user2", - vec![(pools[0].as_str(), 7000), (pools[1].as_str(), 3000)], - ) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "You can only run this action every 10 days" - ); - - let ve_slope = helper - .escrow_helper - .query_lock_info(&mut router, "user2") - .unwrap() - .slope; - let ve_power = helper - .escrow_helper - .query_user_vp(&mut router, "user2") - .unwrap(); - let user_info = helper.query_user_info(&mut router, "user2").unwrap(); - assert_eq!(ve_slope, user_info.slope); - assert_eq!(router.block_info().time.seconds(), user_info.vote_ts); - assert_eq!( - ve_power, - user_info.voting_power.u128() as f32 / MULTIPLIER as f32 - ); - let resp_votes = user_info - .votes - .into_iter() - .map(|(addr, bps)| (addr.to_string(), bps.into())) - .collect::>(); - assert_eq!( - vec![(pools[0].to_string(), 3000), (pools[1].to_string(), 7000)], - resp_votes - ); - - router.next_block(86400 * 10); - // In 10 days user will be able to vote again - helper - .vote( - &mut router, - "user2", - vec![(pools[0].as_str(), 500), (pools[1].as_str(), 9500)], - ) - .unwrap(); -} - -fn create_unregistered_pool( - router: &mut App, - helper: &mut ControllerHelper, -) -> StdResult { - let pair_contract = Box::new( - ContractWrapper::new_with_empty( - astroport_pair::contract::execute, - astroport_pair::contract::instantiate, - astroport_pair::contract::query, - ) - .with_reply_empty(astroport_pair::contract::reply), - ); - - let pair_code_id = router.store_code(pair_contract); - - let test_token1 = helper.init_cw20_token(router, "TST").unwrap(); - let test_token2 = helper.init_cw20_token(router, "TSB").unwrap(); - - let pair_addr = router - .instantiate_contract( - pair_code_id, - Addr::unchecked("owner"), - &astroport::pair::InstantiateMsg { - asset_infos: vec![ - AssetInfo::Token { - contract_addr: test_token1.clone(), - }, - AssetInfo::Token { - contract_addr: test_token2.clone(), - }, - ], - token_code_id: 1, - factory_addr: helper.factory.to_string(), - init_params: None, - }, - &[], - "Unregistered pair".to_string(), - None, - ) - .unwrap(); - - let res: PairInfo = router - .wrap() - .query_wasm_smart(pair_addr, &astroport::pair::QueryMsg::Pair {})?; - - Ok(res) -} - -#[test] -fn check_tuning() { - let mut router = mock_app(); - let owner = "owner"; - let owner_addr = Addr::unchecked(owner); - let mut helper = ControllerHelper::init(&mut router, &owner_addr); - let user1 = "user1"; - let user2 = "user2"; - let user3 = "user3"; - let ve_locks = vec![(user1, 10), (user2, 5), (user3, 50)]; - - let pools = vec![ - helper - .create_pool_with_tokens(&mut router, "FOO", "BAR") - .unwrap(), - helper - .create_pool_with_tokens(&mut router, "BAR", "ADN") - .unwrap(), - helper - .create_pool_with_tokens(&mut router, "FOO", "ADN") - .unwrap(), - ]; - - helper - .update_whitelist( - &mut router, - "owner", - Some(pools.iter().map(|el| el.to_string()).collect()), - None, - ) - .unwrap(); - - let err = helper - .update_whitelist(&mut router, "owner", Some(vec![pools[0].to_string()]), None) - .unwrap_err(); - assert_eq!("Generic error: The resulting whitelist contains duplicated pools. It's either provided 'add' list contains duplicated pools or some of the added pools are already whitelisted.", err.root_cause().to_string()); - - let config_resp = helper.query_config(&mut router).unwrap(); - assert_eq!(config_resp.whitelisted_pools, pools); - - for (user, duration) in ve_locks { - helper.escrow_helper.mint_xastro(&mut router, user, 1000); - helper - .escrow_helper - .create_lock(&mut router, user, duration * WEEK, 100f32) - .unwrap(); - } - - let res = create_unregistered_pool(&mut router, &mut helper).unwrap(); - let err = helper - .vote( - &mut router, - user1, - vec![ - (pools[0].as_str(), 5000), - (pools[1].as_str(), 4000), - (res.liquidity_token.as_str(), 1000), - ], - ) - .unwrap_err(); - assert_eq!( - "Pool is not whitelisted: contract23", - err.root_cause().to_string() - ); - - let err = helper - .vote( - &mut router, - user1, - vec![ - (pools[0].as_str(), 5000), - (pools[1].as_str(), 2000), - (pools[1].as_str(), 2000), - ], - ) - .unwrap_err(); - assert_eq!( - "Votes contain duplicated pool addresses", - err.root_cause().to_string() - ); - - helper - .vote( - &mut router, - user1, - vec![(pools[0].as_str(), 5000), (pools[1].as_str(), 5000)], - ) - .unwrap(); - - helper - .vote( - &mut router, - user2, - vec![ - (pools[0].as_str(), 5000), - (pools[1].as_str(), 2000), - (pools[2].as_str(), 3000), - ], - ) - .unwrap(); - helper - .vote( - &mut router, - user3, - vec![ - (pools[0].as_str(), 2000), - (pools[1].as_str(), 3000), - (pools[2].as_str(), 5000), - ], - ) - .unwrap(); - - // The contract was just created so we need to wait for 2 weeks - let err = helper.tune(&mut router).unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "You can only run this action every 14 days" - ); - - router.next_block(WEEK); - let err = helper.tune(&mut router).unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "You can only run this action every 14 days" - ); - - router.next_block(WEEK); - - helper.tune(&mut router).unwrap(); - - let resp: TuneInfo = router - .wrap() - .query_wasm_smart(helper.controller.clone(), &QueryMsg::TuneInfo {}) - .unwrap(); - assert_eq!(get_period(resp.tune_ts).unwrap(), router.block_period()); - assert_eq!(resp.pool_alloc_points.len(), pools.len()); - let total_apoints: u128 = resp - .pool_alloc_points - .iter() - .cloned() - .map(|(_, apoints)| apoints.u128()) - .sum(); - assert_eq!(total_apoints, 357423036); - - router.next_block(2 * WEEK); - // Reduce pools limit 5 -> 2 (5 is initial limit in integration tests) - let limit = 2u64; - let err = router - .execute_contract( - Addr::unchecked("somebody"), - helper.controller.clone(), - &ExecuteMsg::ChangePoolsLimit { limit }, - &[], - ) - .unwrap_err(); - assert_eq!(err.root_cause().to_string(), "Unauthorized"); - - router - .execute_contract( - owner_addr.clone(), - helper.controller.clone(), - &ExecuteMsg::ChangePoolsLimit { limit }, - &[], - ) - .unwrap(); - - let err = router - .execute_contract( - owner_addr.clone(), - helper.controller.clone(), - &ExecuteMsg::ChangePoolsLimit { limit: 101 }, - &[], - ) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Invalid pool number: 101. Must be within [2, 100] range" - ); - - helper.tune(&mut router).unwrap(); - - let resp: TuneInfo = router - .wrap() - .query_wasm_smart(helper.controller.clone(), &QueryMsg::TuneInfo {}) - .unwrap(); - assert_eq!(get_period(resp.tune_ts).unwrap(), router.block_period()); - assert_eq!(resp.pool_alloc_points.len(), limit as usize); - let total_apoints: u128 = resp - .pool_alloc_points - .iter() - .cloned() - .map(|(_, apoints)| apoints.u128()) - .sum(); - assert_eq!(total_apoints, 191009600); - - // Check alloc points are properly set in generator - for (pool_addr, apoints) in resp.pool_alloc_points { - let resp: PoolInfoResponse = router - .wrap() - .query_wasm_smart( - helper.generator.clone(), - &astroport::generator::QueryMsg::PoolInfo { - lp_token: pool_addr.to_string(), - }, - ) - .unwrap(); - assert_eq!(apoints, resp.alloc_point) - } - - // Check the last pool did not receive alloc points - let generator_resp: PoolInfoResponse = router - .wrap() - .query_wasm_smart( - helper.generator.clone(), - &astroport::generator::QueryMsg::PoolInfo { - lp_token: pools[2].to_string(), - }, - ) - .unwrap(); - assert_eq!(generator_resp.alloc_point.u128(), 0) -} - -#[test] -fn check_bad_pools_filtering() { - let mut router = mock_app(); - let owner = "owner"; - let owner_addr = Addr::unchecked(owner); - let helper = ControllerHelper::init(&mut router, &owner_addr); - let user = "user1"; - - let foo_token = helper.init_cw20_token(&mut router, "FOO").unwrap(); - let bar_token = helper.init_cw20_token(&mut router, "BAR").unwrap(); - let adn_token = helper.init_cw20_token(&mut router, "ADN").unwrap(); - let pools = vec![ - helper - .create_pool(&mut router, &foo_token, &bar_token) - .unwrap(), - helper - .create_pool(&mut router, &foo_token, &adn_token) - .unwrap(), - helper - .create_pool(&mut router, &bar_token, &adn_token) - .unwrap(), - ]; - - helper.escrow_helper.mint_xastro(&mut router, user, 1000); - helper - .escrow_helper - .create_lock(&mut router, user, 10 * WEEK, 100f32) - .unwrap(); - - let err = helper - .update_whitelist( - &mut router, - "owner", - Some(vec![("random_pool".to_string())]), - None, - ) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Invalid lp token address: random_pool" - ); - - helper - .update_whitelist( - &mut router, - "owner", - Some(pools.iter().map(|el| el.to_string()).collect()), - None, - ) - .unwrap(); - - helper - .vote(&mut router, user, vec![(pools[0].as_str(), 5000)]) - .unwrap(); - - router.next_block(2 * WEEK); - - helper.tune(&mut router).unwrap(); - let resp: TuneInfo = router - .wrap() - .query_wasm_smart(helper.controller.clone(), &QueryMsg::TuneInfo {}) - .unwrap(); - // There was only one valid pool - assert_eq!(resp.pool_alloc_points.len(), 1); - - router.next_block(2 * WEEK); - - // Deregister first pair - let asset_infos = vec![ - AssetInfo::Token { - contract_addr: foo_token.clone(), - }, - AssetInfo::Token { - contract_addr: bar_token.clone(), - }, - ]; - router - .execute_contract( - owner_addr.clone(), - helper.factory.clone(), - &astroport::factory::ExecuteMsg::Deregister { - asset_infos: asset_infos.to_vec(), - }, - &[], - ) - .unwrap(); - - // We cannot vote for deregistered pool - let err = helper - .vote(&mut router, user, vec![(pools[0].as_str(), 10000)]) - .unwrap_err(); - assert_eq!( - "The pair aren't registered: contract8-contract9", - err.root_cause().to_string() - ); - - let err = helper.tune(&mut router).unwrap_err(); - assert_eq!(err.root_cause().to_string(), "There are no pools to tune"); - - router.next_block(2 * WEEK); - - // Blocking FOO token so pair[0] and pair[1] become blocked as well - let foo_asset_info = AssetInfo::Token { - contract_addr: foo_token.clone(), - }; - router - .execute_contract( - owner_addr.clone(), - helper.generator.clone(), - &astroport::generator::ExecuteMsg::UpdateBlockedTokenslist { - add: Some(vec![foo_asset_info]), - remove: None, - }, - &[], - ) - .unwrap(); - - // Voting for 2 valid pools - helper - .vote( - &mut router, - user, - vec![(pools[1].as_str(), 1000), (pools[2].as_str(), 8000)], - ) - .unwrap(); - - router.next_block(WEEK); - helper.tune(&mut router).unwrap(); - - let resp: TuneInfo = router - .wrap() - .query_wasm_smart(helper.controller.clone(), &QueryMsg::TuneInfo {}) - .unwrap(); - // Only one pool is eligible to receive alloc points - assert_eq!(resp.pool_alloc_points.len(), 1); - let total_apoints: u128 = resp - .pool_alloc_points - .iter() - .cloned() - .map(|(_, apoints)| apoints.u128()) - .sum(); - assert_eq!(total_apoints, 36615382) -} - -#[test] -fn check_update_owner() { - let mut app = mock_app(); - let owner = Addr::unchecked("owner"); - let helper = ControllerHelper::init(&mut app, &owner); - - let new_owner = String::from("new_owner"); - - // New owner - let msg = ExecuteMsg::ProposeNewOwner { - new_owner: new_owner.clone(), - expires_in: 100, // seconds - }; - - // Unauthed check - let err = app - .execute_contract( - Addr::unchecked("not_owner"), - helper.controller.clone(), - &msg, - &[], - ) - .unwrap_err(); - assert_eq!(err.root_cause().to_string(), "Generic error: Unauthorized"); - - // Claim before proposal - let err = app - .execute_contract( - Addr::unchecked(new_owner.clone()), - helper.controller.clone(), - &ExecuteMsg::ClaimOwnership {}, - &[], - ) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Generic error: Ownership proposal not found" - ); - - // Propose new owner - app.execute_contract( - Addr::unchecked("owner"), - helper.controller.clone(), - &msg, - &[], - ) - .unwrap(); - - // Claim from invalid addr - let err = app - .execute_contract( - Addr::unchecked("invalid_addr"), - helper.controller.clone(), - &ExecuteMsg::ClaimOwnership {}, - &[], - ) - .unwrap_err(); - assert_eq!(err.root_cause().to_string(), "Generic error: Unauthorized"); - - // Claim ownership - app.execute_contract( - Addr::unchecked(new_owner.clone()), - helper.controller.clone(), - &ExecuteMsg::ClaimOwnership {}, - &[], - ) - .unwrap(); - - // Let's query the contract state - let msg = QueryMsg::Config {}; - let res: ConfigResponse = app - .wrap() - .query_wasm_smart(&helper.controller, &msg) - .unwrap(); - - assert_eq!(res.owner, new_owner) -} - -#[test] -fn check_main_pool() { - let mut router = mock_app(); - let owner_addr = Addr::unchecked("owner"); - let helper = ControllerHelper::init(&mut router, &owner_addr); - let pools = vec![ - helper - .create_pool_with_tokens(&mut router, "FOO", "BAR") - .unwrap(), - helper - .create_pool_with_tokens(&mut router, "BAR", "ADN") - .unwrap(), - helper - .create_pool_with_tokens(&mut router, "FOO", "ADN") - .unwrap(), - ]; - - helper.escrow_helper.mint_xastro(&mut router, "owner", 100); - - for user in ["user1", "user2"] { - helper.escrow_helper.mint_xastro(&mut router, user, 100); - helper - .escrow_helper - .create_lock(&mut router, user, MAX_LOCK_TIME, 100f32) - .unwrap(); - } - - helper - .update_whitelist( - &mut router, - "owner", - Some(pools.iter().map(|el| el.to_string()).collect()), - None, - ) - .unwrap(); - - helper - .vote( - &mut router, - "user1", - vec![ - (pools[0].as_str(), 1000), - (pools[1].as_str(), 5000), - (pools[2].as_str(), 4000), - ], - ) - .unwrap(); - let block_period = router.block_period(); - let main_pool_info = helper - .query_voted_pool_info_at_period(&mut router, pools[0].as_str(), block_period + 2) - .unwrap(); - assert_eq!(main_pool_info.vxastro_amount.u128(), 24759614); - - let err = helper - .update_main_pool( - &mut router, - "owner", - Some(&pools[0]), - Some(Decimal::zero()), - false, - ) - .unwrap_err(); - assert_eq!( - &err.root_cause().to_string(), - "main_pool_min_alloc should be more than 0 and less than 1" - ); - let err = helper - .update_main_pool( - &mut router, - "owner", - Some(&pools[0]), - Some(Decimal::one()), - false, - ) - .unwrap_err(); - assert_eq!( - &err.root_cause().to_string(), - "main_pool_min_alloc should be more than 0 and less than 1" - ); - helper - .update_main_pool( - &mut router, - "owner", - Some(&pools[0]), - Decimal::from_str("0.3").ok(), - false, - ) - .unwrap(); - - // From now users can't vote for the main pool - let err = helper - .vote( - &mut router, - "user2", - vec![(pools[0].as_str(), 1000), (pools[1].as_str(), 9000)], - ) - .unwrap_err(); - assert_eq!( - &err.root_cause().to_string(), - "contract11 is the main pool. Voting or whitelisting the main pool is prohibited." - ); - - router - .execute_contract( - owner_addr.clone(), - helper.controller.clone(), - &ExecuteMsg::ChangePoolsLimit { limit: 2 }, - &[], - ) - .unwrap(); - - router.next_block(2 * WEEK); - helper.tune(&mut router).unwrap(); - - let resp: TuneInfo = router - .wrap() - .query_wasm_smart(helper.controller.clone(), &QueryMsg::TuneInfo {}) - .unwrap(); - // 2 (limit) + 1 (main pool) - assert_eq!(resp.pool_alloc_points.len(), 3 as usize); - let total_apoints: Uint128 = resp - .pool_alloc_points - .iter() - .map(|(_, apoints)| apoints) - .sum(); - assert_eq!(total_apoints.u128(), 318337891); - let main_pool_contribution = resp - .pool_alloc_points - .iter() - .find(|(pool, _)| pool == &pools[0]); - assert_eq!( - main_pool_contribution.unwrap().1, - (total_apoints * Decimal::from_str("0.3").unwrap()) - ); - - // Remove the main pool - helper - .update_main_pool(&mut router, "owner", None, None, true) - .unwrap(); - - router.next_block(2 * WEEK); - helper.tune(&mut router).unwrap(); - - let resp: TuneInfo = router - .wrap() - .query_wasm_smart(helper.controller.clone(), &QueryMsg::TuneInfo {}) - .unwrap(); - // The main pool was removed - assert_eq!(resp.pool_alloc_points.len(), 2 as usize); -} diff --git a/contracts/generator_controller/tests/math_test.rs b/contracts/generator_controller/tests/math_test.rs deleted file mode 100644 index f3394461..00000000 --- a/contracts/generator_controller/tests/math_test.rs +++ /dev/null @@ -1,411 +0,0 @@ -use std::cmp::Ordering; -use std::collections::HashMap; - -use anyhow::Result; -use cosmwasm_std::{Addr, Uint128}; -use cw_multi_test::{App, AppResponse, Executor}; -use itertools::Itertools; -use proptest::prelude::*; - -use astroport_governance::generator_controller::ExecuteMsg; -use astroport_governance::utils::{calc_voting_power, MAX_LOCK_TIME, WEEK}; -use generator_controller::bps::BasicPoints; -use Event::*; -use VeEvent::*; - -use astroport_tests::{ - controller_helper::ControllerHelper, escrow_helper::MULTIPLIER, mock_app, TerraAppExtension, -}; - -#[derive(Clone, Debug)] -enum Event { - Vote(Vec<((String, String), u16)>), - TunePools, - ChangePoolLimit(u64), -} - -#[derive(Clone, Debug)] -enum VeEvent { - CreateLock(f64, u64), - IncreaseTime(u64), - ExtendLock(f64), - Withdraw, -} - -struct Simulator { - user_votes: HashMap>, - locks: HashMap, - helper: ControllerHelper, - router: App, - owner: Addr, - limit: u64, - pairs: HashMap<(String, String), Addr>, -} - -impl Simulator { - pub fn init>(users: &[T]) -> Self { - let mut router = mock_app(); - let owner = Addr::unchecked("owner"); - Self { - helper: ControllerHelper::init(&mut router, &owner), - user_votes: users - .iter() - .cloned() - .map(|user| (user.into(), HashMap::new())) - .collect(), - locks: HashMap::new(), - limit: 5, - pairs: HashMap::new(), - router, - owner, - } - } - - fn escrow_events_router(&mut self, user: &str, event: VeEvent) { - // We don't check voting escrow errors - let _ = match event { - CreateLock(amount, interval) => { - self.helper - .escrow_helper - .mint_xastro(&mut self.router, user, amount as u64); - self.helper.escrow_helper.create_lock( - &mut self.router, - user, - interval, - amount as f32, - ) - } - IncreaseTime(interval) => { - self.helper - .escrow_helper - .extend_lock_time(&mut self.router, user, interval) - } - ExtendLock(amount) => { - self.helper - .escrow_helper - .mint_xastro(&mut self.router, user, amount as u64); - self.helper - .escrow_helper - .extend_lock_amount(&mut self.router, user, amount as f32) - } - Withdraw => self.helper.escrow_helper.withdraw(&mut self.router, user), - }; - } - - fn vote(&mut self, user: &str, votes: Vec<((String, String), u16)>) -> Result { - let votes: Vec<_> = votes - .iter() - .map(|(tokens, bps)| { - let addr = self - .pairs - .get(tokens) - .cloned() - .expect(&format!("Pair {}-{} was not found", tokens.0, tokens.1)); - (addr, *bps) - }) - .collect(); - self.helper - .vote(&mut self.router, user, votes.clone()) - .map(|response| { - let lock_info = self - .helper - .escrow_helper - .query_lock_info(&mut self.router, user) - .unwrap(); - let vp = self - .helper - .escrow_helper - .query_user_vp(&mut self.router, user) - .unwrap(); - self.locks.insert( - user.to_string(), - (lock_info.slope, self.router.block_period(), vp), - ); - self.user_votes.insert(user.to_string(), HashMap::new()); - for (pool, bps) in votes { - self.user_votes - .get_mut(user) - .expect("User not found!") - .insert(pool.to_string(), bps); - } - let user_info = self.helper.query_user_info(&mut self.router, user).unwrap(); - let total_apoints: u16 = user_info - .votes - .iter() - .cloned() - .map(|pair| u16::from(pair.1)) - .sum(); - if total_apoints > 10000 { - panic!("{} > 10000", total_apoints) - } - assert_eq!(user_info.vote_ts, self.router.block_info().time.seconds()); - response - }) - } - - fn change_pool_limit(&mut self, limit: u64) -> Result { - self.router - .execute_contract( - self.owner.clone(), - self.helper.controller.clone(), - &ExecuteMsg::ChangePoolsLimit { limit }, - &[], - ) - .map(|response| { - self.limit = limit; - response - }) - } - - pub fn event_router(&mut self, user: &str, event: Event) { - println!("User {} Event {:?}", user, event); - match event { - Vote(votes) => { - if let Err(err) = self.vote(user, votes) { - println!("{}", err); - } - } - TunePools => { - if let Err(err) = self.helper.tune(&mut self.router) { - println!("{}", err); - } - } - ChangePoolLimit(limit) => { - if let Err(err) = self.change_pool_limit(limit) { - println!("{}", err); - } - } - } - } - - pub fn register_pools(&mut self, tokens: &[String]) { - for token1 in tokens { - for token2 in tokens { - if matches!(token1.cmp(token2), Ordering::Less) { - self.pairs.insert( - (token1.to_string(), token2.to_string()), - self.helper - .create_pool_with_tokens(&mut self.router, token1, token2) - .unwrap(), - ); - } - } - } - } - - pub fn simulate_case( - &mut self, - tokens: &[String], - ve_events_tuples: &[(usize, String, VeEvent)], - events_tuples: &[(usize, String, Event)], - ) { - self.register_pools(tokens); - let pools = self - .pairs - .values() - .map(|pool_addr| pool_addr.to_string()) - .collect_vec(); - - let mut events: Vec> = vec![vec![]; MAX_PERIOD + 1]; - let mut ve_events: Vec> = vec![vec![]; MAX_PERIOD + 1]; - - for (period, user, event) in events_tuples.iter().cloned() { - events[period].push((user, event)); - } - for (period, user, event) in ve_events_tuples.iter().cloned() { - ve_events[period].push((user, event)) - } - - for period in 0..events.len() { - // vxASTRO events - if let Some(period_events) = ve_events.get(period) { - for (user, event) in period_events { - self.escrow_events_router(user, event.clone()) - } - } - // Generator controller events - if let Some(period_events) = events.get(period) { - if !period_events.is_empty() { - println!("Period {}:", period); - } - for (user, event) in period_events { - self.event_router(user, event.clone()) - } - } - - let mut voted_pools: HashMap = HashMap::new(); - - // Checking calculations - for user in self.user_votes.keys() { - let votes = self.user_votes.get(user).unwrap(); - if let Some((slope, start, vp)) = self.locks.get(user) { - let user_vp = calc_voting_power( - *slope, - Uint128::from((*vp * MULTIPLIER as f32) as u128), - *start, - period as u64, - ); - let user_vp = user_vp.u128() as f32 / MULTIPLIER as f32; - votes.iter().for_each(|(pool, &bps)| { - let vp = voted_pools.entry(pool.clone()).or_default(); - *vp += (bps as f32 / BasicPoints::MAX as f32) * user_vp - }) - } - } - let block_period = self.router.block_period(); - for pool_addr in &pools { - let pool_vp = self - .helper - .query_voted_pool_info_at_period(&mut self.router, pool_addr, block_period + 1) - .unwrap() - .vxastro_amount - .u128() as f32 - / MULTIPLIER as f32; - let real_vp = voted_pools.get(pool_addr).cloned().unwrap_or(0f32); - if (pool_vp - real_vp).abs() >= 10e-3 { - assert_eq!(pool_vp, real_vp, "Period: {}, pool: {}", period, pool_addr) - } - } - self.router.next_block(WEEK); - } - } -} - -const MAX_PERIOD: usize = 20; -const MAX_USERS: usize = 10; -const MAX_POOLS: usize = 5; -const MAX_EVENTS: usize = 100; - -fn escrow_events_strategy() -> impl Strategy { - prop_oneof![ - Just(VeEvent::Withdraw), - (1f64..=100f64).prop_map(VeEvent::ExtendLock), - (WEEK..MAX_LOCK_TIME).prop_map(VeEvent::IncreaseTime), - ((1f64..=100f64), WEEK..MAX_LOCK_TIME).prop_map(|(a, b)| VeEvent::CreateLock(a, b)), - ] -} - -fn vote_strategy(tokens: Vec) -> impl Strategy { - prop::collection::vec( - (prop::sample::subsequence(tokens, 2), 1..=2500u16), - 1..MAX_POOLS, - ) - .prop_filter_map( - "Accepting only BPS sum <= 10000", - |vec: Vec<(Vec, u16)>| { - let votes = vec - .iter() - .into_grouping_map_by(|(pair, _)| { - let mut pair = pair.clone(); - pair.sort(); - (pair[0].clone(), pair[1].clone()) - }) - .aggregate(|acc, _, (_, val)| Some(acc.unwrap_or(0) + *val)) - .into_iter() - .collect_vec(); - if votes.iter().map(|(_, bps)| bps).sum::() <= 10000 { - Some(Event::Vote(votes)) - } else { - None - } - }, - ) -} - -fn controller_events_strategy(tokens: Vec) -> impl Strategy { - prop_oneof![ - Just(Event::TunePools), - (2..=MAX_POOLS as u64).prop_map(Event::ChangePoolLimit), - vote_strategy(tokens) - ] -} - -fn generate_cases() -> impl Strategy< - Value = ( - Vec, - Vec, - Vec<(usize, String, VeEvent)>, - Vec<(usize, String, Event)>, - ), -> { - let tokens_strategy = - prop::collection::hash_set("[A-Z]{3}", MAX_POOLS * MAX_POOLS / 2 - MAX_POOLS); - let users_strategy = prop::collection::vec("[a-z]{10}", 1..MAX_USERS); - (users_strategy, tokens_strategy).prop_flat_map(|(users, tokens)| { - ( - Just(users.clone()), - Just(tokens.iter().cloned().collect()), - prop::collection::vec( - ( - 1..=MAX_PERIOD, - prop::sample::select(users.clone()), - escrow_events_strategy(), - ), - 0..MAX_EVENTS, - ), - prop::collection::vec( - ( - 1..=MAX_PERIOD, - prop::sample::select(users), - controller_events_strategy(tokens.iter().cloned().collect_vec()), - ), - 0..MAX_EVENTS, - ), - ) - }) -} - -proptest! { - #[test] - fn run_simulations( - case in generate_cases() - ) { - let (users, tokens, ve_events_tuples, events_tuples) = case; - let mut simulator = Simulator::init(&users); - simulator.simulate_case(&tokens, &ve_events_tuples[..], &events_tuples[..]); - } -} - -#[test] -fn exact_simulation() { - let case = ( - ["rsgnawburh", "kxhuagnkvo"], - ["FOO", "BAR"], - [ - (4, "rsgnawburh", CreateLock(100.0, 1809600)), - (5, "rsgnawburh", IncreaseTime(604800)), - (6, "kxhuagnkvo", CreateLock(100.0, 604800)), - ], - [ - ( - 4, - "rsgnawburh", - Vote(vec![(("BAR".to_string(), "FOO".to_string()), 10000)]), - ), - ( - 6, - "kxhuagnkvo", - Vote(vec![(("BAR".to_string(), "FOO".to_string()), 10000)]), - ), - ( - 6, - "rsgnawburh", - Vote(vec![(("BAR".to_string(), "FOO".to_string()), 10000)]), - ), - ], - ); - - let (users, tokens, ve_events_tuples, events_tuples) = case; - let tokens = tokens.iter().map(|item| item.to_string()).collect_vec(); - let ve_events_tuples = ve_events_tuples - .iter() - .map(|(period, user, event)| (*period, user.to_string(), event.clone())) - .collect_vec(); - let events_tuples = events_tuples - .iter() - .map(|(period, user, event)| (*period, user.to_string(), event.clone())) - .collect_vec(); - - let mut simulator = Simulator::init(&users); - simulator.simulate_case(&tokens, &ve_events_tuples[..], &events_tuples[..]); -} diff --git a/contracts/generator_controller_lite/.cargo/config b/contracts/generator_controller_lite/.cargo/config deleted file mode 100644 index 8d4bc738..00000000 --- a/contracts/generator_controller_lite/.cargo/config +++ /dev/null @@ -1,6 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib" -integration-test = "test --test integration" -schema = "run --example schema" diff --git a/contracts/generator_controller_lite/Cargo.toml b/contracts/generator_controller_lite/Cargo.toml deleted file mode 100644 index 0fec7417..00000000 --- a/contracts/generator_controller_lite/Cargo.toml +++ /dev/null @@ -1,48 +0,0 @@ -[package] -name = "generator-controller-lite" -version = "1.0.0" -authors = ["Astroport"] -edition = "2021" -repository = "https://github.com/astroport-fi/astroport-governance" -homepage = "https://astroport.fi" - -exclude = [ - # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. - "contract.wasm", - "hash.txt", -] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] - -[dependencies] -cw2 = "0.15" -cw20 = "0.15" -cosmwasm-std = "1.1" -cw-storage-plus = "0.15" -thiserror = { version = "1.0" } -itertools = "0.10" -astroport-governance = { path = "../../packages/astroport-governance" } -cosmwasm-schema = "1.1" - -[dev-dependencies] -cw-multi-test = "0.16" -astroport-tests-lite = { path = "../../packages/astroport-tests-lite" } - -astroport-generator = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -astroport-pair = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -astroport-factory = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -astroport-token = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -astroport-staking = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -astroport-whitelist = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -cw20 = "0.15" -voting-escrow = { path = "../voting_escrow" } -anyhow = "1" -proptest = "1.0" diff --git a/contracts/generator_controller_lite/src/bps.rs b/contracts/generator_controller_lite/src/bps.rs deleted file mode 100644 index 63799710..00000000 --- a/contracts/generator_controller_lite/src/bps.rs +++ /dev/null @@ -1,89 +0,0 @@ -use crate::error::ContractError; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Decimal, Fraction, StdError, Uint128}; -use std::convert::{TryFrom, TryInto}; -use std::ops::Mul; - -/// BasicPoints struct implementation. BasicPoints value is within [0, 10000] interval. -/// Technically BasicPoints is wrapper over [`u16`] with additional limit checks and -/// several implementations of math functions so BasicPoints object -/// can be used in formulas along with [`Uint128`] and [`Decimal`]. -#[cw_serde] -#[derive(Default, Copy)] -pub struct BasicPoints(u16); - -impl BasicPoints { - pub const MAX: u16 = 10000; - - pub fn checked_add(self, rhs: Self) -> Result { - let next_value = self.0 + rhs.0; - if next_value > Self::MAX { - Err(ContractError::BPSLimitError {}) - } else { - Ok(Self(next_value)) - } - } - - pub fn from_ratio(numerator: Uint128, denominator: Uint128) -> Result { - numerator - .checked_multiply_ratio(Self::MAX, denominator) - .map_err(|_| StdError::generic_err("Checked multiply ratio error!"))? - .u128() - .try_into() - } -} - -impl TryFrom for BasicPoints { - type Error = ContractError; - - fn try_from(value: u16) -> Result { - if value <= Self::MAX { - Ok(Self(value)) - } else { - Err(ContractError::BPSConverstionError(value as u128)) - } - } -} - -impl TryFrom for BasicPoints { - type Error = ContractError; - - fn try_from(value: u128) -> Result { - if value <= Self::MAX as u128 { - Ok(Self(value as u16)) - } else { - Err(ContractError::BPSConverstionError(value)) - } - } -} - -impl From for u16 { - fn from(value: BasicPoints) -> Self { - value.0 - } -} - -impl From for Uint128 { - fn from(value: BasicPoints) -> Self { - Uint128::from(u16::from(value)) - } -} - -impl Mul for BasicPoints { - type Output = Uint128; - - fn mul(self, rhs: Uint128) -> Self::Output { - rhs.multiply_ratio(self.0, Self::MAX) - } -} - -impl Mul for BasicPoints { - type Output = Decimal; - - fn mul(self, rhs: Decimal) -> Self::Output { - Decimal::from_ratio( - rhs.numerator() * Uint128::from(self.0), - rhs.denominator() * Uint128::from(Self::MAX), - ) - } -} diff --git a/contracts/generator_controller_lite/src/contract.rs b/contracts/generator_controller_lite/src/contract.rs deleted file mode 100644 index 3f89871a..00000000 --- a/contracts/generator_controller_lite/src/contract.rs +++ /dev/null @@ -1,937 +0,0 @@ -use std::collections::HashSet; -use std::convert::TryInto; - -use crate::astroport; -use astroport::common::{claim_ownership, drop_ownership_proposal, propose_new_owner}; -use astroport_governance::assembly::{ - Config as AssemblyConfig, ExecuteMsg::ExecuteEmissionsProposal, -}; -use astroport_governance::astroport::asset::addr_opt_validate; -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{ - to_json_binary, Binary, CosmosMsg, Decimal, Deps, DepsMut, Env, Fraction, MessageInfo, Order, - Response, StdError, StdResult, Uint128, WasmMsg, -}; -use cw2::set_contract_version; -use itertools::Itertools; - -use astroport_governance::generator_controller_lite::{ - ConfigResponse, ExecuteMsg, InstantiateMsg, MigrateMsg, NetworkInfo, QueryMsg, - UserInfoResponse, VOTERS_MAX_LIMIT, -}; -use astroport_governance::utils::{check_contract_supports_channel, get_lite_period}; -use astroport_governance::voting_escrow_lite::QueryMsg::CheckVotersAreBlacklisted; -use astroport_governance::voting_escrow_lite::{ - get_emissions_voting_power, get_lock_info, BlacklistedVotersResponse, -}; - -use crate::bps::BasicPoints; -use crate::error::ContractError; -use crate::state::{ - Config, TuneInfo, UserInfo, VotedPoolInfo, CONFIG, OWNERSHIP_PROPOSAL, POOLS, TUNE_INFO, - USER_INFO, -}; - -use crate::utils::{ - cancel_user_changes, check_duplicated, determine_address_prefix, filter_pools, get_pool_info, - group_pools_by_network, update_pool_info, validate_pool, validate_pools_limit, vote_for_pool, -}; - -/// Contract name that is used for migration. -const CONTRACT_NAME: &str = "generator-controller-lite"; -/// Contract version that is used for migration. -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -type ExecuteResult = Result; - -/// Creates a new contract with the specified parameters in the [`InstantiateMsg`]. -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> ExecuteResult { - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - - CONFIG.save( - deps.storage, - &Config { - owner: deps.api.addr_validate(&msg.owner)?, - escrow_addr: deps.api.addr_validate(&msg.escrow_addr)?, - generator_addr: deps.api.addr_validate(&msg.generator_addr)?, - factory_addr: deps.api.addr_validate(&msg.factory_addr)?, - assembly_addr: deps.api.addr_validate(&msg.assembly_addr)?, - hub_addr: addr_opt_validate(deps.api, &msg.hub_addr)?, - pools_limit: validate_pools_limit(msg.pools_limit)?, - kick_voters_limit: None, - main_pool: None, - main_pool_min_alloc: Decimal::zero(), - whitelisted_pools: vec![], - // Set the current network as allowed by default - whitelisted_networks: vec![NetworkInfo { - address_prefix: determine_address_prefix(&msg.generator_addr)?, - generator_address: deps.api.addr_validate(&msg.generator_addr)?, - ibc_channel: None, - }], - }, - )?; - - // Set tune_ts just for safety so the first tuning could happen in 2 weeks - TUNE_INFO.save( - deps.storage, - &TuneInfo { - tune_period: get_lite_period(env.block.time.seconds())?, - pool_alloc_points: vec![], - }, - )?; - - Ok(Response::default()) -} - -/// Exposes all the execute functions available in the contract. -/// -/// ## Execute messages -/// * **ExecuteMsg::KickBlacklistedVoters { blacklisted_voters }** Removes all votes applied by -/// blacklisted voters -/// -/// * **ExecuteMsg::KickUnlockedVoters { blacklisted_voters }** Removes all votes applied by -/// voters that started unlocking -/// -/// * **ExecuteMsg::KickUnlockedOutpostVoter { blacklisted_voters }** Removes all votes applied by -/// voters that started unlocking on an Outpost -/// -/// * **ExecuteMsg::Vote { votes }** Casts votes for pools -/// -/// * **ExecuteMsg::OutpostVote { voter, votes, voting_power }** Casts votes for pools from an Outpost -/// -/// * **ExecuteMsg::TunePools** Launches pool tuning -/// -/// * **ExecuteMsg::ChangePoolsLimit { limit }** Changes the number of pools which are eligible -/// to receive allocation points -/// -/// * **ExecuteMsg::UpdateConfig { blacklisted_voters_limit }** Changes the number of blacklisted -/// voters that can be kicked at once -/// -/// * **ExecuteMsg::UpdateWhitelist { add, remove }** Adds or removes lp tokens which are eligible -/// to receive votes. -/// -/// * **ExecuteMsg::UpdateNetworks { add, remove }** Adds or removes networks mappings for tuning -/// pools on remote chains via a special governance proposal -/// -/// * **ExecuteMsg::ProposeNewOwner { owner, expires_in }** Creates a new request to change -/// contract ownership. -/// -/// * **ExecuteMsg::DropOwnershipProposal {}** Removes a request to change contract ownership. -/// -/// * **ExecuteMsg::ClaimOwnership {}** Claims contract ownership. -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> ExecuteResult { - match msg { - ExecuteMsg::KickBlacklistedVoters { blacklisted_voters } => { - kick_blacklisted_voters(deps, env, blacklisted_voters) - } - ExecuteMsg::KickUnlockedVoters { unlocked_voters } => { - kick_unlocked_voters(deps, env, unlocked_voters) - } - ExecuteMsg::KickUnlockedOutpostVoter { unlocked_voter } => { - kick_unlocked_outpost_voter(deps, env, info, unlocked_voter) - } - ExecuteMsg::Vote { votes } => handle_vote(deps, env, info, votes), - ExecuteMsg::OutpostVote { - voter, - votes, - voting_power, - } => handle_outpost_vote(deps, env, info, voter, votes, voting_power), - ExecuteMsg::TunePools {} => tune_pools(deps, env), - ExecuteMsg::ChangePoolsLimit { limit } => change_pools_limit(deps, info, limit), - ExecuteMsg::UpdateConfig { - assembly_addr, - kick_voters_limit, - main_pool, - main_pool_min_alloc, - remove_main_pool, - hub_addr, - } => update_config( - deps, - info, - assembly_addr, - kick_voters_limit, - main_pool, - main_pool_min_alloc, - remove_main_pool, - hub_addr, - ), - ExecuteMsg::UpdateWhitelist { add, remove } => update_whitelist(deps, info, add, remove), - ExecuteMsg::UpdateNetworks { add, remove } => update_networks(deps, info, add, remove), - ExecuteMsg::ProposeNewOwner { - new_owner, - expires_in, - } => { - let config: Config = CONFIG.load(deps.storage)?; - - propose_new_owner( - deps, - info, - env, - new_owner, - expires_in, - config.owner, - OWNERSHIP_PROPOSAL, - ) - .map_err(Into::into) - } - ExecuteMsg::DropOwnershipProposal {} => { - let config: Config = CONFIG.load(deps.storage)?; - - drop_ownership_proposal(deps, info, config.owner, OWNERSHIP_PROPOSAL) - .map_err(Into::into) - } - ExecuteMsg::ClaimOwnership {} => { - claim_ownership(deps, info, env, OWNERSHIP_PROPOSAL, |deps, new_owner| { - CONFIG - .update::<_, StdError>(deps.storage, |mut v| { - v.owner = new_owner; - Ok(v) - }) - .map(|_| ()) - }) - .map_err(Into::into) - } - } -} - -/// Adds or removes lp tokens which are eligible to receive votes. -/// Returns a [`ContractError`] on failure. -fn update_whitelist( - deps: DepsMut, - info: MessageInfo, - add: Option>, - remove: Option>, -) -> Result { - let mut config = CONFIG.load(deps.storage)?; - - // Permission check - if info.sender != config.owner { - return Err(ContractError::Unauthorized {}); - } - - // Remove old LP tokens - if let Some(remove_lp_tokens) = remove { - config - .whitelisted_pools - .retain(|pool| !remove_lp_tokens.contains(&pool.to_string())); - } - - // Add new lp tokens - if let Some(add_lp_tokens) = add { - config.whitelisted_pools.append( - &mut add_lp_tokens - .into_iter() - .map(|lp_token| { - validate_pool(&config, &lp_token)?; - Ok(lp_token) - }) - .collect::, ContractError>>()?, - ); - check_duplicated(&config.whitelisted_pools).map_err(|_| - ContractError::Std(StdError::generic_err("The resulting whitelist contains duplicated pools. It's either provided 'add' list contains duplicated pools or some of the added pools are already whitelisted.")))?; - } - - CONFIG.save(deps.storage, &config)?; - Ok(Response::default().add_attribute("action", "update_whitelist")) -} - -/// Adds or removes networks mappings for tuning -/// pools on remote chains via a special governance proposal -/// Returns a [`ContractError`] on failure. -fn update_networks( - deps: DepsMut, - info: MessageInfo, - add: Option>, - remove: Option>, -) -> Result { - let mut config = CONFIG.load(deps.storage)?; - - // Permission check - if info.sender != config.owner { - return Err(ContractError::Unauthorized {}); - } - - // Handle removals - // The network added in instantiate, ie. the network of the contract itself, cannot be removed - if let Some(remove_prefixes) = remove { - let native_prefix = determine_address_prefix(config.generator_addr.as_ref())?; - - if remove_prefixes.contains(&native_prefix) { - return Err(ContractError::Std(StdError::generic_err(format!( - "Cannot remove the native network with prefix {}", - native_prefix - )))); - } - - config - .whitelisted_networks - .retain(|network| !remove_prefixes.contains(&network.address_prefix)); - } - - let mut response = Response::default().add_attribute("action", "update_networks"); - if let Some(add_prefix) = add { - // Get the assembly contract to check if the controller supports a specific channel - let assembly_config: AssemblyConfig = deps - .querier - .query_wasm_smart(config.assembly_addr.clone(), &QueryMsg::Config {})?; - - config.whitelisted_networks.append( - &mut add_prefix - .into_iter() - .map(|mut network_info| { - // If the IBC channel is set, check if the controller supports it - if let Some(ibc_channel) = network_info.ibc_channel.clone() { - match &assembly_config.ibc_controller { - Some(ibc_controller) => { - check_contract_supports_channel( - deps.querier, - ibc_controller, - &ibc_channel, - )?; - } - None => { - return Err(ContractError::Std(StdError::generic_err( - "The Assembly does not have an IBC controller set", - ))) - } - } - } - // Determine the prefix based on the generator address - network_info.address_prefix = - determine_address_prefix(network_info.generator_address.as_ref())?; - Ok(network_info) - }) - .collect::, ContractError>>()?, - ); - let prefixes: Vec = config - .whitelisted_networks - .iter() - .map(|info| info.address_prefix.clone()) - .collect(); - check_duplicated(&prefixes).map_err(|_| - ContractError::Std(StdError::generic_err("The resulting whitelist contains duplicated prefixes. It's either provided 'add' list contains duplicated prefixes or some of the added prefixes are already whitelisted.")))?; - // Emit added prefixes - response = response.add_attribute("added", prefixes.join(",")); - } - - CONFIG.save(deps.storage, &config)?; - Ok(response) -} - -/// This function removes all votes applied by blacklisted voters. -/// -/// * **holders** list with blacklisted holders whose votes will be removed. -fn kick_blacklisted_voters(deps: DepsMut, env: Env, voters: Vec) -> ExecuteResult { - let block_period = get_lite_period(env.block.time.seconds())?; - let config = CONFIG.load(deps.storage)?; - - if voters.len() > config.kick_voters_limit.unwrap_or(VOTERS_MAX_LIMIT) as usize { - return Err(ContractError::KickVotersLimitExceeded {}); - } - - // Check duplicated voters - let addrs_set = voters.iter().collect::>(); - if voters.len() != addrs_set.len() { - return Err(ContractError::DuplicatedVoters {}); - } - - // Check if voters are blacklisted - let res: BlacklistedVotersResponse = deps.querier.query_wasm_smart( - config.escrow_addr, - &CheckVotersAreBlacklisted { - voters: voters.clone(), - }, - )?; - - if !res.eq(&BlacklistedVotersResponse::VotersBlacklisted {}) { - return Err(ContractError::Std(StdError::generic_err(res.to_string()))); - } - - for voter in voters { - if let Some(user_info) = USER_INFO.may_load(deps.storage, &voter)? { - // Cancel changes applied by previous votes immediately - user_info.votes.iter().try_for_each(|(pool_addr, bps)| { - cancel_user_changes( - deps.storage, - block_period, - pool_addr, - *bps, - user_info.voting_power, - ) - })?; - - let user_info = UserInfo { - vote_period: Some(block_period), - ..Default::default() - }; - - USER_INFO.save(deps.storage, &voter, &user_info)?; - } - } - - Ok(Response::new().add_attribute("action", "kick_blocklisted_holders")) -} - -/// This function removes all votes applied by unlocked voters. -/// -/// * **holders** list with unlocked holders whose votes will be removed. -fn kick_unlocked_voters(deps: DepsMut, env: Env, voters: Vec) -> ExecuteResult { - let block_period = get_lite_period(env.block.time.seconds())?; - let config = CONFIG.load(deps.storage)?; - - if voters.len() > config.kick_voters_limit.unwrap_or(VOTERS_MAX_LIMIT) as usize { - return Err(ContractError::KickVotersLimitExceeded {}); - } - - // Check duplicated voters - let addrs_set = voters.iter().collect::>(); - if voters.len() != addrs_set.len() { - return Err(ContractError::DuplicatedVoters {}); - } - - for voter in voters { - let lock_info = get_lock_info(&deps.querier, config.escrow_addr.clone(), voter.clone())?; - if lock_info.end.is_none() { - // This voter has not unlocked - return Err(ContractError::AddressIsLocked(voter)); - } - - if let Some(user_info) = USER_INFO.may_load(deps.storage, &voter)? { - // Cancel changes applied by previous votes immediately - user_info.votes.iter().try_for_each(|(pool_addr, bps)| { - cancel_user_changes( - deps.storage, - block_period, - pool_addr, - *bps, - user_info.voting_power, - ) - })?; - - let user_info = UserInfo { - vote_period: Some(block_period), - ..Default::default() - }; - - USER_INFO.save(deps.storage, &voter, &user_info)?; - } - } - - Ok(Response::new().add_attribute("action", "kick_holders")) -} - -/// This function removes all votes applied by an unlocked voters from an Outpost. -/// -/// * **voter** the unlocked holder whose votes will be removed. -fn kick_unlocked_outpost_voter( - deps: DepsMut, - env: Env, - info: MessageInfo, - voter: String, -) -> ExecuteResult { - let config = CONFIG.load(deps.storage)?; - - // We only allow the Hub to kick a voter from an Outpost - let hub = match config.hub_addr { - Some(hub) => hub, - None => return Err(ContractError::InvalidHub {}), - }; - - if info.sender != hub { - return Err(ContractError::Unauthorized {}); - } - - let block_period = get_lite_period(env.block.time.seconds())?; - if let Some(user_info) = USER_INFO.may_load(deps.storage, &voter)? { - // Cancel changes applied by previous votes immediately - user_info.votes.iter().try_for_each(|(pool_addr, bps)| { - cancel_user_changes( - deps.storage, - block_period, - pool_addr, - *bps, - user_info.voting_power, - ) - })?; - - let user_info = UserInfo { - vote_period: Some(block_period), - ..Default::default() - }; - - USER_INFO.save(deps.storage, &voter, &user_info)?; - } - - Ok(Response::new().add_attribute("action", "kick_outpost_holders")) -} - -/// Handles a vote on the current chain. -/// -/// * **votes** is a vector of pairs ([`String`], [`u16`]). -/// Tuple consists of pool address and percentage of user's voting power for a given pool. -/// Percentage should be in BPS form. -fn handle_vote( - deps: DepsMut, - env: Env, - info: MessageInfo, - votes: Vec<(String, u16)>, -) -> ExecuteResult { - let user = info.sender.to_string(); - let config = CONFIG.load(deps.storage)?; - let user_vp = get_emissions_voting_power(&deps.querier, &config.escrow_addr, &user)?; - - apply_vote(deps, env, user, user_vp, config, votes)?; - - Ok(Response::new().add_attribute("action", "vote")) -} - -/// Handles a vote from an Outpost. -/// -/// * **voter** is the address of the voter from the Outpost. -/// -/// * **votes** is a vector of pairs ([`String`], [`u16`]). -/// Tuple consists of pool address and percentage of user's voting power for a given pool. -/// Percentage should be in BPS form. -/// -/// * **voting_power** is voting power of the voter from the Outpost as validated by the Hub. -fn handle_outpost_vote( - deps: DepsMut, - env: Env, - info: MessageInfo, - voter: String, - votes: Vec<(String, u16)>, - voting_power: Uint128, -) -> ExecuteResult { - let config = CONFIG.load(deps.storage)?; - - // We only allow the Hub to submit emission votes on behalf of Outpost user - // The Hub is responsible for validating the Hub vote with the Outpost - let hub = match config.hub_addr.clone() { - Some(hub) => hub, - None => return Err(ContractError::InvalidHub {}), - }; - - if info.sender != hub { - return Err(ContractError::Unauthorized {}); - } - - apply_vote(deps, env, voter, voting_power, config, votes)?; - - Ok(Response::new().add_attribute("action", "outpost_vote")) -} - -/// Apply the votes for the given user -/// -/// The function checks that: -/// * the user voting power is > 0, -/// * user didn't vote in this period, -/// * 'votes' vector doesn't contain duplicated pool addresses, -/// * sum of all BPS values <= 10000. -/// -/// The function cancels changes applied by previous votes and apply new votes for the this period. -/// New vote parameters are saved in [`USER_INFO`]. -fn apply_vote( - deps: DepsMut, - env: Env, - voter: String, - voting_power: Uint128, - config: ConfigResponse, - votes: Vec<(String, u16)>, -) -> Result<(), ContractError> { - if voting_power.is_zero() { - return Err(ContractError::ZeroVotingPower {}); - } - - if config.whitelisted_pools.is_empty() { - return Err(ContractError::WhitelistEmpty {}); - } - - let user_info = USER_INFO - .may_load(deps.storage, &voter)? - .unwrap_or_default(); - - let block_period = get_lite_period(env.block.time.seconds())?; - if let Some(vote_period) = user_info.vote_period { - if vote_period == block_period { - return Err(ContractError::CooldownError {}); - } - } - - // Has the user voted in this period? - check_duplicated( - &votes - .iter() - .map(|vote| { - let (lp_token, _) = vote; - lp_token - }) - .collect::>(), - )?; - - // Validating addrs and bps - let votes = votes - .into_iter() - .map(|(addr, bps)| { - // Voting for the main pool is prohibited - if let Some(main_pool) = &config.main_pool { - if addr == *main_pool { - return Err(ContractError::MainPoolVoteOrWhitelistingProhibited( - main_pool.to_string(), - )); - } - } - if !config.whitelisted_pools.contains(&addr) { - return Err(ContractError::PoolIsNotWhitelisted(addr)); - } - - validate_pool(&config, &addr)?; - - let bps: BasicPoints = bps.try_into()?; - Ok((addr, bps)) - }) - .collect::, ContractError>>()?; - - // Check the bps sum is within the limit - votes - .iter() - .try_fold(BasicPoints::default(), |acc, (_, bps)| { - acc.checked_add(*bps) - })?; - - // Cancel changes applied by previous votes - user_info.votes.iter().try_for_each(|(pool_addr, bps)| { - cancel_user_changes( - deps.storage, - block_period, - pool_addr, - *bps, - user_info.voting_power, - ) - })?; - - // Votes are applied to current period - // In vxASTRO lite, voting power is removed immediately - // when a user unlocks - votes.iter().try_for_each(|(pool_addr, bps)| { - vote_for_pool( - deps.storage, - block_period, - pool_addr.as_str(), - *bps, - voting_power, - ) - })?; - - let user_info = UserInfo { - vote_period: Some(block_period), - voting_power, - votes, - }; - - Ok(USER_INFO.save(deps.storage, &voter, &user_info)?) -} - -/// The function checks that the last pools tuning happened >= 14 days ago. -/// Then it calculates voting power for each pool at the current period, filters all pools which -/// are not eligible to receive allocation points, -/// takes top X pools by voting power, where X is 'config.pools_limit', calculates allocation points -/// for these pools and applies allocation points in generator contract. -/// -/// For pools on the same network (e.g. Terra), the allocation points are set -/// directly on the generator. For pools on different networks (e.g. Injective), -/// we create a special governance proposal to set the allocation points on the -/// remote generator. -/// -/// We determine the network of a pool by looking at the address prefix. -fn tune_pools(deps: DepsMut, env: Env) -> ExecuteResult { - let mut tune_info = TUNE_INFO.load(deps.storage)?; - let config = CONFIG.load(deps.storage)?; - let block_period = get_lite_period(env.block.time.seconds())?; - - if tune_info.tune_period == block_period { - return Err(ContractError::CooldownError {}); - } - - // We're tuning pools based on the previous voting period - let tune_period = block_period - 1; - let pool_votes: Vec<_> = POOLS - .keys(deps.as_ref().storage, None, None, Order::Ascending) - .collect::>() - .into_iter() - .map(|pool_addr| { - let pool_addr = pool_addr?; - - let pool_info = update_pool_info(deps.storage, tune_period, &pool_addr, None)?; - // Remove pools with zero voting power so we won't iterate over them in future - if pool_info.vxastro_amount.is_zero() { - POOLS.remove(deps.storage, &pool_addr) - } - Ok((pool_addr, pool_info.vxastro_amount)) - }) - .collect::>>()? - .into_iter() - .filter(|(_, vxastro_amount)| !vxastro_amount.is_zero()) - .sorted_by(|(_, a), (_, b)| b.cmp(a)) // Sort in descending order - .collect(); - - // Filter pools which are not eligible to receive allocation points - // Pools might be on a different chain and thus not much can be done in - // terms of validation. That will be handled via governance proposals and - // the whitelist - tune_info.pool_alloc_points = filter_pools( - pool_votes, - config.pools_limit + 1, // +1 additional pool if we will need to remove the main pool - )?; - - // Set allocation points for the main pool - match config.main_pool { - Some(main_pool) if !config.main_pool_min_alloc.is_zero() => { - // Main pool may appear in the pool list thus we need to eliminate its contribution in the total VP. - tune_info - .pool_alloc_points - .retain(|(pool, _)| pool != &main_pool.to_string()); - // If there is no main pool in the filtered list then we need to remove additional pool - tune_info.pool_alloc_points = tune_info - .pool_alloc_points - .iter() - .take(config.pools_limit as usize) - .cloned() - .collect(); - - let total_vp: Uint128 = tune_info - .pool_alloc_points - .iter() - .fold(Uint128::zero(), |acc, (_, vp)| acc + vp); - // Calculate main pool contribution. - // Example (30% for the main pool): VP + x = y, x = 0.3y => y = VP/0.7 => x = 0.3 * VP / 0.7, - // where VP - total VP, x - main pool's contribution, y - new total VP. - // x = 0.3 * VP * (1-0.3)^(-1) - let main_pool_contribution = config.main_pool_min_alloc - * total_vp - * (Decimal::one() - config.main_pool_min_alloc).inv().unwrap(); - tune_info - .pool_alloc_points - .push((main_pool.to_string(), main_pool_contribution)) - } - _ => { - // there is no main pool or min alloc is 0% - tune_info.pool_alloc_points = tune_info - .pool_alloc_points - .iter() - .take(config.pools_limit as usize) - .cloned() - .collect(); - } - } - - if tune_info.pool_alloc_points.is_empty() { - return Err(ContractError::TuneNoPools {}); - } - - // Tuning can only happen once per period. As we're tuning for the previous - // period, we set this to the current period - tune_info.tune_period = block_period; - TUNE_INFO.save(deps.storage, &tune_info)?; - - // Split pools by network and send separate messages for each network - let grouped_pools = group_pools_by_network(&config.whitelisted_networks, &tune_info); - - let mut response = Response::new().add_attribute("action", "tune_pools"); - for (network_info, pool_alloc_points) in &grouped_pools { - // The message to set the allocation points on the generator, either - // directly or via a governance proposal for Outposts - let setup_pools_msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: network_info.generator_address.to_string(), - msg: to_json_binary(&astroport::generator::ExecuteMsg::SetupPools { - pools: pool_alloc_points.to_vec(), - })?, - funds: vec![], - }); - - match &network_info.ibc_channel { - // If the channel is empty, then this is setting up pools on the network - // we are deployed on and we can continue as normal - None => { - response = response - .add_attribute("tune", network_info.address_prefix.to_string()) - .add_attribute("pool_count", pool_alloc_points.len().to_string()) - .add_message(setup_pools_msg); - } - // If the channel is not empty, then this is setting up pools on an - // Outpost - Some(ibc_channel) => { - // We need to submit the setup pools message to the - // Assembly as a proposal to execute on the remote chain - let proposal_msg = to_json_binary(&ExecuteEmissionsProposal { - title: format!( - // Sample title: "Update emissions on the inj outpost", "Update emissions on the neutron outpost" - "Update emissions on the {} outpost", - network_info.address_prefix - ), - description: format!( - // Sample title: "This proposal aims to update emissions on the inj outpost using IBC channel-2" - "This proposal aims to update emissions on the {} outpost using IBC {}", - network_info.address_prefix, ibc_channel - ), - messages: vec![setup_pools_msg], - ibc_channel: Some(ibc_channel.to_string()), - })?; - - let setup_pools_assembly_msg = CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: config.assembly_addr.to_string(), - msg: proposal_msg, - funds: vec![], - }); - - response = response - .add_attribute("tune", network_info.address_prefix.to_string()) - .add_attribute("channel", ibc_channel) - .add_attribute("pool_count", pool_alloc_points.len().to_string()) - .add_message(setup_pools_assembly_msg); - } - } - } - Ok(response) -} - -/// Only contract owner can call this function. -/// The function sets a new limit of blacklisted voters that can be kicked at once. -/// -/// * **assembly_addr** is a new address of the Assembly contract -/// -/// * **kick_voters_limit** is a new limit of blacklisted or unlocked voters which can be kicked at once -/// -/// * **main_pool** is a main pool address -/// -/// * **main_pool_min_alloc** is a minimum percentage of ASTRO emissions that this pool should get every block -/// -/// * **remove_main_pool** should the main pool be removed or not -#[allow(clippy::too_many_arguments)] -fn update_config( - deps: DepsMut, - info: MessageInfo, - assembly_addr: Option, - kick_voters_limit: Option, - main_pool: Option, - main_pool_min_alloc: Option, - remove_main_pool: Option, - hub_addr: Option, -) -> ExecuteResult { - let mut config = CONFIG.load(deps.storage)?; - - if info.sender != config.owner { - return Err(ContractError::Unauthorized {}); - } - - if let Some(assembly_addr) = assembly_addr { - config.assembly_addr = deps.api.addr_validate(&assembly_addr)?; - } - - if let Some(kick_voters_limit) = kick_voters_limit { - config.kick_voters_limit = Some(kick_voters_limit); - } - - if let Some(main_pool_min_alloc) = main_pool_min_alloc { - if main_pool_min_alloc == Decimal::zero() || main_pool_min_alloc >= Decimal::one() { - return Err(ContractError::MainPoolMinAllocFailed {}); - } - config.main_pool_min_alloc = main_pool_min_alloc; - } - - if let Some(main_pool) = main_pool { - if config.main_pool_min_alloc.is_zero() { - return Err(StdError::generic_err("Main pool min alloc can not be zero").into()); - } - config.main_pool = Some(deps.api.addr_validate(&main_pool)?); - } - - if let Some(remove_main_pool) = remove_main_pool { - if remove_main_pool { - config.main_pool = None; - } - } - - if let Some(hub_addr) = hub_addr { - config.hub_addr = Some(deps.api.addr_validate(&hub_addr)?); - } - - CONFIG.save(deps.storage, &config)?; - - Ok(Response::default().add_attribute("action", "update_config")) -} - -/// Only contract owner can call this function. -/// The function sets new limit of pools which are eligible to receive allocation points. -/// -/// * **limit** is a new limit of pools which are eligible to receive allocation points. -fn change_pools_limit(deps: DepsMut, info: MessageInfo, limit: u64) -> ExecuteResult { - let mut config = CONFIG.load(deps.storage)?; - - if info.sender != config.owner { - return Err(ContractError::Unauthorized {}); - } - - config.pools_limit = validate_pools_limit(limit)?; - CONFIG.save(deps.storage, &config)?; - - Ok(Response::default().add_attribute("action", "change_pools_limit")) -} - -/// Expose available contract queries. -/// -/// ## Queries -/// * **QueryMsg::UserInfo { user }** Fetch user information -/// -/// * **QueryMsg::TuneInfo** Fetch last tuning information -/// -/// * **QueryMsg::Config** Fetch contract config -/// -/// * **QueryMsg::PoolInfo { pool_addr }** Fetch pool's voting information at the current period. -/// -/// * **QueryMsg::PoolInfoAtPeriod { pool_addr, period }** Fetch pool's voting information at a specified period. -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::UserInfo { user } => to_json_binary(&user_info(deps, user)?), - QueryMsg::TuneInfo {} => to_json_binary(&TUNE_INFO.load(deps.storage)?), - QueryMsg::Config {} => to_json_binary(&CONFIG.load(deps.storage)?), - QueryMsg::PoolInfo { pool_addr } => to_json_binary(&pool_info(deps, env, pool_addr, None)?), - QueryMsg::PoolInfoAtPeriod { pool_addr, period } => { - to_json_binary(&pool_info(deps, env, pool_addr, Some(period))?) - } - } -} - -/// Returns user information. -fn user_info(deps: Deps, user: String) -> StdResult { - USER_INFO - .may_load(deps.storage, &user)? - .map(UserInfo::into_response) - .ok_or_else(|| StdError::generic_err("User not found")) -} - -/// Returns pool's voting information at a specified period. -fn pool_info( - deps: Deps, - env: Env, - pool_addr: String, - period: Option, -) -> StdResult { - let block_period = get_lite_period(env.block.time.seconds())?; - let period = period.unwrap_or(block_period); - get_pool_info(deps.storage, period, &pool_addr) -} - -/// Manages contract migration -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { - Err(ContractError::MigrationError {}) -} diff --git a/contracts/generator_controller_lite/src/error.rs b/contracts/generator_controller_lite/src/error.rs deleted file mode 100644 index 2c3368b3..00000000 --- a/contracts/generator_controller_lite/src/error.rs +++ /dev/null @@ -1,69 +0,0 @@ -use cosmwasm_std::StdError; -use thiserror::Error; - -/// This enum describes contract errors -#[derive(Error, Debug)] -pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("Unauthorized")] - Unauthorized {}, - - #[error("Basic points conversion error. {0} > 10000")] - BPSConverstionError(u128), - - #[error("Basic points sum exceeds limit")] - BPSLimitError {}, - - #[error("You can't vote with zero voting power")] - ZeroVotingPower {}, - - #[error("{0} is the main pool. Voting or whitelisting the main pool is prohibited.")] - MainPoolVoteOrWhitelistingProhibited(String), - - #[error("main_pool_min_alloc should be more than 0 and less than 1")] - MainPoolMinAllocFailed {}, - - #[error("You can only run this action once in a voting period")] - CooldownError {}, - - #[error("Invalid lp token address: {0}")] - InvalidLPTokenAddress(String), - - #[error("Votes contain duplicated pool addresses")] - DuplicatedPools {}, - - #[error("There are no pools to tune")] - TuneNoPools {}, - - #[error("Invalid pool number: {0}. Must be within [2, 100] range")] - InvalidPoolNumber(u64), - - #[error("The vector contains duplicated addresses")] - DuplicatedVoters {}, - - #[error("Exceeded voters limit for kick blacklisted/unlocked voters operation!")] - KickVotersLimitExceeded {}, - - #[error("Contract can't be migrated!")] - MigrationError {}, - - #[error("Whitelist cannot be empty!")] - WhitelistEmpty {}, - - #[error("The pair aren't registered: {0}-{1}")] - PairNotRegistered(String, String), - - #[error("Pool is already whitelisted: {0}")] - PoolIsWhitelisted(String), - - #[error("Pool is not whitelisted: {0}")] - PoolIsNotWhitelisted(String), - - #[error("Address is still locked: {0}")] - AddressIsLocked(String), - - #[error("Sender is not the Hub installed")] - InvalidHub {}, -} diff --git a/contracts/generator_controller_lite/src/lib.rs b/contracts/generator_controller_lite/src/lib.rs deleted file mode 100644 index 6764e51f..00000000 --- a/contracts/generator_controller_lite/src/lib.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub mod bps; -pub mod contract; -pub mod state; - -// During development this import could be replaced with another astroport version. -// However, in production, the astroport version should be the same for all contracts. -pub use astroport_governance::astroport; - -mod error; -mod utils; diff --git a/contracts/generator_controller_lite/src/state.rs b/contracts/generator_controller_lite/src/state.rs deleted file mode 100644 index 25879046..00000000 --- a/contracts/generator_controller_lite/src/state.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::astroport::common::OwnershipProposal; -use crate::bps::BasicPoints; - -use astroport_governance::generator_controller_lite::{ - ConfigResponse, GaugeInfoResponse, UserInfoResponse, VotedPoolInfoResponse, -}; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::Uint128; -use cw_storage_plus::{Item, Map}; - -/// This structure describes the main control config of generator controller contract. -pub type Config = ConfigResponse; -/// This structure describes voting parameters for a specific pool. -pub type VotedPoolInfo = VotedPoolInfoResponse; -/// This structure describes last tuning parameters. -pub type TuneInfo = GaugeInfoResponse; - -/// The struct describes last user's votes parameters. -#[cw_serde] -#[derive(Default)] -pub struct UserInfo { - /// The period when the user voted last time, None if they've never voted - pub vote_period: Option, - /// The user's vxASTRO voting power - pub voting_power: Uint128, - /// The vote distribution for all the generators/pools the staker picked - pub votes: Vec<(String, BasicPoints)>, -} - -impl UserInfo { - /// The function converts [`UserInfo`] object into [`UserInfoResponse`]. - pub(crate) fn into_response(self) -> UserInfoResponse { - let votes = self - .votes - .iter() - .map(|(pool_addr, bps)| (pool_addr.clone(), u16::from(*bps))) - .collect(); - - UserInfoResponse { - vote_period: self.vote_period, - voting_power: self.voting_power, - votes, - } - } -} - -/// Stores config at the given key. -pub const CONFIG: Item = Item::new("config"); - -/// Stores voting parameters per pool at a specific period by key ( period -> pool_addr ). -pub const POOL_VOTES: Map<(u64, &str), VotedPoolInfo> = Map::new("pool_votes"); - -/// HashSet based on [`Map`]. It contains all pool addresses whose voting power > 0. -pub const POOLS: Map<&str, ()> = Map::new("pools"); - -/// Hashset based on [`Map`]. It stores null object by key ( pool_addr -> period ). -/// This hashset contains all periods which have saved result in [`POOL_VOTES`] for a specific pool address. -pub const POOL_PERIODS: Map<(&str, u64), ()> = Map::new("pool_periods"); - -/// User's voting information. -pub const USER_INFO: Map<&str, UserInfo> = Map::new("user_info"); - -/// Last tuning information. -pub const TUNE_INFO: Item = Item::new("tune_info"); - -/// Contains a proposal to change contract ownership -pub const OWNERSHIP_PROPOSAL: Item = Item::new("ownership_proposal"); diff --git a/contracts/generator_controller_lite/src/utils.rs b/contracts/generator_controller_lite/src/utils.rs deleted file mode 100644 index a7313648..00000000 --- a/contracts/generator_controller_lite/src/utils.rs +++ /dev/null @@ -1,295 +0,0 @@ -use std::collections::{HashMap, HashSet}; -use std::hash::Hash; -use std::ops::RangeInclusive; - -use astroport_governance::generator_controller_lite::{ - ConfigResponse, GaugeInfoResponse, NetworkInfo, -}; -use cosmwasm_std::{Order, StdError, StdResult, Storage, Uint128}; -use cw_storage_plus::Bound; - -use crate::bps::BasicPoints; -use crate::error::ContractError; -use crate::state::{VotedPoolInfo, POOLS, POOL_PERIODS, POOL_VOTES}; - -/// Pools limit should be within the range `[2, 100]` -const POOL_NUMBER_LIMIT: RangeInclusive = 2..=100; - -/// The enum defines math operations with voting power and slope. -#[derive(Debug)] -pub(crate) enum Operation { - Add, - Sub, -} - -impl Operation { - pub fn calc_voting_power(&self, cur_vp: Uint128, vp: Uint128, bps: BasicPoints) -> Uint128 { - match self { - Operation::Add => cur_vp + bps * vp, - Operation::Sub => cur_vp.saturating_sub(bps * vp), - } - } -} - -/// Enum wraps [`VotedPoolInfo`] so the contract can leverage storage operations efficiently. -#[derive(Debug)] -pub(crate) enum VotedPoolInfoResult { - Unchanged(VotedPoolInfo), - New(VotedPoolInfo), -} - -/// Filters pairs (LP token address, voting parameters) by only taking up to -/// pool_limit -/// We can no longer validate the pools as they might be on a different chain -pub(crate) fn filter_pools( - pools: Vec<(String, Uint128)>, - pools_limit: u64, -) -> StdResult> { - let pools = pools - .into_iter() - .map(|(pool_addr, vxastro_amount)| (pool_addr, vxastro_amount)) - .take(pools_limit as usize) - .collect(); - Ok(pools) -} - -/// Cancels user changes using old voting parameters for a given pool. -/// Firstly, it removes slope change scheduled for previous lockup end period. -/// Secondly, it updates voting parameters for the given period, but without user's vote. -pub(crate) fn cancel_user_changes( - storage: &mut dyn Storage, - period: u64, - pool_addr: &str, - old_bps: BasicPoints, - old_vp: Uint128, -) -> StdResult<()> { - update_pool_info( - storage, - period, - pool_addr, - Some((old_bps, old_vp, Operation::Sub)), - ) - .map(|_| ()) -} - -/// Applies user's vote for a given pool. -/// It updates voting parameters with applied user's vote. -pub(crate) fn vote_for_pool( - storage: &mut dyn Storage, - period: u64, - pool_addr: &str, - bps: BasicPoints, - vp: Uint128, -) -> StdResult<()> { - update_pool_info(storage, period, pool_addr, Some((bps, vp, Operation::Add))).map(|_| ()) -} - -/// Fetches voting parameters for a given pool at specific period, applies new changes, saves it in storage -/// and returns new voting parameters in [`VotedPoolInfo`] object. -/// If there are no changes in 'changes' parameter -/// and voting parameters were already calculated before the function just returns [`VotedPoolInfo`]. -pub(crate) fn update_pool_info( - storage: &mut dyn Storage, - period: u64, - pool_addr: &str, - changes: Option<(BasicPoints, Uint128, Operation)>, -) -> StdResult { - if POOLS.may_load(storage, pool_addr)?.is_none() { - POOLS.save(storage, pool_addr, &())? - } - let period_key = period; - let pool_info = match get_pool_info_mut(storage, period, pool_addr)? { - VotedPoolInfoResult::Unchanged(mut pool_info) | VotedPoolInfoResult::New(mut pool_info) - if changes.is_some() => - { - if let Some((bps, vp, op)) = changes { - pool_info.vxastro_amount = op.calc_voting_power(pool_info.vxastro_amount, vp, bps); - } - POOL_PERIODS.save(storage, (pool_addr, period_key), &())?; - POOL_VOTES.save(storage, (period_key, pool_addr), &pool_info)?; - pool_info - } - VotedPoolInfoResult::New(pool_info) => { - POOL_PERIODS.save(storage, (pool_addr, period_key), &())?; - POOL_VOTES.save(storage, (period_key, pool_addr), &pool_info)?; - pool_info - } - VotedPoolInfoResult::Unchanged(pool_info) => pool_info, - }; - - Ok(pool_info) -} - -/// Returns pool info at specified period -pub(crate) fn get_pool_info_mut( - storage: &mut dyn Storage, - period: u64, - pool_addr: &str, -) -> StdResult { - let pool_info_result = - if let Some(pool_info) = POOL_VOTES.may_load(storage, (period, pool_addr))? { - VotedPoolInfoResult::Unchanged(pool_info) - } else { - let pool_info_result = - if let Some(prev_period) = fetch_last_pool_period(storage, period, pool_addr)? { - let pool_info = POOL_VOTES.load(storage, (prev_period, pool_addr))?; - VotedPoolInfo { - vxastro_amount: pool_info.vxastro_amount, - ..pool_info - } - } else { - VotedPoolInfo::default() - }; - - VotedPoolInfoResult::New(pool_info_result) - }; - - Ok(pool_info_result) -} - -/// Returns pool info at specified period. -pub(crate) fn get_pool_info( - storage: &dyn Storage, - period: u64, - pool_addr: &str, -) -> StdResult { - let pool_info = if let Some(pool_info) = POOL_VOTES.may_load(storage, (period, pool_addr))? { - pool_info - } else if let Some(prev_period) = fetch_last_pool_period(storage, period, pool_addr)? { - let pool_info = POOL_VOTES.load(storage, (prev_period, pool_addr))?; - VotedPoolInfo { - vxastro_amount: pool_info.vxastro_amount, - ..pool_info - } - } else { - VotedPoolInfo::default() - }; - - Ok(pool_info) -} - -/// Fetches last period for specified pool which has saved result in [`POOL_PERIODS`]. -pub(crate) fn fetch_last_pool_period( - storage: &dyn Storage, - period: u64, - pool_addr: &str, -) -> StdResult> { - let period_opt = POOL_PERIODS - .prefix(pool_addr) - .range( - storage, - None, - Some(Bound::exclusive(period)), - Order::Descending, - ) - .next() - .transpose()? - .map(|(period, _)| period); - Ok(period_opt) -} - -/// Input validation for pools limit. -pub(crate) fn validate_pools_limit(number: u64) -> Result { - if !POOL_NUMBER_LIMIT.contains(&number) { - Err(ContractError::InvalidPoolNumber(number)) - } else { - Ok(number) - } -} - -/// Check if a pool isn't the main pool. Check if a pool is an LP token. -/// In the lite version this no longer validates if a pool is an LP token -/// or that it is registered in the factory. That is because in the lite -/// version we are dealing with multi chain addresses -pub fn validate_pool(config: &ConfigResponse, pool: &str) -> Result<(), ContractError> { - // Voting for the main pool or updating it is prohibited - if let Some(main_pool) = &config.main_pool { - if pool == *main_pool { - return Err(ContractError::MainPoolVoteOrWhitelistingProhibited( - main_pool.to_string(), - )); - } - } - Ok(()) -} - -/// Checks for duplicate items in a slice -pub fn check_duplicated(items: &[T]) -> Result<(), ContractError> { - let mut uniq = HashSet::new(); - if !items.iter().all(|item| uniq.insert(item)) { - return Err(ContractError::DuplicatedPools {}); - } - - Ok(()) -} - -/// Filters pools by network prefixes to enable sending the message to the -/// correct contracts -pub fn group_pools_by_network<'a>( - networks: &'a [NetworkInfo], - gauge_info: &GaugeInfoResponse, -) -> HashMap<&'a NetworkInfo, Vec<(String, Uint128)>> { - networks - .iter() - .map(|network_info| { - let matching_pools: Vec<_> = gauge_info - .pool_alloc_points - .iter() - .filter(|(address, _)| address.starts_with(network_info.address_prefix.as_str())) - .cloned() - .collect(); - - (network_info, matching_pools) - }) - .collect() -} - -/// Finds the prefix by returning all the characters before the first instance -/// of the first instance of "1" as Cosmos addresses are all based on prefix1restofaddress -/// If the prefix could not be determined, an error is returned -pub fn determine_address_prefix(s: &str) -> Result { - let prefix: String = s.chars().take_while(|&c| c != '1').collect(); - if prefix.is_empty() { - Err(ContractError::Std(StdError::GenericErr { - msg: "Invalid prefix".to_string(), - })) - } else { - Ok(prefix) - } -} - -#[test] -fn test_determine_address_prefix() { - // Test that the prefix is determined correctly, format is - // (expected_prefix, address) - let test_addresses = vec![ - ("inj", "inj19aenkaj6qhymmt746av8ck4r8euthq3zmxr2r6"), - ("inj", "inj1z354nkau8f0dukgwctq9mladvdwu6zcj8k4928"), - ( - "neutron", - "neutron1eeyntmsq448c68ez06jsy6h2mtjke5tpuplnwtjfwcdznqmw72kswnlmm0", - ), - ( - "neutron", - "neutron1unc0549k2f0d7mjjyfm94fuz2x53wrx3px0pr55va27grdgmspcqgzfr8p", - ), - ( - "sei", - "sei1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrsgshtdj", - ), - ( - "terra", - "terra15hlvnufpk8a3gcex09djzkhkz3jg9dpqvv6fvgd0ynudtu2z0qlq2fyfaq", - ), - ("terra", "terra174gu7kg8ekk5gsxdma5jlfcedm653tyg6ayppw"), - ("contract", "contract"), - ("contract", "contract1"), - ("contract", "contract1abc"), - ("wasm", "wasm12345"), - ]; - - for (expected_prefix, address) in test_addresses { - let prefix = determine_address_prefix(address).unwrap(); - assert_eq!(expected_prefix, prefix); - } -} diff --git a/contracts/generator_controller_lite/tests/integration.rs b/contracts/generator_controller_lite/tests/integration.rs deleted file mode 100644 index 2104a7ba..00000000 --- a/contracts/generator_controller_lite/tests/integration.rs +++ /dev/null @@ -1,1621 +0,0 @@ -use astroport::asset::AssetInfo; -use astroport::generator::PoolInfoResponse; -use cosmwasm_std::{attr, Addr, Decimal, StdResult, Uint128}; -use cw_multi_test::{App, ContractWrapper, Executor}; -use generator_controller_lite::astroport; -use std::str::FromStr; - -use crate::astroport::asset::PairInfo; -use astroport_governance::generator_controller_lite::{ - ConfigResponse, ExecuteMsg, NetworkInfo, QueryMsg, VOTERS_MAX_LIMIT, -}; -use astroport_governance::utils::{get_lite_period, LITE_VOTING_PERIOD, MAX_LOCK_TIME, WEEK}; -use astroport_tests_lite::{ - controller_helper::ControllerHelper, escrow_helper::MULTIPLIER, mock_app, TerraAppExtension, -}; -use generator_controller_lite::state::TuneInfo; - -#[test] -fn update_configs() { - let mut router = mock_app(); - let owner = Addr::unchecked("owner"); - let helper = ControllerHelper::init(&mut router, &owner, None); - - let config = helper.query_config(&mut router).unwrap(); - assert_eq!(config.kick_voters_limit, None); - - // check if user2 cannot update config - let err = helper - .update_blacklisted_limit(&mut router, "user2", Some(4u32)) - .unwrap_err(); - assert_eq!("Unauthorized", err.root_cause().to_string()); - - // successful update config by owner - helper - .update_blacklisted_limit(&mut router, "owner", Some(4u32)) - .unwrap(); - - let config = helper.query_config(&mut router).unwrap(); - assert_eq!(config.kick_voters_limit, Some(4u32)); -} - -#[test] -fn check_kick_holders_works() { - let mut router = mock_app(); - let owner = Addr::unchecked("owner"); - let helper = ControllerHelper::init(&mut router, &owner, None); - let pools = vec![ - helper - .create_pool_with_tokens(&mut router, "FOO", "BAR") - .unwrap(), - helper - .create_pool_with_tokens(&mut router, "BAR", "ADN") - .unwrap(), - ]; - - let err = helper - .vote(&mut router, "user1", vec![(pools[0].as_str(), 1000)]) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "You can't vote with zero voting power" - ); - - helper.escrow_helper.mint_xastro(&mut router, "owner", 100); - helper.escrow_helper.mint_xastro(&mut router, "user1", 100); - // Create short lock - helper - .escrow_helper - .create_lock(&mut router, "user1", WEEK, 100f32) - .unwrap(); - - helper - .update_whitelist( - &mut router, - "owner", - Some(pools.iter().map(|el| el.to_string()).collect()), - None, - ) - .unwrap(); - - // Votes from user1 - helper - .vote(&mut router, "user1", vec![(pools[0].as_str(), 1000)]) - .unwrap(); - - helper.escrow_helper.mint_xastro(&mut router, "user2", 100); - helper - .escrow_helper - .create_lock(&mut router, "user2", 10 * WEEK, 100f32) - .unwrap(); - - // Votes from user2 - helper - .vote( - &mut router, - "user2", - vec![(pools[0].as_str(), 3000), (pools[1].as_str(), 7000)], - ) - .unwrap(); - - let ve_power = helper - .escrow_helper - .query_user_emissions_vp(&mut router, "user2") - .unwrap(); - let user_info = helper.query_user_info(&mut router, "user2").unwrap(); - assert_eq!( - get_lite_period(router.block_info().time.seconds()).unwrap(), - user_info.vote_period.unwrap() - ); - assert_eq!( - ve_power, - user_info.voting_power.u128() as f32 / MULTIPLIER as f32 - ); - let resp_votes = user_info - .votes - .into_iter() - .map(|(addr, bps)| (addr, bps.into())) - .collect::>(); - assert_eq!( - vec![(pools[0].to_string(), 3000), (pools[1].to_string(), 7000)], - resp_votes - ); - - // Add user2 to the blacklist - let res = helper - .escrow_helper - .update_blacklist(&mut router, Some(vec!["user2".to_string()]), None) - .unwrap(); - assert_eq!( - res.events[1].attributes[1], - attr("action", "update_blacklist") - ); - - // Let's take the period for which the vote was applied. - let current_period = router.block_period() + 1u64; - - // Get pools info before kick holder - let res = helper - .query_voted_pool_info_at_period(&mut router, pools[0].as_str(), current_period) - .unwrap(); - assert_eq!(Uint128::new(0), res.slope); - assert_eq!(Uint128::new(40_000_000), res.vxastro_amount); - - let res = helper - .query_voted_pool_info_at_period(&mut router, pools[1].as_str(), current_period) - .unwrap(); - assert_eq!(Uint128::new(0), res.slope); - assert_eq!(Uint128::new(70_000_000), res.vxastro_amount); - - // check if blacklisted voters limit exceeded for kick operation - let err = helper - .kick_holders( - &mut router, - "user1", - vec!["user2".to_string(); (VOTERS_MAX_LIMIT + 1) as usize], - ) - .unwrap_err(); - assert_eq!( - "Exceeded voters limit for kick blacklisted/unlocked voters operation!", - err.root_cause().to_string() - ); - - // Removes votes for user2 - helper - .kick_holders(&mut router, "user1", vec!["user2".to_string()]) - .unwrap(); - - let ve_power = helper - .escrow_helper - .query_user_vp(&mut router, "user2") - .unwrap(); - - let user_info = helper.query_user_info(&mut router, "user2").unwrap(); - assert_eq!( - get_lite_period(router.block_info().time.seconds()).unwrap(), - user_info.vote_period.unwrap() - ); - assert_eq!( - ve_power, - user_info.voting_power.u128() as f32 / MULTIPLIER as f32 - ); - assert_eq!(user_info.votes, vec![]); - - // Get pool info after kick holder - let res = helper - .query_voted_pool_info_at_period(&mut router, pools[0].as_str(), current_period) - .unwrap(); - assert_eq!(Uint128::new(0), res.slope); - assert_eq!(Uint128::new(10_000_000), res.vxastro_amount); - - let res1 = helper - .query_voted_pool_info_at_period(&mut router, pools[1].as_str(), current_period) - .unwrap(); - assert_eq!(Uint128::new(0), res1.slope); - assert_eq!(Uint128::new(0), res1.vxastro_amount); -} - -#[test] -fn check_kick_unlocked_holders_works() { - let mut router = mock_app(); - let owner = Addr::unchecked("owner"); - let helper = ControllerHelper::init(&mut router, &owner, None); - let pools = vec![ - helper - .create_pool_with_tokens(&mut router, "FOO", "BAR") - .unwrap(), - helper - .create_pool_with_tokens(&mut router, "BAR", "ADN") - .unwrap(), - ]; - - let err = helper - .vote(&mut router, "user1", vec![(pools[0].as_str(), 1000)]) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "You can't vote with zero voting power" - ); - - helper.escrow_helper.mint_xastro(&mut router, "owner", 100); - helper.escrow_helper.mint_xastro(&mut router, "user1", 100); - // Create short lock - helper - .escrow_helper - .create_lock(&mut router, "user1", WEEK, 100f32) - .unwrap(); - - helper - .update_whitelist( - &mut router, - "owner", - Some(pools.iter().map(|el| el.to_string()).collect()), - None, - ) - .unwrap(); - - // Votes from user1 - helper - .vote(&mut router, "user1", vec![(pools[0].as_str(), 1000)]) - .unwrap(); - - helper.escrow_helper.mint_xastro(&mut router, "user2", 100); - helper - .escrow_helper - .create_lock(&mut router, "user2", 10 * WEEK, 100f32) - .unwrap(); - - // Votes from user2 - helper - .vote( - &mut router, - "user2", - vec![(pools[0].as_str(), 3000), (pools[1].as_str(), 7000)], - ) - .unwrap(); - - let ve_power = helper - .escrow_helper - .query_user_emissions_vp(&mut router, "user2") - .unwrap(); - - let user_info = helper.query_user_info(&mut router, "user2").unwrap(); - assert_eq!( - get_lite_period(router.block_info().time.seconds()).unwrap(), - user_info.vote_period.unwrap() - ); - assert_eq!( - ve_power, - user_info.voting_power.u128() as f32 / MULTIPLIER as f32 - ); - - let resp_votes = user_info - .votes - .into_iter() - .map(|(addr, bps)| (addr, bps.into())) - .collect::>(); - assert_eq!( - vec![(pools[0].to_string(), 3000), (pools[1].to_string(), 7000)], - resp_votes - ); - - // Let's take the period for which the vote was applied. - let current_period = router.block_period() + 1u64; - // Get pools info before kick holder - let res = helper - .query_voted_pool_info_at_period(&mut router, pools[0].as_str(), current_period) - .unwrap(); - - // We should see 40_000_000 as the vxASTRO amount here because: - // User1 voted with 10% of the 100_000_000 total voting power - // User2 voted with 30% of the 100_000_000 total voting power - // Total voting power is 40_000_000 - assert_eq!(Uint128::new(0), res.slope); - assert_eq!(Uint128::new(40_000_000), res.vxastro_amount); - - let res = helper - .query_voted_pool_info_at_period(&mut router, pools[1].as_str(), current_period) - .unwrap(); - assert_eq!(Uint128::new(0), res.slope); - assert_eq!(Uint128::new(70_000_000), res.vxastro_amount); - - // Unlock user2, which results in an immediate kick - helper.escrow_helper.unlock(&mut router, "user2").unwrap(); - - // check if blacklisted voters limit exceeded for kick operation - let err = helper - .kick_unlocked_holders( - &mut router, - "user1", - vec!["user2".to_string(); (VOTERS_MAX_LIMIT + 1) as usize], - ) - .unwrap_err(); - assert_eq!( - "Exceeded voters limit for kick blacklisted/unlocked voters operation!", - err.root_cause().to_string() - ); - - // Removes votes for user2 - // Not strictly needed as the user is kicked immediately when unlock starts - helper - .kick_unlocked_holders(&mut router, "user1", vec!["user2".to_string()]) - .unwrap(); - - let ve_power = helper - .escrow_helper - .query_user_vp(&mut router, "user2") - .unwrap(); - - let user_info = helper.query_user_info(&mut router, "user2").unwrap(); - assert_eq!( - get_lite_period(router.block_info().time.seconds()).unwrap(), - user_info.vote_period.unwrap() - ); - assert_eq!( - ve_power, - user_info.voting_power.u128() as f32 / MULTIPLIER as f32 - ); - // All votes should be removed for this user - assert_eq!(user_info.votes, vec![]); - - // Get pool info after kick holder - // We should see 10_000_000 as the vxASTRO amount here because: - // User1 voted with 10% of the 100_000_000 total voting power - // User2 voted with 30% of the 100_000_000 total voting power - // User2 was kicked removing the 30% of the 100_000_000 total voting power - // Total voting power is now 10_000_000 - let res = helper - .query_voted_pool_info_at_period(&mut router, pools[0].as_str(), current_period) - .unwrap(); - assert_eq!(Uint128::new(0), res.slope); - assert_eq!(Uint128::new(10_000_000), res.vxastro_amount); - - let res1 = helper - .query_voted_pool_info_at_period(&mut router, pools[1].as_str(), current_period) - .unwrap(); - assert_eq!(Uint128::new(0), res1.slope); - assert_eq!(Uint128::new(0), res1.vxastro_amount); -} - -#[test] -fn check_kick_unlocked_outpost_holders_works() { - let mut router = mock_app(); - let owner = Addr::unchecked("owner"); - let helper = ControllerHelper::init(&mut router, &owner, Some("hub".to_string())); - let pools = vec![ - helper - .create_pool_with_tokens(&mut router, "FOO", "BAR") - .unwrap(), - helper - .create_pool_with_tokens(&mut router, "BAR", "ADN") - .unwrap(), - ]; - - let voter1 = "outpost_voter1".to_string(); - let voter1_power = Uint128::from(50_000_000u64); - let voter2 = "outpost_voter2".to_string(); - let voter2_power = Uint128::from(100_000_000u64); - - helper - .update_whitelist( - &mut router, - "owner", - Some(pools.iter().map(|el| el.to_string()).collect()), - None, - ) - .unwrap(); - - helper - .outpost_vote( - &mut router, - "hub", - voter1.clone(), - voter1_power, - vec![(pools[0].as_str(), 8000), (pools[1].as_str(), 1000)], - ) - .unwrap(); - - // Votes from user2 - helper - .outpost_vote( - &mut router, - "hub", - voter2, - voter2_power, - vec![(pools[0].as_str(), 2000)], - ) - .unwrap(); - - let user_info = helper - .query_user_info(&mut router, voter1.as_ref()) - .unwrap(); - assert_eq!( - get_lite_period(router.block_info().time.seconds()).unwrap(), - user_info.vote_period.unwrap() - ); - - let resp_votes = user_info - .votes - .into_iter() - .map(|(addr, bps)| (addr, bps.into())) - .collect::>(); - assert_eq!( - vec![(pools[0].to_string(), 8000), (pools[1].to_string(), 1000)], - resp_votes - ); - - // Let's take the period for which the vote was applied. - let current_period = router.block_period() + 1u64; - - // Get pools info before kick holder - let res = helper - .query_voted_pool_info_at_period(&mut router, pools[0].as_str(), current_period) - .unwrap(); - assert_eq!(Uint128::new(0), res.slope); - assert_eq!(Uint128::new(60_000_000), res.vxastro_amount); - - let res = helper - .query_voted_pool_info_at_period(&mut router, pools[1].as_str(), current_period) - .unwrap(); - assert_eq!(Uint128::new(0), res.slope); - assert_eq!(Uint128::new(5_000_000), res.vxastro_amount); - - // Check that only Hub can call this - let err = helper - .kick_unlocked_outpost_holders(&mut router, "not_hub", voter1.to_string()) - .unwrap_err(); - assert_eq!(err.root_cause().to_string(), "Unauthorized"); - - helper - .kick_unlocked_outpost_holders(&mut router, "hub", voter1.to_string()) - .unwrap(); - - let user_info = helper - .query_user_info(&mut router, voter1.as_ref()) - .unwrap(); - assert_eq!( - get_lite_period(router.block_info().time.seconds()).unwrap(), - user_info.vote_period.unwrap() - ); - - // Get pool info after kick holder - let res = helper - .query_voted_pool_info_at_period(&mut router, pools[0].as_str(), current_period) - .unwrap(); - assert_eq!(Uint128::new(0), res.slope); - assert_eq!(Uint128::new(20_000_000), res.vxastro_amount); - - // Since Outpost user 1 was kicked, their voting power should be removed for pools[1] - let res1 = helper - .query_voted_pool_info_at_period(&mut router, pools[1].as_str(), current_period) - .unwrap(); - assert_eq!(Uint128::new(0), res1.slope); - assert_eq!(Uint128::new(0), res1.vxastro_amount); -} - -#[test] -fn check_kick_unlocked_outpost_holders_unauthorized() { - let mut router = mock_app(); - let owner = Addr::unchecked("owner"); - let helper = ControllerHelper::init(&mut router, &owner, Some("hub".to_string())); - let pools = vec![ - helper - .create_pool_with_tokens(&mut router, "FOO", "BAR") - .unwrap(), - helper - .create_pool_with_tokens(&mut router, "BAR", "ADN") - .unwrap(), - ]; - - let voter1 = "outpost_voter1".to_string(); - let voter1_power = Uint128::from(50_000_000u64); - - helper - .update_whitelist( - &mut router, - "owner", - Some(pools.iter().map(|el| el.to_string()).collect()), - None, - ) - .unwrap(); - - helper - .outpost_vote( - &mut router, - "hub", - voter1.clone(), - voter1_power, - vec![(pools[0].as_str(), 8000), (pools[1].as_str(), 1000)], - ) - .unwrap(); - - // Check that only Hub can call this - let err = helper - .kick_unlocked_outpost_holders(&mut router, "not_hub", voter1) - .unwrap_err(); - assert_eq!(err.root_cause().to_string(), "Unauthorized"); -} - -#[test] -fn check_vote_works() { - let mut router = mock_app(); - let owner = Addr::unchecked("owner"); - let helper = ControllerHelper::init(&mut router, &owner, None); - let pools = vec![ - helper - .create_pool_with_tokens(&mut router, "FOO", "BAR") - .unwrap(), - helper - .create_pool_with_tokens(&mut router, "BAR", "ADN") - .unwrap(), - ]; - - let err = helper - .vote(&mut router, "user1", vec![(pools[0].as_str(), 1000)]) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "You can't vote with zero voting power" - ); - - helper.escrow_helper.mint_xastro(&mut router, "owner", 100); - helper.escrow_helper.mint_xastro(&mut router, "user1", 100); - // Create short lock - helper - .escrow_helper - .create_lock(&mut router, "user1", WEEK, 100f32) - .unwrap(); - let err = helper - .vote(&mut router, "user1", vec![(pools[0].as_str(), 1000)]) - .unwrap_err(); - assert_eq!("Whitelist cannot be empty!", err.root_cause().to_string()); - - let err = helper - .update_whitelist(&mut router, "user1", Some(vec![pools[0].to_string()]), None) - .unwrap_err(); - assert_eq!("Unauthorized", err.root_cause().to_string()); - - helper - .update_whitelist( - &mut router, - "owner", - Some(pools.iter().map(|el| el.to_string()).collect()), - None, - ) - .unwrap(); - - helper - .vote(&mut router, "user1", vec![(pools[0].as_str(), 1000)]) - .unwrap(); - - helper.escrow_helper.mint_xastro(&mut router, "user2", 100); - helper - .escrow_helper - .create_lock(&mut router, "user2", 10 * WEEK, 100f32) - .unwrap(); - - // Bps is > 10000 - let err = helper - .vote(&mut router, "user2", vec![(pools[1].as_str(), 10001)]) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Basic points conversion error. 10001 > 10000" - ); - - // Bps sum is > 10000 - let err = helper - .vote( - &mut router, - "user2", - vec![(pools[0].as_str(), 3000), (pools[1].as_str(), 8000)], - ) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Basic points sum exceeds limit" - ); - - // Duplicated pools - let err = helper - .vote( - &mut router, - "user2", - vec![(pools[0].as_str(), 3000), (pools[0].as_str(), 7000)], - ) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Votes contain duplicated pool addresses" - ); - - // Valid votes - helper - .vote( - &mut router, - "user2", - vec![(pools[0].as_str(), 3000), (pools[1].as_str(), 7000)], - ) - .unwrap(); - - let err = helper - .vote( - &mut router, - "user2", - vec![(pools[0].as_str(), 7000), (pools[1].as_str(), 3000)], - ) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "You can only run this action once in a voting period" - ); - - let ve_power = helper - .escrow_helper - .query_user_emissions_vp(&mut router, "user2") - .unwrap(); - let user_info = helper.query_user_info(&mut router, "user2").unwrap(); - assert_eq!( - get_lite_period(router.block_info().time.seconds()).unwrap(), - user_info.vote_period.unwrap() - ); - assert_eq!( - ve_power, - user_info.voting_power.u128() as f32 / MULTIPLIER as f32 - ); - let resp_votes = user_info - .votes - .into_iter() - .map(|(addr, bps)| (addr, bps.into())) - .collect::>(); - assert_eq!( - vec![(pools[0].to_string(), 3000), (pools[1].to_string(), 7000)], - resp_votes - ); - - router.next_block(LITE_VOTING_PERIOD); - // In the next period the user will be able to vote again - helper - .vote( - &mut router, - "user2", - vec![(pools[0].as_str(), 500), (pools[1].as_str(), 9500)], - ) - .unwrap(); -} - -#[test] -fn check_outpost_vote_no_hub() { - let mut router = mock_app(); - let owner = Addr::unchecked("owner"); - let helper = ControllerHelper::init(&mut router, &owner, None); - let pools = vec![ - helper - .create_pool_with_tokens(&mut router, "FOO", "BAR") - .unwrap(), - helper - .create_pool_with_tokens(&mut router, "BAR", "ADN") - .unwrap(), - ]; - - let voter1 = "voter1".to_string(); - let voter1_power = Uint128::from(100_000u64); - - let err = helper - .outpost_vote( - &mut router, - "not_hub", - voter1, - voter1_power, - vec![(pools[0].as_str(), 1000)], - ) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Sender is not the Hub installed" - ); -} - -#[test] -fn check_outpost_vote_unauthorised() { - let mut router = mock_app(); - let owner = Addr::unchecked("owner"); - let helper = ControllerHelper::init(&mut router, &owner, Some("hub".to_string())); - let pools = vec![ - helper - .create_pool_with_tokens(&mut router, "FOO", "BAR") - .unwrap(), - helper - .create_pool_with_tokens(&mut router, "BAR", "ADN") - .unwrap(), - ]; - - let voter1 = "voter1".to_string(); - let voter1_power = Uint128::from(100_000u64); - - let err = helper - .outpost_vote( - &mut router, - "not_hub", - voter1, - voter1_power, - vec![(pools[0].as_str(), 1000)], - ) - .unwrap_err(); - assert_eq!(err.root_cause().to_string(), "Unauthorized"); -} - -#[test] -fn check_outpost_vote_works() { - let mut router = mock_app(); - let owner = Addr::unchecked("owner"); - let helper = ControllerHelper::init(&mut router, &owner, Some("hub".to_string())); - let pools = vec![ - helper - .create_pool_with_tokens(&mut router, "FOO", "BAR") - .unwrap(), - helper - .create_pool_with_tokens(&mut router, "BAR", "ADN") - .unwrap(), - ]; - - let voter1 = "voter1".to_string(); - let voter1_power = Uint128::from(100_000u64); - - let err = helper - .outpost_vote( - &mut router, - "hub", - voter1.clone(), - Uint128::zero(), - vec![(pools[0].as_str(), 1000)], - ) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "You can't vote with zero voting power" - ); - - let err = helper - .outpost_vote( - &mut router, - "hub", - voter1.clone(), - voter1_power, - vec![(pools[0].as_str(), 1000)], - ) - .unwrap_err(); - assert_eq!(err.root_cause().to_string(), "Whitelist cannot be empty!"); - - helper - .update_whitelist( - &mut router, - "owner", - Some(pools.iter().map(|el| el.to_string()).collect()), - None, - ) - .unwrap(); - - // Bps is > 10000 - let err = helper - .outpost_vote( - &mut router, - "hub", - voter1.clone(), - voter1_power, - vec![(pools[0].as_str(), 10001)], - ) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Basic points conversion error. 10001 > 10000" - ); - - let err = helper - .outpost_vote( - &mut router, - "hub", - voter1.clone(), - voter1_power, - vec![(pools[0].as_str(), 3000), (pools[1].as_str(), 8000)], - ) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Basic points sum exceeds limit" - ); - - let err = helper - .outpost_vote( - &mut router, - "hub", - voter1.clone(), - voter1_power, - vec![(pools[0].as_str(), 3000), (pools[0].as_str(), 7000)], - ) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Votes contain duplicated pool addresses" - ); - - // Valid votes - helper - .outpost_vote( - &mut router, - "hub", - voter1.clone(), - voter1_power, - vec![(pools[0].as_str(), 3000), (pools[1].as_str(), 7000)], - ) - .unwrap(); - - let err = helper - .outpost_vote( - &mut router, - "hub", - voter1.clone(), - voter1_power, - vec![(pools[0].as_str(), 3000), (pools[1].as_str(), 7000)], - ) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "You can only run this action once in a voting period" - ); - - let user_info = helper - .query_user_info(&mut router, voter1.as_ref()) - .unwrap(); - assert_eq!( - get_lite_period(router.block_info().time.seconds()).unwrap(), - user_info.vote_period.unwrap() - ); - - let resp_votes = user_info - .votes - .into_iter() - .map(|(addr, bps)| (addr, bps.into())) - .collect::>(); - assert_eq!( - vec![(pools[0].to_string(), 3000), (pools[1].to_string(), 7000)], - resp_votes - ); - - router.next_block(LITE_VOTING_PERIOD); - // In the next period the user will be able to vote again - helper - .outpost_vote( - &mut router, - "hub", - voter1.clone(), - voter1_power, - vec![(pools[0].as_str(), 3000), (pools[1].as_str(), 7000)], - ) - .unwrap(); -} - -fn create_unregistered_pool( - router: &mut App, - helper: &mut ControllerHelper, -) -> StdResult { - let pair_contract = Box::new( - ContractWrapper::new_with_empty( - astroport_pair::contract::execute, - astroport_pair::contract::instantiate, - astroport_pair::contract::query, - ) - .with_reply_empty(astroport_pair::contract::reply), - ); - - let pair_code_id = router.store_code(pair_contract); - - let test_token1 = helper.init_cw20_token(router, "TST").unwrap(); - let test_token2 = helper.init_cw20_token(router, "TSB").unwrap(); - - let pair_addr = router - .instantiate_contract( - pair_code_id, - Addr::unchecked("owner"), - &astroport::pair::InstantiateMsg { - asset_infos: vec![ - AssetInfo::Token { - contract_addr: test_token1, - }, - AssetInfo::Token { - contract_addr: test_token2, - }, - ], - token_code_id: 1, - factory_addr: helper.factory.to_string(), - init_params: None, - }, - &[], - "Unregistered pair".to_string(), - None, - ) - .unwrap(); - - let res: PairInfo = router - .wrap() - .query_wasm_smart(pair_addr, &astroport::pair::QueryMsg::Pair {})?; - - Ok(res) -} - -#[test] -fn check_tuning() { - let mut router = mock_app(); - let owner = "owner"; - let owner_addr = Addr::unchecked(owner); - let mut helper = ControllerHelper::init(&mut router, &owner_addr, None); - let user1 = "user1"; - let user2 = "user2"; - let user3 = "user3"; - let ve_locks = vec![(user1, 10), (user2, 5), (user3, 50)]; - - let pools = vec![ - helper - .create_pool_with_tokens(&mut router, "FOO", "BAR") - .unwrap(), - helper - .create_pool_with_tokens(&mut router, "BAR", "ADN") - .unwrap(), - helper - .create_pool_with_tokens(&mut router, "FOO", "ADN") - .unwrap(), - ]; - - helper - .update_whitelist( - &mut router, - "owner", - Some(pools.iter().map(|el| el.to_string()).collect()), - None, - ) - .unwrap(); - - let err = helper - .update_whitelist(&mut router, "owner", Some(vec![pools[0].to_string()]), None) - .unwrap_err(); - assert_eq!("Generic error: The resulting whitelist contains duplicated pools. It's either provided 'add' list contains duplicated pools or some of the added pools are already whitelisted.", err.root_cause().to_string()); - - let config_resp = helper.query_config(&mut router).unwrap(); - assert_eq!(config_resp.whitelisted_pools, pools); - - for (user, duration) in ve_locks { - helper.escrow_helper.mint_xastro(&mut router, user, 1000); - helper - .escrow_helper - .create_lock(&mut router, user, duration * WEEK, 100f32) - .unwrap(); - } - - let res = create_unregistered_pool(&mut router, &mut helper).unwrap(); - let err = helper - .vote( - &mut router, - user1, - vec![ - (pools[0].as_str(), 5000), - (pools[1].as_str(), 4000), - (res.liquidity_token.as_str(), 1000), - ], - ) - .unwrap_err(); - assert_eq!( - "Pool is not whitelisted: wasm1contract25", - err.root_cause().to_string() - ); - - let err = helper - .vote( - &mut router, - user1, - vec![ - (pools[0].as_str(), 5000), - (pools[1].as_str(), 2000), - (pools[1].as_str(), 2000), - ], - ) - .unwrap_err(); - assert_eq!( - "Votes contain duplicated pool addresses", - err.root_cause().to_string() - ); - - helper - .vote( - &mut router, - user1, - vec![(pools[0].as_str(), 5000), (pools[1].as_str(), 5000)], - ) - .unwrap(); - - helper - .vote( - &mut router, - user2, - vec![ - (pools[0].as_str(), 5000), - (pools[1].as_str(), 2000), - (pools[2].as_str(), 3000), - ], - ) - .unwrap(); - helper - .vote( - &mut router, - user3, - vec![ - (pools[0].as_str(), 2000), - (pools[1].as_str(), 3000), - (pools[2].as_str(), 5000), - ], - ) - .unwrap(); - - // The contract was just created so we need to wait for the next period - let err = helper.tune(&mut router).unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "You can only run this action once in a voting period" - ); - - // Periods are two weeks, so this should fail as well - router.next_block(WEEK); - let err = helper.tune(&mut router).unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "You can only run this action once in a voting period" - ); - - // This should now be the next period - router.next_block(WEEK); - helper.tune(&mut router).unwrap(); - - let resp: TuneInfo = router - .wrap() - .query_wasm_smart(helper.controller.clone(), &QueryMsg::TuneInfo {}) - .unwrap(); - assert_eq!(resp.tune_period, router.block_period()); - assert_eq!(resp.pool_alloc_points.len(), pools.len()); - let total_apoints: u128 = resp - .pool_alloc_points - .iter() - .cloned() - .map(|(_, apoints)| apoints.u128()) - .sum(); - assert_eq!(total_apoints, 300_000_000); - - router.next_block(2 * WEEK); - // Reduce pools limit 5 -> 2 (5 is initial limit in integration tests) - let limit = 2u64; - let err = router - .execute_contract( - Addr::unchecked("somebody"), - helper.controller.clone(), - &ExecuteMsg::ChangePoolsLimit { limit }, - &[], - ) - .unwrap_err(); - assert_eq!(err.root_cause().to_string(), "Unauthorized"); - - router - .execute_contract( - owner_addr.clone(), - helper.controller.clone(), - &ExecuteMsg::ChangePoolsLimit { limit }, - &[], - ) - .unwrap(); - - let err = router - .execute_contract( - owner_addr.clone(), - helper.controller.clone(), - &ExecuteMsg::ChangePoolsLimit { limit: 101 }, - &[], - ) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Invalid pool number: 101. Must be within [2, 100] range" - ); - - helper.tune(&mut router).unwrap(); - - let resp: TuneInfo = router - .wrap() - .query_wasm_smart(helper.controller.clone(), &QueryMsg::TuneInfo {}) - .unwrap(); - assert_eq!(resp.tune_period, router.block_period()); - assert_eq!(resp.pool_alloc_points.len(), limit as usize); - let total_apoints: u128 = resp - .pool_alloc_points - .iter() - .cloned() - .map(|(_, apoints)| apoints.u128()) - .sum(); - assert_eq!(total_apoints, 220_000_000); - - // Check alloc points are properly set in generator - for (pool_addr, apoints) in resp.pool_alloc_points { - let resp: PoolInfoResponse = router - .wrap() - .query_wasm_smart( - helper.generator.clone(), - &astroport::generator::QueryMsg::PoolInfo { - lp_token: pool_addr.to_string(), - }, - ) - .unwrap(); - assert_eq!(apoints, resp.alloc_point) - } - - // Check the last pool did not receive alloc points - let generator_resp: PoolInfoResponse = router - .wrap() - .query_wasm_smart( - helper.generator.clone(), - &astroport::generator::QueryMsg::PoolInfo { - lp_token: pools[2].to_string(), - }, - ) - .unwrap(); - assert_eq!(generator_resp.alloc_point.u128(), 0) -} - -#[test] -fn check_bad_pools_filtering() { - let mut router = mock_app(); - let owner = "owner"; - let owner_addr = Addr::unchecked(owner); - let helper = ControllerHelper::init(&mut router, &owner_addr, None); - let user = "user1"; - - let foo_token = helper.init_cw20_token(&mut router, "FOO").unwrap(); - let bar_token = helper.init_cw20_token(&mut router, "BAR").unwrap(); - let adn_token = helper.init_cw20_token(&mut router, "ADN").unwrap(); - let pools = vec![ - helper - .create_pool(&mut router, &foo_token, &bar_token) - .unwrap(), - helper - .create_pool(&mut router, &foo_token, &adn_token) - .unwrap(), - helper - .create_pool(&mut router, &bar_token, &adn_token) - .unwrap(), - ]; - - helper.escrow_helper.mint_xastro(&mut router, user, 1000); - helper - .escrow_helper - .create_lock(&mut router, user, 10 * WEEK, 100f32) - .unwrap(); - - // We must be able to add any pool to the whitelist as we can't validate - // pools on other chains - let result = helper.update_whitelist( - &mut router, - "owner", - Some(vec![("random_pool".to_string())]), - None, - ); - assert!(result.is_ok()); - - helper - .update_whitelist( - &mut router, - "owner", - Some(pools.iter().map(|el| el.to_string()).collect()), - None, - ) - .unwrap(); - - helper - .vote(&mut router, user, vec![(pools[0].as_str(), 5000)]) - .unwrap(); - - router.next_block(2 * WEEK); - - helper.tune(&mut router).unwrap(); - let resp: TuneInfo = router - .wrap() - .query_wasm_smart(helper.controller.clone(), &QueryMsg::TuneInfo {}) - .unwrap(); - // There was only one valid pool - assert_eq!(resp.pool_alloc_points.len(), 1); - - router.next_block(2 * WEEK); - - // Deregister first pair - let asset_infos = vec![ - AssetInfo::Token { - contract_addr: foo_token.clone(), - }, - AssetInfo::Token { - contract_addr: bar_token.clone(), - }, - ]; - router - .execute_contract( - owner_addr.clone(), - helper.factory.clone(), - &astroport::factory::ExecuteMsg::Deregister { - asset_infos: asset_infos.to_vec(), - }, - &[], - ) - .unwrap(); - - // We can vote for deregistered pool as we can't validate the information - // from other chains - let result = helper.vote(&mut router, user, vec![(pools[0].as_str(), 10000)]); - assert!(result.is_ok()); - - // Tune should fail as the pair is not registered in the generator - let err = helper.tune(&mut router).unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Generic error: The pair is not registered: wasm1contract10-wasm1contract11" - ); - - router.next_block(2 * WEEK); - - // Blocking FOO token so pair[0] and pair[1] become blocked as well - let foo_asset_info = AssetInfo::Token { - contract_addr: foo_token.clone(), - }; - router - .execute_contract( - owner_addr.clone(), - helper.generator.clone(), - &astroport::generator::ExecuteMsg::UpdateBlockedTokenslist { - add: Some(vec![foo_asset_info]), - remove: None, - }, - &[], - ) - .unwrap(); - - // Voting for 2 valid pools - helper - .vote( - &mut router, - user, - vec![(pools[1].as_str(), 1000), (pools[2].as_str(), 8000)], - ) - .unwrap(); - - router.next_block(WEEK); - // Tune should fail as we have a token blocked in the generator - let err = helper.tune(&mut router).unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Generic error: Token wasm1contract10 is blocked!" - ); - - let resp: TuneInfo = router - .wrap() - .query_wasm_smart(helper.controller.clone(), &QueryMsg::TuneInfo {}) - .unwrap(); - // Only one pool is eligible to receive alloc points - assert_eq!(resp.pool_alloc_points.len(), 1); - let total_apoints: u128 = resp - .pool_alloc_points - .iter() - .cloned() - .map(|(_, apoints)| apoints.u128()) - .sum(); - assert_eq!(total_apoints, 50_000_000) -} - -#[test] -fn check_update_owner() { - let mut app = mock_app(); - let owner = Addr::unchecked("owner"); - let helper = ControllerHelper::init(&mut app, &owner, None); - - let new_owner = String::from("new_owner"); - - // New owner - let msg = ExecuteMsg::ProposeNewOwner { - new_owner: new_owner.clone(), - expires_in: 100, // seconds - }; - - // Unauthed check - let err = app - .execute_contract( - Addr::unchecked("not_owner"), - helper.controller.clone(), - &msg, - &[], - ) - .unwrap_err(); - assert_eq!(err.root_cause().to_string(), "Generic error: Unauthorized"); - - // Claim before proposal - let err = app - .execute_contract( - Addr::unchecked(new_owner.clone()), - helper.controller.clone(), - &ExecuteMsg::ClaimOwnership {}, - &[], - ) - .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Generic error: Ownership proposal not found" - ); - - // Propose new owner - app.execute_contract( - Addr::unchecked("owner"), - helper.controller.clone(), - &msg, - &[], - ) - .unwrap(); - - // Claim from invalid addr - let err = app - .execute_contract( - Addr::unchecked("invalid_addr"), - helper.controller.clone(), - &ExecuteMsg::ClaimOwnership {}, - &[], - ) - .unwrap_err(); - assert_eq!(err.root_cause().to_string(), "Generic error: Unauthorized"); - - // Claim ownership - app.execute_contract( - Addr::unchecked(new_owner.clone()), - helper.controller.clone(), - &ExecuteMsg::ClaimOwnership {}, - &[], - ) - .unwrap(); - - // Let's query the contract state - let msg = QueryMsg::Config {}; - let res: ConfigResponse = app - .wrap() - .query_wasm_smart(&helper.controller, &msg) - .unwrap(); - - assert_eq!(res.owner, new_owner) -} - -#[test] -fn check_main_pool() { - let mut router = mock_app(); - let owner_addr = Addr::unchecked("owner"); - let helper = ControllerHelper::init(&mut router, &owner_addr, None); - let pools = vec![ - helper - .create_pool_with_tokens(&mut router, "FOO", "BAR") - .unwrap(), - helper - .create_pool_with_tokens(&mut router, "BAR", "ADN") - .unwrap(), - helper - .create_pool_with_tokens(&mut router, "FOO", "ADN") - .unwrap(), - ]; - - helper.escrow_helper.mint_xastro(&mut router, "owner", 100); - - for user in ["user1", "user2"] { - helper.escrow_helper.mint_xastro(&mut router, user, 100); - helper - .escrow_helper - .create_lock(&mut router, user, MAX_LOCK_TIME, 100f32) - .unwrap(); - } - - helper - .update_whitelist( - &mut router, - "owner", - Some(pools.iter().map(|el| el.to_string()).collect()), - None, - ) - .unwrap(); - - helper - .vote( - &mut router, - "user1", - vec![ - (pools[0].as_str(), 1000), - (pools[1].as_str(), 5000), - (pools[2].as_str(), 4000), - ], - ) - .unwrap(); - let block_period = router.block_period(); - let main_pool_info = helper - .query_voted_pool_info_at_period(&mut router, pools[0].as_str(), block_period + 2) - .unwrap(); - assert_eq!(main_pool_info.vxastro_amount.u128(), 10_000_000); - - let err = helper - .update_main_pool( - &mut router, - "owner", - Some(&pools[0]), - Some(Decimal::zero()), - false, - ) - .unwrap_err(); - assert_eq!( - &err.root_cause().to_string(), - "main_pool_min_alloc should be more than 0 and less than 1" - ); - let err = helper - .update_main_pool( - &mut router, - "owner", - Some(&pools[0]), - Some(Decimal::one()), - false, - ) - .unwrap_err(); - assert_eq!( - &err.root_cause().to_string(), - "main_pool_min_alloc should be more than 0 and less than 1" - ); - helper - .update_main_pool( - &mut router, - "owner", - Some(&pools[0]), - Decimal::from_str("0.3").ok(), - false, - ) - .unwrap(); - - // From now users can't vote for the main pool - let err = helper - .vote( - &mut router, - "user2", - vec![(pools[0].as_str(), 1000), (pools[1].as_str(), 9000)], - ) - .unwrap_err(); - assert_eq!( - &err.root_cause().to_string(), - "wasm1contract13 is the main pool. Voting or whitelisting the main pool is prohibited." - ); - - router - .execute_contract( - owner_addr.clone(), - helper.controller.clone(), - &ExecuteMsg::ChangePoolsLimit { limit: 2 }, - &[], - ) - .unwrap(); - - router.next_block(2 * WEEK); - helper.tune(&mut router).unwrap(); - - let resp: TuneInfo = router - .wrap() - .query_wasm_smart(helper.controller.clone(), &QueryMsg::TuneInfo {}) - .unwrap(); - // 2 (limit) + 1 (main pool) - assert_eq!(resp.pool_alloc_points.len(), 3_usize); - let total_apoints: Uint128 = resp - .pool_alloc_points - .iter() - .map(|(_, apoints)| apoints) - .sum(); - assert_eq!(total_apoints.u128(), 128571428); - let main_pool_contribution = resp - .pool_alloc_points - .iter() - .find(|(pool, _)| pool == &pools[0]); - assert_eq!( - main_pool_contribution.unwrap().1, - (total_apoints * Decimal::from_str("0.3").unwrap()) - ); - - // Remove the main pool - helper - .update_main_pool(&mut router, "owner", None, None, true) - .unwrap(); - - router.next_block(2 * WEEK); - helper.tune(&mut router).unwrap(); - - let resp: TuneInfo = router - .wrap() - .query_wasm_smart(helper.controller.clone(), &QueryMsg::TuneInfo {}) - .unwrap(); - // The main pool was removed - assert_eq!(resp.pool_alloc_points.len(), 2_usize); -} - -#[test] -fn check_add_network() { - let mut router = mock_app(); - let owner_addr = Addr::unchecked("owner"); - let helper = ControllerHelper::init(&mut router, &owner_addr, None); - - // Attempt to duplicate the native/home network - let add_network = NetworkInfo { - address_prefix: "unknown".to_string(), - generator_address: Addr::unchecked("wasm1contract"), - ibc_channel: None, - }; - - // Test success - let result = helper.update_networks(&mut router, "owner", Some(vec![add_network]), None); - assert!(result.is_err()); - - let add_network = NetworkInfo { - address_prefix: "unknown".to_string(), - generator_address: Addr::unchecked("wasmx1contract"), - ibc_channel: None, - }; - - // Test success - let result = helper.update_networks(&mut router, "owner", Some(vec![add_network]), None); - assert!(result.is_ok()); - - let add_network = NetworkInfo { - address_prefix: "unknown".to_string(), - generator_address: Addr::unchecked("wasm1contract"), - ibc_channel: None, - }; - - // Test for duplicate - let err = helper - .update_networks(&mut router, "owner", Some(vec![add_network]), None) - .unwrap_err(); - assert_eq!( - "Generic error: The resulting whitelist contains duplicated prefixes. It's either provided 'add' list contains duplicated prefixes or some of the added prefixes are already whitelisted.", - err.root_cause().to_string() - ); -} - -#[test] -fn check_remove_network() { - let mut router = mock_app(); - let owner_addr = Addr::unchecked("owner"); - let helper = ControllerHelper::init(&mut router, &owner_addr, None); - - let add_network = NetworkInfo { - address_prefix: "unknown".to_string(), - generator_address: Addr::unchecked("wasmx1contract"), - ibc_channel: None, - }; - - // Add network - helper - .update_networks(&mut router, "owner", Some(vec![add_network]), None) - .unwrap(); - - // Test remove invalid network - helper - .update_networks(&mut router, "owner", None, Some(vec!["testx".to_string()])) - .unwrap(); - - // We'll still have the default and the added network - let config = helper.query_config(&mut router).unwrap(); - let prefixes: Vec = config - .whitelisted_networks - .into_iter() - .map(|network_info| network_info.address_prefix) - .collect(); - assert_eq!(prefixes, vec!["wasm".to_string(), "wasmx".to_string()]); - - // Test remove native/home network, this should not succeed - let err = helper - .update_networks(&mut router, "owner", None, Some(vec!["wasm".to_string()])) - .unwrap_err(); - - assert_eq!( - err.root_cause().to_string(), - "Generic error: Cannot remove the native network with prefix wasm".to_string() - ); - - // Attempt to remove the network we added, should pass - helper - .update_networks(&mut router, "owner", None, Some(vec!["wasmx".to_string()])) - .unwrap(); - - // We'll still have the default and the added network - let config = helper.query_config(&mut router).unwrap(); - let prefixes: Vec = config - .whitelisted_networks - .into_iter() - .map(|network_info| network_info.address_prefix) - .collect(); - assert_eq!(prefixes, vec!["wasm".to_string()]); -} diff --git a/contracts/generator_controller_lite/tests/math_test.rs b/contracts/generator_controller_lite/tests/math_test.rs deleted file mode 100644 index 73187e5b..00000000 --- a/contracts/generator_controller_lite/tests/math_test.rs +++ /dev/null @@ -1,394 +0,0 @@ -use std::cmp::Ordering; -use std::collections::HashMap; - -use anyhow::Result; -use cosmwasm_std::{Addr, Uint128}; -use cw_multi_test::{App, AppResponse, Executor}; -use itertools::Itertools; -use proptest::prelude::*; - -use astroport_governance::generator_controller::ExecuteMsg; -use astroport_governance::utils::{MAX_LOCK_TIME, WEEK}; -use generator_controller_lite::bps::BasicPoints; -use Event::*; -use VeEvent::*; - -use astroport_tests_lite::{ - controller_helper::ControllerHelper, escrow_helper::MULTIPLIER, mock_app, TerraAppExtension, -}; - -#[derive(Clone, Debug)] -enum Event { - Vote(Vec<((String, String), u16)>), - TunePools, - ChangePoolLimit(u64), -} - -#[derive(Clone, Debug)] -enum VeEvent { - CreateLock(f64, u64), - ExtendLock(f64), - Withdraw, -} - -struct Simulator { - user_votes: HashMap>, - locks: HashMap, - helper: ControllerHelper, - router: App, - owner: Addr, - limit: u64, - pairs: HashMap<(String, String), Addr>, -} - -impl Simulator { - pub fn init>(users: &[T]) -> Self { - let mut router = mock_app(); - let owner = Addr::unchecked("owner"); - Self { - helper: ControllerHelper::init(&mut router, &owner, None), - user_votes: users - .iter() - .cloned() - .map(|user| (user.into(), HashMap::new())) - .collect(), - locks: HashMap::new(), - limit: 5, - pairs: HashMap::new(), - router, - owner, - } - } - - fn escrow_events_router(&mut self, user: &str, event: VeEvent) { - // We don't check voting escrow errors - let _ = match event { - CreateLock(amount, interval) => { - self.helper - .escrow_helper - .mint_xastro(&mut self.router, user, amount as u64); - self.helper.escrow_helper.create_lock( - &mut self.router, - user, - interval, - amount as f32, - ) - } - ExtendLock(amount) => { - self.helper - .escrow_helper - .mint_xastro(&mut self.router, user, amount as u64); - self.helper - .escrow_helper - .extend_lock_amount(&mut self.router, user, amount as f32) - } - Withdraw => self.helper.escrow_helper.withdraw(&mut self.router, user), - }; - } - - fn vote(&mut self, user: &str, votes: Vec<((String, String), u16)>) -> Result { - let votes: Vec<_> = votes - .iter() - .map(|(tokens, bps)| { - let addr = self - .pairs - .get(tokens) - .cloned() - .expect(&format!("Pair {}-{} was not found", tokens.0, tokens.1)); - (addr, *bps) - }) - .collect(); - self.helper - .vote(&mut self.router, user, votes.clone()) - .map(|response| { - let vp = self - .helper - .escrow_helper - .query_user_vp(&mut self.router, user) - .unwrap(); - self.locks - .insert(user.to_string(), (self.router.block_period(), vp)); - self.user_votes.insert(user.to_string(), HashMap::new()); - for (pool, bps) in votes { - self.user_votes - .get_mut(user) - .expect("User not found!") - .insert(pool.to_string(), bps); - } - let user_info = self.helper.query_user_info(&mut self.router, user).unwrap(); - let total_apoints: u16 = user_info - .votes - .iter() - .cloned() - .map(|pair| u16::from(pair.1)) - .sum(); - if total_apoints > 10000 { - panic!("{} > 10000", total_apoints) - } - assert_eq!( - user_info.vote_period.unwrap(), - self.router.block_info().time.seconds() - ); - response - }) - } - - fn change_pool_limit(&mut self, limit: u64) -> Result { - self.router - .execute_contract( - self.owner.clone(), - self.helper.controller.clone(), - &ExecuteMsg::ChangePoolsLimit { limit }, - &[], - ) - .map(|response| { - self.limit = limit; - response - }) - } - - pub fn event_router(&mut self, user: &str, event: Event) { - println!("User {} Event {:?}", user, event); - match event { - Vote(votes) => { - if let Err(err) = self.vote(user, votes) { - println!("{}", err); - } - } - TunePools => { - if let Err(err) = self.helper.tune(&mut self.router) { - println!("{}", err); - } - } - ChangePoolLimit(limit) => { - if let Err(err) = self.change_pool_limit(limit) { - println!("{}", err); - } - } - } - } - - pub fn register_pools(&mut self, tokens: &[String]) { - for token1 in tokens { - for token2 in tokens { - if matches!(token1.cmp(token2), Ordering::Less) { - self.pairs.insert( - (token1.to_string(), token2.to_string()), - self.helper - .create_pool_with_tokens(&mut self.router, token1, token2) - .unwrap(), - ); - } - } - } - } - - pub fn simulate_case( - &mut self, - tokens: &[String], - ve_events_tuples: &[(usize, String, VeEvent)], - events_tuples: &[(usize, String, Event)], - ) { - self.register_pools(tokens); - let pools = self - .pairs - .values() - .map(|pool_addr| pool_addr.to_string()) - .collect_vec(); - - let mut events: Vec> = vec![vec![]; MAX_PERIOD + 1]; - let mut ve_events: Vec> = vec![vec![]; MAX_PERIOD + 1]; - - for (period, user, event) in events_tuples.iter().cloned() { - events[period].push((user, event)); - } - for (period, user, event) in ve_events_tuples.iter().cloned() { - ve_events[period].push((user, event)) - } - - for period in 0..events.len() { - // vxASTRO events - if let Some(period_events) = ve_events.get(period) { - for (user, event) in period_events { - self.escrow_events_router(user, event.clone()) - } - } - // Generator controller events - if let Some(period_events) = events.get(period) { - if !period_events.is_empty() { - println!("Period {}:", period); - } - for (user, event) in period_events { - self.event_router(user, event.clone()) - } - } - - let mut voted_pools: HashMap = HashMap::new(); - - // Checking calculations - for user in self.user_votes.keys() { - let votes = self.user_votes.get(user).unwrap(); - if let Some((_, vp)) = self.locks.get(user) { - let user_vp = Uint128::from((*vp * MULTIPLIER as f32) as u128); - let user_vp = user_vp.u128() as f32 / MULTIPLIER as f32; - votes.iter().for_each(|(pool, &bps)| { - let vp = voted_pools.entry(pool.clone()).or_default(); - *vp += (bps as f32 / BasicPoints::MAX as f32) * user_vp - }) - } - } - let block_period = self.router.block_period(); - for pool_addr in &pools { - let pool_vp = self - .helper - .query_voted_pool_info_at_period(&mut self.router, pool_addr, block_period + 1) - .unwrap() - .vxastro_amount - .u128() as f32 - / MULTIPLIER as f32; - let real_vp = voted_pools.get(pool_addr).cloned().unwrap_or(0f32); - if (pool_vp - real_vp).abs() >= 10e-3 { - assert_eq!(pool_vp, real_vp, "Period: {}, pool: {}", period, pool_addr) - } - } - self.router.next_block(WEEK); - } - } -} - -const MAX_PERIOD: usize = 20; -const MAX_USERS: usize = 10; -const MAX_POOLS: usize = 5; -const MAX_EVENTS: usize = 100; - -fn escrow_events_strategy() -> impl Strategy { - prop_oneof![ - Just(VeEvent::Withdraw), - (1f64..=100f64).prop_map(VeEvent::ExtendLock), - ((1f64..=100f64), WEEK..MAX_LOCK_TIME).prop_map(|(a, b)| VeEvent::CreateLock(a, b)), - ] -} - -fn vote_strategy(tokens: Vec) -> impl Strategy { - prop::collection::vec( - (prop::sample::subsequence(tokens, 2), 1..=2500u16), - 1..MAX_POOLS, - ) - .prop_filter_map( - "Accepting only BPS sum <= 10000", - |vec: Vec<(Vec, u16)>| { - let votes = vec - .iter() - .into_grouping_map_by(|(pair, _)| { - let mut pair = pair.clone(); - pair.sort(); - (pair[0].clone(), pair[1].clone()) - }) - .aggregate(|acc, _, (_, val)| Some(acc.unwrap_or(0) + *val)) - .into_iter() - .collect_vec(); - if votes.iter().map(|(_, bps)| bps).sum::() <= 10000 { - Some(Event::Vote(votes)) - } else { - None - } - }, - ) -} - -fn controller_events_strategy(tokens: Vec) -> impl Strategy { - prop_oneof![ - Just(Event::TunePools), - (2..=MAX_POOLS as u64).prop_map(Event::ChangePoolLimit), - vote_strategy(tokens) - ] -} - -fn generate_cases() -> impl Strategy< - Value = ( - Vec, - Vec, - Vec<(usize, String, VeEvent)>, - Vec<(usize, String, Event)>, - ), -> { - let tokens_strategy = - prop::collection::hash_set("[A-Z]{3}", MAX_POOLS * MAX_POOLS / 2 - MAX_POOLS); - let users_strategy = prop::collection::vec("[a-z]{10}", 1..MAX_USERS); - (users_strategy, tokens_strategy).prop_flat_map(|(users, tokens)| { - ( - Just(users.clone()), - Just(tokens.iter().cloned().collect()), - prop::collection::vec( - ( - 1..=MAX_PERIOD, - prop::sample::select(users.clone()), - escrow_events_strategy(), - ), - 0..MAX_EVENTS, - ), - prop::collection::vec( - ( - 1..=MAX_PERIOD, - prop::sample::select(users), - controller_events_strategy(tokens.iter().cloned().collect_vec()), - ), - 0..MAX_EVENTS, - ), - ) - }) -} - -proptest! { - #[test] - fn run_simulations( - case in generate_cases() - ) { - let (users, tokens, ve_events_tuples, events_tuples) = case; - let mut simulator = Simulator::init(&users); - simulator.simulate_case(&tokens, &ve_events_tuples[..], &events_tuples[..]); - } -} - -#[test] -fn exact_simulation() { - let case = ( - ["rsgnawburh", "kxhuagnkvo"], - ["FOO", "BAR"], - [ - (4, "rsgnawburh", CreateLock(100.0, 1809600)), - (6, "kxhuagnkvo", CreateLock(100.0, 604800)), - ], - [ - ( - 4, - "rsgnawburh", - Vote(vec![(("BAR".to_string(), "FOO".to_string()), 10000)]), - ), - ( - 6, - "kxhuagnkvo", - Vote(vec![(("BAR".to_string(), "FOO".to_string()), 10000)]), - ), - ( - 6, - "rsgnawburh", - Vote(vec![(("BAR".to_string(), "FOO".to_string()), 10000)]), - ), - ], - ); - - let (users, tokens, ve_events_tuples, events_tuples) = case; - let tokens = tokens.iter().map(|item| item.to_string()).collect_vec(); - let ve_events_tuples = ve_events_tuples - .iter() - .map(|(period, user, event)| (*period, user.to_string(), event.clone())) - .collect_vec(); - let events_tuples = events_tuples - .iter() - .map(|(period, user, event)| (*period, user.to_string(), event.clone())) - .collect_vec(); - - let mut simulator = Simulator::init(&users); - simulator.simulate_case(&tokens, &ve_events_tuples[..], &events_tuples[..]); -} diff --git a/contracts/hub/.cargo/config b/contracts/hub/.cargo/config deleted file mode 100644 index f1bf3f59..00000000 --- a/contracts/hub/.cargo/config +++ /dev/null @@ -1,6 +0,0 @@ -[alias] -wasm = "build --release --lib --target wasm32-unknown-unknown" -wasm-debug = "build --lib --target wasm32-unknown-unknown" -unit-test = "test --lib" -integration-test = "test --test integration" -schema = "run --bin schema" \ No newline at end of file diff --git a/contracts/hub/Cargo.toml b/contracts/hub/Cargo.toml deleted file mode 100644 index c010a372..00000000 --- a/contracts/hub/Cargo.toml +++ /dev/null @@ -1,41 +0,0 @@ -[package] -name = "astroport-hub" -version = "1.0.0" -authors = ["Astroport"] -edition = "2021" -description = "Handles interchain actions from Astroport outposts" -license = "GPL-3.0" - -exclude = [ - # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. - "contract.wasm", - "hash.txt", -] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] -library = [] - -[dependencies] -cw2 = "1.0.1" -cw20 = "1.1" -cosmwasm-schema = "1.1.0" -cosmwasm-std = { version = "1.1", features = ["iterator", "ibc3"] } -cw-storage-plus = "0.15" -schemars = "0.8.12" -serde = { version = "1.0.164", default-features = false, features = ["derive"] } -thiserror = "1.0.40" -astroport = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -astroport-governance = { path = "../../packages/astroport-governance" } -serde-json-wasm = "0.5.1" - -[dev-dependencies] -cw-multi-test = "0.16.5" -anyhow = "1.0" diff --git a/contracts/hub/README.md b/contracts/hub/README.md deleted file mode 100644 index 38520790..00000000 --- a/contracts/hub/README.md +++ /dev/null @@ -1,139 +0,0 @@ -# Hub - -The Hub contract enables staking, unstaking, voting in governance as well as voting on vxASTRO emissions from any chain where the Outpost contract is deployed on. The Hub and Outpost contracts are designed to work together, connected over IBC channels. - -The Hub defines the following messages that can be received over IBC: - -```rust -/// Hub defines the messages that can be sent from an Outpost to the Hub -pub enum Hub { - /// Queries the Assembly for a proposal by ID via the Hub - QueryProposal { - /// The ID of the proposal to query - id: u64, - }, - /// Cast a vote on an Assembly proposal - CastAssemblyVote { - /// The ID of the proposal to vote on - proposal_id: u64, - /// The address of the voter - voter: Addr, - /// The vote choice - vote_option: ProposalVoteOption, - /// The voting power held by the voter, in this case xASTRO holdings - voting_power: Uint128, - }, - /// Cast a vote during an emissions voting period - CastEmissionsVote { - /// The address of the voter - voter: Addr, - /// The voting power held by the voter, in this case vxASTRO lite holdings - voting_power: Uint128, - /// The votes in the format (pool address, percent of voting power) - votes: Vec<(String, u16)>, - }, - /// Stake ASTRO tokens for xASTRO - Stake {}, - /// Unstake xASTRO tokens for ASTRO - Unstake { - // The user requesting the unstake and that should receive it - receiver: String, - /// The amount of xASTRO to unstake - amount: Uint128, - }, - /// Kick an unlocked voter's voting power from the Generator Controller lite - KickUnlockedVoter { - /// The address of the voter to kick - voter: Addr, - }, - /// Withdraw stuck funds from the Hub in case of specific IBC failures - WithdrawFunds { - /// The address of the user to withdraw funds for - user: Addr, - }, -} -``` - -The Hub is unable to verify the information it receives, such as xASTRO holdings on an Outpost. To prevent invalid data reaching the Hub, it is only allowed to receive messages from the Outpost contract which verifies the data before sending it. - -The Hub defines the following execute messages: - -```rust -pub enum ExecuteMsg { - /// Receive a message of type [`Cw20ReceiveMsg`] - Receive(Cw20ReceiveMsg), - /// Update parameters in the Hub contract. Only the owner is allowed to - /// update the config - UpdateConfig { - /// The new address of the Assembly on the Hub, ignored if None - assembly_addr: Option, - /// The new address of the CW20-ICS20 contract on the Hub that - /// supports memo handling, ignored if None - cw20_ics20_addr: Option, - }, - /// Add a new Outpost to the Hub. Only allowed Outposts can send IBC messages - AddOutpost { - /// The remote contract address of the Outpost to add - outpost_addr: String, - /// The channel to use for CW20-ICS20 IBC transfers - cw20_ics20_channel: String, - }, - /// Remove an Outpost from the Hub - RemoveOutpost { - /// The remote contract address of the Outpost to remove - outpost_addr: String, - }, -} -``` - -## Message details - -**Receive ASTRO via a Cw20HookMsg message containing an OutpostMemo** - -To stake ASTRO from an Outpost a user needs to send the ASTRO over IBC (via the CW20-ICS20 contract) to the Hub. Together with these tokens they need to provide a valid JSON memo indicating the action to take. Currently, only staking is supported. - -Using a chain's CLI, the command looks as follows - -```bash -wasmd tx ibc-transfer transfer transfer channel-1 cw20_ics20_contract address 2000ibc/81A0618D89A81E830D4D670650E674770DEFFE344DCE3EDF3F62A9E3A506C0B4 -- --from user --memo '{"stake": {}}' -``` - -Once the memo is interpreted and executed, the xASTRO is minted to the user on the Outpost. - -**Update Config** - -Update config allows the owner to set a new address for the Assembly and the CW20-ICS20 contracts - -```json -{ - "update_config": { - "assembly_addr": "wasm123...", - "cw20_ics20_addr": "wasm456..." - } -} -``` - -**Adding an Outpost** - -Only Outposts listed in the contract are allowed to open IBC channels and send messages. - -```json -{ - "add_outpost":{ - "outpost_addr": "wasm123...", - "cw20_ics20_channel": "ASTRO transfer channel in CW20-ICS20 contract" - } -} -``` - -**Remove an Outpost** - -Removing an Outpost will not close the IBC channels, but will block new messages sent from the Outpost - -```json -{ - "remove_outpost":{ - "outpost_addr": "wasm123..." - } -} -``` \ No newline at end of file diff --git a/contracts/hub/src/contract.rs b/contracts/hub/src/contract.rs deleted file mode 100644 index 3681c5f4..00000000 --- a/contracts/hub/src/contract.rs +++ /dev/null @@ -1,133 +0,0 @@ -use cosmwasm_std::{entry_point, DepsMut, Env, MessageInfo, Response}; -use cw2::set_contract_version; - -use astroport::staking::{ConfigResponse, QueryMsg}; -use astroport_governance::{ - hub::{Config, InstantiateMsg, MigrateMsg}, - interchain::{MAX_IBC_TIMEOUT_SECONDS, MIN_IBC_TIMEOUT_SECONDS}, -}; - -use crate::{error::ContractError, state::CONFIG}; - -/// Contract name that is used for migration. -const CONTRACT_NAME: &str = "astroport-hub"; -/// Contract version that is used for migration. -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -/// Instantiates the contract, storing the config and querying the staking contract. -/// Returns a `Response` object on successful execution or a `ContractError` on failure. -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> Result { - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - - // Query staking contract for ASTRO and xASTRO address - let staking_config: ConfigResponse = deps - .querier - .query_wasm_smart(&msg.staking_addr, &QueryMsg::Config {})?; - - if !(MIN_IBC_TIMEOUT_SECONDS..=MAX_IBC_TIMEOUT_SECONDS).contains(&msg.ibc_timeout_seconds) { - return Err(ContractError::InvalidIBCTimeout { - timeout: msg.ibc_timeout_seconds, - min: MIN_IBC_TIMEOUT_SECONDS, - max: MAX_IBC_TIMEOUT_SECONDS, - }); - } - - let config = Config { - owner: deps.api.addr_validate(&msg.owner)?, - assembly_addr: deps.api.addr_validate(&msg.assembly_addr)?, - cw20_ics20_addr: deps.api.addr_validate(&msg.cw20_ics20_addr)?, - staking_addr: deps.api.addr_validate(&msg.staking_addr)?, - token_addr: staking_config.deposit_token_addr, - xtoken_addr: staking_config.share_token_addr, - generator_controller_addr: deps.api.addr_validate(&msg.generator_controller_addr)?, - ibc_timeout_seconds: msg.ibc_timeout_seconds, - }; - CONFIG.save(deps.storage, &config)?; - - Ok(Response::default()) -} - -/// Migrates the contract to a new version. -#[entry_point] -pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { - Err(ContractError::MigrationError {}) -} - -#[cfg(test)] -mod tests { - - use super::*; - - use crate::{ - contract::instantiate, - mock::{mock_all, ASSEMBLY, CW20ICS20, GENERATOR_CONTROLLER, OWNER, STAKING}, - }; - - // Test Cases: - // - // Expect Success - // - Invalid IBC timeouts are rejected - // - #[test] - fn invalid_ibc_timeout() { - let (mut deps, env, info) = mock_all(OWNER); - - // Test MAX + 1 - let ibc_timeout_seconds = MAX_IBC_TIMEOUT_SECONDS + 1; - let err = instantiate( - deps.as_mut(), - env.clone(), - info.clone(), - astroport_governance::hub::InstantiateMsg { - owner: OWNER.to_string(), - assembly_addr: ASSEMBLY.to_string(), - cw20_ics20_addr: CW20ICS20.to_string(), - staking_addr: STAKING.to_string(), - generator_controller_addr: GENERATOR_CONTROLLER.to_string(), - ibc_timeout_seconds, - }, - ) - .unwrap_err(); - - assert_eq!( - err, - ContractError::InvalidIBCTimeout { - timeout: ibc_timeout_seconds, - min: MIN_IBC_TIMEOUT_SECONDS, - max: MAX_IBC_TIMEOUT_SECONDS - } - ); - - // Test MIN - 1 - let ibc_timeout_seconds = MIN_IBC_TIMEOUT_SECONDS - 1; - let err = instantiate( - deps.as_mut(), - env, - info, - astroport_governance::hub::InstantiateMsg { - owner: OWNER.to_string(), - assembly_addr: ASSEMBLY.to_string(), - cw20_ics20_addr: CW20ICS20.to_string(), - staking_addr: STAKING.to_string(), - generator_controller_addr: GENERATOR_CONTROLLER.to_string(), - ibc_timeout_seconds, - }, - ) - .unwrap_err(); - - assert_eq!( - err, - ContractError::InvalidIBCTimeout { - timeout: ibc_timeout_seconds, - min: MIN_IBC_TIMEOUT_SECONDS, - max: MAX_IBC_TIMEOUT_SECONDS - } - ); - } -} diff --git a/contracts/hub/src/error.rs b/contracts/hub/src/error.rs deleted file mode 100644 index b1bb819b..00000000 --- a/contracts/hub/src/error.rs +++ /dev/null @@ -1,70 +0,0 @@ -use cosmwasm_std::{OverflowError, StdError}; -use serde_json_wasm::de::Error as SerdeError; -use thiserror::Error; - -/// This enum describes Hub's contract errors -#[derive(Error, Debug, PartialEq)] -pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("Unable to parse: {0}")] - ParseError(#[from] std::num::ParseIntError), - - #[error("Contract can't be migrated!")] - MigrationError {}, - - #[error("Unauthorized")] - Unauthorized {}, - - #[error("You can not send 0 tokens")] - ZeroAmount {}, - - #[error("Insufficient funds held for the action")] - InsufficientFunds {}, - - #[error("The provided address does not have any funds")] - NoFunds {}, - - #[error("Voting power exceeds channel balance")] - InvalidVotingPower {}, - - #[error("The action {} is not allowed via an IBC memo", action)] - NotMemoAction { action: String }, - - #[error( - "The action {} is not allowed via IBC and must be actioned via a tranfer memo", - action - )] - NotIBCAction { action: String }, - - #[error("Memo does not conform to the expected format: {}", reason)] - InvalidMemo { reason: SerdeError }, - - #[error("Memo was not intended for the hook contract")] - InvalidDestination {}, - - #[error("Got a submessage reply with unknown id: {id}")] - UnknownReplyId { id: u64 }, - - #[error("Invalid submessage {0}", reason)] - InvalidSubmessage { reason: String }, - - #[error("Outpost already added, remove it first: {0}", address)] - OutpostAlreadyAdded { address: String }, - - #[error("No Outpost found that matches the message channels")] - UnknownOutpost {}, - - #[error("Invalid IBC timeout: {timeout}, must be between {min} and {max} seconds")] - InvalidIBCTimeout { timeout: u64, min: u64, max: u64 }, - - #[error("Channel already established: {channel_id}")] - ChannelAlreadyEstablished { channel_id: String }, -} - -impl From for ContractError { - fn from(o: OverflowError) -> Self { - StdError::from(o).into() - } -} diff --git a/contracts/hub/src/execute.rs b/contracts/hub/src/execute.rs deleted file mode 100644 index 343c6559..00000000 --- a/contracts/hub/src/execute.rs +++ /dev/null @@ -1,1602 +0,0 @@ -use cosmwasm_std::{ - entry_point, from_json, to_json_binary, Addr, DepsMut, Env, MessageInfo, Response, StdError, - StdResult, SubMsg, WasmMsg, -}; -use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; - -use astroport::{ - common::{claim_ownership, drop_ownership_proposal, propose_new_owner}, - querier::query_token_balance, -}; -use astroport_governance::{ - hub::{Config, Cw20HookMsg, ExecuteMsg}, - interchain::{Hub, MAX_IBC_TIMEOUT_SECONDS, MIN_IBC_TIMEOUT_SECONDS}, - utils::check_contract_supports_channel, -}; - -use crate::{ - error::ContractError, - reply::STAKE_ID, - state::{ - OutpostChannels, ReplyData, CONFIG, OUTPOSTS, OWNERSHIP_PROPOSAL, REPLY_DATA, USER_FUNDS, - }, -}; - -/// Exposes all the execute functions available in the contract. -/// -/// ## Execute messages -/// * **ExecuteMsg::Receive(msg)** Receives a message of type [`Cw20ReceiveMsg`] and processes -/// it depending on the received template. -/// -/// * **ExecuteMsg::UpdateConfig { ibc_timeout_seconds }** Update parameters in the Hub contract. Only the owner is allowed to -/// update the config -/// -/// * **ExecuteMsg::AddOutpost { outpost_addr, cw20_ics20_channel }** Add an Outpost to the contract, -/// allowing new IBC connections and IBC messages -/// -/// * **ExecuteMsg::RemoveOutpost { outpost_addr }** Removes an Outpost from the contract, -/// blocking new IBC connections as well as any IBC messages -/// -/// * **ExecuteMsg::ProposeNewOwner { new_owner, expires_in }** Creates a new request to change -/// contract ownership. -/// -/// * **ExecuteMsg::DropOwnershipProposal {}** Removes a request to change contract ownership. -/// -/// * **ExecuteMsg::ClaimOwnership {}** Claims contract ownership. -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - match msg { - ExecuteMsg::Receive(msg) => receive_cw20(deps, env, info, msg), - ExecuteMsg::UpdateConfig { - ibc_timeout_seconds, - } => update_config(deps, info, ibc_timeout_seconds), - ExecuteMsg::AddOutpost { - outpost_addr, - outpost_channel, - cw20_ics20_channel, - } => add_outpost( - deps, - env, - info, - outpost_addr, - outpost_channel, - cw20_ics20_channel, - ), - ExecuteMsg::RemoveOutpost { outpost_addr } => remove_outpost(deps, info, outpost_addr), - ExecuteMsg::ProposeNewOwner { - new_owner, - expires_in, - } => { - let config = CONFIG.load(deps.storage)?; - - propose_new_owner( - deps, - info, - env, - new_owner, - expires_in, - config.owner, - OWNERSHIP_PROPOSAL, - ) - .map_err(Into::into) - } - ExecuteMsg::DropOwnershipProposal {} => { - let config: Config = CONFIG.load(deps.storage)?; - - drop_ownership_proposal(deps, info, config.owner, OWNERSHIP_PROPOSAL) - .map_err(Into::into) - } - ExecuteMsg::ClaimOwnership {} => { - claim_ownership(deps, info, env, OWNERSHIP_PROPOSAL, |deps, new_owner| { - CONFIG - .update::<_, StdError>(deps.storage, |mut v| { - v.owner = new_owner; - Ok(v) - }) - .map(|_| ()) - }) - .map_err(Into::into) - } - } -} - -/// Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on -/// the received template -/// -/// Funds received here must be from the CW20-ICS20 contract and is used for -/// actions initiated from an Outpost that require ASTRO tokens -/// -/// * **cw20_msg** CW20 message to process -fn receive_cw20( - deps: DepsMut, - env: Env, - info: MessageInfo, - cw20_msg: Cw20ReceiveMsg, -) -> Result { - let config = CONFIG.load(deps.storage)?; - - // We only allow ASTRO tokens to be sent here - if info.sender != config.token_addr { - return Err(ContractError::Unauthorized {}); - } - - // The sender of the ASTRO tokens must be the CW20-ICS20 contract - if cw20_msg.sender != config.cw20_ics20_addr { - return Err(ContractError::Unauthorized {}); - } - - // We can't do anything with no tokens - if cw20_msg.amount.is_zero() { - return Err(ContractError::ZeroAmount {}); - } - - // Match the CW20 template - match from_json(&cw20_msg.msg)? { - Cw20HookMsg::OutpostMemo { - channel, - sender, - receiver, - memo, - } => handle_outpost_memo(deps, env, cw20_msg, channel, sender, receiver, memo), - Cw20HookMsg::TransferFailure { receiver } => { - handle_transfer_failure(deps, info, cw20_msg, receiver) - } - } -} - -/// Handle the JSON memo from an Outpost by matching against the available -/// actions. -/// -/// If the memo is not in a valid format for the actions it is -/// considered invalid. -/// -/// If the memo wasn't intended for us we forward it to the original -/// intended receiver -fn handle_outpost_memo( - deps: DepsMut, - env: Env, - msg: Cw20ReceiveMsg, - receiving_channel: String, - original_sender: String, - original_receiver: String, - memo: String, -) -> Result { - // If the receiver is not our contract we assume this is incorrect and fail - // the transfer, causing the funds to be returned to the sender on the - // original chain - if env.contract.address != original_receiver { - return Err(ContractError::InvalidDestination {}); - } - - // But if this was intended for us, parse and handle the memo - let sub_msg: SubMsg = match serde_json_wasm::from_str::(memo.as_str()) { - Ok(hub) => match hub { - Hub::Stake {} => handle_stake_instruction( - deps, - env, - msg, - receiving_channel, - original_sender.clone(), - )?, - _ => { - return Err(ContractError::NotMemoAction { - action: hub.to_string(), - }) - } - }, - Err(reason) => { - // This memo doesn't match any of our action formats - // In case the receiver is set to our handler contract we - // assume the funds were intended to have a valid action but - // are invalid, thus we need to fail the transaction and return - // the funds - return Err(ContractError::InvalidMemo { reason }); - } - }; - - Ok(Response::default() - .add_submessage(sub_msg) - .add_attribute("hub", "handle_memo") - .add_attribute("memo_type", "instruction") - .add_attribute("sender", original_sender)) -} - -/// Handle a stake instruction sent via memo from an Outpost -/// -/// The full amount is staked and the resulting xASTRO is sent to the -/// original sender on the Outpost -fn handle_stake_instruction( - deps: DepsMut, - env: Env, - msg: Cw20ReceiveMsg, - receiving_channel: String, - original_sender: String, -) -> Result { - let config = CONFIG.load(deps.storage)?; - - // Stake all the received ASTRO tokens - // We need a SubMessage here to ensure we only mint the actual - // amount of ASTRO that was staked, which *might* not the full amount sent - let enter_msg = astroport::staking::Cw20HookMsg::Enter {}; - let send_msg = Cw20ExecuteMsg::Send { - contract: config.staking_addr.to_string(), - amount: msg.amount, - msg: to_json_binary(&enter_msg)?, - }; - - // Execute the message, we're using a CW20, so no funds added here - let stake_msg = WasmMsg::Execute { - contract_addr: config.token_addr.to_string(), - msg: to_json_binary(&send_msg)?, - funds: vec![], - }; - - let current_xastro_balance = query_token_balance( - &deps.querier, - config.xtoken_addr.to_string(), - env.contract.address, - )?; - - // Temporarily save the data needed for the SubMessage reply - let reply_data = ReplyData { - receiver: original_sender, - receiving_channel, - value: current_xastro_balance, - original_value: msg.amount, - }; - REPLY_DATA.save(deps.storage, &reply_data)?; - - Ok(SubMsg::reply_on_success(stake_msg, STAKE_ID)) -} - -/// Update the Hub config -fn update_config( - deps: DepsMut, - info: MessageInfo, - ibc_timeout_seconds: Option, -) -> Result { - let mut config = CONFIG.load(deps.storage)?; - - // Only owner can update the config - if info.sender != config.owner { - return Err(ContractError::Unauthorized {}); - } - - if let Some(ibc_timeout_seconds) = ibc_timeout_seconds { - if !(MIN_IBC_TIMEOUT_SECONDS..=MAX_IBC_TIMEOUT_SECONDS).contains(&ibc_timeout_seconds) { - return Err(ContractError::InvalidIBCTimeout { - timeout: ibc_timeout_seconds, - min: MIN_IBC_TIMEOUT_SECONDS, - max: MAX_IBC_TIMEOUT_SECONDS, - }); - } - config.ibc_timeout_seconds = ibc_timeout_seconds; - } - - CONFIG.save(deps.storage, &config)?; - - Ok(Response::default()) -} - -/// Add an Outpost to the Hub -/// -/// Adding an Outpost requires the Outpost address and the CW20-ICS20 channel -/// where funds will be sent through. Adding an Outpost will allow a new IBC -/// channel to be established with the Outpost and the Hub -fn add_outpost( - deps: DepsMut, - env: Env, - info: MessageInfo, - outpost_addr: String, - outpost_channel: String, - cw20_ics20_channel: String, -) -> Result { - let config = CONFIG.load(deps.storage)?; - - // Only owner can add Outposts - if info.sender != config.owner { - return Err(ContractError::Unauthorized {}); - } - - if OUTPOSTS.has(deps.storage, &outpost_addr) { - return Err(ContractError::OutpostAlreadyAdded { - address: outpost_addr, - }); - } - - // Check if the channel is supported in the CW20-ICS20 contract - check_contract_supports_channel(deps.querier, &config.cw20_ics20_addr, &cw20_ics20_channel)?; - // Check that the Hub supports the Outpost channel - check_contract_supports_channel(deps.querier, &env.contract.address, &outpost_channel)?; - - let outpost = OutpostChannels { - outpost: outpost_channel, - cw20_ics20: cw20_ics20_channel.clone(), - }; - - // Store the CW20-ICS20 transfer channel for the Outpost - OUTPOSTS.save(deps.storage, &outpost_addr, &outpost)?; - - Ok(Response::default() - .add_attribute("action", "add_outpost") - .add_attribute("address", outpost_addr) - .add_attribute("cw20_ics20_channel", cw20_ics20_channel)) -} - -/// Remove an Outpost from the Hub -/// -/// Removing an Outpost will block new IBC channels to be established between the -/// Hub and the provided Outpost. All IBC messages will also fail -/// -/// IMPORTANT: This does not close any existing IBC channels -fn remove_outpost( - deps: DepsMut, - info: MessageInfo, - outpost_addr: String, -) -> Result { - let config = CONFIG.load(deps.storage)?; - - // Only owner can remove Outposts - if info.sender != config.owner { - return Err(ContractError::Unauthorized {}); - } - - OUTPOSTS.remove(deps.storage, &outpost_addr); - - Ok(Response::default() - .add_attribute("action", "remove_outpost") - .add_attribute("address", outpost_addr)) -} - -/// Handle failed CW20-ICS20 IBC transfers -/// -/// If a CW20-ICS20 IBC transfer fails that we initiated, we receive the original -/// tokens back and need to store them for the user to retrieve manually -/// -/// Once funds are held here, the original user will need to issue a withdraw -/// transaction on the Outpost to retrieve their funds. -fn handle_transfer_failure( - deps: DepsMut, - info: MessageInfo, - msg: Cw20ReceiveMsg, - receiver: String, -) -> Result { - let user_addr = Addr::unchecked(&receiver); - USER_FUNDS.update(deps.storage, &user_addr, |balance| -> StdResult<_> { - Ok(balance.unwrap_or_default().checked_add(msg.amount)?) - })?; - - Ok(Response::default() - .add_attribute("outpost_handler", "handle_transfer_failure") - .add_attribute("sender", info.sender) - .add_attribute("og_receiver", receiver)) -} - -#[cfg(test)] -mod tests { - use super::*; - use astroport_governance::{hub::HubBalance, interchain::Outpost}; - use cosmwasm_std::{ - testing::{mock_info, MOCK_CONTRACT_ADDR}, - IbcEndpoint, IbcMsg, IbcPacket, IbcPacketTimeoutMsg, Reply, ReplyOn, SubMsgResponse, - SubMsgResult, Uint128, Uint64, - }; - use serde_json_wasm::de::Error as SerdeError; - - use crate::{ - contract::instantiate, - execute::execute, - ibc::ibc_packet_timeout, - mock::{ - mock_all, setup_channel, ASSEMBLY, ASTRO_TOKEN, CW20ICS20, GENERATOR_CONTROLLER, OWNER, - STAKING, XASTRO_TOKEN, - }, - query::query, - reply::{reply, UNSTAKE_ID}, - }; - - // Test Cases: - // - // Expect Success - // - Adding and removing Outposts work correctly - // - // Expect Error - // - Adding an Outpost with duplicate address - // - Adding an Outpost when not the owner - // - Removing an Outpost when not the owner - // - #[test] - fn add_remove_outpost() { - let (mut deps, env, info) = mock_all(OWNER); - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::hub::InstantiateMsg { - owner: OWNER.to_string(), - assembly_addr: ASSEMBLY.to_string(), - cw20_ics20_addr: CW20ICS20.to_string(), - staking_addr: STAKING.to_string(), - generator_controller_addr: GENERATOR_CONTROLLER.to_string(), - ibc_timeout_seconds: 10, - }, - ) - .unwrap(); - - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::hub::ExecuteMsg::AddOutpost { - outpost_addr: "wasm1contractaddress1".to_string(), - outpost_channel: "channel-2".to_string(), - cw20_ics20_channel: "channel-1".to_string(), - }, - ) - .unwrap(); - - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::hub::ExecuteMsg::AddOutpost { - outpost_addr: "wasm1contractaddress2".to_string(), - outpost_channel: "channel-2".to_string(), - cw20_ics20_channel: "channel-2".to_string(), - }, - ) - .unwrap(); - - // Test paging, should return a single result - let outposts = query( - deps.as_ref(), - env.clone(), - astroport_governance::hub::QueryMsg::Outposts { - start_after: None, - limit: Some(1), - }, - ) - .unwrap(); - - assert_eq!( - outposts, - to_json_binary(&vec![astroport_governance::hub::OutpostConfig { - address: "wasm1contractaddress1".to_string(), - channel: "channel-2".to_string(), - cw20_ics20_channel: "channel-1".to_string(), - },]) - .unwrap() - ); - - // Test paging, should return a single result of the second item - let outposts = query( - deps.as_ref(), - env.clone(), - astroport_governance::hub::QueryMsg::Outposts { - start_after: Some("wasm1contractaddress1".to_string()), - limit: Some(1), - }, - ) - .unwrap(); - - assert_eq!( - outposts, - to_json_binary(&vec![astroport_governance::hub::OutpostConfig { - address: "wasm1contractaddress2".to_string(), - channel: "channel-2".to_string(), - cw20_ics20_channel: "channel-2".to_string(), - },]) - .unwrap() - ); - - // Get all - let outposts = query( - deps.as_ref(), - env.clone(), - astroport_governance::hub::QueryMsg::Outposts { - start_after: None, - limit: None, - }, - ) - .unwrap(); - - assert_eq!( - outposts, - to_json_binary(&vec![ - astroport_governance::hub::OutpostConfig { - address: "wasm1contractaddress1".to_string(), - channel: "channel-2".to_string(), - cw20_ics20_channel: "channel-1".to_string(), - }, - astroport_governance::hub::OutpostConfig { - address: "wasm1contractaddress2".to_string(), - channel: "channel-2".to_string(), - cw20_ics20_channel: "channel-2".to_string(), - } - ]) - .unwrap() - ); - - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::hub::ExecuteMsg::RemoveOutpost { - outpost_addr: "wasm1contractaddress1".to_string(), - }, - ) - .unwrap(); - - let outposts = query( - deps.as_ref(), - env.clone(), - astroport_governance::hub::QueryMsg::Outposts { - start_after: None, - limit: None, - }, - ) - .unwrap(); - - assert_eq!( - outposts, - to_json_binary(&vec![astroport_governance::hub::OutpostConfig { - address: "wasm1contractaddress2".to_string(), - channel: "channel-2".to_string(), - cw20_ics20_channel: "channel-2".to_string(), - },]) - .unwrap() - ); - - // Must not allow duplicate Outpost addresses - let err = execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::hub::ExecuteMsg::AddOutpost { - outpost_addr: "wasm1contractaddress2".to_string(), - outpost_channel: "channel-2".to_string(), - cw20_ics20_channel: "channel-2".to_string(), - }, - ) - .unwrap_err(); - assert!(matches!( - err, - ContractError::OutpostAlreadyAdded { address: _ } - )); - - // Must not allow adding if not the owner - let err = execute( - deps.as_mut(), - env.clone(), - mock_info("not_owner", &[]), - astroport_governance::hub::ExecuteMsg::AddOutpost { - outpost_addr: "wasm1contractaddress3".to_string(), - outpost_channel: "channel-2".to_string(), - cw20_ics20_channel: "channel-4".to_string(), - }, - ) - .unwrap_err(); - assert!(matches!(err, ContractError::Unauthorized {})); - - // Must not allow removing if not the owner - let err = execute( - deps.as_mut(), - env, - mock_info("not_owner", &[]), - astroport_governance::hub::ExecuteMsg::RemoveOutpost { - outpost_addr: "wasm1contractaddress2".to_string(), - }, - ) - .unwrap_err(); - assert!(matches!(err, ContractError::Unauthorized {})); - } - - // Test Cases: - // - // Expect Success - // - Updating config works - // - // Expect Error - // - Updating config with invalid addresses - // - Updating config when not the owner - // - #[test] - fn update_config() { - let (mut deps, env, info) = mock_all(OWNER); - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::hub::InstantiateMsg { - owner: OWNER.to_string(), - assembly_addr: ASSEMBLY.to_string(), - cw20_ics20_addr: CW20ICS20.to_string(), - staking_addr: STAKING.to_string(), - generator_controller_addr: GENERATOR_CONTROLLER.to_string(), - ibc_timeout_seconds: 10, - }, - ) - .unwrap(); - - let config = query( - deps.as_ref(), - env.clone(), - astroport_governance::hub::QueryMsg::Config {}, - ) - .unwrap(); - - // Ensure the config set during instantiation is correct - assert_eq!( - config, - to_json_binary(&astroport_governance::hub::Config { - owner: Addr::unchecked(OWNER), - assembly_addr: Addr::unchecked(ASSEMBLY), - cw20_ics20_addr: Addr::unchecked(CW20ICS20), - staking_addr: Addr::unchecked(STAKING), - token_addr: Addr::unchecked(ASTRO_TOKEN), - xtoken_addr: Addr::unchecked(XASTRO_TOKEN), - generator_controller_addr: Addr::unchecked(GENERATOR_CONTROLLER), - ibc_timeout_seconds: 10, - }) - .unwrap() - ); - - // Update the IBC timeout to a value below min - let err = execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::hub::ExecuteMsg::UpdateConfig { - ibc_timeout_seconds: Some(MIN_IBC_TIMEOUT_SECONDS - 1), - }, - ) - .unwrap_err(); - assert!(matches!( - err, - ContractError::InvalidIBCTimeout { - timeout: _, - min: MIN_IBC_TIMEOUT_SECONDS, - max: MAX_IBC_TIMEOUT_SECONDS - } - )); - - // Update the IBC timeout to a value below max - let err = execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::hub::ExecuteMsg::UpdateConfig { - ibc_timeout_seconds: Some(MAX_IBC_TIMEOUT_SECONDS + 1), - }, - ) - .unwrap_err(); - assert!(matches!( - err, - ContractError::InvalidIBCTimeout { - timeout: _, - min: MIN_IBC_TIMEOUT_SECONDS, - max: MAX_IBC_TIMEOUT_SECONDS - } - )); - - // Update the IBC timeout to a correct value - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::hub::ExecuteMsg::UpdateConfig { - ibc_timeout_seconds: Some(50), - }, - ) - .unwrap(); - // Query the new config - let config = query( - deps.as_ref(), - env.clone(), - astroport_governance::hub::QueryMsg::Config {}, - ) - .unwrap(); - - assert_eq!( - config, - to_json_binary(&astroport_governance::hub::Config { - owner: Addr::unchecked(OWNER), - assembly_addr: Addr::unchecked(ASSEMBLY), - cw20_ics20_addr: Addr::unchecked(CW20ICS20), - staking_addr: Addr::unchecked(STAKING), - token_addr: Addr::unchecked(ASTRO_TOKEN), - xtoken_addr: Addr::unchecked(XASTRO_TOKEN), - generator_controller_addr: Addr::unchecked(GENERATOR_CONTROLLER), - ibc_timeout_seconds: 50, - }) - .unwrap() - ); - - // Must not allow updating if not the owner - let err = execute( - deps.as_mut(), - env, - mock_info("not_owner", &[]), - astroport_governance::hub::ExecuteMsg::UpdateConfig { - ibc_timeout_seconds: Some(200), - }, - ) - .unwrap_err(); - - assert!(matches!(err, ContractError::Unauthorized {})); - } - - // Test Cases: - // - // Expect Success - // - Sending the funds results in correct balances - // - // Expect Error - // - When not sent by the CW20-ICS20 contract - // - When tokens are not ASTRO - // - When amount is zero - // - // This tests that balances are correctly tracked by the contract in case of - // IBC failures that result in funds getting stuck on the Hub - #[test] - fn cw20_ics20_transfer_failure() { - let (mut deps, env, info) = mock_all(OWNER); - - let user1 = "user1"; - let user2 = "user2"; - let user1_funds = Uint128::from(100u128); - let user2_funds = Uint128::from(300u128); - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::hub::InstantiateMsg { - owner: OWNER.to_string(), - assembly_addr: ASSEMBLY.to_string(), - cw20_ics20_addr: CW20ICS20.to_string(), - staking_addr: STAKING.to_string(), - generator_controller_addr: GENERATOR_CONTROLLER.to_string(), - ibc_timeout_seconds: 10, - }, - ) - .unwrap(); - - // Add an allowed Outpost - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::hub::ExecuteMsg::AddOutpost { - outpost_addr: "outpost".to_string(), - outpost_channel: "channel-2".to_string(), - cw20_ics20_channel: "channel-1".to_string(), - }, - ) - .unwrap(); - - // Transfer failures are only allowed to be recorded when sent by the - // CW20-ICS20 contract and if the tokens are ASTRO - let err = execute( - deps.as_mut(), - env.clone(), - mock_info(ASTRO_TOKEN, &[]), - astroport_governance::hub::ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: "not_cw20_ics20".to_string(), - amount: user1_funds, - msg: to_json_binary(&astroport_governance::hub::Cw20HookMsg::TransferFailure { - receiver: user1.to_owned(), - }) - .unwrap(), - }), - ) - .unwrap_err(); - - assert!(matches!(err, ContractError::Unauthorized {})); - - // Transfer failures must only accept ASTRO tokens - let err = execute( - deps.as_mut(), - env.clone(), - mock_info(CW20ICS20, &[]), - astroport_governance::hub::ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: CW20ICS20.to_string(), - amount: user1_funds, - msg: to_json_binary(&astroport_governance::hub::Cw20HookMsg::TransferFailure { - receiver: user1.to_owned(), - }) - .unwrap(), - }), - ) - .unwrap_err(); - - assert!(matches!(err, ContractError::Unauthorized {})); - - // Transfer failures will must not accept zero amounts - let err = execute( - deps.as_mut(), - env.clone(), - mock_info(ASTRO_TOKEN, &[]), - astroport_governance::hub::ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: CW20ICS20.to_string(), - amount: Uint128::zero(), - msg: to_json_binary(&astroport_governance::hub::Cw20HookMsg::TransferFailure { - receiver: user1.to_owned(), - }) - .unwrap(), - }), - ) - .unwrap_err(); - - assert!(matches!(err, ContractError::ZeroAmount {})); - - // Add a valid failure for user - execute( - deps.as_mut(), - env.clone(), - mock_info(ASTRO_TOKEN, &[]), - astroport_governance::hub::ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: CW20ICS20.to_string(), - amount: user1_funds, - msg: to_json_binary(&astroport_governance::hub::Cw20HookMsg::TransferFailure { - receiver: user1.to_owned(), - }) - .unwrap(), - }), - ) - .unwrap(); - - // Verify that the amount was added to the user's balance - let balance = query( - deps.as_ref(), - env.clone(), - astroport_governance::hub::QueryMsg::UserFunds { - user: Addr::unchecked(user1), - }, - ) - .unwrap(); - - assert_eq!( - balance, - to_json_binary(&HubBalance { - balance: user1_funds - }) - .unwrap() - ); - - execute( - deps.as_mut(), - env, - mock_info(ASTRO_TOKEN, &[]), - astroport_governance::hub::ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: CW20ICS20.to_string(), - amount: user2_funds, - msg: to_json_binary(&astroport_governance::hub::Cw20HookMsg::TransferFailure { - receiver: user2.to_owned(), - }) - .unwrap(), - }), - ) - .unwrap(); - - // Verify that the amount was added to the user's balance - let stuck_funds = USER_FUNDS - .load(&deps.storage, &Addr::unchecked(user2)) - .unwrap(); - - assert_eq!(stuck_funds, user2_funds); - } - - // Test Cases: - // - // Expect Success - // - Memo is sent from authorised CW20-ICS20 contract - // - // Expect Error - // - Memo's sent from anywhere other than the CW20-ICS20 contract - // - Memo's sent with tokens other than ASTRO - // - Memo's sent with no funds - #[test] - fn receive_memo_auth_checks() { - let (mut deps, env, info) = mock_all(OWNER); - - let user1 = "user1"; - let user1_funds = Uint128::from(100u128); - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::hub::InstantiateMsg { - owner: OWNER.to_string(), - assembly_addr: ASSEMBLY.to_string(), - cw20_ics20_addr: CW20ICS20.to_string(), - staking_addr: STAKING.to_string(), - generator_controller_addr: GENERATOR_CONTROLLER.to_string(), - ibc_timeout_seconds: 10, - }, - ) - .unwrap(); - - // Set up a valid IBC connection - setup_channel(deps.as_mut(), env.clone()); - - // Add an allowed Outpost - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::hub::ExecuteMsg::AddOutpost { - outpost_addr: "outpost".to_string(), - outpost_channel: "channel-3".to_string(), - cw20_ics20_channel: "channel-1".to_string(), - }, - ) - .unwrap(); - - // Memo's can only be handled when sent by the CW20-ICS20 contract - let err = execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::hub::ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: CW20ICS20.to_string(), - amount: user1_funds, - msg: to_json_binary(&astroport_governance::hub::Cw20HookMsg::OutpostMemo { - channel: "channel-1".to_string(), - sender: user1.to_string(), - receiver: MOCK_CONTRACT_ADDR.to_string(), - memo: "{\"stake\":{}}".to_string(), - }) - .unwrap(), - }), - ) - .unwrap_err(); - - assert!(matches!(err, ContractError::Unauthorized {})); - - // Memo's must only accept ASTRO tokens - let err = execute( - deps.as_mut(), - env.clone(), - mock_info(CW20ICS20, &[]), - astroport_governance::hub::ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: CW20ICS20.to_string(), - amount: user1_funds, - msg: to_json_binary(&astroport_governance::hub::Cw20HookMsg::OutpostMemo { - channel: "channel-1".to_string(), - sender: user1.to_string(), - receiver: MOCK_CONTRACT_ADDR.to_string(), - memo: "{\"stake\":{}}".to_string(), - }) - .unwrap(), - }), - ) - .unwrap_err(); - - assert!(matches!(err, ContractError::Unauthorized {})); - - // Memo will must not accept zero amounts - let err = execute( - deps.as_mut(), - env, - mock_info(ASTRO_TOKEN, &[]), - astroport_governance::hub::ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: CW20ICS20.to_string(), - amount: Uint128::zero(), - msg: to_json_binary(&astroport_governance::hub::Cw20HookMsg::OutpostMemo { - channel: "channel-1".to_string(), - sender: user1.to_string(), - receiver: MOCK_CONTRACT_ADDR.to_string(), - memo: "{\"stake\":{}}".to_string(), - }) - .unwrap(), - }), - ) - .unwrap_err(); - - assert!(matches!(err, ContractError::ZeroAmount {})); - } - - // Test Cases: - // - // Expect Success - // - Invalid memo is received and handled - // - // Expect Error - // - Calling this from an unauthorised contract must fail - #[test] - fn receive_invalid_memo() { - let (mut deps, env, info) = mock_all(OWNER); - - let user1 = "user1"; - let user1_funds = Uint128::from(100u128); - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::hub::InstantiateMsg { - owner: OWNER.to_string(), - assembly_addr: ASSEMBLY.to_string(), - cw20_ics20_addr: CW20ICS20.to_string(), - staking_addr: STAKING.to_string(), - generator_controller_addr: GENERATOR_CONTROLLER.to_string(), - ibc_timeout_seconds: 10, - }, - ) - .unwrap(); - - // Set up a valid IBC connection - setup_channel(deps.as_mut(), env.clone()); - - // Add an allowed Outpost - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::hub::ExecuteMsg::AddOutpost { - outpost_addr: "outpost".to_string(), - outpost_channel: "channel-3".to_string(), - cw20_ics20_channel: "channel-1".to_string(), - }, - ) - .unwrap(); - - // Send an invalid memo / broken JSON sent to us must fail - let err = execute( - deps.as_mut(), - env.clone(), - mock_info(ASTRO_TOKEN, &[]), - astroport_governance::hub::ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: CW20ICS20.to_string(), - amount: user1_funds, - msg: to_json_binary(&astroport_governance::hub::Cw20HookMsg::OutpostMemo { - channel: "channel-1".to_string(), - sender: user1.to_string(), - receiver: MOCK_CONTRACT_ADDR.to_string(), - memo: "{\"stak}}".to_string(), - }) - .unwrap(), - }), - ) - .unwrap_err(); - - assert!(matches!( - err, - ContractError::InvalidMemo { - reason: SerdeError::EofWhileParsingString - } - )); - - // Send an unknown memo action - let err = execute( - deps.as_mut(), - env, - mock_info(ASTRO_TOKEN, &[]), - astroport_governance::hub::ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: CW20ICS20.to_string(), - amount: user1_funds, - msg: to_json_binary(&astroport_governance::hub::Cw20HookMsg::OutpostMemo { - channel: "channel-1".to_string(), - sender: user1.to_string(), - receiver: MOCK_CONTRACT_ADDR.to_string(), - memo: "{\"staking\":{}}".to_string(), - }) - .unwrap(), - }), - ) - .unwrap_err(); - - assert!(matches!( - err, - ContractError::InvalidMemo { - reason: SerdeError::Custom(_) - } - )); - } - - // Test Cases: - // - // Expect Success - // - Memo wasn't intended for us, forward funds - #[test] - fn receive_standard_transfer_memo() { - let (mut deps, env, info) = mock_all(OWNER); - - let user1 = "user1"; - let user1_funds = Uint128::from(100u128); - let receiving_user = "user2"; - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::hub::InstantiateMsg { - owner: OWNER.to_string(), - assembly_addr: ASSEMBLY.to_string(), - cw20_ics20_addr: CW20ICS20.to_string(), - staking_addr: STAKING.to_string(), - generator_controller_addr: GENERATOR_CONTROLLER.to_string(), - ibc_timeout_seconds: 10, - }, - ) - .unwrap(); - - // Set up a valid IBC connection - setup_channel(deps.as_mut(), env.clone()); - - // Add an allowed Outpost - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::hub::ExecuteMsg::AddOutpost { - outpost_addr: "outpost".to_string(), - outpost_channel: "channel-3".to_string(), - cw20_ics20_channel: "channel-1".to_string(), - }, - ) - .unwrap(); - - // Send an unknown memo action - let err = execute( - deps.as_mut(), - env, - mock_info(ASTRO_TOKEN, &[]), - astroport_governance::hub::ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: CW20ICS20.to_string(), - amount: user1_funds, - msg: to_json_binary(&astroport_governance::hub::Cw20HookMsg::OutpostMemo { - channel: "channel-1".to_string(), - sender: user1.to_string(), - receiver: receiving_user.to_string(), - memo: "Hello fren, have some ASTRO".to_string(), - }) - .unwrap(), - }), - ) - .unwrap_err(); - - assert!(matches!(err, ContractError::InvalidDestination {})); - } - - // Test Cases: - // - // Expect Success - // - Memo was a staking instruction, stake funds - #[test] - fn receive_stake_memo() { - let (mut deps, env, info) = mock_all(OWNER); - - let user1 = "user1"; - let user1_funds = Uint128::from(100u128); - let ibc_timeout_seconds = 10u64; - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::hub::InstantiateMsg { - owner: OWNER.to_string(), - assembly_addr: ASSEMBLY.to_string(), - cw20_ics20_addr: CW20ICS20.to_string(), - staking_addr: STAKING.to_string(), - generator_controller_addr: GENERATOR_CONTROLLER.to_string(), - ibc_timeout_seconds, - }, - ) - .unwrap(); - - // Set up a valid IBC connection - setup_channel(deps.as_mut(), env.clone()); - - // Add an allowed Outpost - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::hub::ExecuteMsg::AddOutpost { - outpost_addr: "outpost".to_string(), - outpost_channel: "channel-3".to_string(), - cw20_ics20_channel: "channel-1".to_string(), - }, - ) - .unwrap(); - - // Send a valid stake memo - let res = execute( - deps.as_mut(), - env.clone(), - mock_info(ASTRO_TOKEN, &[]), - astroport_governance::hub::ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: CW20ICS20.to_string(), - amount: user1_funds, - msg: to_json_binary(&astroport_governance::hub::Cw20HookMsg::OutpostMemo { - channel: "channel-1".to_string(), - sender: user1.to_string(), - receiver: MOCK_CONTRACT_ADDR.to_owned(), - memo: "{\"stake\":{}}".to_string(), - }) - .unwrap(), - }), - ) - .unwrap(); - - // Verify that the stake message matches the expected message - let stake_msg = to_json_binary(&astroport::staking::Cw20HookMsg::Enter {}).unwrap(); - let send_msg = to_json_binary(&Cw20ExecuteMsg::Send { - contract: STAKING.to_string(), - amount: user1_funds, - msg: stake_msg, - }) - .unwrap(); - - // Verify that we see a stake message reply - assert_eq!( - res.messages[0], - SubMsg { - id: 9000, - gas_limit: None, - reply_on: ReplyOn::Success, - msg: WasmMsg::Execute { - contract_addr: ASTRO_TOKEN.to_string(), - msg: send_msg, - funds: vec![], - } - .into(), - } - ); - - // Construct the reply from the staking contract that will be returned - // to the contract - let stake_reply = Reply { - id: STAKE_ID, - result: SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: None, - }), - }; - - let res = reply(deps.as_mut(), env.clone(), stake_reply).unwrap(); - - // We must have one IBC message - assert_eq!(res.messages.len(), 1); - - // Once staked, we mint the xASTRO on the remote chain - let mint_msg = to_json_binary(&Outpost::MintXAstro { - receiver: user1.to_string(), - amount: user1_funds, - }) - .unwrap(); - - // We should see the IBC message - assert_eq!( - res.messages[0], - SubMsg { - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - msg: IbcMsg::SendPacket { - channel_id: "channel-3".to_string(), - data: mint_msg, - timeout: env.block.time.plus_seconds(ibc_timeout_seconds).into(), - } - .into(), - } - ); - - // At this point the channel must have a balance that matches the amount - let balances = query( - deps.as_ref(), - env.clone(), - astroport_governance::hub::QueryMsg::ChannelBalanceAt { - channel: "channel-3".to_string(), - timestamp: Uint64::from(env.block.time.seconds()), - }, - ) - .unwrap(); - - let expected = HubBalance { - balance: user1_funds, - }; - - assert_eq!(balances, to_json_binary(&expected).unwrap()); - - // At this point the total channel balance must have a balance that matches the amount - let total_balance = query( - deps.as_ref(), - env.clone(), - astroport_governance::hub::QueryMsg::TotalChannelBalancesAt { - timestamp: Uint64::from(env.block.time.seconds()), - }, - ) - .unwrap(); - - let total_expected = HubBalance { - balance: user1_funds, - }; - - assert_eq!(total_balance, to_json_binary(&total_expected).unwrap()); - } - - // Test Cases: - // - // Expect Success - // - Memo was a staking instruction, stake funds - #[test] - fn receive_stake_xastro_mint_timeout() { - let (mut deps, env, info) = mock_all(OWNER); - - let user1 = "user1"; - let user1_funds = Uint128::from(100u128); - let ibc_timeout_seconds = 10u64; - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::hub::InstantiateMsg { - owner: OWNER.to_string(), - assembly_addr: ASSEMBLY.to_string(), - cw20_ics20_addr: CW20ICS20.to_string(), - staking_addr: STAKING.to_string(), - generator_controller_addr: GENERATOR_CONTROLLER.to_string(), - ibc_timeout_seconds, - }, - ) - .unwrap(); - - // Set up a valid IBC connection - setup_channel(deps.as_mut(), env.clone()); - - // Add an allowed Outpost - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::hub::ExecuteMsg::AddOutpost { - outpost_addr: "outpost".to_string(), - outpost_channel: "channel-3".to_string(), - cw20_ics20_channel: "channel-1".to_string(), - }, - ) - .unwrap(); - - // Mint some xASTRO that we can trigger a timeout for - // Send a valid stake memo - let res = execute( - deps.as_mut(), - env.clone(), - mock_info(ASTRO_TOKEN, &[]), - astroport_governance::hub::ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: CW20ICS20.to_string(), - amount: user1_funds, - msg: to_json_binary(&astroport_governance::hub::Cw20HookMsg::OutpostMemo { - channel: "channel-1".to_string(), - sender: user1.to_string(), - receiver: MOCK_CONTRACT_ADDR.to_owned(), - memo: "{\"stake\":{}}".to_string(), - }) - .unwrap(), - }), - ) - .unwrap(); - - // Verify that the stake message matches the expected message - let stake_msg = to_json_binary(&astroport::staking::Cw20HookMsg::Enter {}).unwrap(); - let send_msg = to_json_binary(&Cw20ExecuteMsg::Send { - contract: STAKING.to_string(), - amount: user1_funds, - msg: stake_msg, - }) - .unwrap(); - - // Verify that we see a stake message reply - assert_eq!( - res.messages[0], - SubMsg { - id: 9000, - gas_limit: None, - reply_on: ReplyOn::Success, - msg: WasmMsg::Execute { - contract_addr: ASTRO_TOKEN.to_string(), - msg: send_msg, - funds: vec![], - } - .into(), - } - ); - - // Construct the reply from the staking contract that will be returned - // to the contract - let stake_reply = Reply { - id: STAKE_ID, - result: SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: None, - }), - }; - - let res = reply(deps.as_mut(), env.clone(), stake_reply).unwrap(); - - // We must have one IBC message - assert_eq!(res.messages.len(), 1); - - // At this point the channel must hold user1_funds of value - let balances = query( - deps.as_ref(), - env.clone(), - astroport_governance::hub::QueryMsg::ChannelBalanceAt { - channel: "channel-3".to_string(), - timestamp: Uint64::from(env.block.time.seconds()), - }, - ) - .unwrap(); - - let expected = HubBalance { - balance: user1_funds, - }; - - assert_eq!(balances, to_json_binary(&expected).unwrap()); - - // At this point the total channel balance must have a balance that matches the amount - let total_balance = query( - deps.as_ref(), - env.clone(), - astroport_governance::hub::QueryMsg::TotalChannelBalancesAt { - timestamp: Uint64::from(env.block.time.seconds()), - }, - ) - .unwrap(); - - let total_expected = HubBalance { - balance: user1_funds, - }; - - assert_eq!(total_balance, to_json_binary(&total_expected).unwrap()); - - // Trigger a timeout on minting xASTRO remotely - let mint_msg = to_json_binary(&Outpost::MintXAstro { - receiver: user1.to_owned(), - amount: user1_funds, - }) - .unwrap(); - let packet = IbcPacket::new( - mint_msg, - IbcEndpoint { - port_id: format!("wasm.{}", MOCK_CONTRACT_ADDR), - channel_id: "channel-3".to_string(), - }, - IbcEndpoint { - port_id: "wasm.outpost".to_string(), - channel_id: "channel-5".to_string(), - }, - 3, - env.block.time.plus_seconds(ibc_timeout_seconds).into(), - ); - - // When the timeout occurs, we should see an unstake message to return the ASTRO to the user - let timeout_packet = IbcPacketTimeoutMsg::new(packet, Addr::unchecked("relayer")); - let res = ibc_packet_timeout(deps.as_mut(), env.clone(), timeout_packet).unwrap(); - - // Should have exactly one message - assert_eq!(res.messages.len(), 1); - - // Verify that the unstake message matches the expected message - let unstake_msg = to_json_binary(&astroport::staking::Cw20HookMsg::Leave {}).unwrap(); - let send_msg = to_json_binary(&Cw20ExecuteMsg::Send { - contract: STAKING.to_string(), - amount: user1_funds, - msg: unstake_msg, - }) - .unwrap(); - - // We should see the unstake SubMessagge - assert_eq!( - res.messages[0], - SubMsg { - id: 9001, - gas_limit: None, - reply_on: ReplyOn::Success, - msg: WasmMsg::Execute { - contract_addr: XASTRO_TOKEN.to_string(), - msg: send_msg, - funds: vec![], - } - .into(), - } - ); - - // At this point the channel must still hold the tokens - let balances = query( - deps.as_ref(), - env.clone(), - astroport_governance::hub::QueryMsg::ChannelBalanceAt { - channel: "channel-3".to_string(), - timestamp: Uint64::from(env.block.time.seconds()), - }, - ) - .unwrap(); - - let expected = HubBalance { - balance: user1_funds, - }; - - assert_eq!(balances, to_json_binary(&expected).unwrap()); - - // And the total must still match - let total_balance = query( - deps.as_ref(), - env.clone(), - astroport_governance::hub::QueryMsg::TotalChannelBalancesAt { - timestamp: Uint64::from(env.block.time.seconds()), - }, - ) - .unwrap(); - - let total_expected = HubBalance { - balance: user1_funds, - }; - - assert_eq!(total_balance, to_json_binary(&total_expected).unwrap()); - - // Construct the reply from the staking contract that will be returned - // to the contract - let unstake_reply = Reply { - id: UNSTAKE_ID, - result: SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: None, - }), - }; - - let res = reply(deps.as_mut(), env.clone(), unstake_reply).unwrap(); - - // We must have one CW20-ICS20 transfer message - assert_eq!(res.messages.len(), 1); - - // At this point the channel must have a zero balance after minting remotely - // failed and the tokens were unstaked - let balances = query( - deps.as_ref(), - env.clone(), - astroport_governance::hub::QueryMsg::ChannelBalanceAt { - channel: "channel-3".to_string(), - timestamp: Uint64::from(env.block.time.seconds()), - }, - ) - .unwrap(); - - let expected = HubBalance { - balance: Uint128::zero(), - }; - - assert_eq!(balances, to_json_binary(&expected).unwrap()); - - // And now it shoul be zero - let total_balance = query( - deps.as_ref(), - env.clone(), - astroport_governance::hub::QueryMsg::TotalChannelBalancesAt { - timestamp: Uint64::from(env.block.time.seconds()), - }, - ) - .unwrap(); - - let total_expected = HubBalance { - balance: Uint128::zero(), - }; - - assert_eq!(total_balance, to_json_binary(&total_expected).unwrap()); - - // The rest of the unstaking flow is covered in ibc_staking tests - } -} diff --git a/contracts/hub/src/ibc.rs b/contracts/hub/src/ibc.rs deleted file mode 100644 index 492049b3..00000000 --- a/contracts/hub/src/ibc.rs +++ /dev/null @@ -1,678 +0,0 @@ -use astroport::querier::query_token_balance; -use cosmwasm_std::{ - entry_point, from_json, to_json_binary, Deps, DepsMut, Env, Ibc3ChannelOpenResponse, - IbcBasicResponse, IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg, - IbcChannelOpenResponse, IbcOrder, IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg, - IbcReceiveResponse, Never, StdError, StdResult, SubMsg, -}; - -use astroport_governance::interchain::{get_contract_from_ibc_port, Hub, Outpost, Response}; - -use crate::{ - error::ContractError, - ibc_governance::{ - handle_ibc_blacklisted, handle_ibc_cast_assembly_vote, handle_ibc_cast_emissions_vote, - handle_ibc_unlock, - }, - ibc_misc::handle_ibc_withdraw_stuck_funds, - ibc_query::handle_ibc_query_proposal, - ibc_staking::{construct_unstake_msg, handle_ibc_unstake}, - reply::UNSTAKE_ID, - state::{ReplyData, CONFIG, OUTPOSTS, REPLY_DATA}, -}; - -pub const IBC_APP_VERSION: &str = "astroport-outpost-v1"; -pub const IBC_ORDERING: IbcOrder = IbcOrder::Unordered; - -/// Handle the opening of a new IBC channel -/// -/// We verify that the connection is using the correct configuration -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn ibc_channel_open( - _deps: DepsMut, - _env: Env, - msg: IbcChannelOpenMsg, -) -> Result { - let channel = msg.channel(); - - if channel.order != IBC_ORDERING { - return Err(ContractError::Std(StdError::generic_err( - "Ordering is invalid. The channel must be unordered".to_string(), - ))); - } - if channel.version != IBC_APP_VERSION { - return Err(ContractError::Std(StdError::generic_err(format!( - "Must set version to `{IBC_APP_VERSION}`" - )))); - } - - if let Some(counter_version) = msg.counterparty_version() { - if counter_version != IBC_APP_VERSION { - return Err(ContractError::Std(StdError::generic_err(format!( - "Counterparty version must be `{IBC_APP_VERSION}`" - )))); - } - } - - Ok(Some(Ibc3ChannelOpenResponse { - version: IBC_APP_VERSION.to_string(), - })) -} - -/// Handle the connection of a new IBC channel -/// -/// We verify that the connection is being made to an allowed Outpost and -/// if the channel has not been set, add it -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn ibc_channel_connect( - deps: DepsMut, - _env: Env, - msg: IbcChannelConnectMsg, -) -> Result { - let channel = msg.channel(); - - if let Some(counter_version) = msg.counterparty_version() { - if counter_version != IBC_APP_VERSION { - return Err(ContractError::Std(StdError::generic_err(format!( - "Counterparty version must be `{IBC_APP_VERSION}`" - )))); - } - } - - // We allow any contract with any channel to connect, but we will only - // allow messages from whitelisted Outposts to be accepted - // If a channel has already been established, we will reject the connection - let counterparty_port = - get_contract_from_ibc_port(channel.counterparty_endpoint.port_id.as_str()); - if let Some(channels) = OUTPOSTS.may_load(deps.storage, counterparty_port)? { - return Err(ContractError::ChannelAlreadyEstablished { - channel_id: channels.outpost, - }); - } - - Ok(IbcBasicResponse::new() - .add_attribute("action", "ibc_connect") - .add_attribute("channel_id", &channel.endpoint.channel_id)) -} - -/// Handle the receiving the packets while wrapping the actual call to provide -/// returning errors as an acknowledgement. -/// -/// This allows the original caller from another chain to handle the failure -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn ibc_packet_receive( - deps: DepsMut, - env: Env, - msg: IbcPacketReceiveMsg, -) -> Result { - do_packet_receive(deps, env, msg).or_else(|err| { - // Construct an error acknowledgement that can be handled on the Outpost - let ack_data = to_json_binary(&Response::new_error(err.to_string())).unwrap(); - - Ok(IbcReceiveResponse::new() - .add_attribute("action", "ibc_packet_receive") - .add_attribute("error", err.to_string()) - .set_ack(ack_data)) - }) -} - -/// Process the received packet and return the response -/// -/// Packets are expected to be wrapped in the Hub format, if it doesn't conform -/// it will be failed. -/// -/// If a ContractError is returned, it will be wrapped into a Response -/// containing the error to be handled on the Outpost -fn do_packet_receive( - deps: DepsMut, - env: Env, - msg: IbcPacketReceiveMsg, -) -> Result { - block_unauthorized_packets( - deps.as_ref(), - msg.packet.src.port_id.clone(), - msg.packet.dest.channel_id.to_string(), - )?; - - // Parse the packet data into a Hub message - let outpost_msg: Hub = from_json(&msg.packet.data)?; - match outpost_msg { - Hub::QueryProposal { id } => handle_ibc_query_proposal(deps, id), - Hub::CastAssemblyVote { - proposal_id, - voter, - vote_option, - voting_power, - } => handle_ibc_cast_assembly_vote( - deps, - msg.packet.dest.channel_id, - proposal_id, - voter, - vote_option, - voting_power, - ), - Hub::CastEmissionsVote { - voter, - voting_power, - votes, - } => handle_ibc_cast_emissions_vote( - deps, - env, - msg.packet.dest.channel_id, - voter, - voting_power, - votes, - ), - Hub::Unstake { receiver, amount } => { - handle_ibc_unstake(deps, env, msg.packet.dest.channel_id, receiver, amount) - } - Hub::KickUnlockedVoter { voter } => handle_ibc_unlock(deps, voter), - Hub::KickBlacklistedVoter { voter } => handle_ibc_blacklisted(deps, voter), - Hub::WithdrawFunds { user } => { - handle_ibc_withdraw_stuck_funds(deps, msg.packet.dest.channel_id, user) - } - _ => Err(ContractError::NotIBCAction { - action: outpost_msg.to_string(), - }), - } -} - -/// Handle IBC packet timeouts for messages we sent -/// -/// Timeouts will cause certain actions to be reversed and, when applicable, return -/// funds to the user -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn ibc_packet_timeout( - deps: DepsMut, - env: Env, - msg: IbcPacketTimeoutMsg, -) -> Result { - let failed_msg: Outpost = from_json(&msg.packet.data)?; - match failed_msg { - Outpost::MintXAstro { receiver, amount } => { - let config = CONFIG.load(deps.storage)?; - - // If we get a timeout on a packet to mint remote xASTRO - // we need to undo the transaction and return the original ASTRO - // If we get another timeout returning the original ASTRO the funds - // will be held in this contract to withdraw later - let wasm_msg = construct_unstake_msg( - deps.storage, - deps.querier, - env.clone(), - msg.packet.src.channel_id.clone(), - receiver.clone(), - amount, - )?; - let sub_msg = SubMsg::reply_on_success(wasm_msg, UNSTAKE_ID); - - // We don't decrease the channel balance here, but only after unstaking - let current_astro_balance = query_token_balance( - &deps.querier, - config.token_addr.to_string(), - env.contract.address, - )?; - - // Temporarily save the data needed for the SubMessage reply - let reply_data = ReplyData { - receiver: receiver.clone(), - receiving_channel: msg.packet.src.channel_id, - value: current_astro_balance, - original_value: amount, - }; - REPLY_DATA.save(deps.storage, &reply_data)?; - - Ok(IbcBasicResponse::new() - .add_attribute("action", "ibc_packet_timeout") - .add_submessage(sub_msg) - .add_attribute("original_action", "mint_remote_xastro") - .add_attribute("original_receiver", receiver) - .add_attribute("original_amount", amount.to_string())) - } - } -} - -/// Handle IBC packet acknowledgements for messages we sent -/// -/// We don't need acks for now, we handle failures instead -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn ibc_packet_ack( - _deps: DepsMut, - _env: Env, - _msg: IbcPacketAckMsg, -) -> Result { - Ok(IbcBasicResponse::new().add_attribute("action", "ibc_packet_ack")) -} - -/// Handle the closing of IBC channels, which we don't allow -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn ibc_channel_close( - _deps: DepsMut, - _env: Env, - _channel: IbcChannelCloseMsg, -) -> StdResult { - Err(StdError::generic_err("Closing channel is not allowed")) -} - -/// Checks the provided port against the Outpost list. -/// -/// If the port doesn't exist or the channel doesn't match, this function will -/// return an error, effectively blocking the packet. -fn block_unauthorized_packets( - deps: Deps, - source_port_id: String, - destination_channel_id: String, -) -> Result<(), ContractError> { - let counterparty_port = get_contract_from_ibc_port(source_port_id.as_str()); - - let outpost_channels = OUTPOSTS.load(deps.storage, counterparty_port)?; - if outpost_channels.outpost != destination_channel_id { - return Err(ContractError::Unauthorized {}); - } - Ok(()) -} - -#[cfg(test)] -mod tests { - use cosmwasm_std::{ - testing::{mock_info, MOCK_CONTRACT_ADDR}, - Addr, IbcAcknowledgement, IbcEndpoint, IbcPacket, Uint128, - }; - - use super::*; - use crate::{ - contract::instantiate, - execute::execute, - mock::{ - mock_all, mock_channel, mock_ibc_packet, setup_channel, ASSEMBLY, CW20ICS20, - GENERATOR_CONTROLLER, OWNER, STAKING, - }, - }; - - // Test Cases: - // - // Expect Success - // - Creating a channel with correct settings - // - // Expect Error - // - Attempt to create a channel with an invalid version - // - Attempt to create a channel with an invalid ordering - // - Attempt to create a channel before registering an Outpost - // - Attempt to create a channel with an unauthorize Outpost address - #[test] - fn ibc_open_channel() { - let (mut deps, env, info) = mock_all(OWNER); - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::hub::InstantiateMsg { - owner: OWNER.to_string(), - assembly_addr: ASSEMBLY.to_string(), - cw20_ics20_addr: CW20ICS20.to_string(), - staking_addr: STAKING.to_string(), - generator_controller_addr: GENERATOR_CONTROLLER.to_string(), - ibc_timeout_seconds: 10, - }, - ) - .unwrap(); - - // A connection with invalid ordering is not allowed - let channel = mock_channel( - "wasm.hub", - "channel-2", - "wasm.unknown_contract", - "channel-7", - IbcOrder::Ordered, - "non-astroport-v1", - ); - let open_msg = IbcChannelOpenMsg::new_init(channel); - let err = ibc_channel_open(deps.as_mut(), env.clone(), open_msg).unwrap_err(); - assert_eq!( - err, - ContractError::Std(StdError::generic_err( - "Ordering is invalid. The channel must be unordered" - )) - ); - - // A connection with invalid version is not allowed - let channel = mock_channel( - "wasm.hub", - "channel-2", - "wasm.unknown_contract", - "channel-7", - IbcOrder::Unordered, - "non-astroport-v1", - ); - let open_msg = IbcChannelOpenMsg::new_init(channel); - let err = ibc_channel_open(deps.as_mut(), env.clone(), open_msg).unwrap_err(); - assert_eq!( - err, - ContractError::Std(StdError::generic_err( - "Must set version to `astroport-outpost-v1`" - )) - ); - - // A connection with correct settings is allowed - let channel = mock_channel( - "wasm.hub", - "channel-2", - "wasm.unknown_contract", - "channel-7", - IbcOrder::Unordered, - IBC_APP_VERSION, - ); - let open_msg = IbcChannelOpenMsg::new_init(channel); - ibc_channel_open(deps.as_mut(), env, open_msg).unwrap(); - - // let connect_msg = IbcChannelConnectMsg::new_ack(channel, IBC_APP_VERSION); - // ibc_channel_connect(deps.as_mut(), env.clone(), connect_msg).unwrap(); - } - - // Test Cases: - // - // Expect Success - // - Creating a channel with an allowed Outpost - // - // Expect Error - // - Attempt to connect a channel with an invalid version - // - Attempt to connect a channel before registering an Outpost - // - Attempt to connect a channel with an unauthorize Outpost address - #[test] - fn ibc_connect_channel() { - let (mut deps, env, info) = mock_all(OWNER); - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::hub::InstantiateMsg { - owner: OWNER.to_string(), - assembly_addr: ASSEMBLY.to_string(), - cw20_ics20_addr: CW20ICS20.to_string(), - staking_addr: STAKING.to_string(), - generator_controller_addr: GENERATOR_CONTROLLER.to_string(), - ibc_timeout_seconds: 10, - }, - ) - .unwrap(); - - // Opening a connection with unknown contracts is allowed - let channel = mock_channel( - "wasm.hub", - "channel-2", - "wasm.unknown_contract", - "channel-7", - IbcOrder::Unordered, - IBC_APP_VERSION, - ); - // This should pass - let open_msg = IbcChannelOpenMsg::new_init(channel.clone()); - ibc_channel_open(deps.as_mut(), env.clone(), open_msg).unwrap(); - let connect_msg = IbcChannelConnectMsg::new_ack(channel, IBC_APP_VERSION); - ibc_channel_connect(deps.as_mut(), env.clone(), connect_msg).unwrap(); - - // Now set the allowed Outpost - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::hub::ExecuteMsg::AddOutpost { - outpost_addr: "outpost".to_string(), - outpost_channel: "channel-3".to_string(), - cw20_ics20_channel: "channel-1".to_string(), - }, - ) - .unwrap(); - - // Attempting to connect again should now fail - let channel = mock_channel( - "wasm.hub", - "channel-3", - "wasm.outpost", - "channel-7", - IbcOrder::Unordered, - IBC_APP_VERSION, - ); - let open_msg = IbcChannelOpenMsg::new_init(channel.clone()); - ibc_channel_open(deps.as_mut(), env.clone(), open_msg).unwrap(); - let connect_msg = IbcChannelConnectMsg::new_ack(channel, IBC_APP_VERSION); - let err = ibc_channel_connect(deps.as_mut(), env, connect_msg).unwrap_err(); - - assert_eq!( - err, - ContractError::ChannelAlreadyEstablished { - channel_id: "channel-3".to_string() - } - ); - } - - // Test Cases: - // - // Expect Success - // - Packets are acknowledged without error - #[test] - fn ibc_ack_packet() { - let (mut deps, env, info) = mock_all(OWNER); - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::hub::InstantiateMsg { - owner: OWNER.to_string(), - assembly_addr: ASSEMBLY.to_string(), - cw20_ics20_addr: CW20ICS20.to_string(), - staking_addr: STAKING.to_string(), - generator_controller_addr: GENERATOR_CONTROLLER.to_string(), - ibc_timeout_seconds: 10, - }, - ) - .unwrap(); - - // Set up valid IBC channel - setup_channel(deps.as_mut(), env.clone()); - - // Add allowed Outpost - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::hub::ExecuteMsg::AddOutpost { - outpost_addr: "outpost".to_string(), - outpost_channel: "channel-3".to_string(), - cw20_ics20_channel: "channel-1".to_string(), - }, - ) - .unwrap(); - - // The Hub doesn't do anything with acks, we just check that - // it doesn't fail - let ack = IbcAcknowledgement::new( - to_json_binary(&Response::Result { - action: None, - address: None, - error: None, - }) - .unwrap(), - ); - let mint_msg = to_json_binary(&Outpost::MintXAstro { - receiver: "user".to_owned(), - amount: Uint128::one(), - }) - .unwrap(); - let original_packet = IbcPacket::new( - mint_msg, - IbcEndpoint { - port_id: format!("wasm.{}", MOCK_CONTRACT_ADDR), - channel_id: "channel-3".to_string(), - }, - IbcEndpoint { - port_id: "wasm.outpost".to_string(), - channel_id: "channel-3".to_string(), - }, - 3, - env.block.time.plus_seconds(10).into(), - ); - - let ack_msg = IbcPacketAckMsg::new(ack, original_packet, Addr::unchecked("relayer")); - ibc_packet_ack(deps.as_mut(), env, ack_msg).unwrap(); - } - - // Test Cases: - // - // Expect Success - // - Creating a channel with an allowed Outpost - // - // Expect Error - // - Attempt to connect a channel with an invalid version - // - Attempt to connect a channel before registering an Outpost - // - Attempt to connect a channel with an unauthorize Outpost address - #[test] - fn ibc_close_channel() { - let (mut deps, env, info) = mock_all(OWNER); - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::hub::InstantiateMsg { - owner: OWNER.to_string(), - assembly_addr: ASSEMBLY.to_string(), - cw20_ics20_addr: CW20ICS20.to_string(), - staking_addr: STAKING.to_string(), - generator_controller_addr: GENERATOR_CONTROLLER.to_string(), - ibc_timeout_seconds: 10, - }, - ) - .unwrap(); - - // Set up a valid IBC channel - setup_channel(deps.as_mut(), env.clone()); - - // Add an allowed Outpost - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::hub::ExecuteMsg::AddOutpost { - outpost_addr: "outpost".to_string(), - outpost_channel: "channel-3".to_string(), - cw20_ics20_channel: "channel-1".to_string(), - }, - ) - .unwrap(); - - let channel = mock_channel( - "wasm.hub", - "channel-3", - "wasm.outpost", - "channel-7", - IbcOrder::Unordered, - IBC_APP_VERSION, - ); - - let close_msg = IbcChannelCloseMsg::new_init(channel); - let err = ibc_channel_close(deps.as_mut(), env, close_msg).unwrap_err(); - - assert_eq!(err, StdError::generic_err("Closing channel is not allowed")); - } - - // Test Cases: - // - // Expect Success - // - Only packets from the whitelisted Outpost contract and channel are allowed - // - // Expect Error - // - Attempt to send a packet from an invalid counterparty port - // - Attempt to send a packet from a valid port but invalid channel - #[test] - fn ibc_check_receive_auth() { - let (mut deps, env, info) = mock_all(OWNER); - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::hub::InstantiateMsg { - owner: OWNER.to_string(), - assembly_addr: ASSEMBLY.to_string(), - cw20_ics20_addr: CW20ICS20.to_string(), - staking_addr: STAKING.to_string(), - generator_controller_addr: GENERATOR_CONTROLLER.to_string(), - ibc_timeout_seconds: 10, - }, - ) - .unwrap(); - - // Create a random channel - // Creating an unauthorised channel is allowed - let channel = mock_channel( - "wasm.hub", - "channel-100", - "wasm.outpost", - "channel-150", - IbcOrder::Unordered, - IBC_APP_VERSION, - ); - let open_msg = IbcChannelOpenMsg::new_init(channel.clone()); - ibc_channel_open(deps.as_mut(), env.clone(), open_msg).unwrap(); - let connect_msg = IbcChannelConnectMsg::new_ack(channel, IBC_APP_VERSION); - ibc_channel_connect(deps.as_mut(), env.clone(), connect_msg).unwrap(); - - // Attempt to unstake via the unauthorised channel - // This must always fail as the port and channel is not whitelisted - // We don't need to test every type of Hub message as the safety check - // happens in do_packet_receive which is the entrypoint for all messages - // being received - let ibc_unstake_msg = to_json_binary(&Hub::Unstake { - receiver: "unstaker".to_string(), - amount: Uint128::from(100u128), - }) - .unwrap(); - let recv_packet = mock_ibc_packet("channel-100", ibc_unstake_msg.clone()); - - let msg = IbcPacketReceiveMsg::new(recv_packet, Addr::unchecked("relayer")); - let res = ibc_packet_receive(deps.as_mut(), env.clone(), msg).unwrap(); - let ack: Response = from_json(&res.acknowledgement).unwrap(); - match ack { - Response::Result { error, .. } => { - assert!( - error == Some("astroport_hub::state::OutpostChannels not found".to_string()) - ); - } - _ => panic!("Wrong response type"), - } - - // Whitelist the Outpost - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::hub::ExecuteMsg::AddOutpost { - outpost_addr: "outpost".to_string(), - outpost_channel: "channel-100".to_string(), - cw20_ics20_channel: "channel-1".to_string(), - }, - ) - .unwrap(); - - // Attempt to unstake again via an unauthorised Outpost - let recv_packet = mock_ibc_packet("channel-55", ibc_unstake_msg.clone()); - let msg = IbcPacketReceiveMsg::new(recv_packet, Addr::unchecked("relayer")); - let res = ibc_packet_receive(deps.as_mut(), env.clone(), msg).unwrap(); - let ack: Response = from_json(&res.acknowledgement).unwrap(); - match ack { - Response::Result { error, .. } => { - assert!(error == Some("Unauthorized".to_string())); - } - _ => panic!("Wrong response type"), - } - - // Attempt to unstake via the authorised Outpost - let recv_packet = mock_ibc_packet("channel-100", ibc_unstake_msg); - let msg = IbcPacketReceiveMsg::new(recv_packet, Addr::unchecked("relayer")); - ibc_packet_receive(deps.as_mut(), env, msg).unwrap(); - } -} diff --git a/contracts/hub/src/ibc_governance.rs b/contracts/hub/src/ibc_governance.rs deleted file mode 100644 index d8a1aa64..00000000 --- a/contracts/hub/src/ibc_governance.rs +++ /dev/null @@ -1,727 +0,0 @@ -use cosmwasm_std::{to_json_binary, Addr, DepsMut, Env, IbcReceiveResponse, Uint128, WasmMsg}; - -use astroport_governance::{ - assembly::{Proposal, ProposalVoteOption}, - generator_controller_lite, - interchain::Response, -}; - -use crate::{ - error::ContractError, - state::{channel_balance_at, CONFIG}, -}; - -/// Handle an IBC message to cast a vote on an Assembly proposal from an Outpost -/// and return an IBC acknowledgement -/// -/// The Outpost is responsible for checking and sending the voting power of the -/// voter, we add an additional check to make sure that the voting power is not -/// more than the xASTRO minted remotely via this channel -pub fn handle_ibc_cast_assembly_vote( - deps: DepsMut, - outpost_channel: String, - proposal_id: u64, - voter: Addr, - vote_option: ProposalVoteOption, - voting_power: Uint128, -) -> Result { - let config = CONFIG.load(deps.storage)?; - - // Cast the vote in the Assembly - let vote_msg = astroport_governance::assembly::ExecuteMsg::CastOutpostVote { - proposal_id, - voter: voter.to_string(), - vote: vote_option, - voting_power, - }; - let wasm_msg = WasmMsg::Execute { - contract_addr: config.assembly_addr.to_string(), - msg: to_json_binary(&vote_msg)?, - funds: vec![], - }; - - // Assert that the voting power does not exceed the xASTRO minted via this channel - // at the time the proposal was created - let proposal: Proposal = deps.querier.query_wasm_smart( - config.assembly_addr, - &astroport_governance::assembly::QueryMsg::Proposal { proposal_id }, - )?; - - let xastro_balance = channel_balance_at(deps.storage, &outpost_channel, proposal.start_time)?; - - if voting_power > xastro_balance { - return Err(ContractError::InvalidVotingPower {}); - } - - // If the vote succeeds, the ack will be sent back to the Outpost - let ack_data = to_json_binary(&Response::new_success( - "cast_assembly_vote".to_owned(), - voter.to_string(), - ))?; - - Ok(IbcReceiveResponse::new() - .add_message(wasm_msg) - .set_ack(ack_data)) -} - -/// Handle an IBC message to cast a vote on emissions during a voting period -/// from an Outpost and return an IBC acknowledgement -/// -/// The Outpost is responsible for checking and sending the voting power of the -/// voter, we add an additional check to make sure that the voting power is not -/// more than the xASTRO minted remotely via this channel. vxASTRO lite does -/// not boost voting power and must be equal to the deposit -pub fn handle_ibc_cast_emissions_vote( - deps: DepsMut, - env: Env, - outpost_channel: String, - voter: Addr, - voting_power: Uint128, - votes: Vec<(String, u16)>, -) -> Result { - let config = CONFIG.load(deps.storage)?; - - // Cast the emissions vote - let vote_msg = generator_controller_lite::ExecuteMsg::OutpostVote { - voter: voter.to_string(), - votes, - voting_power, - }; - let msg = WasmMsg::Execute { - contract_addr: config.generator_controller_addr.to_string(), - msg: to_json_binary(&vote_msg)?, - funds: vec![], - }; - - // Assert that the voting power does not exceed the xASTRO minted via this channel at the current block - let xastro_balance = - channel_balance_at(deps.storage, &outpost_channel, env.block.time.seconds())?; - if voting_power > xastro_balance { - return Err(ContractError::InvalidVotingPower {}); - } - - // If the vote succeeds, the ack will be sent back to the Outpost - let ack_data = to_json_binary(&Response::new_success( - "cast_emissions_vote".to_owned(), - voter.to_string(), - ))?; - - Ok(IbcReceiveResponse::new().add_message(msg).set_ack(ack_data)) -} - -/// Handle an IBC message to kick an unlocked voter from the Outpost. -/// -/// We rely on the Outpost to verify the unlock before sending it here. If this -/// transaction succeeds, the voting power will be removed immediately -pub fn handle_ibc_unlock(deps: DepsMut, user: Addr) -> Result { - let config = CONFIG.load(deps.storage)?; - - // Remove the vxASTRO voter's voting power - let unlock_msg = generator_controller_lite::ExecuteMsg::KickUnlockedOutpostVoter { - unlocked_voter: user.to_string(), - }; - let msg = WasmMsg::Execute { - contract_addr: config.generator_controller_addr.to_string(), - msg: to_json_binary(&unlock_msg)?, - funds: vec![], - }; - - // If the unlock succeeds, the ack will be sent back to the Outpost - let ack_data = to_json_binary(&Response::new_success( - "unlock".to_owned(), - user.to_string(), - ))?; - - Ok(IbcReceiveResponse::new().add_message(msg).set_ack(ack_data)) -} - -/// Handle an IBC message to kick a blacklisted voter from the Outpost. -/// -/// We rely on the Outpost to verify the blacklist before sending it here. If this -/// transaction succeeds, the voting power will be removed immediately -pub fn handle_ibc_blacklisted( - deps: DepsMut, - user: Addr, -) -> Result { - let config = CONFIG.load(deps.storage)?; - - // Remove the vxASTRO voter's voting power - let blacklist_msg = generator_controller_lite::ExecuteMsg::KickBlacklistedVoters { - blacklisted_voters: vec![user.to_string()], - }; - let msg = WasmMsg::Execute { - contract_addr: config.generator_controller_addr.to_string(), - msg: to_json_binary(&blacklist_msg)?, - funds: vec![], - }; - - // If the vote succeeds, the ack will be sent back to the Outpost - let ack_data = to_json_binary(&Response::new_success( - "kick_blacklisted".to_owned(), - user.to_string(), - ))?; - - Ok(IbcReceiveResponse::new().add_message(msg).set_ack(ack_data)) -} - -#[cfg(test)] -mod tests { - use astroport_governance::interchain::Hub; - use cosmwasm_std::{ - from_json, - testing::{mock_info, MOCK_CONTRACT_ADDR}, - IbcPacketReceiveMsg, Reply, ReplyOn, SubMsg, SubMsgResponse, SubMsgResult, - }; - use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; - - use super::*; - use crate::{ - contract::instantiate, - execute::execute, - ibc::ibc_packet_receive, - mock::{ - mock_all, mock_ibc_packet, setup_channel, ASSEMBLY, ASTRO_TOKEN, CW20ICS20, - GENERATOR_CONTROLLER, OWNER, STAKING, - }, - reply::{reply, STAKE_ID}, - }; - - // Test Cases: - // - // Expect Success - // - Submitting the vote results in an Assembly message - // - // Expect Error - // - An error is returned instead - #[test] - fn ibc_assembly_vote() { - let (mut deps, env, info) = mock_all(OWNER); - - let voter = "voter1234"; - let voting_power = Uint128::from(100u128); - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::hub::InstantiateMsg { - owner: OWNER.to_string(), - assembly_addr: ASSEMBLY.to_string(), - cw20_ics20_addr: CW20ICS20.to_string(), - staking_addr: STAKING.to_string(), - generator_controller_addr: GENERATOR_CONTROLLER.to_string(), - ibc_timeout_seconds: 10, - }, - ) - .unwrap(); - - // Set up valid IBC channel - setup_channel(deps.as_mut(), env.clone()); - - // Add allowed Outpost - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::hub::ExecuteMsg::AddOutpost { - outpost_addr: "outpost".to_string(), - outpost_channel: "channel-3".to_string(), - cw20_ics20_channel: "channel-1".to_string(), - }, - ) - .unwrap(); - - // Stake tokens to ensure the channel has a non-zero balance - let user1 = "user1"; - let user1_funds = Uint128::from(100u128); - let res = execute( - deps.as_mut(), - env.clone(), - mock_info(ASTRO_TOKEN, &[]), - astroport_governance::hub::ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: CW20ICS20.to_string(), - amount: user1_funds, - msg: to_json_binary(&astroport_governance::hub::Cw20HookMsg::OutpostMemo { - channel: "channel-1".to_string(), - sender: user1.to_string(), - receiver: MOCK_CONTRACT_ADDR.to_owned(), - memo: "{\"stake\":{}}".to_string(), - }) - .unwrap(), - }), - ) - .unwrap(); - - // Verify that the stake message matches the expected message - let stake_msg = to_json_binary(&astroport::staking::Cw20HookMsg::Enter {}).unwrap(); - let send_msg = to_json_binary(&Cw20ExecuteMsg::Send { - contract: STAKING.to_string(), - amount: user1_funds, - msg: stake_msg, - }) - .unwrap(); - - // Verify that we see a stake message reply - assert_eq!( - res.messages[0], - SubMsg { - id: 9000, - gas_limit: None, - reply_on: ReplyOn::Success, - msg: WasmMsg::Execute { - contract_addr: ASTRO_TOKEN.to_string(), - msg: send_msg, - funds: vec![], - } - .into(), - } - ); - - // Construct the reply from the staking contract that will be returned - // to the contract - let stake_reply = Reply { - id: STAKE_ID, - result: SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: None, - }), - }; - - let res = reply(deps.as_mut(), env.clone(), stake_reply).unwrap(); - - // We must have one IBC message - assert_eq!(res.messages.len(), 1); - - // At this point we now have 100 staked tokens - // We can test that voting power may not exceed this - let proposal_id = 1u64; - let vote_option = ProposalVoteOption::For; - - // Attempt a vote with double the voting power - let ibc_vote = to_json_binary(&Hub::CastAssemblyVote { - proposal_id, - voter: Addr::unchecked(voter), - vote_option: vote_option.clone(), - voting_power: voting_power.checked_add(Uint128::from(100u128)).unwrap(), - }) - .unwrap(); - let recv_packet = mock_ibc_packet("channel-3", ibc_vote); - - let msg = IbcPacketReceiveMsg::new(recv_packet, Addr::unchecked("relayer")); - let res = ibc_packet_receive(deps.as_mut(), env.clone(), msg).unwrap(); - - let hub_respone: Response = from_json(&res.acknowledgement).unwrap(); - match hub_respone { - Response::Result { error, .. } => { - assert_eq!( - error, - Some("Voting power exceeds channel balance".to_string()) - ); - } - _ => panic!("Wrong response type"), - } - - // Attempt a vote with the correct voting power - let ibc_vote = to_json_binary(&Hub::CastAssemblyVote { - proposal_id, - voter: Addr::unchecked(voter), - vote_option, - voting_power, - }) - .unwrap(); - let recv_packet = mock_ibc_packet("channel-3", ibc_vote); - - let msg = IbcPacketReceiveMsg::new(recv_packet, Addr::unchecked("relayer")); - let res = ibc_packet_receive(deps.as_mut(), env, msg).unwrap(); - - let hub_respone: Response = from_json(&res.acknowledgement).unwrap(); - match hub_respone { - Response::Result { error, .. } => { - assert!(error.is_none()); - } - _ => panic!("Wrong response type"), - } - - assert_eq!(res.messages.len(), 1); - - let assembly_msg = to_json_binary( - &astroport_governance::assembly::ExecuteMsg::CastOutpostVote { - proposal_id, - vote: ProposalVoteOption::For, - voter: voter.to_string(), - voting_power, - }, - ) - .unwrap(); - - assert_eq!( - res.messages[0], - SubMsg { - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - msg: WasmMsg::Execute { - contract_addr: ASSEMBLY.to_string(), - msg: assembly_msg, - funds: vec![], - } - .into(), - } - ); - } - - // Test Cases: - // - // Expect Success - // - Submitting the vote results in a Generator controller message - // - // Expect Error - // - An error is returned instead - #[test] - fn ibc_emissions_vote() { - let (mut deps, env, info) = mock_all(OWNER); - - let voter = "voter1234"; - let voting_power = Uint128::from(100u128); - let votes = vec![("pooladdress".to_string(), 10000)]; - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::hub::InstantiateMsg { - owner: OWNER.to_string(), - assembly_addr: ASSEMBLY.to_string(), - cw20_ics20_addr: CW20ICS20.to_string(), - staking_addr: STAKING.to_string(), - generator_controller_addr: GENERATOR_CONTROLLER.to_string(), - ibc_timeout_seconds: 10, - }, - ) - .unwrap(); - - // Set up valid IBC channel - setup_channel(deps.as_mut(), env.clone()); - - // Add allowed Outpost - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::hub::ExecuteMsg::AddOutpost { - outpost_addr: "outpost".to_string(), - outpost_channel: "channel-3".to_string(), - cw20_ics20_channel: "channel-1".to_string(), - }, - ) - .unwrap(); - - // Voting must fail if the channel balance in insufficient - let ibc_unstake = to_json_binary(&Hub::CastEmissionsVote { - voter: Addr::unchecked(voter), - voting_power, - votes: votes.clone(), - }) - .unwrap(); - let recv_packet = mock_ibc_packet("channel-3", ibc_unstake); - - let msg = IbcPacketReceiveMsg::new(recv_packet, Addr::unchecked("relayer")); - let res = ibc_packet_receive(deps.as_mut(), env.clone(), msg).unwrap(); - - let hub_respone: Response = from_json(&res.acknowledgement).unwrap(); - match hub_respone { - Response::Result { error, .. } => { - assert_eq!( - error.unwrap(), - "Voting power exceeds channel balance".to_string() - ); - } - _ => panic!("Wrong response type"), - } - - // Stake some ASTRO remotely - // Stake tokens to ensure the channel has a non-zero balance - let user1 = "user1"; - let user1_funds = Uint128::from(100u128); - let res = execute( - deps.as_mut(), - env.clone(), - mock_info(ASTRO_TOKEN, &[]), - astroport_governance::hub::ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: CW20ICS20.to_string(), - amount: user1_funds, - msg: to_json_binary(&astroport_governance::hub::Cw20HookMsg::OutpostMemo { - channel: "channel-1".to_string(), - sender: user1.to_string(), - receiver: MOCK_CONTRACT_ADDR.to_owned(), - memo: "{\"stake\":{}}".to_string(), - }) - .unwrap(), - }), - ) - .unwrap(); - - // Verify that the stake message matches the expected message - let stake_msg = to_json_binary(&astroport::staking::Cw20HookMsg::Enter {}).unwrap(); - let send_msg = to_json_binary(&Cw20ExecuteMsg::Send { - contract: STAKING.to_string(), - amount: user1_funds, - msg: stake_msg, - }) - .unwrap(); - - // Verify that we see a stake message reply - assert_eq!( - res.messages[0], - SubMsg { - id: 9000, - gas_limit: None, - reply_on: ReplyOn::Success, - msg: WasmMsg::Execute { - contract_addr: ASTRO_TOKEN.to_string(), - msg: send_msg, - funds: vec![], - } - .into(), - } - ); - - // Construct the reply from the staking contract that will be returned - // to the contract - let stake_reply = Reply { - id: STAKE_ID, - result: SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: None, - }), - }; - - let res = reply(deps.as_mut(), env.clone(), stake_reply).unwrap(); - - // We must have one IBC message - assert_eq!(res.messages.len(), 1); - - let ibc_vote = to_json_binary(&Hub::CastEmissionsVote { - voter: Addr::unchecked(voter), - voting_power, - votes: votes.clone(), - }) - .unwrap(); - let recv_packet = mock_ibc_packet("channel-3", ibc_vote); - - let msg = IbcPacketReceiveMsg::new(recv_packet, Addr::unchecked("relayer")); - let res = ibc_packet_receive(deps.as_mut(), env, msg).unwrap(); - - let hub_respone: Response = from_json(&res.acknowledgement).unwrap(); - match hub_respone { - Response::Result { error, .. } => { - assert!(error.is_none(),); - } - _ => panic!("Wrong response type"), - } - - assert_eq!(res.messages.len(), 1); - - let generator_controller_msg = to_json_binary( - &astroport_governance::generator_controller_lite::ExecuteMsg::OutpostVote { - voter: voter.to_string(), - voting_power, - votes, - }, - ) - .unwrap(); - - assert_eq!( - res.messages[0], - SubMsg { - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - msg: WasmMsg::Execute { - contract_addr: GENERATOR_CONTROLLER.to_string(), - msg: generator_controller_msg, - funds: vec![], - } - .into(), - } - ); - } - - // Test Cases: - // - // Expect Success - // - Kicking the user results in a Generator controller message - // - // Expect Error - // - An error is returned instead - #[test] - fn ibc_kick_unlocked() { - let (mut deps, env, info) = mock_all(OWNER); - - let voter = "voter1234"; - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::hub::InstantiateMsg { - owner: OWNER.to_string(), - assembly_addr: ASSEMBLY.to_string(), - cw20_ics20_addr: CW20ICS20.to_string(), - staking_addr: STAKING.to_string(), - generator_controller_addr: GENERATOR_CONTROLLER.to_string(), - ibc_timeout_seconds: 10, - }, - ) - .unwrap(); - - // Set up valid IBC channel - setup_channel(deps.as_mut(), env.clone()); - - // Add an allowed Outpost - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::hub::ExecuteMsg::AddOutpost { - outpost_addr: "outpost".to_string(), - outpost_channel: "channel-3".to_string(), - cw20_ics20_channel: "channel-1".to_string(), - }, - ) - .unwrap(); - - // Kick the voter - let ibc_kick_unlocked = to_json_binary(&Hub::KickUnlockedVoter { - voter: Addr::unchecked(voter), - }) - .unwrap(); - let recv_packet = mock_ibc_packet("channel-3", ibc_kick_unlocked); - - let msg = IbcPacketReceiveMsg::new(recv_packet, Addr::unchecked("relayer")); - let res = ibc_packet_receive(deps.as_mut(), env, msg).unwrap(); - - let hub_respone: Response = from_json(&res.acknowledgement).unwrap(); - match hub_respone { - Response::Result { error, .. } => { - assert!(error.is_none()); - } - _ => panic!("Wrong response type"), - } - - // We must have one message - assert_eq!(res.messages.len(), 1); - - // Verify that the message matches the expected message - let controller_msg = to_json_binary( - &astroport_governance::generator_controller_lite::ExecuteMsg::KickUnlockedOutpostVoter { - unlocked_voter:voter.to_string(), - }, - ) - .unwrap(); - - assert_eq!( - res.messages[0], - SubMsg { - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - msg: WasmMsg::Execute { - contract_addr: GENERATOR_CONTROLLER.to_string(), - msg: controller_msg, - funds: vec![], - } - .into(), - } - ); - } - - // Test Cases: - // - // Expect Success - // - Kicking the user results in a Generator controller message - // - // Expect Error - // - An error is returned instead - #[test] - fn ibc_kick_blacklisted() { - let (mut deps, env, info) = mock_all(OWNER); - - let voter = "voter1234"; - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::hub::InstantiateMsg { - owner: OWNER.to_string(), - assembly_addr: ASSEMBLY.to_string(), - cw20_ics20_addr: CW20ICS20.to_string(), - staking_addr: STAKING.to_string(), - generator_controller_addr: GENERATOR_CONTROLLER.to_string(), - ibc_timeout_seconds: 10, - }, - ) - .unwrap(); - - // Set up valid IBC channel - setup_channel(deps.as_mut(), env.clone()); - - // Add allowed Outpost - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::hub::ExecuteMsg::AddOutpost { - outpost_addr: "outpost".to_string(), - outpost_channel: "channel-3".to_string(), - cw20_ics20_channel: "channel-1".to_string(), - }, - ) - .unwrap(); - - // Kick the voter - let ibc_kick_blacklisted = to_json_binary(&Hub::KickBlacklistedVoter { - voter: Addr::unchecked(voter), - }) - .unwrap(); - let recv_packet = mock_ibc_packet("channel-3", ibc_kick_blacklisted); - - let msg = IbcPacketReceiveMsg::new(recv_packet, Addr::unchecked("relayer")); - let res = ibc_packet_receive(deps.as_mut(), env, msg).unwrap(); - - let hub_respone: Response = from_json(&res.acknowledgement).unwrap(); - match hub_respone { - Response::Result { error, .. } => { - assert!(error.is_none()); - } - _ => panic!("Wrong response type"), - } - - // We must have one message - assert_eq!(res.messages.len(), 1); - - // Verify that the message matches the expected message - let controller_msg = to_json_binary( - &astroport_governance::generator_controller_lite::ExecuteMsg::KickBlacklistedVoters { - blacklisted_voters: vec![voter.to_string()], - }, - ) - .unwrap(); - - assert_eq!( - res.messages[0], - SubMsg { - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - msg: WasmMsg::Execute { - contract_addr: GENERATOR_CONTROLLER.to_string(), - msg: controller_msg, - funds: vec![], - } - .into(), - } - ); - } -} diff --git a/contracts/hub/src/ibc_misc.rs b/contracts/hub/src/ibc_misc.rs deleted file mode 100644 index 8d2e67e7..00000000 --- a/contracts/hub/src/ibc_misc.rs +++ /dev/null @@ -1,230 +0,0 @@ -use astroport::cw20_ics20::TransferMsg; -use cosmwasm_std::{to_json_binary, Addr, DepsMut, IbcReceiveResponse, WasmMsg}; -use cw20::Cw20ExecuteMsg; - -use astroport_governance::interchain::Response; - -use crate::{ - error::ContractError, - state::{get_transfer_channel_from_outpost_channel, CONFIG, USER_FUNDS}, -}; - -/// Handle an IBC message to withdraw funds stuck on the Hub -/// -/// In some cases where the CW20-ICS20 IBC transfer to the Outpost user fails -/// (due to timeout or otherwise), the funds will be stuck on the Hub chain. In -/// such a case the CW20-ICS20 contract will send the funds back here and this -/// function will attempt to send them back to the user. -pub fn handle_ibc_withdraw_stuck_funds( - deps: DepsMut, - receive_channel: String, - user: Addr, -) -> Result { - let config = CONFIG.load(deps.storage)?; - - // Check if this user has any funds stuck on the Hub chain - let balance = USER_FUNDS.load(deps.storage, &user)?; - if balance.is_zero() { - return Err(ContractError::NoFunds {}); - } - - // Map the channel the request was received on to the channel used in the - // CW20-ICS20 transfer - // We can use the request channel safely as the Outpost contract enforces the - // address, we can't receive a request for funds for a different address from an - // incorrect Outpost - // Example, an Injective address can't request funds from a Neutron channel - let outpost_channels = - get_transfer_channel_from_outpost_channel(deps.as_ref(), &receive_channel)?; - - // User has funds, try to send it back to them - let transfer_msg = TransferMsg { - channel: outpost_channels.cw20_ics20, - remote_address: user.to_string(), - timeout: Some(config.ibc_timeout_seconds), - memo: None, - }; - - let send_msg = Cw20ExecuteMsg::Send { - contract: config.cw20_ics20_addr.to_string(), - amount: balance, - msg: to_json_binary(&transfer_msg)?, - }; - - let msg = WasmMsg::Execute { - contract_addr: config.token_addr.to_string(), - msg: to_json_binary(&send_msg)?, - funds: vec![], - }; - - // This acknowledgement only indicates that the withdraw was processed without - // error, not that the funds were successfully transferred over IBC to the user - let ack_data = to_json_binary(&Response::new_success( - "withdraw_funds".to_owned(), - user.to_string(), - ))?; - - // We're sending everything back to the user, so we can delete their balance - // If this fails again, the balance will be re-added from the CW20-ICS20 contract - USER_FUNDS.remove(deps.storage, &user); - - Ok(IbcReceiveResponse::new().set_ack(ack_data).add_message(msg)) -} - -#[cfg(test)] -mod tests { - use super::*; - use astroport_governance::interchain::{self, Hub}; - use cosmwasm_std::{ - from_json, testing::mock_info, IbcPacketReceiveMsg, ReplyOn, SubMsg, Uint128, - }; - use cw20::Cw20ReceiveMsg; - - use crate::{ - contract::instantiate, - execute::execute, - ibc::ibc_packet_receive, - mock::{ - mock_all, mock_ibc_packet, setup_channel, ASSEMBLY, ASTRO_TOKEN, CW20ICS20, - GENERATOR_CONTROLLER, OWNER, STAKING, - }, - }; - - // Test Cases: - // - // Expect Success - // - Withdrawing stuck funds results in IBC message - // - // Expect Error - // - When address has no funds stuck - // - // This tests that balances are correctly tracked by the contract in case of - // IBC failures that result in funds getting stuck on the Hub - #[test] - fn ibc_withdraw_stuck_funds() { - let (mut deps, env, info) = mock_all(OWNER); - - let stuck_amount = Uint128::from(100u128); - let user = "user1"; - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::hub::InstantiateMsg { - owner: OWNER.to_string(), - assembly_addr: ASSEMBLY.to_string(), - cw20_ics20_addr: CW20ICS20.to_string(), - staking_addr: STAKING.to_string(), - generator_controller_addr: GENERATOR_CONTROLLER.to_string(), - ibc_timeout_seconds: 10, - }, - ) - .unwrap(); - - // Set up valid IBC channel - setup_channel(deps.as_mut(), env.clone()); - - // Add allowed Outpost - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::hub::ExecuteMsg::AddOutpost { - outpost_addr: "outpost".to_string(), - outpost_channel: "channel-3".to_string(), - cw20_ics20_channel: "channel-1".to_string(), - }, - ) - .unwrap(); - - // Add a valid failure - execute( - deps.as_mut(), - env.clone(), - mock_info(ASTRO_TOKEN, &[]), - astroport_governance::hub::ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: CW20ICS20.to_string(), - amount: stuck_amount, - msg: to_json_binary(&astroport_governance::hub::Cw20HookMsg::TransferFailure { - receiver: user.to_owned(), - }) - .unwrap(), - }), - ) - .unwrap(); - - // Withdraw must fail if the user has no funds stuck - let ibc_withdraw = to_json_binary(&Hub::WithdrawFunds { - user: Addr::unchecked("not_user"), - }) - .unwrap(); - let recv_packet = mock_ibc_packet("channel-3", ibc_withdraw); - let msg = IbcPacketReceiveMsg::new(recv_packet, Addr::unchecked("relayer")); - let res = ibc_packet_receive(deps.as_mut(), env.clone(), msg).unwrap(); - - let hub_respone: interchain::Response = from_json(&res.acknowledgement).unwrap(); - match hub_respone { - interchain::Response::Result { error, .. } => { - assert!(error.is_some()); - assert_eq!( - error.unwrap(), - "cosmwasm_std::math::uint128::Uint128 not found" - ); - } - _ => panic!("Wrong response type"), - } - - // Our user has funds stuck, so withdrawal must succeed - let ibc_withdraw = to_json_binary(&Hub::WithdrawFunds { - user: Addr::unchecked(user), - }) - .unwrap(); - let recv_packet = mock_ibc_packet("channel-3", ibc_withdraw); - - let msg = IbcPacketReceiveMsg::new(recv_packet, Addr::unchecked("relayer")); - let res = ibc_packet_receive(deps.as_mut(), env, msg).unwrap(); - - let hub_respone: interchain::Response = from_json(&res.acknowledgement).unwrap(); - match hub_respone { - interchain::Response::Result { address, error, .. } => { - assert!(error.is_none()); - assert_eq!(address.unwrap(), user); - } - _ => panic!("Wrong response type"), - } - - // We must see one message being emitted from the withdraw - assert_eq!(res.messages.len(), 1); - - // It must be a CW20-ICS20 transfer message - let ibc_transfer_msg = to_json_binary(&TransferMsg { - remote_address: user.to_string(), - channel: "channel-1".to_string(), - timeout: Some(10), - memo: None, - }) - .unwrap(); - let cw_send_msg = to_json_binary(&Cw20ExecuteMsg::Send { - contract: CW20ICS20.to_string(), - amount: stuck_amount, - msg: ibc_transfer_msg, - }) - .unwrap(); - - assert_eq!( - res.messages[0], - SubMsg { - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - msg: WasmMsg::Execute { - contract_addr: "astro".to_string(), - msg: cw_send_msg, - funds: vec![], - } - .into(), - } - ); - } -} diff --git a/contracts/hub/src/ibc_query.rs b/contracts/hub/src/ibc_query.rs deleted file mode 100644 index 47c3a8a3..00000000 --- a/contracts/hub/src/ibc_query.rs +++ /dev/null @@ -1,170 +0,0 @@ -use cosmwasm_std::{to_json_binary, DepsMut, IbcReceiveResponse}; - -use astroport_governance::{ - assembly::Proposal, - assembly::QueryMsg, - interchain::{ProposalSnapshot, Response}, -}; - -use crate::{error::ContractError, state::CONFIG}; - -/// Query the Assembly for a proposal and return the result in an -/// IBC acknowledgement -/// -/// If the proposal doesn't exist, the Outpost will see a generic ABCI error -/// and not "proposal not found" due to limitations in wasmd -pub fn handle_ibc_query_proposal( - deps: DepsMut, - id: u64, -) -> Result { - let config = CONFIG.load(deps.storage)?; - - let proposal: Proposal = deps.querier.query_wasm_smart( - config.assembly_addr, - &QueryMsg::Proposal { proposal_id: id }, - )?; - - let proposal_snapshot = ProposalSnapshot { - id: proposal.proposal_id, - start_time: proposal.start_time, - }; - - let ack_data = to_json_binary(&Response::QueryProposal(proposal_snapshot))?; - Ok(IbcReceiveResponse::new() - .set_ack(ack_data) - .add_attribute("query", "proposal") - .add_attribute("proposal_id", id.to_string())) -} - -#[cfg(test)] -mod tests { - use super::*; - use astroport_governance::interchain::Hub; - use cosmwasm_std::{from_json, testing::mock_info, Addr, IbcPacketReceiveMsg, Uint64}; - - use crate::{ - contract::instantiate, - execute::execute, - ibc::ibc_packet_receive, - mock::{ - mock_all, mock_ibc_packet, setup_channel, ASSEMBLY, CW20ICS20, GENERATOR_CONTROLLER, - OWNER, STAKING, - }, - }; - - // Test Cases: - // - // Expect Success - // - Proposal should not be queried without Assembly - - #[test] - fn query_proposal_fails_invalid_assembly() { - let (mut deps, env, info) = mock_all(OWNER); - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::hub::InstantiateMsg { - owner: OWNER.to_string(), - assembly_addr: "invalid".to_string(), - cw20_ics20_addr: CW20ICS20.to_string(), - staking_addr: STAKING.to_string(), - generator_controller_addr: GENERATOR_CONTROLLER.to_string(), - ibc_timeout_seconds: 10, - }, - ) - .unwrap(); - - // Set up valid IBC channel - setup_channel(deps.as_mut(), env.clone()); - - // Add allowed Outpost - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::hub::ExecuteMsg::AddOutpost { - outpost_addr: "outpost".to_string(), - outpost_channel: "channel-3".to_string(), - cw20_ics20_channel: "channel-1".to_string(), - }, - ) - .unwrap(); - - let ibc_query_proposal = to_json_binary(&Hub::QueryProposal { id: 1 }).unwrap(); - let recv_packet = mock_ibc_packet("channel-3", ibc_query_proposal); - - let msg = IbcPacketReceiveMsg::new(recv_packet, Addr::unchecked("relayer")); - let res = ibc_packet_receive(deps.as_mut(), env, msg).unwrap(); - - let ack: Response = from_json(&res.acknowledgement).unwrap(); - match ack { - Response::Result { error, .. } => { - assert!(error.is_some()); - } - _ => panic!("Wrong response type"), - } - - // No messages should be emitted - assert_eq!(res.messages.len(), 0); - } - - // Test Cases: - // - // Expect Success - // - An IBC ack contains the correct information - - #[test] - fn query_proposal() { - let (mut deps, env, info) = mock_all(OWNER); - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::hub::InstantiateMsg { - owner: OWNER.to_string(), - assembly_addr: ASSEMBLY.to_string(), - cw20_ics20_addr: CW20ICS20.to_string(), - staking_addr: STAKING.to_string(), - generator_controller_addr: GENERATOR_CONTROLLER.to_string(), - ibc_timeout_seconds: 10, - }, - ) - .unwrap(); - - // Set up valid IBC channel - setup_channel(deps.as_mut(), env.clone()); - - // Add allowed Outpost - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::hub::ExecuteMsg::AddOutpost { - outpost_addr: "outpost".to_string(), - outpost_channel: "channel-3".to_string(), - cw20_ics20_channel: "channel-1".to_string(), - }, - ) - .unwrap(); - - let ibc_query_proposal = to_json_binary(&Hub::QueryProposal { id: 1 }).unwrap(); - let recv_packet = mock_ibc_packet("channel-3", ibc_query_proposal); - - let msg = IbcPacketReceiveMsg::new(recv_packet, Addr::unchecked("relayer")); - let res = ibc_packet_receive(deps.as_mut(), env, msg).unwrap(); - - let ack: Response = from_json(&res.acknowledgement).unwrap(); - match ack { - Response::QueryProposal(proposal) => { - assert_eq!(proposal.id, Uint64::from(1u64)); - } - _ => panic!("Wrong response type"), - } - - // No message must be emitted, the ack contains the data - assert_eq!(res.messages.len(), 0); - } -} diff --git a/contracts/hub/src/ibc_staking.rs b/contracts/hub/src/ibc_staking.rs deleted file mode 100644 index 74b3f24d..00000000 --- a/contracts/hub/src/ibc_staking.rs +++ /dev/null @@ -1,330 +0,0 @@ -use astroport::querier::query_token_balance; -use cosmwasm_std::{ - to_json_binary, DepsMut, Env, IbcReceiveResponse, QuerierWrapper, Storage, SubMsg, Uint128, - WasmMsg, -}; -use cw20::Cw20ExecuteMsg; - -use astroport_governance::interchain::Response; - -use crate::{ - error::ContractError, - reply::UNSTAKE_ID, - state::{ReplyData, CONFIG, REPLY_DATA}, -}; - -/// Handle an unstake command from an Outpost -/// -/// Once the xASTRO has been unstaked, the resulting ASTRO will be sent back -/// to the user on the Outpost -pub fn handle_ibc_unstake( - deps: DepsMut, - env: Env, - receive_channel: String, - receiver: String, - amount: Uint128, -) -> Result { - let msg = construct_unstake_msg( - deps.storage, - deps.querier, - env, - receive_channel, - receiver.clone(), - amount, - )?; - // Add to SubMessage to handle the reply - let sub_msg = SubMsg::reply_on_success(msg, UNSTAKE_ID); - - // Set the acknowledgement. This is only to indicate that the unstake - // was processed without error, not that the funds were successfully - let ack_data = to_json_binary(&Response::new_success("unstake".to_owned(), receiver))?; - - Ok(IbcReceiveResponse::new() - .set_ack(ack_data) - .add_submessage(sub_msg)) -} - -/// Create the messages and state to correctly handle the unstaking of xASTRO -pub fn construct_unstake_msg( - storage: &mut dyn Storage, - querier: QuerierWrapper, - env: Env, - receiving_channel: String, - receiver: String, - amount: Uint128, -) -> Result { - let config = CONFIG.load(storage)?; - - // Unstake the received xASTRO amount - // We need a SubMessage here to ensure that we send the correct amount - // of ASTRO to the receiver as the ratio isn't 1:1 - let leave_msg = astroport::staking::Cw20HookMsg::Leave {}; - let send_msg = Cw20ExecuteMsg::Send { - contract: config.staking_addr.to_string(), - amount, - msg: to_json_binary(&leave_msg)?, - }; - - // Send the xASTRO held in the contract to the Staking contract - let msg = WasmMsg::Execute { - contract_addr: config.xtoken_addr.to_string(), - msg: to_json_binary(&send_msg)?, - funds: vec![], - }; - - // Log the amount of ASTRO we currently hold - let current_astro_balance = query_token_balance( - &querier, - config.token_addr.to_string(), - env.contract.address, - )?; - - // Temporarily save the data needed for the SubMessage reply - let reply_data = ReplyData { - receiver, - receiving_channel, - value: current_astro_balance, - original_value: amount, - }; - REPLY_DATA.save(storage, &reply_data)?; - - Ok(msg) -} - -#[cfg(test)] -mod tests { - use super::*; - use astroport::cw20_ics20::TransferMsg; - use astroport_governance::{hub::HubBalance, interchain::Hub}; - use cosmwasm_std::{ - from_json, - testing::{mock_info, MOCK_CONTRACT_ADDR}, - Addr, IbcPacketReceiveMsg, Reply, ReplyOn, SubMsgResponse, SubMsgResult, Uint64, - }; - use cw20::Cw20ReceiveMsg; - - use crate::{ - contract::instantiate, - execute::execute, - ibc::ibc_packet_receive, - mock::{ - mock_all, mock_ibc_packet, setup_channel, ASSEMBLY, ASTRO_TOKEN, CW20ICS20, - GENERATOR_CONTROLLER, OWNER, STAKING, XASTRO_TOKEN, - }, - query::query, - reply::{reply, STAKE_ID}, - }; - - // Test Cases: - // - // Expect Success - // - Unstaked tokens must be returned to the user - - #[test] - fn ibc_unstake() { - let (mut deps, env, info) = mock_all(OWNER); - - let unstaker = "unstaker"; - let unstake_amount = Uint128::from(100u128); - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::hub::InstantiateMsg { - owner: OWNER.to_string(), - assembly_addr: ASSEMBLY.to_string(), - cw20_ics20_addr: CW20ICS20.to_string(), - staking_addr: STAKING.to_string(), - generator_controller_addr: GENERATOR_CONTROLLER.to_string(), - ibc_timeout_seconds: 10, - }, - ) - .unwrap(); - - // Set up valid IBC channel - setup_channel(deps.as_mut(), env.clone()); - - // Add allowed Outpost - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::hub::ExecuteMsg::AddOutpost { - outpost_addr: "outpost".to_string(), - outpost_channel: "channel-3".to_string(), - cw20_ics20_channel: "channel-1".to_string(), - }, - ) - .unwrap(); - - // Send a valid stake memo so we have something to unstake - let res = execute( - deps.as_mut(), - env.clone(), - mock_info(ASTRO_TOKEN, &[]), - astroport_governance::hub::ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: CW20ICS20.to_string(), - amount: unstake_amount, - msg: to_json_binary(&astroport_governance::hub::Cw20HookMsg::OutpostMemo { - channel: "channel-1".to_string(), - sender: unstaker.to_string(), - receiver: MOCK_CONTRACT_ADDR.to_owned(), - memo: "{\"stake\":{}}".to_string(), - }) - .unwrap(), - }), - ) - .unwrap(); - - // Verify that the stake message matches the expected message - let stake_msg = to_json_binary(&astroport::staking::Cw20HookMsg::Enter {}).unwrap(); - let send_msg = to_json_binary(&Cw20ExecuteMsg::Send { - contract: STAKING.to_string(), - amount: unstake_amount, - msg: stake_msg, - }) - .unwrap(); - - // Verify that we see a stake message reply - assert_eq!( - res.messages[0], - SubMsg { - id: 9000, - gas_limit: None, - reply_on: ReplyOn::Success, - msg: WasmMsg::Execute { - contract_addr: ASTRO_TOKEN.to_string(), - msg: send_msg, - funds: vec![], - } - .into(), - } - ); - - // Construct the reply from the staking contract that will be returned - // to the contract - let stake_reply = Reply { - id: STAKE_ID, - result: SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: None, - }), - }; - - let res = reply(deps.as_mut(), env.clone(), stake_reply).unwrap(); - - // We must have one IBC message - assert_eq!(res.messages.len(), 1); - - let ibc_unstake = to_json_binary(&Hub::Unstake { - receiver: unstaker.to_owned(), - amount: unstake_amount, - }) - .unwrap(); - let recv_packet = mock_ibc_packet("channel-3", ibc_unstake); - - let msg = IbcPacketReceiveMsg::new(recv_packet, Addr::unchecked("relayer")); - let res = ibc_packet_receive(deps.as_mut(), env.clone(), msg).unwrap(); - - let ack: Response = from_json(&res.acknowledgement).unwrap(); - match ack { - Response::Result { error, .. } => { - assert!(error.is_none()); - } - _ => panic!("Wrong response type"), - } - - // Should have exactly one message - assert_eq!(res.messages.len(), 1); - - // Verify that the unstake message matches the expected message - let unstake_msg = to_json_binary(&astroport::staking::Cw20HookMsg::Leave {}).unwrap(); - let send_msg = to_json_binary(&Cw20ExecuteMsg::Send { - contract: STAKING.to_string(), - amount: unstake_amount, - msg: unstake_msg, - }) - .unwrap(); - - // We should see the unstake SubMessage - assert_eq!( - res.messages[0], - SubMsg { - id: 9001, - gas_limit: None, - reply_on: ReplyOn::Success, - msg: WasmMsg::Execute { - contract_addr: XASTRO_TOKEN.to_string(), - msg: send_msg, - funds: vec![], - } - .into(), - } - ); - - // Construct the reply from the staking contract that will be returned - // to the contract - let unstake_reply = Reply { - id: UNSTAKE_ID, - result: SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: None, - }), - }; - - let res = reply(deps.as_mut(), env.clone(), unstake_reply).unwrap(); - - // We must have one CW20-ICS20 transfer message - assert_eq!(res.messages.len(), 1); - - // Contruct the CW20-ICS20 ASTRO token transfer we expect to see - let transfer_msg = to_json_binary(&TransferMsg { - channel: "channel-1".to_string(), - remote_address: unstaker.to_string(), - timeout: Some(10), - memo: None, - }) - .unwrap(); - let send_msg = to_json_binary(&Cw20ExecuteMsg::Send { - contract: CW20ICS20.to_string(), - amount: unstake_amount, - msg: transfer_msg, - }) - .unwrap(); - - // We should see the ASTRO token transfer - assert_eq!( - res.messages[0], - SubMsg { - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - msg: WasmMsg::Execute { - contract_addr: ASTRO_TOKEN.to_string(), - msg: send_msg, - funds: vec![], - } - .into(), - } - ); - - // At this point the channel must have a zero balance as everything - // has been unstaked - let balances = query( - deps.as_ref(), - env.clone(), - astroport_governance::hub::QueryMsg::ChannelBalanceAt { - channel: "channel-3".to_string(), - timestamp: Uint64::from(env.block.time.seconds()), - }, - ) - .unwrap(); - - let expected = HubBalance { - balance: Uint128::zero(), - }; - - assert_eq!(balances, to_json_binary(&expected).unwrap()); - } -} diff --git a/contracts/hub/src/lib.rs b/contracts/hub/src/lib.rs deleted file mode 100644 index 692993b9..00000000 --- a/contracts/hub/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub mod contract; -pub mod error; -pub mod execute; -pub mod ibc; -pub mod ibc_governance; -pub mod ibc_misc; -pub mod ibc_query; -pub mod ibc_staking; -pub mod query; -pub mod reply; -pub mod state; - -#[cfg(test)] -mod mock; diff --git a/contracts/hub/src/mock.rs b/contracts/hub/src/mock.rs deleted file mode 100644 index 372b16b2..00000000 --- a/contracts/hub/src/mock.rs +++ /dev/null @@ -1,296 +0,0 @@ -use std::cell::Cell; - -#[cfg(test)] -use cosmwasm_std::{from_json, Uint64}; -use cosmwasm_std::{ - testing::{mock_env, mock_info, MockApi, MockQuerier, MockStorage}, - to_json_binary, Addr, Binary, DepsMut, Env, IbcChannel, IbcChannelConnectMsg, - IbcChannelOpenMsg, IbcEndpoint, IbcOrder, IbcPacket, IbcQuery, ListChannelsResponse, - MessageInfo, OwnedDeps, Timestamp, Uint128, -}; - -use cosmwasm_std::testing::MOCK_CONTRACT_ADDR; -use cosmwasm_std::{ - Empty, Querier, QuerierResult, QueryRequest, SystemError, SystemResult, WasmQuery, -}; -use cw20::BalanceResponse as Cw20BalanceResponse; - -use crate::ibc::{ibc_channel_connect, ibc_channel_open, IBC_APP_VERSION}; - -pub const CONTRACT_PORT: &str = "ibc:wasm1234567890abcdef"; -pub const REMOTE_PORT: &str = "wasm.outpost"; -pub const CONNECTION_ID: &str = "connection-2"; -pub const OWNER: &str = "owner"; -pub const ASSEMBLY: &str = "assembly"; -pub const CW20ICS20: &str = "cw20_ics20"; -pub const GENERATOR_CONTROLLER: &str = "generator_controller"; -pub const STAKING: &str = "staking"; -pub const ASTRO_TOKEN: &str = "astro"; -pub const XASTRO_TOKEN: &str = "xastro"; - -/// mock_dependencies is a drop-in replacement for cosmwasm_std::testing::mock_dependencies. -/// This uses the Astroport CustomQuerier. -#[cfg(test)] -pub fn mock_dependencies() -> OwnedDeps { - let custom_querier: WasmMockQuerier = - WasmMockQuerier::new(MockQuerier::new(&[(MOCK_CONTRACT_ADDR, &[])])); - - OwnedDeps { - storage: MockStorage::default(), - api: MockApi::default(), - querier: custom_querier, - custom_query_type: Default::default(), - } -} - -/// WasmMockQuerier will respond to requests from the custom querier, -/// providing responses to the contracts -pub struct WasmMockQuerier { - base: MockQuerier, - xastro_balance: Cell, - astro_balance: Cell, -} - -impl Querier for WasmMockQuerier { - fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { - // MockQuerier doesn't support Custom, so we ignore it completely - let request: QueryRequest = match from_json(bin_request) { - Ok(v) => v, - Err(e) => { - return SystemResult::Err(SystemError::InvalidRequest { - error: format!("Parsing query request: {}", e), - request: bin_request.into(), - }) - } - }; - self.handle_query(&request) - } -} - -impl WasmMockQuerier { - pub fn handle_query(&self, request: &QueryRequest) -> QuerierResult { - match &request { - QueryRequest::Wasm(WasmQuery::Smart { contract_addr, msg }) => { - if contract_addr == STAKING { - match from_json(msg).unwrap() { - astroport::staking::QueryMsg::Config {} => { - let config = astroport::staking::ConfigResponse { - deposit_token_addr: Addr::unchecked("astro"), - share_token_addr: Addr::unchecked("xastro"), - }; - - SystemResult::Ok(to_json_binary(&config).into()) - } - _ => { - panic!("DO NOT ENTER HERE") - } - } - } else { - if contract_addr == ASTRO_TOKEN { - // Manually increase the ASTRO balance every query - // to help tests - let response = Cw20BalanceResponse { - balance: self.astro_balance.get(), - }; - self.astro_balance.set( - self.astro_balance - .get() - .checked_add(Uint128::from(100u128)) - .unwrap(), - ); - return SystemResult::Ok(to_json_binary(&response).into()); - } - if contract_addr == XASTRO_TOKEN { - // Manually increase the ASTRO balance every query - // to help tests - let response = Cw20BalanceResponse { - balance: self.xastro_balance.get(), - }; - self.xastro_balance.set( - self.xastro_balance - .get() - .checked_add(Uint128::from(100u128)) - .unwrap(), - ); - return SystemResult::Ok(to_json_binary(&response).into()); - } - if contract_addr != ASSEMBLY { - return SystemResult::Err(SystemError::Unknown {}); - } - match from_json(msg).unwrap() { - astroport_governance::assembly::QueryMsg::Proposal { proposal_id } => { - let proposal = astroport_governance::assembly::Proposal { - proposal_id: Uint64::from(proposal_id), - submitter: Addr::unchecked("submitter"), - status: astroport_governance::assembly::ProposalStatus::Active, - for_power: Uint128::zero(), - outpost_against_power: Uint128::zero(), - against_power: Uint128::zero(), - outpost_for_power: Uint128::zero(), - start_block: 1, - start_time: 1571797419, - end_block: 5, - delayed_end_block: 10, - expiration_block: 15, - title: "Test title".to_string(), - description: "Test description".to_string(), - link: None, - messages: vec![], - deposit_amount: Uint128::one(), - ibc_channel: None, - }; - SystemResult::Ok(to_json_binary(&proposal).into()) - } - _ => { - panic!("DO NOT ENTER HERE") - } - } - } - } - QueryRequest::Ibc(IbcQuery::ListChannels { .. }) => { - let response = ListChannelsResponse { - channels: vec![ - IbcChannel::new( - IbcEndpoint { - port_id: "wasm".to_string(), - channel_id: "channel-1".to_string(), - }, - IbcEndpoint { - port_id: "wasm".to_string(), - channel_id: "channel-1".to_string(), - }, - IbcOrder::Unordered, - "version", - "connection-1", - ), - IbcChannel::new( - IbcEndpoint { - port_id: "wasm".to_string(), - channel_id: "channel-2".to_string(), - }, - IbcEndpoint { - port_id: "wasm".to_string(), - channel_id: "channel-2".to_string(), - }, - IbcOrder::Unordered, - "version", - "connection-1", - ), - IbcChannel::new( - IbcEndpoint { - port_id: "wasm".to_string(), - channel_id: "channel-3".to_string(), - }, - IbcEndpoint { - port_id: "wasm".to_string(), - channel_id: "channel-1".to_string(), - }, - IbcOrder::Unordered, - "version", - "connection-1", - ), - IbcChannel::new( - IbcEndpoint { - port_id: "wasm".to_string(), - channel_id: "channel-100".to_string(), - }, - IbcEndpoint { - port_id: "wasm".to_string(), - channel_id: "channel-1".to_string(), - }, - IbcOrder::Unordered, - "version", - "connection-1", - ), - ], - }; - SystemResult::Ok(to_json_binary(&response).into()) - // if contract_addr != "cw20_ics20" { - // return SystemResult::Err(SystemError::Unknown {}); - // } - } - _ => self.base.handle_query(request), - } - } -} - -impl WasmMockQuerier { - pub fn new(base: MockQuerier) -> Self { - WasmMockQuerier { - base, - xastro_balance: Cell::new(Uint128::zero()), - astro_balance: Cell::new(Uint128::zero()), - } - } -} - -/// Mock the dependencies for unit tests -pub fn mock_all( - sender: &str, -) -> ( - OwnedDeps, - Env, - MessageInfo, -) { - let deps = mock_dependencies(); - let env = mock_env(); - let info = mock_info(sender, &[]); - (deps, env, info) -} - -/// Mock an IBC channel -pub fn mock_channel( - our_port: &str, - our_channel_id: &str, - counter_port: &str, - counter_channel: &str, - ibc_order: IbcOrder, - ibc_version: &str, -) -> IbcChannel { - IbcChannel::new( - IbcEndpoint { - port_id: our_port.into(), - channel_id: our_channel_id.into(), - }, - IbcEndpoint { - port_id: counter_port.into(), - channel_id: counter_channel.into(), - }, - ibc_order, - ibc_version.to_string(), - CONNECTION_ID, - ) -} - -/// Set up a valid channel for use in tests -pub fn setup_channel(mut deps: DepsMut, env: Env) { - let channel = mock_channel( - "wasm.hub", - "channel-3", - "wasm.outpost", - "channel-7", - IbcOrder::Unordered, - IBC_APP_VERSION, - ); - let open_msg = IbcChannelOpenMsg::new_init(channel.clone()); - ibc_channel_open(deps.branch(), env.clone(), open_msg).unwrap(); - let connect_msg = IbcChannelConnectMsg::new_ack(channel, IBC_APP_VERSION); - ibc_channel_connect(deps, env, connect_msg).unwrap(); -} - -/// Construct a mock IBC packet -pub fn mock_ibc_packet(my_channel: &str, data: Binary) -> IbcPacket { - IbcPacket::new( - data, - IbcEndpoint { - port_id: REMOTE_PORT.to_string(), - channel_id: "channel-7".to_string(), - }, - IbcEndpoint { - port_id: CONTRACT_PORT.to_string(), - channel_id: my_channel.to_string(), - }, - 3, - Timestamp::from_seconds(1665321069).into(), - ) -} diff --git a/contracts/hub/src/query.rs b/contracts/hub/src/query.rs deleted file mode 100644 index f85be3d7..00000000 --- a/contracts/hub/src/query.rs +++ /dev/null @@ -1,72 +0,0 @@ -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{to_json_binary, Addr, Binary, Deps, Env, Order, StdResult, Uint128}; -use cw_storage_plus::Bound; - -use crate::state::{channel_balance_at, total_balance_at, CONFIG, OUTPOSTS, USER_FUNDS}; -use astroport_governance::{ - hub::{HubBalance, OutpostConfig, QueryMsg}, - DEFAULT_LIMIT, MAX_LIMIT, -}; - -/// Expose available contract queries. -/// -/// ## Queries -/// * **QueryMsg::Config {}** Returns core contract settings stored in the [`Config`] structure. -/// -/// * **QueryMsg::UserFunds { }** Returns a [`HubBalance`] containing the amount of ASTRO this address has held on the Hub due to IBC failures -/// -/// * **QueryMsg::Outposts { }** Returns a [`Vec`] containing the active Outposts -/// -/// * **QueryMsg::ChannelBalanceAt { channel, timestamp }** Returns a [`HubBalance`] containing the amount of xASTRO minted on the specified channel at the specified timestamp -/// -/// * **QueryMsg::TotalChannelBalancesAt { }** Returns a [`HubBalance`] containing the total amount of xASTRO minted across all channels at a specified time -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Config {} => to_json_binary(&CONFIG.load(deps.storage)?), - QueryMsg::UserFunds { user } => query_user_funds(deps, user), - QueryMsg::Outposts { start_after, limit } => query_outposts(deps, start_after, limit), - QueryMsg::ChannelBalanceAt { channel, timestamp } => to_json_binary(&HubBalance { - balance: channel_balance_at(deps.storage, &channel, timestamp.u64())?, - }), - QueryMsg::TotalChannelBalancesAt { timestamp } => to_json_binary(&HubBalance { - balance: total_balance_at(deps.storage, timestamp.u64())?, - }), - } -} - -/// Return a list of Outpost in the format of `OutpostConfig` -/// Paged by address and will only return limit at a time -fn query_outposts( - 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_deref().map(Bound::exclusive); - - let outposts: Vec = OUTPOSTS - .range(deps.storage, start, None, Order::Ascending) - .take(limit) - .map(|item| { - let (key, value) = item.unwrap(); - OutpostConfig { - address: key, - channel: value.outpost, - cw20_ics20_channel: value.cw20_ics20, - } - }) - .collect(); - to_json_binary(&outposts) -} - -/// Return the amount of ASTRO this address has held on the Hub due to IBC -/// failures -fn query_user_funds(deps: Deps, user: Addr) -> StdResult { - let funds = USER_FUNDS - .load(deps.storage, &user) - .unwrap_or(Uint128::zero()); - - to_json_binary(&HubBalance { balance: funds }) -} diff --git a/contracts/hub/src/reply.rs b/contracts/hub/src/reply.rs deleted file mode 100644 index 966611d8..00000000 --- a/contracts/hub/src/reply.rs +++ /dev/null @@ -1,162 +0,0 @@ -use astroport::{cw20_ics20::TransferMsg, querier::query_token_balance}; -use cosmwasm_std::{ - entry_point, to_json_binary, CosmosMsg, DepsMut, Env, IbcMsg, Reply, Response, SubMsgResult, - WasmMsg, -}; -use cw20::Cw20ExecuteMsg; - -use astroport_governance::interchain::Outpost; - -use crate::{ - error::ContractError, - state::{ - decrease_channel_balance, get_outpost_from_cw20ics20_channel, - get_transfer_channel_from_outpost_channel, increase_channel_balance, CONFIG, REPLY_DATA, - }, -}; - -/// Reply ID when staking tokens -pub const STAKE_ID: u64 = 9000; -/// Reply ID when unstaking tokens -pub const UNSTAKE_ID: u64 = 9001; - -/// Handle SubMessage replies -/// -/// To correctly handle staking and unstaking amount we execute the calls using -/// SubMessages and the replies are handled here -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn reply(deps: DepsMut, env: Env, reply: Reply) -> Result { - match reply.id { - STAKE_ID => handle_stake_reply(deps, env, reply), - UNSTAKE_ID => handle_unstake_reply(deps, env, reply), - _ => Err(ContractError::UnknownReplyId { id: reply.id }), - } -} - -/// Handle the reply from a staking transaction -fn handle_stake_reply(deps: DepsMut, env: Env, reply: Reply) -> Result { - match reply.result { - SubMsgResult::Ok(..) => { - let config = CONFIG.load(deps.storage)?; - - // Load the temporary data stored before the SubMessage was executed - let reply_data = REPLY_DATA.load(deps.storage)?; - - // Determine the actual amount of xASTRO we received from staking - // and mint on the Outpost - let current_x_astro_balance = - query_token_balance(&deps.querier, config.xtoken_addr, env.contract.address)?; - let xastro_received = current_x_astro_balance.checked_sub(reply_data.value)?; - - // The channel we received the ASTRO to stake on was the CW20-ICS20 - // channel, we need to determine the channel to use for minting the - // xASTRO be checking the known Outposts - let outpost_channels = - get_outpost_from_cw20ics20_channel(deps.as_ref(), &reply_data.receiving_channel)?; - - // Submit an IBC transaction to mint the same amount of xASTRO - // we received from staking on the Outpost - let mint_remote = Outpost::MintXAstro { - amount: xastro_received, - receiver: reply_data.receiver.clone(), - }; - let msg = CosmosMsg::Ibc(IbcMsg::SendPacket { - channel_id: outpost_channels.outpost.clone(), - data: to_json_binary(&mint_remote)?, - timeout: env - .block - .time - .plus_seconds(config.ibc_timeout_seconds) - .into(), - }); - - // Keep track of the amount of xASTRO minted on the related Outpost - increase_channel_balance( - deps.storage, - env.block.time.seconds(), - &outpost_channels.outpost, - xastro_received, - )?; - - Ok(Response::new() - .add_message(msg) - .add_attribute("action", "mint_remote_xastro") - .add_attribute("amount", xastro_received) - .add_attribute("channel", outpost_channels.outpost) - .add_attribute("receiver", reply_data.receiver)) - } - // In the case where staking fails, the funds will either automatically be returned - // through the CW20-ICS20 contract or the user will need to manually withdraw them - // from this contract. In either case, we don't need to do anything here as the - // original staking memo is already a SubMessage in the CW20-ICS20 contract - SubMsgResult::Err(err) => Err(ContractError::InvalidSubmessage { reason: err }), - } -} - -/// Handle the reply from an unstaking transaction -fn handle_unstake_reply(deps: DepsMut, env: Env, reply: Reply) -> Result { - match reply.result { - SubMsgResult::Ok(..) => { - let config = CONFIG.load(deps.storage)?; - - // Load the temporary data stored before the SubMessage was executed - let reply_data = REPLY_DATA.load(deps.storage)?; - - // Determine the actual amount of ASTRO we received from unstaking - // to determine how much to send back to the user - let current_astro_balance = query_token_balance( - &deps.querier, - config.token_addr.clone(), - env.contract.address, - )?; - let astro_received = current_astro_balance.checked_sub(reply_data.value)?; - - // The channel we received the unstaking from was the Outpost contract - // channel, we need to determine the channel to use for sending the - // ASTRO back using the CW20-ICS20 contract - let outpost_channels = get_transfer_channel_from_outpost_channel( - deps.as_ref(), - &reply_data.receiving_channel, - )?; - - // Send the ASTRO back to the unstaking user on the Outpost chain - // via the CW20-ICS20 contract - let transfer_msg = TransferMsg { - channel: outpost_channels.cw20_ics20.clone(), - remote_address: reply_data.receiver.clone(), - timeout: Some(config.ibc_timeout_seconds), - memo: None, - }; - - let transfer = Cw20ExecuteMsg::Send { - contract: config.cw20_ics20_addr.to_string(), - amount: astro_received, - msg: to_json_binary(&transfer_msg)?, - }; - - let wasm_msg = WasmMsg::Execute { - contract_addr: config.token_addr.to_string(), - msg: to_json_binary(&transfer)?, - funds: vec![], - }; - - // Decrease the amount of xASTRO minted via this Outpost - decrease_channel_balance( - deps.storage, - env.block.time.seconds(), - &outpost_channels.outpost, - reply_data.original_value, - )?; - - Ok(Response::new() - .add_message(wasm_msg) - .add_attribute("action", "return_unstaked_astro") - .add_attribute("amount", astro_received) - .add_attribute("channel", outpost_channels.cw20_ics20) - .add_attribute("receiver", reply_data.receiver)) - } - // If unstaking fails the error will be returned to the Outpost that would undo - // the burning of xASTRO and return the tokens to the user - SubMsgResult::Err(err) => Err(ContractError::InvalidSubmessage { reason: err }), - } -} diff --git a/contracts/hub/src/state.rs b/contracts/hub/src/state.rs deleted file mode 100644 index 60802008..00000000 --- a/contracts/hub/src/state.rs +++ /dev/null @@ -1,169 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Deps, Order, StdError, StdResult, Storage, Uint128}; -use cw_storage_plus::{Bound, Item, Map}; - -use astroport::common::OwnershipProposal; -use astroport_governance::hub::Config; - -use crate::error::ContractError; - -/// Holds temporary data used in the staking/unstaking replies -#[cw_serde] -pub struct ReplyData { - /// The address that should receive the staked/unstaked tokens - pub receiver: String, - /// The IBC channel the original request was received on - pub receiving_channel: String, - /// A generic value to store balances - pub value: Uint128, - /// The original value of a request - pub original_value: Uint128, -} - -/// Holds the IBC channels that are allowed to communicate with the Hub -#[cw_serde] -pub struct OutpostChannels { - /// The channel of the Outpost contract on the remote chain - pub outpost: String, - /// The channel to send ASTRO CW20-ICS20 tokens through - pub cw20_ics20: String, -} - -/// Stores the contract config -pub const CONFIG: Item = Item::new("config"); - -/// Stores data for reply endpoint. -pub const REPLY_DATA: Item = Item::new("reply_data"); - -/// Stores funds that got stuck on the Hub chain due to IBC transfer failures -/// when using cross-chain actions -pub const USER_FUNDS: Map<&Addr, Uint128> = Map::new("user_funds"); - -/// Contains a proposal to change contract ownership -pub const OWNERSHIP_PROPOSAL: Item = Item::new("ownership_proposal"); - -/// Contains a map of outpost addresses to their IBC channels that are allowed -/// to communicate with the Hub over IBC -pub const OUTPOSTS: Map<&str, OutpostChannels> = Map::new("channel_map"); - -/// Contains a map of Outpost channels to their balances at timestamps. That is, the amount -/// of xASTRO minted via an Outpost at a specific time -pub const OUTPOST_CHANNEL_BALANCES: Map<(&str, u64), Uint128> = - Map::new("outpost_channel_balances"); - -pub const TOTAL_OUTPOST_CHANNEL_BALANCE: Map = - Map::new("total_outpost_channel_balances"); - -/// Get the Outpost channels for a given CW20-ICS20 channel -/// -/// The Outposts must be configured and connected before this will return any values -pub fn get_outpost_from_cw20ics20_channel( - deps: Deps, - cw20ics20_channel: &str, -) -> Result { - OUTPOSTS - .range(deps.storage, None, None, Order::Ascending) - .find_map(|item| { - let (_, value) = item.ok()?; - if value.cw20_ics20 == cw20ics20_channel { - Some(value) - } else { - None - } - }) - .ok_or(ContractError::UnknownOutpost {}) -} - -/// Get the Outpost channels for a given contract channel -/// -/// The Outposts must be configured and connected before this will return any values -pub fn get_transfer_channel_from_outpost_channel( - deps: Deps, - outpost_channel: &str, -) -> Result { - OUTPOSTS - .range(deps.storage, None, None, Order::Ascending) - .find_map(|item| { - let (_, value) = item.ok()?; - if value.outpost == outpost_channel { - Some(value) - } else { - None - } - }) - .ok_or(ContractError::UnknownOutpost {}) -} - -/// Increase the balance of xASTRO minted via a specific Outpost -pub(crate) fn increase_channel_balance( - storage: &mut dyn Storage, - timestamp: u64, - outpost_channel: &str, - amount: Uint128, -) -> Result<(), StdError> { - let last_balance = channel_balance_at(storage, outpost_channel, timestamp)?; - OUTPOST_CHANNEL_BALANCES.save( - storage, - (outpost_channel, timestamp), - &last_balance.checked_add(amount)?, - )?; - - let last_total_balance = total_balance_at(storage, timestamp)?; - TOTAL_OUTPOST_CHANNEL_BALANCE.save(storage, timestamp, &last_total_balance.checked_add(amount)?) -} - -/// Decrease the balance of xASTRO minted via a specific Outpost -/// This will return an error if the balance is insufficient -pub(crate) fn decrease_channel_balance( - storage: &mut dyn Storage, - timestamp: u64, - outpost_channel: &str, - amount: Uint128, -) -> Result<(), StdError> { - let last_balance = channel_balance_at(storage, outpost_channel, timestamp)?; - OUTPOST_CHANNEL_BALANCES.save( - storage, - (outpost_channel, timestamp), - &last_balance.checked_sub(amount)?, - )?; - - let last_total_balance = total_balance_at(storage, timestamp)?; - TOTAL_OUTPOST_CHANNEL_BALANCE.save(storage, timestamp, &last_total_balance.checked_sub(amount)?) -} - -/// Fetches last known balance of a channel before or on timestamp -pub(crate) fn channel_balance_at( - storage: &dyn Storage, - outpost_channel: &str, - timestamp: u64, -) -> StdResult { - let balance_opt = OUTPOST_CHANNEL_BALANCES - .prefix(outpost_channel) - .range( - storage, - None, - Some(Bound::inclusive(timestamp)), - Order::Descending, - ) - .next() - .transpose()? - .map(|(_, value)| value); - - Ok(balance_opt.unwrap_or_else(Uint128::zero)) -} - -/// Returns the total channel balances at a specific time -pub fn total_balance_at(storage: &dyn Storage, timestamp: u64) -> StdResult { - // Look for the last value recorded before the current block (if none then value is zero) - let end = Bound::inclusive(timestamp); - let last_value = TOTAL_OUTPOST_CHANNEL_BALANCE - .range(storage, None, Some(end), Order::Descending) - .next(); - - if let Some(value) = last_value { - let (_, v) = value?; - return Ok(v); - } - - Ok(Uint128::zero()) -} diff --git a/contracts/outpost/.cargo/config b/contracts/outpost/.cargo/config deleted file mode 100644 index f5174787..00000000 --- a/contracts/outpost/.cargo/config +++ /dev/null @@ -1,6 +0,0 @@ -[alias] -wasm = "build --release --lib --target wasm32-unknown-unknown" -wasm-debug = "build --lib --target wasm32-unknown-unknown" -unit-test = "test --lib" -integration-test = "test --test integration" -schema = "run --bin schema" diff --git a/contracts/outpost/Cargo.toml b/contracts/outpost/Cargo.toml deleted file mode 100644 index 9bfe2838..00000000 --- a/contracts/outpost/Cargo.toml +++ /dev/null @@ -1,44 +0,0 @@ -[package] -name = "astroport-outpost" -version = "0.1.0" -authors = ["Astroport"] -edition = "2021" -description = "Forwards interchain actions to the Astroport Hub" -license = "GPL-3.0" - -exclude = [ - # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. - "contract.wasm", - "hash.txt", -] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] -library = [] - -[dependencies] -cw2 = "1.0.1" -cw20 = "0.15" -cosmwasm-schema = "1.1.0" -cw-utils = "1.0.1" -cosmwasm-std = { version = "1.1.0", features = ["iterator", "ibc3"] } -cw-storage-plus = "0.15" -schemars = "0.8.12" -semver = "1.0.17" -serde = { version = "1.0.164", default-features = false, features = ["derive"] } -thiserror = "1.0.40" -astroport = { git = "https://github.com/astroport-fi/hidden_astroport_core" } -astroport-governance = { path = "../../packages/astroport-governance" } -serde-json-wasm = "0.5.1" -base64 = { version = "0.13.0" } - -[dev-dependencies] -cw-multi-test = "0.16.5" -anyhow = "1.0" diff --git a/contracts/outpost/README.md b/contracts/outpost/README.md deleted file mode 100644 index ac267a48..00000000 --- a/contracts/outpost/README.md +++ /dev/null @@ -1,131 +0,0 @@ -# Outpost - -The Outpost contract enables staking, unstaking, voting in governance as well as voting on vxASTRO emissions from any chain where the Outpost contract is deployed on. The Hub and Outpost contracts are designed to work together, connected over IBC channels. - -The Outpost defines the following messages that can be received over IBC: - -```rust -/// Defines the messages that can be sent from the Hub to an Outpost -#[cw_serde] -pub enum Outpost { - /// Mint xASTRO tokens for the user - MintXAstro { receiver: String, amount: Uint128 }, -} -``` - -The Outpost is responsible for validation before sending data to the Hub. In a case such as voting, it will query the xASTRO contract for the user's holding at the time a proposal was added and submit that as the voting power. - -The Outpost defines the following execute messages: - -```rust -#[cw_serde] -pub enum ExecuteMsg { - /// Receive a message of type [`Cw20ReceiveMsg`] - Receive(Cw20ReceiveMsg), - /// Update parameters in the Outpost contract. Only the owner is allowed to - /// update the config - UpdateConfig { - /// The new Hub address - hub_addr: Option, - }, - /// Cast a vote on an Assembly proposal from an Outpost - CastAssemblyVote { - /// The ID of the proposal to vote on - proposal_id: u64, - /// The vote choice - vote: ProposalVoteOption, - }, - /// Cast a vote during an emissions voting period - CastEmissionsVote { - /// The votes in the format (pool address, percent of voting power) - votes: Vec<(String, u16)>, - }, - /// Kick an unlocked voter's voting power from the Generator Controller lite - KickUnlocked { - /// The address of the user to kick - user: Addr, - }, - /// Withdraw stuck funds from the Hub in case of specific IBC failures - WithdrawHubFunds {}, -} -``` - -## Message details - -**Receive xASTRO via a Cw20HookMsg message for unstaking** - -To unstake xASTRO from an Outpost a user needs to send the xASTRO to the Outpost. Once received, the contract burns the xASTRO and informs the Hub to unstake the true xASTRO on the Hub and return the resulting ASTRO. Should the IBC transactions fail at any point, the funds are returned to the user. - -The following needs to be executed on the Outpost xASTRO contract. `msg` in this case is the base64 of `{"unstake":{}}` - -```json -{ - "send": { - "contract": "wasm123", - "amount": "1000000", - "msg":"eyJ1bnN0YWtlIjp7fX0=" - } -} -``` - - -**Update Config** - -Update config allows the owner to set a new address for the Hub. Updating the Hub address will remove the known Hub channel and a new one will need to be established. - -```json -{ - "update_config": { - "hub_addr": "wasm123..." - } -} -``` - -**Cast a governance vote in the Assembly** - -In order to cast a vote we need to know the voting power of a user at the time the proposal was created. The contract will retrieve the proposal information if it doesn't have it cached locally before validating the xASTRO holdings and submitting the vote. - -```json -{ - "cast_assembly_vote":{ - "proposal_id": 1, - "vote": "for" - } -} -``` - -**Cast a vote on vxASTRO emissions** - -During voting periods in vxASTRO a user can vote on where emissions should be directed. The contract will check the vxASTRO holdings of the user before submitting the vote. - -```json -{ - "cast_emissions_vote": { - "votes":[ - ["wasm123..pool...", 1000] - ] - } -} -``` - -**Kick an unlocked vxASTRO user** - -When a user unlocks in vxASTRO their voting power is removed immediately. This call may only be made by the vxASTRO contract. Once called the unlock is sent to the Hub to execute on the Generator Controller on the Hub. - -```json -{ - "kick_unlocked":{ - "user":"wasm123" - } -} -``` - -**Withdraw funds from the Hub** - -In cases where specific IBC messages failed (mostly due to timeouts) there could be a situation where the funds are "stuck" on the Hub chain. To allow users to withdraw these funds we hold it in the Hub contract. `WithdrawHubFunds` will submit a request for the funds from the Hub and the funds will be sent over the CW20-ICS20 bridge again, if the user had funds stuck. - -```json -{ - "withdraw_hub_funds":{} -} -``` \ No newline at end of file diff --git a/contracts/outpost/src/contract.rs b/contracts/outpost/src/contract.rs deleted file mode 100644 index 5a0fd50d..00000000 --- a/contracts/outpost/src/contract.rs +++ /dev/null @@ -1,123 +0,0 @@ -use astroport_governance::interchain::{MAX_IBC_TIMEOUT_SECONDS, MIN_IBC_TIMEOUT_SECONDS}; -use cosmwasm_std::{entry_point, DepsMut, Env, MessageInfo, Response}; -use cw2::set_contract_version; - -use astroport_governance::outpost::{Config, InstantiateMsg, MigrateMsg}; - -use crate::error::ContractError; -use crate::state::CONFIG; - -/// Contract name that is used for migration. -const CONTRACT_NAME: &str = "astroport-outpost"; -/// Contract version that is used for migration. -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -/// Instantiates the contract, storing the config. -/// Returns a `Response` object on successful execution or a `ContractError` on failure. -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> Result { - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - - if !(MIN_IBC_TIMEOUT_SECONDS..=MAX_IBC_TIMEOUT_SECONDS).contains(&msg.ibc_timeout_seconds) { - return Err(ContractError::InvalidIBCTimeout { - timeout: msg.ibc_timeout_seconds, - min: MIN_IBC_TIMEOUT_SECONDS, - max: MAX_IBC_TIMEOUT_SECONDS, - }); - } - - let config = Config { - owner: deps.api.addr_validate(&msg.owner)?, - hub_addr: msg.hub_addr, - // The Hub channel will be set when the connection is established - hub_channel: None, - xastro_token_addr: deps.api.addr_validate(&msg.xastro_token_addr)?, - vxastro_token_addr: deps.api.addr_validate(&msg.vxastro_token_addr)?, - ibc_timeout_seconds: msg.ibc_timeout_seconds, - }; - CONFIG.save(deps.storage, &config)?; - - Ok(Response::default()) -} - -/// Migrates the contract to a new version. -#[entry_point] -pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { - Err(ContractError::MigrationError {}) -} - -#[cfg(test)] -mod tests { - - use super::*; - - use crate::{ - contract::instantiate, - mock::{mock_all, HUB, OWNER, VXASTRO_TOKEN, XASTRO_TOKEN}, - }; - - // Test Cases: - // - // Expect Success - // - Invalid IBC timeouts are rejected - // - #[test] - fn invalid_ibc_timeout() { - let (mut deps, env, info) = mock_all(OWNER); - - // Test MAX + 1 - let ibc_timeout_seconds = MAX_IBC_TIMEOUT_SECONDS + 1; - let err = instantiate( - deps.as_mut(), - env.clone(), - info.clone(), - astroport_governance::outpost::InstantiateMsg { - owner: OWNER.to_string(), - xastro_token_addr: XASTRO_TOKEN.to_string(), - vxastro_token_addr: VXASTRO_TOKEN.to_string(), - hub_addr: HUB.to_string(), - ibc_timeout_seconds, - }, - ) - .unwrap_err(); - - assert_eq!( - err, - ContractError::InvalidIBCTimeout { - timeout: ibc_timeout_seconds, - min: MIN_IBC_TIMEOUT_SECONDS, - max: MAX_IBC_TIMEOUT_SECONDS - } - ); - - // Test MIN - 1 - let ibc_timeout_seconds = MIN_IBC_TIMEOUT_SECONDS - 1; - let err = instantiate( - deps.as_mut(), - env, - info, - astroport_governance::outpost::InstantiateMsg { - owner: OWNER.to_string(), - xastro_token_addr: XASTRO_TOKEN.to_string(), - vxastro_token_addr: VXASTRO_TOKEN.to_string(), - hub_addr: HUB.to_string(), - ibc_timeout_seconds, - }, - ) - .unwrap_err(); - - assert_eq!( - err, - ContractError::InvalidIBCTimeout { - timeout: ibc_timeout_seconds, - min: MIN_IBC_TIMEOUT_SECONDS, - max: MAX_IBC_TIMEOUT_SECONDS - } - ); - } -} diff --git a/contracts/outpost/src/error.rs b/contracts/outpost/src/error.rs deleted file mode 100644 index b10ea324..00000000 --- a/contracts/outpost/src/error.rs +++ /dev/null @@ -1,51 +0,0 @@ -use cosmwasm_std::{OverflowError, StdError}; -use thiserror::Error; - -/// This enum describes bribes contract errors -#[derive(Error, Debug, PartialEq)] -pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("Contract can't be migrated!")] - MigrationError {}, - - #[error("Unauthorized")] - Unauthorized {}, - - #[error("You can not send 0 tokens")] - ZeroAmount {}, - - #[error( - "Proposal {0} is being queried from the Hub, please try again in a few minutes", - proposal_id - )] - PendingVoteExists { proposal_id: u64 }, - - #[error( - "The address has no voting power at the start of the proposal: {0}", - address - )] - NoVotingPower { address: String }, - - #[error("The IBC channel to the Hub has not been set")] - MissingHubChannel {}, - - #[error("The user has already voted on this proposal")] - AlreadyVoted {}, - - #[error("Channel already established: {channel_id}")] - ChannelAlreadyEstablished { channel_id: String }, - - #[error("Invalid source port {invalid}. Should be : {valid}")] - InvalidSourcePort { invalid: String, valid: String }, - - #[error("Invalid IBC timeout: {timeout}, must be between {min} and {max} seconds")] - InvalidIBCTimeout { timeout: u64, min: u64, max: u64 }, -} - -impl From for ContractError { - fn from(o: OverflowError) -> Self { - StdError::from(o).into() - } -} diff --git a/contracts/outpost/src/execute.rs b/contracts/outpost/src/execute.rs deleted file mode 100644 index 7b5e6f64..00000000 --- a/contracts/outpost/src/execute.rs +++ /dev/null @@ -1,1324 +0,0 @@ -use astroport_governance::interchain::{MAX_IBC_TIMEOUT_SECONDS, MIN_IBC_TIMEOUT_SECONDS}; -use astroport_governance::utils::check_contract_supports_channel; -use cosmwasm_std::entry_point; -use cosmwasm_std::{ - from_json, to_json_binary, Addr, CosmosMsg, DepsMut, Env, IbcMsg, MessageInfo, Response, - StdError, WasmMsg, -}; -use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; - -use astroport::common::{claim_ownership, drop_ownership_proposal, propose_new_owner}; -use astroport_governance::outpost::Config; -use astroport_governance::{ - assembly::ProposalVoteOption, - interchain::Hub, - outpost::{Cw20HookMsg, ExecuteMsg}, - voting_escrow_lite::get_emissions_voting_power, -}; - -use crate::query::get_user_voting_power; -use crate::state::VOTES; -use crate::{ - error::ContractError, - state::{PendingVote, CONFIG, OWNERSHIP_PROPOSAL, PENDING_VOTES, PROPOSALS_CACHE}, -}; - -/// Exposes all the execute functions available in the contract. -/// -/// ## Execute messages -/// * **ExecuteMsg::Receive(cw20_msg)** Receives a message of type [`Cw20ReceiveMsg`] and processes -/// it depending on the received template. -/// -/// RemoveOutpost { outpost_addr } Removes an outpost from the hub but does not close the channel, but all messages will be rejected -/// -/// * **ExecuteMsg::Receive(msg)** Receives a message of type [`Cw20ReceiveMsg`] and processes -/// it depending on the received template. -/// -/// * **ExecuteMsg::UpdateConfig { hub_addr }** Update parameters in the Outpost contract. Only the owner is allowed to -/// update the config -/// -/// * **ExecuteMsg::CastAssemblyVote { proposal_id, vote }** Cast a vote on an Assembly proposal from an Outpost -/// -/// * **ExecuteMsg::CastEmissionsVote { votes }** Cast a vote during an emissions voting period -/// -/// * **ExecuteMsg::KickUnlocked { user }** Kick an unlocked voter's voting power from the Generator Controller on the Hub -/// -/// * **ExecuteMsg::WithdrawHubFunds {}** Withdraw stuck funds from the Hub in case of specific IBC failures -/// -/// * **ExecuteMsg::ProposeNewOwner { new_owner, expires_in }** Creates a new request to change -/// contract ownership. -/// -/// * **ExecuteMsg::DropOwnershipProposal {}** Removes a request to change contract ownership. -/// -/// * **ExecuteMsg::ClaimOwnership {}** Claims contract ownership. -/// -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - match msg { - ExecuteMsg::Receive(msg) => receive_cw20(deps, env, info, msg), - ExecuteMsg::UpdateConfig { - hub_addr, - hub_channel, - ibc_timeout_seconds, - } => update_config(deps, env, info, hub_addr, hub_channel, ibc_timeout_seconds), - ExecuteMsg::CastAssemblyVote { proposal_id, vote } => { - cast_assembly_vote(deps, env, info, proposal_id, vote) - } - ExecuteMsg::CastEmissionsVote { votes } => cast_emissions_vote(deps, env, info, votes), - ExecuteMsg::KickUnlocked { user } => kick_unlocked(deps, env, info, user), - ExecuteMsg::KickBlacklisted { user } => kick_blacklisted(deps, env, info, user), - ExecuteMsg::WithdrawHubFunds {} => withdraw_hub_funds(deps, env, info), - ExecuteMsg::ProposeNewOwner { - new_owner, - expires_in, - } => { - let config = CONFIG.load(deps.storage)?; - - propose_new_owner( - deps, - info, - env, - new_owner, - expires_in, - config.owner, - OWNERSHIP_PROPOSAL, - ) - .map_err(Into::into) - } - ExecuteMsg::DropOwnershipProposal {} => { - let config: Config = CONFIG.load(deps.storage)?; - - drop_ownership_proposal(deps, info, config.owner, OWNERSHIP_PROPOSAL) - .map_err(Into::into) - } - ExecuteMsg::ClaimOwnership {} => { - claim_ownership(deps, info, env, OWNERSHIP_PROPOSAL, |deps, new_owner| { - CONFIG - .update::<_, StdError>(deps.storage, |mut v| { - v.owner = new_owner; - Ok(v) - }) - .map(|_| ()) - }) - .map_err(Into::into) - } - } -} - -/// Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on -/// the received template -/// -/// Funds received here must be from the xASTRO contract and is used for -/// unstaking. -/// -/// * **cw20_msg** CW20 message to process -fn receive_cw20( - deps: DepsMut, - env: Env, - info: MessageInfo, - cw20_msg: Cw20ReceiveMsg, -) -> Result { - let config = CONFIG.load(deps.storage)?; - - // We only allow xASTRO tokens to be sent here - if info.sender != config.xastro_token_addr { - return Err(ContractError::Unauthorized {}); - } - - match from_json(&cw20_msg.msg)? { - Cw20HookMsg::Unstake {} => execute_remote_unstake(deps, env, cw20_msg), - } -} - -/// Start the process of unstaking xASTRO from the Hub -/// -/// This burns the xASTRO we previously received and sends the unstake message -/// to the Hub where to original xASTRO will be unstaked and ASTRO returned -/// to the sender of this transaction. -/// -/// Note: Incase of IBC failures they xASTRO will be returned to the user or -/// they'll need to withdraw the unstaked ASTRO from the Hub using ExecuteMsg::WithdrawHubFunds -fn execute_remote_unstake( - deps: DepsMut, - env: Env, - msg: Cw20ReceiveMsg, -) -> Result { - let config = CONFIG.load(deps.storage)?; - - // Burn the xASTRO tokens we previously minted - let burn_msg = Cw20ExecuteMsg::Burn { amount: msg.amount }; - let wasm_msg = WasmMsg::Execute { - contract_addr: config.xastro_token_addr.to_string(), - msg: to_json_binary(&burn_msg)?, - funds: vec![], - }; - - let hub_channel = config - .hub_channel - .ok_or(ContractError::MissingHubChannel {})?; - - // Construct the unstake message to send to the Hub - let unstake = Hub::Unstake { - receiver: msg.sender.to_string(), - amount: msg.amount, - }; - let hub_unstake_msg: CosmosMsg = CosmosMsg::Ibc(IbcMsg::SendPacket { - channel_id: hub_channel.clone(), - data: to_json_binary(&unstake)?, - timeout: env - .block - .time - .plus_seconds(config.ibc_timeout_seconds) - .into(), - }); - - Ok(Response::default() - .add_message(wasm_msg) - .add_message(hub_unstake_msg) - .add_attribute("action", unstake.to_string()) - .add_attribute("amount", msg.amount.to_string()) - .add_attribute("channel", hub_channel)) -} - -/// Update the Outpost config -fn update_config( - deps: DepsMut, - env: Env, - info: MessageInfo, - hub_addr: Option, - hub_channel: Option, - ibc_timeout_seconds: Option, -) -> Result { - let mut config = CONFIG.load(deps.storage)?; - - // Only owner can update the config - if info.sender != config.owner { - return Err(ContractError::Unauthorized {}); - } - - if let Some(hub_addr) = hub_addr { - // We can't validate the Hub address - config.hub_addr = hub_addr; - // If a new Hub address is set, we clear the channel as we - // must create a new IBC channel - config.hub_channel = None; - } - - if let Some(hub_channel) = hub_channel { - // Ensure we have the channel that is being set - check_contract_supports_channel(deps.querier, &env.contract.address, &hub_channel)?; - - // Update the channel to the correct one - config.hub_channel = Some(hub_channel); - } - - if let Some(ibc_timeout_seconds) = ibc_timeout_seconds { - if !(MIN_IBC_TIMEOUT_SECONDS..=MAX_IBC_TIMEOUT_SECONDS).contains(&ibc_timeout_seconds) { - return Err(ContractError::InvalidIBCTimeout { - timeout: ibc_timeout_seconds, - min: MIN_IBC_TIMEOUT_SECONDS, - max: MAX_IBC_TIMEOUT_SECONDS, - }); - } - config.ibc_timeout_seconds = ibc_timeout_seconds; - } - - CONFIG.save(deps.storage, &config)?; - - Ok(Response::default()) -} - -/// Cast a vote on a proposal from an Outpost -/// -/// To validate the xASTRO holdings at the time the proposal was created we first -/// query the Hub for the proposal information if it hasn't been queried yet. Once -/// the proposal information is received we validate the vote and submit it -fn cast_assembly_vote( - deps: DepsMut, - env: Env, - info: MessageInfo, - proposal_id: u64, - vote_option: ProposalVoteOption, -) -> Result { - let config = CONFIG.load(deps.storage)?; - - let hub_channel = config - .hub_channel - .ok_or(ContractError::MissingHubChannel {})?; - - // Check if this user has voted already - if VOTES.has(deps.storage, (&info.sender, proposal_id)) { - return Err(ContractError::AlreadyVoted {}); - } - - // If we have this proposal in our local cached already, we can continue - // with fetching the voting power and submitting the vote - if let Some(proposal) = PROPOSALS_CACHE.may_load(deps.storage, proposal_id)? { - let voting_power = - get_user_voting_power(deps.as_ref(), info.sender.clone(), proposal.start_time)?; - - if voting_power.is_zero() { - return Err(ContractError::NoVotingPower { - address: info.sender.to_string(), - }); - } - - // Construct the vote message and submit it to the Hub - let cast_vote = Hub::CastAssemblyVote { - proposal_id: proposal.id.u64(), - vote_option: vote_option.clone(), - voter: info.sender.clone(), - voting_power, - }; - let hub_msg = CosmosMsg::Ibc(IbcMsg::SendPacket { - channel_id: hub_channel, - data: to_json_binary(&cast_vote)?, - timeout: env - .block - .time - .plus_seconds(config.ibc_timeout_seconds) - .into(), - }); - - // Log the vote to prevent spamming - VOTES.save(deps.storage, (&info.sender, proposal_id), &vote_option)?; - - return Ok(Response::new() - .add_message(hub_msg) - .add_attribute("action", cast_vote.to_string()) - .add_attribute("user", info.sender.to_string())); - } - - // If we don't have the proposal in our local cache it means that no - // vote has been cast from this Outpost for this proposal - // In this case we temporarily store the vote and submit an IBC transaction - // to fetch the proposal information. When the information is received via - // an IBC reply, we validate the data and submit the actual vote - - // If we already have a pending vote for this proposal we return an error - // as we're waiting for the proposal IBC query to return. We can't store - // lots of votes as we have no way to automatically submit them without - // the risk of running out of gas - - if PENDING_VOTES.has(deps.storage, proposal_id) { - return Err(ContractError::PendingVoteExists { proposal_id }); - } - - // Temporarily store the vote - let pending_vote = PendingVote { - proposal_id, - voter: info.sender, - vote_option, - }; - PENDING_VOTES.save(deps.storage, proposal_id, &pending_vote)?; - - // Query for proposal - let query_proposal = Hub::QueryProposal { id: proposal_id }; - let hub_query_msg = CosmosMsg::Ibc(IbcMsg::SendPacket { - channel_id: hub_channel, - data: to_json_binary(&query_proposal)?, - timeout: env - .block - .time - .plus_seconds(config.ibc_timeout_seconds) - .into(), - }); - - Ok(Response::default() - .add_message(hub_query_msg) - .add_attribute("action", query_proposal.to_string()) - .add_attribute("id", proposal_id.to_string())) -} - -/// Cast a vote on emissions during a vxASTRO voting period -/// -/// We validate the voting power by checking the vxASTRO power at this -/// moment as vxASTRO lite does not have any warmup period -fn cast_emissions_vote( - deps: DepsMut, - env: Env, - info: MessageInfo, - votes: Vec<(String, u16)>, -) -> Result { - let config = CONFIG.load(deps.storage)?; - - // Validate vxASTRO voting power - let vxastro_voting_power = - get_emissions_voting_power(&deps.querier, config.vxastro_token_addr, &info.sender)?; - - if vxastro_voting_power.is_zero() { - return Err(ContractError::NoVotingPower { - address: info.sender.to_string(), - }); - } - - let hub_channel = config - .hub_channel - .ok_or(ContractError::MissingHubChannel {})?; - - // Construct the vote message and submit it to the Hub - let cast_vote = Hub::CastEmissionsVote { - voter: info.sender.clone(), - voting_power: vxastro_voting_power, - votes, - }; - let hub_msg = CosmosMsg::Ibc(IbcMsg::SendPacket { - channel_id: hub_channel, - data: to_json_binary(&cast_vote)?, - timeout: env - .block - .time - .plus_seconds(config.ibc_timeout_seconds) - .into(), - }); - Ok(Response::new() - .add_message(hub_msg) - .add_attribute("action", cast_vote.to_string()) - .add_attribute("user", info.sender.to_string())) -} - -/// Kick an unlocked voter from the Generator Controller on the Hub -/// which will remove their voting power immediately. -/// -/// We only finalise the unlock in the vxASTRO contract when this kick is -/// successful -fn kick_unlocked( - deps: DepsMut, - env: Env, - info: MessageInfo, - user: Addr, -) -> Result { - let config = CONFIG.load(deps.storage)?; - - // This may only be called from the vxASTRO lite contract - if info.sender != config.vxastro_token_addr { - return Err(ContractError::Unauthorized {}); - } - - let hub_channel = config - .hub_channel - .ok_or(ContractError::MissingHubChannel {})?; - - // Construct the kick message and submit it to the Hub - let kick_unlocked = Hub::KickUnlockedVoter { - voter: user.clone(), - }; - let hub_msg = CosmosMsg::Ibc(IbcMsg::SendPacket { - channel_id: hub_channel, - data: to_json_binary(&kick_unlocked)?, - timeout: env - .block - .time - .plus_seconds(config.ibc_timeout_seconds) - .into(), - }); - - Ok(Response::new() - .add_message(hub_msg) - .add_attribute("action", kick_unlocked.to_string()) - .add_attribute("user", user)) -} - -/// Kick a blacklisted voter from the Generator Controller on the Hub -/// which will remove their voting power immediately. -/// -/// This can be called multiple times without unintended side effects -fn kick_blacklisted( - deps: DepsMut, - env: Env, - info: MessageInfo, - user: Addr, -) -> Result { - let config = CONFIG.load(deps.storage)?; - - // This may only be called from the vxASTRO lite contract - if info.sender != config.vxastro_token_addr { - return Err(ContractError::Unauthorized {}); - } - - let hub_channel = config - .hub_channel - .ok_or(ContractError::MissingHubChannel {})?; - - // Construct the kick message and submit it to the Hub - let kick_blacklisted = Hub::KickBlacklistedVoter { - voter: user.clone(), - }; - let hub_msg = CosmosMsg::Ibc(IbcMsg::SendPacket { - channel_id: hub_channel, - data: to_json_binary(&kick_blacklisted)?, - timeout: env - .block - .time - .plus_seconds(config.ibc_timeout_seconds) - .into(), - }); - - Ok(Response::new() - .add_message(hub_msg) - .add_attribute("action", kick_blacklisted.to_string()) - .add_attribute("user", user)) -} - -/// Submit a request to withdraw / retry sending funds stuck on the Hub -/// back to the sender address. This is possible because of IBC failures. -/// -/// This will only return the funds of the user executing this transaction. -fn withdraw_hub_funds( - deps: DepsMut, - env: Env, - info: MessageInfo, -) -> Result { - let config = CONFIG.load(deps.storage)?; - - let hub_channel = config - .hub_channel - .ok_or(ContractError::MissingHubChannel {})?; - - // Construct the withdraw message and submit it to the Hub - let withdraw = Hub::WithdrawFunds { - user: info.sender.clone(), - }; - let hub_msg = CosmosMsg::Ibc(IbcMsg::SendPacket { - channel_id: hub_channel, - data: to_json_binary(&withdraw)?, - timeout: env - .block - .time - .plus_seconds(config.ibc_timeout_seconds) - .into(), - }); - - Ok(Response::new() - .add_message(hub_msg) - .add_attribute("action", withdraw.to_string()) - .add_attribute("user", info.sender.to_string())) -} - -#[cfg(test)] -mod tests { - - use super::*; - - use cosmwasm_std::{testing::mock_info, IbcMsg, ReplyOn, SubMsg, Uint128, Uint64}; - - use crate::{ - contract::instantiate, - mock::{mock_all, setup_channel, HUB, OWNER, VXASTRO_TOKEN, XASTRO_TOKEN}, - query::query, - }; - use astroport_governance::interchain::{Hub, ProposalSnapshot}; - - // Test Cases: - // - // Expect Success - // - An unstake IBC message is emitted - // - // Expect Error - // - No xASTRO is sent to the contract - // - The funds sent to the contract is not xASTRO - // - The Hub address and channel isn't set - // - #[test] - fn unstake() { - let (mut deps, env, info) = mock_all(OWNER); - - let user = "user"; - let user_funds = Uint128::from(1000u128); - let ibc_timeout_seconds = 10u64; - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::outpost::InstantiateMsg { - owner: OWNER.to_string(), - xastro_token_addr: XASTRO_TOKEN.to_string(), - vxastro_token_addr: VXASTRO_TOKEN.to_string(), - hub_addr: HUB.to_string(), - ibc_timeout_seconds: 10, - }, - ) - .unwrap(); - - // Set up valid Hub - setup_channel(deps.as_mut(), env.clone()); - - // Update config with new channel - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::outpost::ExecuteMsg::UpdateConfig { - hub_addr: None, - hub_channel: Some("channel-3".to_string()), - ibc_timeout_seconds: None, - }, - ) - .unwrap(); - - // Attempt to unstake with an incorrect token - let err = execute( - deps.as_mut(), - env.clone(), - mock_info("not_xastro", &[]), - astroport_governance::outpost::ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: user.to_string(), - amount: user_funds, - msg: to_json_binary(&astroport_governance::outpost::Cw20HookMsg::Unstake {}) - .unwrap(), - }), - ) - .unwrap_err(); - - assert_eq!(err, ContractError::Unauthorized {}); - - // Attempt to unstake correctly - let res = execute( - deps.as_mut(), - env.clone(), - mock_info(XASTRO_TOKEN, &[]), - astroport_governance::outpost::ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: user.to_string(), - amount: user_funds, - msg: to_json_binary(&astroport_governance::outpost::Cw20HookMsg::Unstake {}) - .unwrap(), - }), - ) - .unwrap(); - - // Build the expected message - let ibc_message = to_json_binary(&Hub::Unstake { - receiver: user.to_string(), - amount: user_funds, - }) - .unwrap(); - - // We should have two messages - assert_eq!(res.messages.len(), 2); - - // First message must be the burn of the amount of xASTRO sent - assert_eq!( - res.messages[0], - SubMsg { - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - msg: WasmMsg::Execute { - contract_addr: XASTRO_TOKEN.to_string(), - msg: to_json_binary(&Cw20ExecuteMsg::Burn { amount: user_funds }).unwrap(), - funds: vec![], - } - .into(), - } - ); - - // Second message must be the IBC unstake - assert_eq!( - res.messages[1], - SubMsg { - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - msg: IbcMsg::SendPacket { - channel_id: "channel-3".to_string(), - data: ibc_message, - timeout: env.block.time.plus_seconds(ibc_timeout_seconds).into(), - } - .into(), - } - ); - } - - // Test Cases: - // - // Expect Success - // - The config is updated - // - // Expect Error - // - When the config is updated by a non-owner - // - #[test] - fn update_config() { - let (mut deps, env, info) = mock_all(OWNER); - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::outpost::InstantiateMsg { - owner: OWNER.to_string(), - xastro_token_addr: XASTRO_TOKEN.to_string(), - vxastro_token_addr: VXASTRO_TOKEN.to_string(), - hub_addr: HUB.to_string(), - ibc_timeout_seconds: 10, - }, - ) - .unwrap(); - - setup_channel(deps.as_mut(), env.clone()); - - // Attempt to update the hub address by a non-owner - let err = execute( - deps.as_mut(), - env.clone(), - mock_info("not_owner", &[]), - astroport_governance::outpost::ExecuteMsg::UpdateConfig { - hub_addr: Some("new_hub".to_string()), - hub_channel: None, - ibc_timeout_seconds: None, - }, - ) - .unwrap_err(); - assert_eq!(err, ContractError::Unauthorized {}); - - let config = query( - deps.as_ref(), - env.clone(), - astroport_governance::outpost::QueryMsg::Config {}, - ) - .unwrap(); - - // Ensure the config set during instantiation is still there - assert_eq!( - config, - to_json_binary(&astroport_governance::outpost::Config { - owner: Addr::unchecked(OWNER), - xastro_token_addr: Addr::unchecked(XASTRO_TOKEN), - vxastro_token_addr: Addr::unchecked(VXASTRO_TOKEN), - hub_addr: HUB.to_string(), - hub_channel: None, - ibc_timeout_seconds: 10, - }) - .unwrap() - ); - - // Attempt to update the hub address by the owner - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::outpost::ExecuteMsg::UpdateConfig { - hub_addr: Some("new_owner_hub".to_string()), - hub_channel: None, - ibc_timeout_seconds: None, - }, - ) - .unwrap(); - - let config = query( - deps.as_ref(), - env.clone(), - astroport_governance::outpost::QueryMsg::Config {}, - ) - .unwrap(); - - // Ensure the config set after the update is correct - // Once a new Hub is set, the Hub channel is cleared to allow a new - // connection - assert_eq!( - config, - to_json_binary(&astroport_governance::outpost::Config { - owner: Addr::unchecked(OWNER), - xastro_token_addr: Addr::unchecked(XASTRO_TOKEN), - vxastro_token_addr: Addr::unchecked(VXASTRO_TOKEN), - hub_addr: "new_owner_hub".to_string(), - hub_channel: None, - ibc_timeout_seconds: 10, - }) - .unwrap() - ); - - // Update the hub channel - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::outpost::ExecuteMsg::UpdateConfig { - hub_addr: None, - hub_channel: Some("channel-15".to_string()), - ibc_timeout_seconds: None, - }, - ) - .unwrap(); - - let config = query( - deps.as_ref(), - env.clone(), - astroport_governance::outpost::QueryMsg::Config {}, - ) - .unwrap(); - - // Ensure the config set after the update is correct - // Once a new Hub is set, the Hub channel is cleared to allow a new - // connection - assert_eq!( - config, - to_json_binary(&astroport_governance::outpost::Config { - owner: Addr::unchecked(OWNER), - xastro_token_addr: Addr::unchecked(XASTRO_TOKEN), - vxastro_token_addr: Addr::unchecked(VXASTRO_TOKEN), - hub_addr: "new_owner_hub".to_string(), - hub_channel: Some("channel-15".to_string()), - ibc_timeout_seconds: 10, - }) - .unwrap() - ); - - // Update the IBC timeout - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::outpost::ExecuteMsg::UpdateConfig { - hub_addr: None, - hub_channel: None, - ibc_timeout_seconds: Some(35), - }, - ) - .unwrap(); - - let config = query( - deps.as_ref(), - env, - astroport_governance::outpost::QueryMsg::Config {}, - ) - .unwrap(); - - // Ensure the config set after the update is correct - // Once a new Hub is set, the Hub channel is cleared to allow a new - // connection - assert_eq!( - config, - to_json_binary(&astroport_governance::outpost::Config { - owner: Addr::unchecked(OWNER), - xastro_token_addr: Addr::unchecked(XASTRO_TOKEN), - vxastro_token_addr: Addr::unchecked(VXASTRO_TOKEN), - hub_addr: "new_owner_hub".to_string(), - hub_channel: Some("channel-15".to_string()), - ibc_timeout_seconds: 35, - }) - .unwrap() - ); - } - - // Test Cases: - // - // Expect Success - // - A proposal query is emitted when the proposal is not in the cache - // - A vote is emitted when the proposal is in the cache - // - // Expect Error - // - User has no voting power at the time of the proposal - // - #[test] - fn vote_on_proposal() { - let (mut deps, env, info) = mock_all(OWNER); - - let proposal_id = 1u64; - let user = "user"; - let voting_power = 1000u64; - let ibc_timeout_seconds = 10u64; - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::outpost::InstantiateMsg { - owner: OWNER.to_string(), - xastro_token_addr: XASTRO_TOKEN.to_string(), - vxastro_token_addr: VXASTRO_TOKEN.to_string(), - hub_addr: HUB.to_string(), - ibc_timeout_seconds, - }, - ) - .unwrap(); - - // Set up valid Hub - setup_channel(deps.as_mut(), env.clone()); - - // Update config with new channel - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::outpost::ExecuteMsg::UpdateConfig { - hub_addr: None, - hub_channel: Some("channel-3".to_string()), - ibc_timeout_seconds: None, - }, - ) - .unwrap(); - - // Cast a vote with no proposal in the cache - let res = execute( - deps.as_mut(), - env.clone(), - mock_info(user, &[]), - astroport_governance::outpost::ExecuteMsg::CastAssemblyVote { - proposal_id: 1, - vote: astroport_governance::assembly::ProposalVoteOption::For, - }, - ) - .unwrap(); - - // Wrap the query - let ibc_message = to_json_binary(&Hub::QueryProposal { id: proposal_id }).unwrap(); - - // Ensure a query is emitted - assert_eq!( - res.messages[0], - SubMsg { - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - msg: IbcMsg::SendPacket { - channel_id: "channel-3".to_string(), - data: ibc_message, - timeout: env.block.time.plus_seconds(ibc_timeout_seconds).into(), - } - .into(), - } - ); - - // Add a proposal to the cache - PROPOSALS_CACHE - .save( - &mut deps.storage, - proposal_id, - &ProposalSnapshot { - id: Uint64::from(proposal_id), - start_time: 1689939457, - }, - ) - .unwrap(); - - // Cast a vote with a proposal in the cache - let res = execute( - deps.as_mut(), - env.clone(), - mock_info(user, &[]), - astroport_governance::outpost::ExecuteMsg::CastAssemblyVote { - proposal_id, - vote: astroport_governance::assembly::ProposalVoteOption::For, - }, - ) - .unwrap(); - - // Build the expected message - let ibc_message = to_json_binary(&Hub::CastAssemblyVote { - proposal_id, - voter: Addr::unchecked(user), - vote_option: astroport_governance::assembly::ProposalVoteOption::For, - voting_power: Uint128::from(voting_power), - }) - .unwrap(); - - // We should only have 1 message - assert_eq!(res.messages.len(), 1); - - // Ensure a vote is emitted - assert_eq!( - res.messages[0], - SubMsg { - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - msg: IbcMsg::SendPacket { - channel_id: "channel-3".to_string(), - data: ibc_message, - timeout: env.block.time.plus_seconds(ibc_timeout_seconds).into(), - } - .into(), - } - ); - - // Cast a vote on a proposal already voted on - let err = execute( - deps.as_mut(), - env.clone(), - mock_info(user, &[]), - astroport_governance::outpost::ExecuteMsg::CastAssemblyVote { - proposal_id, - vote: astroport_governance::assembly::ProposalVoteOption::For, - }, - ) - .unwrap_err(); - - assert_eq!(err, ContractError::AlreadyVoted {}); - - // Check that we can query the vote - let vote_data = query( - deps.as_ref(), - env, - astroport_governance::outpost::QueryMsg::ProposalVoted { - proposal_id, - user: user.to_string(), - }, - ) - .unwrap(); - - assert_eq!(vote_data, to_json_binary(&ProposalVoteOption::For).unwrap()); - } - - // Test Cases: - // - // Expect Success - // - An emissions vote is emitted is the user has voting power - // - // Expect Error - // - User has no voting power - // - #[test] - fn vote_on_emissions() { - let (mut deps, env, info) = mock_all(OWNER); - - let user = "user"; - let votes = vec![("pool".to_string(), 10000u16)]; - let voting_power = 1000u64; - let ibc_timeout_seconds = 10u64; - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::outpost::InstantiateMsg { - owner: OWNER.to_string(), - xastro_token_addr: XASTRO_TOKEN.to_string(), - vxastro_token_addr: VXASTRO_TOKEN.to_string(), - hub_addr: HUB.to_string(), - ibc_timeout_seconds, - }, - ) - .unwrap(); - - // Set up valid Hub - setup_channel(deps.as_mut(), env.clone()); - - // Update config with new channel - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::outpost::ExecuteMsg::UpdateConfig { - hub_addr: None, - hub_channel: Some("channel-3".to_string()), - ibc_timeout_seconds: None, - }, - ) - .unwrap(); - - // Cast a vote on emissions - let res = execute( - deps.as_mut(), - env.clone(), - mock_info(user, &[]), - astroport_governance::outpost::ExecuteMsg::CastEmissionsVote { - votes: votes.clone(), - }, - ) - .unwrap(); - - // Build the expected message - let ibc_message = to_json_binary(&Hub::CastEmissionsVote { - voter: Addr::unchecked(user), - votes, - voting_power: Uint128::from(voting_power), - }) - .unwrap(); - - // We should only have 1 message - assert_eq!(res.messages.len(), 1); - - // Ensure a vote is emitted - assert_eq!( - res.messages[0], - SubMsg { - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - msg: IbcMsg::SendPacket { - channel_id: "channel-3".to_string(), - data: ibc_message, - timeout: env.block.time.plus_seconds(ibc_timeout_seconds).into(), - } - .into(), - } - ); - } - - // Test Cases: - // - // Expect Success - // - The kick message is forwarded - // - // Expect Error - // - When the sender is not the vxASTRO contract - // - #[test] - fn kick_unlocked() { - let (mut deps, env, info) = mock_all(OWNER); - - let user = "user"; - let ibc_timeout_seconds = 10u64; - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::outpost::InstantiateMsg { - owner: OWNER.to_string(), - xastro_token_addr: XASTRO_TOKEN.to_string(), - vxastro_token_addr: VXASTRO_TOKEN.to_string(), - hub_addr: HUB.to_string(), - ibc_timeout_seconds, - }, - ) - .unwrap(); - - // Set up valid Hub - setup_channel(deps.as_mut(), env.clone()); - - // Update config with new channel - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::outpost::ExecuteMsg::UpdateConfig { - hub_addr: None, - hub_channel: Some("channel-3".to_string()), - ibc_timeout_seconds: None, - }, - ) - .unwrap(); - - // Kick a user as another user, not allowed - let err = execute( - deps.as_mut(), - env.clone(), - mock_info(user, &[]), - astroport_governance::outpost::ExecuteMsg::KickUnlocked { - user: Addr::unchecked(user), - }, - ) - .unwrap_err(); - - assert_eq!(err, ContractError::Unauthorized {}); - - // Kick a user as the vxASTRO contract - let res = execute( - deps.as_mut(), - env.clone(), - mock_info(VXASTRO_TOKEN, &[]), - astroport_governance::outpost::ExecuteMsg::KickUnlocked { - user: Addr::unchecked(user), - }, - ) - .unwrap(); - - // Build the expected message - let ibc_message = to_json_binary(&Hub::KickUnlockedVoter { - voter: Addr::unchecked(user), - }) - .unwrap(); - - // We should only have 1 message - assert_eq!(res.messages.len(), 1); - - // Ensure a kick is emitted - assert_eq!( - res.messages[0], - SubMsg { - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - msg: IbcMsg::SendPacket { - channel_id: "channel-3".to_string(), - data: ibc_message, - timeout: env.block.time.plus_seconds(ibc_timeout_seconds).into(), - } - .into(), - } - ); - } - - // Test Cases: - // - // Expect Success - // - The kick message is forwarded - // - // Expect Error - // - When the sender is not the vxASTRO contract - // - #[test] - fn kick_blacklisted() { - let (mut deps, env, info) = mock_all(OWNER); - - let user = "user"; - let ibc_timeout_seconds = 10u64; - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::outpost::InstantiateMsg { - owner: OWNER.to_string(), - xastro_token_addr: XASTRO_TOKEN.to_string(), - vxastro_token_addr: VXASTRO_TOKEN.to_string(), - hub_addr: HUB.to_string(), - ibc_timeout_seconds, - }, - ) - .unwrap(); - - // Set up valid Hub - setup_channel(deps.as_mut(), env.clone()); - - // Update config with new channel - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::outpost::ExecuteMsg::UpdateConfig { - hub_addr: None, - hub_channel: Some("channel-3".to_string()), - ibc_timeout_seconds: None, - }, - ) - .unwrap(); - - // Kick a user as another user, not allowed - let err = execute( - deps.as_mut(), - env.clone(), - mock_info(user, &[]), - astroport_governance::outpost::ExecuteMsg::KickBlacklisted { - user: Addr::unchecked(user), - }, - ) - .unwrap_err(); - - assert_eq!(err, ContractError::Unauthorized {}); - - // Kick a user as the vxASTRO contract - let res = execute( - deps.as_mut(), - env.clone(), - mock_info(VXASTRO_TOKEN, &[]), - astroport_governance::outpost::ExecuteMsg::KickBlacklisted { - user: Addr::unchecked(user), - }, - ) - .unwrap(); - - // Build the expected message - let ibc_message = to_json_binary(&Hub::KickBlacklistedVoter { - voter: Addr::unchecked(user), - }) - .unwrap(); - - // We should only have 1 message - assert_eq!(res.messages.len(), 1); - - // Ensure a kick is emitted - assert_eq!( - res.messages[0], - SubMsg { - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - msg: IbcMsg::SendPacket { - channel_id: "channel-3".to_string(), - data: ibc_message, - timeout: env.block.time.plus_seconds(ibc_timeout_seconds).into(), - } - .into(), - } - ); - } - - // Test Cases: - // - // Expect Success - // - The kick message is forwarded - // - // Expect Error - // - When the sender is not the vxASTRO contract - // - #[test] - fn withdraw_funds() { - let (mut deps, env, info) = mock_all(OWNER); - - let user = "user"; - let ibc_timeout_seconds = 10u64; - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::outpost::InstantiateMsg { - owner: OWNER.to_string(), - xastro_token_addr: XASTRO_TOKEN.to_string(), - vxastro_token_addr: VXASTRO_TOKEN.to_string(), - hub_addr: HUB.to_string(), - ibc_timeout_seconds, - }, - ) - .unwrap(); - - // Set up valid Hub - setup_channel(deps.as_mut(), env.clone()); - - // Update config with new channel - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::outpost::ExecuteMsg::UpdateConfig { - hub_addr: None, - hub_channel: Some("channel-3".to_string()), - ibc_timeout_seconds: None, - }, - ) - .unwrap(); - - // Withdraw stuck funds from the Hub - let res = execute( - deps.as_mut(), - env.clone(), - mock_info(user, &[]), - astroport_governance::outpost::ExecuteMsg::WithdrawHubFunds {}, - ) - .unwrap(); - - // Build the expected message - let ibc_message = to_json_binary(&Hub::WithdrawFunds { - user: Addr::unchecked(user), - }) - .unwrap(); - - // We should only have 1 message - assert_eq!(res.messages.len(), 1); - - // Ensure a withdrawal is emitted - assert_eq!( - res.messages[0], - SubMsg { - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - msg: IbcMsg::SendPacket { - channel_id: "channel-3".to_string(), - data: ibc_message, - timeout: env.block.time.plus_seconds(ibc_timeout_seconds).into(), - } - .into(), - } - ); - } -} diff --git a/contracts/outpost/src/ibc.rs b/contracts/outpost/src/ibc.rs deleted file mode 100644 index 291fb650..00000000 --- a/contracts/outpost/src/ibc.rs +++ /dev/null @@ -1,662 +0,0 @@ -use cosmwasm_std::{ - ensure, entry_point, from_json, to_json_binary, CosmosMsg, Deps, DepsMut, Env, - Ibc3ChannelOpenResponse, IbcBasicResponse, IbcChannelCloseMsg, IbcChannelConnectMsg, - IbcChannelOpenMsg, IbcChannelOpenResponse, IbcMsg, IbcOrder, IbcPacketAckMsg, - IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse, Never, StdError, StdResult, -}; - -use astroport_governance::interchain::{get_contract_from_ibc_port, Hub, Outpost, Response}; - -use crate::{ - error::ContractError, - ibc_failure::handle_failed_messages, - ibc_mint::handle_ibc_xastro_mint, - query::get_user_voting_power, - state::{CONFIG, PENDING_VOTES, PROPOSALS_CACHE}, -}; - -pub const IBC_APP_VERSION: &str = "astroport-outpost-v1"; -pub const IBC_ORDERING: IbcOrder = IbcOrder::Unordered; - -/// Handle the opening of a new IBC channel -/// -/// We verify that the connection is using the correct configuration -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn ibc_channel_open( - _deps: DepsMut, - _env: Env, - msg: IbcChannelOpenMsg, -) -> Result { - let channel = msg.channel(); - - if channel.order != IBC_ORDERING { - return Err(ContractError::Std(StdError::generic_err( - "Ordering is invalid. The channel must be unordered".to_string(), - ))); - } - if channel.version != IBC_APP_VERSION { - return Err(ContractError::Std(StdError::generic_err(format!( - "Must set version to `{IBC_APP_VERSION}`" - )))); - } - - if let Some(counter_version) = msg.counterparty_version() { - if counter_version != IBC_APP_VERSION { - return Err(ContractError::Std(StdError::generic_err(format!( - "Counterparty version must be `{IBC_APP_VERSION}`" - )))); - } - } - - Ok(Some(Ibc3ChannelOpenResponse { - version: IBC_APP_VERSION.to_string(), - })) -} - -/// Handle the connection of a new IBC channel -/// -/// We verify that the connection is being made to the configured Hub and -/// if the channel has not been set, add it -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn ibc_channel_connect( - deps: DepsMut, - _env: Env, - msg: IbcChannelConnectMsg, -) -> Result { - let channel = msg.channel(); - - if let Some(counter_version) = msg.counterparty_version() { - if counter_version != IBC_APP_VERSION { - return Err(ContractError::Std(StdError::generic_err(format!( - "Counterparty version must be `{IBC_APP_VERSION}`" - )))); - } - } - - // Only a connection to the Hub is allowed - let counterparty_port = - get_contract_from_ibc_port(channel.counterparty_endpoint.port_id.as_str()); - - let config = CONFIG.load(deps.storage)?; - match config.hub_channel { - Some(channel_id) => { - return Err(ContractError::ChannelAlreadyEstablished { channel_id }); - } - None => { - if counterparty_port != config.hub_addr { - return Err(ContractError::InvalidSourcePort { - invalid: counterparty_port.to_string(), - valid: config.hub_addr.to_string(), - }); - } - } - } - - Ok(IbcBasicResponse::new() - .add_attribute("action", "ibc_connect") - .add_attribute("channel_id", &channel.endpoint.channel_id)) -} - -/// Handle the receiving the packets while wrapping the actual call to provide -/// returning errors as an acknowledgement. -/// -/// This allows the original caller from another chain to handle the failure -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn ibc_packet_receive( - deps: DepsMut, - env: Env, - msg: IbcPacketReceiveMsg, -) -> Result { - do_packet_receive(deps, env, msg).or_else(|err| { - // Construct an error acknowledgement that can be handled on the Hub - let ack_data = to_json_binary(&Response::new_error(err.to_string())).unwrap(); - - Ok(IbcReceiveResponse::new() - .add_attribute("action", "ibc_packet_receive") - .add_attribute("error", err.to_string()) - .set_ack(ack_data)) - }) -} - -/// Process the received packet and return the response -/// -/// Packets are expected to be wrapped in the Outpost format, if it doesn't conform -/// it will be failed. -/// -/// If a ContractError is returned, it will be wrapped into a Response -/// containing the error to be handled on the Outpost -fn do_packet_receive( - deps: DepsMut, - _env: Env, - msg: IbcPacketReceiveMsg, -) -> Result { - block_unauthorized_packets( - deps.as_ref(), - msg.packet.src.port_id.clone(), - msg.packet.dest.channel_id.clone(), - )?; - - // Parse the packet data into a Hub message - let hub_msg: Outpost = from_json(&msg.packet.data)?; - match hub_msg { - Outpost::MintXAstro { receiver, amount } => handle_ibc_xastro_mint(deps, receiver, amount), - } -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn ibc_packet_timeout( - deps: DepsMut, - _env: Env, - msg: IbcPacketTimeoutMsg, -) -> Result { - let mut response = IbcBasicResponse::new().add_attribute("action", "ibc_packet_timeout"); - - // In case of an IBC timeout we might need to reverse actions similar - // to failed messages. - // We look at the original packet to determine what failed and take - // the appropriate action - let failed_msg: Hub = from_json(&msg.packet.data)?; - response = handle_failed_messages(deps, failed_msg, response)?; - - Ok(response) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn ibc_packet_ack( - deps: DepsMut, - env: Env, - msg: IbcPacketAckMsg, -) -> Result { - let mut response = IbcBasicResponse::new().add_attribute("action", "ibc_packet_ack"); - - let ack: Result = from_json(&msg.acknowledgement.data); - match ack { - Ok(hub_response) => { - match hub_response { - Response::QueryProposal(proposal) => { - // We cache the proposal ID and start time for future vote - // checks without needing to query the Hub again - PROPOSALS_CACHE.save(deps.storage, proposal.id.u64(), &proposal)?; - - // We need to submit the initial vote that triggered this - // proposal to be queried from the pending vote cache - if let Some(pending_vote) = - PENDING_VOTES.may_load(deps.storage, proposal.id.u64())? - { - let config = CONFIG.load(deps.storage)?; - - let voting_power = get_user_voting_power( - deps.as_ref(), - pending_vote.voter.clone(), - proposal.start_time, - )?; - - if voting_power.is_zero() { - return Err(ContractError::NoVotingPower { - address: pending_vote.voter.to_string(), - }); - } - - let hub_channel = config - .hub_channel - .ok_or(ContractError::MissingHubChannel {})?; - - // Construct the vote message and submit it to the Hub - let cast_vote = Hub::CastAssemblyVote { - proposal_id: proposal.id.u64(), - vote_option: pending_vote.vote_option, - voter: pending_vote.voter.clone(), - voting_power, - }; - let hub_msg = CosmosMsg::Ibc(IbcMsg::SendPacket { - channel_id: hub_channel, - data: to_json_binary(&cast_vote)?, - timeout: env - .block - .time - .plus_seconds(config.ibc_timeout_seconds) - .into(), - }); - response = response - .add_message(hub_msg) - .add_attribute("action", cast_vote.to_string()) - .add_attribute("user", pending_vote.voter.to_string()); - - // Remove this pending vote from the cache - PENDING_VOTES.remove(deps.storage, proposal.id.u64()); - } - - response = response - .add_attribute("hub_response", "query_response") - .add_attribute("response_type", "proposal") - .add_attribute("proposal_id", proposal.id.to_string()) - .add_attribute("proposal_start", proposal.start_time.to_string()) - } - Response::Result { - action, - address, - error, - } => { - response = response - .add_attribute("action", action.unwrap_or_else(|| "unknown".to_string())) - .add_attribute("user", address.unwrap_or_else(|| "unknown".to_string())) - .add_attribute("err", error.unwrap_or_else(|| "unknown".to_string())) - } - } - } - Err(err) => { - // In case of error, ack.data will be in the format similar to - // {"error":"ABCI code: 5: error handling packet: see events for details"} - // but the events do not contain the details - // - // Instead we look at the original packet to determine what failed, - // the reason for the failure can't be determined at this time due - // to a limitation in wasmd/wasmvm. For us we just need to know what failed, - // the reason is not required to continue - // See https://github.com/CosmWasm/cosmwasm/issues/1707 - - let raw_error = base64::encode(&msg.acknowledgement.data); - // Attach the errors to the response - response = response - .add_attribute("raw_error", raw_error) - .add_attribute("ack_error", err.to_string()); - - // Handle the possible failures - let original: Hub = from_json(&msg.original_packet.data)?; - response = handle_failed_messages(deps, original, response)?; - } - } - Ok(response) -} - -/// Handle the closing of IBC channels, which we don't allow -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn ibc_channel_close( - _deps: DepsMut, - _env: Env, - _channel: IbcChannelCloseMsg, -) -> StdResult { - Err(StdError::generic_err("Closing channel is not allowed")) -} - -/// Checks the provided port against the known Hub. -/// -/// If the port doesn't exist, this function will return an error, effectively blocking the packet. -fn block_unauthorized_packets( - deps: Deps, - port_id: String, - channel_id: String, -) -> Result<(), ContractError> { - let config = CONFIG.load(deps.storage)?; - let counterparty_port = get_contract_from_ibc_port(port_id.as_str()); - ensure!( - config.hub_addr == counterparty_port, - ContractError::Unauthorized {} - ); - - ensure!( - config.hub_channel == Some(channel_id), - ContractError::Unauthorized {} - ); - - Ok(()) -} - -#[cfg(test)] -mod tests { - use astroport_governance::interchain::ProposalSnapshot; - use cosmwasm_std::{ - testing::{mock_info, MOCK_CONTRACT_ADDR}, - Addr, IbcAcknowledgement, IbcEndpoint, IbcPacket, ReplyOn, SubMsg, Uint128, Uint64, - }; - - use super::*; - use crate::{ - contract::instantiate, - execute::execute, - mock::{mock_all, mock_channel, setup_channel, HUB, OWNER, VXASTRO_TOKEN, XASTRO_TOKEN}, - state::PendingVote, - }; - - // Test Cases: - // - // Expect Success - // - Creating a channel with correct settings - // - // Expect Error - // - Attempt to create a channel with an invalid version - // - Attempt to create a channel with an invalid ordering - #[test] - fn ibc_open_channel() { - let (mut deps, env, info) = mock_all(OWNER); - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::outpost::InstantiateMsg { - owner: OWNER.to_string(), - xastro_token_addr: XASTRO_TOKEN.to_string(), - vxastro_token_addr: VXASTRO_TOKEN.to_string(), - hub_addr: HUB.to_string(), - ibc_timeout_seconds: 10, - }, - ) - .unwrap(); - - // A connection with invalid ordering is not allowed - let channel = mock_channel( - "wasm.outpost", - "channel-2", - "wasm.unknown_contract", - "channel-7", - IbcOrder::Ordered, - "non-astroport-v1", - ); - let open_msg = IbcChannelOpenMsg::new_init(channel); - let err = ibc_channel_open(deps.as_mut(), env.clone(), open_msg).unwrap_err(); - assert_eq!( - err, - ContractError::Std(StdError::generic_err( - "Ordering is invalid. The channel must be unordered" - )) - ); - - // A connection with invalid version is not allowed - let channel = mock_channel( - "wasm.outpost", - "channel-2", - "wasm.unknown_contract", - "channel-7", - IbcOrder::Unordered, - "non-astroport-v1", - ); - let open_msg = IbcChannelOpenMsg::new_init(channel); - let err = ibc_channel_open(deps.as_mut(), env.clone(), open_msg).unwrap_err(); - assert_eq!( - err, - ContractError::Std(StdError::generic_err( - "Must set version to `astroport-outpost-v1`" - )) - ); - - // A connection with correct settings is allowed - let channel = mock_channel( - "wasm.outpost", - "channel-2", - "wasm.unknown_contract", - "channel-7", - IbcOrder::Unordered, - IBC_APP_VERSION, - ); - let open_msg = IbcChannelOpenMsg::new_init(channel); - ibc_channel_open(deps.as_mut(), env, open_msg).unwrap(); - - // let connect_msg = IbcChannelConnectMsg::new_ack(channel, IBC_APP_VERSION); - // ibc_channel_connect(deps.as_mut(), env.clone(), connect_msg).unwrap(); - } - - // Test Cases: - // - // Expect Success - // - Creating a channel with an allowed Outpost - // - // Expect Error - // - Attempt to connect a channel with an invalid version - // - Attempt to connect a channel before registering an Outpost - // - Attempt to connect a channel with an unauthorize Outpost address - #[test] - fn ibc_connect_channel() { - let (mut deps, env, info) = mock_all(OWNER); - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::outpost::InstantiateMsg { - owner: OWNER.to_string(), - xastro_token_addr: XASTRO_TOKEN.to_string(), - vxastro_token_addr: VXASTRO_TOKEN.to_string(), - hub_addr: HUB.to_string(), - ibc_timeout_seconds: 10, - }, - ) - .unwrap(); - - // Opening a connection with unknown contracts is not allowed - let channel = mock_channel( - "wasm.outpost", - "channel-2", - "wasm.unknown_contract", - "channel-7", - IbcOrder::Unordered, - IBC_APP_VERSION, - ); - let open_msg = IbcChannelOpenMsg::new_init(channel.clone()); - ibc_channel_open(deps.as_mut(), env.clone(), open_msg).unwrap(); - let connect_msg = IbcChannelConnectMsg::new_ack(channel, IBC_APP_VERSION); - let err = ibc_channel_connect(deps.as_mut(), env.clone(), connect_msg).unwrap_err(); - assert_eq!( - err, - ContractError::InvalidSourcePort { - invalid: "unknown_contract".to_string(), - valid: "hub".to_string() - } - ); - - // Opening a connection with the hub is allowed - let channel = mock_channel( - "wasm.outpost", - "channel-3", - format!("wasm.{}", HUB).as_str(), - "channel-7", - IbcOrder::Unordered, - IBC_APP_VERSION, - ); - - // Attempt to connect with the wrong IBC app version - let open_msg = IbcChannelOpenMsg::new_init(channel.clone()); - ibc_channel_open(deps.as_mut(), env.clone(), open_msg).unwrap(); - let connect_msg = IbcChannelConnectMsg::new_ack(channel.clone(), "WRONG_VERSION"); - let err = ibc_channel_connect(deps.as_mut(), env.clone(), connect_msg).unwrap_err(); - assert_eq!( - err, - ContractError::Std(StdError::generic_err(format!( - "Counterparty version must be `{}`", - IBC_APP_VERSION - ))) - ); - - let open_msg = IbcChannelOpenMsg::new_init(channel.clone()); - ibc_channel_open(deps.as_mut(), env.clone(), open_msg).unwrap(); - let connect_msg = IbcChannelConnectMsg::new_ack(channel, IBC_APP_VERSION); - ibc_channel_connect(deps.as_mut(), env.clone(), connect_msg).unwrap(); - - // Update config with new channel - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::outpost::ExecuteMsg::UpdateConfig { - hub_addr: None, - hub_channel: Some("channel-3".to_string()), - ibc_timeout_seconds: None, - }, - ) - .unwrap(); - - // Attempting to open the channel again is not allowed - let channel = mock_channel( - "wasm.outpost", - "channel-3", - format!("wasm.{}", HUB).as_str(), - "channel-7", - IbcOrder::Unordered, - IBC_APP_VERSION, - ); - let open_msg = IbcChannelOpenMsg::new_init(channel.clone()); - ibc_channel_open(deps.as_mut(), env.clone(), open_msg).unwrap(); - let connect_msg = IbcChannelConnectMsg::new_ack(channel, IBC_APP_VERSION); - let err = ibc_channel_connect(deps.as_mut(), env, connect_msg).unwrap_err(); - assert_eq!( - err, - ContractError::ChannelAlreadyEstablished { - channel_id: "channel-3".to_string(), - } - ); - } - - // Test Cases: - // - // Expect Success - // - Query results returned in the acknoledgement data is processed correctly - #[test] - fn ibc_ack_packet() { - let (mut deps, env, info) = mock_all(OWNER); - - let proposal_id = 1u64; - let user = "user"; - let voting_power = 1000u64; - let ibc_timeout_seconds = 10u64; - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::outpost::InstantiateMsg { - owner: OWNER.to_string(), - xastro_token_addr: XASTRO_TOKEN.to_string(), - vxastro_token_addr: VXASTRO_TOKEN.to_string(), - hub_addr: HUB.to_string(), - ibc_timeout_seconds, - }, - ) - .unwrap(); - - // Set up valid Hub - setup_channel(deps.as_mut(), env.clone()); - - // Update config with new channel - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::outpost::ExecuteMsg::UpdateConfig { - hub_addr: None, - hub_channel: Some("channel-3".to_string()), - ibc_timeout_seconds: None, - }, - ) - .unwrap(); - - // The pending would be stored in the contract before the query is sent - let pending_vote = PendingVote { - proposal_id, - voter: Addr::unchecked(user), - vote_option: astroport_governance::assembly::ProposalVoteOption::For, - }; - PENDING_VOTES - .save(&mut deps.storage, proposal_id, &pending_vote) - .unwrap(); - - let proposal_response = Response::QueryProposal(ProposalSnapshot { - id: Uint64::from(proposal_id), - start_time: 1689942949u64, - }); - - let ack = IbcAcknowledgement::new(to_json_binary(&proposal_response).unwrap()); - let mint_msg = to_json_binary(&Outpost::MintXAstro { - receiver: "user".to_owned(), - amount: Uint128::one(), - }) - .unwrap(); - let original_packet = IbcPacket::new( - mint_msg, - IbcEndpoint { - port_id: format!("wasm.{}", MOCK_CONTRACT_ADDR), - channel_id: "channel-3".to_string(), - }, - IbcEndpoint { - port_id: format!("wasm.{}", HUB), - channel_id: "channel-7".to_string(), - }, - 3, - env.block.time.plus_seconds(10).into(), - ); - - let ack_msg = IbcPacketAckMsg::new(ack, original_packet, Addr::unchecked("relayer")); - let res = ibc_packet_ack(deps.as_mut(), env.clone(), ack_msg).unwrap(); - - // If we received the proposal, we can now submit the vote - assert_eq!(res.messages.len(), 1); - - // Build the expected message - let ibc_message = to_json_binary(&Hub::CastAssemblyVote { - proposal_id, - voter: Addr::unchecked(user), - vote_option: astroport_governance::assembly::ProposalVoteOption::For, - voting_power: Uint128::from(voting_power), - }) - .unwrap(); - - // Ensure a vote is emitted - assert_eq!( - res.messages[0], - SubMsg { - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - msg: IbcMsg::SendPacket { - channel_id: "channel-3".to_string(), - data: ibc_message, - timeout: env.block.time.plus_seconds(ibc_timeout_seconds).into(), - } - .into(), - } - ); - } - - // Test Cases: - // - // Expect Success - // - Creating a channel with an allowed Outpost - // - // Expect Error - // - Attempt to connect a channel with an invalid version - // - Attempt to connect a channel before registering an Outpost - // - Attempt to connect a channel with an unauthorize Outpost address - #[test] - fn ibc_close_channel() { - let (mut deps, env, info) = mock_all(OWNER); - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::outpost::InstantiateMsg { - owner: OWNER.to_string(), - xastro_token_addr: XASTRO_TOKEN.to_string(), - vxastro_token_addr: VXASTRO_TOKEN.to_string(), - hub_addr: HUB.to_string(), - ibc_timeout_seconds: 10, - }, - ) - .unwrap(); - - setup_channel(deps.as_mut(), env.clone()); - - let channel = mock_channel( - "wasm.outpost", - "channel-3", - "wasm.hub", - "channel-7", - IbcOrder::Unordered, - IBC_APP_VERSION, - ); - - let close_msg = IbcChannelCloseMsg::new_init(channel); - let err = ibc_channel_close(deps.as_mut(), env, close_msg).unwrap_err(); - - assert_eq!(err, StdError::generic_err("Closing channel is not allowed")); - } -} diff --git a/contracts/outpost/src/ibc_failure.rs b/contracts/outpost/src/ibc_failure.rs deleted file mode 100644 index 7fd9e6ca..00000000 --- a/contracts/outpost/src/ibc_failure.rs +++ /dev/null @@ -1,656 +0,0 @@ -use cosmwasm_std::{to_json_binary, DepsMut, IbcBasicResponse, WasmMsg}; - -use astroport_governance::{interchain::Hub, voting_escrow_lite}; - -use crate::{ - error::ContractError, - ibc_mint::mint_xastro_msg, - state::{CONFIG, PENDING_VOTES, VOTES}, -}; - -pub fn handle_failed_messages( - deps: DepsMut, - failed_msg: Hub, - mut response: IbcBasicResponse, -) -> Result { - match failed_msg.clone() { - Hub::CastAssemblyVote { - proposal_id, voter, .. - } => { - // Vote failed, remove vote from the log so user may retry - VOTES.remove(deps.storage, (&voter, proposal_id)); - - response = response - .add_attribute("interchain_action", failed_msg.to_string()) - .add_attribute("user", voter.to_string()); - } - Hub::CastEmissionsVote { voter, .. } => { - response = response - .add_attribute("interchain_action", failed_msg.to_string()) - .add_attribute("user", voter.to_string()); - } - Hub::QueryProposal { id } => { - // If the proposal query failed we need to remove the pending vote - // otherwise no other vote will be possible for this proposal - let pending_vote = PENDING_VOTES.load(deps.storage, id)?; - PENDING_VOTES.remove(deps.storage, id); - - response = response - .add_attribute("interchain_action", failed_msg.to_string()) - .add_attribute("user", pending_vote.voter.to_string()); - } - - Hub::Unstake { receiver, amount } => { - // Unstaking involves us burning the received xASTRO before - // sending the unstake message to the Hub. If the unstaking - // fails we need to mint the xASTRO back to the user - let msg = mint_xastro_msg(deps.as_ref(), receiver.clone(), amount)?; - response = response - .add_message(msg) - .add_attribute("interchain_action", failed_msg.to_string()) - .add_attribute("user", receiver); - } - Hub::KickUnlockedVoter { voter } => { - // The voting power has not been removed for this user and we must - // relock their unlocking position - let config = CONFIG.load(deps.storage)?; - - let relock_msg = voting_escrow_lite::ExecuteMsg::Relock { - user: voter.to_string(), - }; - - let msg = WasmMsg::Execute { - contract_addr: config.vxastro_token_addr.to_string(), - msg: to_json_binary(&relock_msg)?, - funds: vec![], - }; - - response = response - .add_message(msg) - .add_attribute("interchain_action", failed_msg.to_string()) - .add_attribute("user", voter); - } - Hub::WithdrawFunds { user } => { - response = response - .add_attribute("interchain_action", failed_msg.to_string()) - .add_attribute("user", user.to_string()); - } - // Not all Hub responses will be received here, we only handle the ones we have - // control over - _ => { - response = response.add_attribute("action", failed_msg.to_string()); - } - } - Ok(response) -} - -#[cfg(test)] -mod tests { - use cosmwasm_std::{ - attr, - testing::{mock_info, MOCK_CONTRACT_ADDR}, - to_json_binary, Addr, IbcEndpoint, IbcPacket, IbcPacketTimeoutMsg, ReplyOn, StdError, - SubMsg, Uint128, WasmMsg, - }; - - use super::*; - use crate::{ - contract::instantiate, - execute::execute, - ibc::ibc_packet_timeout, - mock::{mock_all, setup_channel, HUB, OWNER, VXASTRO_TOKEN, XASTRO_TOKEN}, - state::PendingVote, - }; - - // Test Cases: - // - // Expect Success - // - xASTRO is returned to the original sender - // - // Expect Error - // - Receive timeout from a different channel - #[test] - fn unstake_failure() { - let (mut deps, env, info) = mock_all(OWNER); - - let user = "user"; - let amount = Uint128::from(1000u64); - let ibc_timeout_seconds = 10u64; - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::outpost::InstantiateMsg { - owner: OWNER.to_string(), - xastro_token_addr: XASTRO_TOKEN.to_string(), - vxastro_token_addr: VXASTRO_TOKEN.to_string(), - hub_addr: HUB.to_string(), - ibc_timeout_seconds, - }, - ) - .unwrap(); - - setup_channel(deps.as_mut(), env.clone()); - - // Update config with new channel - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::outpost::ExecuteMsg::UpdateConfig { - hub_addr: None, - hub_channel: Some("channel-3".to_string()), - ibc_timeout_seconds: None, - }, - ) - .unwrap(); - - // Attempt to get timeout from different contract - let original_unstake_msg = to_json_binary(&Hub::Unstake { - receiver: user.to_string(), - amount, - }) - .unwrap(); - - let packet = IbcPacket::new( - original_unstake_msg, - IbcEndpoint { - port_id: format!("wasm.{}", MOCK_CONTRACT_ADDR), - channel_id: "channel-3".to_string(), - }, - IbcEndpoint { - port_id: format!("wasm.{}", HUB), - channel_id: "channel-7".to_string(), - }, - 4, - env.block.time.plus_seconds(ibc_timeout_seconds).into(), - ); - - // When the timeout occurs, we should see an unstake message to return the ASTRO to the user - let timeout_packet = IbcPacketTimeoutMsg::new(packet, Addr::unchecked("relayer")); - let res = ibc_packet_timeout(deps.as_mut(), env, timeout_packet).unwrap(); - - // Should have exactly one message - assert_eq!(res.messages.len(), 1); - - // Verify that the mint message matches the expected message - let xastro_mint_msg = to_json_binary(&cw20::Cw20ExecuteMsg::Mint { - recipient: user.to_string(), - amount, - }) - .unwrap(); - - // We should see the mint xASTRO SubMessage - assert_eq!( - res.messages[0], - SubMsg { - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - msg: WasmMsg::Execute { - contract_addr: XASTRO_TOKEN.to_string(), - msg: xastro_mint_msg, - funds: vec![], - } - .into(), - } - ); - } - - // Test Cases: - // - // Expect Success - // - Vote fails to reach the Hub - #[test] - fn governance_vote_failure() { - let (mut deps, env, info) = mock_all(OWNER); - - let user = "user"; - let proposal_id = 1u64; - let voting_power = Uint128::from(1000u64); - let ibc_timeout_seconds = 10u64; - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::outpost::InstantiateMsg { - owner: OWNER.to_string(), - xastro_token_addr: XASTRO_TOKEN.to_string(), - vxastro_token_addr: VXASTRO_TOKEN.to_string(), - hub_addr: HUB.to_string(), - ibc_timeout_seconds, - }, - ) - .unwrap(); - - setup_channel(deps.as_mut(), env.clone()); - - // Update config with new channel - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::outpost::ExecuteMsg::UpdateConfig { - hub_addr: None, - hub_channel: Some("channel-3".to_string()), - ibc_timeout_seconds: None, - }, - ) - .unwrap(); - - // Construct the original message - let original_msg = to_json_binary(&Hub::CastAssemblyVote { - proposal_id, - voter: Addr::unchecked(user), - vote_option: astroport_governance::assembly::ProposalVoteOption::For, - voting_power, - }) - .unwrap(); - // Authorised channels - let packet = IbcPacket::new( - original_msg, - IbcEndpoint { - port_id: format!("wasm.{}", MOCK_CONTRACT_ADDR), - channel_id: "channel-3".to_string(), - }, - IbcEndpoint { - port_id: format!("wasm.{}", HUB), - channel_id: "channel-7".to_string(), - }, - 4, - env.block.time.plus_seconds(ibc_timeout_seconds).into(), - ); - - // When the timeout occurs, we should see the correct attributes emitted - let timeout_packet = IbcPacketTimeoutMsg::new(packet, Addr::unchecked("relayer")); - let res = ibc_packet_timeout(deps.as_mut(), env, timeout_packet).unwrap(); - - // Should have no messages - assert_eq!(res.messages.len(), 0); - - // Should have the correct attributes - assert_eq!( - res.attributes, - vec![ - attr("action".to_string(), "ibc_packet_timeout".to_string()), - attr( - "interchain_action".to_string(), - "cast_assembly_vote".to_string() - ), - attr("user".to_string(), user.to_string()), - ] - ); - } - - // Test Cases: - // - // Expect Success - // - Emissions Vote fails to reach the Hub - #[test] - fn emissions_vote_failure() { - let (mut deps, env, info) = mock_all(OWNER); - - let user = "user"; - let votes = vec![("pool".to_string(), 10000u16)]; - let voting_power = Uint128::from(1000u64); - let ibc_timeout_seconds = 10u64; - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::outpost::InstantiateMsg { - owner: OWNER.to_string(), - xastro_token_addr: XASTRO_TOKEN.to_string(), - vxastro_token_addr: VXASTRO_TOKEN.to_string(), - hub_addr: HUB.to_string(), - ibc_timeout_seconds, - }, - ) - .unwrap(); - - setup_channel(deps.as_mut(), env.clone()); - - // Update config with new channel - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::outpost::ExecuteMsg::UpdateConfig { - hub_addr: None, - hub_channel: Some("channel-3".to_string()), - ibc_timeout_seconds: None, - }, - ) - .unwrap(); - - // Construct the original message - let original_msg = to_json_binary(&Hub::CastEmissionsVote { - voter: Addr::unchecked(user), - voting_power, - votes, - }) - .unwrap(); - // Authorised channels - let packet = IbcPacket::new( - original_msg, - IbcEndpoint { - port_id: format!("wasm.{}", MOCK_CONTRACT_ADDR), - channel_id: "channel-3".to_string(), - }, - IbcEndpoint { - port_id: format!("wasm.{}", HUB), - channel_id: "channel-7".to_string(), - }, - 4, - env.block.time.plus_seconds(ibc_timeout_seconds).into(), - ); - - // When the timeout occurs, we should see the correct attributes emitted - let timeout_packet = IbcPacketTimeoutMsg::new(packet, Addr::unchecked("relayer")); - let res = ibc_packet_timeout(deps.as_mut(), env, timeout_packet).unwrap(); - - // Should have no messages - assert_eq!(res.messages.len(), 0); - - // Should have the correct attributes - assert_eq!( - res.attributes, - vec![ - attr("action".to_string(), "ibc_packet_timeout".to_string()), - attr( - "interchain_action".to_string(), - "cast_emissions_vote".to_string() - ), - attr("user".to_string(), user.to_string()), - ] - ); - } - - // Test Cases: - // - // Expect Success - // - Proposal query fails - #[test] - fn query_proposal_failure() { - let (mut deps, env, info) = mock_all(OWNER); - - let proposal_id = 1u64; - let user = "user"; - let ibc_timeout_seconds = 10u64; - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::outpost::InstantiateMsg { - owner: OWNER.to_string(), - xastro_token_addr: XASTRO_TOKEN.to_string(), - vxastro_token_addr: VXASTRO_TOKEN.to_string(), - hub_addr: HUB.to_string(), - ibc_timeout_seconds, - }, - ) - .unwrap(); - - setup_channel(deps.as_mut(), env.clone()); - - // Update config with new channel - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::outpost::ExecuteMsg::UpdateConfig { - hub_addr: None, - hub_channel: Some("channel-3".to_string()), - ibc_timeout_seconds: None, - }, - ) - .unwrap(); - - // Construct the original message - let original_msg = to_json_binary(&Hub::QueryProposal { id: proposal_id }).unwrap(); - // Authorised channels - let packet = IbcPacket::new( - original_msg, - IbcEndpoint { - port_id: format!("wasm.{}", MOCK_CONTRACT_ADDR), - channel_id: "channel-3".to_string(), - }, - IbcEndpoint { - port_id: format!("wasm.{}", HUB), - channel_id: "channel-7".to_string(), - }, - 4, - env.block.time.plus_seconds(ibc_timeout_seconds).into(), - ); - - // Ensure we have a pending vote - PENDING_VOTES - .save( - &mut deps.storage, - proposal_id, - &PendingVote { - proposal_id, - vote_option: astroport_governance::assembly::ProposalVoteOption::For, - voter: Addr::unchecked(user), - }, - ) - .unwrap(); - - // When the timeout occurs, we should see the correct attributes emitted - let timeout_packet = IbcPacketTimeoutMsg::new(packet, Addr::unchecked("relayer")); - let res = ibc_packet_timeout(deps.as_mut(), env, timeout_packet).unwrap(); - - // Should have no messages - assert_eq!(res.messages.len(), 0); - - // Should have the correct attributes - assert_eq!( - res.attributes, - vec![ - attr("action".to_string(), "ibc_packet_timeout".to_string()), - attr( - "interchain_action".to_string(), - "query_proposal".to_string() - ), - attr("user".to_string(), user.to_string()), - ] - ); - - // Also ensure pending votes for this proposal was removed - let err = PENDING_VOTES.load(&deps.storage, proposal_id).unwrap_err(); - - assert_eq!( - err, - StdError::NotFound { - kind: "astroport_outpost::state::PendingVote".to_string() - } - ); - } - - // Test Cases: - // - // Expect Success - // - Kicking unlocked fails to reach the Hub - #[test] - fn kick_unlocked() { - let (mut deps, env, info) = mock_all(OWNER); - - let user = "user"; - let ibc_timeout_seconds = 10u64; - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::outpost::InstantiateMsg { - owner: OWNER.to_string(), - xastro_token_addr: XASTRO_TOKEN.to_string(), - vxastro_token_addr: VXASTRO_TOKEN.to_string(), - hub_addr: HUB.to_string(), - ibc_timeout_seconds, - }, - ) - .unwrap(); - - setup_channel(deps.as_mut(), env.clone()); - - // Update config with new channel - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::outpost::ExecuteMsg::UpdateConfig { - hub_addr: None, - hub_channel: Some("channel-3".to_string()), - ibc_timeout_seconds: None, - }, - ) - .unwrap(); - - // Construct the original message - let original_msg = to_json_binary(&Hub::KickUnlockedVoter { - voter: Addr::unchecked(user), - }) - .unwrap(); - // Authorised channels - let packet = IbcPacket::new( - original_msg, - IbcEndpoint { - port_id: format!("wasm.{}", MOCK_CONTRACT_ADDR), - channel_id: "channel-3".to_string(), - }, - IbcEndpoint { - port_id: format!("wasm.{}", HUB), - channel_id: "channel-7".to_string(), - }, - 4, - env.block.time.plus_seconds(ibc_timeout_seconds).into(), - ); - - // When the timeout occurs, we should see the correct attributes emitted - let timeout_packet = IbcPacketTimeoutMsg::new(packet, Addr::unchecked("relayer")); - let res = ibc_packet_timeout(deps.as_mut(), env, timeout_packet).unwrap(); - - // Should have 1 relock message - assert_eq!(res.messages.len(), 1); - - // Should have the correct attributes - assert_eq!( - res.attributes, - vec![ - attr("action".to_string(), "ibc_packet_timeout".to_string()), - attr( - "interchain_action".to_string(), - "kick_unlocked_voter".to_string() - ), - attr("user".to_string(), user.to_string()), - ] - ); - - // Confirm relock message is correct - assert_eq!( - res.messages[0], - SubMsg { - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - msg: WasmMsg::Execute { - contract_addr: VXASTRO_TOKEN.to_string(), - msg: to_json_binary( - &astroport_governance::voting_escrow_lite::ExecuteMsg::Relock { - user: user.to_string() - } - ) - .unwrap(), - funds: vec![], - } - .into(), - } - ); - } - - // Test Cases: - // - // Expect Success - // - Kicking unlocked fails to reach the Hub - #[test] - fn withdraw_funds() { - let (mut deps, env, info) = mock_all(OWNER); - - let user = "user"; - let ibc_timeout_seconds = 10u64; - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::outpost::InstantiateMsg { - owner: OWNER.to_string(), - xastro_token_addr: XASTRO_TOKEN.to_string(), - vxastro_token_addr: VXASTRO_TOKEN.to_string(), - hub_addr: HUB.to_string(), - ibc_timeout_seconds, - }, - ) - .unwrap(); - - setup_channel(deps.as_mut(), env.clone()); - - // Update config with new channel - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::outpost::ExecuteMsg::UpdateConfig { - hub_addr: None, - hub_channel: Some("channel-3".to_string()), - ibc_timeout_seconds: None, - }, - ) - .unwrap(); - - // Construct the original message - let original_msg = to_json_binary(&Hub::WithdrawFunds { - user: Addr::unchecked(user), - }) - .unwrap(); - // Authorised channels - let packet = IbcPacket::new( - original_msg, - IbcEndpoint { - port_id: format!("wasm.{}", MOCK_CONTRACT_ADDR), - channel_id: "channel-3".to_string(), - }, - IbcEndpoint { - port_id: format!("wasm.{}", HUB), - channel_id: "channel-7".to_string(), - }, - 4, - env.block.time.plus_seconds(ibc_timeout_seconds).into(), - ); - - // When the timeout occurs, we should see the correct attributes emitted - let timeout_packet = IbcPacketTimeoutMsg::new(packet, Addr::unchecked("relayer")); - let res = ibc_packet_timeout(deps.as_mut(), env, timeout_packet).unwrap(); - - // Should have no messages - assert_eq!(res.messages.len(), 0); - - // Should have the correct attributes - assert_eq!( - res.attributes, - vec![ - attr("action".to_string(), "ibc_packet_timeout".to_string()), - attr( - "interchain_action".to_string(), - "withdraw_funds".to_string() - ), - attr("user".to_string(), user.to_string()), - ] - ); - } -} diff --git a/contracts/outpost/src/ibc_mint.rs b/contracts/outpost/src/ibc_mint.rs deleted file mode 100644 index 06c078d8..00000000 --- a/contracts/outpost/src/ibc_mint.rs +++ /dev/null @@ -1,180 +0,0 @@ -use astroport_governance::interchain::Response; -use cosmwasm_std::{to_json_binary, Deps, DepsMut, IbcReceiveResponse, Uint128, WasmMsg}; -use cw20::Cw20ExecuteMsg; - -use crate::{error::ContractError, state::CONFIG}; - -/// Mint new xASTRO based on the message received from the Hub, it cannot be -/// called directly. -/// -/// This is called in response to a staking message sent to the Hub -pub fn handle_ibc_xastro_mint( - deps: DepsMut, - recipient: String, - amount: Uint128, -) -> Result { - // Mint the new amount of xASTRO to the recipient that originally initiated - // the ASTRO staking - let msg = mint_xastro_msg(deps.as_ref(), recipient.clone(), amount)?; - - // If the minting succeeds, the ack will be sent back to the Hub - let ack_data = to_json_binary(&Response::new_success( - "mint_xastro".to_owned(), - recipient.to_string(), - ))?; - - let response = IbcReceiveResponse::new() - .add_message(msg) - .set_ack(ack_data) - .add_attribute("action", "mint_xastro") - .add_attribute("user", recipient) - .add_attribute("amount", amount); - - Ok(response) -} - -/// Create a new message to mint xASTRO to a specific address -pub fn mint_xastro_msg( - deps: Deps, - recipient: String, - amount: Uint128, -) -> Result { - let config = CONFIG.load(deps.storage)?; - - let mint_msg = Cw20ExecuteMsg::Mint { recipient, amount }; - Ok(WasmMsg::Execute { - contract_addr: config.xastro_token_addr.to_string(), - msg: to_json_binary(&mint_msg)?, - funds: vec![], - }) -} - -#[cfg(test)] -mod tests { - use astroport_governance::interchain::Outpost; - use cosmwasm_std::{ - from_json, testing::mock_info, Addr, IbcPacketReceiveMsg, ReplyOn, SubMsg, Uint128, - }; - - use super::*; - use crate::{ - contract::instantiate, - execute::execute, - ibc::ibc_packet_receive, - mock::{mock_all, mock_ibc_packet, setup_channel, HUB, OWNER, VXASTRO_TOKEN, XASTRO_TOKEN}, - }; - - // Test Cases: - // - // Expect Success - // - Mint the amount of xASTRO from the Hub to the recipient - // - // Expect Error - // - Sender is not the Hub - #[test] - fn ibc_mint_xastro() { - let (mut deps, env, info) = mock_all(OWNER); - - let receiver = "user"; - let amount = Uint128::from(1000u64); - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::outpost::InstantiateMsg { - owner: OWNER.to_string(), - xastro_token_addr: XASTRO_TOKEN.to_string(), - vxastro_token_addr: VXASTRO_TOKEN.to_string(), - hub_addr: HUB.to_string(), - ibc_timeout_seconds: 10, - }, - ) - .unwrap(); - - setup_channel(deps.as_mut(), env.clone()); - - // Update config with new channel - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::outpost::ExecuteMsg::UpdateConfig { - hub_addr: None, - hub_channel: Some("channel-3".to_string()), - ibc_timeout_seconds: None, - }, - ) - .unwrap(); - - let ibc_mint = to_json_binary(&Outpost::MintXAstro { - receiver: receiver.to_string(), - amount, - }) - .unwrap(); - - // Attempts to mint xASTRO from any other address than the Hub - let recv_packet = mock_ibc_packet("wasm.nothub", "channel-7", ibc_mint.clone()); - - let msg = IbcPacketReceiveMsg::new(recv_packet, Addr::unchecked("relayer")); - let res = ibc_packet_receive(deps.as_mut(), env.clone(), msg).unwrap(); - let ack: Response = from_json(&res.acknowledgement).unwrap(); - match ack { - Response::Result { error, .. } => { - assert!(error == Some("Unauthorized".to_string())); - } - _ => panic!("Wrong response type"), - } - - // Attempts to mint xASTRO from any other channel than the Hub - let recv_packet = mock_ibc_packet(&format!("wasm.{}", HUB), "channel-7", ibc_mint.clone()); - let msg = IbcPacketReceiveMsg::new(recv_packet, Addr::unchecked("relayer")); - let res = ibc_packet_receive(deps.as_mut(), env.clone(), msg).unwrap(); - let ack: Response = from_json(&res.acknowledgement).unwrap(); - match ack { - Response::Result { error, .. } => { - assert!(error == Some("Unauthorized".to_string())); - } - _ => panic!("Wrong response type"), - } - - // Mint from Hub contract and channel - let recv_packet = mock_ibc_packet(&format!("wasm.{}", HUB), "channel-3", ibc_mint); - let msg = IbcPacketReceiveMsg::new(recv_packet, Addr::unchecked("relayer")); - let res = ibc_packet_receive(deps.as_mut(), env, msg).unwrap(); - - let ack: Response = from_json(&res.acknowledgement).unwrap(); - match ack { - Response::Result { error, .. } => { - assert!(error.is_none()); - } - _ => panic!("Wrong response type"), - } - - // Should have exactly one message - assert_eq!(res.messages.len(), 1); - - // Verify that the mint message matches the expected message - let xastro_mint_msg = to_json_binary(&cw20::Cw20ExecuteMsg::Mint { - recipient: receiver.to_string(), - amount, - }) - .unwrap(); - - // We should see the mint xASTRO SubMessage - assert_eq!( - res.messages[0], - SubMsg { - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - msg: WasmMsg::Execute { - contract_addr: XASTRO_TOKEN.to_string(), - msg: xastro_mint_msg, - funds: vec![], - } - .into(), - } - ); - } -} diff --git a/contracts/outpost/src/lib.rs b/contracts/outpost/src/lib.rs deleted file mode 100644 index 04c41938..00000000 --- a/contracts/outpost/src/lib.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub mod contract; -pub mod error; -pub mod execute; -pub mod ibc; -pub mod ibc_failure; -pub mod ibc_mint; -pub mod query; -pub mod state; - -#[cfg(test)] -mod mock; diff --git a/contracts/outpost/src/mock.rs b/contracts/outpost/src/mock.rs deleted file mode 100644 index b6d77e83..00000000 --- a/contracts/outpost/src/mock.rs +++ /dev/null @@ -1,217 +0,0 @@ -#[cfg(test)] -use cosmwasm_std::{ - testing::{mock_env, mock_info, MockApi, MockQuerier, MockStorage}, - to_json_binary, Binary, DepsMut, Env, IbcChannel, IbcChannelConnectMsg, IbcChannelOpenMsg, - IbcEndpoint, IbcOrder, IbcPacket, IbcQuery, ListChannelsResponse, MessageInfo, OwnedDeps, - Timestamp, Uint128, -}; - -use cosmwasm_std::testing::MOCK_CONTRACT_ADDR; -use cosmwasm_std::{ - from_json, Empty, Querier, QuerierResult, QueryRequest, SystemError, SystemResult, WasmQuery, -}; - -use crate::ibc::{ibc_channel_connect, ibc_channel_open, IBC_APP_VERSION}; - -pub const CONTRACT_PORT: &str = "ibc:wasm1234567890abcdef"; -pub const CONNECTION_ID: &str = "connection-2"; -pub const OWNER: &str = "owner"; -pub const HUB: &str = "hub"; -pub const XASTRO_TOKEN: &str = "xastro"; -pub const VXASTRO_TOKEN: &str = "vxastro"; - -/// mock_dependencies is a drop-in replacement for cosmwasm_std::testing::mock_dependencies. -/// This uses the Astroport CustomQuerier. -#[cfg(test)] -pub fn mock_dependencies() -> OwnedDeps { - let custom_querier: WasmMockQuerier = - WasmMockQuerier::new(MockQuerier::new(&[(MOCK_CONTRACT_ADDR, &[])])); - - OwnedDeps { - storage: MockStorage::default(), - api: MockApi::default(), - querier: custom_querier, - custom_query_type: Default::default(), - } -} - -/// WasmMockQuerier will respond to requests from the custom querier, -/// providing responses to the contracts -pub struct WasmMockQuerier { - base: MockQuerier, -} - -impl Querier for WasmMockQuerier { - fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { - // MockQuerier doesn't support Custom, so we ignore it completely - let request: QueryRequest = match from_json(bin_request) { - Ok(v) => v, - Err(e) => { - return SystemResult::Err(SystemError::InvalidRequest { - error: format!("Parsing query request: {}", e), - request: bin_request.into(), - }) - } - }; - self.handle_query(&request) - } -} - -impl WasmMockQuerier { - pub fn handle_query(&self, request: &QueryRequest) -> QuerierResult { - match &request { - QueryRequest::Wasm(WasmQuery::Smart { contract_addr, msg }) => { - if contract_addr == XASTRO_TOKEN { - match from_json(msg).unwrap() { - astroport::xastro_outpost_token::QueryMsg::BalanceAt { - address: _, - timestamp: _, - } => { - let balance = astroport::token::BalanceResponse { - balance: Uint128::from(1000u128), - }; - SystemResult::Ok(to_json_binary(&balance).into()) - } - _ => { - panic!("DO NOT ENTER HERE") - } - } - } else { - match from_json(msg).unwrap() { - astroport_governance::voting_escrow_lite::QueryMsg::UserDepositAt { - user:_, - timestamp:_, - } => { - let balance = astroport::token::BalanceResponse { - balance: Uint128::zero(), - }; - SystemResult::Ok(to_json_binary(&balance).into()) - } - astroport_governance::voting_escrow_lite::QueryMsg::UserEmissionsVotingPower { - user:_, - } => { - let balance = astroport_governance::voting_escrow_lite::VotingPowerResponse { - voting_power: Uint128::from(1000u128), - }; - SystemResult::Ok(to_json_binary(&balance).into()) - } - _ => { - panic!("DO NOT ENTER HERE") - } - } - } - } - QueryRequest::Ibc(IbcQuery::ListChannels { .. }) => { - let response = ListChannelsResponse { - channels: vec![ - IbcChannel::new( - IbcEndpoint { - port_id: "wasm".to_string(), - channel_id: "channel-3".to_string(), - }, - IbcEndpoint { - port_id: "wasm".to_string(), - channel_id: "channel-1".to_string(), - }, - IbcOrder::Unordered, - "version", - "connection-1", - ), - IbcChannel::new( - IbcEndpoint { - port_id: "wasm".to_string(), - channel_id: "channel-15".to_string(), - }, - IbcEndpoint { - port_id: "wasm".to_string(), - channel_id: "channel-1".to_string(), - }, - IbcOrder::Unordered, - "version", - "connection-1", - ), - ], - }; - SystemResult::Ok(to_json_binary(&response).into()) - } - _ => self.base.handle_query(request), - } - } -} - -impl WasmMockQuerier { - pub fn new(base: MockQuerier) -> Self { - WasmMockQuerier { base } - } -} - -/// Mock the dependencies for unit tests -pub fn mock_all( - sender: &str, -) -> ( - OwnedDeps, - Env, - MessageInfo, -) { - let deps = mock_dependencies(); - let env = mock_env(); - let info = mock_info(sender, &[]); - (deps, env, info) -} - -/// Mock an IBC channel -pub fn mock_channel( - our_port: &str, - our_channel_id: &str, - counter_port: &str, - counter_channel: &str, - ibc_order: IbcOrder, - ibc_version: &str, -) -> IbcChannel { - IbcChannel::new( - IbcEndpoint { - port_id: our_port.into(), - channel_id: our_channel_id.into(), - }, - IbcEndpoint { - port_id: counter_port.into(), - channel_id: counter_channel.into(), - }, - ibc_order, - ibc_version.to_string(), - CONNECTION_ID, - ) -} - -/// Set up a valid channel for use in tests -pub fn setup_channel(mut deps: DepsMut, env: Env) { - let channel = mock_channel( - "wasm.outpost", - "channel-3", - "wasm.hub", - "channel-7", - IbcOrder::Unordered, - IBC_APP_VERSION, - ); - let open_msg = IbcChannelOpenMsg::new_init(channel.clone()); - ibc_channel_open(deps.branch(), env.clone(), open_msg).unwrap(); - let connect_msg = IbcChannelConnectMsg::new_ack(channel, IBC_APP_VERSION); - ibc_channel_connect(deps, env, connect_msg).unwrap(); -} - -/// Construct a mock IBC packet -pub fn mock_ibc_packet(remote_port: &str, my_channel: &str, data: Binary) -> IbcPacket { - IbcPacket::new( - data, - IbcEndpoint { - port_id: remote_port.to_string(), - channel_id: "channel-3".to_string(), - }, - IbcEndpoint { - port_id: CONTRACT_PORT.to_string(), - channel_id: my_channel.to_string(), - }, - 3, - Timestamp::from_seconds(1665321069).into(), - ) -} diff --git a/contracts/outpost/src/query.rs b/contracts/outpost/src/query.rs deleted file mode 100644 index 5cd0b865..00000000 --- a/contracts/outpost/src/query.rs +++ /dev/null @@ -1,174 +0,0 @@ -use cosmwasm_std::{entry_point, to_json_binary, Addr, Binary, Deps, Env, StdResult, Uint128}; - -use astroport::xastro_outpost_token::get_voting_power_at_time; -use astroport_governance::outpost::QueryMsg; -use astroport_governance::voting_escrow_lite::get_user_deposit_at_time; - -use crate::error::ContractError; -use crate::state::{CONFIG, VOTES}; - -/// Expose available contract queries. -/// -/// ## Queries -/// * **QueryMsg::Config {}** Returns the config of the Outpost -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Config {} => to_json_binary(&CONFIG.load(deps.storage)?), - QueryMsg::ProposalVoted { proposal_id, user } => { - let user_address = deps.api.addr_validate(&user)?; - to_json_binary(&VOTES.load(deps.storage, (&user_address, proposal_id))?) - } - } -} - -/// Get the user's voting power in total for xASTRO and vxASTRO -/// -/// xASTRO is taken at the time the proposal was added -/// vxASTRO is taken at the current time -pub fn get_user_voting_power( - deps: Deps, - user: Addr, - proposal_start: u64, -) -> Result { - let config = CONFIG.load(deps.storage)?; - - // Get the user's xASTRO balance at the time the proposal was added - let voting_power = get_voting_power_at_time( - &deps.querier, - config.xastro_token_addr.clone(), - user.clone(), - proposal_start, - ) - .unwrap_or(Uint128::zero()); - - // Get the user's underlying xASTRO deposit at the time the proposal was added - let vxastro_balance = get_user_deposit_at_time( - &deps.querier, - config.vxastro_token_addr, - user, - proposal_start, - ) - .unwrap_or(Uint128::zero()); - - Ok(voting_power.checked_add(vxastro_balance)?) -} - -#[cfg(test)] -mod tests { - - use super::*; - - use cosmwasm_std::{testing::mock_info, StdError, Uint64}; - - use crate::{ - contract::instantiate, - execute::execute, - mock::{mock_all, setup_channel, HUB, OWNER, VXASTRO_TOKEN, XASTRO_TOKEN}, - query::query, - state::PROPOSALS_CACHE, - }; - use astroport_governance::{assembly::ProposalVoteOption, interchain::ProposalSnapshot}; - - // Test Cases: - // - // Expect Success - // - Can query for a vote already cast - // - // Expect Error - // - Must fail if the vote doesn't exist - // - #[test] - fn query_votes() { - let (mut deps, env, info) = mock_all(OWNER); - - let proposal_id = 1u64; - let user = "user"; - let ibc_timeout_seconds = 10u64; - - instantiate( - deps.as_mut(), - env.clone(), - info, - astroport_governance::outpost::InstantiateMsg { - owner: OWNER.to_string(), - xastro_token_addr: XASTRO_TOKEN.to_string(), - vxastro_token_addr: VXASTRO_TOKEN.to_string(), - hub_addr: HUB.to_string(), - ibc_timeout_seconds, - }, - ) - .unwrap(); - - // Set up valid Hub - setup_channel(deps.as_mut(), env.clone()); - - // Update config with new channel - execute( - deps.as_mut(), - env.clone(), - mock_info(OWNER, &[]), - astroport_governance::outpost::ExecuteMsg::UpdateConfig { - hub_addr: None, - hub_channel: Some("channel-3".to_string()), - ibc_timeout_seconds: None, - }, - ) - .unwrap(); - - // Add a proposal to the cache - PROPOSALS_CACHE - .save( - &mut deps.storage, - proposal_id, - &ProposalSnapshot { - id: Uint64::from(proposal_id), - start_time: 1689939457, - }, - ) - .unwrap(); - - // Cast a vote with a proposal in the cache - execute( - deps.as_mut(), - env.clone(), - mock_info(user, &[]), - astroport_governance::outpost::ExecuteMsg::CastAssemblyVote { - proposal_id, - vote: astroport_governance::assembly::ProposalVoteOption::For, - }, - ) - .unwrap(); - - // Check that we can query the vote that was cast - let vote_data = query( - deps.as_ref(), - env.clone(), - astroport_governance::outpost::QueryMsg::ProposalVoted { - proposal_id, - user: user.to_string(), - }, - ) - .unwrap(); - - assert_eq!(vote_data, to_json_binary(&ProposalVoteOption::For).unwrap()); - - // Check that we receive an error when querying a vote that doesn't exist - let err = query( - deps.as_ref(), - env, - astroport_governance::outpost::QueryMsg::ProposalVoted { - proposal_id, - user: "other_user".to_string(), - }, - ) - .unwrap_err(); - - assert_eq!( - err, - StdError::NotFound { - kind: "astroport_governance::assembly::ProposalVoteOption".to_string() - } - ); - } -} diff --git a/contracts/outpost/src/state.rs b/contracts/outpost/src/state.rs deleted file mode 100644 index 7161607f..00000000 --- a/contracts/outpost/src/state.rs +++ /dev/null @@ -1,34 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::Addr; -use cw_storage_plus::{Item, Map}; - -use astroport::common::OwnershipProposal; -use astroport_governance::{ - assembly::ProposalVoteOption, interchain::ProposalSnapshot, outpost::Config, -}; - -#[cw_serde] -pub struct PendingVote { - /// The proposal ID to vote on - pub proposal_id: u64, - /// The user voting - pub voter: Addr, - /// The choice in vote - pub vote_option: ProposalVoteOption, -} - -/// Store the contract config -pub const CONFIG: Item = Item::new("config"); - -/// Store a local cache of proposals to verify votes are allowed -pub const PROPOSALS_CACHE: Map = Map::new("proposals_cache"); - -/// Store the pending votes for a proposal while the information is being -/// retrieved from the Hub -pub const PENDING_VOTES: Map = Map::new("pending_votes"); - -/// Record of who has voted on which governance proposal -pub const VOTES: Map<(&Addr, u64), ProposalVoteOption> = Map::new("votes"); - -/// Contains a proposal to change contract ownership -pub const OWNERSHIP_PROPOSAL: Item = Item::new("ownership_proposal"); diff --git a/contracts/voting_escrow/Cargo.toml b/contracts/voting_escrow/Cargo.toml index 86d2aa4a..2c23b38d 100644 --- a/contracts/voting_escrow/Cargo.toml +++ b/contracts/voting_escrow/Cargo.toml @@ -25,7 +25,7 @@ cw-storage-plus.workspace = true thiserror.workspace = true cosmwasm-schema.workspace = true astroport-governance = { path = "../../packages/astroport-governance", version = "3" } -astroport = { git = "https://github.com/astroport-fi/hidden_astroport_core", version = "4" } +astroport.workspace = true [dev-dependencies] cw-multi-test = "1" diff --git a/contracts/voting_escrow/src/contract.rs b/contracts/voting_escrow/src/contract.rs index 7eeac809..09242931 100644 --- a/contracts/voting_escrow/src/contract.rs +++ b/contracts/voting_escrow/src/contract.rs @@ -2,8 +2,8 @@ use astroport::asset::{addr_opt_validate, validate_native_denom}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - attr, coins, to_json_binary, BankMsg, Binary, Deps, DepsMut, Env, MessageInfo, Response, - StdError, StdResult, Uint128, + attr, coins, ensure, to_json_binary, wasm_execute, BankMsg, Binary, Deps, DepsMut, Empty, Env, + MessageInfo, Response, StdError, StdResult, Uint128, }; use cw2::set_contract_version; use cw20::{BalanceResponse, Logo, LogoInfo, MarketingInfoResponse, TokenInfoResponse}; @@ -11,6 +11,7 @@ use cw20_base::contract::{execute_update_marketing, query_marketing_info}; use cw20_base::state::{MinterData, TokenInfo, LOGO, MARKETING_INFO, TOKEN_INFO}; use cw_utils::must_pay; +use astroport_governance::emissions_controller; use astroport_governance::voting_escrow::{ Config, ExecuteMsg, InstantiateMsg, LockInfoResponse, QueryMsg, }; @@ -37,6 +38,7 @@ pub fn instantiate( let config = Config { deposit_denom: msg.deposit_denom.clone(), + emissions_controller: deps.api.addr_validate(&msg.emissions_controller)?, }; CONFIG.save(deps.storage, &config)?; @@ -80,19 +82,6 @@ pub fn instantiate( } /// Exposes all the execute functions available in the contract. -/// -/// ## Execute messages -/// * **ExecuteMsg::ExtendLockTime { time }** Increase a staker's lock time. -/// -/// * **ExecuteMsg::Receive(msg)** Parse incoming messages coming from the xASTRO token contract. -/// -/// * **ExecuteMsg::Withdraw {}** Withdraw all xASTRO from a lock position if the lock has expired. -/// -/// * **ExecuteMsg::ProposeNewOwner { owner, expires_in }** Creates a new request to change contract ownership. -/// -/// * **ExecuteMsg::DropOwnershipProposal {}** Removes a request to change contract ownership. -/// -/// * **ExecuteMsg::ClaimOwnership {}** Claims contract ownership. #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, @@ -110,33 +99,96 @@ pub fn execute( let mut position = Lock::load(deps.storage, block_ts, &receiver)?; position.lock(deps.storage, deposit)?; - Ok(Response::default().add_attributes([ - attr("action", "lock"), - attr("receiver", receiver), - attr("deposit_amount", deposit), - attr("new_lock_amount", position.amount), - ])) + // Update user votes in emissions controller + let update_votes_msg = wasm_execute( + &config.emissions_controller, + &emissions_controller::msg::ExecuteMsg::::UpdateUserVotes { + user: receiver.to_string(), + is_unlock: false, + }, + vec![], + )?; + + Ok(Response::default() + .add_message(update_votes_msg) + .add_attributes([ + attr("action", "lock"), + attr("receiver", receiver), + attr("deposit_amount", deposit), + attr("new_lock_amount", position.amount), + ])) } ExecuteMsg::Unlock {} => { let mut position = Lock::load(deps.storage, env.block.time.seconds(), &info.sender)?; let unlock_time = position.unlock(deps.storage)?; - // TODO: kick user from generator controller votes + // Update user votes in emissions controller + let config = CONFIG.load(deps.storage)?; + let update_votes_msg = wasm_execute( + config.emissions_controller, + &emissions_controller::msg::ExecuteMsg::::UpdateUserVotes { + user: info.sender.to_string(), + is_unlock: true, + }, + vec![], + )?; - Ok(Response::default().add_attributes([ - attr("action", "unlock"), - attr("receiver", info.sender), - attr("unlocked_amount", position.amount), - attr("unlock_time", unlock_time.to_string()), - ])) + Ok(Response::default() + .add_message(update_votes_msg) + .add_attributes([ + attr("action", "unlock"), + attr("receiver", info.sender), + attr("unlocked_amount", position.amount), + attr("unlock_time", unlock_time.to_string()), + ])) } ExecuteMsg::Relock {} => { let mut position = Lock::load(deps.storage, env.block.time.seconds(), &info.sender)?; position.relock(deps.storage)?; + // Update user votes in emissions controller + let config = CONFIG.load(deps.storage)?; + let update_votes_msg = wasm_execute( + config.emissions_controller, + &emissions_controller::msg::ExecuteMsg::::UpdateUserVotes { + user: info.sender.to_string(), + is_unlock: false, + }, + vec![], + )?; + Ok(Response::default() + .add_message(update_votes_msg) .add_attributes([attr("action", "relock"), attr("receiver", info.sender)])) } + ExecuteMsg::ForceRelock { user } => { + let config = CONFIG.load(deps.storage)?; + ensure!( + info.sender == config.emissions_controller, + ContractError::Unauthorized {} + ); + + let user = deps.api.addr_validate(&user)?; + let mut position = Lock::load(deps.storage, env.block.time.seconds(), &user)?; + position.relock(deps.storage)?; + + Ok(Response::default() + .add_attributes([attr("action", "force_relock"), attr("receiver", user)])) + } + ExecuteMsg::ConfirmUnlock { user } => { + let config = CONFIG.load(deps.storage)?; + ensure!( + info.sender == config.emissions_controller, + ContractError::Unauthorized {} + ); + + let user = deps.api.addr_validate(&user)?; + let mut position = Lock::load(deps.storage, env.block.time.seconds(), &user)?; + position.confirm_unlock(deps.storage)?; + + Ok(Response::default() + .add_attributes([attr("action", "confirm_unlock"), attr("receiver", user)])) + } ExecuteMsg::Withdraw {} => { let mut position = Lock::load(deps.storage, env.block.time.seconds(), &info.sender)?; let config = CONFIG.load(deps.storage)?; @@ -163,17 +215,6 @@ pub fn execute( } /// Expose available contract queries. -/// -/// ## Queries -/// * **QueryMsg::TotalVotingPower {}** Fetch the total voting power (vxASTRO supply) at the current block. -/// -/// * **QueryMsg::UserVotingPower { user }** Fetch the user's voting power (vxASTRO balance) at the current block. -/// -/// * **QueryMsg::TotalVotingPowerAt { time }** Fetch the total voting power (vxASTRO supply) at a specified timestamp. -/// -/// * **QueryMsg::UserVotingPowerAt { time }** Fetch the user's voting power (vxASTRO balance) at a specified timestamp. -/// -/// * **QueryMsg::LockInfo { user }** Fetch a user's lock information. #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { diff --git a/contracts/voting_escrow/src/error.rs b/contracts/voting_escrow/src/error.rs index a9a29c3b..be0bd905 100644 --- a/contracts/voting_escrow/src/error.rs +++ b/contracts/voting_escrow/src/error.rs @@ -32,4 +32,7 @@ pub enum ContractError { #[error("Position is already unlocking. Consider relocking to lock more tokens")] PositionUnlocking {}, + + #[error("Hub has not yet confirmed the unlock")] + HubNotConfirmed {}, } diff --git a/contracts/voting_escrow/src/state.rs b/contracts/voting_escrow/src/state.rs index f4ca3e11..8aebb113 100644 --- a/contracts/voting_escrow/src/state.rs +++ b/contracts/voting_escrow/src/state.rs @@ -3,7 +3,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{ensure, Addr, StdResult, Storage, Uint128}; use cw_storage_plus::{Item, SnapshotItem, SnapshotMap, Strategy}; -use astroport_governance::voting_escrow::{Config, LockInfoResponse}; +use astroport_governance::voting_escrow::{Config, LockInfoResponse, UnlockStatus}; use crate::error::ContractError; @@ -20,9 +20,9 @@ fn default_addr() -> Addr { pub struct Lock { /// The total amount of xASTRO tokens that were deposited in the vxASTRO position pub amount: Uint128, - /// The timestamp when a lock will be unlocked. None for positions in Locked state - pub end: Option, - /// NOTE: The fields below are not stored in the state, it is used only in the contract logic + /// Unlocking status. None for positions in Locked state + pub unlock_status: Option, + /// NOTE: The fields below are not stored in the state, they are used only in the contract logic #[serde(default = "default_addr", skip)] pub user: Addr, /// Current block timestamp. @@ -34,7 +34,7 @@ impl Default for Lock { fn default() -> Self { Lock { amount: Uint128::zero(), - end: None, + unlock_status: None, user: default_addr(), block_time: 0, } @@ -70,7 +70,10 @@ impl Lock { storage: &mut dyn Storage, amount: Uint128, ) -> Result<(), ContractError> { - ensure!(self.end.is_none(), ContractError::PositionUnlocking {}); + ensure!( + self.unlock_status.is_none(), + ContractError::PositionUnlocking {} + ); self.amount += amount; LOCKED.save(storage, &self.user, self, self.block_time)?; @@ -83,10 +86,16 @@ impl Lock { pub fn unlock(&mut self, storage: &mut dyn Storage) -> Result { ensure!(!self.amount.is_zero(), ContractError::ZeroBalance {}); - ensure!(self.end.is_none(), ContractError::PositionUnlocking {}); + ensure!( + self.unlock_status.is_none(), + ContractError::PositionUnlocking {} + ); let end = self.block_time + UNLOCK_PERIOD; - self.end = Some(end); + self.unlock_status = Some(UnlockStatus { + end, + hub_confirmed: false, + }); LOCKED.save(storage, &self.user, self, self.block_time)?; // Remove user's voting power from the total @@ -97,10 +106,24 @@ impl Lock { Ok(end) } + pub fn confirm_unlock(&mut self, storage: &mut dyn Storage) -> StdResult<()> { + // If for some reason the unlock status is not set, + // we skip it silently so relayer can finish IBC transaction. + if let Some(unlock_status) = self.unlock_status.as_mut() { + unlock_status.hub_confirmed = true; + LOCKED.save(storage, &self.user, self, self.block_time)?; + } + + Ok(()) + } + pub fn relock(&mut self, storage: &mut dyn Storage) -> Result<(), ContractError> { - ensure!(self.end.is_some(), ContractError::NotInUnlockingState {}); + ensure!( + self.unlock_status.is_some(), + ContractError::NotInUnlockingState {} + ); - self.end = None; + self.unlock_status = None; LOCKED.save(storage, &self.user, self, self.block_time)?; // Add user's voting power back to the total @@ -112,10 +135,15 @@ impl Lock { } pub fn withdraw(&mut self, storage: &mut dyn Storage) -> Result { - if let Some(end) = self.end { + if let Some(unlock_status) = self.unlock_status { + ensure!( + self.block_time >= unlock_status.end, + ContractError::UnlockPeriodNotExpired(unlock_status.end) + ); + ensure!( - self.block_time >= end, - ContractError::UnlockPeriodNotExpired(end) + unlock_status.hub_confirmed, + ContractError::HubNotConfirmed {} ); LOCKED.remove(storage, &self.user, self.block_time)?; @@ -127,7 +155,7 @@ impl Lock { } pub fn get_voting_power(&self) -> Uint128 { - if self.end.is_some() { + if self.unlock_status.is_some() { Uint128::zero() } else { self.amount @@ -139,7 +167,7 @@ impl From for LockInfoResponse { fn from(lock: Lock) -> Self { LockInfoResponse { amount: lock.amount, - end: lock.end, + unlock_status: lock.unlock_status, } } } diff --git a/contracts/voting_escrow/tests/helper.rs b/contracts/voting_escrow/tests/helper.rs index 808a0e14..188a39b9 100644 --- a/contracts/voting_escrow/tests/helper.rs +++ b/contracts/voting_escrow/tests/helper.rs @@ -1,8 +1,11 @@ -use cosmwasm_std::{Addr, Coin, Empty, StdResult, Uint128}; +use cosmwasm_std::{ + Addr, Binary, Coin, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, Uint128, +}; use cw20::Logo; use cw_multi_test::error::AnyResult; use cw_multi_test::{AppResponse, BankSudo, BasicApp, Contract, ContractWrapper, Executor}; +use astroport_governance::emissions_controller; use astroport_governance::voting_escrow::{ ExecuteMsg, InstantiateMsg, LockInfoResponse, QueryMsg, UpdateMarketingInfo, }; @@ -15,11 +18,37 @@ fn vxastro_contract() -> Box> { )) } +fn mock_emissions_controller() -> Box> { + fn instantiate( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: Empty, + ) -> StdResult { + Ok(Response::default()) + } + fn execute( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: emissions_controller::msg::ExecuteMsg, + ) -> StdResult { + Ok(Response::default()) + } + + fn query(_deps: Deps, _env: Env, _msg: Empty) -> StdResult { + unimplemented!() + } + + Box::new(ContractWrapper::new_with_empty(execute, instantiate, query)) +} + pub struct EscrowHelper { pub app: BasicApp, pub owner: Addr, pub xastro_denom: String, pub vxastro_contract: Addr, + pub emissions_controller: Addr, } impl EscrowHelper { @@ -28,12 +57,24 @@ impl EscrowHelper { let owner = Addr::unchecked("owner"); let vxastro_code_id = app.store_code(vxastro_contract()); + let emissions_controller_code_id = app.store_code(mock_emissions_controller()); + let mocked_emission_controller = app + .instantiate_contract( + emissions_controller_code_id, + owner.clone(), + &Empty {}, + &[], + "label", + None, + ) + .unwrap(); let vxastro_contract = app .instantiate_contract( vxastro_code_id, owner.clone(), &InstantiateMsg { deposit_denom: xastro_denom.to_string(), + emissions_controller: mocked_emission_controller.to_string(), marketing: Some(UpdateMarketingInfo { project: None, description: None, @@ -52,6 +93,7 @@ impl EscrowHelper { owner, xastro_denom: xastro_denom.to_string(), vxastro_contract, + emissions_controller: mocked_emission_controller, } } @@ -92,6 +134,17 @@ impl EscrowHelper { ) } + pub fn confirm_unlock(&mut self, user: &Addr) -> AnyResult { + self.app.execute_contract( + self.emissions_controller.clone(), + self.vxastro_contract.clone(), + &ExecuteMsg::ConfirmUnlock { + user: user.to_string(), + }, + &[], + ) + } + pub fn withdraw(&mut self, user: &Addr) -> AnyResult { self.app.execute_contract( user.clone(), diff --git a/contracts/voting_escrow/tests/voting_escrow_integration.rs b/contracts/voting_escrow/tests/voting_escrow_integration.rs index 8e6ac4eb..2214b6d2 100644 --- a/contracts/voting_escrow/tests/voting_escrow_integration.rs +++ b/contracts/voting_escrow/tests/voting_escrow_integration.rs @@ -3,7 +3,7 @@ use cw20::{BalanceResponse, LogoInfo, MarketingInfoResponse, TokenInfoResponse}; use cw_multi_test::Executor; use cw_utils::PaymentError; -use astroport_governance::voting_escrow::{Config, LockInfoResponse, QueryMsg}; +use astroport_governance::voting_escrow::{Config, LockInfoResponse, QueryMsg, UnlockStatus}; use astroport_voting_escrow::error::ContractError; use astroport_voting_escrow::state::UNLOCK_PERIOD; @@ -75,7 +75,7 @@ fn test_lock() { } #[test] -fn test_unlok() { +fn test_unlock() { let xastro_denom = "xastro"; let mut helper = EscrowHelper::new(xastro_denom); @@ -138,7 +138,10 @@ fn test_unlok() { lock, LockInfoResponse { amount: xastro_coin.amount, - end: Some(start_ts + UNLOCK_PERIOD) + unlock_status: Some(UnlockStatus { + end: start_ts + UNLOCK_PERIOD, + hub_confirmed: false + }), } ); @@ -165,11 +168,10 @@ fn test_unlok() { lock, LockInfoResponse { amount: xastro_coin.amount, - end: None, + unlock_status: None, } ); - // Normal unlocking flow let bal_before = helper .app .wrap() @@ -178,6 +180,14 @@ fn test_unlok() { .amount; helper.unlock(&user1).unwrap(); helper.timetravel(UNLOCK_PERIOD); + + let err = helper.withdraw(&user1).unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::HubNotConfirmed {} + ); + + helper.confirm_unlock(&user1).unwrap(); helper.withdraw(&user1).unwrap(); assert_eq!(0, helper.user_vp(&user1, None).unwrap().u128()); @@ -188,6 +198,22 @@ fn test_unlok() { .unwrap() .amount; assert_eq!(xastro_coin.amount, bal_after - bal_before); + + helper.timetravel(10000); + + // Relocks before hub confirmation doesn't harm + helper.lock(&user1, &[xastro_coin]).unwrap(); + helper.unlock(&user1).unwrap(); + helper.timetravel(UNLOCK_PERIOD); + // Confirm cant withdraw + let err = helper.withdraw(&user1).unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::HubNotConfirmed {} + ); + helper.relock(&user1).unwrap(); + // No error + helper.confirm_unlock(&user1).unwrap(); } #[test] @@ -205,12 +231,7 @@ fn test_general_queries() { .wrap() .query_wasm_smart(&helper.vxastro_contract, &QueryMsg::Config {}) .unwrap(); - assert_eq!( - config, - Config { - deposit_denom: xastro_denom.to_string(), - } - ); + assert_eq!(config.deposit_denom, xastro_denom); let token_info: TokenInfoResponse = helper .app diff --git a/packages/astroport-governance/.cargo/config b/packages/astroport-governance/.cargo/config deleted file mode 100644 index 71deaf22..00000000 --- a/packages/astroport-governance/.cargo/config +++ /dev/null @@ -1,4 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib" diff --git a/packages/astroport-governance/Cargo.toml b/packages/astroport-governance/Cargo.toml index 6926a999..26633aef 100644 --- a/packages/astroport-governance/Cargo.toml +++ b/packages/astroport-governance/Cargo.toml @@ -19,4 +19,5 @@ cosmwasm-std = { workspace = true, features = ["ibc3"] } cw-storage-plus.workspace = true cosmwasm-schema.workspace = true thiserror.workspace = true -astroport = { git = "https://github.com/astroport-fi/hidden_astroport_core", version = "4" } +astroport.workspace = true +itertools.workspace = true diff --git a/packages/astroport-governance/src/emissions_controller/consts.rs b/packages/astroport-governance/src/emissions_controller/consts.rs new file mode 100644 index 00000000..1ec8113d --- /dev/null +++ b/packages/astroport-governance/src/emissions_controller/consts.rs @@ -0,0 +1,28 @@ +use std::ops::RangeInclusive; + +use cosmwasm_std::IbcOrder; + +/// vxASTRO voting epoch starts at Mon May 20 00:00:00 UTC 2024 +pub const EPOCHS_START: u64 = 1716163200; +pub const DAY: u64 = 86400; +/// vxASTRO voting epoch lasts 14 days +pub const EPOCH_LENGTH: u64 = DAY * 14; +// TODO: import from the main astroport crate? +/// Astroport token factory LP token subdenom +pub const LP_SUBDENOM: &str = "/astroport/share"; +/// Timeout for IBC messages in seconds. Used for both `ics20` and `vxastro-ibc-v1` packets. +pub const IBC_TIMEOUT: u64 = 3600; +/// Denom used to pay IBC fees +pub const FEE_DENOM: &str = "untrn"; +/// Max number of pools allowed per outpost added +pub const POOL_NUMBER_LIMIT: RangeInclusive = 1..=10; +/// Maximum number of pools that can be voted for +pub const MAX_POOLS_TO_VOTE: usize = 5; +/// Max items per page in queries +pub const MAX_PAGE_LIMIT: u8 = 50; +/// User can vote once every 10 days +pub const VOTE_COOLDOWN: u64 = DAY * 10; +/// vxASTRO IBC version +pub const IBC_APP_VERSION: &str = "vxastro-ibc-v1"; +/// IBC ordering +pub const IBC_ORDERING: IbcOrder = IbcOrder::Unordered; diff --git a/packages/astroport-governance/src/emissions_controller/hub.rs b/packages/astroport-governance/src/emissions_controller/hub.rs new file mode 100644 index 00000000..8518dbbf --- /dev/null +++ b/packages/astroport-governance/src/emissions_controller/hub.rs @@ -0,0 +1,305 @@ +use std::collections::HashMap; + +use astroport::asset::validate_native_denom; +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{ensure, Addr, Coin, Decimal, StdError, StdResult, Uint128}; + +use crate::emissions_controller::consts::POOL_NUMBER_LIMIT; +use crate::voting_escrow::UpdateMarketingInfo; + +/// This structure describes the basic settings for creating a contract. +#[cw_serde] +pub struct HubInstantiateMsg { + /// Contract owner + pub owner: String, + /// vxASTRO contract code id + pub vxastro_code_id: u64, + /// vxASTRO token marketing info + pub vxastro_marketing_info: Option, + /// xASTRO denom + pub vxastro_deposit_denom: String, + /// Astroport Factory contract + pub factory: String, + /// ASTRO denom on the Hub + pub astro_denom: String, + /// Max number of pools that can receive ASTRO emissions per outpost added. + /// For example, if there are 3 outposts, + /// and the pools_limit is 10, then 30 pools can receive ASTRO emissions. + /// This limit doesn't enforce the exact number of pools per outpost, + /// but adds flexibility to the contract + /// to automatically adjust the max number of pools based on the number of outposts. + pub pools_per_outpost: u64, + /// Fee required to whitelist a pool + pub whitelisting_fee: Coin, + /// Address that receives the whitelisting fee + pub fee_receiver: String, + /// Minimal percentage of total voting power required to keep a pool in the whitelist + pub whitelist_threshold: Decimal, +} + +#[cw_serde] +pub enum HubMsg { + /// TunePools transforms the latest vote distribution into alloc_points which turn into ASTRO emissions + TunePools {}, + /// Repeats IBC transfer messages with IBC hook for all outposts in Failed state. + RetryFailedOutposts {}, + /// Update the contract configuration + UpdateConfig { + pools_per_outpost: Option, + whitelisting_fee: Option, + fee_receiver: Option, + }, + /// Whitelists a pool to receive ASTRO emissions. Requires fee payment + WhitelistPool { pool: String }, + /// Register or update an outpost + UpdateOutpost { + /// Bech32 prefix + prefix: String, + /// Astro denom on this outpost + astro_denom: String, + /// Outpost params contain all necessary information to interact with the remote outpost. + /// This field also serves as marker whether it is The hub (params: None) or + /// remote outpost (Some(params)) + outpost_params: Option, + /// A pool that must receive flat ASTRO emissions. Optional. + astro_pool_config: Option, + }, + /// Remove an outpost + RemoveOutpost { prefix: String }, +} + +/// This structure describes the query messages available in the contract. +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + /// UserInfo returns information about a voter and the pools they voted for. + /// If timestamp is not provided, the current block time is used. + #[returns(UserInfoResponse)] + UserInfo { + user: String, + timestamp: Option, + }, + /// TuneInfo returns emissions voting outcome at a certain timestamp. + /// If timestamp is not provided, return the latest tune info. + #[returns(TuneInfo)] + TuneInfo { timestamp: Option }, + /// Config returns the contract configuration + #[returns(Config)] + Config {}, + /// VotedPools returns how much voting power a pool received at a certain timestamp. + #[returns(VotedPoolInfo)] + VotedPool { + pool: String, + timestamp: Option, + }, + /// Returns paginated list of all pools that received votes + #[returns(Vec<(String, VotedPoolInfo)>)] + VotedPoolsList { + limit: Option, + start_after: Option, + }, + /// ListOutposts returns all outposts registered in the contract + #[returns(Vec<(String, OutpostInfo)>)] + ListOutposts {}, + /// QueryWhitelist returns the list of pools that are allowed to be voted for + #[returns(Vec)] + QueryWhitelist {}, +} + +/// General contract configuration +#[cw_serde] +pub struct Config { + /// Address that's allowed to change contract parameters + pub owner: Addr, + /// vxASTRO contract address + pub vxastro: Addr, + /// Astroport Factory contract + pub factory: Addr, + /// ASTRO denom on the Hub + pub astro_denom: String, + /// Max number of pools that can receive ASTRO emissions per outpost added. + /// For example, if there are 3 outposts, + /// and the pools_limit is 10, then 30 pools can receive ASTRO emissions. + /// This limit doesn't enforce the exact number of pools per outpost, + /// but adds flexibility to the contract + /// to automatically adjust the max number of pools based on the number of outposts. + pub pools_per_outpost: u64, + /// Fee required to whitelist a pool + pub whitelisting_fee: Coin, + /// Address that receives the whitelisting fee + pub fee_receiver: Addr, + /// Minimal percentage of total voting power required to keep a pool in the whitelist + pub whitelist_threshold: Decimal, +} + +impl Config { + pub fn validate(&self) -> StdResult<()> { + ensure!( + POOL_NUMBER_LIMIT.contains(&self.pools_per_outpost), + StdError::generic_err(format!( + "Invalid pools_limit_per_outpost. Must be within [{}, {}] range", + POOL_NUMBER_LIMIT.start(), + POOL_NUMBER_LIMIT.end() + )) + ); + validate_native_denom(&self.whitelisting_fee.denom)?; + validate_native_denom(&self.astro_denom)?; + + ensure!( + self.whitelist_threshold > Decimal::zero() && self.whitelist_threshold < Decimal::one(), + StdError::generic_err("whitelist_threshold must be within (0, 1) range") + ); + Ok(()) + } +} + +#[cw_serde] +pub struct OutpostParams { + /// Emissions controller on a given outpost + pub emissions_controller: String, + /// wasm<>wasm IBC channel for voting + pub voting_channel: String, + /// General IBC channel for fungible token transfers + pub ics20_channel: String, +} + +#[cw_serde] +pub struct AstroPoolConfig { + /// The most liquid ASTRO pool on this outpost + pub astro_pool: String, + /// The constant ASTRO pool emissions. Can be set to 0 if emissions are not needed. + pub constant_emissions: Uint128, +} + +#[cw_serde] +pub struct OutpostInfo { + /// Outpost params contain all necessary information to interact with the remote outpost. + /// This field also serves as marker whether it is The hub (params: None) or + /// remote outpost (Some(params)) + pub params: Option, + /// ASTRO token denom + pub astro_denom: String, + /// A pool that must receive flat ASTRO emissions. Optional. + pub astro_pool_config: Option, +} + +#[cw_serde] +#[derive(Default)] +pub struct UserInfo { + /// Last time when a user voted + pub vote_ts: u64, + /// Voting power used for the vote + pub voting_power: Uint128, + /// Vote distribution for all the pools a user picked + pub votes: HashMap, +} + +#[cw_serde] +pub struct UserInfoResponse { + /// Last time when a user voted + pub vote_ts: u64, + /// Voting power used for the vote + pub voting_power: Uint128, + /// Vote distribution for all the pools a user picked + pub votes: HashMap, + /// Actual applied votes. This list excludes non-whitelisted pools + pub applied_votes: HashMap, +} + +#[cw_serde] +pub struct VotedPoolInfo { + /// Time when the pool was whitelisted + pub init_ts: u64, + /// Voting power the pool received + pub voting_power: Uint128, +} + +impl VotedPoolInfo { + /// Consume self and return a new instance with added voting power + pub fn with_add_vp(self, vp: Uint128) -> Self { + Self { + voting_power: self.voting_power + vp, + ..self + } + } + + /// Consume self and return a new instance with subtracted voting power + pub fn with_sub_vp(self, vp: Uint128) -> Self { + Self { + voting_power: self.voting_power.saturating_sub(vp), + ..self + } + } +} + +#[cw_serde] +#[derive(Copy)] +pub enum OutpostStatus { + InProgress, + Failed, + Done, +} + +#[cw_serde] +pub struct TuneInfo { + /// Last time when the tune was executed. + /// Matches epoch start i.e., Monday 00:00 UTC every 2 weeks + pub tune_ts: u64, + /// Map of outpost prefix -> array of pools with their emissions + pub pools_grouped: HashMap>, + /// Map of outpost prefix -> IBC status. Hub should never enter this map. + pub outpost_emissions_statuses: HashMap, +} + +#[cfg(test)] +mod unit_tests { + use cosmwasm_std::coin; + + use super::*; + + #[test] + fn test_validate_config() { + let mut config = Config { + owner: Addr::unchecked(""), + vxastro: Addr::unchecked(""), + factory: Addr::unchecked(""), + astro_denom: "uastro".to_string(), + pools_per_outpost: 0, + whitelisting_fee: coin(100, "uastro"), + fee_receiver: Addr::unchecked(""), + whitelist_threshold: Decimal::percent(10), + }; + assert_eq!( + config.validate().unwrap_err(), + StdError::generic_err("Invalid pools_limit_per_outpost. Must be within [1, 10] range") + ); + + config.pools_per_outpost = 5; + config.whitelist_threshold = Decimal::zero(); + + assert_eq!( + config.validate().unwrap_err(), + StdError::generic_err("whitelist_threshold must be within (0, 1) range") + ); + + config.whitelist_threshold = Decimal::percent(10); + config.whitelisting_fee.denom = "u".to_string(); + + assert_eq!( + config.validate().unwrap_err(), + StdError::generic_err("Invalid denom length [3,128]: u") + ); + + config.whitelisting_fee.denom = "uastro".to_string(); + config.astro_denom = "u".to_string(); + + assert_eq!( + config.validate().unwrap_err(), + StdError::generic_err("Invalid denom length [3,128]: u") + ); + + config.astro_denom = "uastro".to_string(); + + config.validate().unwrap(); + } +} diff --git a/packages/astroport-governance/src/emissions_controller/mod.rs b/packages/astroport-governance/src/emissions_controller/mod.rs new file mode 100644 index 00000000..00c36771 --- /dev/null +++ b/packages/astroport-governance/src/emissions_controller/mod.rs @@ -0,0 +1,5 @@ +pub mod consts; +pub mod hub; +pub mod msg; +pub mod outpost; +pub mod utils; diff --git a/packages/astroport-governance/src/emissions_controller/msg.rs b/packages/astroport-governance/src/emissions_controller/msg.rs new file mode 100644 index 00000000..e4144315 --- /dev/null +++ b/packages/astroport-governance/src/emissions_controller/msg.rs @@ -0,0 +1,70 @@ +use std::collections::HashMap; +use std::fmt::Display; + +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{to_json_binary, Binary, Decimal, Uint128}; + +#[cw_serde] +pub enum ExecuteMsg { + /// Vote allows a vxASTRO holders + /// to cast votes on which pools should get ASTRO emissions in the next epoch + Vote { votes: Vec<(String, Decimal)> }, + /// Only vxASTRO contract can call this endpoint. + /// Updates user votes according to the current voting power. + UpdateUserVotes { user: String, is_unlock: bool }, + /// Permissionless endpoint which allows user to update their + /// voting power contribution in case of IBC failures or pool has been re-added to whitelist. + RefreshUserVotes {}, + /// ProposeNewOwner proposes a new owner for the contract + ProposeNewOwner { + /// Newly proposed contract owner + new_owner: String, + /// The timestamp when the contract ownership change expires + expires_in: u64, + }, + /// DropOwnershipProposal removes the latest contract ownership transfer proposal + DropOwnershipProposal {}, + /// ClaimOwnership allows the newly proposed owner to claim contract ownership + ClaimOwnership {}, + /// Set of endpoints specific for Hub/Outpost + Custom(T), +} + +/// This is a generic ICS acknowledgement format. +/// Proto defined here: +/// https://github.com/cosmos/cosmos-sdk/blob/v0.42.0/proto/ibc/core/channel/v1/channel.proto#L141-L147 +/// This is compatible with the JSON serialization. +#[cw_serde] +pub enum IbcAckResult { + Ok(Binary), + Error(String), +} + +/// Create a serialized error message +pub fn ack_fail(err: impl Display) -> Binary { + to_json_binary(&IbcAckResult::Error(err.to_string())).unwrap() +} + +/// Create a serialized success message +pub fn ack_ok() -> Binary { + to_json_binary(&IbcAckResult::Ok(b"ok".into())).unwrap() +} + +/// Internal IBC message to be sent from outpost to the hub over voting channel +#[cw_serde] +pub enum VxAstroIbcMsg { + Vote { + voter: String, + /// Actual voting power reported from outpost + voting_power: Uint128, + /// Voting power distribution + votes: HashMap, + }, + UpdateUserVotes { + voter: String, + /// Actual voting power reported from outpost + voting_power: Uint128, + /// Marker defines whether this packet was sent from vxASTRO unlock context + is_unlock: bool, + }, +} diff --git a/packages/astroport-governance/src/emissions_controller/outpost.rs b/packages/astroport-governance/src/emissions_controller/outpost.rs new file mode 100644 index 00000000..888db233 --- /dev/null +++ b/packages/astroport-governance/src/emissions_controller/outpost.rs @@ -0,0 +1,96 @@ +use crate::emissions_controller::msg::VxAstroIbcMsg; +use astroport::incentives::InputSchedule; +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::Addr; + +use crate::voting_escrow::UpdateMarketingInfo; + +/// This structure describes the basic settings for creating a contract. +#[cw_serde] +pub struct OutpostInstantiateMsg { + /// Contract owner + pub owner: String, + /// ASTRO denom on the chain + pub astro_denom: String, + /// vxASTRO contract code id + pub vxastro_code_id: u64, + /// vxASTRO token marketing info + pub vxastro_marketing_info: Option, + /// xASTRO denom + pub vxastro_deposit_denom: String, + /// Astroport Factory contract + pub factory: String, + /// Emissions controller on the Hub + pub hub_emissions_controller: String, + /// Official ICS20 IBC channel from this outpost to the Hub + pub ics20_channel: String, +} + +#[cw_serde] +pub enum OutpostMsg { + /// SetEmissions is a permissionless endpoint that allows setting ASTRO emissions for the next epoch + /// from the Hub by leveraging IBC hooks. + SetEmissions { + schedules: Vec<(String, InputSchedule)>, + }, + /// Same as SetEmissions but it allows using funds from contract balance (if available). + /// This endpoint can be called only by contract owner. It is meant to be used in case of + /// IBC hook wasn't triggered upon ics20 packet arrival, for example, if a chain doesn't support IBC hooks. + PermissionedSetEmissions { + schedules: Vec<(String, InputSchedule)>, + }, + UpdateConfig { + /// Voting IBC wasm<>wasm channel + voting_ibc_channel: Option, + /// Emissions controller on the Hub + hub_emissions_controller: Option, + /// Official ICS20 IBC channel from this outpost to the Hub + ics20_channel: Option, + }, +} + +/// This structure describes the query messages available in the contract. +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + /// Config returns the contract configuration + #[returns(Config)] + Config {}, + /// QueryUserIbcStatus returns the status of the user's IBC request. + /// Whether they have a pending request or an error. + #[returns(UserIbcStatus)] + QueryUserIbcStatus { user: String }, +} + +/// Contains failed IBC along with the error message +#[cw_serde] +pub struct UserIbcError { + pub msg: VxAstroIbcMsg, + pub err: String, +} + +/// Contains the pending IBC message or an error +#[cw_serde] +pub struct UserIbcStatus { + pub pending_msg: Option, + pub error: Option, +} + +/// General contract configuration +#[cw_serde] +pub struct Config { + /// Address that's allowed to change contract parameters + pub owner: Addr, + /// vxASTRO contract address + pub vxastro: Addr, + /// ASTRO denom on the chain + pub astro_denom: String, + /// Astroport Factory contract + pub factory: Addr, + /// vxASTRO IBC channel + pub voting_ibc_channel: String, + /// Emissions controller on the Hub + pub hub_emissions_controller: String, + /// Official ICS20 IBC channel from this outpost to the Hub + pub ics20_channel: String, +} diff --git a/packages/astroport-governance/src/emissions_controller/utils.rs b/packages/astroport-governance/src/emissions_controller/utils.rs new file mode 100644 index 00000000..0347cd7e --- /dev/null +++ b/packages/astroport-governance/src/emissions_controller/utils.rs @@ -0,0 +1,83 @@ +use astroport::asset::{pair_info_by_pool, AssetInfo, PairInfo}; +use astroport::{factory, pair}; +use cosmwasm_std::{Addr, QuerierWrapper, StdError, StdResult, Uint128}; + +use crate::emissions_controller::consts::LP_SUBDENOM; +use crate::voting_escrow; + +/// Queries pair info corresponding to given LP token. +/// Handles both native and cw20 tokens. +/// If the token is native, it must follow the following format: +/// factory/{lp_minter}/astroport/share +/// where lp_minter is a valid bech32 address on the current chain. +pub fn query_pair_info(querier: QuerierWrapper, lp_asset: &AssetInfo) -> StdResult { + match lp_asset { + AssetInfo::Token { contract_addr } => pair_info_by_pool(&querier, contract_addr), + AssetInfo::NativeToken { denom } => { + if denom.starts_with("factory/") && denom.ends_with(LP_SUBDENOM) { + let lp_minter = denom.split('/').nth(1).unwrap(); + querier.query_wasm_smart(lp_minter, &pair::QueryMsg::Pair {}) + } else { + Err(StdError::generic_err(format!( + "LP token {denom} doesn't follow token factory format: factory/{{lp_minter}}{LP_SUBDENOM}", + ))) + } + } + } +} + +/// Checks if the given LP token is registered in the factory. +pub fn check_lp_token( + querier: QuerierWrapper, + factory: &Addr, + maybe_lp: &AssetInfo, +) -> StdResult<()> { + let pair_info = query_pair_info(querier, maybe_lp)?; + querier + .query_wasm_smart::( + factory, + &factory::QueryMsg::Pair { + asset_infos: pair_info.asset_infos.to_vec(), + }, + ) + .map_err(|_| { + StdError::generic_err(format!( + "The pair is not registered: {}-{}", + pair_info.asset_infos[0], pair_info.asset_infos[1] + )) + }) + .and_then(|resp| { + if resp.liquidity_token.as_str() == maybe_lp.to_string() { + Ok(()) + } else { + Err(StdError::generic_err(format!( + "LP token {maybe_lp} doesn't match LP token registered in factory {}", + resp.liquidity_token + ))) + } + }) +} + +#[inline] +pub fn get_voting_power( + querier: QuerierWrapper, + vxastro: &Addr, + voter: impl Into, + time: Option, +) -> StdResult { + querier.query_wasm_smart( + vxastro, + &voting_escrow::QueryMsg::UserVotingPower { + user: voter.into(), + time, + }, + ) +} + +#[inline] +pub fn query_incentives_addr(querier: QuerierWrapper, factory: &Addr) -> StdResult { + querier + .query_wasm_smart::(factory, &factory::QueryMsg::Config {})? + .generator_address + .ok_or_else(|| StdError::generic_err("Generator address is not set")) +} diff --git a/packages/astroport-governance/src/generator_controller.rs b/packages/astroport-governance/src/generator_controller.rs deleted file mode 100644 index 30d77be6..00000000 --- a/packages/astroport-governance/src/generator_controller.rs +++ /dev/null @@ -1,146 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Decimal, Uint128}; - -/// The maximum amount of voters that can be kicked at once from -pub const VOTERS_MAX_LIMIT: u32 = 30; - -/// This structure describes the basic settings for creating a contract. -#[cw_serde] -pub struct InstantiateMsg { - /// Contract owner - pub owner: String, - /// The vxASTRO token contract address - pub escrow_addr: String, - /// Generator contract address - pub generator_addr: String, - /// Factory contract address - pub factory_addr: String, - /// Max number of pools that can receive ASTRO emissions at the same time - pub pools_limit: u64, - /// The list of pools which are eligible to receive votes - pub whitelisted_pools: Vec, -} - -/// This structure describes the execute messages available in the contract. -#[cw_serde] -pub enum ExecuteMsg { - /// Removes all votes applied by blacklisted voters - KickBlacklistedVoters { blacklisted_voters: Vec }, - /// Vote allows a vxASTRO holder to cast votes on which generators should get ASTRO emissions in the next epoch - Vote { votes: Vec<(String, u16)> }, - /// TunePools transforms the latest vote distribution into alloc_points which are then applied to ASTRO generators - TunePools {}, - UpdateConfig { - /// The number of voters that can be kicked at once from the pool.. - blacklisted_voters_limit: Option, - /// Main pool that will receive a minimum amount of ASTRO emissions - main_pool: Option, - /// The minimum percentage of ASTRO emissions that main pool should get every block - main_pool_min_alloc: Option, - /// Should the main pool be removed or not? If the variable is omitted then the pool will be kept. - remove_main_pool: Option, - }, - /// ChangePoolsLimit changes the max amount of pools that can be voted at once to receive ASTRO emissions - ChangePoolsLimit { limit: u64 }, - /// ProposeNewOwner proposes a new owner for the contract - ProposeNewOwner { - /// Newly proposed contract owner - new_owner: String, - /// The timestamp when the contract ownership change expires - expires_in: u64, - }, - /// DropOwnershipProposal removes the latest contract ownership transfer proposal - DropOwnershipProposal {}, - /// ClaimOwnership allows the newly proposed owner to claim contract ownership - ClaimOwnership {}, - /// Adds or removes the pools which are eligible to receive votes - UpdateWhitelist { - add: Option>, - remove: Option>, - }, -} - -/// This structure describes the query messages available in the contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// UserInfo returns information about a voter and the generators they voted for - #[returns(UserInfoResponse)] - UserInfo { user: String }, - /// TuneInfo returns information about the latest generators that were voted to receive ASTRO emissions - #[returns(GaugeInfoResponse)] - TuneInfo {}, - /// Config returns the contract configuration - #[returns(ConfigResponse)] - Config {}, - /// PoolInfo returns the latest voting power allocated to a specific pool (generator) - #[returns(VotedPoolInfoResponse)] - PoolInfo { pool_addr: String }, - /// PoolInfo returns the voting power allocated to a specific pool (generator) at a specific period - #[returns(VotedPoolInfoResponse)] - PoolInfoAtPeriod { pool_addr: String, period: u64 }, -} - -/// This structure describes a migration message. -/// We currently take no arguments for migrations. -#[cw_serde] -pub struct MigrateMsg {} - -/// This structure describes the parameters returned when querying for the contract configuration. -#[cw_serde] -pub struct ConfigResponse { - /// Address that's allowed to change contract parameters - pub owner: Addr, - /// The vxASTRO token contract address - pub escrow_addr: Addr, - /// Generator contract address - pub generator_addr: Addr, - /// Factory contract address - pub factory_addr: Addr, - /// Max number of pools that can receive ASTRO emissions at the same time - pub pools_limit: u64, - /// Max number of blacklisted voters which can be removed - pub blacklisted_voters_limit: Option, - /// Main pool that will receive a minimum amount of ASTRO emissions - pub main_pool: Option, - /// The minimum percentage of ASTRO emissions that main pool should get every block - pub main_pool_min_alloc: Decimal, - /// The list of pools which are eligible to receive votes - pub whitelisted_pools: Vec, -} - -/// This structure describes the response used to return voting information for a specific pool (generator). -#[cw_serde] -#[derive(Default)] -pub struct VotedPoolInfoResponse { - /// vxASTRO amount that voted for this pool/generator - pub vxastro_amount: Uint128, - /// The slope at which the amount of vxASTRO that voted for this pool/generator will decay - pub slope: Uint128, -} - -/// This structure describes the response used to return tuning parameters for all pools/generators. -#[cw_serde] -#[derive(Default)] -pub struct GaugeInfoResponse { - /// Last timestamp when a tuning vote happened - pub tune_ts: u64, - /// Distribution of alloc_points to apply in the Generator contract - pub pool_alloc_points: Vec<(String, Uint128)>, -} - -/// The struct describes a response used to return a staker's vxASTRO lock position. -#[cw_serde] -#[derive(Default)] -pub struct UserInfoResponse { - /// Last timestamp when the user voted - pub vote_ts: u64, - /// The user's vxASTRO voting power - pub voting_power: Uint128, - /// The slope at which the user's voting power decays - pub slope: Uint128, - /// Timestamp when the user's lock expires - pub lock_end: u64, - /// The vote distribution for all the generators/pools the staker picked - pub votes: Vec<(Addr, u16)>, -} diff --git a/packages/astroport-governance/src/generator_controller_lite.rs b/packages/astroport-governance/src/generator_controller_lite.rs deleted file mode 100644 index 00fbbd28..00000000 --- a/packages/astroport-governance/src/generator_controller_lite.rs +++ /dev/null @@ -1,184 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Decimal, Uint128}; - -/// The maximum amount of voters that can be kicked at once from -pub const VOTERS_MAX_LIMIT: u32 = 30; - -/// This structure describes the basic settings for creating a contract. -#[cw_serde] -pub struct InstantiateMsg { - /// Contract owner - pub owner: String, - /// The vxASTRO token contract address - pub escrow_addr: String, - /// Generator contract address - pub generator_addr: String, - /// Factory contract address - pub factory_addr: String, - /// Assembly contract address - pub assembly_addr: String, - /// Hub contract address - pub hub_addr: Option, - /// Max number of pools that can receive ASTRO emissions at the same time - pub pools_limit: u64, - /// The list of pools which are eligible to receive votes - pub whitelisted_pools: Vec, -} - -/// This structure describes the execute messages available in the contract. -#[cw_serde] -pub enum ExecuteMsg { - /// Removes all votes applied by blacklisted voters - KickBlacklistedVoters { blacklisted_voters: Vec }, - /// Removes all votes applied by voters that have unlocked - KickUnlockedVoters { unlocked_voters: Vec }, - /// Removes all votes applied by a voter that have unlocked on an Outpost - KickUnlockedOutpostVoter { unlocked_voter: String }, - /// Vote allows a vxASTRO holder to cast votes on which generators should get ASTRO emissions in the next epoch - Vote { votes: Vec<(String, u16)> }, - /// OutpostVote allows a vxASTRO holder on an Outpost to cast votes on which generators should get ASTRO emissions in the next epoch - OutpostVote { - voter: String, - voting_power: Uint128, - votes: Vec<(String, u16)>, - }, - /// TunePools transforms the latest vote distribution into alloc_points which are then applied to ASTRO generators - TunePools {}, - UpdateConfig { - // Assembly contract address - assembly_addr: Option, - /// The number of voters that can be kicked at once from the pool.. - kick_voters_limit: Option, - /// Main pool that will receive a minimum amount of ASTRO emissions - main_pool: Option, - /// The minimum percentage of ASTRO emissions that main pool should get every block - main_pool_min_alloc: Option, - /// Should the main pool be removed or not? If the variable is omitted then the pool will be kept. - remove_main_pool: Option, - // Hub contract address - hub_addr: Option, - }, - /// ChangePoolsLimit changes the max amount of pools that can be voted at once to receive ASTRO emissions - ChangePoolsLimit { limit: u64 }, - /// ProposeNewOwner proposes a new owner for the contract - ProposeNewOwner { - /// Newly proposed contract owner - new_owner: String, - /// The timestamp when the contract ownership change expires - expires_in: u64, - }, - /// DropOwnershipProposal removes the latest contract ownership transfer proposal - DropOwnershipProposal {}, - /// ClaimOwnership allows the newly proposed owner to claim contract ownership - ClaimOwnership {}, - /// Adds or removes the pools which are eligible to receive votes - UpdateWhitelist { - add: Option>, - remove: Option>, - }, - // Update network config for IBC - UpdateNetworks { - // Adding requires a list of (network, address prefix, IBC governance channel) - add: Option>, - remove: Option>, - }, -} - -/// This structure describes the query messages available in the contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// UserInfo returns information about a voter and the generators they voted for - #[returns(UserInfoResponse)] - UserInfo { user: String }, - /// TuneInfo returns information about the latest generators that were voted to receive ASTRO emissions - #[returns(GaugeInfoResponse)] - TuneInfo {}, - /// Config returns the contract configuration - #[returns(ConfigResponse)] - Config {}, - /// PoolInfo returns the latest voting power allocated to a specific pool (generator) - #[returns(VotedPoolInfoResponse)] - PoolInfo { pool_addr: String }, - /// PoolInfo returns the voting power allocated to a specific pool (generator) at a specific period - #[returns(VotedPoolInfoResponse)] - PoolInfoAtPeriod { pool_addr: String, period: u64 }, -} - -/// This structure describes a migration message. -/// We currently take no arguments for migrations. -#[cw_serde] -pub struct MigrateMsg {} - -/// This structure describes the parameters returned when querying for the contract configuration. -#[cw_serde] -pub struct ConfigResponse { - /// Address that's allowed to change contract parameters - pub owner: Addr, - /// The vxASTRO token contract address - pub escrow_addr: Addr, - /// Generator contract address - pub generator_addr: Addr, - /// Factory contract address - pub factory_addr: Addr, - /// Assembly contract address - pub assembly_addr: Addr, - /// Hub contract address - pub hub_addr: Option, - /// Max number of pools that can receive ASTRO emissions at the same time - pub pools_limit: u64, - /// Max number of voters which can be kicked at a time - pub kick_voters_limit: Option, - /// Main pool that will receive a minimum amount of ASTRO emissions - pub main_pool: Option, - /// The minimum percentage of ASTRO emissions that main pool should get every block - pub main_pool_min_alloc: Decimal, - /// The list of pools which are eligible to receive votes - pub whitelisted_pools: Vec, - /// The list of pools which are eligible to receive votes - pub whitelisted_networks: Vec, -} - -/// This structure describes the response used to return voting information for a specific pool (generator). -#[cw_serde] -#[derive(Default)] -pub struct VotedPoolInfoResponse { - /// vxASTRO amount that voted for this pool/generator - pub vxastro_amount: Uint128, - /// The slope at which the amount of vxASTRO that voted for this pool/generator will decay - pub slope: Uint128, -} - -/// This structure describes the response used to return tuning parameters for all pools/generators. -#[cw_serde] -#[derive(Default)] -pub struct GaugeInfoResponse { - /// Last period when a tuning was applied - pub tune_period: u64, - /// Distribution of alloc_points to apply in the Generator contract - pub pool_alloc_points: Vec<(String, Uint128)>, -} - -/// The struct describes a response used to return a staker's vxASTRO lock position. -#[cw_serde] -#[derive(Default)] -pub struct UserInfoResponse { - /// The period when the user voted last time, None if they've never voted - pub vote_period: Option, - /// The user's vxASTRO voting power - pub voting_power: Uint128, - /// The vote distribution for all the generators/pools the staker picked - pub votes: Vec<(String, u16)>, -} - -#[cw_serde] -#[derive(Eq, Hash)] -pub struct NetworkInfo { - /// The address prefix for the network, e.g. "terra". This is determined - /// by the contract and will be overwritten in update_networks - pub address_prefix: String, - /// The address of the generator contract on the Outpost - pub generator_address: Addr, - /// The IBC channel used for governance - pub ibc_channel: Option, -} diff --git a/packages/astroport-governance/src/hub.rs b/packages/astroport-governance/src/hub.rs deleted file mode 100644 index 95fd59eb..00000000 --- a/packages/astroport-governance/src/hub.rs +++ /dev/null @@ -1,145 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Uint128, Uint64}; -use cw20::Cw20ReceiveMsg; - -/// Holds the parameters used for creating a Hub contract -#[cw_serde] -pub struct InstantiateMsg { - /// The contract owner - pub owner: String, - /// The address of the Assembly contract on the Hub - pub assembly_addr: String, - /// The address of the CW20-ICS20 contract on the Hub that supports - /// memo handling - pub cw20_ics20_addr: String, - /// The address of the xASTRO staking contract on the Hub - pub staking_addr: String, - /// The address of the generator controller contract on the Hub - pub generator_controller_addr: String, - /// The timeout in seconds for IBC packets - pub ibc_timeout_seconds: u64, -} - -/// The contract migration message -/// We currently take no arguments for migrations -#[cw_serde] -pub struct MigrateMsg {} - -/// Describes the execute messages available in the contract -#[cw_serde] -pub enum ExecuteMsg { - /// Receive a message of type [`Cw20ReceiveMsg`] - Receive(Cw20ReceiveMsg), - /// Update parameters in the Hub contract. Only the owner is allowed to - /// update the config - UpdateConfig { - /// The timeout in seconds for IBC packets - ibc_timeout_seconds: Option, - }, - /// Add a new Outpost to the Hub. Only allowed Outposts can send IBC messages - AddOutpost { - /// The remote contract address of the Outpost to add - outpost_addr: String, - /// The channel connecting us to the Outpost - outpost_channel: String, - /// The channel to use for CW20-ICS20 IBC transfers - cw20_ics20_channel: String, - }, - /// Remove an Outpost from the Hub - RemoveOutpost { - /// The remote contract address of the Outpost to remove - outpost_addr: String, - }, - /// Propose a new owner for the contract - ProposeNewOwner { new_owner: String, expires_in: u64 }, - /// Remove the ownership transfer proposal - DropOwnershipProposal {}, - /// Claim contract ownership - ClaimOwnership {}, -} - -/// Messages handled via CW20 transfers -#[cw_serde] -pub enum Cw20HookMsg { - /// Handles instructions received via an IBC transfer memo in the - /// CW20-ICS20 contract - OutpostMemo { - /// The channel the memo was received on - channel: String, - /// The original sender of the packet on the outpost - sender: String, - /// The original intended receiver of the packet on the Hub - receiver: String, - /// The memo containing the JSON to handle - memo: String, - }, - /// Handle failed CW20 IBC transfers - TransferFailure { - // The original sender where the funds should be returned to - receiver: String, - }, -} - -/// Describes the query messages available in the contract -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Returns the config of the Hub - #[returns(Config)] - Config {}, - /// Returns the balance of funds held for a user - #[returns(HubBalance)] - UserFunds { user: Addr }, - /// Returns the list of the current Outposts on the Hub - #[returns(Vec)] - Outposts { - start_after: Option, - limit: Option, - }, - /// Returns the current balance of xASTRO minted via a specific Outpost channel - #[returns(HubBalance)] - ChannelBalanceAt { channel: String, timestamp: Uint64 }, - /// Returns the total balance of all xASTRO minted via Outposts - #[returns(HubBalance)] - TotalChannelBalancesAt { timestamp: Uint64 }, -} - -/// The config of the Hub -#[cw_serde] -pub struct Config { - /// The owner of the contract - pub owner: Addr, - /// The address of the Assembly contract on the Hub - pub assembly_addr: Addr, - /// The address of the CW20-ICS20 contract on the Hub that supports memo - /// handling - pub cw20_ics20_addr: Addr, - /// The address of the ASTRO token contract on the Hub - pub token_addr: Addr, - /// The address of the xASTRO token contract on the Hub - pub xtoken_addr: Addr, - /// The address of the staking contract on the Hub - pub staking_addr: Addr, - /// The address of the generator controller contract on the Hub - pub generator_controller_addr: Addr, - /// The timeout in seconds for IBC packets - pub ibc_timeout_seconds: u64, -} - -/// A response containing the Outpost address and channels -#[cw_serde] -pub struct OutpostConfig { - /// The address of the Outpost contract on another chain - pub address: String, - /// The channel connecting the Hub contract with that Outpost contract - pub channel: String, - /// The CS20-ICS20 channel ASTRO is transferred through - pub cw20_ics20_channel: String, -} - -/// A response containing the balance of a channel or user on the Hub -#[cw_serde] -pub struct HubBalance { - /// The balance of the user or channel - pub balance: Uint128, -} diff --git a/packages/astroport-governance/src/interchain.rs b/packages/astroport-governance/src/interchain.rs deleted file mode 100644 index 521f6f46..00000000 --- a/packages/astroport-governance/src/interchain.rs +++ /dev/null @@ -1,164 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Uint128, Uint64}; -use std::fmt::{Display, Formatter, Result}; - -use crate::assembly::ProposalVoteOption; - -// Minimum IBC timeout is 5 seconds -pub const MIN_IBC_TIMEOUT_SECONDS: u64 = 5; -// Maximum IBC timeout is 1 hour -pub const MAX_IBC_TIMEOUT_SECONDS: u64 = 60 * 60; - -/// Hub defines the messages that can be sent from an Outpost to the Hub -#[cw_serde] -#[non_exhaustive] -pub enum Hub { - /// Queries the Assembly for a proposal by ID via the Hub - QueryProposal { - /// The ID of the proposal to query - id: u64, - }, - /// Cast a vote on an Assembly proposal - CastAssemblyVote { - /// The ID of the proposal to vote on - proposal_id: u64, - /// The address of the voter - voter: Addr, - /// The vote choice - vote_option: ProposalVoteOption, - /// The voting power held by the voter, in this case xASTRO holdings - voting_power: Uint128, - }, - /// Cast a vote during an emissions voting period - CastEmissionsVote { - /// The address of the voter - voter: Addr, - /// The voting power held by the voter, in this case vxASTRO lite holdings - voting_power: Uint128, - /// The votes in the format (pool address, percent of voting power) - votes: Vec<(String, u16)>, - }, - /// Stake ASTRO tokens for xASTRO - Stake {}, - /// Unstake xASTRO tokens for ASTRO - Unstake { - // The user requesting the unstake and that should receive it - receiver: String, - /// The amount of xASTRO to unstake - amount: Uint128, - }, - /// Kick an unlocked voter's voting power from the Generator Controller lite - KickUnlockedVoter { - /// The address of the voter to kick - voter: Addr, - }, - /// Kick a blacklisted voter's voting power from the Generator Controller lite - KickBlacklistedVoter { - /// The address of the voter that has been blacklisted - voter: Addr, - }, - /// Withdraw stuck funds from the Hub in case of specific IBC failures - WithdrawFunds { - /// The address of the user to withdraw funds for - user: Addr, - }, -} - -/// Defines the messages that can be sent from the Hub to an Outpost -#[cw_serde] -pub enum Outpost { - /// Mint xASTRO tokens for the user - MintXAstro { receiver: String, amount: Uint128 }, -} - -/// Defines a minimal proposal that is cached on the Outpost -#[cw_serde] -pub struct ProposalSnapshot { - /// Unique proposal ID - pub id: Uint64, - /// Start time of proposal - pub start_time: u64, -} - -/// Defines the messages that can be returned in response to an IBC Hub or -/// Outpost message -#[cw_serde] -pub enum Response { - /// The response to a QueryProposal message that includes a minimal Proposal - QueryProposal(ProposalSnapshot), - /// A generic response to a Hub/Outpost message, mostly used for indicating success - /// or error handling - Result { - /// The action that was performed, None if no specific action was taken - action: Option, - /// The address of the user that took the action, None if the result - /// isn't specific to an address - address: Option, - /// The error message, if None, the action was successful - error: Option, - }, -} - -/// Utility functions for InterchainResponse to ease creation of responses -impl Response { - /// Create a new success response that sets address and action but leaves - /// error as None - pub fn new_success(action: String, address: String) -> Self { - Response::Result { - action: Some(action), - address: Some(address), - error: None, - } - } - /// Create a new error response that sets address and action to None - /// while adding the error message - pub fn new_error(error: String) -> Self { - Response::Result { - action: None, - address: None, - error: Some(error), - } - } -} - -/// Implements Display for Hub -impl Display for Hub { - fn fmt(&self, f: &mut Formatter) -> Result { - write!( - f, - "{}", - match self { - Hub::Stake { .. } => "stake", - Hub::CastAssemblyVote { .. } => "cast_assembly_vote", - Hub::CastEmissionsVote { .. } => "cast_emissions_vote", - Hub::QueryProposal { .. } => "query_proposal", - Hub::Unstake { .. } => "unstake", - Hub::KickUnlockedVoter { .. } => "kick_unlocked_voter", - Hub::KickBlacklistedVoter { .. } => "kick_blacklisted_voter", - Hub::WithdrawFunds { .. } => "withdraw_funds", - } - ) - } -} - -/// Implements Display for Outpost -impl Display for Outpost { - fn fmt(&self, f: &mut Formatter) -> Result { - write!( - f, - "{}", - match self { - Outpost::MintXAstro { .. } => "MintXAstro", - } - ) - } -} - -/// Get the address from an IBC port. If the port is prefixed with `wasm.`, -/// strip it out, if not, return the port as is. -pub fn get_contract_from_ibc_port(ibc_port: &str) -> &str { - match ibc_port.strip_prefix("wasm.") { - Some(suffix) => suffix, // prints: inj1234 - None => ibc_port, - } -} diff --git a/packages/astroport-governance/src/lib.rs b/packages/astroport-governance/src/lib.rs index 0467b164..2ccb6094 100644 --- a/packages/astroport-governance/src/lib.rs +++ b/packages/astroport-governance/src/lib.rs @@ -1,12 +1,6 @@ -pub use astroport; - pub mod assembly; pub mod builder_unlock; -pub mod generator_controller; -pub mod generator_controller_lite; -pub mod hub; -pub mod interchain; -pub mod outpost; +pub mod emissions_controller; pub mod utils; pub mod voting_escrow; diff --git a/packages/astroport-governance/src/outpost.rs b/packages/astroport-governance/src/outpost.rs deleted file mode 100644 index 15cba93e..00000000 --- a/packages/astroport-governance/src/outpost.rs +++ /dev/null @@ -1,107 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::Addr; -use cw20::Cw20ReceiveMsg; - -use crate::assembly::ProposalVoteOption; - -/// Holds the parameters used for creating an Outpost contract -#[cw_serde] -pub struct InstantiateMsg { - /// The contract owner - pub owner: String, - /// The address of the xASTRO token contract on the Outpost - pub xastro_token_addr: String, - /// The address of the vxASTRO lite contract on the Outpost - pub vxastro_token_addr: String, - /// The address of the Hub contract on the Hub chain - pub hub_addr: String, - /// The timeout in seconds for IBC packets - pub ibc_timeout_seconds: u64, -} - -/// The contract migration message -/// We currently take no arguments for migrations -#[cw_serde] -pub struct MigrateMsg {} - -/// Describes the execute messages available in the contract -#[cw_serde] -pub enum ExecuteMsg { - /// Receive a message of type [`Cw20ReceiveMsg`] - Receive(Cw20ReceiveMsg), - /// Update parameters in the Outpost contract. Only the owner is allowed to - /// update the config - UpdateConfig { - /// The new Hub address - hub_addr: Option, - /// The new Hub IBC channel - hub_channel: Option, - /// The timeout in seconds for IBC packets - ibc_timeout_seconds: Option, - }, - /// Cast a vote on an Assembly proposal from an Outpost - CastAssemblyVote { - /// The ID of the proposal to vote on - proposal_id: u64, - /// The vote choice - vote: ProposalVoteOption, - }, - /// Cast a vote during an emissions voting period - CastEmissionsVote { - /// The votes in the format (pool address, percent of voting power) - votes: Vec<(String, u16)>, - }, - /// Kick an unlocked voter's voting power from the Generator Controller lite - KickUnlocked { - /// The address of the user to kick - user: Addr, - }, - /// Kick a blacklisted voter's voting power from the Generator Controller lite - KickBlacklisted { - /// The address of the user that has been blacklisted - user: Addr, - }, - /// Withdraw stuck funds from the Hub in case of specific IBC failures - WithdrawHubFunds {}, - /// Propose a new owner for the contract - ProposeNewOwner { new_owner: String, expires_in: u64 }, - /// Remove the ownership transfer proposal - DropOwnershipProposal {}, - /// Claim contract ownership - ClaimOwnership {}, -} - -/// Messages handled via CW20 transfers -#[cw_serde] -pub enum Cw20HookMsg { - /// Unstake xASTRO from the Hub and return the ASTRO to the sender - Unstake {}, -} - -/// Describes the query messages available in the contract -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Returns the config of the Outpost - #[returns(Config)] - Config {}, - #[returns(ProposalVoteOption)] - ProposalVoted { proposal_id: u64, user: String }, -} - -/// The config of the Outpost -#[cw_serde] -pub struct Config { - /// The owner of the contract - pub owner: Addr, - /// The address of the Hub contract on the Hub chain - pub hub_addr: String, - /// The channel used to communicate with the Hub - pub hub_channel: Option, - /// The address of the xASTRO token contract on the Outpost - pub xastro_token_addr: Addr, - /// The address of the vxASTRO lite contract on the Outpost - pub vxastro_token_addr: Addr, - /// The timeout in seconds for IBC packets - pub ibc_timeout_seconds: u64, -} diff --git a/packages/astroport-governance/src/utils.rs b/packages/astroport-governance/src/utils.rs index 4f099e3a..0728dfbd 100644 --- a/packages/astroport-governance/src/utils.rs +++ b/packages/astroport-governance/src/utils.rs @@ -1,69 +1,4 @@ -use cosmwasm_std::{ - Addr, ChannelResponse, IbcQuery, QuerierWrapper, StdError, StdResult, Uint128, Uint64, -}; - -use crate::hub::HubBalance; - -/// Seconds in one week. It is intended for period number calculation. -pub const WEEK: u64 = 7 * 86400; // lock period is rounded down by week - -/// Default unlock period for a vxASTRO lite lock -pub const DEFAULT_UNLOCK_PERIOD: u64 = 2 * WEEK; - -pub const LITE_VOTING_PERIOD: u64 = 2 * WEEK; - -/// Seconds in 2 years which is the maximum lock period. -pub const MAX_LOCK_TIME: u64 = 2 * 365 * 86400; // 2 years (104 weeks) - -/// The constant describes the maximum number of accounts for which to claim accrued staking rewards in a single transaction. -pub const CLAIM_LIMIT: u64 = 10; - -/// The constant describes the minimum number of accounts for claim. -pub const MIN_CLAIM_LIMIT: u64 = 2; - -/// Feb 28 2022 00:00 UTC, Monday -pub const EPOCH_START: u64 = 1646006400; - -/// Calculates the period number. Time should be formatted as a timestamp. -pub fn get_period(time: u64) -> StdResult { - if time < EPOCH_START { - Err(StdError::generic_err("Invalid time")) - } else { - Ok((time - EPOCH_START) / WEEK) - } -} - -/// Calculates the voting period number for vxASTRO lite. Time should be formatted as a timestamp. -pub fn get_lite_period(time: u64) -> StdResult { - if time < EPOCH_START { - Err(StdError::generic_err("Invalid time")) - } else { - Ok((time - EPOCH_START) / LITE_VOTING_PERIOD) - } -} - -/// Calculates how many periods are in the specified time interval. The time should be in seconds. -pub fn get_periods_count(interval: u64) -> u64 { - interval / WEEK -} - -/// Calculates how many periods are in the specified time interval for vxASTRO lite. The time should be in seconds. -pub fn get_lite_periods_count(interval: u64) -> u64 { - interval / LITE_VOTING_PERIOD -} - -/// Main function used to calculate a user's voting power at a specific period as: previous_power - slope*(x - previous_x). -pub fn calc_voting_power( - slope: Uint128, - old_vp: Uint128, - start_period: u64, - end_period: u64, -) -> Uint128 { - let shift = slope - .checked_mul(Uint128::from(end_period - start_period)) - .unwrap_or_else(|_| Uint128::zero()); - old_vp.saturating_sub(shift) -} +use cosmwasm_std::{Addr, ChannelResponse, IbcQuery, QuerierWrapper, StdError, StdResult}; /// Checks that a contract supports a given IBC-channel. /// ## Params @@ -91,24 +26,3 @@ pub fn check_contract_supports_channel( )) }) } - -/// Retrieves the total amount of voting power held by all Outposts at a given time -/// ## Params -/// * **querier** is an object of type [`QuerierWrapper`]. -/// -/// * **contract** is the Hub contract address -/// -/// * **timestamp** The unix timestamp at which to query the total voting power -pub fn get_total_outpost_voting_power_at( - querier: QuerierWrapper, - contract: &Addr, - timestamp: u64, -) -> Result { - let response: HubBalance = querier.query_wasm_smart( - contract, - &crate::hub::QueryMsg::TotalChannelBalancesAt { - timestamp: Uint64::from(timestamp), - }, - )?; - Ok(response.balance) -} diff --git a/packages/astroport-governance/src/voting_escrow.rs b/packages/astroport-governance/src/voting_escrow.rs index 5a99e7c6..d2922f44 100644 --- a/packages/astroport-governance/src/voting_escrow.rs +++ b/packages/astroport-governance/src/voting_escrow.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::Uint128; +use cosmwasm_std::{Addr, Uint128}; use cw20::{BalanceResponse, Logo, MarketingInfoResponse, TokenInfoResponse}; /// This structure stores marketing information for vxASTRO. @@ -20,6 +20,8 @@ pub struct UpdateMarketingInfo { pub struct InstantiateMsg { /// xASTRO denom pub deposit_denom: String, + /// Astroport Emissions Controller contract + pub emissions_controller: String, /// Marketing info for vxASTRO pub marketing: Option, } @@ -33,6 +35,15 @@ pub enum ExecuteMsg { Unlock {}, /// Cancel unlocking Relock {}, + /// Permissioned to the Emissions Controller contract. + /// Confirms unlocking for a specific user. + /// Unconfirmed unlocks can't be withdrawn. + ConfirmUnlock { user: String }, + /// Permissioned to the Emissions Controller contract. + /// Cancel unlocking for a specific user. + /// This is used on IBC failures/timeouts. + /// Allows users to retry unlocking. + ForceRelock { user: String }, /// Withdraw xASTRO from the vxASTRO contract Withdraw {}, /// Update the marketing info for the vxASTRO contract @@ -78,12 +89,23 @@ pub enum QueryMsg { pub struct Config { /// The xASTRO denom pub deposit_denom: String, + /// Astroport Emissions Controller contract + pub emissions_controller: Addr, +} + +#[derive(Copy)] +#[cw_serde] +pub struct UnlockStatus { + /// The timestamp when a lock will be unlocked. + pub end: u64, + /// Whether The Hub confirmed unlocking + pub hub_confirmed: bool, } #[cw_serde] pub struct LockInfoResponse { - /// The total amount of xASTRO tokens that were deposited in the vxASTRO position + /// The total number of xASTRO tokens that were deposited in the vxASTRO position pub amount: Uint128, - /// The timestamp when a lock will be unlocked. None for positions in Locked state - pub end: Option, + /// Unlocking status. None for positions in Locked state + pub unlock_status: Option, } diff --git a/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json b/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json new file mode 100644 index 00000000..27b8dea4 --- /dev/null +++ b/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json @@ -0,0 +1,786 @@ +{ + "contract_name": "astroport-emissions-controller-outpost", + "contract_version": "1.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "description": "This structure describes the basic settings for creating a contract.", + "type": "object", + "required": [ + "astro_denom", + "factory", + "hub_emissions_controller", + "ics20_channel", + "owner", + "vxastro_code_id", + "vxastro_deposit_denom" + ], + "properties": { + "astro_denom": { + "description": "ASTRO denom on the chain", + "type": "string" + }, + "factory": { + "description": "Astroport Factory contract", + "type": "string" + }, + "hub_emissions_controller": { + "description": "Emissions controller on the Hub", + "type": "string" + }, + "ics20_channel": { + "description": "Official ICS20 IBC channel from this outpost to the Hub", + "type": "string" + }, + "owner": { + "description": "Contract owner", + "type": "string" + }, + "vxastro_code_id": { + "description": "vxASTRO contract code id", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vxastro_deposit_denom": { + "description": "xASTRO denom", + "type": "string" + }, + "vxastro_marketing_info": { + "description": "vxASTRO token marketing info", + "anyOf": [ + { + "$ref": "#/definitions/UpdateMarketingInfo" + }, + { + "type": "null" + } + ] + } + }, + "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" + }, + "EmbeddedLogo": { + "description": "This is used to store the logo on the blockchain in an accepted format. Enforce maximum size of 5KB on all variants.", + "oneOf": [ + { + "description": "Store the Logo as an SVG file. The content must conform to the spec at https://en.wikipedia.org/wiki/Scalable_Vector_Graphics (The contract should do some light-weight sanity-check validation)", + "type": "object", + "required": [ + "svg" + ], + "properties": { + "svg": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + }, + { + "description": "Store the Logo as a PNG file. This will likely only support up to 64x64 or so within the 5KB limit.", + "type": "object", + "required": [ + "png" + ], + "properties": { + "png": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + } + ] + }, + "Logo": { + "description": "This is used for uploading logo data, or setting it in InstantiateData", + "oneOf": [ + { + "description": "A reference to an externally hosted logo. Must be a valid HTTP or HTTPS URL.", + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "description": "Logo content stored on the blockchain. Enforce maximum size of 5KB on all variants", + "type": "object", + "required": [ + "embedded" + ], + "properties": { + "embedded": { + "$ref": "#/definitions/EmbeddedLogo" + } + }, + "additionalProperties": false + } + ] + }, + "UpdateMarketingInfo": { + "description": "This structure stores marketing information for vxASTRO.", + "type": "object", + "properties": { + "description": { + "description": "Token description", + "type": [ + "string", + "null" + ] + }, + "logo": { + "description": "Token logo", + "anyOf": [ + { + "$ref": "#/definitions/Logo" + }, + { + "type": "null" + } + ] + }, + "marketing": { + "description": "Token marketing information", + "type": [ + "string", + "null" + ] + }, + "project": { + "description": "Project URL", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Vote allows a vxASTRO holders to cast votes on which pools should get ASTRO emissions in the next epoch", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "votes" + ], + "properties": { + "votes": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/Decimal" + } + ], + "maxItems": 2, + "minItems": 2 + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Only vxASTRO contract can call this endpoint. Updates user votes according to the current voting power.", + "type": "object", + "required": [ + "update_user_votes" + ], + "properties": { + "update_user_votes": { + "type": "object", + "required": [ + "is_unlock", + "user" + ], + "properties": { + "is_unlock": { + "type": "boolean" + }, + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Permissionless endpoint which allows user to update their voting power contribution in case of IBC failures or pool has been re-added to whitelist.", + "type": "object", + "required": [ + "refresh_user_votes" + ], + "properties": { + "refresh_user_votes": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "ProposeNewOwner proposes a new owner for the contract", + "type": "object", + "required": [ + "propose_new_owner" + ], + "properties": { + "propose_new_owner": { + "type": "object", + "required": [ + "expires_in", + "new_owner" + ], + "properties": { + "expires_in": { + "description": "The timestamp when the contract ownership change expires", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "new_owner": { + "description": "Newly proposed contract owner", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "DropOwnershipProposal removes the latest contract ownership transfer proposal", + "type": "object", + "required": [ + "drop_ownership_proposal" + ], + "properties": { + "drop_ownership_proposal": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "ClaimOwnership allows the newly proposed owner to claim contract ownership", + "type": "object", + "required": [ + "claim_ownership" + ], + "properties": { + "claim_ownership": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Set of endpoints specific for Hub/Outpost", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/OutpostMsg" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Asset": { + "description": "This enum describes a Terra asset (native or CW20).", + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "description": "A token amount", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "info": { + "description": "Information about an asset stored in a [`AssetInfo`] struct", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "InputSchedule": { + "type": "object", + "required": [ + "duration_periods", + "reward" + ], + "properties": { + "duration_periods": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "reward": { + "$ref": "#/definitions/Asset" + } + }, + "additionalProperties": false + }, + "OutpostMsg": { + "oneOf": [ + { + "description": "SetEmissions is a permissionless endpoint that allows setting ASTRO emissions for the next epoch from the Hub by leveraging IBC hooks.", + "type": "object", + "required": [ + "set_emissions" + ], + "properties": { + "set_emissions": { + "type": "object", + "required": [ + "schedules" + ], + "properties": { + "schedules": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/InputSchedule" + } + ], + "maxItems": 2, + "minItems": 2 + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Same as SetEmissions but it allows using funds from contract balance (if available). This endpoint can be called only by contract owner. It is meant to be used in case of IBC hook wasn't triggered upon ics20 packet arrival, for example, if a chain doesn't support IBC hooks.", + "type": "object", + "required": [ + "permissioned_set_emissions" + ], + "properties": { + "permissioned_set_emissions": { + "type": "object", + "required": [ + "schedules" + ], + "properties": { + "schedules": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/InputSchedule" + } + ], + "maxItems": 2, + "minItems": 2 + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "hub_emissions_controller": { + "description": "Emissions controller on the Hub", + "type": [ + "string", + "null" + ] + }, + "ics20_channel": { + "description": "Official ICS20 IBC channel from this outpost to the Hub", + "type": [ + "string", + "null" + ] + }, + "voting_ibc_channel": { + "description": "Voting IBC wasm<>wasm channel", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "description": "This structure describes the query messages available in the contract.", + "oneOf": [ + { + "description": "Config returns the contract configuration", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "QueryUserIbcStatus returns the status of the user's IBC request. Whether they have a pending request or an error.", + "type": "object", + "required": [ + "query_user_ibc_status" + ], + "properties": { + "query_user_ibc_status": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": null, + "sudo": null, + "responses": { + "config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config", + "description": "General contract configuration", + "type": "object", + "required": [ + "astro_denom", + "factory", + "hub_emissions_controller", + "ics20_channel", + "owner", + "voting_ibc_channel", + "vxastro" + ], + "properties": { + "astro_denom": { + "description": "ASTRO denom on the chain", + "type": "string" + }, + "factory": { + "description": "Astroport Factory contract", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "hub_emissions_controller": { + "description": "Emissions controller on the Hub", + "type": "string" + }, + "ics20_channel": { + "description": "Official ICS20 IBC channel from this outpost to the Hub", + "type": "string" + }, + "owner": { + "description": "Address that's allowed to change contract parameters", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "voting_ibc_channel": { + "description": "vxASTRO IBC channel", + "type": "string" + }, + "vxastro": { + "description": "vxASTRO contract address", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } + }, + "query_user_ibc_status": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "UserIbcStatus", + "description": "Contains the pending IBC message or an error", + "type": "object", + "properties": { + "error": { + "anyOf": [ + { + "$ref": "#/definitions/UserIbcError" + }, + { + "type": "null" + } + ] + }, + "pending_msg": { + "anyOf": [ + { + "$ref": "#/definitions/VxAstroIbcMsg" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "UserIbcError": { + "description": "Contains failed IBC along with the error message", + "type": "object", + "required": [ + "err", + "msg" + ], + "properties": { + "err": { + "type": "string" + }, + "msg": { + "$ref": "#/definitions/VxAstroIbcMsg" + } + }, + "additionalProperties": false + }, + "VxAstroIbcMsg": { + "description": "Internal IBC message to be sent from outpost to the hub over voting channel", + "oneOf": [ + { + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "voter", + "votes", + "voting_power" + ], + "properties": { + "voter": { + "type": "string" + }, + "votes": { + "description": "Voting power distribution", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Decimal" + } + }, + "voting_power": { + "description": "Actual voting power reported from outpost", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_user_votes" + ], + "properties": { + "update_user_votes": { + "type": "object", + "required": [ + "is_unlock", + "voter", + "voting_power" + ], + "properties": { + "is_unlock": { + "description": "Marker defines whether this packet was sent from vxASTRO unlock context", + "type": "boolean" + }, + "voter": { + "type": "string" + }, + "voting_power": { + "description": "Actual voting power reported from outpost", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + } + } + } + } +} diff --git a/schemas/astroport-emissions-controller-outpost/raw/execute.json b/schemas/astroport-emissions-controller-outpost/raw/execute.json new file mode 100644 index 00000000..0c2c1b63 --- /dev/null +++ b/schemas/astroport-emissions-controller-outpost/raw/execute.json @@ -0,0 +1,370 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Vote allows a vxASTRO holders to cast votes on which pools should get ASTRO emissions in the next epoch", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "votes" + ], + "properties": { + "votes": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/Decimal" + } + ], + "maxItems": 2, + "minItems": 2 + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Only vxASTRO contract can call this endpoint. Updates user votes according to the current voting power.", + "type": "object", + "required": [ + "update_user_votes" + ], + "properties": { + "update_user_votes": { + "type": "object", + "required": [ + "is_unlock", + "user" + ], + "properties": { + "is_unlock": { + "type": "boolean" + }, + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Permissionless endpoint which allows user to update their voting power contribution in case of IBC failures or pool has been re-added to whitelist.", + "type": "object", + "required": [ + "refresh_user_votes" + ], + "properties": { + "refresh_user_votes": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "ProposeNewOwner proposes a new owner for the contract", + "type": "object", + "required": [ + "propose_new_owner" + ], + "properties": { + "propose_new_owner": { + "type": "object", + "required": [ + "expires_in", + "new_owner" + ], + "properties": { + "expires_in": { + "description": "The timestamp when the contract ownership change expires", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "new_owner": { + "description": "Newly proposed contract owner", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "DropOwnershipProposal removes the latest contract ownership transfer proposal", + "type": "object", + "required": [ + "drop_ownership_proposal" + ], + "properties": { + "drop_ownership_proposal": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "ClaimOwnership allows the newly proposed owner to claim contract ownership", + "type": "object", + "required": [ + "claim_ownership" + ], + "properties": { + "claim_ownership": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Set of endpoints specific for Hub/Outpost", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/OutpostMsg" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Asset": { + "description": "This enum describes a Terra asset (native or CW20).", + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "description": "A token amount", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "info": { + "description": "Information about an asset stored in a [`AssetInfo`] struct", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "InputSchedule": { + "type": "object", + "required": [ + "duration_periods", + "reward" + ], + "properties": { + "duration_periods": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "reward": { + "$ref": "#/definitions/Asset" + } + }, + "additionalProperties": false + }, + "OutpostMsg": { + "oneOf": [ + { + "description": "SetEmissions is a permissionless endpoint that allows setting ASTRO emissions for the next epoch from the Hub by leveraging IBC hooks.", + "type": "object", + "required": [ + "set_emissions" + ], + "properties": { + "set_emissions": { + "type": "object", + "required": [ + "schedules" + ], + "properties": { + "schedules": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/InputSchedule" + } + ], + "maxItems": 2, + "minItems": 2 + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Same as SetEmissions but it allows using funds from contract balance (if available). This endpoint can be called only by contract owner. It is meant to be used in case of IBC hook wasn't triggered upon ics20 packet arrival, for example, if a chain doesn't support IBC hooks.", + "type": "object", + "required": [ + "permissioned_set_emissions" + ], + "properties": { + "permissioned_set_emissions": { + "type": "object", + "required": [ + "schedules" + ], + "properties": { + "schedules": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/InputSchedule" + } + ], + "maxItems": 2, + "minItems": 2 + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "hub_emissions_controller": { + "description": "Emissions controller on the Hub", + "type": [ + "string", + "null" + ] + }, + "ics20_channel": { + "description": "Official ICS20 IBC channel from this outpost to the Hub", + "type": [ + "string", + "null" + ] + }, + "voting_ibc_channel": { + "description": "Voting IBC wasm<>wasm channel", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/schemas/astroport-emissions-controller-outpost/raw/instantiate.json b/schemas/astroport-emissions-controller-outpost/raw/instantiate.json new file mode 100644 index 00000000..b4eead92 --- /dev/null +++ b/schemas/astroport-emissions-controller-outpost/raw/instantiate.json @@ -0,0 +1,166 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "description": "This structure describes the basic settings for creating a contract.", + "type": "object", + "required": [ + "astro_denom", + "factory", + "hub_emissions_controller", + "ics20_channel", + "owner", + "vxastro_code_id", + "vxastro_deposit_denom" + ], + "properties": { + "astro_denom": { + "description": "ASTRO denom on the chain", + "type": "string" + }, + "factory": { + "description": "Astroport Factory contract", + "type": "string" + }, + "hub_emissions_controller": { + "description": "Emissions controller on the Hub", + "type": "string" + }, + "ics20_channel": { + "description": "Official ICS20 IBC channel from this outpost to the Hub", + "type": "string" + }, + "owner": { + "description": "Contract owner", + "type": "string" + }, + "vxastro_code_id": { + "description": "vxASTRO contract code id", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vxastro_deposit_denom": { + "description": "xASTRO denom", + "type": "string" + }, + "vxastro_marketing_info": { + "description": "vxASTRO token marketing info", + "anyOf": [ + { + "$ref": "#/definitions/UpdateMarketingInfo" + }, + { + "type": "null" + } + ] + } + }, + "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" + }, + "EmbeddedLogo": { + "description": "This is used to store the logo on the blockchain in an accepted format. Enforce maximum size of 5KB on all variants.", + "oneOf": [ + { + "description": "Store the Logo as an SVG file. The content must conform to the spec at https://en.wikipedia.org/wiki/Scalable_Vector_Graphics (The contract should do some light-weight sanity-check validation)", + "type": "object", + "required": [ + "svg" + ], + "properties": { + "svg": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + }, + { + "description": "Store the Logo as a PNG file. This will likely only support up to 64x64 or so within the 5KB limit.", + "type": "object", + "required": [ + "png" + ], + "properties": { + "png": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + } + ] + }, + "Logo": { + "description": "This is used for uploading logo data, or setting it in InstantiateData", + "oneOf": [ + { + "description": "A reference to an externally hosted logo. Must be a valid HTTP or HTTPS URL.", + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "description": "Logo content stored on the blockchain. Enforce maximum size of 5KB on all variants", + "type": "object", + "required": [ + "embedded" + ], + "properties": { + "embedded": { + "$ref": "#/definitions/EmbeddedLogo" + } + }, + "additionalProperties": false + } + ] + }, + "UpdateMarketingInfo": { + "description": "This structure stores marketing information for vxASTRO.", + "type": "object", + "properties": { + "description": { + "description": "Token description", + "type": [ + "string", + "null" + ] + }, + "logo": { + "description": "Token logo", + "anyOf": [ + { + "$ref": "#/definitions/Logo" + }, + { + "type": "null" + } + ] + }, + "marketing": { + "description": "Token marketing information", + "type": [ + "string", + "null" + ] + }, + "project": { + "description": "Project URL", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + } +} diff --git a/schemas/astroport-emissions-controller-outpost/raw/query.json b/schemas/astroport-emissions-controller-outpost/raw/query.json new file mode 100644 index 00000000..3a3a8301 --- /dev/null +++ b/schemas/astroport-emissions-controller-outpost/raw/query.json @@ -0,0 +1,43 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "description": "This structure describes the query messages available in the contract.", + "oneOf": [ + { + "description": "Config returns the contract configuration", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "QueryUserIbcStatus returns the status of the user's IBC request. Whether they have a pending request or an error.", + "type": "object", + "required": [ + "query_user_ibc_status" + ], + "properties": { + "query_user_ibc_status": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/schemas/astroport-emissions-controller-outpost/raw/response_to_config.json b/schemas/astroport-emissions-controller-outpost/raw/response_to_config.json new file mode 100644 index 00000000..10b2d199 --- /dev/null +++ b/schemas/astroport-emissions-controller-outpost/raw/response_to_config.json @@ -0,0 +1,64 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config", + "description": "General contract configuration", + "type": "object", + "required": [ + "astro_denom", + "factory", + "hub_emissions_controller", + "ics20_channel", + "owner", + "voting_ibc_channel", + "vxastro" + ], + "properties": { + "astro_denom": { + "description": "ASTRO denom on the chain", + "type": "string" + }, + "factory": { + "description": "Astroport Factory contract", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "hub_emissions_controller": { + "description": "Emissions controller on the Hub", + "type": "string" + }, + "ics20_channel": { + "description": "Official ICS20 IBC channel from this outpost to the Hub", + "type": "string" + }, + "owner": { + "description": "Address that's allowed to change contract parameters", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "voting_ibc_channel": { + "description": "vxASTRO IBC channel", + "type": "string" + }, + "vxastro": { + "description": "vxASTRO contract address", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } +} diff --git a/schemas/astroport-emissions-controller-outpost/raw/response_to_query_user_ibc_status.json b/schemas/astroport-emissions-controller-outpost/raw/response_to_query_user_ibc_status.json new file mode 100644 index 00000000..c53b9a21 --- /dev/null +++ b/schemas/astroport-emissions-controller-outpost/raw/response_to_query_user_ibc_status.json @@ -0,0 +1,134 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "UserIbcStatus", + "description": "Contains the pending IBC message or an error", + "type": "object", + "properties": { + "error": { + "anyOf": [ + { + "$ref": "#/definitions/UserIbcError" + }, + { + "type": "null" + } + ] + }, + "pending_msg": { + "anyOf": [ + { + "$ref": "#/definitions/VxAstroIbcMsg" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "UserIbcError": { + "description": "Contains failed IBC along with the error message", + "type": "object", + "required": [ + "err", + "msg" + ], + "properties": { + "err": { + "type": "string" + }, + "msg": { + "$ref": "#/definitions/VxAstroIbcMsg" + } + }, + "additionalProperties": false + }, + "VxAstroIbcMsg": { + "description": "Internal IBC message to be sent from outpost to the hub over voting channel", + "oneOf": [ + { + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "voter", + "votes", + "voting_power" + ], + "properties": { + "voter": { + "type": "string" + }, + "votes": { + "description": "Voting power distribution", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Decimal" + } + }, + "voting_power": { + "description": "Actual voting power reported from outpost", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_user_votes" + ], + "properties": { + "update_user_votes": { + "type": "object", + "required": [ + "is_unlock", + "voter", + "voting_power" + ], + "properties": { + "is_unlock": { + "description": "Marker defines whether this packet was sent from vxASTRO unlock context", + "type": "boolean" + }, + "voter": { + "type": "string" + }, + "voting_power": { + "description": "Actual voting power reported from outpost", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/schemas/astroport-emissions-controller/astroport-emissions-controller.json b/schemas/astroport-emissions-controller/astroport-emissions-controller.json new file mode 100644 index 00000000..044b2a00 --- /dev/null +++ b/schemas/astroport-emissions-controller/astroport-emissions-controller.json @@ -0,0 +1,1179 @@ +{ + "contract_name": "astroport-emissions-controller", + "contract_version": "1.0.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "description": "This structure describes the basic settings for creating a contract.", + "type": "object", + "required": [ + "astro_denom", + "factory", + "fee_receiver", + "owner", + "pools_per_outpost", + "vxastro_code_id", + "vxastro_deposit_denom", + "whitelist_threshold", + "whitelisting_fee" + ], + "properties": { + "astro_denom": { + "description": "ASTRO denom on the Hub", + "type": "string" + }, + "factory": { + "description": "Astroport Factory contract", + "type": "string" + }, + "fee_receiver": { + "description": "Address that receives the whitelisting fee", + "type": "string" + }, + "owner": { + "description": "Contract owner", + "type": "string" + }, + "pools_per_outpost": { + "description": "Max number of pools that can receive ASTRO emissions per outpost added. For example, if there are 3 outposts, and the pools_limit is 10, then 30 pools can receive ASTRO emissions. This limit doesn't enforce the exact number of pools per outpost, but adds flexibility to the contract to automatically adjust the max number of pools based on the number of outposts.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vxastro_code_id": { + "description": "vxASTRO contract code id", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vxastro_deposit_denom": { + "description": "xASTRO denom", + "type": "string" + }, + "vxastro_marketing_info": { + "description": "vxASTRO token marketing info", + "anyOf": [ + { + "$ref": "#/definitions/UpdateMarketingInfo" + }, + { + "type": "null" + } + ] + }, + "whitelist_threshold": { + "description": "Minimal percentage of total voting power required to keep a pool in the whitelist", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "whitelisting_fee": { + "description": "Fee required to whitelist a pool", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + } + }, + "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" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "EmbeddedLogo": { + "description": "This is used to store the logo on the blockchain in an accepted format. Enforce maximum size of 5KB on all variants.", + "oneOf": [ + { + "description": "Store the Logo as an SVG file. The content must conform to the spec at https://en.wikipedia.org/wiki/Scalable_Vector_Graphics (The contract should do some light-weight sanity-check validation)", + "type": "object", + "required": [ + "svg" + ], + "properties": { + "svg": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + }, + { + "description": "Store the Logo as a PNG file. This will likely only support up to 64x64 or so within the 5KB limit.", + "type": "object", + "required": [ + "png" + ], + "properties": { + "png": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + } + ] + }, + "Logo": { + "description": "This is used for uploading logo data, or setting it in InstantiateData", + "oneOf": [ + { + "description": "A reference to an externally hosted logo. Must be a valid HTTP or HTTPS URL.", + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "description": "Logo content stored on the blockchain. Enforce maximum size of 5KB on all variants", + "type": "object", + "required": [ + "embedded" + ], + "properties": { + "embedded": { + "$ref": "#/definitions/EmbeddedLogo" + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "UpdateMarketingInfo": { + "description": "This structure stores marketing information for vxASTRO.", + "type": "object", + "properties": { + "description": { + "description": "Token description", + "type": [ + "string", + "null" + ] + }, + "logo": { + "description": "Token logo", + "anyOf": [ + { + "$ref": "#/definitions/Logo" + }, + { + "type": "null" + } + ] + }, + "marketing": { + "description": "Token marketing information", + "type": [ + "string", + "null" + ] + }, + "project": { + "description": "Project URL", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Vote allows a vxASTRO holders to cast votes on which pools should get ASTRO emissions in the next epoch", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "votes" + ], + "properties": { + "votes": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/Decimal" + } + ], + "maxItems": 2, + "minItems": 2 + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Only vxASTRO contract can call this endpoint. Updates user votes according to the current voting power.", + "type": "object", + "required": [ + "update_user_votes" + ], + "properties": { + "update_user_votes": { + "type": "object", + "required": [ + "is_unlock", + "user" + ], + "properties": { + "is_unlock": { + "type": "boolean" + }, + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Permissionless endpoint which allows user to update their voting power contribution in case of IBC failures or pool has been re-added to whitelist.", + "type": "object", + "required": [ + "refresh_user_votes" + ], + "properties": { + "refresh_user_votes": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "ProposeNewOwner proposes a new owner for the contract", + "type": "object", + "required": [ + "propose_new_owner" + ], + "properties": { + "propose_new_owner": { + "type": "object", + "required": [ + "expires_in", + "new_owner" + ], + "properties": { + "expires_in": { + "description": "The timestamp when the contract ownership change expires", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "new_owner": { + "description": "Newly proposed contract owner", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "DropOwnershipProposal removes the latest contract ownership transfer proposal", + "type": "object", + "required": [ + "drop_ownership_proposal" + ], + "properties": { + "drop_ownership_proposal": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "ClaimOwnership allows the newly proposed owner to claim contract ownership", + "type": "object", + "required": [ + "claim_ownership" + ], + "properties": { + "claim_ownership": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Set of endpoints specific for Hub/Outpost", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/HubMsg" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "AstroPoolConfig": { + "type": "object", + "required": [ + "astro_pool", + "constant_emissions" + ], + "properties": { + "astro_pool": { + "description": "The most liquid ASTRO pool on this outpost", + "type": "string" + }, + "constant_emissions": { + "description": "The constant ASTRO pool emissions. Can be set to 0 if emissions are not needed.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "HubMsg": { + "oneOf": [ + { + "description": "TunePools transforms the latest vote distribution into alloc_points which turn into ASTRO emissions", + "type": "object", + "required": [ + "tune_pools" + ], + "properties": { + "tune_pools": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Repeats IBC transfer messages with IBC hook for all outposts in Failed state.", + "type": "object", + "required": [ + "retry_failed_outposts" + ], + "properties": { + "retry_failed_outposts": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Update the contract configuration", + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "fee_receiver": { + "type": [ + "string", + "null" + ] + }, + "pools_per_outpost": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "whitelisting_fee": { + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Whitelists a pool to receive ASTRO emissions. Requires fee payment", + "type": "object", + "required": [ + "whitelist_pool" + ], + "properties": { + "whitelist_pool": { + "type": "object", + "required": [ + "pool" + ], + "properties": { + "pool": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Register or update an outpost", + "type": "object", + "required": [ + "update_outpost" + ], + "properties": { + "update_outpost": { + "type": "object", + "required": [ + "astro_denom", + "prefix" + ], + "properties": { + "astro_denom": { + "description": "Astro denom on this outpost", + "type": "string" + }, + "astro_pool_config": { + "description": "A pool that must receive flat ASTRO emissions. Optional.", + "anyOf": [ + { + "$ref": "#/definitions/AstroPoolConfig" + }, + { + "type": "null" + } + ] + }, + "outpost_params": { + "description": "Outpost params contain all necessary information to interact with the remote outpost. This field also serves as marker whether it is The hub (params: None) or remote outpost (Some(params))", + "anyOf": [ + { + "$ref": "#/definitions/OutpostParams" + }, + { + "type": "null" + } + ] + }, + "prefix": { + "description": "Bech32 prefix", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Remove an outpost", + "type": "object", + "required": [ + "remove_outpost" + ], + "properties": { + "remove_outpost": { + "type": "object", + "required": [ + "prefix" + ], + "properties": { + "prefix": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "OutpostParams": { + "type": "object", + "required": [ + "emissions_controller", + "ics20_channel", + "voting_channel" + ], + "properties": { + "emissions_controller": { + "description": "Emissions controller on a given outpost", + "type": "string" + }, + "ics20_channel": { + "description": "General IBC channel for fungible token transfers", + "type": "string" + }, + "voting_channel": { + "description": "wasm<>wasm IBC channel for voting", + "type": "string" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "description": "This structure describes the query messages available in the contract.", + "oneOf": [ + { + "description": "UserInfo returns information about a voter and the pools they voted for. If timestamp is not provided, the current block time is used.", + "type": "object", + "required": [ + "user_info" + ], + "properties": { + "user_info": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "timestamp": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "TuneInfo returns emissions voting outcome at a certain timestamp. If timestamp is not provided, return the latest tune info.", + "type": "object", + "required": [ + "tune_info" + ], + "properties": { + "tune_info": { + "type": "object", + "properties": { + "timestamp": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Config returns the contract configuration", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "VotedPools returns how much voting power a pool received at a certain timestamp.", + "type": "object", + "required": [ + "voted_pool" + ], + "properties": { + "voted_pool": { + "type": "object", + "required": [ + "pool" + ], + "properties": { + "pool": { + "type": "string" + }, + "timestamp": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns paginated list of all pools that received votes", + "type": "object", + "required": [ + "voted_pools_list" + ], + "properties": { + "voted_pools_list": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint8", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "ListOutposts returns all outposts registered in the contract", + "type": "object", + "required": [ + "list_outposts" + ], + "properties": { + "list_outposts": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "QueryWhitelist returns the list of pools that are allowed to be voted for", + "type": "object", + "required": [ + "query_whitelist" + ], + "properties": { + "query_whitelist": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": null, + "sudo": null, + "responses": { + "config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config", + "description": "General contract configuration", + "type": "object", + "required": [ + "astro_denom", + "factory", + "fee_receiver", + "owner", + "pools_per_outpost", + "vxastro", + "whitelist_threshold", + "whitelisting_fee" + ], + "properties": { + "astro_denom": { + "description": "ASTRO denom on the Hub", + "type": "string" + }, + "factory": { + "description": "Astroport Factory contract", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "fee_receiver": { + "description": "Address that receives the whitelisting fee", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "owner": { + "description": "Address that's allowed to change contract parameters", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "pools_per_outpost": { + "description": "Max number of pools that can receive ASTRO emissions per outpost added. For example, if there are 3 outposts, and the pools_limit is 10, then 30 pools can receive ASTRO emissions. This limit doesn't enforce the exact number of pools per outpost, but adds flexibility to the contract to automatically adjust the max number of pools based on the number of outposts.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vxastro": { + "description": "vxASTRO contract address", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "whitelist_threshold": { + "description": "Minimal percentage of total voting power required to keep a pool in the whitelist", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "whitelisting_fee": { + "description": "Fee required to whitelist a pool", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "list_outposts": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Tuple_of_String_and_OutpostInfo", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/OutpostInfo" + } + ], + "maxItems": 2, + "minItems": 2 + }, + "definitions": { + "AstroPoolConfig": { + "type": "object", + "required": [ + "astro_pool", + "constant_emissions" + ], + "properties": { + "astro_pool": { + "description": "The most liquid ASTRO pool on this outpost", + "type": "string" + }, + "constant_emissions": { + "description": "The constant ASTRO pool emissions. Can be set to 0 if emissions are not needed.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + }, + "OutpostInfo": { + "type": "object", + "required": [ + "astro_denom" + ], + "properties": { + "astro_denom": { + "description": "ASTRO token denom", + "type": "string" + }, + "astro_pool_config": { + "description": "A pool that must receive flat ASTRO emissions. Optional.", + "anyOf": [ + { + "$ref": "#/definitions/AstroPoolConfig" + }, + { + "type": "null" + } + ] + }, + "params": { + "description": "Outpost params contain all necessary information to interact with the remote outpost. This field also serves as marker whether it is The hub (params: None) or remote outpost (Some(params))", + "anyOf": [ + { + "$ref": "#/definitions/OutpostParams" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "OutpostParams": { + "type": "object", + "required": [ + "emissions_controller", + "ics20_channel", + "voting_channel" + ], + "properties": { + "emissions_controller": { + "description": "Emissions controller on a given outpost", + "type": "string" + }, + "ics20_channel": { + "description": "General IBC channel for fungible token transfers", + "type": "string" + }, + "voting_channel": { + "description": "wasm<>wasm IBC channel for voting", + "type": "string" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "query_whitelist": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_String", + "type": "array", + "items": { + "type": "string" + } + }, + "tune_info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TuneInfo", + "type": "object", + "required": [ + "outpost_emissions_statuses", + "pools_grouped", + "tune_ts" + ], + "properties": { + "outpost_emissions_statuses": { + "description": "Map of outpost prefix -> IBC status. Hub should never enter this map.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/OutpostStatus" + } + }, + "pools_grouped": { + "description": "Map of outpost prefix -> array of pools with their emissions", + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/Uint128" + } + ], + "maxItems": 2, + "minItems": 2 + } + } + }, + "tune_ts": { + "description": "Last time when the tune was executed. Matches epoch start i.e., Monday 00:00 UTC every 2 weeks", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "OutpostStatus": { + "type": "string", + "enum": [ + "in_progress", + "failed", + "done" + ] + }, + "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" + } + } + }, + "user_info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "UserInfoResponse", + "type": "object", + "required": [ + "applied_votes", + "vote_ts", + "votes", + "voting_power" + ], + "properties": { + "applied_votes": { + "description": "Actual applied votes. This list excludes non-whitelisted pools", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Decimal" + } + }, + "vote_ts": { + "description": "Last time when a user voted", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "votes": { + "description": "Vote distribution for all the pools a user picked", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Decimal" + } + }, + "voting_power": { + "description": "Voting power used for the vote", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, + "voted_pool": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VotedPoolInfo", + "type": "object", + "required": [ + "init_ts", + "voting_power" + ], + "properties": { + "init_ts": { + "description": "Time when the pool was whitelisted", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "voting_power": { + "description": "Voting power the pool received", + "allOf": [ + { + "$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" + } + } + }, + "voted_pools_list": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Tuple_of_String_and_VotedPoolInfo", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/VotedPoolInfo" + } + ], + "maxItems": 2, + "minItems": 2 + }, + "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" + }, + "VotedPoolInfo": { + "type": "object", + "required": [ + "init_ts", + "voting_power" + ], + "properties": { + "init_ts": { + "description": "Time when the pool was whitelisted", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "voting_power": { + "description": "Voting power the pool received", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + } + } + } + } +} diff --git a/schemas/astroport-emissions-controller/raw/execute.json b/schemas/astroport-emissions-controller/raw/execute.json new file mode 100644 index 00000000..01e5b5bf --- /dev/null +++ b/schemas/astroport-emissions-controller/raw/execute.json @@ -0,0 +1,388 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "description": "Vote allows a vxASTRO holders to cast votes on which pools should get ASTRO emissions in the next epoch", + "type": "object", + "required": [ + "vote" + ], + "properties": { + "vote": { + "type": "object", + "required": [ + "votes" + ], + "properties": { + "votes": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/Decimal" + } + ], + "maxItems": 2, + "minItems": 2 + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Only vxASTRO contract can call this endpoint. Updates user votes according to the current voting power.", + "type": "object", + "required": [ + "update_user_votes" + ], + "properties": { + "update_user_votes": { + "type": "object", + "required": [ + "is_unlock", + "user" + ], + "properties": { + "is_unlock": { + "type": "boolean" + }, + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Permissionless endpoint which allows user to update their voting power contribution in case of IBC failures or pool has been re-added to whitelist.", + "type": "object", + "required": [ + "refresh_user_votes" + ], + "properties": { + "refresh_user_votes": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "ProposeNewOwner proposes a new owner for the contract", + "type": "object", + "required": [ + "propose_new_owner" + ], + "properties": { + "propose_new_owner": { + "type": "object", + "required": [ + "expires_in", + "new_owner" + ], + "properties": { + "expires_in": { + "description": "The timestamp when the contract ownership change expires", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "new_owner": { + "description": "Newly proposed contract owner", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "DropOwnershipProposal removes the latest contract ownership transfer proposal", + "type": "object", + "required": [ + "drop_ownership_proposal" + ], + "properties": { + "drop_ownership_proposal": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "ClaimOwnership allows the newly proposed owner to claim contract ownership", + "type": "object", + "required": [ + "claim_ownership" + ], + "properties": { + "claim_ownership": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Set of endpoints specific for Hub/Outpost", + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "$ref": "#/definitions/HubMsg" + } + }, + "additionalProperties": false + } + ], + "definitions": { + "AstroPoolConfig": { + "type": "object", + "required": [ + "astro_pool", + "constant_emissions" + ], + "properties": { + "astro_pool": { + "description": "The most liquid ASTRO pool on this outpost", + "type": "string" + }, + "constant_emissions": { + "description": "The constant ASTRO pool emissions. Can be set to 0 if emissions are not needed.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "HubMsg": { + "oneOf": [ + { + "description": "TunePools transforms the latest vote distribution into alloc_points which turn into ASTRO emissions", + "type": "object", + "required": [ + "tune_pools" + ], + "properties": { + "tune_pools": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Repeats IBC transfer messages with IBC hook for all outposts in Failed state.", + "type": "object", + "required": [ + "retry_failed_outposts" + ], + "properties": { + "retry_failed_outposts": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Update the contract configuration", + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "properties": { + "fee_receiver": { + "type": [ + "string", + "null" + ] + }, + "pools_per_outpost": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "whitelisting_fee": { + "anyOf": [ + { + "$ref": "#/definitions/Coin" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Whitelists a pool to receive ASTRO emissions. Requires fee payment", + "type": "object", + "required": [ + "whitelist_pool" + ], + "properties": { + "whitelist_pool": { + "type": "object", + "required": [ + "pool" + ], + "properties": { + "pool": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Register or update an outpost", + "type": "object", + "required": [ + "update_outpost" + ], + "properties": { + "update_outpost": { + "type": "object", + "required": [ + "astro_denom", + "prefix" + ], + "properties": { + "astro_denom": { + "description": "Astro denom on this outpost", + "type": "string" + }, + "astro_pool_config": { + "description": "A pool that must receive flat ASTRO emissions. Optional.", + "anyOf": [ + { + "$ref": "#/definitions/AstroPoolConfig" + }, + { + "type": "null" + } + ] + }, + "outpost_params": { + "description": "Outpost params contain all necessary information to interact with the remote outpost. This field also serves as marker whether it is The hub (params: None) or remote outpost (Some(params))", + "anyOf": [ + { + "$ref": "#/definitions/OutpostParams" + }, + { + "type": "null" + } + ] + }, + "prefix": { + "description": "Bech32 prefix", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Remove an outpost", + "type": "object", + "required": [ + "remove_outpost" + ], + "properties": { + "remove_outpost": { + "type": "object", + "required": [ + "prefix" + ], + "properties": { + "prefix": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "OutpostParams": { + "type": "object", + "required": [ + "emissions_controller", + "ics20_channel", + "voting_channel" + ], + "properties": { + "emissions_controller": { + "description": "Emissions controller on a given outpost", + "type": "string" + }, + "ics20_channel": { + "description": "General IBC channel for fungible token transfers", + "type": "string" + }, + "voting_channel": { + "description": "wasm<>wasm IBC channel for voting", + "type": "string" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/schemas/astroport-emissions-controller/raw/instantiate.json b/schemas/astroport-emissions-controller/raw/instantiate.json new file mode 100644 index 00000000..ff266bb6 --- /dev/null +++ b/schemas/astroport-emissions-controller/raw/instantiate.json @@ -0,0 +1,209 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "description": "This structure describes the basic settings for creating a contract.", + "type": "object", + "required": [ + "astro_denom", + "factory", + "fee_receiver", + "owner", + "pools_per_outpost", + "vxastro_code_id", + "vxastro_deposit_denom", + "whitelist_threshold", + "whitelisting_fee" + ], + "properties": { + "astro_denom": { + "description": "ASTRO denom on the Hub", + "type": "string" + }, + "factory": { + "description": "Astroport Factory contract", + "type": "string" + }, + "fee_receiver": { + "description": "Address that receives the whitelisting fee", + "type": "string" + }, + "owner": { + "description": "Contract owner", + "type": "string" + }, + "pools_per_outpost": { + "description": "Max number of pools that can receive ASTRO emissions per outpost added. For example, if there are 3 outposts, and the pools_limit is 10, then 30 pools can receive ASTRO emissions. This limit doesn't enforce the exact number of pools per outpost, but adds flexibility to the contract to automatically adjust the max number of pools based on the number of outposts.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vxastro_code_id": { + "description": "vxASTRO contract code id", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vxastro_deposit_denom": { + "description": "xASTRO denom", + "type": "string" + }, + "vxastro_marketing_info": { + "description": "vxASTRO token marketing info", + "anyOf": [ + { + "$ref": "#/definitions/UpdateMarketingInfo" + }, + { + "type": "null" + } + ] + }, + "whitelist_threshold": { + "description": "Minimal percentage of total voting power required to keep a pool in the whitelist", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "whitelisting_fee": { + "description": "Fee required to whitelist a pool", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + } + }, + "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" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "EmbeddedLogo": { + "description": "This is used to store the logo on the blockchain in an accepted format. Enforce maximum size of 5KB on all variants.", + "oneOf": [ + { + "description": "Store the Logo as an SVG file. The content must conform to the spec at https://en.wikipedia.org/wiki/Scalable_Vector_Graphics (The contract should do some light-weight sanity-check validation)", + "type": "object", + "required": [ + "svg" + ], + "properties": { + "svg": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + }, + { + "description": "Store the Logo as a PNG file. This will likely only support up to 64x64 or so within the 5KB limit.", + "type": "object", + "required": [ + "png" + ], + "properties": { + "png": { + "$ref": "#/definitions/Binary" + } + }, + "additionalProperties": false + } + ] + }, + "Logo": { + "description": "This is used for uploading logo data, or setting it in InstantiateData", + "oneOf": [ + { + "description": "A reference to an externally hosted logo. Must be a valid HTTP or HTTPS URL.", + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "description": "Logo content stored on the blockchain. Enforce maximum size of 5KB on all variants", + "type": "object", + "required": [ + "embedded" + ], + "properties": { + "embedded": { + "$ref": "#/definitions/EmbeddedLogo" + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "UpdateMarketingInfo": { + "description": "This structure stores marketing information for vxASTRO.", + "type": "object", + "properties": { + "description": { + "description": "Token description", + "type": [ + "string", + "null" + ] + }, + "logo": { + "description": "Token logo", + "anyOf": [ + { + "$ref": "#/definitions/Logo" + }, + { + "type": "null" + } + ] + }, + "marketing": { + "description": "Token marketing information", + "type": [ + "string", + "null" + ] + }, + "project": { + "description": "Project URL", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + } +} diff --git a/schemas/astroport-emissions-controller/raw/query.json b/schemas/astroport-emissions-controller/raw/query.json new file mode 100644 index 00000000..f70a61d8 --- /dev/null +++ b/schemas/astroport-emissions-controller/raw/query.json @@ -0,0 +1,163 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "description": "This structure describes the query messages available in the contract.", + "oneOf": [ + { + "description": "UserInfo returns information about a voter and the pools they voted for. If timestamp is not provided, the current block time is used.", + "type": "object", + "required": [ + "user_info" + ], + "properties": { + "user_info": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "timestamp": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "TuneInfo returns emissions voting outcome at a certain timestamp. If timestamp is not provided, return the latest tune info.", + "type": "object", + "required": [ + "tune_info" + ], + "properties": { + "tune_info": { + "type": "object", + "properties": { + "timestamp": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Config returns the contract configuration", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "VotedPools returns how much voting power a pool received at a certain timestamp.", + "type": "object", + "required": [ + "voted_pool" + ], + "properties": { + "voted_pool": { + "type": "object", + "required": [ + "pool" + ], + "properties": { + "pool": { + "type": "string" + }, + "timestamp": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns paginated list of all pools that received votes", + "type": "object", + "required": [ + "voted_pools_list" + ], + "properties": { + "voted_pools_list": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint8", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "ListOutposts returns all outposts registered in the contract", + "type": "object", + "required": [ + "list_outposts" + ], + "properties": { + "list_outposts": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "QueryWhitelist returns the list of pools that are allowed to be voted for", + "type": "object", + "required": [ + "query_whitelist" + ], + "properties": { + "query_whitelist": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/schemas/astroport-emissions-controller/raw/response_to_config.json b/schemas/astroport-emissions-controller/raw/response_to_config.json new file mode 100644 index 00000000..e6986a21 --- /dev/null +++ b/schemas/astroport-emissions-controller/raw/response_to_config.json @@ -0,0 +1,106 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config", + "description": "General contract configuration", + "type": "object", + "required": [ + "astro_denom", + "factory", + "fee_receiver", + "owner", + "pools_per_outpost", + "vxastro", + "whitelist_threshold", + "whitelisting_fee" + ], + "properties": { + "astro_denom": { + "description": "ASTRO denom on the Hub", + "type": "string" + }, + "factory": { + "description": "Astroport Factory contract", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "fee_receiver": { + "description": "Address that receives the whitelisting fee", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "owner": { + "description": "Address that's allowed to change contract parameters", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "pools_per_outpost": { + "description": "Max number of pools that can receive ASTRO emissions per outpost added. For example, if there are 3 outposts, and the pools_limit is 10, then 30 pools can receive ASTRO emissions. This limit doesn't enforce the exact number of pools per outpost, but adds flexibility to the contract to automatically adjust the max number of pools based on the number of outposts.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vxastro": { + "description": "vxASTRO contract address", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "whitelist_threshold": { + "description": "Minimal percentage of total voting power required to keep a pool in the whitelist", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "whitelisting_fee": { + "description": "Fee required to whitelist a pool", + "allOf": [ + { + "$ref": "#/definitions/Coin" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Coin": { + "type": "object", + "required": [ + "amount", + "denom" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "denom": { + "type": "string" + } + } + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/schemas/astroport-emissions-controller/raw/response_to_list_outposts.json b/schemas/astroport-emissions-controller/raw/response_to_list_outposts.json new file mode 100644 index 00000000..7dba5ebf --- /dev/null +++ b/schemas/astroport-emissions-controller/raw/response_to_list_outposts.json @@ -0,0 +1,104 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Tuple_of_String_and_OutpostInfo", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/OutpostInfo" + } + ], + "maxItems": 2, + "minItems": 2 + }, + "definitions": { + "AstroPoolConfig": { + "type": "object", + "required": [ + "astro_pool", + "constant_emissions" + ], + "properties": { + "astro_pool": { + "description": "The most liquid ASTRO pool on this outpost", + "type": "string" + }, + "constant_emissions": { + "description": "The constant ASTRO pool emissions. Can be set to 0 if emissions are not needed.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + }, + "OutpostInfo": { + "type": "object", + "required": [ + "astro_denom" + ], + "properties": { + "astro_denom": { + "description": "ASTRO token denom", + "type": "string" + }, + "astro_pool_config": { + "description": "A pool that must receive flat ASTRO emissions. Optional.", + "anyOf": [ + { + "$ref": "#/definitions/AstroPoolConfig" + }, + { + "type": "null" + } + ] + }, + "params": { + "description": "Outpost params contain all necessary information to interact with the remote outpost. This field also serves as marker whether it is The hub (params: None) or remote outpost (Some(params))", + "anyOf": [ + { + "$ref": "#/definitions/OutpostParams" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "OutpostParams": { + "type": "object", + "required": [ + "emissions_controller", + "ics20_channel", + "voting_channel" + ], + "properties": { + "emissions_controller": { + "description": "Emissions controller on a given outpost", + "type": "string" + }, + "ics20_channel": { + "description": "General IBC channel for fungible token transfers", + "type": "string" + }, + "voting_channel": { + "description": "wasm<>wasm IBC channel for voting", + "type": "string" + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/schemas/astroport-emissions-controller/raw/response_to_query_whitelist.json b/schemas/astroport-emissions-controller/raw/response_to_query_whitelist.json new file mode 100644 index 00000000..4290cb1a --- /dev/null +++ b/schemas/astroport-emissions-controller/raw/response_to_query_whitelist.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_String", + "type": "array", + "items": { + "type": "string" + } +} diff --git a/schemas/astroport-emissions-controller/raw/response_to_tune_info.json b/schemas/astroport-emissions-controller/raw/response_to_tune_info.json new file mode 100644 index 00000000..34775acb --- /dev/null +++ b/schemas/astroport-emissions-controller/raw/response_to_tune_info.json @@ -0,0 +1,60 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TuneInfo", + "type": "object", + "required": [ + "outpost_emissions_statuses", + "pools_grouped", + "tune_ts" + ], + "properties": { + "outpost_emissions_statuses": { + "description": "Map of outpost prefix -> IBC status. Hub should never enter this map.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/OutpostStatus" + } + }, + "pools_grouped": { + "description": "Map of outpost prefix -> array of pools with their emissions", + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/Uint128" + } + ], + "maxItems": 2, + "minItems": 2 + } + } + }, + "tune_ts": { + "description": "Last time when the tune was executed. Matches epoch start i.e., Monday 00:00 UTC every 2 weeks", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false, + "definitions": { + "OutpostStatus": { + "type": "string", + "enum": [ + "in_progress", + "failed", + "done" + ] + }, + "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/schemas/astroport-emissions-controller/raw/response_to_user_info.json b/schemas/astroport-emissions-controller/raw/response_to_user_info.json new file mode 100644 index 00000000..b8c3d045 --- /dev/null +++ b/schemas/astroport-emissions-controller/raw/response_to_user_info.json @@ -0,0 +1,52 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "UserInfoResponse", + "type": "object", + "required": [ + "applied_votes", + "vote_ts", + "votes", + "voting_power" + ], + "properties": { + "applied_votes": { + "description": "Actual applied votes. This list excludes non-whitelisted pools", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Decimal" + } + }, + "vote_ts": { + "description": "Last time when a user voted", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "votes": { + "description": "Vote distribution for all the pools a user picked", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Decimal" + } + }, + "voting_power": { + "description": "Voting power used for the vote", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/schemas/astroport-emissions-controller/raw/response_to_voted_pool.json b/schemas/astroport-emissions-controller/raw/response_to_voted_pool.json new file mode 100644 index 00000000..11cfbc1f --- /dev/null +++ b/schemas/astroport-emissions-controller/raw/response_to_voted_pool.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VotedPoolInfo", + "type": "object", + "required": [ + "init_ts", + "voting_power" + ], + "properties": { + "init_ts": { + "description": "Time when the pool was whitelisted", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "voting_power": { + "description": "Voting power the pool received", + "allOf": [ + { + "$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/schemas/astroport-emissions-controller/raw/response_to_voted_pools_list.json b/schemas/astroport-emissions-controller/raw/response_to_voted_pools_list.json new file mode 100644 index 00000000..3cdf584d --- /dev/null +++ b/schemas/astroport-emissions-controller/raw/response_to_voted_pools_list.json @@ -0,0 +1,48 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Tuple_of_String_and_VotedPoolInfo", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/VotedPoolInfo" + } + ], + "maxItems": 2, + "minItems": 2 + }, + "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" + }, + "VotedPoolInfo": { + "type": "object", + "required": [ + "init_ts", + "voting_power" + ], + "properties": { + "init_ts": { + "description": "Time when the pool was whitelisted", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "voting_power": { + "description": "Voting power the pool received", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + } + } +} diff --git a/schemas/astroport-voting-escrow/astroport-voting-escrow.json b/schemas/astroport-voting-escrow/astroport-voting-escrow.json index 37735849..77380f2c 100644 --- a/schemas/astroport-voting-escrow/astroport-voting-escrow.json +++ b/schemas/astroport-voting-escrow/astroport-voting-escrow.json @@ -8,13 +8,18 @@ "description": "This structure stores general parameters for the vxASTRO contract.", "type": "object", "required": [ - "deposit_denom" + "deposit_denom", + "emissions_controller" ], "properties": { "deposit_denom": { "description": "xASTRO denom", "type": "string" }, + "emissions_controller": { + "description": "Astroport Emissions Controller contract", + "type": "string" + }, "marketing": { "description": "Marketing info for vxASTRO", "anyOf": [ @@ -191,6 +196,50 @@ }, "additionalProperties": false }, + { + "description": "Permissioned to the Emissions Controller contract. Confirms unlocking for a specific user. Unconfirmed unlocks can't be withdrawn.", + "type": "object", + "required": [ + "confirm_unlock" + ], + "properties": { + "confirm_unlock": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Permissioned to the Emissions Controller contract. Cancel unlocking for a specific user. This is used on IBC failures/timeouts. Allows users to retry unlocking.", + "type": "object", + "required": [ + "force_relock" + ], + "properties": { + "force_relock": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Withdraw xASTRO from the vxASTRO contract", "type": "object", @@ -420,15 +469,30 @@ "description": "This structure stores the main parameters for the voting escrow contract.", "type": "object", "required": [ - "deposit_denom" + "deposit_denom", + "emissions_controller" ], "properties": { "deposit_denom": { "description": "The xASTRO denom", "type": "string" + }, + "emissions_controller": { + "description": "Astroport Emissions Controller contract", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] } }, - "additionalProperties": false + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } }, "lock_info": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -439,21 +503,23 @@ ], "properties": { "amount": { - "description": "The total amount of xASTRO tokens that were deposited in the vxASTRO position", + "description": "The total number of xASTRO tokens that were deposited in the vxASTRO position", "allOf": [ { "$ref": "#/definitions/Uint128" } ] }, - "end": { - "description": "The timestamp when a lock will be unlocked. None for positions in Locked state", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 + "unlock_status": { + "description": "Unlocking status. None for positions in Locked state", + "anyOf": [ + { + "$ref": "#/definitions/UnlockStatus" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false, @@ -461,6 +527,26 @@ "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" + }, + "UnlockStatus": { + "type": "object", + "required": [ + "end", + "hub_confirmed" + ], + "properties": { + "end": { + "description": "The timestamp when a lock will be unlocked.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "hub_confirmed": { + "description": "Whether The Hub confirmed unlocking", + "type": "boolean" + } + }, + "additionalProperties": false } } }, diff --git a/schemas/astroport-voting-escrow/raw/execute.json b/schemas/astroport-voting-escrow/raw/execute.json index 88c6a3c1..c529c0ad 100644 --- a/schemas/astroport-voting-escrow/raw/execute.json +++ b/schemas/astroport-voting-escrow/raw/execute.json @@ -53,6 +53,50 @@ }, "additionalProperties": false }, + { + "description": "Permissioned to the Emissions Controller contract. Confirms unlocking for a specific user. Unconfirmed unlocks can't be withdrawn.", + "type": "object", + "required": [ + "confirm_unlock" + ], + "properties": { + "confirm_unlock": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Permissioned to the Emissions Controller contract. Cancel unlocking for a specific user. This is used on IBC failures/timeouts. Allows users to retry unlocking.", + "type": "object", + "required": [ + "force_relock" + ], + "properties": { + "force_relock": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "user": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Withdraw xASTRO from the vxASTRO contract", "type": "object", diff --git a/schemas/astroport-voting-escrow/raw/instantiate.json b/schemas/astroport-voting-escrow/raw/instantiate.json index 1bd521af..0114d2ad 100644 --- a/schemas/astroport-voting-escrow/raw/instantiate.json +++ b/schemas/astroport-voting-escrow/raw/instantiate.json @@ -4,13 +4,18 @@ "description": "This structure stores general parameters for the vxASTRO contract.", "type": "object", "required": [ - "deposit_denom" + "deposit_denom", + "emissions_controller" ], "properties": { "deposit_denom": { "description": "xASTRO denom", "type": "string" }, + "emissions_controller": { + "description": "Astroport Emissions Controller contract", + "type": "string" + }, "marketing": { "description": "Marketing info for vxASTRO", "anyOf": [ diff --git a/schemas/astroport-voting-escrow/raw/response_to_config.json b/schemas/astroport-voting-escrow/raw/response_to_config.json index 82583b5e..ae67715e 100644 --- a/schemas/astroport-voting-escrow/raw/response_to_config.json +++ b/schemas/astroport-voting-escrow/raw/response_to_config.json @@ -4,13 +4,28 @@ "description": "This structure stores the main parameters for the voting escrow contract.", "type": "object", "required": [ - "deposit_denom" + "deposit_denom", + "emissions_controller" ], "properties": { "deposit_denom": { "description": "The xASTRO denom", "type": "string" + }, + "emissions_controller": { + "description": "Astroport Emissions Controller contract", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] } }, - "additionalProperties": false + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + } + } } diff --git a/schemas/astroport-voting-escrow/raw/response_to_lock_info.json b/schemas/astroport-voting-escrow/raw/response_to_lock_info.json index 06b854ef..337d8d0b 100644 --- a/schemas/astroport-voting-escrow/raw/response_to_lock_info.json +++ b/schemas/astroport-voting-escrow/raw/response_to_lock_info.json @@ -7,21 +7,23 @@ ], "properties": { "amount": { - "description": "The total amount of xASTRO tokens that were deposited in the vxASTRO position", + "description": "The total number of xASTRO tokens that were deposited in the vxASTRO position", "allOf": [ { "$ref": "#/definitions/Uint128" } ] }, - "end": { - "description": "The timestamp when a lock will be unlocked. None for positions in Locked state", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 0.0 + "unlock_status": { + "description": "Unlocking status. None for positions in Locked state", + "anyOf": [ + { + "$ref": "#/definitions/UnlockStatus" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false, @@ -29,6 +31,26 @@ "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" + }, + "UnlockStatus": { + "type": "object", + "required": [ + "end", + "hub_confirmed" + ], + "properties": { + "end": { + "description": "The timestamp when a lock will be unlocked.", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "hub_confirmed": { + "description": "Whether The Hub confirmed unlocking", + "type": "boolean" + } + }, + "additionalProperties": false } } } From 578818a482bfc6251db1066a4df387386984c022 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Tue, 4 Jun 2024 12:01:18 +0400 Subject: [PATCH 09/32] feat(emissions controller): dynamic emissions curve --- Cargo.lock | 175 +++++++------ contracts/emissions_controller/Cargo.toml | 7 +- contracts/emissions_controller/src/error.rs | 5 +- contracts/emissions_controller/src/execute.rs | 118 +++------ .../emissions_controller/src/instantiate.rs | 40 ++- contracts/emissions_controller/src/query.rs | 42 ++- contracts/emissions_controller/src/state.rs | 1 - contracts/emissions_controller/src/utils.rs | 167 +++++++++++- .../tests/common/contracts.rs | 33 ++- .../tests/common/helper.rs | 164 ++++++++++-- .../emissions_controller/tests/common/mod.rs | 3 +- .../tests/common/stargate.rs | 121 +++++++++ .../tests/emissions_controller_integration.rs | 83 +++++- .../src/execute.rs | 7 +- .../src/instantiate.rs | 8 +- .../tests/common/helper.rs | 2 +- .../src/emissions_controller/hub.rs | 87 ++++++- .../src/emissions_controller/outpost.rs | 6 +- ...stroport-emissions-controller-outpost.json | 19 +- .../raw/instantiate.json | 10 +- .../raw/response_to_config.json | 9 + .../astroport-emissions-controller.json | 245 +++++++++++++++++- .../raw/execute.json | 20 ++ .../raw/instantiate.json | 39 ++- .../raw/query.json | 16 +- .../raw/response_to_config.json | 43 ++- .../raw/response_to_simulate_tune.json | 79 ++++++ .../raw/response_to_tune_info.json | 48 ++++ 28 files changed, 1335 insertions(+), 262 deletions(-) create mode 100644 contracts/emissions_controller/tests/common/stargate.rs create mode 100644 schemas/astroport-emissions-controller/raw/response_to_simulate_tune.json diff --git a/Cargo.lock b/Cargo.lock index b2eeec92..149df25e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,9 +15,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.83" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "astro-assembly" @@ -25,14 +25,14 @@ version = "2.0.0" dependencies = [ "anyhow", "astro-satellite", - "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "astroport 4.0.2", "astroport-governance 3.0.0", - "astroport-staking", + "astroport-staking 2.1.0 (git+https://github.com/astroport-fi/hidden_astroport_core)", "astroport-tokenfactory-tracker", "builder-unlock", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 0.20.0 (git+https://github.com/astroport-fi/cw-multi-test?branch=feat/bank_with_send_hooks)", + "cw-multi-test 0.20.0", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -123,8 +123,8 @@ dependencies = [ [[package]] name = "astroport" -version = "4.0.2" -source = "git+https://github.com/astroport-fi/hidden_astroport_core#02eafc677d9c402a55e45e956fd185d67ac3a8a6" +version = "4.0.3" +source = "git+https://github.com/astroport-fi/hidden_astroport_core#5e7d5cd3920dd6094a6e7a090a81b51a4371a8c6" dependencies = [ "astroport-circular-buffer 0.2.0 (git+https://github.com/astroport-fi/hidden_astroport_core)", "cosmwasm-schema", @@ -163,7 +163,7 @@ dependencies = [ [[package]] name = "astroport-circular-buffer" version = "0.2.0" -source = "git+https://github.com/astroport-fi/hidden_astroport_core#02eafc677d9c402a55e45e956fd185d67ac3a8a6" +source = "git+https://github.com/astroport-fi/hidden_astroport_core#5e7d5cd3920dd6094a6e7a090a81b51a4371a8c6" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -175,11 +175,13 @@ dependencies = [ name = "astroport-emissions-controller" version = "1.0.0" dependencies = [ - "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "anyhow", + "astroport 4.0.2", "astroport-factory", "astroport-governance 3.0.0", "astroport-incentives", "astroport-pair", + "astroport-staking 2.1.0 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", "astroport-voting-escrow", "cosmwasm-schema", "cosmwasm-std", @@ -191,6 +193,7 @@ dependencies = [ "derivative", "itertools 0.12.1", "neutron-sdk", + "osmosis-std", "serde_json", "thiserror", ] @@ -199,7 +202,7 @@ dependencies = [ name = "astroport-emissions-controller-outpost" version = "1.0.0" dependencies = [ - "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "astroport 4.0.2", "astroport-factory", "astroport-governance 3.0.0", "astroport-incentives", @@ -252,7 +255,7 @@ dependencies = [ name = "astroport-governance" version = "3.0.0" dependencies = [ - "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "astroport 4.0.2", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", @@ -275,7 +278,7 @@ name = "astroport-incentives" version = "1.2.0" source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#ada68324a4cca143008646d3c81baa715cba12d2" dependencies = [ - "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "astroport 4.0.2", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", @@ -307,9 +310,23 @@ dependencies = [ [[package]] name = "astroport-staking" version = "2.1.0" -source = "git+https://github.com/astroport-fi/hidden_astroport_core#02eafc677d9c402a55e45e956fd185d67ac3a8a6" +source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#ada68324a4cca143008646d3c81baa715cba12d2" dependencies = [ - "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core)", + "astroport 4.0.2", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "osmosis-std", + "thiserror", +] + +[[package]] +name = "astroport-staking" +version = "2.1.0" +source = "git+https://github.com/astroport-fi/hidden_astroport_core#5e7d5cd3920dd6094a6e7a090a81b51a4371a8c6" +dependencies = [ + "astroport 4.0.3", "cosmwasm-std", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", @@ -321,9 +338,9 @@ dependencies = [ [[package]] name = "astroport-tokenfactory-tracker" version = "1.0.0" -source = "git+https://github.com/astroport-fi/hidden_astroport_core#02eafc677d9c402a55e45e956fd185d67ac3a8a6" +source = "git+https://github.com/astroport-fi/hidden_astroport_core#5e7d5cd3920dd6094a6e7a090a81b51a4371a8c6" dependencies = [ - "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core)", + "astroport 4.0.3", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", @@ -335,7 +352,7 @@ dependencies = [ name = "astroport-voting-escrow" version = "1.0.0" dependencies = [ - "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "astroport 4.0.2", "astroport-governance 3.0.0", "cosmwasm-schema", "cosmwasm-std", @@ -412,11 +429,11 @@ checksum = "56953345e39537a3e18bdaeba4cb0c58a78c1f61f361dc0fa7c5c7340ae87c5f" name = "builder-unlock" version = "3.0.0" dependencies = [ - "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "astroport 4.0.2", "astroport-governance 3.0.0", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cw-multi-test 0.20.1", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -465,8 +482,8 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32560304ab4c365791fd307282f76637213d8083c1a98490c35159cd67852237" dependencies = [ - "prost 0.12.4", - "prost-types 0.12.4", + "prost 0.12.6", + "prost-types 0.12.6", "tendermint-proto", ] @@ -495,9 +512,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8467874827d384c131955ff6f4d47d02e72a956a08eb3c0ff24f8c903a5517b4" +checksum = "7879036156092ad1c22fe0d7316efc5a5eceec2bc3906462a2560215f2a2f929" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -508,9 +525,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6db85d98ac80922aef465e564d5b21fa9cfac5058cb62df7f116c3682337393" +checksum = "0bb57855fbfc83327f8445ae0d413b1a05ac0d68c396ab4d122b2abd7bb82cb6" dependencies = [ "proc-macro2", "quote", @@ -625,8 +642,7 @@ dependencies = [ [[package]] name = "cw-multi-test" version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fff029689ae89127cf6d7655809a68d712f3edbdb9686c70b018ba438b26ca" +source = "git+https://github.com/astroport-fi/cw-multi-test?branch=feat/bank_with_send_hooks#3220f4cd126b5a8da2ce8b00152afef046a9b391" dependencies = [ "anyhow", "bech32 0.9.1", @@ -635,7 +651,7 @@ dependencies = [ "cw-utils 1.0.3", "derivative", "itertools 0.12.1", - "prost 0.12.4", + "prost 0.12.6", "schemars", "serde", "sha2 0.10.8", @@ -644,8 +660,9 @@ dependencies = [ [[package]] name = "cw-multi-test" -version = "0.20.0" -source = "git+https://github.com/astroport-fi/cw-multi-test?branch=feat/bank_with_send_hooks#3220f4cd126b5a8da2ce8b00152afef046a9b391" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc392a5cb7e778e3f90adbf7faa43c4db7f35b6623224b08886d796718edb875" dependencies = [ "anyhow", "bech32 0.9.1", @@ -654,7 +671,7 @@ dependencies = [ "cw-utils 1.0.3", "derivative", "itertools 0.12.1", - "prost 0.12.4", + "prost 0.12.6", "schemars", "serde", "sha2 0.10.8", @@ -674,7 +691,7 @@ dependencies = [ "cw-utils 1.0.3", "derivative", "itertools 0.12.1", - "prost 0.12.4", + "prost 0.12.6", "schemars", "serde", "sha2 0.10.8", @@ -907,9 +924,9 @@ dependencies = [ [[package]] name = "either" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" [[package]] name = "elliptic-curve" @@ -1079,9 +1096,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.154" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "neutron-sdk" @@ -1093,8 +1110,8 @@ dependencies = [ "cosmos-sdk-proto", "cosmwasm-schema", "cosmwasm-std", - "prost 0.12.4", - "prost-types 0.12.4", + "prost 0.12.6", + "prost-types 0.12.6", "protobuf 3.4.0", "schemars", "serde", @@ -1152,8 +1169,8 @@ dependencies = [ "chrono", "cosmwasm-std", "osmosis-std-derive", - "prost 0.12.4", - "prost-types 0.12.4", + "prost 0.12.6", + "prost-types 0.12.6", "schemars", "serde", "serde-cw-value", @@ -1196,9 +1213,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro2" -version = "1.0.82" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" dependencies = [ "unicode-ident", ] @@ -1215,12 +1232,12 @@ dependencies = [ [[package]] name = "prost" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f5d036824e4761737860779c906171497f6d55681139d8312388f8fe398922" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ "bytes", - "prost-derive 0.12.5", + "prost-derive 0.12.6", ] [[package]] @@ -1238,15 +1255,15 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.12.5" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9554e3ab233f0a932403704f1a1d08c30d5ccd931adfdfa1e8b5a19b52c1d55a" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.66", ] [[package]] @@ -1260,11 +1277,11 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3235c33eb02c1f1e212abdbe34c78b264b038fb58ca612664343271e36e55ffe" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" dependencies = [ - "prost 0.12.4", + "prost 0.12.6", ] [[package]] @@ -1344,9 +1361,9 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "schemars" -version = "0.8.19" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6e7ed6919cb46507fb01ff1654309219f62b4d603822501b0b80d42f6f21ef" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" dependencies = [ "dyn-clone", "schemars_derive", @@ -1356,14 +1373,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.19" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185f2b7aa7e02d418e453790dde16890256bbd2bcd04b7dc5348811052b53f49" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.61", + "syn 2.0.66", ] [[package]] @@ -1388,9 +1405,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.201" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] @@ -1433,24 +1450,24 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.201" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.66", ] [[package]] name = "serde_derive_internals" -version = "0.29.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.66", ] [[package]] @@ -1543,7 +1560,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.61", + "syn 2.0.66", ] [[package]] @@ -1574,9 +1591,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.61" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", @@ -1593,8 +1610,8 @@ dependencies = [ "flex-error", "num-derive", "num-traits", - "prost 0.12.4", - "prost-types 0.12.4", + "prost 0.12.6", + "prost-types 0.12.6", "serde", "serde_bytes", "subtle-encoding", @@ -1619,7 +1636,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.66", ] [[package]] @@ -1630,28 +1647,28 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.66", "test-case-core", ] [[package]] name = "thiserror" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.66", ] [[package]] @@ -1721,6 +1738,6 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/contracts/emissions_controller/Cargo.toml b/contracts/emissions_controller/Cargo.toml index 7f6f5858..a1416cbc 100644 --- a/contracts/emissions_controller/Cargo.toml +++ b/contracts/emissions_controller/Cargo.toml @@ -18,7 +18,7 @@ library = [] [dependencies] cw2.workspace = true cw-utils.workspace = true -cosmwasm-std = { workspace = true, features = ["ibc3"] } +cosmwasm-std = { workspace = true, features = ["cosmwasm_1_1", "ibc3"] } cw-storage-plus.workspace = true cosmwasm-schema.workspace = true thiserror.workspace = true @@ -29,10 +29,13 @@ neutron-sdk = "0.10.0" serde_json = "1" [dev-dependencies] -cw-multi-test = "1" +cw-multi-test = { version = "1", features = ["cosmwasm_1_1"] } astroport-voting-escrow = { path = "../voting_escrow", version = "1", features = ["library"] } astroport-factory = { version = "1.7", features = ["library"] } astroport-pair = { version = "1.5", features = ["library"] } cw20-base = { version = "1", features = ["library"] } astroport-incentives = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "1.2", features = ["library"] } +astroport-staking = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "2.1" } +osmosis-std = "0.21.0" derivative = "2.2" +anyhow = "1" diff --git a/contracts/emissions_controller/src/error.rs b/contracts/emissions_controller/src/error.rs index 684ec66f..c3216200 100644 --- a/contracts/emissions_controller/src/error.rs +++ b/contracts/emissions_controller/src/error.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Coin, StdError}; +use cosmwasm_std::{CheckedFromRatioError, Coin, StdError}; use cw_utils::{ParseReplyError, PaymentError}; use neutron_sdk::NeutronError; use thiserror::Error; @@ -20,6 +20,9 @@ pub enum ContractError { #[error("{0}")] ParseReplyError(#[from] ParseReplyError), + #[error("{0}")] + CheckedFromRatioError(#[from] CheckedFromRatioError), + #[error("Unauthorized")] Unauthorized {}, diff --git a/contracts/emissions_controller/src/execute.rs b/contracts/emissions_controller/src/execute.rs index 02d30d63..8ca8c981 100644 --- a/contracts/emissions_controller/src/execute.rs +++ b/contracts/emissions_controller/src/execute.rs @@ -22,9 +22,7 @@ use astroport_governance::emissions_controller::hub::{ VotedPoolInfo, }; use astroport_governance::emissions_controller::msg::ExecuteMsg; -use astroport_governance::emissions_controller::utils::{ - check_lp_token, get_voting_power, query_incentives_addr, -}; +use astroport_governance::emissions_controller::utils::{check_lp_token, get_voting_power}; use astroport_governance::utils::check_contract_supports_channel; use astroport_governance::voting_escrow; @@ -33,8 +31,9 @@ use crate::state::{ CONFIG, OUTPOSTS, OWNERSHIP_PROPOSAL, POOLS_WHITELIST, TUNE_INFO, USER_INFO, VOTED_POOLS, }; use crate::utils::{ - astro_emissions_curve, build_emission_ibc_msg, determine_outpost_prefix, get_epoch_start, - get_outpost_prefix, min_ntrn_ibc_fee, raw_emissions_to_schedules, validate_outpost_prefix, + build_emission_ibc_msg, determine_outpost_prefix, get_epoch_start, get_outpost_prefix, + min_ntrn_ibc_fee, raw_emissions_to_schedules, simulate_tune, validate_outpost_prefix, + TuneResult, }; /// Exposes all the execute functions available in the contract. @@ -161,12 +160,16 @@ pub fn execute( pools_per_outpost: pools_limit, whitelisting_fee, fee_receiver, + emissions_multiple, + max_astro, } => update_config( deps.into_empty(), info, pools_limit, whitelisting_fee, fee_receiver, + emissions_multiple, + max_astro, ), }, } @@ -570,38 +573,24 @@ pub fn tune_pools( ContractError::TuneCooldown(tune_info.tune_ts + EPOCH_LENGTH) ); + let config = CONFIG.load(deps.storage)?; + let ibc_fee = min_ntrn_ibc_fee(deps.as_ref())?; + let deps = deps.into_empty(); + let voted_pools = VOTED_POOLS .keys(deps.storage, None, None, Order::Ascending) .collect::>>()?; - let outposts = OUTPOSTS .range(deps.storage, None, None, Order::Ascending) .collect::>>()?; - let epoch_start = get_epoch_start(block_ts); - // Determine outpost prefix and filter out non-outpost pools. - let mut candidates = voted_pools - .iter() - .filter_map(|pool| get_outpost_prefix(pool, &outposts).map(|prefix| (prefix, pool))) - .map(|(prefix, pool)| { - let pool_vp = VOTED_POOLS - .may_load_at_height( - deps.storage, - pool, - epoch_start, // We need to get the VP at the begging of the epoch - )? - .map(|info| info.voting_power) - .unwrap_or_default(); - Ok((prefix, (pool, pool_vp))) - }) - .collect::>>()?; - - candidates.sort_by( - |(_, (_, a)), (_, (_, b))| b.cmp(a), // Sort in descending order - ); + let TuneResult { + candidates, + new_emissions_state, + next_pools_grouped, + } = simulate_tune(deps.as_ref(), &voted_pools, &outposts, epoch_start, &config)?; - let config = CONFIG.load(deps.storage)?; let total_pool_limit = config.pools_per_outpost as usize * outposts.len(); // If candidates list size is more than the total pool number limit, @@ -635,47 +624,7 @@ pub fn tune_pools( POOLS_WHITELIST.save(deps.storage, &new_whitelist.into_iter().collect())?; } - let astro_for_the_next_period = astro_emissions_curve(); - - // Total voting power of all selected pools - let total_selected_vp = candidates - .iter() - .take(total_pool_limit) - .fold(Uint128::zero(), |acc, (_, (_, vp))| acc + vp); - let mut next_pools = candidates - .into_iter() - .take(total_pool_limit) - .map(|(prefix, (pool, pool_vp))| { - let astro_for_pool = - astro_for_the_next_period.multiply_ratio(pool_vp, total_selected_vp); - (prefix, (pool.clone(), astro_for_pool)) - }) - .collect_vec(); - - // Add astro pools for each registered outpost - next_pools.extend(outposts.iter().filter_map(|(prefix, outpost)| { - outpost.astro_pool_config.as_ref().map(|astro_pool_config| { - ( - prefix.clone(), - ( - astro_pool_config.astro_pool.clone(), - astro_pool_config.constant_emissions, - ), - ) - }) - })); - - let ibc_fee = min_ntrn_ibc_fee(deps.as_ref())?; - - let next_pools_grouped: HashMap<_, _> = next_pools - .into_iter() - .into_group_map() - .into_iter() - .collect(); - let mut attrs = vec![attr("action", "tune_pools")]; - - let deps = deps.into_empty(); let mut outpost_emissions_statuses = HashMap::new(); let setup_pools_msgs = next_pools_grouped .iter() @@ -693,21 +642,8 @@ pub fn tune_pools( outpost_emissions_statuses.insert(prefix.clone(), OutpostStatus::InProgress); build_emission_ibc_msg(&env, params, &ibc_fee, astro_funds, &schedules)? } else { - let incentives_contract = query_incentives_addr(deps.querier, &config.factory)?; - // Ensure on the Hub that all LP tokens are valid. - // Otherwise, keep ASTRO directed to invalid pools on the emissions controller. - let schedules = schedules - .into_iter() - .filter(|(pool, _)| { - determine_asset_info(pool, deps.api) - .and_then(|maybe_lp| { - check_lp_token(deps.querier, &config.factory, &maybe_lp) - }) - .is_ok() - }) - .collect_vec(); let incentives_msg = incentives::ExecuteMsg::IncentivizeMany(schedules); - wasm_execute(incentives_contract, &incentives_msg, vec![astro_funds])?.into() + wasm_execute(&config.incentives_addr, &incentives_msg, vec![astro_funds])?.into() }; attrs.push(attr("outpost", prefix)); @@ -727,6 +663,7 @@ pub fn tune_pools( tune_ts: epoch_start, pools_grouped: next_pools_grouped, outpost_emissions_statuses, + emissions_state: new_emissions_state, }, block_ts, )?; @@ -744,6 +681,8 @@ pub fn update_config( pools_limit: Option, whitelisting_fee: Option, fee_receiver: Option, + emissions_multiple: Option, + max_astro: Option, ) -> Result, ContractError> { nonpayable(&info)?; let mut config = CONFIG.load(deps.storage)?; @@ -767,6 +706,21 @@ pub fn update_config( config.fee_receiver = deps.api.addr_validate(&fee_receiver)?; } + if let Some(emissions_multiple) = emissions_multiple { + attrs.push(attr( + "new_emissions_multiple", + emissions_multiple.to_string(), + )); + config.emissions_multiple = emissions_multiple; + } + + if let Some(max_astro) = max_astro { + attrs.push(attr("new_max_astro", max_astro.to_string())); + config.max_astro = max_astro; + } + + config.validate()?; + CONFIG.save(deps.storage, &config)?; Ok(Response::default().add_attributes(attrs)) diff --git a/contracts/emissions_controller/src/instantiate.rs b/contracts/emissions_controller/src/instantiate.rs index a3fcbf03..f90d5062 100644 --- a/contracts/emissions_controller/src/instantiate.rs +++ b/contracts/emissions_controller/src/instantiate.rs @@ -3,20 +3,21 @@ use astroport::asset::validate_native_denom; use cosmwasm_std::entry_point; use cosmwasm_std::{ ensure, to_json_binary, Addr, DepsMut, Env, MessageInfo, Reply, Response, StdError, SubMsg, - SubMsgResponse, SubMsgResult, WasmMsg, + SubMsgResponse, SubMsgResult, Uint128, WasmMsg, }; use cw2::set_contract_version; use cw_utils::parse_instantiate_response_data; use neutron_sdk::bindings::msg::NeutronMsg; use neutron_sdk::bindings::query::NeutronQuery; -use astroport_governance::emissions_controller::hub::HubInstantiateMsg; use astroport_governance::emissions_controller::hub::{Config, TuneInfo}; +use astroport_governance::emissions_controller::hub::{EmissionsState, HubInstantiateMsg}; +use astroport_governance::emissions_controller::utils::query_incentives_addr; use astroport_governance::voting_escrow; use crate::error::ContractError; use crate::state::{CONFIG, POOLS_WHITELIST, TUNE_INFO}; -use crate::utils::get_epoch_start; +use crate::utils::{get_epoch_start, get_xastro_rate_and_share}; /// Contract name that is used for migration. pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); @@ -33,24 +34,47 @@ pub fn instantiate( _info: MessageInfo, msg: HubInstantiateMsg, ) -> Result, ContractError> { + let deps = deps.into_empty(); + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; validate_native_denom(&msg.astro_denom)?; + let factory = deps.api.addr_validate(&msg.factory)?; + + let staking = + if msg.xastro_denom.starts_with("factory/") && msg.xastro_denom.ends_with("/xASTRO") { + deps.api + .addr_validate(msg.xastro_denom.split('/').nth(1).unwrap()) + } else { + Err(StdError::generic_err(format!( + "Invalid xASTRO denom {}", + msg.xastro_denom + ))) + }?; + let config = Config { owner: deps.api.addr_validate(&msg.owner)?, vxastro: Addr::unchecked(""), - factory: deps.api.addr_validate(&msg.factory)?, + incentives_addr: query_incentives_addr(deps.querier, &factory)?, + factory, astro_denom: msg.astro_denom.to_string(), pools_per_outpost: msg.pools_per_outpost, whitelisting_fee: msg.whitelisting_fee, fee_receiver: deps.api.addr_validate(&msg.fee_receiver)?, whitelist_threshold: msg.whitelist_threshold, + emissions_multiple: msg.emissions_multiple, + max_astro: msg.max_astro, + staking, + xastro_denom: msg.xastro_denom.clone(), }; config.validate()?; CONFIG.save(deps.storage, &config)?; + // Query dynamic emissions curve state + let (xastro_rate, _) = get_xastro_rate_and_share(deps.querier, &config)?; + // Set tune_ts just for safety so the first tuning could happen in 2 weeks TUNE_INFO.save( deps.storage, @@ -58,17 +82,21 @@ pub fn instantiate( tune_ts: get_epoch_start(env.block.time.seconds()), pools_grouped: Default::default(), outpost_emissions_statuses: Default::default(), + emissions_state: EmissionsState { + xastro_rate, + collected_astro: msg.collected_astro, + emissions_amount: Uint128::zero(), + }, }, env.block.time.seconds(), )?; // Instantiate vxASTRO contract - validate_native_denom(&msg.vxastro_deposit_denom)?; let init_vxastro_msg = WasmMsg::Instantiate { admin: Some(msg.owner), code_id: msg.vxastro_code_id, msg: to_json_binary(&voting_escrow::InstantiateMsg { - deposit_denom: msg.vxastro_deposit_denom.to_string(), + deposit_denom: msg.xastro_denom.to_string(), emissions_controller: env.contract.address.to_string(), marketing: msg.vxastro_marketing_info, })?, diff --git a/contracts/emissions_controller/src/query.rs b/contracts/emissions_controller/src/query.rs index 033b540e..85a40fff 100644 --- a/contracts/emissions_controller/src/query.rs +++ b/contracts/emissions_controller/src/query.rs @@ -1,3 +1,5 @@ +use std::collections::{HashMap, HashSet}; + #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{to_json_binary, Binary, Deps, Env, Order, StdError, StdResult}; @@ -6,13 +8,17 @@ use itertools::Itertools; use neutron_sdk::bindings::query::NeutronQuery; use astroport_governance::emissions_controller::consts::MAX_PAGE_LIMIT; -use astroport_governance::emissions_controller::hub::{QueryMsg, UserInfoResponse}; +use astroport_governance::emissions_controller::hub::{ + QueryMsg, SimulateTuneResponse, UserInfoResponse, +}; +use crate::error::ContractError; use crate::state::{CONFIG, OUTPOSTS, POOLS_WHITELIST, TUNE_INFO, USER_INFO, VOTED_POOLS}; +use crate::utils::{get_epoch_start, simulate_tune}; /// Expose available contract queries. #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result { match msg { QueryMsg::UserInfo { user, timestamp } => { let block_time = env.block.time.seconds(); @@ -50,7 +56,7 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { let block_time = env.block.time.seconds(); @@ -60,9 +66,9 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult TUNE_INFO.may_load_at_height(deps.storage, timestamp), }? .ok_or_else(|| StdError::generic_err(format!("Tune info not found at {timestamp}")))?; - to_json_binary(&tune_info) + Ok(to_json_binary(&tune_info)?) } - QueryMsg::Config {} => to_json_binary(&CONFIG.load(deps.storage)?), + QueryMsg::Config {} => Ok(to_json_binary(&CONFIG.load(deps.storage)?)?), QueryMsg::VotedPool { pool, timestamp } => { let block_time = env.block.time.seconds(); let timestamp = timestamp.unwrap_or(block_time); @@ -71,7 +77,7 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult VOTED_POOLS.may_load_at_height(deps.storage, &pool, timestamp), }? .ok_or_else(|| StdError::generic_err(format!("Voted pool not found at {timestamp}")))?; - to_json_binary(&voted_pool) + Ok(to_json_binary(&voted_pool)?) } QueryMsg::VotedPoolsList { limit, start_after } => { let limit = limit.unwrap_or(MAX_PAGE_LIMIT) as usize; @@ -84,14 +90,32 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult>>()?; - to_json_binary(&voted_pools) + Ok(to_json_binary(&voted_pools)?) } QueryMsg::ListOutposts {} => { let outposts = OUTPOSTS .range(deps.storage, None, None, Order::Ascending) .collect::>>()?; - to_json_binary(&outposts) + Ok(to_json_binary(&outposts)?) + } + QueryMsg::QueryWhitelist {} => Ok(to_json_binary(&POOLS_WHITELIST.load(deps.storage)?)?), + QueryMsg::SimulateTune {} => { + let deps = deps.into_empty(); + + let voted_pools = VOTED_POOLS + .keys(deps.storage, None, None, Order::Ascending) + .collect::>>()?; + let outposts = OUTPOSTS + .range(deps.storage, None, None, Order::Ascending) + .collect::>>()?; + let epoch_start = get_epoch_start(env.block.time.seconds()); + let config = CONFIG.load(deps.storage)?; + + let tune_result = simulate_tune(deps, &voted_pools, &outposts, epoch_start, &config)?; + Ok(to_json_binary(&SimulateTuneResponse { + new_emissions_state: tune_result.new_emissions_state, + next_pools_grouped: tune_result.next_pools_grouped, + })?) } - QueryMsg::QueryWhitelist {} => to_json_binary(&POOLS_WHITELIST.load(deps.storage)?), } } diff --git a/contracts/emissions_controller/src/state.rs b/contracts/emissions_controller/src/state.rs index ef927c1c..2375fc98 100644 --- a/contracts/emissions_controller/src/state.rs +++ b/contracts/emissions_controller/src/state.rs @@ -7,7 +7,6 @@ use astroport_governance::emissions_controller::hub::{ /// Stores config at the given key. pub const CONFIG: Item = Item::new("config"); - /// Contains a proposal to change contract ownership pub const OWNERSHIP_PROPOSAL: Item = Item::new("ownership_proposal"); /// Array of pools eligible for voting. diff --git a/contracts/emissions_controller/src/utils.rs b/contracts/emissions_controller/src/utils.rs index 024f0370..8b3c7a5b 100644 --- a/contracts/emissions_controller/src/utils.rs +++ b/contracts/emissions_controller/src/utils.rs @@ -1,11 +1,12 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; -use astroport::asset::Asset; +use astroport::asset::{determine_asset_info, Asset}; use astroport::incentives::{IncentivesSchedule, InputSchedule}; use cosmwasm_schema::cw_serde; use cosmwasm_schema::serde::Serialize; use cosmwasm_std::{ - coin, Coin, CosmosMsg, Deps, Env, Order, StdError, StdResult, Storage, Uint128, + coin, Coin, CosmosMsg, Decimal, Deps, Env, Order, QuerierWrapper, StdError, StdResult, Storage, + Uint128, }; use itertools::Itertools; use neutron_sdk::bindings::msg::{IbcFee, NeutronMsg}; @@ -16,11 +17,14 @@ use neutron_sdk::sudo::msg::RequestPacketTimeoutHeight; use astroport_governance::emissions_controller::consts::{ EPOCHS_START, EPOCH_LENGTH, FEE_DENOM, IBC_TIMEOUT, LP_SUBDENOM, }; -use astroport_governance::emissions_controller::hub::{OutpostInfo, OutpostParams}; +use astroport_governance::emissions_controller::hub::{ + Config, EmissionsState, OutpostInfo, OutpostParams, +}; use astroport_governance::emissions_controller::outpost::OutpostMsg; +use astroport_governance::emissions_controller::utils::check_lp_token; use crate::error::ContractError; -use crate::state::OUTPOSTS; +use crate::state::{OUTPOSTS, TUNE_INFO, VOTED_POOLS}; /// Determine outpost prefix from address or denom. pub fn determine_outpost_prefix(value: &str) -> Option { @@ -205,9 +209,156 @@ pub fn get_epoch_start(timestamp: u64) -> u64 { } } -// TODO: Implement dynamic emissions curve -pub fn astro_emissions_curve() -> Uint128 { - Uint128::new(100_000_000_000) +/// Query the staking contract ASTRO balance and xASTRO total supply and derive xASTRO staking rate. +/// Return (staking rate, total xASTRO supply). +pub fn get_xastro_rate_and_share( + querier: QuerierWrapper, + config: &Config, +) -> Result<(Decimal, Uint128), ContractError> { + let total_deposit = querier + .query_balance(&config.staking, &config.astro_denom)? + .amount; + let total_shares = querier.query_supply(&config.xastro_denom)?.amount; + let rate = Decimal::checked_from_ratio(total_deposit, total_shares)?; + + Ok((rate, total_shares)) +} + +/// Calculate the number of ASTRO tokens collected by the staking contract from the previous epoch +/// and derive emissions for the upcoming epoch. +/// +/// Calculate two-epochs EMA by the following formula: +/// (V_n-1 * 2/3 + V_n-2 * 1/3), +/// where V_n is the collected ASTRO at epoch n, n is the current epoch (a starting one). +/// +/// Dynamic emissions formula is: +/// next emissions = MAX(MIN(max_astro, V_n-2 * emissions_multiple), MIN(max_astro, two-epochs EMA)) +pub fn astro_emissions_curve( + deps: Deps, + emissions_state: EmissionsState, + config: &Config, +) -> Result { + let (actual_rate, shares) = get_xastro_rate_and_share(deps.querier, config)?; + let growth = actual_rate - emissions_state.xastro_rate; + let collected_astro = shares * growth; + + let two_thirds = Decimal::from_ratio(2u8, 3u8); + let one_third = Decimal::from_ratio(1u8, 3u8); + let ema = collected_astro * two_thirds + emissions_state.collected_astro * one_third; + + let min_1 = (emissions_state.collected_astro * config.emissions_multiple).min(config.max_astro); + let min_2 = (ema * config.emissions_multiple).min(config.max_astro); + + Ok(EmissionsState { + xastro_rate: actual_rate, + collected_astro, + emissions_amount: min_1.max(min_2), + }) +} + +pub struct TuneResult { + pub candidates: Vec<(String, (String, Uint128))>, + pub new_emissions_state: EmissionsState, + pub next_pools_grouped: HashMap>, +} + +/// Simulate the next tune outcome based on the voting power distribution at given timestamp. +/// In actual tuning context (function tune_pools) timestamp must match current epoch start. +pub fn simulate_tune( + deps: Deps, + voted_pools: &HashSet, + outposts: &HashMap, + timestamp: u64, + config: &Config, +) -> Result { + // Determine outpost prefix and filter out non-outpost pools. + let mut candidates = voted_pools + .iter() + .filter_map(|pool| get_outpost_prefix(pool, outposts).map(|prefix| (prefix, pool.clone()))) + .map(|(prefix, pool)| { + let pool_vp = VOTED_POOLS + .may_load_at_height(deps.storage, &pool, timestamp)? + .map(|info| info.voting_power) + .unwrap_or_default(); + Ok((prefix, (pool, pool_vp))) + }) + .collect::>>()?; + + candidates.sort_by( + |(_, (_, a)), (_, (_, b))| b.cmp(a), // Sort in descending order + ); + + let total_pool_limit = config.pools_per_outpost as usize * outposts.len(); + + let tune_info = TUNE_INFO.load(deps.storage)?; + + let new_emissions_state = astro_emissions_curve(deps, tune_info.emissions_state, config)?; + + // Total voting power of all selected pools + let total_selected_vp = candidates + .iter() + .take(total_pool_limit) + .fold(Uint128::zero(), |acc, (_, (_, vp))| acc + vp); + // Calculate each pool's ASTRO emissions + let mut next_pools = candidates + .iter() + .take(total_pool_limit) + .map(|(prefix, (pool, pool_vp))| { + let astro_for_pool = new_emissions_state + .emissions_amount + .multiply_ratio(*pool_vp, total_selected_vp); + (prefix.clone(), ((*pool).clone(), astro_for_pool)) + }) + .collect_vec(); + + // Add astro pools for each registered outpost + next_pools.extend(outposts.iter().filter_map(|(prefix, outpost)| { + outpost.astro_pool_config.as_ref().map(|astro_pool_config| { + ( + prefix.clone(), + ( + astro_pool_config.astro_pool.clone(), + astro_pool_config.constant_emissions, + ), + ) + }) + })); + + let next_pools_grouped: HashMap<_, _> = next_pools + .into_iter() + .filter(|(_, (_, astro_for_pool))| !astro_for_pool.is_zero()) + .into_group_map() + .into_iter() + .filter_map(|(prefix, pools)| { + if outposts.get(&prefix).unwrap().params.is_none() { + // Ensure on the Hub that all LP tokens are valid. + // Otherwise, keep ASTRO directed to invalid pools on the emissions controller. + let pools = pools + .into_iter() + .filter(|(pool, _)| { + determine_asset_info(pool, deps.api) + .and_then(|maybe_lp| { + check_lp_token(deps.querier, &config.factory, &maybe_lp) + }) + .is_ok() + }) + .collect_vec(); + if !pools.is_empty() { + Some((prefix, pools)) + } else { + None + } + } else { + Some((prefix, pools)) + } + }) + .collect(); + + Ok(TuneResult { + candidates, + new_emissions_state, + next_pools_grouped, + }) } #[cfg(test)] diff --git a/contracts/emissions_controller/tests/common/contracts.rs b/contracts/emissions_controller/tests/common/contracts.rs index dc82a3f7..2e169bff 100644 --- a/contracts/emissions_controller/tests/common/contracts.rs +++ b/contracts/emissions_controller/tests/common/contracts.rs @@ -1,7 +1,9 @@ use std::fmt::Debug; use cosmwasm_schema::schemars::JsonSchema; -use cosmwasm_std::{CustomMsg, CustomQuery}; +use cosmwasm_std::{ + Binary, CustomMsg, CustomQuery, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, +}; use cw_multi_test::{Contract, ContractWrapper}; use neutron_sdk::bindings::msg::NeutronMsg; use neutron_sdk::bindings::query::NeutronQuery; @@ -83,3 +85,32 @@ pub fn emissions_controller() -> Box> { .with_reply_empty(astroport_emissions_controller::instantiate::reply), ) } + +pub fn staking_contract() -> Box> +where + T: CustomMsg + Clone + Debug + PartialEq + JsonSchema + 'static, + C: CustomQuery + for<'de> cosmwasm_schema::serde::Deserialize<'de> + 'static, +{ + Box::new( + ContractWrapper::new_with_empty( + astroport_staking::contract::execute, + astroport_staking::contract::instantiate, + astroport_staking::contract::query, + ) + .with_reply_empty(astroport_staking::contract::reply), + ) +} + +pub fn tracker_contract() -> Box> +where + T: CustomMsg + Clone + Debug + PartialEq + JsonSchema + 'static, + C: CustomQuery + for<'de> cosmwasm_schema::serde::Deserialize<'de> + 'static, +{ + Box::new(ContractWrapper::new_with_empty( + |_: DepsMut, _: Env, _: MessageInfo, _: Empty| -> StdResult { unimplemented!() }, + |_: DepsMut, _: Env, _: MessageInfo, _: Empty| -> StdResult { + Ok(Response::default()) + }, + |_: Deps, _: Env, _: Empty| -> StdResult { unimplemented!() }, + )) +} diff --git a/contracts/emissions_controller/tests/common/helper.rs b/contracts/emissions_controller/tests/common/helper.rs index 8dc66235..c08ab644 100644 --- a/contracts/emissions_controller/tests/common/helper.rs +++ b/contracts/emissions_controller/tests/common/helper.rs @@ -1,16 +1,16 @@ use astroport::asset::{AssetInfo, PairInfo}; use astroport::factory::{PairConfig, PairType}; use astroport::incentives::RewardInfo; -use astroport::token::Logo; -use astroport::{factory, incentives}; +use astroport::token::{Logo, MinterResponse}; +use astroport::{factory, incentives, staking}; use cosmwasm_std::{ - coin, coins, Addr, BlockInfo, Coin, Decimal, Empty, MemoryStorage, StdResult, Timestamp, - Uint128, + coin, coins, from_json, to_json_binary, Addr, BlockInfo, Coin, Decimal, Empty, MemoryStorage, + StdResult, Timestamp, Uint128, }; use cw_multi_test::error::AnyResult; use cw_multi_test::{ no_init, App, AppBuilder, AppResponse, BankKeeper, BankSudo, DistributionKeeper, Executor, - MockAddressGenerator, MockApiBech32, StakeKeeper, WasmKeeper, + GovFailingModule, MockAddressGenerator, MockApiBech32, StakeKeeper, WasmKeeper, }; use derivative::Derivative; use itertools::Itertools; @@ -19,7 +19,8 @@ use neutron_sdk::bindings::query::NeutronQuery; use astroport_governance::emissions_controller::consts::EPOCHS_START; use astroport_governance::emissions_controller::hub::{ - HubInstantiateMsg, HubMsg, OutpostInfo, UserInfoResponse, VotedPoolInfo, + EmissionsState, HubInstantiateMsg, HubMsg, OutpostInfo, SimulateTuneResponse, TuneInfo, + UserInfoResponse, VotedPoolInfo, }; use astroport_governance::voting_escrow::UpdateMarketingInfo; use astroport_governance::{emissions_controller, voting_escrow}; @@ -27,6 +28,7 @@ use astroport_governance::{emissions_controller, voting_escrow}; use crate::common::contracts::*; use crate::common::ibc_module::IbcMockModule; use crate::common::neutron_module::MockNeutronModule; +use crate::common::stargate::StargateModule; pub type NeutronApp = App< BankKeeper, @@ -37,6 +39,8 @@ pub type NeutronApp = App< StakeKeeper, DistributionKeeper, IbcMockModule, + GovFailingModule, + StargateModule, >; fn mock_ntrn_app() -> NeutronApp { @@ -46,6 +50,7 @@ fn mock_ntrn_app() -> NeutronApp { .with_api(api) .with_wasm(WasmKeeper::new().with_address_generator(MockAddressGenerator)) .with_ibc(IbcMockModule) + .with_stargate(StargateModule) .with_block(BlockInfo { height: 1, time: Timestamp::from_seconds(EPOCHS_START), @@ -63,6 +68,7 @@ pub struct ControllerHelper { pub astro: String, pub xastro: String, pub factory: Addr, + pub staking: Addr, pub vxastro: Addr, pub whitelisting_fee: Coin, pub emission_controller: Addr, @@ -74,7 +80,6 @@ impl ControllerHelper { let mut app = mock_ntrn_app(); let owner = app.api().addr_make("owner"); let astro_denom = "astro"; - let xastro_denom = "xastro"; let vxastro_code_id = app.store_code(vxastro_contract()); let emissions_controller_code_id = app.store_code(emissions_controller()); @@ -82,6 +87,8 @@ impl ControllerHelper { let xyk_code_id = app.store_code(pair_contract()); let factory_code_id = app.store_code(factory_contract()); let incentives_code_id = app.store_code(incentives_contract()); + let staking_code_id = app.store_code(staking_contract()); + let tracker_code_id = app.store_code(tracker_contract()); let factory = app .instantiate_contract( @@ -142,6 +149,47 @@ impl ControllerHelper { ) .unwrap(); + let astro_staking_amount = coins(1_000000, astro_denom); + app.sudo( + BankSudo::Mint { + to_address: owner.to_string(), + amount: astro_staking_amount.clone(), + } + .into(), + ) + .unwrap(); + + let msg = staking::InstantiateMsg { + deposit_token_denom: astro_denom.to_string(), + tracking_admin: owner.to_string(), + tracking_code_id: tracker_code_id, + token_factory_addr: app.api().addr_make("token_factory").to_string(), + }; + let staking = app + .instantiate_contract( + staking_code_id, + owner.clone(), + &msg, + &[], + String::from("Astroport Staking"), + None, + ) + .unwrap(); + let xastro_denom = app + .wrap() + .query_wasm_smart::(&staking, &staking::QueryMsg::Config {}) + .unwrap() + .xastro_denom; + + // Lock some ASTRO in staking to get initial staking rate + app.execute_contract( + owner.clone(), + staking.clone(), + &staking::ExecuteMsg::Enter { receiver: None }, + &astro_staking_amount, + ) + .unwrap(); + let whitelisting_fee = coin(1_000_000, astro_denom); let emission_controller = app .instantiate_contract( @@ -156,13 +204,16 @@ impl ControllerHelper { marketing: None, logo: Some(Logo::Url("".to_string())), }), - vxastro_deposit_denom: xastro_denom.to_string(), + xastro_denom: xastro_denom.clone(), factory: factory.to_string(), astro_denom: astro_denom.to_string(), pools_per_outpost: 5, whitelisting_fee: whitelisting_fee.clone(), fee_receiver: app.api().addr_make("fee_receiver").to_string(), whitelist_threshold: Decimal::percent(1), + emissions_multiple: Decimal::percent(80), + max_astro: 1_400_000_000_000u128.into(), + collected_astro: 334_000_000_000u128.into(), }, &[], "label", @@ -182,9 +233,10 @@ impl ControllerHelper { let helper = Self { app, owner, - xastro: xastro_denom.to_string(), + xastro: xastro_denom.clone(), astro: astro_denom.to_string(), factory, + staking, vxastro, whitelisting_fee, emission_controller, @@ -205,14 +257,27 @@ impl ControllerHelper { ) } - pub fn lock(&mut self, user: &Addr, amount: u128) -> AnyResult { - let funds = coins(amount, &self.xastro); + pub fn enter_staking(&mut self, user: &Addr, amount: u128) -> AnyResult { + let funds = coins(amount, &self.astro); self.mint_tokens(user, &funds).unwrap(); + self.app.execute_contract( + user.clone(), + self.staking.clone(), + &staking::ExecuteMsg::Enter { receiver: None }, + &funds, + ) + } + + pub fn lock(&mut self, user: &Addr, amount: u128) -> AnyResult { + let data = self.enter_staking(user, amount)?.data.unwrap(); + let mint_amount = from_json::(&data) + .unwrap() + .xastro_amount; self.app.execute_contract( user.clone(), self.vxastro.clone(), &voting_escrow::ExecuteMsg::Lock { receiver: None }, - &funds, + &coins(mint_amount.u128(), &self.xastro), ) } @@ -342,6 +407,25 @@ impl ControllerHelper { ) } + pub fn query_current_emissions(&self) -> StdResult { + self.query_tune_info(None).map(|x| x.emissions_state) + } + + pub fn query_simulate_tune(&self) -> StdResult { + self.app + .wrap() + .query_wasm_smart::( + &self.emission_controller, + &emissions_controller::hub::QueryMsg::SimulateTune {}, + ) + .map(|mut x| { + x.next_pools_grouped + .iter_mut() + .for_each(|(_, array)| array.sort()); + x + }) + } + pub fn query_pool_vp(&self, pool: &str, timestamp: Option) -> StdResult { self.query_voted_pool(pool, timestamp) .map(|x| x.voting_power) @@ -400,14 +484,54 @@ impl ControllerHelper { ) } - pub fn query_tune_info( - &self, - timestamp: Option, - ) -> StdResult { - self.app.wrap().query_wasm_smart( - &self.emission_controller, - &emissions_controller::hub::QueryMsg::TuneInfo { timestamp }, - ) + pub fn query_tune_info(&self, timestamp: Option) -> StdResult { + self.app + .wrap() + .query_wasm_smart::( + &self.emission_controller, + &emissions_controller::hub::QueryMsg::TuneInfo { timestamp }, + ) + .map(|mut x| { + x.pools_grouped + .iter_mut() + .for_each(|(_, array)| array.sort()); + x + }) + } + + pub fn reset_astro_reward(&mut self, lp_token: &Addr) -> AnyResult { + // Mocking LP provide and depositing to incentives contract + // NOTE: + // it doesn't really provide assets to the pair + // but this is fine in the context of emissions controller + self.app + .wrap() + .query_wasm_smart(lp_token, &cw20_base::msg::QueryMsg::Minter {}) + .map_err(Into::into) + .and_then(|info: MinterResponse| { + self.app.execute_contract( + Addr::unchecked(info.minter), + lp_token.clone(), + &cw20_base::msg::ExecuteMsg::Mint { + recipient: self.owner.to_string(), + amount: 10000u128.into(), + }, + &[], + ) + }) + .and_then(|_| { + self.app.execute_contract( + self.owner.clone(), + lp_token.clone(), + &cw20_base::msg::ExecuteMsg::Send { + contract: self.incentives.to_string(), + amount: 10000u128.into(), + msg: to_json_binary(&incentives::Cw20Msg::Deposit { recipient: None }) + .unwrap(), + }, + &[], + ) + }) } pub fn query_rewards(&self, pool: impl Into) -> StdResult> { diff --git a/contracts/emissions_controller/tests/common/mod.rs b/contracts/emissions_controller/tests/common/mod.rs index 782eead8..5849a9e2 100644 --- a/contracts/emissions_controller/tests/common/mod.rs +++ b/contracts/emissions_controller/tests/common/mod.rs @@ -1,4 +1,5 @@ -pub mod contracts; +mod contracts; pub mod helper; mod ibc_module; mod neutron_module; +mod stargate; diff --git a/contracts/emissions_controller/tests/common/stargate.rs b/contracts/emissions_controller/tests/common/stargate.rs new file mode 100644 index 00000000..ae9e99a2 --- /dev/null +++ b/contracts/emissions_controller/tests/common/stargate.rs @@ -0,0 +1,121 @@ +use anyhow::anyhow; +use cosmwasm_schema::serde::de::DeserializeOwned; +use cosmwasm_std::{ + coin, Addr, Api, BankMsg, Binary, BlockInfo, CustomMsg, CustomQuery, Empty, Querier, Storage, + SubMsgResponse, +}; +use cw_multi_test::error::AnyResult; +use cw_multi_test::{ + AppResponse, BankSudo, CosmosRouter, Module, Stargate, StargateMsg, StargateQuery, +}; +use osmosis_std::types::osmosis::tokenfactory::v1beta1::{ + MsgBurn, MsgCreateDenom, MsgCreateDenomResponse, MsgMint, MsgSetBeforeSendHook, + MsgSetDenomMetadata, +}; + +#[derive(Default)] +pub struct StargateModule; + +impl Stargate for StargateModule {} + +impl Module for StargateModule { + type ExecT = StargateMsg; + type QueryT = StargateQuery; + type SudoT = Empty; + + fn execute( + &self, + api: &dyn Api, + storage: &mut dyn Storage, + router: &dyn CosmosRouter, + block: &BlockInfo, + sender: Addr, + msg: Self::ExecT, + ) -> AnyResult + where + ExecC: CustomMsg + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, + { + match msg.type_url.as_str() { + MsgCreateDenom::TYPE_URL => { + let tf_msg: MsgCreateDenom = msg.value.try_into()?; + let submsg_response = SubMsgResponse { + events: vec![], + data: Some( + MsgCreateDenomResponse { + new_token_denom: format!( + "factory/{}/{}", + tf_msg.sender, tf_msg.subdenom + ), + } + .into(), + ), + }; + Ok(submsg_response.into()) + } + MsgMint::TYPE_URL => { + let tf_msg: MsgMint = msg.value.try_into()?; + let mint_coins = tf_msg + .amount + .expect("Empty amount in tokenfactory MsgMint!"); + let cw_coin = coin(mint_coins.amount.parse()?, mint_coins.denom); + let bank_sudo = BankSudo::Mint { + to_address: tf_msg.mint_to_address.clone(), + amount: vec![cw_coin.clone()], + }; + + router.sudo(api, storage, block, bank_sudo.into()) + } + MsgBurn::TYPE_URL => { + let tf_msg: MsgBurn = msg.value.try_into()?; + let burn_coins = tf_msg + .amount + .expect("Empty amount in tokenfactory MsgBurn!"); + let cw_coin = coin(burn_coins.amount.parse()?, burn_coins.denom); + let burn_msg = BankMsg::Burn { + amount: vec![cw_coin.clone()], + }; + + router.execute( + api, + storage, + block, + Addr::unchecked(&tf_msg.sender), + burn_msg.into(), + ) + } + MsgSetDenomMetadata::TYPE_URL => Ok(AppResponse::default()), + MsgSetBeforeSendHook::TYPE_URL => Ok(AppResponse::default()), + _ => Err(anyhow!( + "Unexpected exec msg {} from {sender:?}", + msg.type_url + )), + } + } + + fn query( + &self, + _api: &dyn Api, + _storage: &dyn Storage, + _querier: &dyn Querier, + _block: &BlockInfo, + _request: Self::QueryT, + ) -> AnyResult { + unimplemented!("Stargate queries are not implemented") + } + + fn sudo( + &self, + _api: &dyn Api, + _storage: &mut dyn Storage, + _router: &dyn CosmosRouter, + _block: &BlockInfo, + _msg: Self::SudoT, + ) -> AnyResult + where + ExecC: CustomMsg + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, + { + unimplemented!("Stargate sudo is not implemented") + } +} diff --git a/contracts/emissions_controller/tests/emissions_controller_integration.rs b/contracts/emissions_controller/tests/emissions_controller_integration.rs index aea085a8..53eac56f 100644 --- a/contracts/emissions_controller/tests/emissions_controller_integration.rs +++ b/contracts/emissions_controller/tests/emissions_controller_integration.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::str::FromStr; use astroport::asset::AssetInfo; use astroport::incentives::RewardType; @@ -13,7 +14,8 @@ use astroport_emissions_controller::utils::get_epoch_start; use astroport_governance::emissions_controller; use astroport_governance::emissions_controller::consts::{DAY, EPOCH_LENGTH, VOTE_COOLDOWN}; use astroport_governance::emissions_controller::hub::{ - AstroPoolConfig, HubMsg, OutpostInfo, OutpostParams, OutpostStatus, TuneInfo, UserInfoResponse, + AstroPoolConfig, EmissionsState, HubMsg, OutpostInfo, OutpostParams, OutpostStatus, TuneInfo, + UserInfoResponse, }; use astroport_governance::emissions_controller::msg::ExecuteMsg; use astroport_voting_escrow::state::UNLOCK_PERIOD; @@ -511,7 +513,8 @@ fn test_tune_only_hub() { .unwrap(); helper.tune(&owner).unwrap(); - let expected_rps = Decimal256::from_ratio(100_000_000_000u128 / 2, EPOCH_LENGTH); + let cur_emissions = helper.query_current_emissions().unwrap().emissions_amount; + let expected_rps = Decimal256::from_ratio(cur_emissions.u128() / 2, EPOCH_LENGTH); let rewards = helper.query_rewards(&lp_token1).unwrap(); let epoch_start = get_epoch_start(helper.app.block_info().time.seconds()); let first_epoch_start = epoch_start; @@ -553,10 +556,33 @@ fn test_tune_only_hub() { // Imagine bot executed the tune late helper.timetravel(EPOCH_LENGTH + 3 * DAY); + + // Mocking received ASTRO in staking + helper + .mint_tokens( + &helper.staking.clone(), + &coins(500_000_000_000, helper.astro.clone()), + ) + .unwrap(); + + let sim_tune_result = helper.query_simulate_tune().unwrap(); + helper.tune(&owner).unwrap(); + let actual_emissions_state = helper.query_current_emissions().unwrap(); + assert_eq!(sim_tune_result.new_emissions_state, actual_emissions_state); + let actual_tune_info = helper.query_tune_info(None).unwrap(); + assert_eq!( + sim_tune_result.next_pools_grouped, + actual_tune_info.pools_grouped, + ); + + // Reset incentives as nobody claimed rewards + helper.reset_astro_reward(&lp_token1).unwrap(); + // User didn't change his votes. Emissions were 3 days late, thus their duration is 11 days. - let expected_rps = Decimal256::from_ratio(100_000_000_000u128 / 2, EPOCH_LENGTH - 3 * DAY); + let cur_emissions = helper.query_current_emissions().unwrap().emissions_amount; + let expected_rps = Decimal256::from_ratio(cur_emissions.u128() / 2, EPOCH_LENGTH - 3 * DAY); let rewards = helper.query_rewards(&lp_token1).unwrap(); let epoch_start = get_epoch_start(helper.app.block_info().time.seconds()); assert_eq!(rewards.len(), 1); @@ -598,8 +624,8 @@ fn test_tune_only_hub() { pools_grouped: HashMap::from([( "neutron".to_string(), vec![ - (lp_token1.to_string(), Uint128::new(50000000000)), - (lp_token2.to_string(), Uint128::new(50000000000)), + (lp_token1.to_string(), Uint128::new(133333333332)), + (lp_token2.to_string(), Uint128::new(133333333332)), (astro_pool.to_string(), Uint128::new(1000000000)), ] .into_iter() @@ -607,6 +633,11 @@ fn test_tune_only_hub() { .collect(), )]), outpost_emissions_statuses: Default::default(), + emissions_state: EmissionsState { + xastro_rate: Decimal::from_str("499501.4995004995004995").unwrap(), + collected_astro: 499999999999u128.into(), + emissions_amount: 266666666665u128.into(), + }, }; assert_eq!(tune_info, expected_tune_info); @@ -621,8 +652,8 @@ fn test_tune_only_hub() { pools_grouped: HashMap::from([( "neutron".to_string(), vec![ - (lp_token1.to_string(), Uint128::new(50000000000)), - (lp_token2.to_string(), Uint128::new(50000000000)), + (lp_token1.to_string(), Uint128::new(133600000000)), + (lp_token2.to_string(), Uint128::new(133600000000)), (astro_pool.to_string(), Uint128::new(1000000000)), ] .into_iter() @@ -630,6 +661,11 @@ fn test_tune_only_hub() { .collect(), )]), outpost_emissions_statuses: Default::default(), + emissions_state: EmissionsState { + xastro_rate: Decimal::one(), + collected_astro: 0u128.into(), + emissions_amount: 267200000000u128.into(), + }, }; assert_eq!(tune_info, expected_tune_info); } @@ -702,8 +738,8 @@ fn test_tune_outpost() { pools_grouped: HashMap::from([( "osmo".to_string(), vec![ - (lp_token1.to_string(), Uint128::new(50000000000)), - (lp_token2.to_string(), Uint128::new(50000000000)), + (lp_token1.to_string(), Uint128::new(133600000000)), + (lp_token2.to_string(), Uint128::new(133600000000)), (astro_pool.to_string(), Uint128::new(1000000000)), ] .into_iter() @@ -714,6 +750,11 @@ fn test_tune_outpost() { "osmo".to_string(), OutpostStatus::InProgress, )]), + emissions_state: EmissionsState { + xastro_rate: Decimal::one(), + collected_astro: 0u128.into(), + emissions_amount: 267200000000u128.into(), + }, }; assert_eq!(tune_info, expected_tune_info); @@ -803,8 +844,8 @@ fn test_tune_outpost() { pools_grouped: HashMap::from([( "osmo".to_string(), vec![ - (lp_token1.to_string(), Uint128::new(50000000000)), - (lp_token2.to_string(), Uint128::new(50000000000)), + (lp_token1.to_string(), Uint128::new(133600000000)), + (lp_token2.to_string(), Uint128::new(133600000000)), (astro_pool.to_string(), Uint128::new(1000000000)), ] .into_iter() @@ -812,6 +853,11 @@ fn test_tune_outpost() { .collect(), )]), outpost_emissions_statuses: HashMap::from([("osmo".to_string(), OutpostStatus::Done)]), + emissions_state: EmissionsState { + xastro_rate: Decimal::one(), + collected_astro: 0u128.into(), + emissions_amount: 267200000000u128.into(), + }, }; assert_eq!(tune_info, expected_tune_info); @@ -1010,8 +1056,8 @@ fn test_lock_unlock_vxastro() { .wrap() .query_balance(bob, &helper.xastro) .unwrap(); - assert_eq!(alice_balance, coin(2_000000, "xastro")); - assert_eq!(bob_balance, coin(1_000000, "xastro")); + assert_eq!(alice_balance, coin(2_000000, &helper.xastro)); + assert_eq!(bob_balance, coin(1_000000, &helper.xastro)); } #[test] @@ -1063,7 +1109,7 @@ fn test_some_epochs() { helper .mint_tokens( &helper.emission_controller.clone(), - &coins(1000000000000, helper.astro.clone()), + &coins(100_000_000_000_000, helper.astro.clone()), ) .unwrap(); @@ -1118,6 +1164,8 @@ fn test_some_epochs() { pools_per_outpost: Some(1), whitelisting_fee: None, fee_receiver: None, + emissions_multiple: None, + max_astro: None, }), &[], ) @@ -1289,6 +1337,8 @@ fn test_update_config() { pools_per_outpost: Some(8), whitelisting_fee: Some(coin(100, "astro")), fee_receiver: Some(fee_receiver.to_string()), + emissions_multiple: Some(Decimal::percent(90)), + max_astro: Some(1_000_000u128.into()), }); let err = helper @@ -1324,10 +1374,15 @@ fn test_update_config() { vxastro: helper.vxastro.clone(), factory: helper.factory.clone(), astro_denom: helper.astro.clone(), + xastro_denom: helper.xastro.clone(), + staking: helper.staking.clone(), + incentives_addr: helper.incentives.clone(), pools_per_outpost: 8, whitelisting_fee: coin(100, "astro"), fee_receiver, whitelist_threshold: Decimal::percent(1), + emissions_multiple: Decimal::percent(90), + max_astro: 1_000_000u128.into(), } ); } diff --git a/contracts/emissions_controller_outpost/src/execute.rs b/contracts/emissions_controller_outpost/src/execute.rs index 39be8a76..79422ead 100644 --- a/contracts/emissions_controller_outpost/src/execute.rs +++ b/contracts/emissions_controller_outpost/src/execute.rs @@ -17,9 +17,7 @@ use astroport_governance::emissions_controller::consts::{IBC_TIMEOUT, MAX_POOLS_ use astroport_governance::emissions_controller::msg::ExecuteMsg; use astroport_governance::emissions_controller::msg::VxAstroIbcMsg; use astroport_governance::emissions_controller::outpost::{Config, OutpostMsg}; -use astroport_governance::emissions_controller::utils::{ - check_lp_token, get_voting_power, query_incentives_addr, -}; +use astroport_governance::emissions_controller::utils::{check_lp_token, get_voting_power}; use astroport_governance::utils::check_contract_supports_channel; use crate::error::ContractError; @@ -206,9 +204,8 @@ pub fn execute_emissions( ensure!(!schedules.is_empty(), ContractError::NoValidSchedules {}); - let incentives_contract = query_incentives_addr(deps.querier, &config.factory)?; let incentives_msg = wasm_execute( - incentives_contract, + config.incentives_addr, &incentives::ExecuteMsg::IncentivizeMany(schedules), coins(expected_amount, &config.astro_denom), )?; diff --git a/contracts/emissions_controller_outpost/src/instantiate.rs b/contracts/emissions_controller_outpost/src/instantiate.rs index 394fabfb..3e282a80 100644 --- a/contracts/emissions_controller_outpost/src/instantiate.rs +++ b/contracts/emissions_controller_outpost/src/instantiate.rs @@ -10,6 +10,7 @@ use cw2::set_contract_version; use cw_utils::parse_instantiate_response_data; use astroport_governance::emissions_controller::outpost::Config; +use astroport_governance::emissions_controller::utils::query_incentives_addr; use astroport_governance::voting_escrow; use crate::error::ContractError; @@ -34,10 +35,13 @@ pub fn instantiate( validate_native_denom(&msg.astro_denom)?; + let factory = deps.api.addr_validate(&msg.factory)?; + let config = Config { owner: deps.api.addr_validate(&msg.owner)?, vxastro: Addr::unchecked(""), astro_denom: msg.astro_denom, + incentives_addr: query_incentives_addr(deps.querier, &factory)?, factory: deps.api.addr_validate(&msg.factory)?, // Contract owner is responsible for setting a channel via UpdateConfig voting_ibc_channel: "".to_string(), @@ -48,12 +52,12 @@ pub fn instantiate( CONFIG.save(deps.storage, &config)?; // Instantiate vxASTRO contract - validate_native_denom(&msg.vxastro_deposit_denom)?; + validate_native_denom(&msg.xastro_denom)?; let init_vxastro_msg = WasmMsg::Instantiate { admin: Some(msg.owner), code_id: msg.vxastro_code_id, msg: to_json_binary(&voting_escrow::InstantiateMsg { - deposit_denom: msg.vxastro_deposit_denom.to_string(), + deposit_denom: msg.xastro_denom.to_string(), emissions_controller: env.contract.address.to_string(), marketing: msg.vxastro_marketing_info, })?, diff --git a/contracts/emissions_controller_outpost/tests/common/helper.rs b/contracts/emissions_controller_outpost/tests/common/helper.rs index b3b12bc8..eb02684b 100644 --- a/contracts/emissions_controller_outpost/tests/common/helper.rs +++ b/contracts/emissions_controller_outpost/tests/common/helper.rs @@ -164,7 +164,7 @@ impl ControllerHelper { marketing: None, logo: Some(Logo::Url("".to_string())), }), - vxastro_deposit_denom: xastro_denom.to_string(), + xastro_denom: xastro_denom.to_string(), factory: factory.to_string(), hub_emissions_controller: "emissions_controller".to_string(), ics20_channel: "channel-2".to_string(), diff --git a/packages/astroport-governance/src/emissions_controller/hub.rs b/packages/astroport-governance/src/emissions_controller/hub.rs index 8518dbbf..91561ab2 100644 --- a/packages/astroport-governance/src/emissions_controller/hub.rs +++ b/packages/astroport-governance/src/emissions_controller/hub.rs @@ -17,7 +17,7 @@ pub struct HubInstantiateMsg { /// vxASTRO token marketing info pub vxastro_marketing_info: Option, /// xASTRO denom - pub vxastro_deposit_denom: String, + pub xastro_denom: String, /// Astroport Factory contract pub factory: String, /// ASTRO denom on the Hub @@ -35,6 +35,15 @@ pub struct HubInstantiateMsg { pub fee_receiver: String, /// Minimal percentage of total voting power required to keep a pool in the whitelist pub whitelist_threshold: Decimal, + /// Controls ASTRO emissions for the next epoch. + /// If multiple < 1 then protocol emits less ASTRO than it buys back, + /// otherwise protocol is inflating ASTRO supply. + pub emissions_multiple: Decimal, + /// Max ASTRO allowed per epoch. Parameter of the dynamic emissions curve. + pub max_astro: Uint128, + /// Defines the number of ASTRO collected to staking contract + /// from 2-weeks period preceding the current epoch. + pub collected_astro: Uint128, } #[cw_serde] @@ -48,6 +57,8 @@ pub enum HubMsg { pools_per_outpost: Option, whitelisting_fee: Option, fee_receiver: Option, + emissions_multiple: Option, + max_astro: Option, }, /// Whitelists a pool to receive ASTRO emissions. Requires fee payment WhitelistPool { pool: String }, @@ -92,7 +103,7 @@ pub enum QueryMsg { pool: String, timestamp: Option, }, - /// Returns paginated list of all pools that received votes + /// Returns paginated list of all pools that received votes at the current epoch #[returns(Vec<(String, VotedPoolInfo)>)] VotedPoolsList { limit: Option, @@ -104,6 +115,15 @@ pub enum QueryMsg { /// QueryWhitelist returns the list of pools that are allowed to be voted for #[returns(Vec)] QueryWhitelist {}, + /// SimulateTune simulates the ASTRO amount that will be emitted in the next epoch per pool + /// considering if the next epoch starts right now. + /// This query is useful for the UI to show the expected ASTRO emissions + /// as well as might be useful for integrator estimations. + /// It filters out pools which don't belong to any of outposts and invalid Hub-based LP tokens. + /// Returns TuneResultResponse object which contains + /// emissions state and next pools grouped by outpost prefix. + #[returns(SimulateTuneResponse)] + SimulateTune {}, } /// General contract configuration @@ -117,6 +137,12 @@ pub struct Config { pub factory: Addr, /// ASTRO denom on the Hub pub astro_denom: String, + /// xASTRO denom + pub xastro_denom: String, + /// Staking contract + pub staking: Addr, + /// The Astroport Incentives contract + pub incentives_addr: Addr, /// Max number of pools that can receive ASTRO emissions per outpost added. /// For example, if there are 3 outposts, /// and the pools_limit is 10, then 30 pools can receive ASTRO emissions. @@ -130,6 +156,13 @@ pub struct Config { pub fee_receiver: Addr, /// Minimal percentage of total voting power required to keep a pool in the whitelist pub whitelist_threshold: Decimal, + /// Controls the number of ASTRO emissions for the next epoch + /// where next amount = two epoch EMA * emissions_multiple. + /// If multiple < 1 then protocol emits less ASTRO than it buys back, + /// otherwise protocol is inflating ASTRO supply. + pub emissions_multiple: Decimal, + /// Max ASTRO allowed per epoch. Parameter of the dynamic emissions curve. + pub max_astro: Uint128, } impl Config { @@ -149,6 +182,17 @@ impl Config { self.whitelist_threshold > Decimal::zero() && self.whitelist_threshold < Decimal::one(), StdError::generic_err("whitelist_threshold must be within (0, 1) range") ); + + ensure!( + !self.emissions_multiple.is_zero(), + StdError::generic_err("emissions_multiple must be greater than 0") + ); + + ensure!( + !self.max_astro.is_zero(), + StdError::generic_err("max_astro must be greater than 0") + ); + Ok(()) } } @@ -249,6 +293,24 @@ pub struct TuneInfo { pub pools_grouped: HashMap>, /// Map of outpost prefix -> IBC status. Hub should never enter this map. pub outpost_emissions_statuses: HashMap, + /// State of the dynamic emissions curve + pub emissions_state: EmissionsState, +} + +#[cw_serde] +pub struct SimulateTuneResponse { + pub new_emissions_state: EmissionsState, + pub next_pools_grouped: HashMap>, +} + +#[cw_serde] +pub struct EmissionsState { + /// xASTRO to ASTRO staking rate from the previous epoch + pub xastro_rate: Decimal, + /// Collected ASTRO from previous epoch. + pub collected_astro: Uint128, + /// Amount of ASTRO to be emitted in the current epoch + pub emissions_amount: Uint128, } #[cfg(test)] @@ -264,10 +326,15 @@ mod unit_tests { vxastro: Addr::unchecked(""), factory: Addr::unchecked(""), astro_denom: "uastro".to_string(), + xastro_denom: "".to_string(), + staking: Addr::unchecked(""), + incentives_addr: Addr::unchecked(""), pools_per_outpost: 0, whitelisting_fee: coin(100, "uastro"), fee_receiver: Addr::unchecked(""), whitelist_threshold: Decimal::percent(10), + emissions_multiple: Decimal::percent(80), + max_astro: 1_400_000_000_000u128.into(), }; assert_eq!( config.validate().unwrap_err(), @@ -299,6 +366,22 @@ mod unit_tests { ); config.astro_denom = "uastro".to_string(); + config.emissions_multiple = Decimal::zero(); + + assert_eq!( + config.validate().unwrap_err(), + StdError::generic_err("emissions_multiple must be greater than 0") + ); + + config.emissions_multiple = Decimal::percent(80); + config.max_astro = Uint128::zero(); + + assert_eq!( + config.validate().unwrap_err(), + StdError::generic_err("max_astro must be greater than 0") + ); + + config.max_astro = 1_400_000_000_000u128.into(); config.validate().unwrap(); } diff --git a/packages/astroport-governance/src/emissions_controller/outpost.rs b/packages/astroport-governance/src/emissions_controller/outpost.rs index 888db233..e8f403cc 100644 --- a/packages/astroport-governance/src/emissions_controller/outpost.rs +++ b/packages/astroport-governance/src/emissions_controller/outpost.rs @@ -12,12 +12,12 @@ pub struct OutpostInstantiateMsg { pub owner: String, /// ASTRO denom on the chain pub astro_denom: String, + /// xASTRO denom + pub xastro_denom: String, /// vxASTRO contract code id pub vxastro_code_id: u64, /// vxASTRO token marketing info pub vxastro_marketing_info: Option, - /// xASTRO denom - pub vxastro_deposit_denom: String, /// Astroport Factory contract pub factory: String, /// Emissions controller on the Hub @@ -87,6 +87,8 @@ pub struct Config { pub astro_denom: String, /// Astroport Factory contract pub factory: Addr, + /// The Astroport Incentives contract + pub incentives_addr: Addr, /// vxASTRO IBC channel pub voting_ibc_channel: String, /// Emissions controller on the Hub diff --git a/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json b/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json index 27b8dea4..0c4e1ad4 100644 --- a/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json +++ b/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json @@ -14,7 +14,7 @@ "ics20_channel", "owner", "vxastro_code_id", - "vxastro_deposit_denom" + "xastro_denom" ], "properties": { "astro_denom": { @@ -43,10 +43,6 @@ "format": "uint64", "minimum": 0.0 }, - "vxastro_deposit_denom": { - "description": "xASTRO denom", - "type": "string" - }, "vxastro_marketing_info": { "description": "vxASTRO token marketing info", "anyOf": [ @@ -57,6 +53,10 @@ "type": "null" } ] + }, + "xastro_denom": { + "description": "xASTRO denom", + "type": "string" } }, "additionalProperties": false, @@ -594,6 +594,7 @@ "factory", "hub_emissions_controller", "ics20_channel", + "incentives_addr", "owner", "voting_ibc_channel", "vxastro" @@ -619,6 +620,14 @@ "description": "Official ICS20 IBC channel from this outpost to the Hub", "type": "string" }, + "incentives_addr": { + "description": "The Astroport Incentives contract", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, "owner": { "description": "Address that's allowed to change contract parameters", "allOf": [ diff --git a/schemas/astroport-emissions-controller-outpost/raw/instantiate.json b/schemas/astroport-emissions-controller-outpost/raw/instantiate.json index b4eead92..641e06ba 100644 --- a/schemas/astroport-emissions-controller-outpost/raw/instantiate.json +++ b/schemas/astroport-emissions-controller-outpost/raw/instantiate.json @@ -10,7 +10,7 @@ "ics20_channel", "owner", "vxastro_code_id", - "vxastro_deposit_denom" + "xastro_denom" ], "properties": { "astro_denom": { @@ -39,10 +39,6 @@ "format": "uint64", "minimum": 0.0 }, - "vxastro_deposit_denom": { - "description": "xASTRO denom", - "type": "string" - }, "vxastro_marketing_info": { "description": "vxASTRO token marketing info", "anyOf": [ @@ -53,6 +49,10 @@ "type": "null" } ] + }, + "xastro_denom": { + "description": "xASTRO denom", + "type": "string" } }, "additionalProperties": false, diff --git a/schemas/astroport-emissions-controller-outpost/raw/response_to_config.json b/schemas/astroport-emissions-controller-outpost/raw/response_to_config.json index 10b2d199..5b2812d4 100644 --- a/schemas/astroport-emissions-controller-outpost/raw/response_to_config.json +++ b/schemas/astroport-emissions-controller-outpost/raw/response_to_config.json @@ -8,6 +8,7 @@ "factory", "hub_emissions_controller", "ics20_channel", + "incentives_addr", "owner", "voting_ibc_channel", "vxastro" @@ -33,6 +34,14 @@ "description": "Official ICS20 IBC channel from this outpost to the Hub", "type": "string" }, + "incentives_addr": { + "description": "The Astroport Incentives contract", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, "owner": { "description": "Address that's allowed to change contract parameters", "allOf": [ diff --git a/schemas/astroport-emissions-controller/astroport-emissions-controller.json b/schemas/astroport-emissions-controller/astroport-emissions-controller.json index 044b2a00..042f910b 100644 --- a/schemas/astroport-emissions-controller/astroport-emissions-controller.json +++ b/schemas/astroport-emissions-controller/astroport-emissions-controller.json @@ -9,20 +9,39 @@ "type": "object", "required": [ "astro_denom", + "collected_astro", + "emissions_multiple", "factory", "fee_receiver", + "max_astro", "owner", "pools_per_outpost", "vxastro_code_id", - "vxastro_deposit_denom", "whitelist_threshold", - "whitelisting_fee" + "whitelisting_fee", + "xastro_denom" ], "properties": { "astro_denom": { "description": "ASTRO denom on the Hub", "type": "string" }, + "collected_astro": { + "description": "Defines the number of ASTRO collected to staking contract from 2-weeks period preceding the current epoch.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "emissions_multiple": { + "description": "Controls ASTRO emissions for the next epoch. If multiple < 1 then protocol emits less ASTRO than it buys back, otherwise protocol is inflating ASTRO supply.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, "factory": { "description": "Astroport Factory contract", "type": "string" @@ -31,6 +50,14 @@ "description": "Address that receives the whitelisting fee", "type": "string" }, + "max_astro": { + "description": "Max ASTRO allowed per epoch. Parameter of the dynamic emissions curve.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, "owner": { "description": "Contract owner", "type": "string" @@ -47,10 +74,6 @@ "format": "uint64", "minimum": 0.0 }, - "vxastro_deposit_denom": { - "description": "xASTRO denom", - "type": "string" - }, "vxastro_marketing_info": { "description": "vxASTRO token marketing info", "anyOf": [ @@ -77,6 +100,10 @@ "$ref": "#/definitions/Coin" } ] + }, + "xastro_denom": { + "description": "xASTRO denom", + "type": "string" } }, "additionalProperties": false, @@ -444,12 +471,32 @@ "update_config": { "type": "object", "properties": { + "emissions_multiple": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, "fee_receiver": { "type": [ "string", "null" ] }, + "max_astro": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, "pools_per_outpost": { "type": [ "integer", @@ -703,7 +750,7 @@ "additionalProperties": false }, { - "description": "Returns paginated list of all pools that received votes", + "description": "Returns paginated list of all pools that received votes at the current epoch", "type": "object", "required": [ "voted_pools_list" @@ -759,6 +806,20 @@ } }, "additionalProperties": false + }, + { + "description": "SimulateTune simulates the ASTRO amount that will be emitted in the next epoch per pool considering if the next epoch starts right now. This query is useful for the UI to show the expected ASTRO emissions as well as might be useful for integrator estimations. It filters out pools which don't belong to any of outposts and invalid Hub-based LP tokens. Returns TuneResultResponse object which contains emissions state and next pools grouped by outpost prefix.", + "type": "object", + "required": [ + "simulate_tune" + ], + "properties": { + "simulate_tune": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false } ] }, @@ -772,19 +833,32 @@ "type": "object", "required": [ "astro_denom", + "emissions_multiple", "factory", "fee_receiver", + "incentives_addr", + "max_astro", "owner", "pools_per_outpost", + "staking", "vxastro", "whitelist_threshold", - "whitelisting_fee" + "whitelisting_fee", + "xastro_denom" ], "properties": { "astro_denom": { "description": "ASTRO denom on the Hub", "type": "string" }, + "emissions_multiple": { + "description": "Controls the number of ASTRO emissions for the next epoch where next amount = two epoch EMA * emissions_multiple. If multiple < 1 then protocol emits less ASTRO than it buys back, otherwise protocol is inflating ASTRO supply.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, "factory": { "description": "Astroport Factory contract", "allOf": [ @@ -801,6 +875,22 @@ } ] }, + "incentives_addr": { + "description": "The Astroport Incentives contract", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "max_astro": { + "description": "Max ASTRO allowed per epoch. Parameter of the dynamic emissions curve.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, "owner": { "description": "Address that's allowed to change contract parameters", "allOf": [ @@ -815,6 +905,14 @@ "format": "uint64", "minimum": 0.0 }, + "staking": { + "description": "Staking contract", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, "vxastro": { "description": "vxASTRO contract address", "allOf": [ @@ -838,6 +936,10 @@ "$ref": "#/definitions/Coin" } ] + }, + "xastro_denom": { + "description": "xASTRO denom", + "type": "string" } }, "additionalProperties": false, @@ -983,16 +1085,104 @@ "type": "string" } }, + "simulate_tune": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SimulateTuneResponse", + "type": "object", + "required": [ + "new_emissions_state", + "next_pools_grouped" + ], + "properties": { + "new_emissions_state": { + "$ref": "#/definitions/EmissionsState" + }, + "next_pools_grouped": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/Uint128" + } + ], + "maxItems": 2, + "minItems": 2 + } + } + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "EmissionsState": { + "type": "object", + "required": [ + "collected_astro", + "emissions_amount", + "xastro_rate" + ], + "properties": { + "collected_astro": { + "description": "Collected ASTRO from previous epoch.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "emissions_amount": { + "description": "Amount of ASTRO to be emitted in the current epoch", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "xastro_rate": { + "description": "xASTRO to ASTRO staking rate from the previous epoch", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, "tune_info": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "TuneInfo", "type": "object", "required": [ + "emissions_state", "outpost_emissions_statuses", "pools_grouped", "tune_ts" ], "properties": { + "emissions_state": { + "description": "State of the dynamic emissions curve", + "allOf": [ + { + "$ref": "#/definitions/EmissionsState" + } + ] + }, "outpost_emissions_statuses": { "description": "Map of outpost prefix -> IBC status. Hub should never enter this map.", "type": "object", @@ -1029,6 +1219,45 @@ }, "additionalProperties": false, "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "EmissionsState": { + "type": "object", + "required": [ + "collected_astro", + "emissions_amount", + "xastro_rate" + ], + "properties": { + "collected_astro": { + "description": "Collected ASTRO from previous epoch.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "emissions_amount": { + "description": "Amount of ASTRO to be emitted in the current epoch", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "xastro_rate": { + "description": "xASTRO to ASTRO staking rate from the previous epoch", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + }, "OutpostStatus": { "type": "string", "enum": [ diff --git a/schemas/astroport-emissions-controller/raw/execute.json b/schemas/astroport-emissions-controller/raw/execute.json index 01e5b5bf..18a0b391 100644 --- a/schemas/astroport-emissions-controller/raw/execute.json +++ b/schemas/astroport-emissions-controller/raw/execute.json @@ -231,12 +231,32 @@ "update_config": { "type": "object", "properties": { + "emissions_multiple": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + }, "fee_receiver": { "type": [ "string", "null" ] }, + "max_astro": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, "pools_per_outpost": { "type": [ "integer", diff --git a/schemas/astroport-emissions-controller/raw/instantiate.json b/schemas/astroport-emissions-controller/raw/instantiate.json index ff266bb6..49ba7c85 100644 --- a/schemas/astroport-emissions-controller/raw/instantiate.json +++ b/schemas/astroport-emissions-controller/raw/instantiate.json @@ -5,20 +5,39 @@ "type": "object", "required": [ "astro_denom", + "collected_astro", + "emissions_multiple", "factory", "fee_receiver", + "max_astro", "owner", "pools_per_outpost", "vxastro_code_id", - "vxastro_deposit_denom", "whitelist_threshold", - "whitelisting_fee" + "whitelisting_fee", + "xastro_denom" ], "properties": { "astro_denom": { "description": "ASTRO denom on the Hub", "type": "string" }, + "collected_astro": { + "description": "Defines the number of ASTRO collected to staking contract from 2-weeks period preceding the current epoch.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "emissions_multiple": { + "description": "Controls ASTRO emissions for the next epoch. If multiple < 1 then protocol emits less ASTRO than it buys back, otherwise protocol is inflating ASTRO supply.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, "factory": { "description": "Astroport Factory contract", "type": "string" @@ -27,6 +46,14 @@ "description": "Address that receives the whitelisting fee", "type": "string" }, + "max_astro": { + "description": "Max ASTRO allowed per epoch. Parameter of the dynamic emissions curve.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, "owner": { "description": "Contract owner", "type": "string" @@ -43,10 +70,6 @@ "format": "uint64", "minimum": 0.0 }, - "vxastro_deposit_denom": { - "description": "xASTRO denom", - "type": "string" - }, "vxastro_marketing_info": { "description": "vxASTRO token marketing info", "anyOf": [ @@ -73,6 +96,10 @@ "$ref": "#/definitions/Coin" } ] + }, + "xastro_denom": { + "description": "xASTRO denom", + "type": "string" } }, "additionalProperties": false, diff --git a/schemas/astroport-emissions-controller/raw/query.json b/schemas/astroport-emissions-controller/raw/query.json index f70a61d8..be099de1 100644 --- a/schemas/astroport-emissions-controller/raw/query.json +++ b/schemas/astroport-emissions-controller/raw/query.json @@ -102,7 +102,7 @@ "additionalProperties": false }, { - "description": "Returns paginated list of all pools that received votes", + "description": "Returns paginated list of all pools that received votes at the current epoch", "type": "object", "required": [ "voted_pools_list" @@ -158,6 +158,20 @@ } }, "additionalProperties": false + }, + { + "description": "SimulateTune simulates the ASTRO amount that will be emitted in the next epoch per pool considering if the next epoch starts right now. This query is useful for the UI to show the expected ASTRO emissions as well as might be useful for integrator estimations. It filters out pools which don't belong to any of outposts and invalid Hub-based LP tokens. Returns TuneResultResponse object which contains emissions state and next pools grouped by outpost prefix.", + "type": "object", + "required": [ + "simulate_tune" + ], + "properties": { + "simulate_tune": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false } ] } diff --git a/schemas/astroport-emissions-controller/raw/response_to_config.json b/schemas/astroport-emissions-controller/raw/response_to_config.json index e6986a21..1d71073c 100644 --- a/schemas/astroport-emissions-controller/raw/response_to_config.json +++ b/schemas/astroport-emissions-controller/raw/response_to_config.json @@ -5,19 +5,32 @@ "type": "object", "required": [ "astro_denom", + "emissions_multiple", "factory", "fee_receiver", + "incentives_addr", + "max_astro", "owner", "pools_per_outpost", + "staking", "vxastro", "whitelist_threshold", - "whitelisting_fee" + "whitelisting_fee", + "xastro_denom" ], "properties": { "astro_denom": { "description": "ASTRO denom on the Hub", "type": "string" }, + "emissions_multiple": { + "description": "Controls the number of ASTRO emissions for the next epoch where next amount = two epoch EMA * emissions_multiple. If multiple < 1 then protocol emits less ASTRO than it buys back, otherwise protocol is inflating ASTRO supply.", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, "factory": { "description": "Astroport Factory contract", "allOf": [ @@ -34,6 +47,22 @@ } ] }, + "incentives_addr": { + "description": "The Astroport Incentives contract", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "max_astro": { + "description": "Max ASTRO allowed per epoch. Parameter of the dynamic emissions curve.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, "owner": { "description": "Address that's allowed to change contract parameters", "allOf": [ @@ -48,6 +77,14 @@ "format": "uint64", "minimum": 0.0 }, + "staking": { + "description": "Staking contract", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, "vxastro": { "description": "vxASTRO contract address", "allOf": [ @@ -71,6 +108,10 @@ "$ref": "#/definitions/Coin" } ] + }, + "xastro_denom": { + "description": "xASTRO denom", + "type": "string" } }, "additionalProperties": false, diff --git a/schemas/astroport-emissions-controller/raw/response_to_simulate_tune.json b/schemas/astroport-emissions-controller/raw/response_to_simulate_tune.json new file mode 100644 index 00000000..8ad8d36e --- /dev/null +++ b/schemas/astroport-emissions-controller/raw/response_to_simulate_tune.json @@ -0,0 +1,79 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SimulateTuneResponse", + "type": "object", + "required": [ + "new_emissions_state", + "next_pools_grouped" + ], + "properties": { + "new_emissions_state": { + "$ref": "#/definitions/EmissionsState" + }, + "next_pools_grouped": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/Uint128" + } + ], + "maxItems": 2, + "minItems": 2 + } + } + } + }, + "additionalProperties": false, + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "EmissionsState": { + "type": "object", + "required": [ + "collected_astro", + "emissions_amount", + "xastro_rate" + ], + "properties": { + "collected_astro": { + "description": "Collected ASTRO from previous epoch.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "emissions_amount": { + "description": "Amount of ASTRO to be emitted in the current epoch", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "xastro_rate": { + "description": "xASTRO to ASTRO staking rate from the previous epoch", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/schemas/astroport-emissions-controller/raw/response_to_tune_info.json b/schemas/astroport-emissions-controller/raw/response_to_tune_info.json index 34775acb..08837871 100644 --- a/schemas/astroport-emissions-controller/raw/response_to_tune_info.json +++ b/schemas/astroport-emissions-controller/raw/response_to_tune_info.json @@ -3,11 +3,20 @@ "title": "TuneInfo", "type": "object", "required": [ + "emissions_state", "outpost_emissions_statuses", "pools_grouped", "tune_ts" ], "properties": { + "emissions_state": { + "description": "State of the dynamic emissions curve", + "allOf": [ + { + "$ref": "#/definitions/EmissionsState" + } + ] + }, "outpost_emissions_statuses": { "description": "Map of outpost prefix -> IBC status. Hub should never enter this map.", "type": "object", @@ -44,6 +53,45 @@ }, "additionalProperties": false, "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "EmissionsState": { + "type": "object", + "required": [ + "collected_astro", + "emissions_amount", + "xastro_rate" + ], + "properties": { + "collected_astro": { + "description": "Collected ASTRO from previous epoch.", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "emissions_amount": { + "description": "Amount of ASTRO to be emitted in the current epoch", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "xastro_rate": { + "description": "xASTRO to ASTRO staking rate from the previous epoch", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + } + }, + "additionalProperties": false + }, "OutpostStatus": { "type": "string", "enum": [ From ad763834bd1257f303bc1fe692ab46c05f6be49b Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Fri, 7 Jun 2024 12:40:30 +0400 Subject: [PATCH 10/32] feat(governance): connect vxASTRO to the governance --- Cargo.lock | 198 +++++++++++------- contracts/assembly/Cargo.toml | 1 + contracts/assembly/src/contract.rs | 139 +++++++++--- contracts/assembly/src/error.rs | 3 + contracts/assembly/src/ibc.rs | 2 + contracts/assembly/src/migration.rs | 166 ++++++++++----- contracts/assembly/src/queries.rs | 7 +- contracts/assembly/src/unit_tests.rs | 14 +- contracts/assembly/src/utils.rs | 29 ++- .../assembly/tests/assembly_integration.rs | 25 ++- contracts/assembly/tests/common/helper.rs | 113 +++++++++- contracts/assembly/tests/common/stargate.rs | 5 +- contracts/emissions_controller/Cargo.toml | 5 +- contracts/emissions_controller/src/execute.rs | 72 ++++++- contracts/emissions_controller/src/ibc.rs | 54 ++++- .../emissions_controller/src/instantiate.rs | 1 + contracts/emissions_controller/src/sudo.rs | 8 +- contracts/emissions_controller/src/utils.rs | 12 +- .../tests/common/contracts.rs | 81 ++++++- .../tests/common/helper.rs | 146 ++++++++++++- .../tests/common/ibc_module.rs | 2 +- .../tests/emissions_controller_integration.rs | 138 +++++++++++- .../emissions_controller_outpost/src/error.rs | 3 + .../src/execute.rs | 117 +++++++---- .../emissions_controller_outpost/src/ibc.rs | 76 ++++++- .../emissions_controller_outpost/src/lib.rs | 1 + .../emissions_controller_outpost/src/state.rs | 7 +- .../emissions_controller_outpost/src/utils.rs | 29 +++ .../tests/common/contracts.rs | 13 +- .../tests/common/helper.rs | 101 +++++++-- ...missions_controller_outpost_integration.rs | 185 ++++++++++++---- contracts/voting_escrow/src/contract.rs | 12 +- contracts/voting_escrow/tests/helper.rs | 13 +- packages/astroport-governance/src/assembly.rs | 87 ++++---- .../src/emissions_controller/hub.rs | 7 + .../src/emissions_controller/msg.rs | 20 +- .../src/emissions_controller/outpost.rs | 14 +- .../src/emissions_controller/utils.rs | 4 +- .../astroport-governance/src/voting_escrow.rs | 7 +- schemas/astro-assembly/astro-assembly.json | 183 +++++++++------- schemas/astro-assembly/raw/execute.json | 92 ++++++-- schemas/astro-assembly/raw/instantiate.json | 28 --- .../raw/response_to_config.json | 23 ++ .../raw/response_to_proposal.json | 20 +- .../raw/response_to_proposals.json | 20 +- ...stroport-emissions-controller-outpost.json | 137 +++++++++++- .../raw/execute.json | 42 ++++ .../raw/response_to_config.json | 2 +- .../response_to_query_user_ibc_status.json | 93 +++++++- .../astroport-emissions-controller.json | 38 ++++ .../raw/execute.json | 24 +++ .../raw/instantiate.json | 5 + .../raw/response_to_config.json | 9 + .../astroport-voting-escrow.json | 4 +- .../astroport-voting-escrow/raw/query.json | 4 +- 55 files changed, 2047 insertions(+), 594 deletions(-) create mode 100644 contracts/emissions_controller_outpost/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 149df25e..f7f6f579 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,9 +15,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" [[package]] name = "astro-assembly" @@ -25,14 +25,15 @@ version = "2.0.0" dependencies = [ "anyhow", "astro-satellite", - "astroport 4.0.2", + "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", "astroport-governance 3.0.0", "astroport-staking 2.1.0 (git+https://github.com/astroport-fi/hidden_astroport_core)", - "astroport-tokenfactory-tracker", + "astroport-tokenfactory-tracker 1.0.0 (git+https://github.com/astroport-fi/hidden_astroport_core)", + "astroport-voting-escrow", "builder-unlock", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 0.20.0", + "cw-multi-test 0.20.0 (git+https://github.com/astroport-fi/cw-multi-test?branch=feat/bank_with_send_hooks)", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -123,8 +124,8 @@ dependencies = [ [[package]] name = "astroport" -version = "4.0.3" -source = "git+https://github.com/astroport-fi/hidden_astroport_core#5e7d5cd3920dd6094a6e7a090a81b51a4371a8c6" +version = "4.0.2" +source = "git+https://github.com/astroport-fi/hidden_astroport_core#02eafc677d9c402a55e45e956fd185d67ac3a8a6" dependencies = [ "astroport-circular-buffer 0.2.0 (git+https://github.com/astroport-fi/hidden_astroport_core)", "cosmwasm-schema", @@ -163,7 +164,7 @@ dependencies = [ [[package]] name = "astroport-circular-buffer" version = "0.2.0" -source = "git+https://github.com/astroport-fi/hidden_astroport_core#5e7d5cd3920dd6094a6e7a090a81b51a4371a8c6" +source = "git+https://github.com/astroport-fi/hidden_astroport_core#02eafc677d9c402a55e45e956fd185d67ac3a8a6" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -176,16 +177,19 @@ name = "astroport-emissions-controller" version = "1.0.0" dependencies = [ "anyhow", - "astroport 4.0.2", + "astro-assembly", + "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", "astroport-factory", "astroport-governance 3.0.0", "astroport-incentives", "astroport-pair", "astroport-staking 2.1.0 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "astroport-tokenfactory-tracker 1.0.0 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", "astroport-voting-escrow", + "builder-unlock", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 1.1.0", + "cw-multi-test 1.0.0", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -202,7 +206,7 @@ dependencies = [ name = "astroport-emissions-controller-outpost" version = "1.0.0" dependencies = [ - "astroport 4.0.2", + "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", "astroport-factory", "astroport-governance 3.0.0", "astroport-incentives", @@ -255,7 +259,7 @@ dependencies = [ name = "astroport-governance" version = "3.0.0" dependencies = [ - "astroport 4.0.2", + "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", @@ -278,7 +282,7 @@ name = "astroport-incentives" version = "1.2.0" source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#ada68324a4cca143008646d3c81baa715cba12d2" dependencies = [ - "astroport 4.0.2", + "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", @@ -312,7 +316,7 @@ name = "astroport-staking" version = "2.1.0" source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#ada68324a4cca143008646d3c81baa715cba12d2" dependencies = [ - "astroport 4.0.2", + "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", "cosmwasm-std", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", @@ -324,9 +328,9 @@ dependencies = [ [[package]] name = "astroport-staking" version = "2.1.0" -source = "git+https://github.com/astroport-fi/hidden_astroport_core#5e7d5cd3920dd6094a6e7a090a81b51a4371a8c6" +source = "git+https://github.com/astroport-fi/hidden_astroport_core#02eafc677d9c402a55e45e956fd185d67ac3a8a6" dependencies = [ - "astroport 4.0.3", + "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core)", "cosmwasm-std", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", @@ -338,9 +342,22 @@ dependencies = [ [[package]] name = "astroport-tokenfactory-tracker" version = "1.0.0" -source = "git+https://github.com/astroport-fi/hidden_astroport_core#5e7d5cd3920dd6094a6e7a090a81b51a4371a8c6" +source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#ada68324a4cca143008646d3c81baa715cba12d2" +dependencies = [ + "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw2 1.1.2", + "thiserror", +] + +[[package]] +name = "astroport-tokenfactory-tracker" +version = "1.0.0" +source = "git+https://github.com/astroport-fi/hidden_astroport_core#02eafc677d9c402a55e45e956fd185d67ac3a8a6" dependencies = [ - "astroport 4.0.3", + "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core)", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", @@ -352,7 +369,7 @@ dependencies = [ name = "astroport-voting-escrow" version = "1.0.0" dependencies = [ - "astroport 4.0.2", + "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", "astroport-governance 3.0.0", "cosmwasm-schema", "cosmwasm-std", @@ -429,11 +446,11 @@ checksum = "56953345e39537a3e18bdaeba4cb0c58a78c1f61f361dc0fa7c5c7340ae87c5f" name = "builder-unlock" version = "3.0.0" dependencies = [ - "astroport 4.0.2", + "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", "astroport-governance 3.0.0", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 0.20.1", + "cw-multi-test 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -482,8 +499,8 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32560304ab4c365791fd307282f76637213d8083c1a98490c35159cd67852237" dependencies = [ - "prost 0.12.6", - "prost-types 0.12.6", + "prost 0.12.4", + "prost-types 0.12.4", "tendermint-proto", ] @@ -512,9 +529,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema" -version = "1.5.5" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7879036156092ad1c22fe0d7316efc5a5eceec2bc3906462a2560215f2a2f929" +checksum = "8467874827d384c131955ff6f4d47d02e72a956a08eb3c0ff24f8c903a5517b4" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -525,9 +542,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.5.5" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb57855fbfc83327f8445ae0d413b1a05ac0d68c396ab4d122b2abd7bb82cb6" +checksum = "f6db85d98ac80922aef465e564d5b21fa9cfac5058cb62df7f116c3682337393" dependencies = [ "proc-macro2", "quote", @@ -642,7 +659,8 @@ dependencies = [ [[package]] name = "cw-multi-test" version = "0.20.0" -source = "git+https://github.com/astroport-fi/cw-multi-test?branch=feat/bank_with_send_hooks#3220f4cd126b5a8da2ce8b00152afef046a9b391" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67fff029689ae89127cf6d7655809a68d712f3edbdb9686c70b018ba438b26ca" dependencies = [ "anyhow", "bech32 0.9.1", @@ -651,7 +669,7 @@ dependencies = [ "cw-utils 1.0.3", "derivative", "itertools 0.12.1", - "prost 0.12.6", + "prost 0.12.4", "schemars", "serde", "sha2 0.10.8", @@ -660,9 +678,8 @@ dependencies = [ [[package]] name = "cw-multi-test" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc392a5cb7e778e3f90adbf7faa43c4db7f35b6623224b08886d796718edb875" +version = "0.20.0" +source = "git+https://github.com/astroport-fi/cw-multi-test?branch=feat/bank_with_send_hooks#3220f4cd126b5a8da2ce8b00152afef046a9b391" dependencies = [ "anyhow", "bech32 0.9.1", @@ -671,7 +688,26 @@ dependencies = [ "cw-utils 1.0.3", "derivative", "itertools 0.12.1", - "prost 0.12.6", + "prost 0.12.4", + "schemars", + "serde", + "sha2 0.10.8", + "thiserror", +] + +[[package]] +name = "cw-multi-test" +version = "1.0.0" +source = "git+https://github.com/astroport-fi/cw-multi-test?branch=feat/bank_with_send_hooks_1_0#b4ba6101dfd67b73aff3b75a8759915bd215ae85" +dependencies = [ + "anyhow", + "bech32 0.11.0", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "derivative", + "itertools 0.12.1", + "prost 0.12.4", "schemars", "serde", "sha2 0.10.8", @@ -691,7 +727,7 @@ dependencies = [ "cw-utils 1.0.3", "derivative", "itertools 0.12.1", - "prost 0.12.6", + "prost 0.12.4", "schemars", "serde", "sha2 0.10.8", @@ -924,9 +960,9 @@ dependencies = [ [[package]] name = "either" -version = "1.12.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" [[package]] name = "elliptic-curve" @@ -1096,9 +1132,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.155" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "neutron-sdk" @@ -1110,8 +1146,8 @@ dependencies = [ "cosmos-sdk-proto", "cosmwasm-schema", "cosmwasm-std", - "prost 0.12.6", - "prost-types 0.12.6", + "prost 0.12.4", + "prost-types 0.12.4", "protobuf 3.4.0", "schemars", "serde", @@ -1169,8 +1205,8 @@ dependencies = [ "chrono", "cosmwasm-std", "osmosis-std-derive", - "prost 0.12.6", - "prost-types 0.12.6", + "prost 0.12.4", + "prost-types 0.12.4", "schemars", "serde", "serde-cw-value", @@ -1213,9 +1249,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro2" -version = "1.0.85" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" dependencies = [ "unicode-ident", ] @@ -1232,12 +1268,12 @@ dependencies = [ [[package]] name = "prost" -version = "0.12.6" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +checksum = "d0f5d036824e4761737860779c906171497f6d55681139d8312388f8fe398922" dependencies = [ "bytes", - "prost-derive 0.12.6", + "prost-derive 0.12.5", ] [[package]] @@ -1255,15 +1291,15 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.12.6" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +checksum = "9554e3ab233f0a932403704f1a1d08c30d5ccd931adfdfa1e8b5a19b52c1d55a" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.10.5", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.61", ] [[package]] @@ -1277,11 +1313,11 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.12.6" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +checksum = "3235c33eb02c1f1e212abdbe34c78b264b038fb58ca612664343271e36e55ffe" dependencies = [ - "prost 0.12.6", + "prost 0.12.4", ] [[package]] @@ -1361,9 +1397,9 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "schemars" -version = "0.8.21" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +checksum = "fc6e7ed6919cb46507fb01ff1654309219f62b4d603822501b0b80d42f6f21ef" dependencies = [ "dyn-clone", "schemars_derive", @@ -1373,14 +1409,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.21" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +checksum = "185f2b7aa7e02d418e453790dde16890256bbd2bcd04b7dc5348811052b53f49" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.66", + "syn 2.0.61", ] [[package]] @@ -1405,9 +1441,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.203" +version = "1.0.201" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" dependencies = [ "serde_derive", ] @@ -1450,24 +1486,24 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.201" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.61", ] [[package]] name = "serde_derive_internals" -version = "0.29.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.61", ] [[package]] @@ -1560,7 +1596,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.66", + "syn 2.0.61", ] [[package]] @@ -1591,9 +1627,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.66" +version = "2.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" dependencies = [ "proc-macro2", "quote", @@ -1610,8 +1646,8 @@ dependencies = [ "flex-error", "num-derive", "num-traits", - "prost 0.12.6", - "prost-types 0.12.6", + "prost 0.12.4", + "prost-types 0.12.4", "serde", "serde_bytes", "subtle-encoding", @@ -1636,7 +1672,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.61", ] [[package]] @@ -1647,28 +1683,28 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.61", "test-case-core", ] [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.61", ] [[package]] @@ -1738,6 +1774,6 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "zeroize" -version = "1.8.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/contracts/assembly/Cargo.toml b/contracts/assembly/Cargo.toml index e63fd9f7..dfdaac8c 100644 --- a/contracts/assembly/Cargo.toml +++ b/contracts/assembly/Cargo.toml @@ -33,6 +33,7 @@ cw-multi-test = { git = "https://github.com/astroport-fi/cw-multi-test", branch osmosis-std = "0.21" astroport-staking = { git = "https://github.com/astroport-fi/hidden_astroport_core", version = "2" } astroport-tokenfactory-tracker = { git = "https://github.com/astroport-fi/hidden_astroport_core", version = "1" } +astroport-voting-escrow = { path = "../voting_escrow", version = "1.0", features = ["library"] } builder-unlock = { path = "../builder_unlock", version = "3" } anyhow = "1" test-case = "3.3.1" \ No newline at end of file diff --git a/contracts/assembly/src/contract.rs b/contracts/assembly/src/contract.rs index f93ed619..a9b994ad 100644 --- a/contracts/assembly/src/contract.rs +++ b/contracts/assembly/src/contract.rs @@ -5,18 +5,20 @@ use astroport::staking; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - attr, coins, wasm_execute, Api, BankMsg, CosmosMsg, Decimal, DepsMut, Env, MessageInfo, - QuerierWrapper, Response, StdError, SubMsg, Uint128, Uint64, WasmMsg, + attr, coins, ensure, wasm_execute, Addr, Api, BankMsg, CosmosMsg, Decimal, DepsMut, Env, + MessageInfo, QuerierWrapper, Response, StdError, Storage, SubMsg, Uint128, Uint64, WasmMsg, }; use cw2::set_contract_version; use cw_utils::must_pay; use ibc_controller_package::ExecuteMsg as ControllerExecuteMsg; use astroport_governance::assembly::{ - helpers::validate_links, Config, ExecuteMsg, InstantiateMsg, Proposal, ProposalStatus, + validate_links, Config, ExecuteMsg, InstantiateMsg, Proposal, ProposalStatus, ProposalVoteOption, UpdateConfig, }; +use astroport_governance::emissions_controller::hub::HubMsg; use astroport_governance::utils::check_contract_supports_channel; +use astroport_governance::{emissions_controller, voting_escrow}; use crate::error::ContractError; use crate::state::{CONFIG, PROPOSALS, PROPOSAL_COUNT, PROPOSAL_VOTERS}; @@ -54,6 +56,8 @@ pub fn instantiate( let config = Config { xastro_denom: staking_config.xastro_denom, xastro_denom_tracking: tracker_config.tracker_addr, + vxastro_contract: None, + emissions_controller: None, ibc_controller: addr_opt_validate(deps.api, &msg.ibc_controller)?, builder_unlock_addr: deps.api.addr_validate(&msg.builder_unlock_addr)?, proposal_voting_period: msg.proposal_voting_period, @@ -122,7 +126,56 @@ pub fn execute( messages, ibc_channel, ), - ExecuteMsg::CastVote { proposal_id, vote } => cast_vote(deps, env, info, proposal_id, vote), + ExecuteMsg::CastVote { proposal_id, vote } => { + let voter = info.sender.to_string(); + let proposal = PROPOSALS.load(deps.storage, proposal_id)?; + + let voting_power = calc_voting_power(deps.as_ref(), voter.clone(), &proposal)?; + ensure!(!voting_power.is_zero(), ContractError::NoVotingPower {}); + + cast_vote( + deps.storage, + env, + voter, + voting_power, + proposal_id, + proposal, + vote, + ) + } + ExecuteMsg::CastVoteOutpost { + voter, + voting_power, + proposal_id, + vote, + } => { + let config = CONFIG.load(deps.storage)?; + ensure!( + Some(info.sender) == config.emissions_controller, + ContractError::Unauthorized {} + ); + + // This endpoint should never fail if called from the emissions controller. + // Otherwise, an IBC packet will never be acknowledged. + (|| { + let proposal = PROPOSALS.load(deps.storage, proposal_id)?; + + cast_vote( + deps.storage, + env, + voter, + voting_power, + proposal_id, + proposal, + vote, + ) + })() + .or_else(|err| { + Ok(Response::new() + .add_attribute("action", "cast_vote") + .add_attribute("error", err.to_string())) + }) + } ExecuteMsg::EndProposal { proposal_id } => end_proposal(deps, env, proposal_id), ExecuteMsg::ExecuteProposal { proposal_id } => execute_proposal(deps, env, proposal_id), ExecuteMsg::CheckMessages(messages) => check_messages(deps.api, env, messages), @@ -189,9 +242,7 @@ pub fn submit_proposal( submitter: info.sender.clone(), status: ProposalStatus::Active, for_power: Uint128::zero(), - outpost_for_power: Uint128::zero(), against_power: Uint128::zero(), - outpost_against_power: Uint128::zero(), start_block: env.block.height, start_time: env.block.time.seconds(), end_block: env.block.height + config.proposal_voting_period, @@ -221,7 +272,7 @@ pub fn submit_proposal( PROPOSALS.save(deps.storage, count.u64(), &proposal)?; - Ok(Response::new().add_attributes(vec![ + let mut response = Response::new().add_attributes([ attr("action", "submit_proposal"), attr("submitter", info.sender), attr("proposal_id", count), @@ -229,23 +280,43 @@ pub fn submit_proposal( "proposal_end_height", (env.block.height + config.proposal_voting_period).to_string(), ), - ])) + ]); + + if let Some(emissions_controller) = config.emissions_controller { + // Send IBC packets to all outposts to register this proposal. + let outposts_register_msg = wasm_execute( + emissions_controller, + &emissions_controller::msg::ExecuteMsg::Custom(HubMsg::RegisterProposal { + proposal_id: count.u64(), + }), + vec![], + )?; + response = response.add_message(outposts_register_msg); + } + + Ok(response) } /// Cast a vote on a proposal. /// +/// * **voter** is the bech32 address of the voter from any of the supported outposts. +/// +/// * **voting_power** is the voting power of the voter. +/// /// * **proposal_id** is the identifier of the proposal. /// +/// * **proposal** is [`Proposal`] object. +/// /// * **vote_option** contains the vote option. pub fn cast_vote( - deps: DepsMut, + storage: &mut dyn Storage, env: Env, - info: MessageInfo, + voter: String, + voting_power: Uint128, proposal_id: u64, + mut proposal: Proposal, vote_option: ProposalVoteOption, ) -> Result { - let mut proposal = PROPOSALS.load(deps.storage, proposal_id)?; - if proposal.status != ProposalStatus::Active { return Err(ContractError::ProposalNotActive {}); } @@ -254,16 +325,10 @@ pub fn cast_vote( return Err(ContractError::VotingPeriodEnded {}); } - if PROPOSAL_VOTERS.has(deps.storage, (proposal_id, info.sender.to_string())) { + if PROPOSAL_VOTERS.has(storage, (proposal_id, voter.clone())) { return Err(ContractError::UserAlreadyVoted {}); } - let voting_power = calc_voting_power(deps.as_ref(), info.sender.to_string(), &proposal)?; - - if voting_power.is_zero() { - return Err(ContractError::NoVotingPower {}); - } - match vote_option { ProposalVoteOption::For => { proposal.for_power = proposal.for_power.checked_add(voting_power)?; @@ -272,18 +337,14 @@ pub fn cast_vote( proposal.against_power = proposal.against_power.checked_add(voting_power)?; } }; - PROPOSAL_VOTERS.save( - deps.storage, - (proposal_id, info.sender.to_string()), - &vote_option, - )?; + PROPOSAL_VOTERS.save(storage, (proposal_id, voter.clone()), &vote_option)?; - PROPOSALS.save(deps.storage, proposal_id, &proposal)?; + PROPOSALS.save(storage, proposal_id, &proposal)?; Ok(Response::new().add_attributes(vec![ attr("action", "cast_vote"), attr("proposal_id", proposal_id.to_string()), - attr("voter", &info.sender), + attr("voter", &voter), attr("vote", vote_option.to_string()), attr("voting_power", voting_power), ])) @@ -479,7 +540,7 @@ pub fn update_config( } if let Some(proposal_required_deposit) = updated_config.proposal_required_deposit { - config.proposal_required_deposit = Uint128::from(proposal_required_deposit); + config.proposal_required_deposit = proposal_required_deposit; attrs.push(attr( "new_proposal_required_deposit", proposal_required_deposit.to_string(), @@ -487,18 +548,18 @@ pub fn update_config( } if let Some(proposal_required_quorum) = updated_config.proposal_required_quorum { - config.proposal_required_quorum = Decimal::from_str(&proposal_required_quorum)?; + config.proposal_required_quorum = proposal_required_quorum; attrs.push(attr( "new_proposal_required_quorum", - proposal_required_quorum, + proposal_required_quorum.to_string(), )); } if let Some(proposal_required_threshold) = updated_config.proposal_required_threshold { - config.proposal_required_threshold = Decimal::from_str(&proposal_required_threshold)?; + config.proposal_required_threshold = proposal_required_threshold; attrs.push(attr( "new_proposal_required_threshold", - proposal_required_threshold, + proposal_required_threshold.to_string(), )); } @@ -530,6 +591,22 @@ pub fn update_config( } } + if let Some(vxastro) = updated_config.vxastro { + let emissions_controller = deps + .querier + .query_wasm_smart::( + &vxastro, + &voting_escrow::QueryMsg::Config {}, + )? + .emissions_controller; + + config.emissions_controller = Some(Addr::unchecked(&emissions_controller)); + config.vxastro_contract = Some(Addr::unchecked(&vxastro)); + + attrs.push(attr("new_emissions_controller", emissions_controller)); + attrs.push(attr("new_vxastro_contract", vxastro)); + } + #[cfg(not(feature = "testnet"))] config.validate()?; diff --git a/contracts/assembly/src/error.rs b/contracts/assembly/src/error.rs index d794ac6e..5a954e88 100644 --- a/contracts/assembly/src/error.rs +++ b/contracts/assembly/src/error.rs @@ -67,4 +67,7 @@ pub enum ContractError { #[error("{0}")] PaymentError(#[from] PaymentError), + + #[error("Failed to migrate contract")] + MigrationError {}, } diff --git a/contracts/assembly/src/ibc.rs b/contracts/assembly/src/ibc.rs index 1b36688a..21d25c65 100644 --- a/contracts/assembly/src/ibc.rs +++ b/contracts/assembly/src/ibc.rs @@ -1,3 +1,5 @@ +#![cfg(not(tarpaulin_include))] + #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ diff --git a/contracts/assembly/src/migration.rs b/contracts/assembly/src/migration.rs index 883c3456..4106cdd2 100644 --- a/contracts/assembly/src/migration.rs +++ b/contracts/assembly/src/migration.rs @@ -1,62 +1,116 @@ -use crate::contract::{instantiate, CONTRACT_NAME, CONTRACT_VERSION}; -use crate::error::ContractError; -use astroport_governance::assembly::InstantiateMsg; +#![cfg(not(tarpaulin_include))] + +use cosmwasm_schema::cw_serde; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{Addr, DepsMut, Env, IbcMsg, MessageInfo, Response, StdError}; +use cosmwasm_std::{Addr, CosmosMsg, DepsMut, Env, Order, Response, StdResult, Uint128, Uint64}; +use cw2::{get_contract_version, set_contract_version}; +use cw_storage_plus::Map; + +use astroport_governance::assembly::{Config, Proposal, ProposalStatus}; +use astroport_governance::voting_escrow; + +use crate::contract::{CONTRACT_NAME, CONTRACT_VERSION}; +use crate::error::ContractError; +use crate::state::{CONFIG, PROPOSALS}; + +#[cw_serde] +pub struct MigrateMsg { + pub vxastro_contract: String, +} + +#[cw_serde] +pub struct OldProposal { + pub proposal_id: Uint64, + pub submitter: Addr, + pub status: ProposalStatus, + pub for_power: Uint128, + pub outpost_for_power: Uint128, + pub against_power: Uint128, + pub outpost_against_power: Uint128, + pub start_block: u64, + pub start_time: u64, + pub end_block: u64, + pub delayed_end_block: u64, + pub expiration_block: u64, + pub title: String, + pub description: String, + pub link: Option, + pub messages: Vec, + pub deposit_amount: Uint128, + pub ibc_channel: Option, + pub total_voting_power: Uint128, +} -const EXPECTED_CONTRACT_NAME: &str = "astro-satellite-neutron"; -const EXPECTED_CONTRACT_VERSION: &str = "1.1.0-hubmove"; +const OLD_PROPOSALS: Map = Map::new("proposals"); -/// This migration is used to convert the satellite contract on Neutron into Assembly. -/// Cosmwasm migration is meant to be executed from multisig controlled by Astroport to prevent abnormal subsequences -/// and be able to react promptly in case of any issues. -/// -/// Mainnet contract which is only subject of this migration: https://neutron.celat.one/neutron-1/contracts/neutron1ffus553eet978k024lmssw0czsxwr97mggyv85lpcsdkft8v9ufsz3sa07 #[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate(deps: DepsMut, env: Env, msg: InstantiateMsg) -> Result { - cw2::assert_contract_version( - deps.storage, - EXPECTED_CONTRACT_NAME, - EXPECTED_CONTRACT_VERSION, - )?; - - // Clear satellite's state - astro_satellite::state::LATEST_HUB_SIGNAL_TIME.remove(deps.storage); - astro_satellite::state::REPLY_DATA.remove(deps.storage); - astro_satellite::state::RESULTS.clear(deps.storage); - - // Close old governance channel with Terra - let satellite_config = astro_satellite::state::CONFIG.load(deps.storage)?; - let close_msg = IbcMsg::CloseChannel { - channel_id: satellite_config.gov_channel.ok_or_else(|| { - StdError::generic_err("Missing governance channel in satellite config") - })?, - }; - - let cw_admin = deps - .querier - .query_wasm_contract_info(&env.contract.address)? - .admin - .unwrap(); - // Even though info object is ignored in instantiate, we provide it for clarity - let info = MessageInfo { - sender: Addr::unchecked(cw_admin), - funds: vec![], - }; - // Instantiate Assembly state. - // Config and cw2 info will be overwritten. - let contract_version = cw2::get_contract_version(deps.storage)?; - - instantiate(deps, env, info, msg).map(|resp| { - resp.add_message(close_msg).add_attributes([ - ("previous_contract_name", contract_version.contract.as_str()), - ( - "previous_contract_version", - contract_version.version.as_str(), - ), - ("new_contract_name", CONTRACT_NAME), - ("new_contract_version", CONTRACT_VERSION), - ]) - }) +pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result { + let contract_version = get_contract_version(deps.storage)?; + + match contract_version.contract.as_ref() { + CONTRACT_NAME => match contract_version.version.as_ref() { + "2.0.0" => { + // vxastro and emissions_controller are optional fields, + // thus old config can be deserialized to new config + let config = CONFIG.load(deps.storage)?; + + let emissions_controller = deps + .querier + .query_wasm_smart::( + &msg.vxastro_contract, + &voting_escrow::QueryMsg::Config {}, + )? + .emissions_controller; + + CONFIG.save( + deps.storage, + &Config { + vxastro_contract: Some(Addr::unchecked(msg.vxastro_contract)), + emissions_controller: Some(emissions_controller), + ..config + }, + )?; + + let proposals = OLD_PROPOSALS + .range(deps.storage, None, None, Order::Ascending) + .collect::>>()?; + + proposals.into_iter().try_for_each(|(id, old_proposal)| { + let proposal = Proposal { + proposal_id: old_proposal.proposal_id, + submitter: old_proposal.submitter, + status: old_proposal.status, + for_power: old_proposal.for_power, + against_power: old_proposal.against_power, + start_block: old_proposal.start_block, + start_time: old_proposal.start_time, + end_block: old_proposal.end_block, + delayed_end_block: old_proposal.delayed_end_block, + expiration_block: old_proposal.expiration_block, + title: old_proposal.title, + description: old_proposal.description, + link: old_proposal.link, + messages: old_proposal.messages, + deposit_amount: old_proposal.deposit_amount, + ibc_channel: old_proposal.ibc_channel, + total_voting_power: old_proposal.total_voting_power, + }; + PROPOSALS + .save(deps.storage, id, &proposal) + .map_err(ContractError::Std) + }) + } + _ => Err(ContractError::MigrationError {}), + }, + _ => Err(ContractError::MigrationError {}), + }?; + + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + Ok(Response::new() + .add_attribute("previous_contract_name", &contract_version.contract) + .add_attribute("previous_contract_version", &contract_version.version) + .add_attribute("new_contract_name", CONTRACT_NAME) + .add_attribute("new_contract_version", CONTRACT_VERSION)) } diff --git a/contracts/assembly/src/queries.rs b/contracts/assembly/src/queries.rs index d099e1ea..989fd130 100644 --- a/contracts/assembly/src/queries.rs +++ b/contracts/assembly/src/queries.rs @@ -1,4 +1,6 @@ -use cosmwasm_std::{entry_point, to_json_binary, Binary, Deps, Env, Order, StdResult}; +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{to_json_binary, Binary, Deps, Env, Order, StdResult}; use cw_storage_plus::Bound; use astroport_governance::assembly::{ @@ -50,9 +52,6 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { } QueryMsg::UserVotingPower { user, proposal_id } => { let proposal = PROPOSALS.load(deps.storage, proposal_id)?; - - deps.api.addr_validate(&user)?; - to_json_binary(&calc_voting_power(deps, user, &proposal)?) } QueryMsg::TotalVotingPower { proposal_id } => { diff --git a/contracts/assembly/src/unit_tests.rs b/contracts/assembly/src/unit_tests.rs index 85363f5f..6669370b 100644 --- a/contracts/assembly/src/unit_tests.rs +++ b/contracts/assembly/src/unit_tests.rs @@ -124,6 +124,8 @@ fn check_proposal_validation( let config = Config { xastro_denom: XASTRO_DENOM.to_string(), xastro_denom_tracking: "".to_string(), + vxastro_contract: None, + emissions_controller: None, ibc_controller: None, builder_unlock_addr: Addr::unchecked(""), proposal_voting_period: *VOTING_PERIOD_INTERVAL.start(), @@ -172,9 +174,7 @@ fn check_proposal_validation( submitter: Addr::unchecked("creator"), status: ProposalStatus::Active, for_power: Default::default(), - outpost_for_power: Default::default(), against_power: Default::default(), - outpost_against_power: Default::default(), start_block: env.block.height, start_time: env.block.time.seconds(), end_block: env.block.height + config.proposal_voting_period, @@ -208,6 +208,8 @@ fn check_submit_ibc_proposal() { let mut config = Config { xastro_denom: XASTRO_DENOM.to_string(), xastro_denom_tracking: "".to_string(), + vxastro_contract: None, + emissions_controller: None, ibc_controller: None, builder_unlock_addr: Addr::unchecked(""), proposal_voting_period: *VOTING_PERIOD_INTERVAL.start(), @@ -280,6 +282,8 @@ fn check_execute_ibc_proposal() { let mut config = Config { xastro_denom: "".to_string(), xastro_denom_tracking: "".to_string(), + vxastro_contract: None, + emissions_controller: None, ibc_controller: None, builder_unlock_addr: Addr::unchecked(""), proposal_voting_period: *VOTING_PERIOD_INTERVAL.start(), @@ -302,9 +306,7 @@ fn check_execute_ibc_proposal() { submitter: Addr::unchecked(""), status: ProposalStatus::Passed, for_power: Default::default(), - outpost_for_power: Default::default(), against_power: Default::default(), - outpost_against_power: Default::default(), start_block: 0, start_time: 0, end_block: 0, @@ -355,6 +357,8 @@ fn check_controller_callback() { let mut config = Config { xastro_denom: "".to_string(), xastro_denom_tracking: "".to_string(), + vxastro_contract: None, + emissions_controller: None, ibc_controller: None, builder_unlock_addr: Addr::unchecked(""), proposal_voting_period: *VOTING_PERIOD_INTERVAL.start(), @@ -378,9 +382,7 @@ fn check_controller_callback() { submitter: Addr::unchecked(""), status: ProposalStatus::Active, for_power: Default::default(), - outpost_for_power: Default::default(), against_power: Default::default(), - outpost_against_power: Default::default(), start_block: 0, start_time: 0, end_block: 0, diff --git a/contracts/assembly/src/utils.rs b/contracts/assembly/src/utils.rs index 3ed2c997..757cc5f6 100644 --- a/contracts/assembly/src/utils.rs +++ b/contracts/assembly/src/utils.rs @@ -6,6 +6,7 @@ use astroport_governance::assembly::Proposal; use astroport_governance::builder_unlock::{ AllocationResponse, QueryMsg as BuilderUnlockQueryMsg, State, }; +use astroport_governance::voting_escrow; use crate::state::CONFIG; @@ -17,7 +18,7 @@ use crate::state::CONFIG; pub fn calc_voting_power(deps: Deps, sender: String, proposal: &Proposal) -> StdResult { let config = CONFIG.load(deps.storage)?; - let mut total: Uint128 = deps.querier.query_wasm_smart( + let xastro_vp: Uint128 = deps.querier.query_wasm_smart( &config.xastro_denom_tracking, &tokenfactory_tracker::QueryMsg::BalanceAt { address: sender.clone(), @@ -29,20 +30,32 @@ pub fn calc_voting_power(deps: Deps, sender: String, proposal: &Proposal) -> Std let locked_amount: AllocationResponse = deps.querier.query_wasm_smart( config.builder_unlock_addr, &BuilderUnlockQueryMsg::Allocation { - account: sender, + account: sender.clone(), timestamp: Some(proposal.start_time - 1), }, )?; - total += locked_amount.status.amount - locked_amount.status.astro_withdrawn; + let vxastro_vp = if let Some(vxastro_contract) = &config.vxastro_contract { + deps.querier.query_wasm_smart( + vxastro_contract, + &voting_escrow::QueryMsg::UserVotingPower { + user: sender, + timestamp: Some(proposal.start_time - 1), + }, + )? + } else { + Uint128::zero() + }; - Ok(total) + Ok(xastro_vp + + (locked_amount.status.amount - locked_amount.status.astro_withdrawn) + + vxastro_vp) } /// Calculates the combined total voting power at a specified timestamp (that is relevant for a specific proposal). /// Combined voting power includes: /// * xASTRO total supply -/// * ASTRO tokens which still locked in the builder's unlock contract +/// * ASTRO tokens which are still locked in the builder's unlock contract /// /// ## Parameters /// * **config** contract settings. @@ -52,7 +65,7 @@ pub fn calc_total_voting_power_at( config: &Config, timestamp: u64, ) -> StdResult { - let mut total: Uint128 = querier.query_wasm_smart( + let total: Uint128 = querier.query_wasm_smart( &config.xastro_denom_tracking, &tokenfactory_tracker::QueryMsg::TotalSupplyAt { timestamp: Some(timestamp), @@ -67,7 +80,5 @@ pub fn calc_total_voting_power_at( }, )?; - total += builder_state.remaining_astro_tokens; - - Ok(total) + Ok(total + builder_state.remaining_astro_tokens) } diff --git a/contracts/assembly/tests/assembly_integration.rs b/contracts/assembly/tests/assembly_integration.rs index 6c05df5b..8a2e38e1 100644 --- a/contracts/assembly/tests/assembly_integration.rs +++ b/contracts/assembly/tests/assembly_integration.rs @@ -636,6 +636,7 @@ fn test_update_config() { proposal_required_threshold: None, whitelist_remove: None, whitelist_add: None, + vxastro: None, })), &[], ) @@ -651,11 +652,12 @@ fn test_update_config() { proposal_voting_period: Some(*VOTING_PERIOD_INTERVAL.end()), proposal_effective_delay: Some(*DELAY_INTERVAL.end()), proposal_expiration_period: Some(*EXPIRATION_PERIOD_INTERVAL.end()), - proposal_required_deposit: Some(*DEPOSIT_INTERVAL.end()), - proposal_required_quorum: Some("0.5".to_string()), - proposal_required_threshold: Some("0.5".to_string()), + proposal_required_deposit: Some((*DEPOSIT_INTERVAL.end()).into()), + proposal_required_quorum: Some(Decimal::percent(50)), + proposal_required_threshold: Some(Decimal::percent(50)), whitelist_remove: Some(vec!["https://some.link/".to_string()]), whitelist_add: Some(vec!["https://another.link/".to_string()]), + vxastro: None, }; helper @@ -716,10 +718,12 @@ fn test_voting_power() { struct TestBalance { xastro: u128, builder_allocation: u128, + vxastro: u128, } let mut total_xastro = 0u128; let mut total_builder_allocation = 0u128; + let mut total_vxastro = 0u128; let users_num = 100; let balances: HashMap = (1..=users_num) @@ -728,14 +732,19 @@ fn test_voting_power() { let balances = TestBalance { xastro: i * 1_000000, builder_allocation: if i % 2 == 0 { i * 1_000000 } else { 0 }, + vxastro: if i % 2 == 1 { i * 1_000000 } else { 0 }, }; helper.get_xastro(&user, balances.xastro); if balances.builder_allocation > 0 { helper.create_builder_allocation(&user, balances.builder_allocation); } + if balances.vxastro > 0 { + helper.get_vxastro(&user, balances.vxastro); + } total_xastro += balances.xastro; total_builder_allocation += balances.builder_allocation; + total_vxastro += balances.vxastro; (user, balances) }) @@ -752,14 +761,14 @@ fn test_voting_power() { let proposal = helper.proposal(1); assert_eq!( proposal.total_voting_power.u128(), - total_xastro + total_builder_allocation + 1001 + total_xastro + total_builder_allocation + total_vxastro + 1001 ); // First 40 users vote against the proposal let mut against_power = 0u128; balances.iter().take(40).for_each(|(addr, balances)| { helper.next_block(100); - against_power += balances.xastro + balances.builder_allocation; + against_power += balances.xastro + balances.builder_allocation + balances.vxastro; helper .cast_vote(1, addr, ProposalVoteOption::Against) .unwrap(); @@ -776,7 +785,7 @@ fn test_voting_power() { .take(40) .for_each(|(addr, balances)| { helper.next_block(100); - for_power += balances.xastro + balances.builder_allocation; + for_power += balances.xastro + balances.builder_allocation + balances.vxastro; helper.cast_vote(1, addr, ProposalVoteOption::For).unwrap(); }); @@ -787,7 +796,7 @@ fn test_voting_power() { let proposal = helper.proposal(1); assert_eq!( proposal.total_voting_power.u128(), - total_xastro + total_builder_allocation + 1001 + total_xastro + total_builder_allocation + total_vxastro + 1001 ); helper.next_block_height(PROPOSAL_VOTING_PERIOD); @@ -798,7 +807,7 @@ fn test_voting_power() { assert_eq!( proposal.total_voting_power.u128(), - total_xastro + total_builder_allocation + 1001 + total_xastro + total_builder_allocation + total_vxastro + 1001 ); assert_eq!(proposal.submitter, submitter.clone()); assert_eq!(proposal.status, ProposalStatus::Passed); diff --git a/contracts/assembly/tests/common/helper.rs b/contracts/assembly/tests/common/helper.rs index d1e195be..e2551541 100644 --- a/contracts/assembly/tests/common/helper.rs +++ b/contracts/assembly/tests/common/helper.rs @@ -2,6 +2,7 @@ use anyhow::Result as AnyResult; use astroport::staking; +use astroport::token::Logo; use cosmwasm_std::testing::MockApi; use cosmwasm_std::{ coin, coins, Addr, BankMsg, Binary, Coin, CosmosMsg, Decimal, Deps, DepsMut, Empty, Env, @@ -14,11 +15,13 @@ use cw_multi_test::{ use astroport_governance::assembly::{ ExecuteMsg, InstantiateMsg, Proposal, ProposalVoteOption, ProposalVoterResponse, - ProposalVotesResponse, QueryMsg, DELAY_INTERVAL, DEPOSIT_INTERVAL, EXPIRATION_PERIOD_INTERVAL, - MINIMUM_PROPOSAL_REQUIRED_QUORUM_PERCENTAGE, MINIMUM_PROPOSAL_REQUIRED_THRESHOLD_PERCENTAGE, - VOTING_PERIOD_INTERVAL, + ProposalVotesResponse, QueryMsg, UpdateConfig, DELAY_INTERVAL, DEPOSIT_INTERVAL, + EXPIRATION_PERIOD_INTERVAL, MINIMUM_PROPOSAL_REQUIRED_QUORUM_PERCENTAGE, + MINIMUM_PROPOSAL_REQUIRED_THRESHOLD_PERCENTAGE, VOTING_PERIOD_INTERVAL, }; use astroport_governance::builder_unlock::{CreateAllocationParams, Schedule}; +use astroport_governance::voting_escrow::UpdateMarketingInfo; +use astroport_governance::{emissions_controller, voting_escrow}; use crate::common::stargate::StargateKeeper; @@ -54,6 +57,14 @@ fn assembly_contract() -> Box> { )) } +fn vxastro_contract() -> Box> { + Box::new(ContractWrapper::new_with_empty( + astroport_voting_escrow::contract::execute, + astroport_voting_escrow::contract::instantiate, + astroport_voting_escrow::contract::query, + )) +} + fn builder_contract() -> Box> { Box::new(ContractWrapper::new_with_empty( builder_unlock::contract::execute, @@ -83,6 +94,31 @@ pub fn noop_contract() -> Box> { )) } +fn mock_emissions_controller() -> Box> { + fn instantiate( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: Empty, + ) -> StdResult { + Ok(Response::default()) + } + fn execute( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: emissions_controller::msg::ExecuteMsg, + ) -> StdResult { + Ok(Response::default()) + } + + fn query(_deps: Deps, _env: Env, _msg: Empty) -> StdResult { + unimplemented!() + } + + Box::new(ContractWrapper::new_with_empty(execute, instantiate, query)) +} + pub const PROPOSAL_REQUIRED_DEPOSIT: Uint128 = Uint128::new(*DEPOSIT_INTERVAL.start()); pub const PROPOSAL_VOTING_PERIOD: u64 = *VOTING_PERIOD_INTERVAL.start(); pub const PROPOSAL_DELAY: u64 = *DELAY_INTERVAL.start(); @@ -91,11 +127,7 @@ pub const PROPOSAL_EXPIRATION: u64 = *EXPIRATION_PERIOD_INTERVAL.start(); pub fn default_init_msg(staking: &Addr, builder_unlock: &Addr) -> InstantiateMsg { InstantiateMsg { staking_addr: staking.to_string(), - vxastro_token_addr: None, - voting_escrow_delegator_addr: None, ibc_controller: None, - generator_controller_addr: None, - hub_addr: None, builder_unlock_addr: builder_unlock.to_string(), proposal_voting_period: PROPOSAL_VOTING_PERIOD, proposal_effective_delay: PROPOSAL_DELAY, @@ -132,6 +164,7 @@ pub struct Helper { pub assembly: Addr, pub builder_unlock: Addr, pub xastro_denom: String, + pub vxastro: Addr, pub assembly_code_id: u64, } @@ -151,6 +184,8 @@ impl Helper { let staking_code_id = app.store_code(staking_contract()); let tracker_code_id = app.store_code(tracker_contract()); let assembly_code_id = app.store_code(assembly_contract()); + let vxastro_code_id = app.store_code(vxastro_contract()); + let emissions_controller_code_id = app.store_code(mock_emissions_controller()); let msg = staking::InstantiateMsg { deposit_token_denom: ASTRO_DENOM.to_string(), @@ -173,6 +208,36 @@ impl Helper { .query_wasm_smart(&staking, &staking::QueryMsg::Config {}) .unwrap(); + let mocked_emission_controller = app + .instantiate_contract( + emissions_controller_code_id, + owner.clone(), + &Empty {}, + &[], + "label", + None, + ) + .unwrap(); + let vxastro = app + .instantiate_contract( + vxastro_code_id, + owner.clone(), + &voting_escrow::InstantiateMsg { + deposit_denom: xastro_denom.to_string(), + emissions_controller: mocked_emission_controller.to_string(), + marketing: Some(UpdateMarketingInfo { + project: None, + description: None, + marketing: Some(owner.to_string()), + logo: Some(Logo::Url("https://example.com".to_string())), + }), + }, + &[], + "label", + None, + ) + .unwrap(); + let builder_unlock_code_id = app.store_code(builder_contract()); let msg = astroport_governance::builder_unlock::InstantiateMsg { @@ -203,6 +268,26 @@ impl Helper { ) .unwrap(); + app.execute_contract( + assembly.clone(), + assembly.clone(), + &ExecuteMsg::UpdateConfig(Box::new(UpdateConfig { + ibc_controller: None, + builder_unlock_addr: None, + proposal_voting_period: None, + proposal_effective_delay: None, + proposal_expiration_period: None, + proposal_required_deposit: None, + proposal_required_quorum: None, + proposal_required_threshold: None, + whitelist_remove: None, + whitelist_add: None, + vxastro: Some(vxastro.to_string()), + })), + &[], + ) + .unwrap(); + app.execute( owner.clone(), WasmMsg::UpdateAdmin { @@ -220,6 +305,7 @@ impl Helper { assembly, builder_unlock, xastro_denom, + vxastro, assembly_code_id, }) } @@ -257,6 +343,19 @@ impl Helper { self.stake(recipient, amount.into()).unwrap() } + pub fn get_vxastro(&mut self, recipient: &Addr, amount: impl Into + Copy) -> AppResponse { + let amount = amount.into(); + self.get_xastro(recipient, amount); + self.app + .execute_contract( + recipient.clone(), + self.vxastro.clone(), + &voting_escrow::ExecuteMsg::Lock { receiver: None }, + &coins(amount, &self.xastro_denom), + ) + .unwrap() + } + pub fn create_builder_allocation(&mut self, recipient: &Addr, amount: u128) { self.app .execute_contract( diff --git a/contracts/assembly/tests/common/stargate.rs b/contracts/assembly/tests/common/stargate.rs index f15f01b2..a54b9291 100644 --- a/contracts/assembly/tests/common/stargate.rs +++ b/contracts/assembly/tests/common/stargate.rs @@ -78,10 +78,7 @@ impl Stargate for StargateKeeper { burn_msg.into(), ) } - MsgSetDenomMetadata::TYPE_URL => { - // TODO: Implement this if needed - Ok(AppResponse::default()) - } + MsgSetDenomMetadata::TYPE_URL => Ok(AppResponse::default()), MsgSetBeforeSendHook::TYPE_URL => { let tf_msg: MsgSetBeforeSendHook = value.try_into()?; diff --git a/contracts/emissions_controller/Cargo.toml b/contracts/emissions_controller/Cargo.toml index a1416cbc..9230ac03 100644 --- a/contracts/emissions_controller/Cargo.toml +++ b/contracts/emissions_controller/Cargo.toml @@ -29,13 +29,16 @@ neutron-sdk = "0.10.0" serde_json = "1" [dev-dependencies] -cw-multi-test = { version = "1", features = ["cosmwasm_1_1"] } +cw-multi-test = { git = "https://github.com/astroport-fi/cw-multi-test", branch = "feat/bank_with_send_hooks_1_0", features = ["cosmwasm_1_1"] } astroport-voting-escrow = { path = "../voting_escrow", version = "1", features = ["library"] } +astro-assembly = { path = "../assembly", version = "2", features = ["library"] } +builder-unlock = { path = "../builder_unlock", version = "3", features = ["library"] } astroport-factory = { version = "1.7", features = ["library"] } astroport-pair = { version = "1.5", features = ["library"] } cw20-base = { version = "1", features = ["library"] } astroport-incentives = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "1.2", features = ["library"] } astroport-staking = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "2.1" } +astroport-tokenfactory-tracker = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "1" } osmosis-std = "0.21.0" derivative = "2.2" anyhow = "1" diff --git a/contracts/emissions_controller/src/execute.rs b/contracts/emissions_controller/src/execute.rs index 8ca8c981..b9b0404c 100644 --- a/contracts/emissions_controller/src/execute.rs +++ b/contracts/emissions_controller/src/execute.rs @@ -6,8 +6,9 @@ use astroport::incentives; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - attr, ensure, wasm_execute, BankMsg, Coin, CosmosMsg, Decimal, DepsMut, Env, Fraction, - MessageInfo, Order, Response, StdError, StdResult, Storage, Uint128, + attr, ensure, to_json_binary, wasm_execute, BankMsg, Coin, CosmosMsg, Decimal, DepsMut, Env, + Fraction, IbcMsg, IbcTimeout, MessageInfo, Order, Response, StdError, StdResult, Storage, + Uint128, }; use cw_utils::{must_pay, nonpayable}; use itertools::Itertools; @@ -15,16 +16,16 @@ use neutron_sdk::bindings::msg::NeutronMsg; use neutron_sdk::bindings::query::NeutronQuery; use astroport_governance::emissions_controller::consts::{ - EPOCH_LENGTH, MAX_POOLS_TO_VOTE, VOTE_COOLDOWN, + EPOCH_LENGTH, IBC_TIMEOUT, MAX_POOLS_TO_VOTE, VOTE_COOLDOWN, }; use astroport_governance::emissions_controller::hub::{ AstroPoolConfig, HubMsg, OutpostInfo, OutpostParams, OutpostStatus, TuneInfo, UserInfo, VotedPoolInfo, }; -use astroport_governance::emissions_controller::msg::ExecuteMsg; +use astroport_governance::emissions_controller::msg::{ExecuteMsg, VxAstroIbcMsg}; use astroport_governance::emissions_controller::utils::{check_lp_token, get_voting_power}; use astroport_governance::utils::check_contract_supports_channel; -use astroport_governance::voting_escrow; +use astroport_governance::{assembly, voting_escrow}; use crate::error::ContractError; use crate::state::{ @@ -163,7 +164,7 @@ pub fn execute( emissions_multiple, max_astro, } => update_config( - deps.into_empty(), + deps, info, pools_limit, whitelisting_fee, @@ -171,6 +172,7 @@ pub fn execute( emissions_multiple, max_astro, ), + HubMsg::RegisterProposal { proposal_id } => register_proposal(deps, env, proposal_id), }, } } @@ -676,7 +678,7 @@ pub fn tune_pools( /// Permissioned to the contract owner. /// Updates the contract configuration. pub fn update_config( - deps: DepsMut, + deps: DepsMut, info: MessageInfo, pools_limit: Option, whitelisting_fee: Option, @@ -725,3 +727,59 @@ pub fn update_config( Ok(Response::default().add_attributes(attrs)) } + +/// Register an active proposal on all available outposts. +/// Endpoint is permissionless so anyone can retry to register a proposal in case of IBC timeout. +pub fn register_proposal( + deps: DepsMut, + env: Env, + proposal_id: u64, +) -> Result, ContractError> { + let config = CONFIG.load(deps.storage)?; + // Ensure a proposal exists and active + let proposal = deps + .querier + .query_wasm_smart::( + &config.assembly, + &assembly::QueryMsg::Proposal { proposal_id }, + ) + .and_then(|proposal| { + ensure!( + env.block.height <= proposal.end_block, + StdError::generic_err("Proposal is not active") + ); + + Ok(proposal) + })?; + + let outposts = OUTPOSTS + .range(deps.storage, None, None, Order::Ascending) + .collect::>>()?; + + let data = to_json_binary(&VxAstroIbcMsg::RegisterProposal { + proposal_id, + start_time: proposal.start_time, + })?; + let timeout = IbcTimeout::from(env.block.time.plus_seconds(IBC_TIMEOUT)); + + let mut attrs = vec![("action", "register_proposal")]; + + let ibc_messages: Vec> = outposts + .iter() + .filter_map(|(outpost, outpost_info)| { + attrs.push(("outpost", outpost)); + outpost_info.params.as_ref().map(|params| { + IbcMsg::SendPacket { + channel_id: params.voting_channel.clone(), + data: data.clone(), + timeout: timeout.clone(), + } + .into() + }) + }) + .collect(); + + Ok(Response::new() + .add_messages(ibc_messages) + .add_attributes(attrs)) +} diff --git a/contracts/emissions_controller/src/ibc.rs b/contracts/emissions_controller/src/ibc.rs index ed5831be..ee8d51ff 100644 --- a/contracts/emissions_controller/src/ibc.rs +++ b/contracts/emissions_controller/src/ibc.rs @@ -1,17 +1,21 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - ensure, from_json, DepsMut, Env, Ibc3ChannelOpenResponse, IbcBasicResponse, IbcChannelCloseMsg, - IbcChannelConnectMsg, IbcChannelOpenMsg, IbcPacketAckMsg, IbcPacketReceiveMsg, - IbcPacketTimeoutMsg, IbcReceiveResponse, Never, Order, StdError, StdResult, + ensure, from_json, wasm_execute, DepsMut, Env, Ibc3ChannelOpenResponse, IbcBasicResponse, + IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg, IbcPacketAckMsg, + IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse, Never, Order, StdError, + StdResult, }; +use astroport_governance::assembly; use astroport_governance::emissions_controller::consts::{IBC_APP_VERSION, IBC_ORDERING}; -use astroport_governance::emissions_controller::msg::{ack_fail, ack_ok, VxAstroIbcMsg}; +use astroport_governance::emissions_controller::msg::{ + ack_fail, ack_ok, IbcAckResult, VxAstroIbcMsg, +}; use crate::error::ContractError; use crate::execute::{handle_update_user, handle_vote}; -use crate::state::OUTPOSTS; +use crate::state::{CONFIG, OUTPOSTS}; #[cfg_attr(not(feature = "library"), entry_point)] pub fn ibc_channel_open( @@ -102,7 +106,7 @@ pub fn do_packet_receive( })?; match from_json(&msg.packet.data)? { - VxAstroIbcMsg::Vote { + VxAstroIbcMsg::EmissionsVote { voter, voting_power, votes, @@ -122,6 +126,31 @@ pub fn do_packet_receive( .set_ack(ack_ok()) }, ), + VxAstroIbcMsg::GovernanceVote { + voter, + voting_power, + proposal_id, + vote, + } => { + let config = CONFIG.load(deps.storage)?; + let cast_vote_msg = wasm_execute( + config.assembly, + &assembly::ExecuteMsg::CastVoteOutpost { + voter, + voting_power, + proposal_id, + vote, + }, + vec![], + )?; + + Ok(IbcReceiveResponse::new() + .add_message(cast_vote_msg) + .set_ack(ack_ok())) + } + VxAstroIbcMsg::RegisterProposal { .. } => { + unreachable!("Hub can't receive RegisterProposal message") + } } } @@ -130,9 +159,14 @@ pub fn do_packet_receive( pub fn ibc_packet_ack( _deps: DepsMut, _env: Env, - _msg: IbcPacketAckMsg, + msg: IbcPacketAckMsg, ) -> StdResult { - unimplemented!("This contract is only receiving IBC messages") + match from_json(msg.acknowledgement.data)? { + IbcAckResult::Ok(_) => { + Ok(IbcBasicResponse::default().add_attribute("action", "ibc_packet_ack")) + } + IbcAckResult::Error(err) => Ok(IbcBasicResponse::default().add_attribute("error", err)), + } } #[cfg(not(tarpaulin_include))] @@ -142,7 +176,7 @@ pub fn ibc_packet_timeout( _env: Env, _msg: IbcPacketTimeoutMsg, ) -> StdResult { - unimplemented!("This contract is only receiving IBC messages") + Ok(IbcBasicResponse::default().add_attribute("action", "ibc_packet_timeout")) } #[cfg(not(tarpaulin_include))] @@ -303,7 +337,7 @@ mod unit_tests { fn test_packet_receive() { let mut deps = mock_custom_dependencies(); - let voting_msg = VxAstroIbcMsg::Vote { + let voting_msg = VxAstroIbcMsg::EmissionsVote { voter: "osmo1voter".to_string(), voting_power: 1000u128.into(), votes: HashMap::from([("osmo1pool1".to_string(), Decimal::one())]), diff --git a/contracts/emissions_controller/src/instantiate.rs b/contracts/emissions_controller/src/instantiate.rs index f90d5062..28c0fa5a 100644 --- a/contracts/emissions_controller/src/instantiate.rs +++ b/contracts/emissions_controller/src/instantiate.rs @@ -55,6 +55,7 @@ pub fn instantiate( let config = Config { owner: deps.api.addr_validate(&msg.owner)?, + assembly: deps.api.addr_validate(&msg.assembly)?, vxastro: Addr::unchecked(""), incentives_addr: query_incentives_addr(deps.querier, &factory)?, factory, diff --git a/contracts/emissions_controller/src/sudo.rs b/contracts/emissions_controller/src/sudo.rs index 81b46ebe..9657f4c4 100644 --- a/contracts/emissions_controller/src/sudo.rs +++ b/contracts/emissions_controller/src/sudo.rs @@ -6,7 +6,7 @@ use neutron_sdk::sudo::msg::{RequestPacket, TransferSudoMsg}; use astroport_governance::emissions_controller::hub::OutpostStatus; use crate::state::TUNE_INFO; -use crate::utils::get_outpost_from_hub_ics20_channel; +use crate::utils::get_outpost_from_hub_channel; #[cfg_attr(not(feature = "library"), entry_point)] pub fn sudo(deps: DepsMut, env: Env, msg: TransferSudoMsg) -> StdResult { @@ -29,7 +29,11 @@ pub fn process_ibc_reply( packet: RequestPacket, failed: bool, ) -> StdResult { - let outpost = get_outpost_from_hub_ics20_channel(storage, packet.source_channel)?; + let source_channel = packet + .source_channel + .ok_or_else(|| StdError::generic_err("Missing source_channel in IBC ack packet"))?; + let outpost = + get_outpost_from_hub_channel(storage, source_channel, |params| ¶ms.ics20_channel)?; let mut tune_info = TUNE_INFO.load(storage)?; tune_info diff --git a/contracts/emissions_controller/src/utils.rs b/contracts/emissions_controller/src/utils.rs index 8b3c7a5b..0812a349 100644 --- a/contracts/emissions_controller/src/utils.rs +++ b/contracts/emissions_controller/src/utils.rs @@ -74,20 +74,18 @@ pub fn validate_outpost_prefix(value: &str, prefix: &str) -> Result<(), Contract .map(|_| ()) } -/// Helper function to get outpost prefix from the ICS20 IBC packet. -pub fn get_outpost_from_hub_ics20_channel( +/// Helper function to get outpost prefix from an IBC channel. +pub fn get_outpost_from_hub_channel( store: &dyn Storage, - source_channel: Option, + source_channel: String, + get_channel_closure: impl Fn(&OutpostParams) -> &String, ) -> StdResult { - let source_channel = source_channel - .ok_or_else(|| StdError::generic_err("Missing source_channel in IBC ack packet"))?; - // Find outpost by ics20 channel OUTPOSTS .range(store, None, None, Order::Ascending) .find_map(|data| { let (outpost_prefix, outpost) = data.ok()?; outpost.params.as_ref().and_then(|params| { - if source_channel == params.ics20_channel { + if get_channel_closure(params).eq(&source_channel) { Some(outpost_prefix.clone()) } else { None diff --git a/contracts/emissions_controller/tests/common/contracts.rs b/contracts/emissions_controller/tests/common/contracts.rs index 2e169bff..2089132c 100644 --- a/contracts/emissions_controller/tests/common/contracts.rs +++ b/contracts/emissions_controller/tests/common/contracts.rs @@ -1,12 +1,18 @@ use std::fmt::Debug; +use cosmwasm_schema::cw_serde; use cosmwasm_schema::schemars::JsonSchema; use cosmwasm_std::{ - Binary, CustomMsg, CustomQuery, Deps, DepsMut, Empty, Env, MessageInfo, Response, StdResult, + Binary, CustomMsg, CustomQuery, DepsMut, Empty, Env, IbcPacketReceiveMsg, MessageInfo, + Response, StdResult, }; use cw_multi_test::{Contract, ContractWrapper}; use neutron_sdk::bindings::msg::NeutronMsg; use neutron_sdk::bindings::query::NeutronQuery; +use neutron_sdk::sudo::msg::RequestPacket; + +use astroport_emissions_controller::ibc::ibc_packet_receive; +use astroport_emissions_controller::sudo::process_ibc_reply; pub fn token_contract() -> Box> where @@ -74,6 +80,40 @@ where ) } +/// Extended version of [`TransferSudoMsg`] with additional variants to test IBC endpoints. +#[cw_serde] +pub enum TestSudoMsg { + Response { + request: RequestPacket, + data: Binary, + }, + Error { + request: RequestPacket, + details: String, + }, + Timeout { + request: RequestPacket, + }, + IbcRecv(IbcPacketReceiveMsg), +} + +fn emissions_controller_sudo(deps: DepsMut, env: Env, msg: TestSudoMsg) -> StdResult { + match msg { + TestSudoMsg::Response { request, .. } => { + process_ibc_reply(deps.storage, env, request, false) + } + TestSudoMsg::Error { request, .. } | TestSudoMsg::Timeout { request } => { + process_ibc_reply(deps.storage, env, request, true) + } + TestSudoMsg::IbcRecv(packet) => { + let ibc_response = ibc_packet_receive(deps, env, packet).unwrap(); + Ok(Response::default() + .add_attributes(ibc_response.attributes) + .add_submessages(ibc_response.messages)) + } + } +} + pub fn emissions_controller() -> Box> { Box::new( ContractWrapper::new( @@ -81,7 +121,7 @@ pub fn emissions_controller() -> Box> { astroport_emissions_controller::instantiate::instantiate, astroport_emissions_controller::query::query, ) - .with_sudo_empty(astroport_emissions_controller::sudo::sudo) + .with_sudo_empty(emissions_controller_sudo) .with_reply_empty(astroport_emissions_controller::instantiate::reply), ) } @@ -102,15 +142,42 @@ where } pub fn tracker_contract() -> Box> +where + T: CustomMsg + Clone + Debug + PartialEq + JsonSchema + 'static, + C: CustomQuery + for<'de> cosmwasm_schema::serde::Deserialize<'de> + 'static, +{ + Box::new( + ContractWrapper::new_with_empty( + |_: DepsMut, _: Env, _: MessageInfo, _: Empty| -> StdResult { + unimplemented!() + }, + astroport_tokenfactory_tracker::contract::instantiate, + astroport_tokenfactory_tracker::query::query, + ) + .with_sudo_empty(astroport_tokenfactory_tracker::contract::sudo), + ) +} + +pub fn assembly_contract() -> Box> +where + T: CustomMsg + Clone + Debug + PartialEq + JsonSchema + 'static, + C: CustomQuery + for<'de> cosmwasm_schema::serde::Deserialize<'de> + 'static, +{ + Box::new(ContractWrapper::new_with_empty( + astro_assembly::contract::execute, + astro_assembly::contract::instantiate, + astro_assembly::queries::query, + )) +} + +pub fn builder_unlock_contract() -> Box> where T: CustomMsg + Clone + Debug + PartialEq + JsonSchema + 'static, C: CustomQuery + for<'de> cosmwasm_schema::serde::Deserialize<'de> + 'static, { Box::new(ContractWrapper::new_with_empty( - |_: DepsMut, _: Env, _: MessageInfo, _: Empty| -> StdResult { unimplemented!() }, - |_: DepsMut, _: Env, _: MessageInfo, _: Empty| -> StdResult { - Ok(Response::default()) - }, - |_: Deps, _: Env, _: Empty| -> StdResult { unimplemented!() }, + builder_unlock::contract::execute, + builder_unlock::contract::instantiate, + builder_unlock::query::query, )) } diff --git a/contracts/emissions_controller/tests/common/helper.rs b/contracts/emissions_controller/tests/common/helper.rs index c08ab644..8b0db98a 100644 --- a/contracts/emissions_controller/tests/common/helper.rs +++ b/contracts/emissions_controller/tests/common/helper.rs @@ -4,8 +4,8 @@ use astroport::incentives::RewardInfo; use astroport::token::{Logo, MinterResponse}; use astroport::{factory, incentives, staking}; use cosmwasm_std::{ - coin, coins, from_json, to_json_binary, Addr, BlockInfo, Coin, Decimal, Empty, MemoryStorage, - StdResult, Timestamp, Uint128, + coin, coins, from_json, to_json_binary, Addr, BlockInfo, Coin, Decimal, Empty, IbcEndpoint, + IbcPacket, IbcPacketReceiveMsg, MemoryStorage, StdResult, Timestamp, Uint128, }; use cw_multi_test::error::AnyResult; use cw_multi_test::{ @@ -17,19 +17,30 @@ use itertools::Itertools; use neutron_sdk::bindings::msg::NeutronMsg; use neutron_sdk::bindings::query::NeutronQuery; +use astroport_governance::assembly::{ + ExecuteMsg, UpdateConfig, DELAY_INTERVAL, DEPOSIT_INTERVAL, EXPIRATION_PERIOD_INTERVAL, + MINIMUM_PROPOSAL_REQUIRED_QUORUM_PERCENTAGE, MINIMUM_PROPOSAL_REQUIRED_THRESHOLD_PERCENTAGE, + VOTING_PERIOD_INTERVAL, +}; use astroport_governance::emissions_controller::consts::EPOCHS_START; use astroport_governance::emissions_controller::hub::{ EmissionsState, HubInstantiateMsg, HubMsg, OutpostInfo, SimulateTuneResponse, TuneInfo, UserInfoResponse, VotedPoolInfo, }; +use astroport_governance::emissions_controller::msg::VxAstroIbcMsg; use astroport_governance::voting_escrow::UpdateMarketingInfo; -use astroport_governance::{emissions_controller, voting_escrow}; +use astroport_governance::{assembly, emissions_controller, voting_escrow}; use crate::common::contracts::*; use crate::common::ibc_module::IbcMockModule; use crate::common::neutron_module::MockNeutronModule; use crate::common::stargate::StargateModule; +pub const PROPOSAL_REQUIRED_DEPOSIT: Uint128 = Uint128::new(*DEPOSIT_INTERVAL.start()); +pub const PROPOSAL_VOTING_PERIOD: u64 = *VOTING_PERIOD_INTERVAL.start(); +pub const PROPOSAL_DELAY: u64 = *DELAY_INTERVAL.start(); +pub const PROPOSAL_EXPIRATION: u64 = *EXPIRATION_PERIOD_INTERVAL.start(); + pub type NeutronApp = App< BankKeeper, MockApiBech32, @@ -65,6 +76,7 @@ pub struct ControllerHelper { #[derivative(Debug = "ignore")] pub app: NeutronApp, pub owner: Addr, + pub assembly: Addr, pub astro: String, pub xastro: String, pub factory: Addr, @@ -89,6 +101,8 @@ impl ControllerHelper { let incentives_code_id = app.store_code(incentives_contract()); let staking_code_id = app.store_code(staking_contract()); let tracker_code_id = app.store_code(tracker_contract()); + let assembly_code_id = app.store_code(assembly_contract()); + let builder_code_id = app.store_code(builder_unlock_contract()); let factory = app .instantiate_contract( @@ -190,6 +204,49 @@ impl ControllerHelper { ) .unwrap(); + let builder_unlock_addr = app + .instantiate_contract( + builder_code_id, + owner.clone(), + &astroport_governance::builder_unlock::InstantiateMsg { + owner: owner.to_string(), + astro_denom: astro_denom.to_string(), + max_allocations_amount: Default::default(), + }, + &[], + "label", + None, + ) + .unwrap(); + + let assembly = app + .instantiate_contract( + assembly_code_id, + owner.clone(), + &assembly::InstantiateMsg { + staking_addr: staking.to_string(), + ibc_controller: None, + builder_unlock_addr: builder_unlock_addr.to_string(), + proposal_voting_period: PROPOSAL_VOTING_PERIOD, + proposal_effective_delay: PROPOSAL_DELAY, + proposal_expiration_period: PROPOSAL_EXPIRATION, + proposal_required_deposit: PROPOSAL_REQUIRED_DEPOSIT, + proposal_required_quorum: MINIMUM_PROPOSAL_REQUIRED_QUORUM_PERCENTAGE + .to_string(), + proposal_required_threshold: Decimal::from_atomics( + MINIMUM_PROPOSAL_REQUIRED_THRESHOLD_PERCENTAGE, + 2, + ) + .unwrap() + .to_string(), + whitelisted_links: vec!["https://some.link/".to_string()], + }, + &[], + "label", + None, + ) + .unwrap(); + let whitelisting_fee = coin(1_000_000, astro_denom); let emission_controller = app .instantiate_contract( @@ -197,6 +254,7 @@ impl ControllerHelper { owner.clone(), &HubInstantiateMsg { owner: owner.to_string(), + assembly: assembly.to_string(), vxastro_code_id, vxastro_marketing_info: Some(UpdateMarketingInfo { project: None, @@ -230,6 +288,26 @@ impl ControllerHelper { .unwrap() .vxastro; + app.execute_contract( + assembly.clone(), + assembly.clone(), + &ExecuteMsg::UpdateConfig(Box::new(UpdateConfig { + ibc_controller: None, + builder_unlock_addr: None, + proposal_voting_period: None, + proposal_effective_delay: None, + proposal_expiration_period: None, + proposal_required_deposit: None, + proposal_required_quorum: None, + proposal_required_threshold: None, + whitelist_remove: None, + whitelist_add: None, + vxastro: Some(vxastro.to_string()), + })), + &[], + ) + .unwrap(); + let helper = Self { app, owner, @@ -241,6 +319,7 @@ impl ControllerHelper { whitelisting_fee, emission_controller, incentives, + assembly, }; dbg!(&helper); @@ -314,12 +393,18 @@ impl ControllerHelper { }) } - pub fn user_vp(&self, user: &Addr, time: Option) -> StdResult { + pub fn blocktravel(&mut self, blocks: u64) { + self.app.update_block(|block| { + block.height += blocks; + }) + } + + pub fn user_vp(&self, user: &Addr, timestamp: Option) -> StdResult { self.app.wrap().query_wasm_smart( &self.vxastro, &voting_escrow::QueryMsg::UserVotingPower { user: user.to_string(), - time, + timestamp, }, ) } @@ -499,6 +584,57 @@ impl ControllerHelper { }) } + pub fn submit_proposal(&mut self, submitter: &Addr) -> AnyResult { + let deposit = coins(PROPOSAL_REQUIRED_DEPOSIT.u128(), &self.xastro); + self.mint_tokens(submitter, &deposit).unwrap(); + self.app.execute_contract( + submitter.clone(), + self.assembly.clone(), + &ExecuteMsg::SubmitProposal { + title: "Test title".to_string(), + description: "Test description".to_string(), + link: None, + messages: vec![], + ibc_channel: None, + }, + &deposit, + ) + } + + pub fn register_proposal(&mut self, proposal_id: u64) -> AnyResult { + self.app.execute_contract( + self.owner.clone(), + self.emission_controller.clone(), + &emissions_controller::msg::ExecuteMsg::Custom(HubMsg::RegisterProposal { + proposal_id, + }), + &[], + ) + } + + pub fn mock_packet_receive(&mut self, ibc_msg: VxAstroIbcMsg) -> AnyResult { + let packet = IbcPacketReceiveMsg::new( + IbcPacket::new( + to_json_binary(&ibc_msg).unwrap(), + IbcEndpoint { + port_id: "".to_string(), + channel_id: "".to_string(), + }, + IbcEndpoint { + port_id: "".to_string(), + channel_id: "channel-1".to_string(), + }, + 0, + Timestamp::from_seconds(0).into(), + ), + Addr::unchecked("relayer"), + ); + self.app.wasm_sudo( + self.emission_controller.clone(), + &TestSudoMsg::IbcRecv(packet), + ) + } + pub fn reset_astro_reward(&mut self, lp_token: &Addr) -> AnyResult { // Mocking LP provide and depositing to incentives contract // NOTE: diff --git a/contracts/emissions_controller/tests/common/ibc_module.rs b/contracts/emissions_controller/tests/common/ibc_module.rs index c84a9a82..d8cad01c 100644 --- a/contracts/emissions_controller/tests/common/ibc_module.rs +++ b/contracts/emissions_controller/tests/common/ibc_module.rs @@ -28,7 +28,7 @@ impl Module for IbcMockModule { ExecC: CustomMsg + DeserializeOwned + 'static, QueryC: CustomQuery + DeserializeOwned + 'static, { - unimplemented!() + Ok(AppResponse::default()) } fn query( diff --git a/contracts/emissions_controller/tests/emissions_controller_integration.rs b/contracts/emissions_controller/tests/emissions_controller_integration.rs index 53eac56f..fa9e8461 100644 --- a/contracts/emissions_controller/tests/emissions_controller_integration.rs +++ b/contracts/emissions_controller/tests/emissions_controller_integration.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use astroport::asset::AssetInfo; use astroport::incentives::RewardType; -use cosmwasm_std::{coin, coins, Decimal, Decimal256, Empty, Uint128}; +use cosmwasm_std::{coin, coins, Decimal, Decimal256, Empty, Event, Uint128}; use cw_multi_test::Executor; use cw_utils::PaymentError; use itertools::Itertools; @@ -11,16 +11,17 @@ use neutron_sdk::sudo::msg::{RequestPacket, TransferSudoMsg}; use astroport_emissions_controller::error::ContractError; use astroport_emissions_controller::utils::get_epoch_start; -use astroport_governance::emissions_controller; +use astroport_governance::assembly::{ProposalVoteOption, ProposalVoterResponse}; use astroport_governance::emissions_controller::consts::{DAY, EPOCH_LENGTH, VOTE_COOLDOWN}; use astroport_governance::emissions_controller::hub::{ AstroPoolConfig, EmissionsState, HubMsg, OutpostInfo, OutpostParams, OutpostStatus, TuneInfo, UserInfoResponse, }; -use astroport_governance::emissions_controller::msg::ExecuteMsg; +use astroport_governance::emissions_controller::msg::{ExecuteMsg, VxAstroIbcMsg}; +use astroport_governance::{assembly, emissions_controller}; use astroport_voting_escrow::state::UNLOCK_PERIOD; -use crate::common::helper::ControllerHelper; +use crate::common::helper::{ControllerHelper, PROPOSAL_VOTING_PERIOD}; mod common; @@ -1217,6 +1218,134 @@ fn test_some_epochs() { ); } +#[test] +fn test_interchain_governance() { + let mut helper = ControllerHelper::new(); + let owner = helper.owner.clone(); + + // No proposal yet + let err = helper.register_proposal(1).unwrap_err(); + assert_eq!(err.root_cause().to_string(), "Generic error: Querier contract error: type: astroport_governance::assembly::Proposal; key: [00, 09, 70, 72, 6F, 70, 6F, 73, 61, 6C, 73, 00, 00, 00, 00, 00, 00, 00, 01] not found"); + + helper.submit_proposal(&owner).unwrap(); + + // No outposts yet but it shouldn't fail transaction + let resp = helper.register_proposal(1).unwrap(); + assert!( + !resp.has_event( + &Event::new("wasm") + .add_attributes([("action", "register_proposal"), ("outpost", "osmo")]) + ), + "Controller tried to register outpost {:?}", + resp.events + ); + + // Add outpost + helper + .add_outpost( + "osmo", + OutpostInfo { + astro_denom: "ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9" + .to_string(), + params: Some(OutpostParams { + emissions_controller: "osmo1controller".to_string(), + voting_channel: "channel-1".to_string(), + ics20_channel: "channel-2".to_string(), + }), + astro_pool_config: None, + }, + ) + .unwrap(); + + // Submit 2nd proposal. Ensure it registers a proposal on osmosis + let resp = helper.submit_proposal(&owner).unwrap(); + resp.assert_event( + &Event::new("wasm").add_attributes([("action", "register_proposal"), ("outpost", "osmo")]), + ); + + // Now we can register 1st proposal + helper.register_proposal(1).unwrap(); + resp.assert_event( + &Event::new("wasm").add_attributes([("action", "register_proposal"), ("outpost", "osmo")]), + ); + + // Timeout both proposals + helper.blocktravel(PROPOSAL_VOTING_PERIOD + 1); + + let err = helper.register_proposal(1).unwrap_err(); + assert_eq!( + err.root_cause().to_string(), + "Generic error: Proposal is not active" + ); + + // Emulate outpost vote after a voting period is over. + // It shouldn't fail but must not register a vote + let resp = helper + .mock_packet_receive(VxAstroIbcMsg::GovernanceVote { + voter: "osmo1voter".to_string(), + voting_power: Default::default(), + proposal_id: 1, + vote: ProposalVoteOption::For, + }) + .unwrap(); + resp.assert_event( + &Event::new("wasm") + .add_attributes([("action", "cast_vote"), ("error", "Voting period ended!")]), + ); + + // Submit 3rd proposal + helper.submit_proposal(&owner).unwrap(); + + // Emulate vote from osmosis + let resp = helper + .mock_packet_receive(VxAstroIbcMsg::GovernanceVote { + voter: "osmo1voter".to_string(), + voting_power: 1_000000u128.into(), + proposal_id: 3, + vote: ProposalVoteOption::For, + }) + .unwrap(); + + resp.assert_event(&Event::new("wasm").add_attributes([ + ("action", "cast_vote"), + ("proposal_id", "3"), + ("voter", "osmo1voter"), + ("vote", "for"), + ("voting_power", "1000000"), + ])); + + // Ensure voter has been reflected in assembly + let proposal = helper + .app + .wrap() + .query_wasm_smart::( + helper.assembly.clone(), + &assembly::QueryMsg::Proposal { proposal_id: 3 }, + ) + .unwrap(); + assert_eq!(proposal.for_power.u128(), 1_000000); + + let voters = helper + .app + .wrap() + .query_wasm_smart::>( + helper.assembly.clone(), + &assembly::QueryMsg::ProposalVoters { + proposal_id: 3, + start_after: None, + limit: None, + }, + ) + .unwrap(); + assert_eq!( + voters, + vec![ProposalVoterResponse { + address: "osmo1voter".to_string(), + vote_option: ProposalVoteOption::For, + }] + ); +} + #[test] fn test_change_ownership() { let mut helper = ControllerHelper::new(); @@ -1371,6 +1500,7 @@ fn test_update_config() { config, emissions_controller::hub::Config { owner: helper.owner.clone(), + assembly: helper.assembly.clone(), vxastro: helper.vxastro.clone(), factory: helper.factory.clone(), astro_denom: helper.astro.clone(), diff --git a/contracts/emissions_controller_outpost/src/error.rs b/contracts/emissions_controller_outpost/src/error.rs index d5bdd750..8e389834 100644 --- a/contracts/emissions_controller_outpost/src/error.rs +++ b/contracts/emissions_controller_outpost/src/error.rs @@ -45,4 +45,7 @@ pub enum ContractError { #[error("No valid schedules found")] NoValidSchedules {}, + + #[error("User already voted")] + AlreadyVoted {}, } diff --git a/contracts/emissions_controller_outpost/src/execute.rs b/contracts/emissions_controller_outpost/src/execute.rs index 79422ead..e24db4c2 100644 --- a/contracts/emissions_controller_outpost/src/execute.rs +++ b/contracts/emissions_controller_outpost/src/execute.rs @@ -7,12 +7,13 @@ use astroport::incentives::{IncentivesSchedule, InputSchedule}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - attr, coin, coins, ensure, to_json_binary, wasm_execute, Addr, Coin, Decimal, DepsMut, Env, - IbcMsg, MessageInfo, Response, StdError, Uint128, + attr, coin, coins, ensure, wasm_execute, Addr, Coin, Decimal, DepsMut, Env, IbcMsg, + MessageInfo, Response, StdError, Uint128, }; use cw_utils::{may_pay, nonpayable}; use itertools::Itertools; +use astroport_governance::assembly::ProposalVoteOption; use astroport_governance::emissions_controller::consts::{IBC_TIMEOUT, MAX_POOLS_TO_VOTE}; use astroport_governance::emissions_controller::msg::ExecuteMsg; use astroport_governance::emissions_controller::msg::VxAstroIbcMsg; @@ -21,7 +22,8 @@ use astroport_governance::emissions_controller::utils::{check_lp_token, get_voti use astroport_governance::utils::check_contract_supports_channel; use crate::error::ContractError; -use crate::state::{CONFIG, OWNERSHIP_PROPOSAL, PENDING_MESSAGES}; +use crate::state::{CONFIG, OWNERSHIP_PROPOSAL, PROPOSAL_VOTERS, REGISTERED_PROPOSALS}; +use crate::utils::prepare_ibc_packet; /// Exposes all execute endpoints available in the contract. #[cfg_attr(not(feature = "library"), entry_point)] @@ -54,7 +56,7 @@ pub fn execute( let config = CONFIG.load(deps.storage)?; let voting_power = get_voting_power(deps.querier, &config.vxastro, &info.sender, None)?; - // Blocking updates if this is not unlocking and new_voting_power is zero. + // Blocking updates if new_voting_power is zero. // Potentially reduces IBC spam attack vector ensure!(!voting_power.is_zero(), ContractError::ZeroVotingPower {}); handle_update_user(deps, env, info.sender, voting_power, false, config) @@ -113,6 +115,9 @@ pub fn execute( hub_emissions_controller, ics20_channel, ), + OutpostMsg::CastVote { proposal_id, vote } => { + governance_vote(deps, env, info, proposal_id, vote) + } }, } } @@ -266,25 +271,17 @@ pub fn handle_vote( let voting_power = get_voting_power(deps.querier, &config.vxastro, &info.sender, None)?; ensure!(!voting_power.is_zero(), ContractError::ZeroVotingPower {}); - let vote_payload = VxAstroIbcMsg::Vote { - voter: info.sender.to_string(), - voting_power, - votes: votes_map, - }; - - // Blocks any new IBC messages for users with pending IBC requests - // until the previous one is acknowledged, failed or timed out. - PENDING_MESSAGES.update(deps.storage, info.sender.as_ref(), |v| match v { - Some(_) => Err(ContractError::PendingUser(info.sender.to_string())), - None => Ok(vote_payload.clone()), - })?; - - let config = CONFIG.load(deps.storage)?; - let vote_ibc_msg = IbcMsg::SendPacket { - channel_id: config.voting_ibc_channel, - data: to_json_binary(&vote_payload)?, - timeout: env.block.time.plus_seconds(IBC_TIMEOUT).into(), - }; + let vote_ibc_msg = prepare_ibc_packet( + deps.storage, + &env, + info.sender.as_str(), + VxAstroIbcMsg::EmissionsVote { + voter: info.sender.to_string(), + voting_power, + votes: votes_map, + }, + config.voting_ibc_channel, + )?; Ok(Response::default() .add_attributes([("action", "vote")]) @@ -307,24 +304,17 @@ pub fn handle_update_user( attr("new_voting_power", voting_power), ]; - let payload = VxAstroIbcMsg::UpdateUserVotes { - voter: voter.to_string(), - voting_power, - is_unlock, - }; - - // Blocks any new IBC messages for users with pending IBC requests - // until the previous one is acknowledged, failed or timed out. - PENDING_MESSAGES.update(deps.storage, voter.as_str(), |v| match v { - Some(_) => Err(ContractError::PendingUser(voter.to_string())), - None => Ok(payload.clone()), - })?; - - let ibc_msg = IbcMsg::SendPacket { - channel_id: config.voting_ibc_channel, - data: to_json_binary(&payload)?, - timeout: env.block.time.plus_seconds(IBC_TIMEOUT).into(), - }; + let ibc_msg = prepare_ibc_packet( + deps.storage, + &env, + voter.as_str(), + VxAstroIbcMsg::UpdateUserVotes { + voter: voter.to_string(), + voting_power, + is_unlock, + }, + config.voting_ibc_channel, + )?; Ok(Response::default() .add_attributes(attrs) @@ -374,3 +364,48 @@ fn update_config( Ok(Response::default().add_attributes(attrs)) } + +pub fn governance_vote( + deps: DepsMut, + env: Env, + info: MessageInfo, + proposal_id: u64, + vote: ProposalVoteOption, +) -> Result { + let config = CONFIG.load(deps.storage)?; + let voter = info.sender.to_string(); + + ensure!( + !PROPOSAL_VOTERS.has(deps.storage, (proposal_id, voter.clone())), + ContractError::AlreadyVoted {} + ); + + let start_time = REGISTERED_PROPOSALS.load(deps.storage, proposal_id)?; + + let voting_power = + get_voting_power(deps.querier, &config.vxastro, &voter, Some(start_time - 1))?; + ensure!(!voting_power.is_zero(), ContractError::ZeroVotingPower {}); + + let attrs = vec![ + attr("action", "governance_vote"), + attr("voter", &info.sender), + attr("voting_power", voting_power), + ]; + + let ibc_msg = prepare_ibc_packet( + deps.storage, + &env, + &voter, + VxAstroIbcMsg::GovernanceVote { + voter: voter.clone(), + voting_power, + proposal_id, + vote, + }, + config.voting_ibc_channel, + )?; + + Ok(Response::default() + .add_attributes(attrs) + .add_message(ibc_msg)) +} diff --git a/contracts/emissions_controller_outpost/src/ibc.rs b/contracts/emissions_controller_outpost/src/ibc.rs index f5020a8e..6c2051d5 100644 --- a/contracts/emissions_controller_outpost/src/ibc.rs +++ b/contracts/emissions_controller_outpost/src/ibc.rs @@ -8,11 +8,15 @@ use cosmwasm_std::{ }; use astroport_governance::emissions_controller::consts::{IBC_APP_VERSION, IBC_ORDERING}; -use astroport_governance::emissions_controller::msg::{IbcAckResult, VxAstroIbcMsg}; +use astroport_governance::emissions_controller::msg::{ + ack_fail, ack_ok, IbcAckResult, VxAstroIbcMsg, +}; use astroport_governance::emissions_controller::outpost::UserIbcError; use astroport_governance::voting_escrow; -use crate::state::{CONFIG, PENDING_MESSAGES, USER_IBC_ERROR}; +use crate::state::{ + CONFIG, PENDING_MESSAGES, PROPOSAL_VOTERS, REGISTERED_PROPOSALS, USER_IBC_ERROR, +}; #[cfg_attr(not(feature = "library"), entry_point)] pub fn ibc_channel_open( @@ -64,14 +68,53 @@ pub fn ibc_channel_connect( .add_attribute("channel_id", &channel.endpoint.channel_id)) } -#[cfg(not(tarpaulin_include))] #[cfg_attr(not(feature = "library"), entry_point)] pub fn ibc_packet_receive( - _deps: DepsMut, - _env: Env, - _msg: IbcPacketReceiveMsg, + deps: DepsMut, + env: Env, + msg: IbcPacketReceiveMsg, ) -> Result { - unimplemented!("This contract is only sending IBC messages") + do_packet_receive(deps, env, msg).or_else(|err| { + Ok(IbcReceiveResponse::new() + .add_attribute("action", "ibc_packet_receive") + .set_ack(ack_fail(err))) + }) +} + +pub fn do_packet_receive( + deps: DepsMut, + _env: Env, + msg: IbcPacketReceiveMsg, +) -> StdResult { + // Accept messages only from the trusted channel + let config = CONFIG.load(deps.storage)?; + ensure!( + msg.packet.dest.channel_id == config.voting_ibc_channel, + StdError::generic_err("Invalid channel") + ); + + match from_json(msg.packet.data)? { + VxAstroIbcMsg::RegisterProposal { + proposal_id, + start_time, + } => { + ensure!( + !REGISTERED_PROPOSALS.has(deps.storage, proposal_id), + StdError::generic_err("Proposal already registered") + ); + + // Save proposal in state + REGISTERED_PROPOSALS.save(deps.storage, proposal_id, &start_time)?; + + let response = IbcReceiveResponse::new() + .set_ack(ack_ok()) + .add_attribute("action", "register_proposal") + .add_attribute("proposal_id", proposal_id.to_string()) + .add_attribute("start_time", start_time.to_string()); + Ok(response) + } + _ => Err(StdError::generic_err("Invalid IBC message type")), + } } #[cfg_attr(not(feature = "library"), entry_point)] @@ -105,7 +148,17 @@ pub fn ibc_packet_ack( voter } VxAstroIbcMsg::UpdateUserVotes { voter, .. } - | VxAstroIbcMsg::Vote { voter, .. } => voter, + | VxAstroIbcMsg::EmissionsVote { voter, .. } => voter, + VxAstroIbcMsg::RegisterProposal { .. } => { + unreachable!("Outpost can't send RegisterProposal ibc msg") + } + VxAstroIbcMsg::GovernanceVote { + voter, proposal_id, .. + } => { + // Mark voter as voted on this proposal + PROPOSAL_VOTERS.save(deps.storage, (*proposal_id, voter.clone()), &())?; + voter + } }; USER_IBC_ERROR.remove(deps.storage, voter); PENDING_MESSAGES.remove(deps.storage, voter); @@ -165,8 +218,11 @@ pub fn process_ibc_error( .add_message(relock_msg); voter.clone() } - VxAstroIbcMsg::Vote { voter, .. } | VxAstroIbcMsg::UpdateUserVotes { voter, .. } => { - voter.clone() + VxAstroIbcMsg::EmissionsVote { voter, .. } + | VxAstroIbcMsg::UpdateUserVotes { voter, .. } + | VxAstroIbcMsg::GovernanceVote { voter, .. } => voter.clone(), + VxAstroIbcMsg::RegisterProposal { .. } => { + unreachable!("Outpost can't send RegisterProposal ibc msg") } }; diff --git a/contracts/emissions_controller_outpost/src/lib.rs b/contracts/emissions_controller_outpost/src/lib.rs index 28edd73c..e4ede680 100644 --- a/contracts/emissions_controller_outpost/src/lib.rs +++ b/contracts/emissions_controller_outpost/src/lib.rs @@ -5,3 +5,4 @@ pub mod error; pub mod ibc; pub mod instantiate; pub mod query; +pub mod utils; diff --git a/contracts/emissions_controller_outpost/src/state.rs b/contracts/emissions_controller_outpost/src/state.rs index 2cf73075..d17d5cf6 100644 --- a/contracts/emissions_controller_outpost/src/state.rs +++ b/contracts/emissions_controller_outpost/src/state.rs @@ -1,7 +1,7 @@ use astroport::common::OwnershipProposal; -use astroport_governance::emissions_controller::msg::VxAstroIbcMsg; use cw_storage_plus::{Item, Map}; +use astroport_governance::emissions_controller::msg::VxAstroIbcMsg; use astroport_governance::emissions_controller::outpost::{Config, UserIbcError}; /// Stores config at the given key. @@ -15,3 +15,8 @@ pub const USER_IBC_ERROR: Map<&str, UserIbcError> = Map::new("user_ibc_error"); /// The contract blocks any new IBC messages for these users /// until the previous one is acknowledged, failed or timed out. pub const PENDING_MESSAGES: Map<&str, VxAstroIbcMsg> = Map::new("pending_messages"); +/// Map of registered proposals (proposal id -> start time). +/// Users are allowed to vote only on registered proposals. +pub const REGISTERED_PROPOSALS: Map = Map::new("registered_proposals"); +/// Contains all the voters per proposal. Map proposal id -> voter address. +pub const PROPOSAL_VOTERS: Map<(u64, String), ()> = Map::new("proposal_votes"); diff --git a/contracts/emissions_controller_outpost/src/utils.rs b/contracts/emissions_controller_outpost/src/utils.rs new file mode 100644 index 00000000..4c019ad6 --- /dev/null +++ b/contracts/emissions_controller_outpost/src/utils.rs @@ -0,0 +1,29 @@ +use cosmwasm_std::{to_json_binary, Env, IbcMsg, Storage}; + +use astroport_governance::emissions_controller::consts::IBC_TIMEOUT; +use astroport_governance::emissions_controller::msg::VxAstroIbcMsg; + +use crate::error::ContractError; +use crate::state::PENDING_MESSAGES; + +/// Ensure voter has no pending IBC requests and prepare an IBC packet. +pub fn prepare_ibc_packet( + storage: &mut dyn Storage, + env: &Env, + voter: &str, + payload: VxAstroIbcMsg, + channel_id: String, +) -> Result { + // Block any new IBC messages for users with pending IBC requests + // until the previous one is acknowledged, failed or timed out. + PENDING_MESSAGES.update(storage, voter, |v| match v { + Some(_) => Err(ContractError::PendingUser(voter.to_string())), + None => Ok(payload.clone()), + })?; + + Ok(IbcMsg::SendPacket { + channel_id, + data: to_json_binary(&payload)?, + timeout: env.block.time.plus_seconds(IBC_TIMEOUT).into(), + }) +} diff --git a/contracts/emissions_controller_outpost/tests/common/contracts.rs b/contracts/emissions_controller_outpost/tests/common/contracts.rs index 7c32c09e..af719ea1 100644 --- a/contracts/emissions_controller_outpost/tests/common/contracts.rs +++ b/contracts/emissions_controller_outpost/tests/common/contracts.rs @@ -1,10 +1,13 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{ - DepsMut, Empty, Env, IbcPacketAckMsg, IbcPacketTimeoutMsg, Response, StdResult, + DepsMut, Empty, Env, IbcBasicResponse, IbcPacketAckMsg, IbcPacketReceiveMsg, + IbcPacketTimeoutMsg, Response, StdResult, }; use cw_multi_test::{Contract, ContractWrapper}; -use astroport_emissions_controller_outpost::ibc::{ibc_packet_ack, ibc_packet_timeout}; +use astroport_emissions_controller_outpost::ibc::{ + do_packet_receive, ibc_packet_ack, ibc_packet_timeout, +}; pub fn token_contract() -> Box> { Box::new(ContractWrapper::new_with_empty( @@ -56,12 +59,18 @@ pub fn factory_contract() -> Box> { pub enum TestSudoMsg { Ack(IbcPacketAckMsg), Timeout(IbcPacketTimeoutMsg), + IbcRecv(IbcPacketReceiveMsg), } fn sudo(deps: DepsMut, env: Env, msg: TestSudoMsg) -> StdResult { match msg { TestSudoMsg::Ack(packet) => ibc_packet_ack(deps, env, packet), TestSudoMsg::Timeout(packet) => ibc_packet_timeout(deps, env, packet), + TestSudoMsg::IbcRecv(packet) => do_packet_receive(deps, env, packet).map(|ibc_response| { + IbcBasicResponse::default() + .add_attributes(ibc_response.attributes) + .add_submessages(ibc_response.messages) + }), } .map(|ibc_response| { Response::default() diff --git a/contracts/emissions_controller_outpost/tests/common/helper.rs b/contracts/emissions_controller_outpost/tests/common/helper.rs index eb02684b..05c51e56 100644 --- a/contracts/emissions_controller_outpost/tests/common/helper.rs +++ b/contracts/emissions_controller_outpost/tests/common/helper.rs @@ -3,10 +3,11 @@ use astroport::factory::{PairConfig, PairType}; use astroport::incentives::{InputSchedule, RewardInfo}; use astroport::token::Logo; use astroport::{factory, incentives}; +use astroport_emissions_controller_outpost::state::REGISTERED_PROPOSALS; use cosmwasm_std::{ coin, coins, to_json_binary, Addr, BlockInfo, Coin, Decimal, Empty, IbcAcknowledgement, - IbcEndpoint, IbcPacket, IbcPacketAckMsg, IbcPacketTimeoutMsg, MemoryStorage, StdResult, - Timestamp, Uint128, + IbcEndpoint, IbcPacket, IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg, + MemoryStorage, StdResult, Timestamp, Uint128, }; use cw_multi_test::error::AnyResult; use cw_multi_test::{ @@ -15,6 +16,7 @@ use cw_multi_test::{ }; use derivative::Derivative; +use astroport_governance::assembly::ProposalVoteOption; use astroport_governance::emissions_controller::consts::{EPOCHS_START, EPOCH_LENGTH}; use astroport_governance::emissions_controller::msg::{ExecuteMsg, IbcAckResult, VxAstroIbcMsg}; use astroport_governance::emissions_controller::outpost::{OutpostInstantiateMsg, OutpostMsg}; @@ -226,12 +228,12 @@ impl ControllerHelper { ) } - pub fn user_vp(&self, user: &Addr, time: Option) -> StdResult { + pub fn user_vp(&self, user: &Addr, timestamp: Option) -> StdResult { self.app.wrap().query_wasm_smart( &self.vxastro, &voting_escrow::QueryMsg::UserVotingPower { user: user.to_string(), - time, + timestamp, }, ) } @@ -376,19 +378,13 @@ impl ControllerHelper { } pub fn set_voting_channel(&mut self) { - self.app - .execute_contract( - self.owner.clone(), - self.emission_controller.clone(), - &ExecuteMsg::Custom(OutpostMsg::UpdateConfig { - // channel-1 is hardcoded in the mocked ibc module - voting_ibc_channel: Some("channel-1".to_string()), - hub_emissions_controller: None, - ics20_channel: None, - }), - &[], - ) - .unwrap(); + self.update_config( + &self.owner.clone(), + Some("channel-1".to_string()), + None, + None, + ) + .unwrap(); } pub fn mock_ibc_ack( @@ -399,7 +395,7 @@ impl ControllerHelper { let ack_result = if let Some(err) = error { IbcAckResult::Error(err.to_string()) } else { - IbcAckResult::Ok(b"ok".into()) + IbcAckResult::Ok(b"null".into()) }; let packet = IbcPacketAckMsg::new( IbcAcknowledgement::encode_json(&ack_result).unwrap(), @@ -444,4 +440,73 @@ impl ControllerHelper { &TestSudoMsg::Timeout(packet), ) } + + pub fn mock_packet_receive( + &mut self, + ibc_msg: VxAstroIbcMsg, + dst_channel: &str, + ) -> AnyResult { + let packet = IbcPacketReceiveMsg::new( + IbcPacket::new( + to_json_binary(&ibc_msg).unwrap(), + IbcEndpoint { + port_id: "".to_string(), + channel_id: "".to_string(), + }, + IbcEndpoint { + port_id: "".to_string(), + channel_id: dst_channel.to_string(), + }, + 0, + Timestamp::from_seconds(0).into(), + ), + Addr::unchecked("relayer"), + ); + self.app.wasm_sudo( + self.emission_controller.clone(), + &TestSudoMsg::IbcRecv(packet), + ) + } + + pub fn update_config( + &mut self, + sender: &Addr, + voting_ibc_channel: Option, + hub_emissions_controller: Option, + ics20_channel: Option, + ) -> AnyResult { + self.app.execute_contract( + sender.clone(), + self.emission_controller.clone(), + &ExecuteMsg::Custom(OutpostMsg::UpdateConfig { + voting_ibc_channel, + hub_emissions_controller, + ics20_channel, + }), + &[], + ) + } + + pub fn cast_vote(&mut self, user: &Addr, proposal_id: u64) -> AnyResult { + self.app.execute_contract( + user.clone(), + self.emission_controller.clone(), + &ExecuteMsg::Custom(OutpostMsg::CastVote { + proposal_id, + vote: ProposalVoteOption::For, + }), + &[], + ) + } + + pub fn is_prop_registered(&self, proposal_id: u64) -> bool { + REGISTERED_PROPOSALS + .query( + &self.app.wrap(), + self.emission_controller.clone(), + proposal_id, + ) + .unwrap() + .is_some() + } } diff --git a/contracts/emissions_controller_outpost/tests/emissions_controller_outpost_integration.rs b/contracts/emissions_controller_outpost/tests/emissions_controller_outpost_integration.rs index add455f4..af71a7da 100644 --- a/contracts/emissions_controller_outpost/tests/emissions_controller_outpost_integration.rs +++ b/contracts/emissions_controller_outpost/tests/emissions_controller_outpost_integration.rs @@ -5,9 +5,10 @@ use cw_multi_test::Executor; use cw_utils::PaymentError; use astroport_emissions_controller_outpost::error::ContractError; +use astroport_governance::assembly::ProposalVoteOption; use astroport_governance::emissions_controller::consts::{EPOCH_LENGTH, IBC_TIMEOUT}; use astroport_governance::emissions_controller::msg::{ExecuteMsg, VxAstroIbcMsg}; -use astroport_governance::emissions_controller::outpost::{OutpostMsg, UserIbcError}; +use astroport_governance::emissions_controller::outpost::UserIbcError; use astroport_governance::voting_escrow::LockInfoResponse; use astroport_voting_escrow::state::UNLOCK_PERIOD; @@ -393,7 +394,7 @@ fn test_voting() { ); // Time out IBC packet - let mock_packet = VxAstroIbcMsg::Vote { + let mock_packet = VxAstroIbcMsg::EmissionsVote { voter: user.to_string(), voting_power: Default::default(), votes: Default::default(), @@ -424,7 +425,7 @@ fn test_voting() { helper .mock_ibc_ack( - VxAstroIbcMsg::Vote { + VxAstroIbcMsg::EmissionsVote { voter: user.to_string(), voting_power: Default::default(), votes: Default::default(), @@ -541,6 +542,143 @@ fn test_unlock_and_withdraw() { assert_eq!(user_bal, 1000); } +#[test] +fn test_interchain_governance() { + let mut helper = ControllerHelper::new(); + helper.set_voting_channel(); + + let user = helper.app.api().addr_make("user"); + + // Proposal is not registered + helper.cast_vote(&user, 1).unwrap_err(); + + let now = helper.app.block_info().time.seconds(); + + let err = helper + .mock_packet_receive( + VxAstroIbcMsg::RegisterProposal { + proposal_id: 1, + start_time: now - 10, + }, + "channel-100", + ) + .unwrap_err(); + assert_eq!( + err.root_cause().to_string(), + "Generic error: Invalid channel" + ); + + helper + .mock_packet_receive( + VxAstroIbcMsg::RegisterProposal { + proposal_id: 1, + start_time: now, + }, + "channel-1", + ) + .unwrap(); + + assert!( + helper.is_prop_registered(1), + "Proposal should be registered" + ); + + let err = helper + .mock_packet_receive( + VxAstroIbcMsg::RegisterProposal { + proposal_id: 1, + start_time: now, + }, + "channel-1", + ) + .unwrap_err(); + assert_eq!( + err.root_cause().to_string(), + "Generic error: Proposal already registered" + ); + + let err = helper.cast_vote(&user, 1).unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::ZeroVotingPower {} + ); + + helper.lock(&user, 1000u64.into()).unwrap(); + + // User locked after proposal registration. Still zero voting power + let err = helper.cast_vote(&user, 1).unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::ZeroVotingPower {} + ); + + helper.timetravel(100); + + let now = helper.app.block_info().time.seconds(); + + helper + .mock_packet_receive( + VxAstroIbcMsg::RegisterProposal { + proposal_id: 2, + start_time: now, + }, + "channel-1", + ) + .unwrap(); + + let err = helper.cast_vote(&user, 2).unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::PendingUser(user.to_string()) + ); + + // Mock ibc ack + helper + .mock_ibc_ack( + VxAstroIbcMsg::UpdateUserVotes { + voter: user.to_string(), + voting_power: Default::default(), + is_unlock: false, + }, + None, + ) + .unwrap(); + + helper.cast_vote(&user, 2).unwrap(); + + // Timeout voting packet + helper + .mock_ibc_timeout(VxAstroIbcMsg::GovernanceVote { + voter: user.to_string(), + voting_power: Default::default(), + proposal_id: 2, + vote: ProposalVoteOption::For, + }) + .unwrap(); + + helper.cast_vote(&user, 2).unwrap(); + + // Mock ack + helper + .mock_ibc_ack( + VxAstroIbcMsg::GovernanceVote { + voter: user.to_string(), + voting_power: Default::default(), + proposal_id: 2, + vote: ProposalVoteOption::For, + }, + None, + ) + .unwrap(); + + // Can't vote again + let err = helper.cast_vote(&user, 2).unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::AlreadyVoted {} + ); +} + #[test] fn test_update_config() { let mut helper = ControllerHelper::new(); @@ -548,36 +686,14 @@ fn test_update_config() { // Unauthorized check let random = helper.app.api().addr_make("random"); - let err = helper - .app - .execute_contract( - random.clone(), - helper.emission_controller.clone(), - &ExecuteMsg::Custom(OutpostMsg::UpdateConfig { - voting_ibc_channel: None, - hub_emissions_controller: None, - ics20_channel: None, - }), - &[], - ) - .unwrap_err(); + let err = helper.update_config(&random, None, None, None).unwrap_err(); assert_eq!( err.downcast::().unwrap(), ContractError::Unauthorized {} ); let err = helper - .app - .execute_contract( - owner.clone(), - helper.emission_controller.clone(), - &ExecuteMsg::Custom(OutpostMsg::UpdateConfig { - voting_ibc_channel: Some("channel-100".to_string()), - hub_emissions_controller: None, - ics20_channel: None, - }), - &[], - ) + .update_config(&owner, Some("channel-100".to_string()), None, None) .unwrap_err(); assert_eq!( err.root_cause().to_string(), @@ -585,16 +701,11 @@ fn test_update_config() { ); helper - .app - .execute_contract( - owner.clone(), - helper.emission_controller.clone(), - &ExecuteMsg::Custom(OutpostMsg::UpdateConfig { - voting_ibc_channel: Some("channel-1".to_string()), - hub_emissions_controller: Some("hub_emissions_controller".to_string()), - ics20_channel: Some("channel-10".to_string()), - }), - &[], + .update_config( + &owner, + Some("channel-1".to_string()), + Some("hub_emissions_controller".to_string()), + Some("channel-10".to_string()), ) .unwrap(); let config = helper.query_config().unwrap(); diff --git a/contracts/voting_escrow/src/contract.rs b/contracts/voting_escrow/src/contract.rs index 09242931..41af491d 100644 --- a/contracts/voting_escrow/src/contract.rs +++ b/contracts/voting_escrow/src/contract.rs @@ -218,11 +218,13 @@ pub fn execute( #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::TotalVotingPower { time } => { - to_json_binary(&get_total_vp(deps.storage, env.block.time.seconds(), time)?) - } - QueryMsg::UserVotingPower { user, time } => { - to_json_binary(&query_user_voting_power(deps, env, user, time)?) + QueryMsg::TotalVotingPower { timestamp } => to_json_binary(&get_total_vp( + deps.storage, + env.block.time.seconds(), + timestamp, + )?), + QueryMsg::UserVotingPower { user, timestamp } => { + to_json_binary(&query_user_voting_power(deps, env, user, timestamp)?) } QueryMsg::LockInfo { user } => { let user = deps.api.addr_validate(&user)?; diff --git a/contracts/voting_escrow/tests/helper.rs b/contracts/voting_escrow/tests/helper.rs index 188a39b9..bae3d63d 100644 --- a/contracts/voting_escrow/tests/helper.rs +++ b/contracts/voting_escrow/tests/helper.rs @@ -160,20 +160,21 @@ impl EscrowHelper { }) } - pub fn user_vp(&self, user: &Addr, time: Option) -> StdResult { + pub fn user_vp(&self, user: &Addr, timestamp: Option) -> StdResult { self.app.wrap().query_wasm_smart( &self.vxastro_contract, &QueryMsg::UserVotingPower { user: user.to_string(), - time, + timestamp, }, ) } - pub fn total_vp(&self, time: Option) -> StdResult { - self.app - .wrap() - .query_wasm_smart(&self.vxastro_contract, &QueryMsg::TotalVotingPower { time }) + pub fn total_vp(&self, timestamp: Option) -> StdResult { + self.app.wrap().query_wasm_smart( + &self.vxastro_contract, + &QueryMsg::TotalVotingPower { timestamp }, + ) } pub fn lock_info(&self, user: &Addr) -> StdResult { diff --git a/packages/astroport-governance/src/assembly.rs b/packages/astroport-governance/src/assembly.rs index 0d475eb4..bfd68271 100644 --- a/packages/astroport-governance/src/assembly.rs +++ b/packages/astroport-governance/src/assembly.rs @@ -5,8 +5,6 @@ use std::str::FromStr; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, CosmosMsg, Decimal, StdError, StdResult, Uint128, Uint64}; -use crate::assembly::helpers::is_safe_link; - pub const MINIMUM_PROPOSAL_REQUIRED_THRESHOLD_PERCENTAGE: u64 = 33; pub const MAX_PROPOSAL_REQUIRED_THRESHOLD_PERCENTAGE: u64 = 100; pub const MAX_PROPOSAL_REQUIRED_QUORUM_PERCENTAGE: &str = "1"; @@ -30,22 +28,15 @@ const MAX_LINK_LENGTH: usize = 128; /// Special characters that are allowed in proposal text const SAFE_TEXT_CHARS: &str = "!&?#()*+'-./\""; +const SAFE_LINK_CHARS: &str = "-_:/?#@!$&()*+,;=.~[]'%"; /// This structure holds the parameters used for creating an Assembly contract. #[cw_serde] pub struct InstantiateMsg { /// Astroport xASTRO staking address. xASTRO denom and tracker contract address are queried on assembly instantiation. pub staking_addr: String, - /// Address of vxASTRO token - pub vxastro_token_addr: Option, - /// Voting Escrow delegator address - pub voting_escrow_delegator_addr: Option, /// Astroport IBC controller contract pub ibc_controller: Option, - /// Generator controller contract capable of immediate proposals - pub generator_controller_addr: Option, - /// Hub contract that handles voting from Outposts - pub hub_addr: Option, /// Address of the builder unlock contract pub builder_unlock_addr: String, /// Proposal voting period @@ -84,6 +75,18 @@ pub enum ExecuteMsg { /// Vote option vote: ProposalVoteOption, }, + /// Cast a vote for an active proposal. + /// Permissioned to emissions controller contract. + /// Called on an IBC packet receive. + CastVoteOutpost { + voter: String, + /// Voting power reported from outpost + voting_power: Uint128, + /// Proposal identifier + proposal_id: u64, + /// Vote option + vote: ProposalVoteOption, + }, /// Set the status of a proposal that expired EndProposal { /// Proposal identifier @@ -156,8 +159,12 @@ pub enum QueryMsg { pub struct Config { /// xASTRO token denom pub xastro_denom: String, - // xASTRO denom tracking contract + /// xASTRO denom tracking contract pub xastro_denom_tracking: String, + /// vxASTRO contract address. Optional + pub vxastro_contract: Option, + /// Emissions controller contract. Optional + pub emissions_controller: Option, /// Astroport IBC controller contract pub ibc_controller: Option, /// Builder unlock contract address @@ -251,15 +258,18 @@ pub struct UpdateConfig { /// Proposal expiration period pub proposal_expiration_period: Option, /// Proposal required deposit - pub proposal_required_deposit: Option, + pub proposal_required_deposit: Option, /// Proposal required quorum - pub proposal_required_quorum: Option, + pub proposal_required_quorum: Option, /// Proposal required threshold - pub proposal_required_threshold: Option, + pub proposal_required_threshold: Option, /// Links to remove from whitelist pub whitelist_remove: Option>, /// Links to add to whitelist pub whitelist_add: Option>, + /// Set vxASTRO and emissions controller contract at the same time. + /// Emissions controller is queried from the vxASTRO contract. + pub vxastro: Option, } /// This structure stores data for a proposal. @@ -273,12 +283,8 @@ pub struct Proposal { pub status: ProposalStatus, /// `For` power of proposal pub for_power: Uint128, - /// `For` power of proposal cast from all Outposts - pub outpost_for_power: Uint128, /// `Against` power of proposal pub against_power: Uint128, - /// `Against` power of proposal cast from all Outposts - pub outpost_against_power: Uint128, /// Start block of proposal pub start_block: u64, /// Start time of proposal @@ -297,7 +303,7 @@ pub struct Proposal { pub link: Option, /// Proposal messages pub messages: Vec, - /// Amount of xASTRO deposited in order to post the proposal + /// Amount of xASTRO deposited to post the proposal pub deposit_amount: Uint128, /// IBC channel pub ibc_channel: Option, @@ -385,15 +391,6 @@ impl Display for ProposalStatus { } } -/// This structure describes a proposal vote. -#[cw_serde] -pub struct ProposalVote { - /// Voted option for the proposal - pub option: ProposalVoteOption, - /// Vote power - pub power: Uint128, -} - /// This enum describes available options for voting on a proposal. #[cw_serde] pub enum ProposalVoteOption { @@ -438,27 +435,21 @@ pub struct ProposalVoterResponse { pub vote_option: ProposalVoteOption, } -pub mod helpers { - use cosmwasm_std::{StdError, StdResult}; - - const SAFE_LINK_CHARS: &str = "-_:/?#@!$&()*+,;=.~[]'%"; - - /// Checks if the link is valid. Returns a boolean value. - pub fn is_safe_link(link: &str) -> bool { - link.chars() - .all(|c| c.is_ascii_alphanumeric() || SAFE_LINK_CHARS.contains(c)) - } +/// Checks if the link is valid. Returns a boolean value. +pub fn is_safe_link(link: &str) -> bool { + link.chars() + .all(|c| c.is_ascii_alphanumeric() || SAFE_LINK_CHARS.contains(c)) +} - /// Validating the list of links. Returns an error if a list has an invalid link. - pub fn validate_links(links: &[String]) -> StdResult<()> { - for link in links { - if !(is_safe_link(link) && link.contains('.') && link.ends_with('/')) { - return Err(StdError::generic_err(format!( - "Link is not properly formatted or contains unsafe characters: {link}." - ))); - } +/// Validating the list of links. Returns an error if a list has an invalid link. +pub fn validate_links(links: &[String]) -> StdResult<()> { + for link in links { + if !(is_safe_link(link) && link.contains('.') && link.ends_with('/')) { + return Err(StdError::generic_err(format!( + "Link is not properly formatted or contains unsafe characters: {link}." + ))); } - - Ok(()) } + + Ok(()) } diff --git a/packages/astroport-governance/src/emissions_controller/hub.rs b/packages/astroport-governance/src/emissions_controller/hub.rs index 91561ab2..ee2bfc19 100644 --- a/packages/astroport-governance/src/emissions_controller/hub.rs +++ b/packages/astroport-governance/src/emissions_controller/hub.rs @@ -12,6 +12,8 @@ use crate::voting_escrow::UpdateMarketingInfo; pub struct HubInstantiateMsg { /// Contract owner pub owner: String, + /// Astroport Assembly contract address + pub assembly: String, /// vxASTRO contract code id pub vxastro_code_id: u64, /// vxASTRO token marketing info @@ -77,6 +79,8 @@ pub enum HubMsg { }, /// Remove an outpost RemoveOutpost { prefix: String }, + /// Permissionless endpoint to stream proposal info from the Hub to all outposts + RegisterProposal { proposal_id: u64 }, } /// This structure describes the query messages available in the contract. @@ -131,6 +135,8 @@ pub enum QueryMsg { pub struct Config { /// Address that's allowed to change contract parameters pub owner: Addr, + /// Astroport Assembly contract address + pub assembly: Addr, /// vxASTRO contract address pub vxastro: Addr, /// Astroport Factory contract @@ -323,6 +329,7 @@ mod unit_tests { fn test_validate_config() { let mut config = Config { owner: Addr::unchecked(""), + assembly: Addr::unchecked(""), vxastro: Addr::unchecked(""), factory: Addr::unchecked(""), astro_denom: "uastro".to_string(), diff --git a/packages/astroport-governance/src/emissions_controller/msg.rs b/packages/astroport-governance/src/emissions_controller/msg.rs index e4144315..4a223740 100644 --- a/packages/astroport-governance/src/emissions_controller/msg.rs +++ b/packages/astroport-governance/src/emissions_controller/msg.rs @@ -4,6 +4,8 @@ use std::fmt::Display; use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_json_binary, Binary, Decimal, Uint128}; +use crate::assembly::ProposalVoteOption; + #[cw_serde] pub enum ExecuteMsg { /// Vote allows a vxASTRO holders @@ -50,16 +52,18 @@ pub fn ack_ok() -> Binary { to_json_binary(&IbcAckResult::Ok(b"ok".into())).unwrap() } -/// Internal IBC message to be sent from outpost to the hub over voting channel +/// Internal IBC messages for hub and outposts interactions #[cw_serde] pub enum VxAstroIbcMsg { - Vote { + /// Sender: Outpost + EmissionsVote { voter: String, /// Actual voting power reported from outpost voting_power: Uint128, /// Voting power distribution votes: HashMap, }, + /// Sender: Outpost UpdateUserVotes { voter: String, /// Actual voting power reported from outpost @@ -67,4 +71,16 @@ pub enum VxAstroIbcMsg { /// Marker defines whether this packet was sent from vxASTRO unlock context is_unlock: bool, }, + /// Sender: Hub + RegisterProposal { proposal_id: u64, start_time: u64 }, + /// Sender: Outpost + GovernanceVote { + voter: String, + /// Actual voting power reported from outpost + voting_power: Uint128, + /// Proposal id + proposal_id: u64, + /// Vote option + vote: ProposalVoteOption, + }, } diff --git a/packages/astroport-governance/src/emissions_controller/outpost.rs b/packages/astroport-governance/src/emissions_controller/outpost.rs index e8f403cc..0118a924 100644 --- a/packages/astroport-governance/src/emissions_controller/outpost.rs +++ b/packages/astroport-governance/src/emissions_controller/outpost.rs @@ -1,8 +1,9 @@ -use crate::emissions_controller::msg::VxAstroIbcMsg; use astroport::incentives::InputSchedule; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::Addr; +use crate::assembly::ProposalVoteOption; +use crate::emissions_controller::msg::VxAstroIbcMsg; use crate::voting_escrow::UpdateMarketingInfo; /// This structure describes the basic settings for creating a contract. @@ -39,6 +40,15 @@ pub enum OutpostMsg { PermissionedSetEmissions { schedules: Vec<(String, InputSchedule)>, }, + /// Allows using vxASTRO voting power to vote on general DAO proposals. + /// The contract requires a proposal with specific id to be registered via + /// a special permissionless IBC message. + CastVote { + /// Proposal id + proposal_id: u64, + /// Vote option + vote: ProposalVoteOption, + }, UpdateConfig { /// Voting IBC wasm<>wasm channel voting_ibc_channel: Option, @@ -93,6 +103,6 @@ pub struct Config { pub voting_ibc_channel: String, /// Emissions controller on the Hub pub hub_emissions_controller: String, - /// Official ICS20 IBC channel from this outpost to the Hub + /// ICS20 IBC channel from this outpost to the Hub pub ics20_channel: String, } diff --git a/packages/astroport-governance/src/emissions_controller/utils.rs b/packages/astroport-governance/src/emissions_controller/utils.rs index 0347cd7e..9621b222 100644 --- a/packages/astroport-governance/src/emissions_controller/utils.rs +++ b/packages/astroport-governance/src/emissions_controller/utils.rs @@ -63,13 +63,13 @@ pub fn get_voting_power( querier: QuerierWrapper, vxastro: &Addr, voter: impl Into, - time: Option, + timestamp: Option, ) -> StdResult { querier.query_wasm_smart( vxastro, &voting_escrow::QueryMsg::UserVotingPower { user: voter.into(), - time, + timestamp, }, ) } diff --git a/packages/astroport-governance/src/voting_escrow.rs b/packages/astroport-governance/src/voting_escrow.rs index d2922f44..f91f0601 100644 --- a/packages/astroport-governance/src/voting_escrow.rs +++ b/packages/astroport-governance/src/voting_escrow.rs @@ -72,10 +72,13 @@ pub enum QueryMsg { MarketingInfo {}, /// Return the current total amount of vxASTRO #[returns(Uint128)] - TotalVotingPower { time: Option }, + TotalVotingPower { timestamp: Option }, /// Return the user's current voting power (vxASTRO balance) #[returns(Uint128)] - UserVotingPower { user: String, time: Option }, + UserVotingPower { + user: String, + timestamp: Option, + }, /// Fetch a user's lock information #[returns(LockInfoResponse)] LockInfo { user: String }, diff --git a/schemas/astro-assembly/astro-assembly.json b/schemas/astro-assembly/astro-assembly.json index 2c519640..5e93b341 100644 --- a/schemas/astro-assembly/astro-assembly.json +++ b/schemas/astro-assembly/astro-assembly.json @@ -23,20 +23,6 @@ "description": "Address of the builder unlock contract", "type": "string" }, - "generator_controller_addr": { - "description": "Generator controller contract capable of immediate proposals", - "type": [ - "string", - "null" - ] - }, - "hub_addr": { - "description": "Hub contract that handles voting from Outposts", - "type": [ - "string", - "null" - ] - }, "ibc_controller": { "description": "Astroport IBC controller contract", "type": [ @@ -82,20 +68,6 @@ "description": "Astroport xASTRO staking address. xASTRO denom and tracker contract address are queried on assembly instantiation.", "type": "string" }, - "voting_escrow_delegator_addr": { - "description": "Voting Escrow delegator address", - "type": [ - "string", - "null" - ] - }, - "vxastro_token_addr": { - "description": "Address of vxASTRO token", - "type": [ - "string", - "null" - ] - }, "whitelisted_links": { "description": "Whitelisted links", "type": "array", @@ -197,6 +169,53 @@ }, "additionalProperties": false }, + { + "description": "Cast a vote for an active proposal. Permissioned to emissions controller contract. Called on an IBC packet receive.", + "type": "object", + "required": [ + "cast_vote_outpost" + ], + "properties": { + "cast_vote_outpost": { + "type": "object", + "required": [ + "proposal_id", + "vote", + "voter", + "voting_power" + ], + "properties": { + "proposal_id": { + "description": "Proposal identifier", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "description": "Vote option", + "allOf": [ + { + "$ref": "#/definitions/ProposalVoteOption" + } + ] + }, + "voter": { + "type": "string" + }, + "voting_power": { + "description": "Voting power reported from outpost", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Set the status of a proposal that expired", "type": "object", @@ -524,6 +543,10 @@ } ] }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, "DistributionMsg": { "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", "oneOf": [ @@ -926,25 +949,35 @@ }, "proposal_required_deposit": { "description": "Proposal required deposit", - "type": [ - "integer", - "null" - ], - "format": "uint128", - "minimum": 0.0 + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] }, "proposal_required_quorum": { "description": "Proposal required quorum", - "type": [ - "string", - "null" + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } ] }, "proposal_required_threshold": { "description": "Proposal required threshold", - "type": [ - "string", - "null" + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } ] }, "proposal_voting_period": { @@ -956,6 +989,13 @@ "format": "uint64", "minimum": 0.0 }, + "vxastro": { + "description": "Set vxASTRO and emissions controller contract at the same time. Emissions controller is queried from the vxASTRO contract.", + "type": [ + "string", + "null" + ] + }, "whitelist_add": { "description": "Links to add to whitelist", "type": [ @@ -1390,6 +1430,17 @@ } ] }, + "emissions_controller": { + "description": "Emissions controller contract. Optional", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, "ibc_controller": { "description": "Astroport IBC controller contract", "anyOf": [ @@ -1443,6 +1494,17 @@ "format": "uint64", "minimum": 0.0 }, + "vxastro_contract": { + "description": "vxASTRO contract address. Optional", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, "whitelisted_links": { "description": "Whitelisted links", "type": "array", @@ -1455,6 +1517,7 @@ "type": "string" }, "xastro_denom_tracking": { + "description": "xASTRO denom tracking contract", "type": "string" } }, @@ -1488,8 +1551,6 @@ "expiration_block", "for_power", "messages", - "outpost_against_power", - "outpost_for_power", "proposal_id", "start_block", "start_time", @@ -1514,7 +1575,7 @@ "minimum": 0.0 }, "deposit_amount": { - "description": "Amount of xASTRO deposited in order to post the proposal", + "description": "Amount of xASTRO deposited to post the proposal", "allOf": [ { "$ref": "#/definitions/Uint128" @@ -1566,22 +1627,6 @@ "$ref": "#/definitions/CosmosMsg_for_Empty" } }, - "outpost_against_power": { - "description": "`Against` power of proposal cast from all Outposts", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "outpost_for_power": { - "description": "`For` power of proposal cast from all Outposts", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, "proposal_id": { "description": "Unique proposal ID", "allOf": [ @@ -2926,8 +2971,6 @@ "expiration_block", "for_power", "messages", - "outpost_against_power", - "outpost_for_power", "proposal_id", "start_block", "start_time", @@ -2952,7 +2995,7 @@ "minimum": 0.0 }, "deposit_amount": { - "description": "Amount of xASTRO deposited in order to post the proposal", + "description": "Amount of xASTRO deposited to post the proposal", "allOf": [ { "$ref": "#/definitions/Uint128" @@ -3004,22 +3047,6 @@ "$ref": "#/definitions/CosmosMsg_for_Empty" } }, - "outpost_against_power": { - "description": "`Against` power of proposal cast from all Outposts", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "outpost_for_power": { - "description": "`For` power of proposal cast from all Outposts", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, "proposal_id": { "description": "Unique proposal ID", "allOf": [ diff --git a/schemas/astro-assembly/raw/execute.json b/schemas/astro-assembly/raw/execute.json index d81a1dda..a935ce8f 100644 --- a/schemas/astro-assembly/raw/execute.json +++ b/schemas/astro-assembly/raw/execute.json @@ -83,6 +83,53 @@ }, "additionalProperties": false }, + { + "description": "Cast a vote for an active proposal. Permissioned to emissions controller contract. Called on an IBC packet receive.", + "type": "object", + "required": [ + "cast_vote_outpost" + ], + "properties": { + "cast_vote_outpost": { + "type": "object", + "required": [ + "proposal_id", + "vote", + "voter", + "voting_power" + ], + "properties": { + "proposal_id": { + "description": "Proposal identifier", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "description": "Vote option", + "allOf": [ + { + "$ref": "#/definitions/ProposalVoteOption" + } + ] + }, + "voter": { + "type": "string" + }, + "voting_power": { + "description": "Voting power reported from outpost", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Set the status of a proposal that expired", "type": "object", @@ -410,6 +457,10 @@ } ] }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, "DistributionMsg": { "description": "The message types of the distribution module.\n\nSee https://github.com/cosmos/cosmos-sdk/blob/v0.42.4/proto/cosmos/distribution/v1beta1/tx.proto", "oneOf": [ @@ -812,25 +863,35 @@ }, "proposal_required_deposit": { "description": "Proposal required deposit", - "type": [ - "integer", - "null" - ], - "format": "uint128", - "minimum": 0.0 + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] }, "proposal_required_quorum": { "description": "Proposal required quorum", - "type": [ - "string", - "null" + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } ] }, "proposal_required_threshold": { "description": "Proposal required threshold", - "type": [ - "string", - "null" + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } ] }, "proposal_voting_period": { @@ -842,6 +903,13 @@ "format": "uint64", "minimum": 0.0 }, + "vxastro": { + "description": "Set vxASTRO and emissions controller contract at the same time. Emissions controller is queried from the vxASTRO contract.", + "type": [ + "string", + "null" + ] + }, "whitelist_add": { "description": "Links to add to whitelist", "type": [ diff --git a/schemas/astro-assembly/raw/instantiate.json b/schemas/astro-assembly/raw/instantiate.json index a92e80e1..fc1d3d22 100644 --- a/schemas/astro-assembly/raw/instantiate.json +++ b/schemas/astro-assembly/raw/instantiate.json @@ -19,20 +19,6 @@ "description": "Address of the builder unlock contract", "type": "string" }, - "generator_controller_addr": { - "description": "Generator controller contract capable of immediate proposals", - "type": [ - "string", - "null" - ] - }, - "hub_addr": { - "description": "Hub contract that handles voting from Outposts", - "type": [ - "string", - "null" - ] - }, "ibc_controller": { "description": "Astroport IBC controller contract", "type": [ @@ -78,20 +64,6 @@ "description": "Astroport xASTRO staking address. xASTRO denom and tracker contract address are queried on assembly instantiation.", "type": "string" }, - "voting_escrow_delegator_addr": { - "description": "Voting Escrow delegator address", - "type": [ - "string", - "null" - ] - }, - "vxastro_token_addr": { - "description": "Address of vxASTRO token", - "type": [ - "string", - "null" - ] - }, "whitelisted_links": { "description": "Whitelisted links", "type": "array", diff --git a/schemas/astro-assembly/raw/response_to_config.json b/schemas/astro-assembly/raw/response_to_config.json index 75d1d491..a41c8af7 100644 --- a/schemas/astro-assembly/raw/response_to_config.json +++ b/schemas/astro-assembly/raw/response_to_config.json @@ -24,6 +24,17 @@ } ] }, + "emissions_controller": { + "description": "Emissions controller contract. Optional", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, "ibc_controller": { "description": "Astroport IBC controller contract", "anyOf": [ @@ -77,6 +88,17 @@ "format": "uint64", "minimum": 0.0 }, + "vxastro_contract": { + "description": "vxASTRO contract address. Optional", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, "whitelisted_links": { "description": "Whitelisted links", "type": "array", @@ -89,6 +111,7 @@ "type": "string" }, "xastro_denom_tracking": { + "description": "xASTRO denom tracking contract", "type": "string" } }, diff --git a/schemas/astro-assembly/raw/response_to_proposal.json b/schemas/astro-assembly/raw/response_to_proposal.json index 21be9c2c..9f625bbc 100644 --- a/schemas/astro-assembly/raw/response_to_proposal.json +++ b/schemas/astro-assembly/raw/response_to_proposal.json @@ -12,8 +12,6 @@ "expiration_block", "for_power", "messages", - "outpost_against_power", - "outpost_for_power", "proposal_id", "start_block", "start_time", @@ -38,7 +36,7 @@ "minimum": 0.0 }, "deposit_amount": { - "description": "Amount of xASTRO deposited in order to post the proposal", + "description": "Amount of xASTRO deposited to post the proposal", "allOf": [ { "$ref": "#/definitions/Uint128" @@ -90,22 +88,6 @@ "$ref": "#/definitions/CosmosMsg_for_Empty" } }, - "outpost_against_power": { - "description": "`Against` power of proposal cast from all Outposts", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "outpost_for_power": { - "description": "`For` power of proposal cast from all Outposts", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, "proposal_id": { "description": "Unique proposal ID", "allOf": [ diff --git a/schemas/astro-assembly/raw/response_to_proposals.json b/schemas/astro-assembly/raw/response_to_proposals.json index 32e2b6f6..54a22890 100644 --- a/schemas/astro-assembly/raw/response_to_proposals.json +++ b/schemas/astro-assembly/raw/response_to_proposals.json @@ -474,8 +474,6 @@ "expiration_block", "for_power", "messages", - "outpost_against_power", - "outpost_for_power", "proposal_id", "start_block", "start_time", @@ -500,7 +498,7 @@ "minimum": 0.0 }, "deposit_amount": { - "description": "Amount of xASTRO deposited in order to post the proposal", + "description": "Amount of xASTRO deposited to post the proposal", "allOf": [ { "$ref": "#/definitions/Uint128" @@ -552,22 +550,6 @@ "$ref": "#/definitions/CosmosMsg_for_Empty" } }, - "outpost_against_power": { - "description": "`Against` power of proposal cast from all Outposts", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "outpost_for_power": { - "description": "`For` power of proposal cast from all Outposts", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, "proposal_id": { "description": "Unique proposal ID", "allOf": [ diff --git a/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json b/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json index 0c4e1ad4..cbb5828a 100644 --- a/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json +++ b/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json @@ -494,6 +494,40 @@ }, "additionalProperties": false }, + { + "description": "Allows using vxASTRO voting power to vote on general DAO proposals. The contract requires a proposal with specific id to be registered via a special permissionless IBC message.", + "type": "object", + "required": [ + "cast_vote" + ], + "properties": { + "cast_vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "description": "Proposal id", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "description": "Vote option", + "allOf": [ + { + "$ref": "#/definitions/ProposalVoteOption" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ @@ -532,6 +566,14 @@ } ] }, + "ProposalVoteOption": { + "description": "This enum describes available options for voting on a proposal.", + "type": "string", + "enum": [ + "for", + "against" + ] + }, "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" @@ -617,7 +659,7 @@ "type": "string" }, "ics20_channel": { - "description": "Official ICS20 IBC channel from this outpost to the Hub", + "description": "ICS20 IBC channel from this outpost to the Hub", "type": "string" }, "incentives_addr": { @@ -690,6 +732,14 @@ "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" }, + "ProposalVoteOption": { + "description": "This enum describes available options for voting on a proposal.", + "type": "string", + "enum": [ + "for", + "against" + ] + }, "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" @@ -712,15 +762,16 @@ "additionalProperties": false }, "VxAstroIbcMsg": { - "description": "Internal IBC message to be sent from outpost to the hub over voting channel", + "description": "Internal IBC messages for hub and outposts interactions", "oneOf": [ { + "description": "Sender: Outpost", "type": "object", "required": [ - "vote" + "emissions_vote" ], "properties": { - "vote": { + "emissions_vote": { "type": "object", "required": [ "voter", @@ -753,6 +804,7 @@ "additionalProperties": false }, { + "description": "Sender: Outpost", "type": "object", "required": [ "update_user_votes" @@ -786,6 +838,83 @@ } }, "additionalProperties": false + }, + { + "description": "Sender: Hub", + "type": "object", + "required": [ + "register_proposal" + ], + "properties": { + "register_proposal": { + "type": "object", + "required": [ + "proposal_id", + "start_time" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "start_time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Sender: Outpost", + "type": "object", + "required": [ + "governance_vote" + ], + "properties": { + "governance_vote": { + "type": "object", + "required": [ + "proposal_id", + "vote", + "voter", + "voting_power" + ], + "properties": { + "proposal_id": { + "description": "Proposal id", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "description": "Vote option", + "allOf": [ + { + "$ref": "#/definitions/ProposalVoteOption" + } + ] + }, + "voter": { + "type": "string" + }, + "voting_power": { + "description": "Actual voting power reported from outpost", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ] } diff --git a/schemas/astroport-emissions-controller-outpost/raw/execute.json b/schemas/astroport-emissions-controller-outpost/raw/execute.json index 0c2c1b63..5bcf1b5b 100644 --- a/schemas/astroport-emissions-controller-outpost/raw/execute.json +++ b/schemas/astroport-emissions-controller-outpost/raw/execute.json @@ -324,6 +324,40 @@ }, "additionalProperties": false }, + { + "description": "Allows using vxASTRO voting power to vote on general DAO proposals. The contract requires a proposal with specific id to be registered via a special permissionless IBC message.", + "type": "object", + "required": [ + "cast_vote" + ], + "properties": { + "cast_vote": { + "type": "object", + "required": [ + "proposal_id", + "vote" + ], + "properties": { + "proposal_id": { + "description": "Proposal id", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "description": "Vote option", + "allOf": [ + { + "$ref": "#/definitions/ProposalVoteOption" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ @@ -362,6 +396,14 @@ } ] }, + "ProposalVoteOption": { + "description": "This enum describes available options for voting on a proposal.", + "type": "string", + "enum": [ + "for", + "against" + ] + }, "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/schemas/astroport-emissions-controller-outpost/raw/response_to_config.json b/schemas/astroport-emissions-controller-outpost/raw/response_to_config.json index 5b2812d4..638885f2 100644 --- a/schemas/astroport-emissions-controller-outpost/raw/response_to_config.json +++ b/schemas/astroport-emissions-controller-outpost/raw/response_to_config.json @@ -31,7 +31,7 @@ "type": "string" }, "ics20_channel": { - "description": "Official ICS20 IBC channel from this outpost to the Hub", + "description": "ICS20 IBC channel from this outpost to the Hub", "type": "string" }, "incentives_addr": { diff --git a/schemas/astroport-emissions-controller-outpost/raw/response_to_query_user_ibc_status.json b/schemas/astroport-emissions-controller-outpost/raw/response_to_query_user_ibc_status.json index c53b9a21..0fe5be3b 100644 --- a/schemas/astroport-emissions-controller-outpost/raw/response_to_query_user_ibc_status.json +++ b/schemas/astroport-emissions-controller-outpost/raw/response_to_query_user_ibc_status.json @@ -31,6 +31,14 @@ "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", "type": "string" }, + "ProposalVoteOption": { + "description": "This enum describes available options for voting on a proposal.", + "type": "string", + "enum": [ + "for", + "against" + ] + }, "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" @@ -53,15 +61,16 @@ "additionalProperties": false }, "VxAstroIbcMsg": { - "description": "Internal IBC message to be sent from outpost to the hub over voting channel", + "description": "Internal IBC messages for hub and outposts interactions", "oneOf": [ { + "description": "Sender: Outpost", "type": "object", "required": [ - "vote" + "emissions_vote" ], "properties": { - "vote": { + "emissions_vote": { "type": "object", "required": [ "voter", @@ -94,6 +103,7 @@ "additionalProperties": false }, { + "description": "Sender: Outpost", "type": "object", "required": [ "update_user_votes" @@ -127,6 +137,83 @@ } }, "additionalProperties": false + }, + { + "description": "Sender: Hub", + "type": "object", + "required": [ + "register_proposal" + ], + "properties": { + "register_proposal": { + "type": "object", + "required": [ + "proposal_id", + "start_time" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "start_time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Sender: Outpost", + "type": "object", + "required": [ + "governance_vote" + ], + "properties": { + "governance_vote": { + "type": "object", + "required": [ + "proposal_id", + "vote", + "voter", + "voting_power" + ], + "properties": { + "proposal_id": { + "description": "Proposal id", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vote": { + "description": "Vote option", + "allOf": [ + { + "$ref": "#/definitions/ProposalVoteOption" + } + ] + }, + "voter": { + "type": "string" + }, + "voting_power": { + "description": "Actual voting power reported from outpost", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ] } diff --git a/schemas/astroport-emissions-controller/astroport-emissions-controller.json b/schemas/astroport-emissions-controller/astroport-emissions-controller.json index 042f910b..28089e96 100644 --- a/schemas/astroport-emissions-controller/astroport-emissions-controller.json +++ b/schemas/astroport-emissions-controller/astroport-emissions-controller.json @@ -8,6 +8,7 @@ "description": "This structure describes the basic settings for creating a contract.", "type": "object", "required": [ + "assembly", "astro_denom", "collected_astro", "emissions_multiple", @@ -22,6 +23,10 @@ "xastro_denom" ], "properties": { + "assembly": { + "description": "Astroport Assembly contract address", + "type": "string" + }, "astro_denom": { "description": "ASTRO denom on the Hub", "type": "string" @@ -614,6 +619,30 @@ } }, "additionalProperties": false + }, + { + "description": "Permissionless endpoint to stream proposal info from the Hub to all outposts", + "type": "object", + "required": [ + "register_proposal" + ], + "properties": { + "register_proposal": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ] }, @@ -832,6 +861,7 @@ "description": "General contract configuration", "type": "object", "required": [ + "assembly", "astro_denom", "emissions_multiple", "factory", @@ -847,6 +877,14 @@ "xastro_denom" ], "properties": { + "assembly": { + "description": "Astroport Assembly contract address", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, "astro_denom": { "description": "ASTRO denom on the Hub", "type": "string" diff --git a/schemas/astroport-emissions-controller/raw/execute.json b/schemas/astroport-emissions-controller/raw/execute.json index 18a0b391..ff092032 100644 --- a/schemas/astroport-emissions-controller/raw/execute.json +++ b/schemas/astroport-emissions-controller/raw/execute.json @@ -374,6 +374,30 @@ } }, "additionalProperties": false + }, + { + "description": "Permissionless endpoint to stream proposal info from the Hub to all outposts", + "type": "object", + "required": [ + "register_proposal" + ], + "properties": { + "register_proposal": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ] }, diff --git a/schemas/astroport-emissions-controller/raw/instantiate.json b/schemas/astroport-emissions-controller/raw/instantiate.json index 49ba7c85..91e6514a 100644 --- a/schemas/astroport-emissions-controller/raw/instantiate.json +++ b/schemas/astroport-emissions-controller/raw/instantiate.json @@ -4,6 +4,7 @@ "description": "This structure describes the basic settings for creating a contract.", "type": "object", "required": [ + "assembly", "astro_denom", "collected_astro", "emissions_multiple", @@ -18,6 +19,10 @@ "xastro_denom" ], "properties": { + "assembly": { + "description": "Astroport Assembly contract address", + "type": "string" + }, "astro_denom": { "description": "ASTRO denom on the Hub", "type": "string" diff --git a/schemas/astroport-emissions-controller/raw/response_to_config.json b/schemas/astroport-emissions-controller/raw/response_to_config.json index 1d71073c..fb59f4a4 100644 --- a/schemas/astroport-emissions-controller/raw/response_to_config.json +++ b/schemas/astroport-emissions-controller/raw/response_to_config.json @@ -4,6 +4,7 @@ "description": "General contract configuration", "type": "object", "required": [ + "assembly", "astro_denom", "emissions_multiple", "factory", @@ -19,6 +20,14 @@ "xastro_denom" ], "properties": { + "assembly": { + "description": "Astroport Assembly contract address", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, "astro_denom": { "description": "ASTRO denom on the Hub", "type": "string" diff --git a/schemas/astroport-voting-escrow/astroport-voting-escrow.json b/schemas/astroport-voting-escrow/astroport-voting-escrow.json index 77380f2c..d94d27bc 100644 --- a/schemas/astroport-voting-escrow/astroport-voting-escrow.json +++ b/schemas/astroport-voting-escrow/astroport-voting-escrow.json @@ -358,7 +358,7 @@ "total_voting_power": { "type": "object", "properties": { - "time": { + "timestamp": { "type": [ "integer", "null" @@ -385,7 +385,7 @@ "user" ], "properties": { - "time": { + "timestamp": { "type": [ "integer", "null" diff --git a/schemas/astroport-voting-escrow/raw/query.json b/schemas/astroport-voting-escrow/raw/query.json index 86839735..3b034b86 100644 --- a/schemas/astroport-voting-escrow/raw/query.json +++ b/schemas/astroport-voting-escrow/raw/query.json @@ -63,7 +63,7 @@ "total_voting_power": { "type": "object", "properties": { - "time": { + "timestamp": { "type": [ "integer", "null" @@ -90,7 +90,7 @@ "user" ], "properties": { - "time": { + "timestamp": { "type": [ "integer", "null" From 1d2bbfae4548e2d7a68c4d1b10145c6c1381c0ba Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Fri, 7 Jun 2024 12:58:04 +0400 Subject: [PATCH 11/32] bump crate versions --- Cargo.lock | 48 +++++++++---------- contracts/assembly/Cargo.toml | 4 +- contracts/builder_unlock/Cargo.toml | 2 +- contracts/emissions_controller/Cargo.toml | 4 +- .../emissions_controller_outpost/Cargo.toml | 2 +- contracts/voting_escrow/Cargo.toml | 2 +- packages/astroport-governance/Cargo.toml | 2 +- 7 files changed, 32 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f7f6f579..fea7c638 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,12 +21,12 @@ checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" [[package]] name = "astro-assembly" -version = "2.0.0" +version = "3.0.0" dependencies = [ "anyhow", "astro-satellite", - "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", - "astroport-governance 3.0.0", + "astroport 4.0.3", + "astroport-governance 4.0.0", "astroport-staking 2.1.0 (git+https://github.com/astroport-fi/hidden_astroport_core)", "astroport-tokenfactory-tracker 1.0.0 (git+https://github.com/astroport-fi/hidden_astroport_core)", "astroport-voting-escrow", @@ -109,9 +109,9 @@ dependencies = [ [[package]] name = "astroport" version = "4.0.2" -source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#ada68324a4cca143008646d3c81baa715cba12d2" +source = "git+https://github.com/astroport-fi/hidden_astroport_core#02eafc677d9c402a55e45e956fd185d67ac3a8a6" dependencies = [ - "astroport-circular-buffer 0.2.0 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "astroport-circular-buffer 0.2.0 (git+https://github.com/astroport-fi/hidden_astroport_core)", "cosmwasm-schema", "cosmwasm-std", "cw-asset", @@ -124,10 +124,10 @@ dependencies = [ [[package]] name = "astroport" -version = "4.0.2" -source = "git+https://github.com/astroport-fi/hidden_astroport_core#02eafc677d9c402a55e45e956fd185d67ac3a8a6" +version = "4.0.3" +source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#7610b906facf2f2cc5a2bd7f5198a3533b63ee2e" dependencies = [ - "astroport-circular-buffer 0.2.0 (git+https://github.com/astroport-fi/hidden_astroport_core)", + "astroport-circular-buffer 0.2.0 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", "cosmwasm-schema", "cosmwasm-std", "cw-asset", @@ -153,7 +153,7 @@ dependencies = [ [[package]] name = "astroport-circular-buffer" version = "0.2.0" -source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#ada68324a4cca143008646d3c81baa715cba12d2" +source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#7610b906facf2f2cc5a2bd7f5198a3533b63ee2e" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -178,9 +178,9 @@ version = "1.0.0" dependencies = [ "anyhow", "astro-assembly", - "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "astroport 4.0.3", "astroport-factory", - "astroport-governance 3.0.0", + "astroport-governance 4.0.0", "astroport-incentives", "astroport-pair", "astroport-staking 2.1.0 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", @@ -206,9 +206,9 @@ dependencies = [ name = "astroport-emissions-controller-outpost" version = "1.0.0" dependencies = [ - "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "astroport 4.0.3", "astroport-factory", - "astroport-governance 3.0.0", + "astroport-governance 4.0.0", "astroport-incentives", "astroport-pair", "astroport-voting-escrow", @@ -257,9 +257,9 @@ dependencies = [ [[package]] name = "astroport-governance" -version = "3.0.0" +version = "4.0.0" dependencies = [ - "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "astroport 4.0.3", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", @@ -282,7 +282,7 @@ name = "astroport-incentives" version = "1.2.0" source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#ada68324a4cca143008646d3c81baa715cba12d2" dependencies = [ - "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "astroport 4.0.3", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", @@ -316,7 +316,7 @@ name = "astroport-staking" version = "2.1.0" source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#ada68324a4cca143008646d3c81baa715cba12d2" dependencies = [ - "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "astroport 4.0.3", "cosmwasm-std", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", @@ -330,7 +330,7 @@ name = "astroport-staking" version = "2.1.0" source = "git+https://github.com/astroport-fi/hidden_astroport_core#02eafc677d9c402a55e45e956fd185d67ac3a8a6" dependencies = [ - "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core)", + "astroport 4.0.2", "cosmwasm-std", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", @@ -344,7 +344,7 @@ name = "astroport-tokenfactory-tracker" version = "1.0.0" source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#ada68324a4cca143008646d3c81baa715cba12d2" dependencies = [ - "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "astroport 4.0.3", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", @@ -357,7 +357,7 @@ name = "astroport-tokenfactory-tracker" version = "1.0.0" source = "git+https://github.com/astroport-fi/hidden_astroport_core#02eafc677d9c402a55e45e956fd185d67ac3a8a6" dependencies = [ - "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core)", + "astroport 4.0.2", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", @@ -369,8 +369,8 @@ dependencies = [ name = "astroport-voting-escrow" version = "1.0.0" dependencies = [ - "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", - "astroport-governance 3.0.0", + "astroport 4.0.3", + "astroport-governance 4.0.0", "cosmwasm-schema", "cosmwasm-std", "cw-multi-test 1.1.0", @@ -446,8 +446,8 @@ checksum = "56953345e39537a3e18bdaeba4cb0c58a78c1f61f361dc0fa7c5c7340ae87c5f" name = "builder-unlock" version = "3.0.0" dependencies = [ - "astroport 4.0.2 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", - "astroport-governance 3.0.0", + "astroport 4.0.3", + "astroport-governance 4.0.0", "cosmwasm-schema", "cosmwasm-std", "cw-multi-test 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/contracts/assembly/Cargo.toml b/contracts/assembly/Cargo.toml index dfdaac8c..17c569b2 100644 --- a/contracts/assembly/Cargo.toml +++ b/contracts/assembly/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astro-assembly" -version = "2.0.0" +version = "3.0.0" authors = ["Astroport"] edition = "2021" description = "Astroport DAO Contract" @@ -23,7 +23,7 @@ cw-storage-plus.workspace = true thiserror.workspace = true cosmwasm-schema.workspace = true cw-utils.workspace = true -astroport-governance = { path = "../../packages/astroport-governance", version = "3" } +astroport-governance = { path = "../../packages/astroport-governance", version = "4" } astroport.workspace = true astro-satellite = { version = "1.1.0", features = ["library"] } ibc-controller-package = "1.0.0" diff --git a/contracts/builder_unlock/Cargo.toml b/contracts/builder_unlock/Cargo.toml index 7deaeb6b..8d6a63de 100644 --- a/contracts/builder_unlock/Cargo.toml +++ b/contracts/builder_unlock/Cargo.toml @@ -22,7 +22,7 @@ cosmwasm-std.workspace = true cw-storage-plus.workspace = true cosmwasm-schema.workspace = true thiserror.workspace = true -astroport-governance = { path = "../../packages/astroport-governance", version = "3" } +astroport-governance = { path = "../../packages/astroport-governance", version = "4" } astroport.workspace = true [dev-dependencies] diff --git a/contracts/emissions_controller/Cargo.toml b/contracts/emissions_controller/Cargo.toml index 9230ac03..8708f948 100644 --- a/contracts/emissions_controller/Cargo.toml +++ b/contracts/emissions_controller/Cargo.toml @@ -23,7 +23,7 @@ cw-storage-plus.workspace = true cosmwasm-schema.workspace = true thiserror.workspace = true itertools.workspace = true -astroport-governance = { path = "../../packages/astroport-governance", version = "3" } +astroport-governance = { path = "../../packages/astroport-governance", version = "4" } astroport = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "4" } neutron-sdk = "0.10.0" serde_json = "1" @@ -31,7 +31,7 @@ serde_json = "1" [dev-dependencies] cw-multi-test = { git = "https://github.com/astroport-fi/cw-multi-test", branch = "feat/bank_with_send_hooks_1_0", features = ["cosmwasm_1_1"] } astroport-voting-escrow = { path = "../voting_escrow", version = "1", features = ["library"] } -astro-assembly = { path = "../assembly", version = "2", features = ["library"] } +astro-assembly = { path = "../assembly", version = "3", features = ["library"] } builder-unlock = { path = "../builder_unlock", version = "3", features = ["library"] } astroport-factory = { version = "1.7", features = ["library"] } astroport-pair = { version = "1.5", features = ["library"] } diff --git a/contracts/emissions_controller_outpost/Cargo.toml b/contracts/emissions_controller_outpost/Cargo.toml index f1837e24..660837b1 100644 --- a/contracts/emissions_controller_outpost/Cargo.toml +++ b/contracts/emissions_controller_outpost/Cargo.toml @@ -23,7 +23,7 @@ cw-storage-plus.workspace = true cosmwasm-schema.workspace = true thiserror.workspace = true itertools.workspace = true -astroport-governance = { path = "../../packages/astroport-governance", version = "3" } +astroport-governance = { path = "../../packages/astroport-governance", version = "4" } astroport.workspace = true serde_json = "1" diff --git a/contracts/voting_escrow/Cargo.toml b/contracts/voting_escrow/Cargo.toml index 2c23b38d..1c11b47e 100644 --- a/contracts/voting_escrow/Cargo.toml +++ b/contracts/voting_escrow/Cargo.toml @@ -24,7 +24,7 @@ cosmwasm-std.workspace = true cw-storage-plus.workspace = true thiserror.workspace = true cosmwasm-schema.workspace = true -astroport-governance = { path = "../../packages/astroport-governance", version = "3" } +astroport-governance = { path = "../../packages/astroport-governance", version = "4" } astroport.workspace = true [dev-dependencies] diff --git a/packages/astroport-governance/Cargo.toml b/packages/astroport-governance/Cargo.toml index 26633aef..29e6e811 100644 --- a/packages/astroport-governance/Cargo.toml +++ b/packages/astroport-governance/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astroport-governance" -version = "3.0.0" +version = "4.0.0" authors = ["Astroport"] edition = "2021" repository = "https://github.com/astroport-fi/astroport-governance" From 7d256d37462e2edf9560f014d0a0a5b4951d83ba Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Fri, 7 Jun 2024 12:58:38 +0400 Subject: [PATCH 12/32] update schema --- schemas/astro-assembly/astro-assembly.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schemas/astro-assembly/astro-assembly.json b/schemas/astro-assembly/astro-assembly.json index 5e93b341..39f2813e 100644 --- a/schemas/astro-assembly/astro-assembly.json +++ b/schemas/astro-assembly/astro-assembly.json @@ -1,6 +1,6 @@ { "contract_name": "astro-assembly", - "contract_version": "2.0.0", + "contract_version": "3.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", From 6caae2c0d89f5e864fc4c7d8941b47cea22cc4aa Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Fri, 7 Jun 2024 17:59:03 +0400 Subject: [PATCH 13/32] vxASTRO marketing info is mandatory --- contracts/assembly/tests/common/helper.rs | 4 +-- .../tests/common/helper.rs | 4 +-- .../tests/common/helper.rs | 4 +-- contracts/voting_escrow/src/contract.rs | 36 +++++++++---------- contracts/voting_escrow/tests/helper.rs | 4 +-- .../src/emissions_controller/hub.rs | 2 +- .../src/emissions_controller/outpost.rs | 2 +- .../astroport-governance/src/voting_escrow.rs | 2 +- ...stroport-emissions-controller-outpost.json | 6 ++-- .../raw/instantiate.json | 6 ++-- .../astroport-emissions-controller.json | 6 ++-- .../raw/instantiate.json | 6 ++-- .../astroport-voting-escrow.json | 8 ++--- .../raw/instantiate.json | 8 ++--- 14 files changed, 41 insertions(+), 57 deletions(-) diff --git a/contracts/assembly/tests/common/helper.rs b/contracts/assembly/tests/common/helper.rs index e2551541..702feddd 100644 --- a/contracts/assembly/tests/common/helper.rs +++ b/contracts/assembly/tests/common/helper.rs @@ -225,12 +225,12 @@ impl Helper { &voting_escrow::InstantiateMsg { deposit_denom: xastro_denom.to_string(), emissions_controller: mocked_emission_controller.to_string(), - marketing: Some(UpdateMarketingInfo { + marketing: UpdateMarketingInfo { project: None, description: None, marketing: Some(owner.to_string()), logo: Some(Logo::Url("https://example.com".to_string())), - }), + }, }, &[], "label", diff --git a/contracts/emissions_controller/tests/common/helper.rs b/contracts/emissions_controller/tests/common/helper.rs index 8b0db98a..5e6bad9a 100644 --- a/contracts/emissions_controller/tests/common/helper.rs +++ b/contracts/emissions_controller/tests/common/helper.rs @@ -256,12 +256,12 @@ impl ControllerHelper { owner: owner.to_string(), assembly: assembly.to_string(), vxastro_code_id, - vxastro_marketing_info: Some(UpdateMarketingInfo { + vxastro_marketing_info: UpdateMarketingInfo { project: None, description: None, marketing: None, logo: Some(Logo::Url("".to_string())), - }), + }, xastro_denom: xastro_denom.clone(), factory: factory.to_string(), astro_denom: astro_denom.to_string(), diff --git a/contracts/emissions_controller_outpost/tests/common/helper.rs b/contracts/emissions_controller_outpost/tests/common/helper.rs index 05c51e56..4924b489 100644 --- a/contracts/emissions_controller_outpost/tests/common/helper.rs +++ b/contracts/emissions_controller_outpost/tests/common/helper.rs @@ -160,12 +160,12 @@ impl ControllerHelper { owner: owner.to_string(), astro_denom: astro_denom.to_string(), vxastro_code_id, - vxastro_marketing_info: Some(UpdateMarketingInfo { + vxastro_marketing_info: UpdateMarketingInfo { project: None, description: None, marketing: None, logo: Some(Logo::Url("".to_string())), - }), + }, xastro_denom: xastro_denom.to_string(), factory: factory.to_string(), hub_emissions_controller: "emissions_controller".to_string(), diff --git a/contracts/voting_escrow/src/contract.rs b/contracts/voting_escrow/src/contract.rs index 41af491d..0bfbbd4d 100644 --- a/contracts/voting_escrow/src/contract.rs +++ b/contracts/voting_escrow/src/contract.rs @@ -42,27 +42,23 @@ pub fn instantiate( }; CONFIG.save(deps.storage, &config)?; - if let Some(marketing) = msg.marketing { - let logo = match &marketing.logo { - Some(Logo::Url(url)) => { - LOGO.save(deps.storage, &marketing.logo.clone().unwrap())?; - Some(LogoInfo::Url(url.clone())) - } - _ => { - return Err(StdError::generic_err("Logo url must be set").into()); - } - }; + let logo = match &msg.marketing.logo { + Some(Logo::Url(url)) => { + LOGO.save(deps.storage, &msg.marketing.logo.clone().unwrap())?; + Some(LogoInfo::Url(url.clone())) + } + _ => { + return Err(StdError::generic_err("Logo url must be set").into()); + } + }; - let data = MarketingInfoResponse { - project: marketing.project, - description: marketing.description, - marketing: addr_opt_validate(deps.api, &marketing.marketing)?, - logo, - }; - MARKETING_INFO.save(deps.storage, &data)?; - } else { - return Err(StdError::generic_err("Marketing info is required").into()); - } + let data = MarketingInfoResponse { + project: msg.marketing.project, + description: msg.marketing.description, + marketing: addr_opt_validate(deps.api, &msg.marketing.marketing)?, + logo, + }; + MARKETING_INFO.save(deps.storage, &data)?; // Store token info let data = TokenInfo { diff --git a/contracts/voting_escrow/tests/helper.rs b/contracts/voting_escrow/tests/helper.rs index bae3d63d..4dd3fbac 100644 --- a/contracts/voting_escrow/tests/helper.rs +++ b/contracts/voting_escrow/tests/helper.rs @@ -75,12 +75,12 @@ impl EscrowHelper { &InstantiateMsg { deposit_denom: xastro_denom.to_string(), emissions_controller: mocked_emission_controller.to_string(), - marketing: Some(UpdateMarketingInfo { + marketing: UpdateMarketingInfo { project: None, description: None, marketing: Some(owner.to_string()), logo: Some(Logo::Url("https://example.com".to_string())), - }), + }, }, &[], "label", diff --git a/packages/astroport-governance/src/emissions_controller/hub.rs b/packages/astroport-governance/src/emissions_controller/hub.rs index ee2bfc19..9e1941da 100644 --- a/packages/astroport-governance/src/emissions_controller/hub.rs +++ b/packages/astroport-governance/src/emissions_controller/hub.rs @@ -17,7 +17,7 @@ pub struct HubInstantiateMsg { /// vxASTRO contract code id pub vxastro_code_id: u64, /// vxASTRO token marketing info - pub vxastro_marketing_info: Option, + pub vxastro_marketing_info: UpdateMarketingInfo, /// xASTRO denom pub xastro_denom: String, /// Astroport Factory contract diff --git a/packages/astroport-governance/src/emissions_controller/outpost.rs b/packages/astroport-governance/src/emissions_controller/outpost.rs index 0118a924..f390c0c8 100644 --- a/packages/astroport-governance/src/emissions_controller/outpost.rs +++ b/packages/astroport-governance/src/emissions_controller/outpost.rs @@ -18,7 +18,7 @@ pub struct OutpostInstantiateMsg { /// vxASTRO contract code id pub vxastro_code_id: u64, /// vxASTRO token marketing info - pub vxastro_marketing_info: Option, + pub vxastro_marketing_info: UpdateMarketingInfo, /// Astroport Factory contract pub factory: String, /// Emissions controller on the Hub diff --git a/packages/astroport-governance/src/voting_escrow.rs b/packages/astroport-governance/src/voting_escrow.rs index f91f0601..ab5eba9a 100644 --- a/packages/astroport-governance/src/voting_escrow.rs +++ b/packages/astroport-governance/src/voting_escrow.rs @@ -23,7 +23,7 @@ pub struct InstantiateMsg { /// Astroport Emissions Controller contract pub emissions_controller: String, /// Marketing info for vxASTRO - pub marketing: Option, + pub marketing: UpdateMarketingInfo, } /// This structure describes the execute functions in the contract. diff --git a/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json b/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json index cbb5828a..32d2007d 100644 --- a/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json +++ b/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json @@ -14,6 +14,7 @@ "ics20_channel", "owner", "vxastro_code_id", + "vxastro_marketing_info", "xastro_denom" ], "properties": { @@ -45,12 +46,9 @@ }, "vxastro_marketing_info": { "description": "vxASTRO token marketing info", - "anyOf": [ + "allOf": [ { "$ref": "#/definitions/UpdateMarketingInfo" - }, - { - "type": "null" } ] }, diff --git a/schemas/astroport-emissions-controller-outpost/raw/instantiate.json b/schemas/astroport-emissions-controller-outpost/raw/instantiate.json index 641e06ba..6c9abebc 100644 --- a/schemas/astroport-emissions-controller-outpost/raw/instantiate.json +++ b/schemas/astroport-emissions-controller-outpost/raw/instantiate.json @@ -10,6 +10,7 @@ "ics20_channel", "owner", "vxastro_code_id", + "vxastro_marketing_info", "xastro_denom" ], "properties": { @@ -41,12 +42,9 @@ }, "vxastro_marketing_info": { "description": "vxASTRO token marketing info", - "anyOf": [ + "allOf": [ { "$ref": "#/definitions/UpdateMarketingInfo" - }, - { - "type": "null" } ] }, diff --git a/schemas/astroport-emissions-controller/astroport-emissions-controller.json b/schemas/astroport-emissions-controller/astroport-emissions-controller.json index 28089e96..900c9ea8 100644 --- a/schemas/astroport-emissions-controller/astroport-emissions-controller.json +++ b/schemas/astroport-emissions-controller/astroport-emissions-controller.json @@ -18,6 +18,7 @@ "owner", "pools_per_outpost", "vxastro_code_id", + "vxastro_marketing_info", "whitelist_threshold", "whitelisting_fee", "xastro_denom" @@ -81,12 +82,9 @@ }, "vxastro_marketing_info": { "description": "vxASTRO token marketing info", - "anyOf": [ + "allOf": [ { "$ref": "#/definitions/UpdateMarketingInfo" - }, - { - "type": "null" } ] }, diff --git a/schemas/astroport-emissions-controller/raw/instantiate.json b/schemas/astroport-emissions-controller/raw/instantiate.json index 91e6514a..32ce98a5 100644 --- a/schemas/astroport-emissions-controller/raw/instantiate.json +++ b/schemas/astroport-emissions-controller/raw/instantiate.json @@ -14,6 +14,7 @@ "owner", "pools_per_outpost", "vxastro_code_id", + "vxastro_marketing_info", "whitelist_threshold", "whitelisting_fee", "xastro_denom" @@ -77,12 +78,9 @@ }, "vxastro_marketing_info": { "description": "vxASTRO token marketing info", - "anyOf": [ + "allOf": [ { "$ref": "#/definitions/UpdateMarketingInfo" - }, - { - "type": "null" } ] }, diff --git a/schemas/astroport-voting-escrow/astroport-voting-escrow.json b/schemas/astroport-voting-escrow/astroport-voting-escrow.json index d94d27bc..337deaa4 100644 --- a/schemas/astroport-voting-escrow/astroport-voting-escrow.json +++ b/schemas/astroport-voting-escrow/astroport-voting-escrow.json @@ -9,7 +9,8 @@ "type": "object", "required": [ "deposit_denom", - "emissions_controller" + "emissions_controller", + "marketing" ], "properties": { "deposit_denom": { @@ -22,12 +23,9 @@ }, "marketing": { "description": "Marketing info for vxASTRO", - "anyOf": [ + "allOf": [ { "$ref": "#/definitions/UpdateMarketingInfo" - }, - { - "type": "null" } ] } diff --git a/schemas/astroport-voting-escrow/raw/instantiate.json b/schemas/astroport-voting-escrow/raw/instantiate.json index 0114d2ad..562470df 100644 --- a/schemas/astroport-voting-escrow/raw/instantiate.json +++ b/schemas/astroport-voting-escrow/raw/instantiate.json @@ -5,7 +5,8 @@ "type": "object", "required": [ "deposit_denom", - "emissions_controller" + "emissions_controller", + "marketing" ], "properties": { "deposit_denom": { @@ -18,12 +19,9 @@ }, "marketing": { "description": "Marketing info for vxASTRO", - "anyOf": [ + "allOf": [ { "$ref": "#/definitions/UpdateMarketingInfo" - }, - { - "type": "null" } ] } From 41dd3a8b59a077e36e6e89087736c817df4805d6 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Mon, 10 Jun 2024 12:23:15 +0400 Subject: [PATCH 14/32] rename field --- contracts/emissions_controller/src/execute.rs | 2 +- contracts/emissions_controller/tests/common/helper.rs | 2 +- packages/astroport-governance/src/emissions_controller/hub.rs | 2 +- .../astroport-emissions-controller.json | 4 ++-- schemas/astroport-emissions-controller/raw/execute.json | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/contracts/emissions_controller/src/execute.rs b/contracts/emissions_controller/src/execute.rs index b9b0404c..b979b313 100644 --- a/contracts/emissions_controller/src/execute.rs +++ b/contracts/emissions_controller/src/execute.rs @@ -139,7 +139,7 @@ pub fn execute( .map_err(Into::into) } ExecuteMsg::Custom(hub_msg) => match hub_msg { - HubMsg::WhitelistPool { pool } => whitelist_pool(deps, env, info, pool), + HubMsg::WhitelistPool { lp_token: pool } => whitelist_pool(deps, env, info, pool), HubMsg::UpdateOutpost { prefix, astro_denom, diff --git a/contracts/emissions_controller/tests/common/helper.rs b/contracts/emissions_controller/tests/common/helper.rs index 5e6bad9a..d2c09e4e 100644 --- a/contracts/emissions_controller/tests/common/helper.rs +++ b/contracts/emissions_controller/tests/common/helper.rs @@ -462,7 +462,7 @@ impl ControllerHelper { user.clone(), self.emission_controller.clone(), &emissions_controller::msg::ExecuteMsg::Custom(HubMsg::WhitelistPool { - pool: pool.into(), + lp_token: pool.into(), }), fees, ) diff --git a/packages/astroport-governance/src/emissions_controller/hub.rs b/packages/astroport-governance/src/emissions_controller/hub.rs index 9e1941da..e82caed4 100644 --- a/packages/astroport-governance/src/emissions_controller/hub.rs +++ b/packages/astroport-governance/src/emissions_controller/hub.rs @@ -63,7 +63,7 @@ pub enum HubMsg { max_astro: Option, }, /// Whitelists a pool to receive ASTRO emissions. Requires fee payment - WhitelistPool { pool: String }, + WhitelistPool { lp_token: String }, /// Register or update an outpost UpdateOutpost { /// Bech32 prefix diff --git a/schemas/astroport-emissions-controller/astroport-emissions-controller.json b/schemas/astroport-emissions-controller/astroport-emissions-controller.json index 900c9ea8..cd5b342e 100644 --- a/schemas/astroport-emissions-controller/astroport-emissions-controller.json +++ b/schemas/astroport-emissions-controller/astroport-emissions-controller.json @@ -534,10 +534,10 @@ "whitelist_pool": { "type": "object", "required": [ - "pool" + "lp_token" ], "properties": { - "pool": { + "lp_token": { "type": "string" } }, diff --git a/schemas/astroport-emissions-controller/raw/execute.json b/schemas/astroport-emissions-controller/raw/execute.json index ff092032..461a9e4c 100644 --- a/schemas/astroport-emissions-controller/raw/execute.json +++ b/schemas/astroport-emissions-controller/raw/execute.json @@ -291,10 +291,10 @@ "whitelist_pool": { "type": "object", "required": [ - "pool" + "lp_token" ], "properties": { - "pool": { + "lp_token": { "type": "string" } }, From de1f4ceb2ffc96fa6e48936089953ca86a33a563 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Mon, 10 Jun 2024 18:13:42 +0400 Subject: [PATCH 15/32] fix dynamic emissions curve --- contracts/emissions_controller/src/instantiate.rs | 1 + contracts/emissions_controller/src/query.rs | 13 +++++++++---- contracts/emissions_controller/src/utils.rs | 7 ++++--- .../emissions_controller/tests/common/helper.rs | 3 ++- .../tests/emissions_controller_integration.rs | 10 +++++++--- .../src/emissions_controller/hub.rs | 6 +++++- .../astroport-emissions-controller.json | 6 +++--- .../astroport-emissions-controller/raw/query.json | 4 ++-- ...pools_list.json => response_to_voted_pools.json} | 0 9 files changed, 33 insertions(+), 17 deletions(-) rename schemas/astroport-emissions-controller/raw/{response_to_voted_pools_list.json => response_to_voted_pools.json} (100%) diff --git a/contracts/emissions_controller/src/instantiate.rs b/contracts/emissions_controller/src/instantiate.rs index 28c0fa5a..c0a4a2cb 100644 --- a/contracts/emissions_controller/src/instantiate.rs +++ b/contracts/emissions_controller/src/instantiate.rs @@ -86,6 +86,7 @@ pub fn instantiate( emissions_state: EmissionsState { xastro_rate, collected_astro: msg.collected_astro, + ema: msg.ema, emissions_amount: Uint128::zero(), }, }, diff --git a/contracts/emissions_controller/src/query.rs b/contracts/emissions_controller/src/query.rs index 85a40fff..f0b51e4c 100644 --- a/contracts/emissions_controller/src/query.rs +++ b/contracts/emissions_controller/src/query.rs @@ -14,7 +14,7 @@ use astroport_governance::emissions_controller::hub::{ use crate::error::ContractError; use crate::state::{CONFIG, OUTPOSTS, POOLS_WHITELIST, TUNE_INFO, USER_INFO, VOTED_POOLS}; -use crate::utils::{get_epoch_start, simulate_tune}; +use crate::utils::simulate_tune; /// Expose available contract queries. #[cfg_attr(not(feature = "library"), entry_point)] @@ -79,7 +79,7 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result { + QueryMsg::VotedPools { limit, start_after } => { let limit = limit.unwrap_or(MAX_PAGE_LIMIT) as usize; let voted_pools = VOTED_POOLS .range( @@ -108,10 +108,15 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result>>()?; - let epoch_start = get_epoch_start(env.block.time.seconds()); let config = CONFIG.load(deps.storage)?; - let tune_result = simulate_tune(deps, &voted_pools, &outposts, epoch_start, &config)?; + let tune_result = simulate_tune( + deps, + &voted_pools, + &outposts, + env.block.time.seconds(), + &config, + )?; Ok(to_json_binary(&SimulateTuneResponse { new_emissions_state: tune_result.new_emissions_state, next_pools_grouped: tune_result.next_pools_grouped, diff --git a/contracts/emissions_controller/src/utils.rs b/contracts/emissions_controller/src/utils.rs index 0812a349..456d964a 100644 --- a/contracts/emissions_controller/src/utils.rs +++ b/contracts/emissions_controller/src/utils.rs @@ -226,11 +226,11 @@ pub fn get_xastro_rate_and_share( /// and derive emissions for the upcoming epoch. /// /// Calculate two-epochs EMA by the following formula: -/// (V_n-1 * 2/3 + V_n-2 * 1/3), +/// (V_n-1 * 2/3 + EMA_n-1 * 1/3), /// where V_n is the collected ASTRO at epoch n, n is the current epoch (a starting one). /// /// Dynamic emissions formula is: -/// next emissions = MAX(MIN(max_astro, V_n-2 * emissions_multiple), MIN(max_astro, two-epochs EMA)) +/// next emissions = MAX(MIN(max_astro, V_n-1 * emissions_multiple), MIN(max_astro, two-epochs EMA)) pub fn astro_emissions_curve( deps: Deps, emissions_state: EmissionsState, @@ -242,7 +242,7 @@ pub fn astro_emissions_curve( let two_thirds = Decimal::from_ratio(2u8, 3u8); let one_third = Decimal::from_ratio(1u8, 3u8); - let ema = collected_astro * two_thirds + emissions_state.collected_astro * one_third; + let ema = collected_astro * two_thirds + emissions_state.ema * one_third; let min_1 = (emissions_state.collected_astro * config.emissions_multiple).min(config.max_astro); let min_2 = (ema * config.emissions_multiple).min(config.max_astro); @@ -250,6 +250,7 @@ pub fn astro_emissions_curve( Ok(EmissionsState { xastro_rate: actual_rate, collected_astro, + ema, emissions_amount: min_1.max(min_2), }) } diff --git a/contracts/emissions_controller/tests/common/helper.rs b/contracts/emissions_controller/tests/common/helper.rs index d2c09e4e..f8c12dda 100644 --- a/contracts/emissions_controller/tests/common/helper.rs +++ b/contracts/emissions_controller/tests/common/helper.rs @@ -272,6 +272,7 @@ impl ControllerHelper { emissions_multiple: Decimal::percent(80), max_astro: 1_400_000_000_000u128.into(), collected_astro: 334_000_000_000u128.into(), + ema: 300_000_000_000u128.into(), }, &[], "label", @@ -519,7 +520,7 @@ impl ControllerHelper { pub fn query_voted_pools(&self, limit: Option) -> StdResult> { self.app.wrap().query_wasm_smart( &self.emission_controller, - &emissions_controller::hub::QueryMsg::VotedPoolsList { + &emissions_controller::hub::QueryMsg::VotedPools { limit, start_after: None, }, diff --git a/contracts/emissions_controller/tests/emissions_controller_integration.rs b/contracts/emissions_controller/tests/emissions_controller_integration.rs index fa9e8461..c6712784 100644 --- a/contracts/emissions_controller/tests/emissions_controller_integration.rs +++ b/contracts/emissions_controller/tests/emissions_controller_integration.rs @@ -625,8 +625,8 @@ fn test_tune_only_hub() { pools_grouped: HashMap::from([( "neutron".to_string(), vec![ - (lp_token1.to_string(), Uint128::new(133333333332)), - (lp_token2.to_string(), Uint128::new(133333333332)), + (lp_token1.to_string(), Uint128::new(146666666665)), + (lp_token2.to_string(), Uint128::new(146666666665)), (astro_pool.to_string(), Uint128::new(1000000000)), ] .into_iter() @@ -637,7 +637,8 @@ fn test_tune_only_hub() { emissions_state: EmissionsState { xastro_rate: Decimal::from_str("499501.4995004995004995").unwrap(), collected_astro: 499999999999u128.into(), - emissions_amount: 266666666665u128.into(), + ema: 366666666664u128.into(), + emissions_amount: 293333333331u128.into(), }, }; assert_eq!(tune_info, expected_tune_info); @@ -665,6 +666,7 @@ fn test_tune_only_hub() { emissions_state: EmissionsState { xastro_rate: Decimal::one(), collected_astro: 0u128.into(), + ema: 99999999999u128.into(), emissions_amount: 267200000000u128.into(), }, }; @@ -754,6 +756,7 @@ fn test_tune_outpost() { emissions_state: EmissionsState { xastro_rate: Decimal::one(), collected_astro: 0u128.into(), + ema: 99999999999u128.into(), emissions_amount: 267200000000u128.into(), }, }; @@ -857,6 +860,7 @@ fn test_tune_outpost() { emissions_state: EmissionsState { xastro_rate: Decimal::one(), collected_astro: 0u128.into(), + ema: 99999999999u128.into(), emissions_amount: 267200000000u128.into(), }, }; diff --git a/packages/astroport-governance/src/emissions_controller/hub.rs b/packages/astroport-governance/src/emissions_controller/hub.rs index e82caed4..b8696b36 100644 --- a/packages/astroport-governance/src/emissions_controller/hub.rs +++ b/packages/astroport-governance/src/emissions_controller/hub.rs @@ -46,6 +46,8 @@ pub struct HubInstantiateMsg { /// Defines the number of ASTRO collected to staking contract /// from 2-weeks period preceding the current epoch. pub collected_astro: Uint128, + /// EMA of the collected ASTRO from the previous epoch + pub ema: Uint128, } #[cw_serde] @@ -109,7 +111,7 @@ pub enum QueryMsg { }, /// Returns paginated list of all pools that received votes at the current epoch #[returns(Vec<(String, VotedPoolInfo)>)] - VotedPoolsList { + VotedPools { limit: Option, start_after: Option, }, @@ -315,6 +317,8 @@ pub struct EmissionsState { pub xastro_rate: Decimal, /// Collected ASTRO from previous epoch. pub collected_astro: Uint128, + /// EMA of the collected ASTRO from the previous epoch + pub ema: Uint128, /// Amount of ASTRO to be emitted in the current epoch pub emissions_amount: Uint128, } diff --git a/schemas/astroport-emissions-controller/astroport-emissions-controller.json b/schemas/astroport-emissions-controller/astroport-emissions-controller.json index cd5b342e..ef8e6dfb 100644 --- a/schemas/astroport-emissions-controller/astroport-emissions-controller.json +++ b/schemas/astroport-emissions-controller/astroport-emissions-controller.json @@ -780,10 +780,10 @@ "description": "Returns paginated list of all pools that received votes at the current epoch", "type": "object", "required": [ - "voted_pools_list" + "voted_pools" ], "properties": { - "voted_pools_list": { + "voted_pools": { "type": "object", "properties": { "limit": { @@ -1392,7 +1392,7 @@ } } }, - "voted_pools_list": { + "voted_pools": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Array_of_Tuple_of_String_and_VotedPoolInfo", "type": "array", diff --git a/schemas/astroport-emissions-controller/raw/query.json b/schemas/astroport-emissions-controller/raw/query.json index be099de1..6b0489c2 100644 --- a/schemas/astroport-emissions-controller/raw/query.json +++ b/schemas/astroport-emissions-controller/raw/query.json @@ -105,10 +105,10 @@ "description": "Returns paginated list of all pools that received votes at the current epoch", "type": "object", "required": [ - "voted_pools_list" + "voted_pools" ], "properties": { - "voted_pools_list": { + "voted_pools": { "type": "object", "properties": { "limit": { diff --git a/schemas/astroport-emissions-controller/raw/response_to_voted_pools_list.json b/schemas/astroport-emissions-controller/raw/response_to_voted_pools.json similarity index 100% rename from schemas/astroport-emissions-controller/raw/response_to_voted_pools_list.json rename to schemas/astroport-emissions-controller/raw/response_to_voted_pools.json From 1253a9865cda47ef37c2cff7663a3452050774da Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Mon, 10 Jun 2024 18:20:19 +0400 Subject: [PATCH 16/32] update schemas --- assets/interchain-emissions-voting.png | Bin 67014 -> 0 bytes assets/interchain-governance-voting.png | Bin 64141 -> 0 bytes assets/interchain-ibc-channels.png | Bin 55006 -> 0 bytes assets/interchain-stake-astro.png | Bin 97310 -> 0 bytes assets/interchain-unlock.png | Bin 80521 -> 0 bytes assets/interchain-withdraw-funds.png | Bin 65482 -> 0 bytes .../astroport-emissions-controller.json | 27 ++++++++++++++++++ .../raw/instantiate.json | 9 ++++++ .../raw/response_to_simulate_tune.json | 9 ++++++ .../raw/response_to_tune_info.json | 9 ++++++ 10 files changed, 54 insertions(+) delete mode 100644 assets/interchain-emissions-voting.png delete mode 100644 assets/interchain-governance-voting.png delete mode 100644 assets/interchain-ibc-channels.png delete mode 100644 assets/interchain-stake-astro.png delete mode 100644 assets/interchain-unlock.png delete mode 100644 assets/interchain-withdraw-funds.png diff --git a/assets/interchain-emissions-voting.png b/assets/interchain-emissions-voting.png deleted file mode 100644 index 4bc20181fd61d6ce8c4f3cc2f4a060aac3d9922c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67014 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 nd!5kp@gJjYhyZ*`u5Y(T{z$3iO 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 diff --git a/assets/interchain-governance-voting.png b/assets/interchain-governance-voting.png deleted file mode 100644 index ba6b2bf91f16023701d4ad0e3ab423b20ae7dfe3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64141 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 YasNPm98;R}2k@tMU+Z3xvQ_Z^0Z!K&l>h($ diff --git a/assets/interchain-ibc-channels.png b/assets/interchain-ibc-channels.png deleted file mode 100644 index 92393773212f71cb9cf9f8ad68f8600892697685..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55006 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

fs(cR~Ai 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 ALjV8( diff --git a/assets/interchain-unlock.png b/assets/interchain-unlock.png deleted file mode 100644 index bea573b9addede7f2617eeedc6d4980ee59d207c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 80521 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 diff --git a/assets/interchain-withdraw-funds.png b/assets/interchain-withdraw-funds.png deleted file mode 100644 index b571b5c1ec4e3f2990ff5d24f86f379c7d55dfaf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65482 zcmeFZXH=6}7d8w?5hJ1?FrWe!M7mN%s(@4x=_T|kC4lsfNK+B%ARr*pdk?*dC=fz# zNvK0F0i+~AfP6RejEpnmzi+*3z3*D%56pr)=RRkjeRjF_wa@FPPvj`hF`OeHAfQl? zmr)}iARZ$iAVQxd1%7kTkI(`BBX*Wj&^QbH^Eqq&ihzKbKtblAhKJ$W1es5)X5F{% zteHgVL6hDanqFj=h#y^2<*YBZaCJr3727`_Egzk1HzdqDH{hKBJkco4d_5c|%KG>` z+d1Ofd@)nC2@##$A{)Ys-cAx}*yF|6kIs5QFA0dwGQarOj|7&;)pGIn(2J4;gv9^) z(L#Q)^n2*PzXgmWNlScoto+3{xl6zLCbRLZ!>vDAVvBI3rcMwM*&I`yw9 z{xNOci_FF_B1Wc1zZ&($i}2=O%pQ2OoLRCZoME|7=M z4FHB$wn&LIjXZtT2bB<(-IbB1%+&Kt!e0%h>a`=34)nEn7)4CVzkE%Ft2HVmlnlKn zdaRXv!>Z>)=ayIv;g8KXCNle~>~zs}bx0BWq1~TDL+)a4WWL6S$NIm2m+Q7o7R# zSC-G1A{~SldGtAz;)^bO--pGpR(rpyq<2YefyI~gq>4;UOfgP&#j|m7B@4w-wTE?L z14Ft}#c-L9=~Bg$dIANj)u|L}$oB`K?Mm^m=y{G?w{L6ZYvkWa5vi(YmVw1_$jhz@ zwS%GdG0q3XdbMgKqnHNIts2x-F*p6}$H{CWZs_=+AYwM%Pc@aJZd!M=M;ax3N3IIH ztpyF`8MJX-Iy!EM*!{-%E`mwoI*Sr*g1FEGb5xWkpWRKKQw62tFgD^hR1{`SDmBP5 z?Nu!+D_i&ML~7n)?-Cm|u(L+xx3?R1x&AmyEi^PWYcC*e*>DE=M+)1v3`Q4=J@u?+ zhCjE%%qIZ-d)h3_r44<1o?cLq(Q|1M^q|xd?$voE@v1n2(O!_R=XjNE_V$zEr^Th9 z)`M}Mz@X6JPO_usNKX)LXT3wi@7z}eEMNL;?F$W*`P$}blr>!K%PMeaI5OEfUx&gy zI|`j29-SIKnvv3f7We-Bok6RfC|&1;PXFV5A?>NWrDt@dS;ZnnJ0Gv#G~ZLn$Wr>| z7A;{UPA}+G$!S!pm+F0>N=~kj{WdHt;hIRbO>)>u+sr+BLDR~hkRZkC;*_~H#C^~0 z6w3MOnVSRo?E=L-+q9hVBo9EF5_k`-H#8h8`IYk&w57UBV4X`E^9s| zrM!~&(6mw%vw38l+_7;gFALD)!Nm;VV0E4GuR}qwCi6pi&pem$lhLHRD~iocLa^HH zfDc7F9^d1H%(1DB+{E5Ow!mA5@TKylwh^{=$WkM8nSNrJ<(q?hj(xZ)n*H zm7bKi^B~_SC07Y}v@CKDm8)fVY3`3ImDEmiK3**gB(#eki-{1=tQd?=c!nedWqf-Q z+jCHd<|_9>d+jexjAm+u+pIGdfdv!DV{$rqeV^7JjU>-Emo&{7;

!>+#uK1un}g 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 Date: Tue, 11 Jun 2024 12:57:13 +0400 Subject: [PATCH 17/32] refactor --- Cargo.lock | 41 ------------------- contracts/assembly/Cargo.toml | 1 - contracts/assembly/tests/common/helper.rs | 2 +- contracts/emissions_controller/Cargo.toml | 2 +- .../tests/common/helper.rs | 2 +- .../src/execute.rs | 1 + .../src/instantiate.rs | 2 +- .../tests/common/helper.rs | 2 +- contracts/voting_escrow/.cargo/config | 5 +-- contracts/voting_escrow/src/contract.rs | 10 ++--- contracts/voting_escrow/src/error.rs | 5 +-- contracts/voting_escrow/src/lib.rs | 3 +- contracts/voting_escrow/src/state.rs | 6 +-- contracts/voting_escrow/tests/helper.rs | 2 +- .../src/emissions_controller/consts.rs | 2 +- .../astroport-governance/src/voting_escrow.rs | 8 ++-- ...stroport-emissions-controller-outpost.json | 8 ++-- .../raw/instantiate.json | 8 ++-- .../astroport-emissions-controller.json | 8 ++-- .../raw/instantiate.json | 8 ++-- .../astroport-voting-escrow.json | 14 +++---- .../raw/instantiate.json | 8 ++-- .../astroport-voting-escrow/raw/query.json | 2 +- .../raw/response_to_lock_info.json | 4 +- 24 files changed, 52 insertions(+), 102 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fea7c638..dc333ac8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,7 +24,6 @@ name = "astro-assembly" version = "3.0.0" dependencies = [ "anyhow", - "astro-satellite", "astroport 4.0.3", "astroport-governance 4.0.0", "astroport-staking 2.1.0 (git+https://github.com/astroport-fi/hidden_astroport_core)", @@ -43,36 +42,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "astro-satellite" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e6e7c9f6e9ef66b5b0bb93a69530f012ddcf9c8a5a2d17ad598deae54024919" -dependencies = [ - "astro-satellite-package", - "astroport-ibc", - "cosmwasm-schema", - "cosmwasm-std", - "cosmwasm-storage", - "cw-storage-plus 0.15.1", - "cw-utils 0.15.1", - "cw2 0.15.1", - "ibc-controller-package", - "itertools 0.10.5", - "thiserror", -] - -[[package]] -name = "astro-satellite-package" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "893363819104a2a4685d99f19e35e5d81102fb782e622619ac643e54ff65d638" -dependencies = [ - "astroport-governance 1.2.0", - "cosmwasm-schema", - "cosmwasm-std", -] - [[package]] name = "astroport" version = "2.9.5" @@ -573,16 +542,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cosmwasm-storage" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66de2ab9db04757bcedef2b5984fbe536903ada4a8a9766717a4a71197ef34f6" -dependencies = [ - "cosmwasm-std", - "serde", -] - [[package]] name = "cpufeatures" version = "0.2.12" diff --git a/contracts/assembly/Cargo.toml b/contracts/assembly/Cargo.toml index 17c569b2..01a89679 100644 --- a/contracts/assembly/Cargo.toml +++ b/contracts/assembly/Cargo.toml @@ -25,7 +25,6 @@ cosmwasm-schema.workspace = true cw-utils.workspace = true astroport-governance = { path = "../../packages/astroport-governance", version = "4" } astroport.workspace = true -astro-satellite = { version = "1.1.0", features = ["library"] } ibc-controller-package = "1.0.0" [dev-dependencies] diff --git a/contracts/assembly/tests/common/helper.rs b/contracts/assembly/tests/common/helper.rs index 702feddd..dda8c4e7 100644 --- a/contracts/assembly/tests/common/helper.rs +++ b/contracts/assembly/tests/common/helper.rs @@ -229,7 +229,7 @@ impl Helper { project: None, description: None, marketing: Some(owner.to_string()), - logo: Some(Logo::Url("https://example.com".to_string())), + logo: Logo::Url("https://example.com".to_string()), }, }, &[], diff --git a/contracts/emissions_controller/Cargo.toml b/contracts/emissions_controller/Cargo.toml index 8708f948..dcfe0ec2 100644 --- a/contracts/emissions_controller/Cargo.toml +++ b/contracts/emissions_controller/Cargo.toml @@ -24,7 +24,7 @@ cosmwasm-schema.workspace = true thiserror.workspace = true itertools.workspace = true astroport-governance = { path = "../../packages/astroport-governance", version = "4" } -astroport = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "4" } +astroport.workspace = true neutron-sdk = "0.10.0" serde_json = "1" diff --git a/contracts/emissions_controller/tests/common/helper.rs b/contracts/emissions_controller/tests/common/helper.rs index f8c12dda..99227860 100644 --- a/contracts/emissions_controller/tests/common/helper.rs +++ b/contracts/emissions_controller/tests/common/helper.rs @@ -260,7 +260,7 @@ impl ControllerHelper { project: None, description: None, marketing: None, - logo: Some(Logo::Url("".to_string())), + logo: Logo::Url("".to_string()), }, xastro_denom: xastro_denom.clone(), factory: factory.to_string(), diff --git a/contracts/emissions_controller_outpost/src/execute.rs b/contracts/emissions_controller_outpost/src/execute.rs index e24db4c2..254cd65f 100644 --- a/contracts/emissions_controller_outpost/src/execute.rs +++ b/contracts/emissions_controller_outpost/src/execute.rs @@ -322,6 +322,7 @@ pub fn handle_update_user( } /// Only contract owner can call this function. +/// /// * voting_ibc_channel: new IBC channel to send votes to the Hub. /// The contract must be connected to this channel. /// * hub_emissions_controller: new address of the Hub Emissions Controller contract. diff --git a/contracts/emissions_controller_outpost/src/instantiate.rs b/contracts/emissions_controller_outpost/src/instantiate.rs index 3e282a80..985601f6 100644 --- a/contracts/emissions_controller_outpost/src/instantiate.rs +++ b/contracts/emissions_controller_outpost/src/instantiate.rs @@ -42,7 +42,7 @@ pub fn instantiate( vxastro: Addr::unchecked(""), astro_denom: msg.astro_denom, incentives_addr: query_incentives_addr(deps.querier, &factory)?, - factory: deps.api.addr_validate(&msg.factory)?, + factory, // Contract owner is responsible for setting a channel via UpdateConfig voting_ibc_channel: "".to_string(), hub_emissions_controller: msg.hub_emissions_controller, diff --git a/contracts/emissions_controller_outpost/tests/common/helper.rs b/contracts/emissions_controller_outpost/tests/common/helper.rs index 4924b489..3c1d440c 100644 --- a/contracts/emissions_controller_outpost/tests/common/helper.rs +++ b/contracts/emissions_controller_outpost/tests/common/helper.rs @@ -164,7 +164,7 @@ impl ControllerHelper { project: None, description: None, marketing: None, - logo: Some(Logo::Url("".to_string())), + logo: Logo::Url("".to_string()), }, xastro_denom: xastro_denom.to_string(), factory: factory.to_string(), diff --git a/contracts/voting_escrow/.cargo/config b/contracts/voting_escrow/.cargo/config index 8d4bc738..4b4e1369 100644 --- a/contracts/voting_escrow/.cargo/config +++ b/contracts/voting_escrow/.cargo/config @@ -1,6 +1,5 @@ [alias] wasm = "build --release --target wasm32-unknown-unknown" wasm-debug = "build --target wasm32-unknown-unknown" -unit-test = "test --lib" -integration-test = "test --test integration" -schema = "run --example schema" +integration-test = "test --test voting_escrow_integration" +schema = "run --example voting_escrow_schema" diff --git a/contracts/voting_escrow/src/contract.rs b/contracts/voting_escrow/src/contract.rs index 0bfbbd4d..d47eddf6 100644 --- a/contracts/voting_escrow/src/contract.rs +++ b/contracts/voting_escrow/src/contract.rs @@ -20,9 +20,9 @@ use crate::error::ContractError; use crate::state::{get_total_vp, Lock, CONFIG}; /// Contract name that is used for migration. -const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); /// Contract version that is used for migration. -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); /// Creates a new contract with the specified parameters in [`InstantiateMsg`]. #[cfg_attr(not(feature = "library"), entry_point)] @@ -37,14 +37,14 @@ pub fn instantiate( validate_native_denom(&msg.deposit_denom)?; let config = Config { - deposit_denom: msg.deposit_denom.clone(), + deposit_denom: msg.deposit_denom, emissions_controller: deps.api.addr_validate(&msg.emissions_controller)?, }; CONFIG.save(deps.storage, &config)?; let logo = match &msg.marketing.logo { - Some(Logo::Url(url)) => { - LOGO.save(deps.storage, &msg.marketing.logo.clone().unwrap())?; + Logo::Url(url) => { + LOGO.save(deps.storage, &msg.marketing.logo)?; Some(LogoInfo::Url(url.clone())) } _ => { diff --git a/contracts/voting_escrow/src/error.rs b/contracts/voting_escrow/src/error.rs index be0bd905..96b25a70 100644 --- a/contracts/voting_escrow/src/error.rs +++ b/contracts/voting_escrow/src/error.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{OverflowError, StdError}; +use cosmwasm_std::StdError; use cw20_base::ContractError as CW20Error; use cw_utils::PaymentError; use thiserror::Error; @@ -12,9 +12,6 @@ pub enum ContractError { #[error("{0}")] PaymentError(#[from] PaymentError), - #[error("{0}")] - OverflowError(#[from] OverflowError), - #[error("{0}")] Cw20Base(#[from] CW20Error), diff --git a/contracts/voting_escrow/src/lib.rs b/contracts/voting_escrow/src/lib.rs index 4fd6c769..3d3e89c8 100644 --- a/contracts/voting_escrow/src/lib.rs +++ b/contracts/voting_escrow/src/lib.rs @@ -1,4 +1,3 @@ pub mod contract; -pub mod state; - pub mod error; +pub mod state; diff --git a/contracts/voting_escrow/src/state.rs b/contracts/voting_escrow/src/state.rs index 8aebb113..f3357519 100644 --- a/contracts/voting_escrow/src/state.rs +++ b/contracts/voting_escrow/src/state.rs @@ -1,4 +1,3 @@ -use astroport::common::OwnershipProposal; use cosmwasm_schema::cw_serde; use cosmwasm_std::{ensure, Addr, StdResult, Storage, Uint128}; use cw_storage_plus::{Item, SnapshotItem, SnapshotMap, Strategy}; @@ -18,7 +17,7 @@ fn default_addr() -> Addr { #[cw_serde] pub struct Lock { - /// The total amount of xASTRO tokens that were deposited in the vxASTRO position + /// The total number of xASTRO tokens that were deposited in the vxASTRO position pub amount: Uint128, /// Unlocking status. None for positions in Locked state pub unlock_status: Option, @@ -198,6 +197,3 @@ pub const TOTAL_POWER: SnapshotItem = SnapshotItem::new( "total_power__changelog", Strategy::EveryBlock, ); - -/// Contains a proposal to change contract ownership -pub const OWNERSHIP_PROPOSAL: Item = Item::new("ownership_proposal"); diff --git a/contracts/voting_escrow/tests/helper.rs b/contracts/voting_escrow/tests/helper.rs index 4dd3fbac..73257b21 100644 --- a/contracts/voting_escrow/tests/helper.rs +++ b/contracts/voting_escrow/tests/helper.rs @@ -79,7 +79,7 @@ impl EscrowHelper { project: None, description: None, marketing: Some(owner.to_string()), - logo: Some(Logo::Url("https://example.com".to_string())), + logo: Logo::Url("https://example.com".to_string()), }, }, &[], diff --git a/packages/astroport-governance/src/emissions_controller/consts.rs b/packages/astroport-governance/src/emissions_controller/consts.rs index 1ec8113d..8c145599 100644 --- a/packages/astroport-governance/src/emissions_controller/consts.rs +++ b/packages/astroport-governance/src/emissions_controller/consts.rs @@ -2,7 +2,7 @@ use std::ops::RangeInclusive; use cosmwasm_std::IbcOrder; -/// vxASTRO voting epoch starts at Mon May 20 00:00:00 UTC 2024 +/// vxASTRO voting epoch starts on Mon May 20 00:00:00 UTC 2024 pub const EPOCHS_START: u64 = 1716163200; pub const DAY: u64 = 86400; /// vxASTRO voting epoch lasts 14 days diff --git a/packages/astroport-governance/src/voting_escrow.rs b/packages/astroport-governance/src/voting_escrow.rs index ab5eba9a..3c70ee70 100644 --- a/packages/astroport-governance/src/voting_escrow.rs +++ b/packages/astroport-governance/src/voting_escrow.rs @@ -12,7 +12,7 @@ pub struct UpdateMarketingInfo { /// Token marketing information pub marketing: Option, /// Token logo - pub logo: Option, + pub logo: Logo, } /// This structure stores general parameters for the vxASTRO contract. @@ -82,7 +82,7 @@ pub enum QueryMsg { /// Fetch a user's lock information #[returns(LockInfoResponse)] LockInfo { user: String }, - /// Return the vxASTRO contract configuration + /// Return the vxASTRO contract configuration #[returns(Config)] Config {}, } @@ -99,7 +99,7 @@ pub struct Config { #[derive(Copy)] #[cw_serde] pub struct UnlockStatus { - /// The timestamp when a lock will be unlocked. + /// The timestamp when position will be unlocked. pub end: u64, /// Whether The Hub confirmed unlocking pub hub_confirmed: bool, @@ -109,6 +109,6 @@ pub struct UnlockStatus { pub struct LockInfoResponse { /// The total number of xASTRO tokens that were deposited in the vxASTRO position pub amount: Uint128, - /// Unlocking status. None for positions in Locked state + /// Unlocking status. None for positions in locked state pub unlock_status: Option, } diff --git a/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json b/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json index 32d2007d..0afb1ddc 100644 --- a/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json +++ b/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json @@ -128,6 +128,9 @@ "UpdateMarketingInfo": { "description": "This structure stores marketing information for vxASTRO.", "type": "object", + "required": [ + "logo" + ], "properties": { "description": { "description": "Token description", @@ -138,12 +141,9 @@ }, "logo": { "description": "Token logo", - "anyOf": [ + "allOf": [ { "$ref": "#/definitions/Logo" - }, - { - "type": "null" } ] }, diff --git a/schemas/astroport-emissions-controller-outpost/raw/instantiate.json b/schemas/astroport-emissions-controller-outpost/raw/instantiate.json index 6c9abebc..bc724286 100644 --- a/schemas/astroport-emissions-controller-outpost/raw/instantiate.json +++ b/schemas/astroport-emissions-controller-outpost/raw/instantiate.json @@ -124,6 +124,9 @@ "UpdateMarketingInfo": { "description": "This structure stores marketing information for vxASTRO.", "type": "object", + "required": [ + "logo" + ], "properties": { "description": { "description": "Token description", @@ -134,12 +137,9 @@ }, "logo": { "description": "Token logo", - "anyOf": [ + "allOf": [ { "$ref": "#/definitions/Logo" - }, - { - "type": "null" } ] }, diff --git a/schemas/astroport-emissions-controller/astroport-emissions-controller.json b/schemas/astroport-emissions-controller/astroport-emissions-controller.json index 2c8227fa..a864cbfc 100644 --- a/schemas/astroport-emissions-controller/astroport-emissions-controller.json +++ b/schemas/astroport-emissions-controller/astroport-emissions-controller.json @@ -212,6 +212,9 @@ "UpdateMarketingInfo": { "description": "This structure stores marketing information for vxASTRO.", "type": "object", + "required": [ + "logo" + ], "properties": { "description": { "description": "Token description", @@ -222,12 +225,9 @@ }, "logo": { "description": "Token logo", - "anyOf": [ + "allOf": [ { "$ref": "#/definitions/Logo" - }, - { - "type": "null" } ] }, diff --git a/schemas/astroport-emissions-controller/raw/instantiate.json b/schemas/astroport-emissions-controller/raw/instantiate.json index 788e8d26..87b3a695 100644 --- a/schemas/astroport-emissions-controller/raw/instantiate.json +++ b/schemas/astroport-emissions-controller/raw/instantiate.json @@ -208,6 +208,9 @@ "UpdateMarketingInfo": { "description": "This structure stores marketing information for vxASTRO.", "type": "object", + "required": [ + "logo" + ], "properties": { "description": { "description": "Token description", @@ -218,12 +221,9 @@ }, "logo": { "description": "Token logo", - "anyOf": [ + "allOf": [ { "$ref": "#/definitions/Logo" - }, - { - "type": "null" } ] }, diff --git a/schemas/astroport-voting-escrow/astroport-voting-escrow.json b/schemas/astroport-voting-escrow/astroport-voting-escrow.json index 337deaa4..9fa2b902 100644 --- a/schemas/astroport-voting-escrow/astroport-voting-escrow.json +++ b/schemas/astroport-voting-escrow/astroport-voting-escrow.json @@ -101,6 +101,9 @@ "UpdateMarketingInfo": { "description": "This structure stores marketing information for vxASTRO.", "type": "object", + "required": [ + "logo" + ], "properties": { "description": { "description": "Token description", @@ -111,12 +114,9 @@ }, "logo": { "description": "Token logo", - "anyOf": [ + "allOf": [ { "$ref": "#/definitions/Logo" - }, - { - "type": "null" } ] }, @@ -423,7 +423,7 @@ "additionalProperties": false }, { - "description": "Return the vxASTRO contract configuration", + "description": "Return the vxASTRO contract configuration", "type": "object", "required": [ "config" @@ -509,7 +509,7 @@ ] }, "unlock_status": { - "description": "Unlocking status. None for positions in Locked state", + "description": "Unlocking status. None for positions in locked state", "anyOf": [ { "$ref": "#/definitions/UnlockStatus" @@ -534,7 +534,7 @@ ], "properties": { "end": { - "description": "The timestamp when a lock will be unlocked.", + "description": "The timestamp when position will be unlocked.", "type": "integer", "format": "uint64", "minimum": 0.0 diff --git a/schemas/astroport-voting-escrow/raw/instantiate.json b/schemas/astroport-voting-escrow/raw/instantiate.json index 562470df..e4301531 100644 --- a/schemas/astroport-voting-escrow/raw/instantiate.json +++ b/schemas/astroport-voting-escrow/raw/instantiate.json @@ -97,6 +97,9 @@ "UpdateMarketingInfo": { "description": "This structure stores marketing information for vxASTRO.", "type": "object", + "required": [ + "logo" + ], "properties": { "description": { "description": "Token description", @@ -107,12 +110,9 @@ }, "logo": { "description": "Token logo", - "anyOf": [ + "allOf": [ { "$ref": "#/definitions/Logo" - }, - { - "type": "null" } ] }, diff --git a/schemas/astroport-voting-escrow/raw/query.json b/schemas/astroport-voting-escrow/raw/query.json index 3b034b86..81a71a71 100644 --- a/schemas/astroport-voting-escrow/raw/query.json +++ b/schemas/astroport-voting-escrow/raw/query.json @@ -130,7 +130,7 @@ "additionalProperties": false }, { - "description": "Return the vxASTRO contract configuration", + "description": "Return the vxASTRO contract configuration", "type": "object", "required": [ "config" diff --git a/schemas/astroport-voting-escrow/raw/response_to_lock_info.json b/schemas/astroport-voting-escrow/raw/response_to_lock_info.json index 337d8d0b..cba607a1 100644 --- a/schemas/astroport-voting-escrow/raw/response_to_lock_info.json +++ b/schemas/astroport-voting-escrow/raw/response_to_lock_info.json @@ -15,7 +15,7 @@ ] }, "unlock_status": { - "description": "Unlocking status. None for positions in Locked state", + "description": "Unlocking status. None for positions in locked state", "anyOf": [ { "$ref": "#/definitions/UnlockStatus" @@ -40,7 +40,7 @@ ], "properties": { "end": { - "description": "The timestamp when a lock will be unlocked.", + "description": "The timestamp when position will be unlocked.", "type": "integer", "format": "uint64", "minimum": 0.0 From be71fc38d74478311cfa7db94c2f97ba95544baf Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Wed, 19 Jun 2024 11:23:39 +0300 Subject: [PATCH 18/32] refine comments; remove unused errors --- README.md | 34 ++++++------------ assets/sc_diagram.png | Bin 227058 -> 94524 bytes contracts/emissions_controller/src/error.rs | 5 +-- contracts/emissions_controller/src/execute.rs | 12 ++++--- .../emissions_controller/src/instantiate.rs | 2 +- contracts/emissions_controller/src/utils.rs | 6 +++- .../src/execute.rs | 4 +-- ...missions_controller_outpost_integration.rs | 2 +- .../src/emissions_controller/hub.rs | 8 +++-- .../src/emissions_controller/msg.rs | 2 +- .../astroport-governance/src/voting_escrow.rs | 4 +-- ...stroport-emissions-controller-outpost.json | 2 +- .../raw/execute.json | 2 +- .../astroport-emissions-controller.json | 14 ++++---- .../raw/execute.json | 9 ++--- .../raw/response_to_list_outposts.json | 5 +-- .../astroport-voting-escrow.json | 4 +-- .../astroport-voting-escrow/raw/execute.json | 2 +- .../raw/instantiate.json | 2 +- 19 files changed, 58 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index 03d0c7fe..cc75d271 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![codecov](https://codecov.io/gh/astroport-fi/hidden_astroport_governance/branch/main/graph/badge.svg?token=Z2SWBLUJTV)](https://codecov.io/gh/astroport-fi/hidden_astroport_governance) -This repo contains Astroport Governance related contracts. +This repo contains Astroport Governance contracts. ## Contracts diagram @@ -10,13 +10,13 @@ This repo contains Astroport Governance related contracts. ## Contracts -| Name | Description | -|--------------------------------------------------------------|----------------------------------------------------------------| -| [`assembly`](contracts/assembly) | The Astral Assembly governance contract | -| [`builder_unlock`](contracts/builder_unlock) | ASTRO unlock/vesting contract for Initial Builders | -| [`escrow_fee_distributor`](contracts/escrow_fee_distributor) | vxASTRO fee distributor | -| [`generator_controller`](contracts/emissions_controller) | Generator Controller used to vote on directing ASTRO emissions | -| [`voting_escrow`](contracts/voting_escrow) | vxASTRO contract | +| Name | Description | +|--------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------| +| [`assembly`](contracts/assembly) | The Astral Assembly governance contract | +| [`builder_unlock`](contracts/builder_unlock) | ASTRO unlock/vesting contract for Initial Builders | +| [`emissions_controller`](contracts/emissions_controller) | Emissions Controller (Hub) is responsible for receiving vxASTRO votes and managing ASTRO emissions | +| [`emissions_controller_outpost`](contracts/emissions_controller_outpost) | Emissions Controller (Outpost) is a lightweight satellite for Hub's counterpart which lives on outposts | +| [`voting_escrow`](contracts/voting_escrow) | Vote escrowed xASTRO with 14 days lockup | ## Building Contracts @@ -24,13 +24,10 @@ You will need Rust 1.64.0+ with wasm32-unknown-unknown target installed. ### You can compile each contract: -Go to contract directory and run +Go to the repository root and run ``` -cargo wasm -cp ../../target/wasm32-unknown-unknown/release/astroport_token.wasm . -ls -l astroport_token.wasm -sha256sum astroport_token.wasm +./scripts/build_release.sh ``` ### You can run tests for all contracts @@ -41,20 +38,11 @@ Run the following from the repository root cargo test ``` -### For a production-ready (compressed) build: - -Run the following from the repository root - -``` -./scripts/build_release.sh -``` - The optimized contracts are generated in the artifacts/ directory. ## Deployment -You can find versions and commits for actually deployed -contracts [here](https://github.com/astroport-fi/astroport-changelog). +Actual deployed contracts and with respective commits [here](https://github.com/astroport-fi/astroport-changelog). ## Docs diff --git a/assets/sc_diagram.png b/assets/sc_diagram.png index 2276fb9de895961dc17d69fd4e86dde46703220c..d62e390c4d7a681cf56ad2eabb74d82766265f74 100644 GIT binary patch literal 94524 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%0A#7AR7X`~#% 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 !|^wKw#w`lYf;oa23Jf>c) zCNQ{#MO%4i3l7^nqEzc=}G`Xn>sS+29@@c_5)lI|JHsvpw~o;y?u-Y%zm znSZ=0K8@Hdo;OSSidS|q(j}G@c~v~lOHn41>NwVx&anD{-PPCe>pM_^<>v4Nu%aS1 zrGqKNKg^WZKWz}pn4WrGHC|6KMm10MTI=m`mbVc&JTc*YXQgdCK~x{Ov?2*Xe#Fe* zrqP}e2EB3HDAR~{8q5_{DmifFx*rFv&Qd=cPnMswYcvX1Dk)&pmv8#tVIY?2;t|XsRfvdg~DAk zcJ;YQ>#CHtd?88*{-IW~y}MxZ85d>7#?LmsG2R5LC)>VzHtAdaDq9A-@=*2%rn~-q z%j;G1w|n3~nw6q%AB1v5xApY;0uCaaUed2HB#xEplD9h&M!Q}Yl{!>O6J1|EKb)Ir zSL9W8G;O9*S=ONzjD##NNLiA$m_4%LnHC_+8NV<&l-0SfLLWkcaI`pGb|UTY1xj{R zG&zFYD?d3Mw8indJvUc;``GMcK{ZzrhuC0$s!?H@GMcV;{6){72BdL6!j2%wHRa{9 zT0{+0^#&21&nIc;hPFWNpW-#?IAypTEm>6-7yK4ksmJ1{=bMBeP`C3Qa}cI{-KmNe zVsFUw{2aY*eHZ5u5S+P^a1#(GbNQG}z+U+Bt-;GMaHXTb@FPZzU?R^Ev<30xZl;Yl-ZiL4iw#?-bun^3yHF1yjw zqL8ob$eFoedMF7N_>q#2y*WxCIF8o9*=_rXfYk_-g4%(wZu+nwbPIM_Fh(xEdxcO0mEFEwaE4J$N4KXB$5w3Af~44 zrsBx8b&y03O7ClFX8##(yV*qhSvT7)L%%mA+?&pGk1)HT<;a$`Z&d9>*F#pp8Pyj9) zs_CJ1F=4ggbnKfcmxo@6ZM@?JLAa~n2L{=@a&U(3%}+LgJS47*nae&%Z6Rk2ByZ7X z*CGO5W_z7qU%n$AFHOWn@fcPcDI}TfkB>jamQnjy68$xPJ=Q@(6$(6LM%R?WzyDp!omy{FW$Yi#~9*buN%0|#oSs75E8JeiMkul#}1Cr{wN-Yc0 zoE?}$MLo7C(=fJZe1^L?((c~T7?t&!1%0Aa$%r~_=S@-Op*9+B|0W0UEF9pPHaU`L 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 O-I7(2$+>aI=l=l;w9-!i diff --git a/contracts/emissions_controller/src/error.rs b/contracts/emissions_controller/src/error.rs index c3216200..352a7022 100644 --- a/contracts/emissions_controller/src/error.rs +++ b/contracts/emissions_controller/src/error.rs @@ -44,7 +44,7 @@ pub enum ContractError { #[error("Pool {0} is already whitelisted")] PoolAlreadyWhitelisted(String), - #[error("Invalid total votes weight. Must be 1.")] + #[error("Invalid total votes weight. Must be 1")] InvalidTotalWeight {}, #[error("Failed to parse reply")] @@ -65,9 +65,6 @@ pub enum ContractError { #[error("Failed to determine outpost for pool {0}")] NoOutpostForPool(String), - #[error("Unknown IBC packet data")] - UnknownIbcPacketData {}, - #[error("You can vote maximum for {MAX_POOLS_TO_VOTE} pools")] ExceededMaxPoolsToVote {}, diff --git a/contracts/emissions_controller/src/execute.rs b/contracts/emissions_controller/src/execute.rs index b979b313..5bc7045a 100644 --- a/contracts/emissions_controller/src/execute.rs +++ b/contracts/emissions_controller/src/execute.rs @@ -158,7 +158,7 @@ pub fn execute( HubMsg::TunePools {} => tune_pools(deps, env), HubMsg::RetryFailedOutposts {} => retry_failed_outposts(deps, info, env), HubMsg::UpdateConfig { - pools_per_outpost: pools_limit, + pools_per_outpost, whitelisting_fee, fee_receiver, emissions_multiple, @@ -166,7 +166,7 @@ pub fn execute( } => update_config( deps, info, - pools_limit, + pools_per_outpost, whitelisting_fee, fee_receiver, emissions_multiple, @@ -261,6 +261,7 @@ pub fn update_outpost( astro_pool_config: Option, ) -> Result, ContractError> { nonpayable(&info)?; + let deps = deps.into_empty(); let config = CONFIG.load(deps.storage)?; ensure!(info.sender == config.owner, ContractError::Unauthorized {}); @@ -291,7 +292,8 @@ pub fn update_outpost( ); } else { if let Some(conf) = &astro_pool_config { - deps.api.addr_validate(&conf.astro_pool)?; + let maybe_lp_token = determine_asset_info(&conf.astro_pool, deps.api)?; + check_lp_token(deps.querier, &config.factory, &maybe_lp_token)?; } ensure!( astro_denom == config.astro_denom, @@ -345,7 +347,7 @@ pub fn remove_outpost( Ok(Response::default().add_attributes([("action", "remove_outpost"), ("prefix", &prefix)])) } -/// This permissionless function retries failed emission IBC messages. +/// This permissionless endpoint retries failed emission IBC messages. pub fn retry_failed_outposts( deps: DepsMut, info: MessageInfo, @@ -767,8 +769,8 @@ pub fn register_proposal( let ibc_messages: Vec> = outposts .iter() .filter_map(|(outpost, outpost_info)| { - attrs.push(("outpost", outpost)); outpost_info.params.as_ref().map(|params| { + attrs.push(("outpost", outpost)); IbcMsg::SendPacket { channel_id: params.voting_channel.clone(), data: data.clone(), diff --git a/contracts/emissions_controller/src/instantiate.rs b/contracts/emissions_controller/src/instantiate.rs index c0a4a2cb..5631940b 100644 --- a/contracts/emissions_controller/src/instantiate.rs +++ b/contracts/emissions_controller/src/instantiate.rs @@ -76,7 +76,7 @@ pub fn instantiate( // Query dynamic emissions curve state let (xastro_rate, _) = get_xastro_rate_and_share(deps.querier, &config)?; - // Set tune_ts just for safety so the first tuning could happen in 2 weeks + // Set tune_ts so the first tuning happens in 2 weeks TUNE_INFO.save( deps.storage, &TuneInfo { diff --git a/contracts/emissions_controller/src/utils.rs b/contracts/emissions_controller/src/utils.rs index 456d964a..a7db9b86 100644 --- a/contracts/emissions_controller/src/utils.rs +++ b/contracts/emissions_controller/src/utils.rs @@ -26,7 +26,7 @@ use astroport_governance::emissions_controller::utils::check_lp_token; use crate::error::ContractError; use crate::state::{OUTPOSTS, TUNE_INFO, VOTED_POOLS}; -/// Determine outpost prefix from address or denom. +/// Determine outpost prefix from address or tokenfactory denom. pub fn determine_outpost_prefix(value: &str) -> Option { let mut maybe_addr = Some(value); @@ -255,9 +255,13 @@ pub fn astro_emissions_curve( }) } +/// Internal structure to pass the tune simulation result. pub struct TuneResult { + /// All candidates with their voting power and outpost prefix. pub candidates: Vec<(String, (String, Uint128))>, + /// Dynammic emissions curve state pub new_emissions_state: EmissionsState, + /// Next pools grouped by outpost prefix. pub next_pools_grouped: HashMap>, } diff --git a/contracts/emissions_controller_outpost/src/execute.rs b/contracts/emissions_controller_outpost/src/execute.rs index 254cd65f..ba800b44 100644 --- a/contracts/emissions_controller_outpost/src/execute.rs +++ b/contracts/emissions_controller_outpost/src/execute.rs @@ -10,7 +10,7 @@ use cosmwasm_std::{ attr, coin, coins, ensure, wasm_execute, Addr, Coin, Decimal, DepsMut, Env, IbcMsg, MessageInfo, Response, StdError, Uint128, }; -use cw_utils::{may_pay, nonpayable}; +use cw_utils::{must_pay, nonpayable}; use itertools::Itertools; use astroport_governance::assembly::ProposalVoteOption; @@ -131,7 +131,7 @@ pub fn set_emissions( schedules: Vec<(String, InputSchedule)>, ) -> Result { let config = CONFIG.load(deps.storage)?; - let amount = may_pay(&info, &config.astro_denom)?; + let amount = must_pay(&info, &config.astro_denom)?; // Ensure we received exact amount of ASTRO let schedules_total: Uint128 = schedules diff --git a/contracts/emissions_controller_outpost/tests/emissions_controller_outpost_integration.rs b/contracts/emissions_controller_outpost/tests/emissions_controller_outpost_integration.rs index af71a7da..8131ca89 100644 --- a/contracts/emissions_controller_outpost/tests/emissions_controller_outpost_integration.rs +++ b/contracts/emissions_controller_outpost/tests/emissions_controller_outpost_integration.rs @@ -37,7 +37,7 @@ fn set_emissions_test() { let err = helper.set_emissions(&user, &schedules, &funds).unwrap_err(); assert_eq!( err.downcast::().unwrap(), - ContractError::PaymentError(PaymentError::ExtraDenom("token1".to_string())) + ContractError::PaymentError(PaymentError::MissingDenom("astro".to_string())) ); // Trying to bypass payment error by sending astro. diff --git a/packages/astroport-governance/src/emissions_controller/hub.rs b/packages/astroport-governance/src/emissions_controller/hub.rs index b8696b36..5487c160 100644 --- a/packages/astroport-governance/src/emissions_controller/hub.rs +++ b/packages/astroport-governance/src/emissions_controller/hub.rs @@ -52,7 +52,7 @@ pub struct HubInstantiateMsg { #[cw_serde] pub enum HubMsg { - /// TunePools transforms the latest vote distribution into alloc_points which turn into ASTRO emissions + /// TunePools transforms the latest vote distribution into ASTRO emissions TunePools {}, /// Repeats IBC transfer messages with IBC hook for all outposts in Failed state. RetryFailedOutposts {}, @@ -215,11 +215,13 @@ pub struct OutpostParams { pub ics20_channel: String, } +/// Each outpost may have one pool that receives flat ASTRO emissions. +/// This pools doesn't participate in the voting process. #[cw_serde] pub struct AstroPoolConfig { - /// The most liquid ASTRO pool on this outpost + /// Pool with ASTRO which needs to receive flat emissions pub astro_pool: String, - /// The constant ASTRO pool emissions. Can be set to 0 if emissions are not needed. + /// Amount of ASTRO per epoch pub constant_emissions: Uint128, } diff --git a/packages/astroport-governance/src/emissions_controller/msg.rs b/packages/astroport-governance/src/emissions_controller/msg.rs index 4a223740..c8bd0f79 100644 --- a/packages/astroport-governance/src/emissions_controller/msg.rs +++ b/packages/astroport-governance/src/emissions_controller/msg.rs @@ -15,7 +15,7 @@ pub enum ExecuteMsg { /// Updates user votes according to the current voting power. UpdateUserVotes { user: String, is_unlock: bool }, /// Permissionless endpoint which allows user to update their - /// voting power contribution in case of IBC failures or pool has been re-added to whitelist. + /// voting power contribution in case of IBC failures or if pool has been re-added to whitelist. RefreshUserVotes {}, /// ProposeNewOwner proposes a new owner for the contract ProposeNewOwner { diff --git a/packages/astroport-governance/src/voting_escrow.rs b/packages/astroport-governance/src/voting_escrow.rs index 3c70ee70..8cea6635 100644 --- a/packages/astroport-governance/src/voting_escrow.rs +++ b/packages/astroport-governance/src/voting_escrow.rs @@ -15,7 +15,7 @@ pub struct UpdateMarketingInfo { pub logo: Logo, } -/// This structure stores general parameters for the vxASTRO contract. +/// vxASTRO contract instantiation message #[cw_serde] pub struct InstantiateMsg { /// xASTRO denom @@ -26,7 +26,7 @@ pub struct InstantiateMsg { pub marketing: UpdateMarketingInfo, } -/// This structure describes the execute functions in the contract. +/// This structure describes the execute endpoints in the contract. #[cw_serde] pub enum ExecuteMsg { /// Create a vxASTRO position and lock xASTRO diff --git a/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json b/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json index 0afb1ddc..6d0423f0 100644 --- a/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json +++ b/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json @@ -232,7 +232,7 @@ "additionalProperties": false }, { - "description": "Permissionless endpoint which allows user to update their voting power contribution in case of IBC failures or pool has been re-added to whitelist.", + "description": "Permissionless endpoint which allows user to update their voting power contribution in case of IBC failures or if pool has been re-added to whitelist.", "type": "object", "required": [ "refresh_user_votes" diff --git a/schemas/astroport-emissions-controller-outpost/raw/execute.json b/schemas/astroport-emissions-controller-outpost/raw/execute.json index 5bcf1b5b..49767617 100644 --- a/schemas/astroport-emissions-controller-outpost/raw/execute.json +++ b/schemas/astroport-emissions-controller-outpost/raw/execute.json @@ -64,7 +64,7 @@ "additionalProperties": false }, { - "description": "Permissionless endpoint which allows user to update their voting power contribution in case of IBC failures or pool has been re-added to whitelist.", + "description": "Permissionless endpoint which allows user to update their voting power contribution in case of IBC failures or if pool has been re-added to whitelist.", "type": "object", "required": [ "refresh_user_votes" diff --git a/schemas/astroport-emissions-controller/astroport-emissions-controller.json b/schemas/astroport-emissions-controller/astroport-emissions-controller.json index a864cbfc..bf1503c7 100644 --- a/schemas/astroport-emissions-controller/astroport-emissions-controller.json +++ b/schemas/astroport-emissions-controller/astroport-emissions-controller.json @@ -316,7 +316,7 @@ "additionalProperties": false }, { - "description": "Permissionless endpoint which allows user to update their voting power contribution in case of IBC failures or pool has been re-added to whitelist.", + "description": "Permissionless endpoint which allows user to update their voting power contribution in case of IBC failures or if pool has been re-added to whitelist.", "type": "object", "required": [ "refresh_user_votes" @@ -403,6 +403,7 @@ ], "definitions": { "AstroPoolConfig": { + "description": "Each outpost may have one pool that receives flat ASTRO emissions. This pools doesn't participate in the voting process.", "type": "object", "required": [ "astro_pool", @@ -410,11 +411,11 @@ ], "properties": { "astro_pool": { - "description": "The most liquid ASTRO pool on this outpost", + "description": "Pool with ASTRO which needs to receive flat emissions", "type": "string" }, "constant_emissions": { - "description": "The constant ASTRO pool emissions. Can be set to 0 if emissions are not needed.", + "description": "Amount of ASTRO per epoch", "allOf": [ { "$ref": "#/definitions/Uint128" @@ -446,7 +447,7 @@ "HubMsg": { "oneOf": [ { - "description": "TunePools transforms the latest vote distribution into alloc_points which turn into ASTRO emissions", + "description": "TunePools transforms the latest vote distribution into ASTRO emissions", "type": "object", "required": [ "tune_pools" @@ -1037,6 +1038,7 @@ }, "definitions": { "AstroPoolConfig": { + "description": "Each outpost may have one pool that receives flat ASTRO emissions. This pools doesn't participate in the voting process.", "type": "object", "required": [ "astro_pool", @@ -1044,11 +1046,11 @@ ], "properties": { "astro_pool": { - "description": "The most liquid ASTRO pool on this outpost", + "description": "Pool with ASTRO which needs to receive flat emissions", "type": "string" }, "constant_emissions": { - "description": "The constant ASTRO pool emissions. Can be set to 0 if emissions are not needed.", + "description": "Amount of ASTRO per epoch", "allOf": [ { "$ref": "#/definitions/Uint128" diff --git a/schemas/astroport-emissions-controller/raw/execute.json b/schemas/astroport-emissions-controller/raw/execute.json index 461a9e4c..da973ce8 100644 --- a/schemas/astroport-emissions-controller/raw/execute.json +++ b/schemas/astroport-emissions-controller/raw/execute.json @@ -64,7 +64,7 @@ "additionalProperties": false }, { - "description": "Permissionless endpoint which allows user to update their voting power contribution in case of IBC failures or pool has been re-added to whitelist.", + "description": "Permissionless endpoint which allows user to update their voting power contribution in case of IBC failures or if pool has been re-added to whitelist.", "type": "object", "required": [ "refresh_user_votes" @@ -151,6 +151,7 @@ ], "definitions": { "AstroPoolConfig": { + "description": "Each outpost may have one pool that receives flat ASTRO emissions. This pools doesn't participate in the voting process.", "type": "object", "required": [ "astro_pool", @@ -158,11 +159,11 @@ ], "properties": { "astro_pool": { - "description": "The most liquid ASTRO pool on this outpost", + "description": "Pool with ASTRO which needs to receive flat emissions", "type": "string" }, "constant_emissions": { - "description": "The constant ASTRO pool emissions. Can be set to 0 if emissions are not needed.", + "description": "Amount of ASTRO per epoch", "allOf": [ { "$ref": "#/definitions/Uint128" @@ -194,7 +195,7 @@ "HubMsg": { "oneOf": [ { - "description": "TunePools transforms the latest vote distribution into alloc_points which turn into ASTRO emissions", + "description": "TunePools transforms the latest vote distribution into ASTRO emissions", "type": "object", "required": [ "tune_pools" diff --git a/schemas/astroport-emissions-controller/raw/response_to_list_outposts.json b/schemas/astroport-emissions-controller/raw/response_to_list_outposts.json index 7dba5ebf..6228d8b2 100644 --- a/schemas/astroport-emissions-controller/raw/response_to_list_outposts.json +++ b/schemas/astroport-emissions-controller/raw/response_to_list_outposts.json @@ -17,6 +17,7 @@ }, "definitions": { "AstroPoolConfig": { + "description": "Each outpost may have one pool that receives flat ASTRO emissions. This pools doesn't participate in the voting process.", "type": "object", "required": [ "astro_pool", @@ -24,11 +25,11 @@ ], "properties": { "astro_pool": { - "description": "The most liquid ASTRO pool on this outpost", + "description": "Pool with ASTRO which needs to receive flat emissions", "type": "string" }, "constant_emissions": { - "description": "The constant ASTRO pool emissions. Can be set to 0 if emissions are not needed.", + "description": "Amount of ASTRO per epoch", "allOf": [ { "$ref": "#/definitions/Uint128" diff --git a/schemas/astroport-voting-escrow/astroport-voting-escrow.json b/schemas/astroport-voting-escrow/astroport-voting-escrow.json index 9fa2b902..00a938ea 100644 --- a/schemas/astroport-voting-escrow/astroport-voting-escrow.json +++ b/schemas/astroport-voting-escrow/astroport-voting-escrow.json @@ -5,7 +5,7 @@ "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "InstantiateMsg", - "description": "This structure stores general parameters for the vxASTRO contract.", + "description": "vxASTRO contract instantiation message", "type": "object", "required": [ "deposit_denom", @@ -142,7 +142,7 @@ "execute": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "ExecuteMsg", - "description": "This structure describes the execute functions in the contract.", + "description": "This structure describes the execute endpoints in the contract.", "oneOf": [ { "description": "Create a vxASTRO position and lock xASTRO", diff --git a/schemas/astroport-voting-escrow/raw/execute.json b/schemas/astroport-voting-escrow/raw/execute.json index c529c0ad..45b60ac6 100644 --- a/schemas/astroport-voting-escrow/raw/execute.json +++ b/schemas/astroport-voting-escrow/raw/execute.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "ExecuteMsg", - "description": "This structure describes the execute functions in the contract.", + "description": "This structure describes the execute endpoints in the contract.", "oneOf": [ { "description": "Create a vxASTRO position and lock xASTRO", diff --git a/schemas/astroport-voting-escrow/raw/instantiate.json b/schemas/astroport-voting-escrow/raw/instantiate.json index e4301531..ae6a36fa 100644 --- a/schemas/astroport-voting-escrow/raw/instantiate.json +++ b/schemas/astroport-voting-escrow/raw/instantiate.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "InstantiateMsg", - "description": "This structure stores general parameters for the vxASTRO contract.", + "description": "vxASTRO contract instantiation message", "type": "object", "required": [ "deposit_denom", From 78b8fa551c8556fabc48fbde5e7524eec206e7a0 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Wed, 19 Jun 2024 16:17:39 +0300 Subject: [PATCH 19/32] add readmes and diagrams --- .../emissions_controller_general.excalidraw | 1 + assets/emissions_controller_general.png | Bin 0 -> 181446 bytes assets/tuning_flow.excalidraw | 1 + assets/tuning_flow.png | Bin 0 -> 175490 bytes assets/vxastro_flow.excalidraw | 1423 +++++++++++++++++ assets/vxastro_flow.png | Bin 0 -> 235090 bytes contracts/emissions_controller/README.md | 343 ++-- contracts/emissions_controller/src/execute.rs | 1 - .../emissions_controller_outpost/README.md | 317 +--- contracts/voting_escrow/README.md | 536 +------ 10 files changed, 1570 insertions(+), 1052 deletions(-) create mode 100644 assets/emissions_controller_general.excalidraw create mode 100644 assets/emissions_controller_general.png create mode 100644 assets/tuning_flow.excalidraw create mode 100644 assets/tuning_flow.png create mode 100644 assets/vxastro_flow.excalidraw create mode 100644 assets/vxastro_flow.png diff --git a/assets/emissions_controller_general.excalidraw b/assets/emissions_controller_general.excalidraw new file mode 100644 index 00000000..15868849 --- /dev/null +++ b/assets/emissions_controller_general.excalidraw @@ -0,0 +1 @@ +data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABHQAAAQ4CAYAAABolouhAAAAAXNSR0IArs4c6QAAIABJREFUeF7s3QWYXNXdx/GDQykeihQIgeJQpEiCu1uxYMHhxSFYoViw4AT3AkEbPCmWQHBCcHcN7lLc3+d72rO9O5ndnZmduzvyPc/DQ7J758rn3J3s/c3/nDPOb7/99luwKaCAAgoooIACCiiggAIKKKCAAgrUjcA4Bjp101eeqAIKKKCAAgoooIACCiiggAIKKBAFDHS8ERRQQAEFFFBAAQUUUEABBRRQQIE6EzDQqbMO83QVUEABBRRQQAEFFFBAAQUUUEABAx3vAQUUUEABBRRQQAEFFFBAAQUUUKDOBAx06qzDPF0FFFBAAQUUUEABBRRQQAEFFFDAQMd7QAEFFFBAAQUUUEABBRRQQAEFFKgzAQOdOuswT1cBBRRQQAEFFFBAAQUUUEABBRQw0PEeUEABBRRQQAEFFFBAAQUUUEABBepMwECnzjrM01VAAQUUUEABBepN4JtvvgkTTTRRGH/88cc69Z9++in8/PPPYZJJJqm3y+rU+f7222/hq6++CpNPPnmn9uOLFVBAAQWaV8BAp3n73itXQAEFFFBAAQVyF3j//fdD7969w6KLLhouvvjisQKMFVZYIXz55ZfhiiuuCPPMM0/u51MrBxgwYED0OOOMM8K6665bK6fleSiggAIK1JGAgU4ddZanqoACCiiggAIK1JvAU0891RJYXHfddTHYybaePXvGvx5wwAFht912q7fLq/h8t91223DnnXeGZZZZJlx++eUV78cXKqCAAgo0r4CBTvP2vVeugAIKKKCAAgrkLvDEE0+E9ddfPx5n8ODBYfnlly8a6Oy6667hb3/7W+7nk9cBXn/99fDWW2+FHj16hLnnnrvo8LLssbfeeutw9913hwUXXDAMGzYsr9NyvwoooIACDSxgoNPAneulKaCAAgoooIAC3S2QDXTOPvvssNZaaxUNdLbaaqtw1FFHdffpVnT8Sy+9NBx66KEtr51//vnD0UcfHRZeeOE298f13nPPPWGWWWYJ9913X0XH9UUKKKCAAs0tYKDT3P3v1StQtwJff/11uOWWW+IvwszNYFNAAQUUqE2Bxx57LGywwQbx5E477bSWah3+zsTAs846a/zeZpttFo477rjavIh2zurtt98OSy+9dNEtCHX69etX9HtbbrllDHJmmGGGMHr06Lq7bk9YAQUUUKD7BQx0ur8PPAMFFKhAgPJ9PvWlDRkyJJdQ56OPPgqffvppU03SWUFXlPWSV199Nfzud78LM844Y1mvc2MFFCgu8N1334Xnn38+LLTQQmG88carSaZHHnkkbLTRRvHczj///LDaaqu1nOcvv/wSZptttvj37bffPhx22GE1eQ3tndQ555zTbhDVv3//sPfee4+1CwKsUaNGhTnmmCPccccddXfdnrACCiigQPcLGOh0fx94BgooUKbAhx9+GBZffPGWV5155plhnXXWKXMv7W/+8ssvh1VWWSVuxKSVs88+e1X334w7u/rqq8P+++8fP43mU+kJJpigJhh++OGHuJyyTYFiAg899FBg+Mykk05ak0CLLLJIDJ5POOGE0Ldv35o8R0ILwgvaZZddFpZddtmW8+Tnb84554x/33333eN7RL21HXfcMYwYMSKeNsHNNttsEyc5Pumkk1ouZc899wz77rtvq0vbcMMNw6OPPuocOvXW4Z6vAgooUEMCBjo11BmeigIKlCZwwQUXxLkJUmOp27bK3Uvb49hbZYcI7LHHHmG//fardFe+7r8CzJ1x/PHHF32o6yqkL774Itx1113hwQcfDI8//nh45ZVX4qF5WOfB+JBDDomTmdqaU+CFF14I//rXv2LVBIHjJJNMEuc4Yc4X7t9aa9nhSszVcuONN9baKcbzYeJfJgCmFa5yxfDZ+eabL36vXle5YslxVvJiCDDvL+OPP/5Y180XCkOd9DpXuarJ29aTUkABBepCwECnLrrJk1RAgSTAAwyf7rKSSGo33XRTWGCBBaqK9PDDD4eNN9447pN5Do455piq7r8Zd3bGGWe0fGLNn3mYyat99dVX4aWXXgrTTjttYOgcf7733nvD8OHD2z0kwc7TTz/d4eo05Z43n9ZTocTqPnvttVe3D4354IMP4gSuDNc56KCDWh6oy72uRtn+p59+inO7cF+21Z599tkw2WST1dQl//zzzy3Vg7U8sS7VK1Sx0Jj7LAU4/P2zzz5rmTj4iCOOiNUt9dZSldTaa68dzjrrrFan/+KLL8b5g7755pv4dVbxYjUv2sorrxxD5VVXXTXwQYVNAQUUUECBcgUMdMoVc3sFFOhWgexcDOlEeFDv2bNnVc+LCSrT8AXme2Deh19//TV8/vnngSqPiSeeOM4FM9VUU1X1uI28Mx6YTznllHiJrGTDCi88kH7yySfxYQfPySefvNNDWxh+wife6QGqPVMCHPqS16TGcLtqDsH68ccf4xwZqRHo7LPPPt3a1dm+mGaaaeJD9vTTT9+t59RdB//+++/jpLWEuO01hstQYVFLjSDqT3/6Uzwl7mVCp3HHHTf8+9//jj9XzKnD16eccsqqh5TlONx8880tIQYVLGnOHPaRHUJ74oknhk022aScXXf7ttlQra3w/7nnngtrrrlmy7nedtttcW423qf4cILAZ9CgQd1+LZ6AAgoooED9CRjo1F+fecYKNLXATjvtNFaVBZMjTz311FV14QF3l112adknDyCvv/76WMdgvgQmvLS1L0AYduSRR4aLL764ZUMqCrKVVukbnR1CN2bMmFZzdGTPjAoZhs/85S9/Cb169YoPv7TXXnstbLHFFoHwhQf7NGSiGv3KPpmwNhswFVu6uRrHKnUfl1xySTj88MNbNufhcujQoVUNsko9l+7cjgl5CWmo8kttt912ixP4EvIx3PL9999v+V4e7zWduX6qzxZbbLFWP1Ocd2GYueiii8ahTt3VbrjhhpZJgQnLmUcrtewKUXlX7uVx/dnAln8zDjzwwKKH4YOHtNoV1XE77LBDnMyf+8sq0Dx6xn0qoIACzSFgoNMc/exVKtAQAqwCwioohY2H8VIewHl449PUwuoLqm4uvPDC+EkxD0hMUllKdQfnse2224YBAwY0hG81L4K5SHiApEqABzZMS23nnXdeWH311UvdfKzt3nnnnbDUUku1+jr3zf/93/+F6aabrs39cn8Q8IwzzjgVH7utFxIGnnrqqTE0oa244oqtwq22Xkf1CPdrJedE9QavK/azwbVeddVV4R//+EdLUMm5ETzVWmvvOjp7rldeeWUccpZaYdDGcJnsikx5TMBe6jXw3kUQ98Ybb8T3Kc6tWCBabH8E0lTGdFe75pprWuYhY0jjFFNM0XIqb775ZlhuueXi3y+66KKw0kordddpVnTcbIUOw8qYh6tYywY/aSnzNFSL96a///3vFR3fFymggAIKNLeAgU5z979Xr0DdCDBxJnPnZIfGpJOnIqNY42GYyU1HjhwZnnnmmThpJY3hL6y0kj4lzi6B3hEIE4+ywtZcc80Vhw38+c9/LjofCiERx+N8GUrEcJZ5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y62bS8Cfj+bqb69WAQUUUEABAx3vAQUUUECBuhHwgbVuusoT7QaBvn37ht69e4f+/ft3w9E9pAIKKKCAAgp0tYCBTleLezwFFFBAgYoFCHQefPDBMGTIkIr34QsVaFQBA51G7VmvSwEFFFBAgeICBjreGQoooIACdSNgoFM3XeWJdoOAgU43oHtIBRRQQAEFulHAQKcb8T20AgoooEB5AgY65Xm5dXMJGOg0V397tQoooIACChjoeA8ooIACCtSNwOjRo8OgQYMcclU3PeaJdqWAgU5XanssBRRQQAEFul/AQKf7+8AzUEABBRQoUcBAp0SoIpvdcMMN4cMPPww77rhjGG+88Srfka+sWQEDnZrtGk9MAQUUUECBXAQMdHJhdacKKKCAAnkIGOhUpvrMM8+EtddeO754xIgRYa655qpsR76qpgUIdFjhipWubAoooIACCijQ+AIGOo3fx16hAgoo0DACBjqVdeUJJ5wQzjrrrPjiBx54IMw000yV7chX1bSAgU5Nd48np4ACCiigQNUFDHSqTuoOFVBAAQXyEjDQKV/2559/Dn/+85/DN998E1/87LPPhskmm6z8HfmKmhcw0Kn5LvIEFVBAAQUUqKqAgU5VOd2ZAgoooECeAgY65evefPPNYdddd2154RtvvBHGHXfc8nfkK2pewECn5rvIE1RAAQUUUKCqAgY6VeV0ZwoooIACeQoY6JSn+/XXX4dll102fPrpp/GFk046aXj++efL24lb141Az5494wpwzqFTN13miSqggAIKKNApAQOdTvH5YgUUUECBrhQw0ClP+/DDDw+XXHJJmGeeecILL7zQYaBD8DPVVFO1VPB8/PHH4a677grLLbdcmG666Vod/MsvvwxU+yywwAKtVs3i66ymxfZTTDFFhyfMkDCCpymnnLLNbd96661A308wwQQxrJhhhhk63G8zbmCg04y97jUroIACCjSzgIFOM/e+166AAgrUmYCBTukddvfdd4ett946vuC2224Lq6++ephmmmnC448/Hr/GMuY//PBD2HTTTQMhDNs+8cQTcTWsM888M4wzzjjhoIMOCldeeWXYYYcdwqGHHtpy8J9++iksscQSsfKH4Vz77bdfuO6668JFF10Ug6PUCII233zzeGzaL7/8Eve9+OKLhz59+oTnnnsu9OvXL+7nqKOOCltttVWrC+T8mND5wgsvbPX1vfbaK+yzzz6lYzTJlgY6TdLRXqYCCiiggAL/FTDQ8VZQQAEFFKgbAQOd0rrq3XffDausskqcCPnss88Oyy+/fJh33nljZQuGtGWWWSZQ+ULAQyBz5513tuz8sssui0O1Nt544/Dwww+H9Pe0AftgvhYaQc9NN90Uw6DUqAhi32kiZkKbAQMGhE8++SQGQXz/iiuuiOeYhoPx2qeffrqlqofQaOeddw533HFH3O2SSy4Zfv3115bzJ7Dq1atXaSBNspWBTpN0tJepgAIKKKCAgY73gAIKKKBAvQkY6HTcY1S1EMQ89dRTsermyCOPDP/+97/j0KhZZpkl3HfffXEna621VlzxiqBk1KhR8WtzzDFHeOWVV2L1C1UwDG96//33476yQ6LSUC72t++++8ZtaczRc/7554ell146hi8M1yLIIdzhXPbff/8w//zzx+14LdU8/J9Qh/AnO//LySefHE4//fS47eDBg8Niiy0Wvv3227hvtuc4q622WscgTbJFCtmcQ6dJOtzLVEABBRRQIIRghY63gQIKKKBA3Qikh9YxY8bUzTl39YmmYVIMr6IK5vPPPw/3339/OOuss+KpUJnDNmmoUzo/hkv98Y9/jCHJyiuvHIc5zTrrrPHbr732Whh//PHjn9977704XIp2wQUXxMCFYVW0oUOHhoUWWqjVJVMtRGhEowpoxRVXbPk+rx0+fHig2uaQQw4Jhx12WNh+++1bHYONZ5ttthgEPfDAAy0VPQ899FCYfvrpu5q3Zo9noFOzXeOJKaCAAgookJuAgU5utO5YAQUUUKDaAgY67YteffXVsQqmvUagc/TRR8eJjlOjiifNt7PIIouE77//Pq6GxTAtKmduueWWMN9884Uff/wxzqdzzz33xHlwrrnmmljds9lmm7Wq/sken4ofAiLatddeGzbaaKOWb994441h4YUXDq+//npYYYUVwnrrrRercgiKOEcqhKjGYR+pMWxs4MCBrYKhat9n9bg/A5167DXPWQEFFFBAgc4JGOh0zs9XK6CAAgp0oYCBTtvYI0aMCDvuuGOrDQhLCGiovCG0oWE47rjjxkCGxvCsk046qeV1VMgwb829994bh0tRVUOIwr7/9a9/tcyVQ+iy6qqrhpdeein+n8Y5zDXXXPHPrF716KOPxmogApvdd989BkRMokzjmByb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UEABBRToEgEDnS5h9iAKKKCAAgrkI3DJJZfEIVcnnnhi2GSTTfI5iHttVyAFOhtssEEYNGiQWgoooIACCiigQJcIGOh0CbMHUUABBRSoV4Fff/01jDvuuFU5/WeeeSZW0hx55JHhj3/8Y1X2ecEFF4Sjjz46nHnmmWGdddapyj7ZyY033hhGjhwZz3e88car2n4bcUdPPvlkWG+99UK/fv1iX9gUUEABBRRQQIGuEDDQ6Qplj6GAAgooUJcCTz/9dNhmm23C1FNPHa677rrwzTfflDTxcFshUKqm2XfffcOee+5ZFROCHKpzmBR5pZVWivusRgi14447hhEjRoQhQ4aE3r17V+VcG3UnjzzySNhoo43C//3f/4W///3vjXqZXpcCCiiggAIK1JiAgU6NdYino4ACCiiQn8BPP/0U55v55JNPwkknnRSmmGKKdg92/PHHh7PPPjtuw1Ca/v37h4cffjhMN910bb7ujjvuCNtvv33YZZddwoEHHthquwsvvDAcddRRYdttt40rIlWjUUHDuf3zn/8Mffr0CSeffHI4/fTTw3nnnRcn6q20cQ1cS2f3U+nx6+l1o0aNCptttlnYZ599wl577VVPp+65KqCAAgoooEAdCxjo1HHneeoKKKCAAuUJpLCFV11zzTVh8cUXb3MHVLkst9xy4a233orDadZff/0YxHQUcGRDoOOOOy4+6Kd22mmnhVNOOSXMMMMMYdlllw1jxowJX375ZZh00kljWFTJktfpeAyRWnjhhcO6664bnnrqqXjIW265Jcw333zlIf23wofzHj16dFh00UXDzDPPHN5+++1YoUQIRmDUXqhV9gHr/AV333132HrrrcPBBx8cdtpppzq/Gk9fAQUUUEABBepFwECnXnrK81RAAQUU6LTAzTffHHbddde4n44CnfSQzra33XZb+PTTT8MWW2zRYRXGd999F6644oowbNiwMOWUU4ZpppkmvPfee+GVV16J+yjWCHSoHNp0003LvkbmbGEeHYZHzTXXXOH9998PDO3i74QyxQIGzpGJfGl/+ctfwjjjjBOeeOKJcM4554SPPvoo/rmtxvWwVPr8889f9rk26gtSUHjMMceELbfcslEv0+tSQAEFFFBAgRoTMNCpsQ7xdBRQQAEF8hPIhjTZQOe3334L3377bayUSY2KC7ZfZpllwuWXXx5DDqp0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z9mceOKJ4dFHHw1nnnlmXF7epoACCiiggAIK1KOAgU499prnrIACCiigQAUCjz32WNhggw3CPvvsE/baa68K9tAYL+ndu3d4//33w4UXXhhWWWWVxrgor0IBBRRQQAEFmk7AQKfputwLVkABBRToboGBAweGKaaYIuy2225deipXXXVVOPDAA+NxDzjggC49dq0c7Ntvvw3zzDNPPJ2zzz47rLXWWrVyap6HAgoooIACCihQloCBTllcbqyAAgoooEDnBXr27Bl38tJLL4WJJ5648zsscQ/nnXdeIEw64ogjwjbbbFPiq+pjs99++y2MM844HZ7siy++GFZbbbW43THHHBO23HLLDl/jBgoooIACCiigQC0KGOjUYq94TgoooIACDS2QAh2GQPXo0aPLrpW5Y5g3hv/WWWedLjtungf64Ycfwvbbbx+ef/75cN1114VevXq1e7jhw4eHnXbaKW5z1FFHha222irP03PfCiiggAIKKKBAbgIGOrnRumMFFFBAAQXGFiCAmHPOOeM3GO7z888/h3fffTf+fdZZZw1nnXVWbmwMt2LY1ZAhQwLzyDRC+/LLL8Of//zneCmzzTZbGDZsWJhsssnavLRUpcQGjRRsNUJfeg0KKKCAAgooUJ6AgU55Xm6tgAIKKKBA2QJXX311uPXWW+NEvC+88EKbr59//vkD20466aRlH6OUF1DJcscdd4QRI0aEueaaq5SX1MU2Tz31VLj00kvDAw88EE4//fSw+OKLt3neqUqJDbBeYokl6uIaPUkFFFBAAQUUUKBQwEDHe0IBBRRQQIEcBX799dc2hwGtuuqqYfnllw/zzjtvrNppK8ihCuXDDz8M0003XZxMudK27rrrBsIPluwuZ7nuJ554Ijz77LNxuXPCksknn7zSU4gVSa+++mq8DvbX1e3oo48OF1xwQTzs008/3SnPrj53j6eAAgoooIACCmQFDHS8HxRQQAEFFMhZgBWl7r333jgZLxUh++23X/jmm29itcwcc8xR9Oi//PJLnBPmoosualXVs9xyy4XNN988rL766mWfNcERxy01yHjvvffCwQcfHO68885Wx7r44ovDiiuuWPbx33zzzbD33nsHAiLaoosuGvbff/+iw7+++OKLcOWVVwYmMR533HHjXENbbLFFh3PkZE+K8Oruu+8OX331VRzetvDCC4dDDz00VvPgyP9tCiiggAIKKKBAvQoY6NRrz3neCiiggAJ1K9C3b98wevToMHTo0LDQQguNdR0ffPBB2HnnnVuCDzZgqe233norBjK0fv36hQEDBoTxxx+/JIePP/44Bii0vfbaKzCXz+effx6rbZiDhrBj5plnbtkX57DxxhvHY9IIRBgu9vrrr4dpppkmVvkQtJTa2M8yyyxTdHOGQW2yySYt33vllVcCRp9++mmr7Zkjh6XG07Lj6Zvff/99uOyyy8Iqq6wS5yGinXHGGeGkk05q9Xq8L7/88nDNNdeEQw45JOy4446lnr7bKaCAAgoooIACNSdgoFNzXeIJKaCAAgo0ukCay4YKlKWWWmqsy2Vi3z322CN+nWFY559/flh66aUDw7fuuuuuGOQQkGy99dbhyCOPLImLaps+ffq0uy0hCMOyWAJ8o402iqEN4cngwYPjcK/nnnsurLnmmnEfjz/+eAx2Sm2sKHXhhRe2bM5cN6xMde6558av3XPPPTGMITTacMMNY3BFFU3//v3jZMfnnHNO/I8/33777a2CLAKglVdeOeywww6xAoeKom233Tbul2FtTABNpQ/hGUuVs28qg3bfffdST9/tFFBAAQUUUECBmhMw0Km5LvGEFFBAAQUaXYAghqFAVJUsu+yyY10uk/syrIpWrIqHVbGWXHLJ+H3CjAknnLBDsmygw9w1e+65Z6zYocKHkIPhXzRCG4aH7bLLLi37XHDBBWP1zk033RS/xjCxtH2HB/7vBltuuWW477774t849r777tvyZ66RaiAqahhKRqiTDasImKi+4VppJ5xwQqzgSY2hXIQ/DANjOFiqgKIChyFj44wzTtyUP1OhQ6Mi6cYbbyz19N1OAQUUUEABBRSoOQEDnZrrEk9IAQUUUKDRBXbaaacwfPjwWHnDvDo05suhuoSghIBis802C7PMMktLCJI1SRUpfO21114radgVQ6jSik7Z47KPMWPGtARLhDannHJKrHKhWmfkyJEtw7zYlhCIQGX22Wcvq5uokmGVLxpVOWussUb8M+ERw8cIjVhSnO0IjFgVbIIJJojb/POf/wx/+9vfWo5HZRCh1ySTTBK/9vXXX4f55psvVjMxeXOvXr3i1x955JHwhz/8If6Z/TGMLdvYx0wzzVTWdbixAgoooIACCihQKwIGOrXSE56HAgoooEDTCBx++OHh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L6ROLAgEgSAQBIJAENiYCITQ2ZjjklYFgSAQBILAAAQSPyePxjwj8LjHPa7ZZZddmgMPPHCpm294wxuaL37xi42//+qv/moq3UeaiqUjhXksCASBySPQ792ffK2p4Te/+U2DwD788MOb29/+9gEkCMwcAiF0Zm7I0uAgEASCwGIjgNB52tOeFuXAYj8Gc9t7z/cNbnCD5uMf//hSH/fff//mQx/6UPPVr361uepVrzqVvieOzlRgTiVBYAmBfu9+4Jk8At/73veaPffcs3n84x/fHHrooZOvMDUEgTVGIITOGgOa4oJAEAgCQWByCNQmM4TO5DBOyeuLgE3d3/zN37RuhWVPfOITm4985CPNZz7zmWa77babWgOTvnxqUKeiGUPgxBNPbF772te2irknPelJzf3vf/9miy22WFUv+r37Ky3w//l//p/m5S9/eXPyySc3173udZtDDjmk2XXXXVda3Fzf953vfKfZa6+9WpUOzGJBYNYQCKEzayOW9gaBIBAEFhiBCojMDSTxcxb4QZjjrtvUUeFQ45Q94QlPaD760Y82n/zkJ9vN2bQsgZGnhXTqmSUEzj777Oaud73rJk2+5z3v2bzqVa9qttpqqxV3pd+7v9LCTjjhhJbE6Rr1CRVKbFMEitDZZ599WjfTWBCYNQRC6MzaiKW9QSAIBIEFRqAInaRTXqyH4JJLLmn8ufSlLz21jr/3ve9tTjvttMYzd6lLXWpq9drU/e3f/m3z6U9/eqnOxzzmMc0nPvGJ5gtf+EJzzWtec2ptSWDkqUGdimYIgde//vXNS17yklZJt++++zb+/z//8z/NHnvs0ca5usxlLrOi3vR791dUUNM0j3zkI5szzjij+fu///tWmfO6172uLeqggw5qnvrUp6602Lm87+tf/3pzn/vcp3nEIx7RHHnkkXPZx3RqvhEIoTPf45veBYEgEATmCoFpEDr/9//+38YC71Of+lSrkvjxj3/cXO5yl2sXxgcffHD775Xa//7v/65alj9K3dOqZ5S2rPSaz372s8373//+5re//W3751e/+lVLavyf//N/VlrkWPeVKmaaajCk1fWud73mxje+cfPhD394qb0PfehDm8997nPNN7/5zeav//qvx+rHai4OobMa9HLvWiJw7rnnNuecc05zt7vdbWCx3o+zzjqrude97tVc9rKXXcvqNynrsMMOa972trc1b3rTm1qlzn//93+3ypdvf/vbKyZ1Br37o3Si33x/pzvdqYFZxd0SVP3Rj350SzzJXMdtOfb/Q+BLX/pS84AHPKDZb7/9muc+97mBJQjMHAIhdGZuyNLgIBAEgsDiIjAJQgdR8O///u/Nlltu2fz0pz9tzjzzzHaB3s9e/OIXNw972MNWNACC3MpicsABB2wmhV9RgQNumlY9a9nmblkXXnhhY8PUL7uSuBW3vvWtJ1X1JuUaK1g6cRcwcxr2l7/8pbnhDW/Y3OpWt2pOOumkpSrF5/jyl7/c/OAHP5iqSimEzjRGPXUMQ0A8mO2337697Bvf+EZz5Stfue8tj3rUo1q3xOc85zmNQOKTsiJ7P/jBDzY77bRTWw2ihCrGe3rve9+7ja8zjg1694eV8cMf/rCt91rXulbz1re+dYnI6pcNEsHDNUxbV/MtG9amWfs9shxpHvXSrI1c2lsIhNDJsxAEgkAQCAIzgwBCB+GylqmU3/Oe92x2Wnnb2962jdEjXokgtL/4xS+aZzzjGc1LX/rSdrG+EnvZy17W/Nu//Vt7q3IsICdh06pnEm130vyUpzylVeYwJ8lbb731UuaRXqJjEm1QpnYYH4GJKbM8Az/5yU/ajRCFzL/8y78017jGNda8+j/96U/tBpHrBgVAmWfORnbarobeN4o14xALAuuFQMU4ufzlL9+qJwe5XnovX/nKV7ZqC39PyhCd5oYuoaOu3//+9y1hQtXpO4VkGdUGvfvD7kdgIbIYtyEYdAmw3jmjsOwNvD6snnn+fWH4vOc9rxGAPhYEZg2BEDqzNmJpbxAIAkFggRGYBKEjHXSd5gqK6JSuXyah1boxXXDBBc073vGO5j/+4z9aMuCNb3zjZiPpGgtudstb3nJZ96JB145SzyQeIYSH0+Ib3ehGKyY7KHCe9axntc2TnaWyspxyyintuDDy+Ktf/epr3oWvfe1rbZwJii3/HmQCFr/97W9vdt555zVvw+9+97vmZje7Wbsp/Nd//del8imEbBK/+93vrnmdyxUYQmeqcKeyAQj80z/9U3P00UcPVTe6xrXcsvrNr1w3za/m3xvc4AYrxrtcIHsJHQUKquu9KVenUSsZ9O6Pcv8HPvCBxtz5rW99q+GqivDiusn6kcAUf8jprlvnKPXM6zWlaj3qqKOahz/84fPazfRrjhEIoTPHg5uuBYEgEATmDYFJEDoWtjvuuGPj9HfaG2bjY4PxsY99rF2Ii3NQ1m9xOc61g8aetN8in9JkVJXJH/7whzbQZ/dk3ImyhbDgvaeffnrzm9/8pq0SjmJZSOc7jjlVvv3tb9+6u3FX6CqhlH2LW9yiLU6w4l122WWzohFuP/rRj1rXOafPo9Tf7VcFHu4tGHGjLTe5yU1asgqh0zXqLUQQBc3FF1/cbqSc4OtLr+kbwk37+sX4+NnPftZQh/Wmz62Mbt1U5t2ykT1+p++uVX7XtIs67Ctf+Urz85//vCUskUQ2dsvhFEJnnCc4104KgTvc4Q4toXnqqac2N73pTQdW86IXvaiNa1MKHXMKdxoKDDHRuCyWITPEqupn7nM9F1zX3PzmN2/fLfMFlyrvhfe9H6EjFs4vf/nLsdQ52jDo3R8XU3OgdpdrcD9CxzfPd4D6cSOZtpvHKWOXi5U2zlw/yrW+KQJFG9f73e9+GwmStCUIjIRACJ2RYMpFQSAIBIEgsBEQmMQGs+TpXQm6jTeiAsFC4l8bAZt57lL9AnMqxyK0MiLZgPz5z39udthhh4Gbhsc+9rFtJpKuyXB0pStdqXUzqo28ske9dtA42fC/5jWvaTc4ZU6pZfZAIFRmFuqUz3zmM219SAfxbI499tiWqOCeJn6FzY77uobI4WKAADFO45rN2oEHHtgqVKiYeu2Zz3xmGxTZJqqb6QnOTsU/8pGPtKfOzDg95CEPaWNL1LXD+oVUe8ELXtDc+c53bgQUffe7390+A7LZyGTTz7hY3Pe+922Dj/aak14SfhsKhA+VUTc20wMf+MDWVQJRVFbpkAVYNf5lCEfYItC6ZlP28pe/vN3Eds3mpBRNfv6P//iPzfve977N2sidzFj1U6S5eBLv27jPRa5fbATE1EKkMu/ZchnnKtYUt03z0z3ucY8lorlQNI8hMih4+hEa5nyBcbvkj28DN1/z0ite8YqlAUEc3e52t2vj+yAhzNtl5qLnP//5bR1i+vQSp+aOiy66qNlmm23aWwa9++OOfrmD1X3cJeGn3+LqrDQDl/KoiI4//vg28PQWW2zRXO1qV2uJI31fjTkcOOKII5ZcqeFt/jKe3fEeda7XlnGupZw15sccc0xz97vffTVdyb1BYF0QCKGzLrCn0iAQBIJAEFgJApPYYFJYVKBdRIANRBED3TZaZNqQlzsMcgHRgtx55zvf2S5IxT8hfbeYdNJHWfKqV72qXZgypAKVjc0+gsXiu+yFL3xhs/feey8t8Lt1j3Ntbz3aeMghh2wSZBdpRLVRBIONvcWs/tuEIHDe/OY3t5saBFYZwoVLFDKi1ETUMu5xij2KKmbQuCMwuDI5ZUfEjGKu7xIfRSp1CTixaJAmo/SrWyeVkM2b+wRB7Wfl4uF3SBQEmbGHH2zhyg2t1EuuQ3jJhlOmDnjWuCGIEDBiNpXZiMHZSXKZ8eMqWCQPZQ/yqFQ8VAk2WpXBxX3qsQH1fCPnqA1gRrnTTzE0ifdtlHHNNUGgEKB2ETvLnIVgHWTnn3/+knLPe4n8Mfcyz7hnmWpuuSyF4t4gguueu9zlLu375X3hjmseXy4ovvkTISDwvYxcVH+sn/uVdxcxXmpG34Z+7/4oT4K5houqeQq5y+1skMHRd8cc2yWghtVjTnVfdy5zD6KI+m+Q2mlYub5P1IJdAq3uedCDHtSICYc8GmeuH+dadfn2UcQidvopK4f1Ib8PAuuNQAid9R6B1B8EgkAQCAIjIzCJDabNrMV612wAkCtS0oplU242TlWRFn6PlLHBELdAENta6CrPxrlUG13FiZNjp7wW+FyexGIoYsRCG1mCMLKA7ZpF76jXuq9bDxXHbrvttlQc1YiFPzXR9773vXYhqx/ICG4ICAaKEpuoaps+cDGooMTdQJwKtvB2EjxOENDeQdd3ZNg4p6TliqEsuNnI2LCJg2OT8Za3vKWthjvbcccdN7Rf3TbZGCGXpPcdFBS46u/NjoIUFJtJf0oRBV/Z1IwztytZrIqMgvdee+21RPJ1s/Qgbq5//es3d7zjHdtNTZmgrwKgehaRVuINUYTZkHgWC0fjre/GTnvKlUGwY+owp+0UQf3IuEm8byO/7LkwCHRckYYF8e2Ns2XONCfUvOz5p8Lo564JaNd5n4u8QSRwMfXOItu9v8gd3wCEqznQu4zI4C7VJWmRHIghmaT6tburOqKGpJAr0n4lGbqoZtxX7zzXJd80fdJf3y/fo66SUApz/RrFfCcq7o55yHyoj+Ytf/ybwnElhP5HP/rRTQjzJz3pSe18VwrDcoMaZ673rUSsj/JduPa1r90qV5F/g9x5R8Eo1wSB9UQghM56op+6g0AQCAJBYCwEJrHBpLRxkmrRS9HADcaJ4aBMKtXgInSK7KifU0rYxNtol9LHaa0YDNyGbOS5v1C02HT4NyKi1DI2CNQZXH+6xM4413br4fJVLgsIBJv3rikXcWWxb1PEtcDGv8xJMrJF7Ar9KbcHmwYLYa5JZYgif0aNzdNtRy2qe92NlntAysWCSkp68V4irMiMZz/72W18nVH6VfUpz4YMOQbPXhMrowKPIr769bkCpHq2xBlC4nUNuad8mxWkSxFlXZXSH//4x1bVg2CsLGni4HRJOu1wDeKqNrBf+MIXWnczJ/GIG/cqYxybxPs2Tv25NgggUCpuTs2jvaggSBGi5qYu8SnmDULGe1hGQUNNV+nG6+dcSyuznLlbzC5ztu8D685LpczrvqfUcdSeFEXXuc51Wpcugc0RK936leVdRBh1VUf93v1RR983xRzVVRNW4OZurCDksOD15hTfha222mqkKnwPkTq+jZSoDCHsu1HKGq6fXcXpSAU3TYuRexlFo28Qo3o9+OCDW0LMvObQgKJwlLnemI167ZOf/ORWhYqYWi6u0qj9yXVBYD0QCKGzHqinziAQBIJAEFgRApPYYJY8eznXmn6NLUKn3+8sQi2ykSFObiuFbaUU743LIpaC61//+tcvnSw6ET788MM3k4CPcm23HqTHDW94w7aZ/YgH5YnVgwiwmKb6qLg+CACuR+IYUKpw07FYFjOiTOwHi+EusYMgQ6Is597Qi5uNRsndkULcxIad+BahM4isqNgx2oLgGKdfNnc2ed2NnM1LpQa24avTfu4S/eJxeF5t5pAqNn+9dsIJJ7T9LELntNNOa2MXURLst99+7eW1oe2mMrdZPPLII5fGrTfeByLqH/7hH9r7uY34vXv6xX5a7kWcxPu2ohc/Ny00AmJaIWvMa+USVYAgpKk5KkZUP5WFuY1aD7lTJLv3AsGNfOmm+UZKILW7RnEpvtYVr3jF9sejqPfKZbP73rrX96CI1VI8+nm/d3/UQS8ySPB29bLHPe5xraKIeoeb5UrNQYPvg+8R1SGSixXhUuUirREv/Vw3l6vb3Fx4d1VD1IblxiVZgTkUSTPKXF+EzijXInTMlwj8KHRW+pTkvvVGIITOeo9A6g8CQSAIBIGREZjEBtPpoFNCyhqbdAEaLfqdtp533nltIEikB3UFJQS1itPNLqGDDLLBLquTPtJ0RIfFL0WFuDROOG0QuDY55SQNFyOF2Vi8//3vb7jTlGQcwYJEGOfabj02QWLI6FM3XovybJK02wJY/9/61re26iQ/11cBiGuBXmVWTCALfYv4UjLJpoLYQVIwZAeybJw4DbAq9ybKJ5sSp8BOlqlSkBtUJzZhzGIcXt1YC35u3Gy6tEcbKWEESR2lXzWGlc6+q4ypWAvGEDlSQasRRdWm7sNMdUMlZDOEDKsNobGArU2ifyOPbPxqU4fQ8kyxInRsbirNsN9zVbBZ81wh6hBrAqwqp7upKleF7tiP+sJNIqvcqHXnuiBQCBSB6f/ma++keYebkvkIocp6MwNSopg7imyl2DEn2eh77yhxkNjUdsoshQiVjbJdYx7rjQ9TbkJdAqV3tLpxzxC097rXvVpivxus3D3ICu3o9+6P+gRw++VeZq7jzssq49dyQd1HKV8MMHOKNlI4clEyr1WcHkQLDGEFD/PMqMof9Xfjsfm/udrcXzGF/EyfkOujzvX6POq1+lOEjm8coi8WBGYNgRA6szZiaW8QCAJBYIERmAShQyFRJMQwaC2YbSgs+rouV4iIcknqBkGudKil/vnABz7QiBFADk/mXYtWhI1AmrVxQOwgcrgh1aLfhl3QzFGudWLbracbvJdrjuwkguXWabWfOSW9whWu0KpFkElFMhQmFC6Ik1KokN9///vfb+NJCABdJIINAOWMU3FuAFyKxjHkEqKjCK1+95L/U/8gMiqgMPcFSiRBp8t9zSYEkcLlbNR+VX0VW0kZNoH6VQQMfKmHSlHUi1WVgYxBuNTmUZwbm0XtLzPGxpU5gYZdxevws65rlzYZu9oEjYIv0gkengcuDONYCJ1x0Mq1k0IAKYMQ6b43vXX1U+8UASuYu/nq6le/entbNw6O94P6sJQ/5g/z4HLGfZNrV/c97Xd9ub72/g65jNA1R1Lwccvq9+6PimcFju7G6yk3LKQ4MmSl1uta1S0HESJIdDeYNFIM2VPuqMPqLfUVLEtpaC6v+d+8Lci879Woc/043wXtK7evcdx9h/Urvw8C00QghM400U5dQSAIBIEgsCoEJkHo9GbEQNogbJAcXIsoQq5ylau0Cp1uClXKFifHTmsRBogIG3cbjzL/R4KI24D4qIW3ey3qa2NQ11vUqs8GxqlrES5OKJ0220SMci1CiJy/6imCyAlrN3W2xTdiSnrfSmdr06Qv1C9dEyOCO8K2227bZoyp+DSuQXogo/xNSVMLcy5ngnWOa9orjTkJf6WNVzZyRBu6rkNcLShxups9mwm4+1MBrUftV3fsEDC9Gc8qADKXAPj5fcVE6tdPwaQprrrp6bXJcwJ7Y15WAaxt9MRhgjUrJVKRP6WW8jtkJHVVr9m0UiqVAmtcl0LlhdAZ98nN9ZNCgLqGMg5RUWZOoEzh4tkvy1LFqKrrKT+ufOUrN8oqVY953ryIOPDemftt8HvTe5uTzJ2IZO8v4lXw9eUybyFDqFkoNLla+Q4gJagOEeQOE8yR5rRB7/4oeKrHHF4Zp9yjbZQ1yKKuenSU8nqvMZ8j5muONWchiXbfffelS7l3IY9YN9bOsPqK0EFWm98q8Lu+UPzApw4LRp3r1TnOtQ4mfFOGBd4e1pf8PgisFwIhdNYL+dQbBIJAEAgCYyMwCUJHI2x+LeZtEMYJ6GuRPyzOi2sQQZVdiEuXzULdZ7GsXyTiveakUoDkUnCMc21vPVX2b3/72yU3hGFt721Pt79IJxshZEVvKlv32WiR/feLLTPuwI+CM2JFn7l4jRvHYVD51D1O75nN2L777ttukspsBGwMe+N69Osftz3ue9wReoMjd6+3aeK+ZmNZ42NzZgMoFgY8tddGDVnEHvCAB7S/81x5Rrhv1O9sJpVp0+j5HsdC6IyDVq6dBgLec4QMlyvvQs2rg+qmpqOarPehex1CluqOuvIjH/lI88QnPrH9tY09kogbpfmSOoQLprrNA5Q/CBjZqpZ7l8fFo9+7P2oZ5mNt6qqLpHI3H447z/erE2nkQMLcOqjP5l/zD+KsN0D9oH4UofOf//mfbTsFuPZnue/GOHP9qNfKNqb+cqMdFfdcFwQ2AgIhdDbCKKQNQSAIBIEgMBICkyJ0Rqp8whchRbj1UFVYlDsFpvzptzAe59oJN7slFyiNkBAW/RVrqJQxk65/0uXbpBiDcQI8T7pNyqcQopLqBqPurVc2H6584xJcVU4InWmMZOqYNALmJcpB86sNvjkKaVOxy6r+T33qUy2B24+gdg2iQqrvQanPJ92PeSy/l9CZxz6mT0Fg0giE0Jk0wik/CASBIBAE1gyBeSZ01gykFLRQCEjl/LGPfayNOYF42m677Zqb3/zm7aZznOCk/UALobNQj1I62zStyoVaR7DiX//6122gcS5Gt771rduAwLG1RYBbFfUU11quzbEgEATGRyCEzviY5Y4gEASCQBBYJwRC6KwT8Kl2IREIobOQw55OB4GpIcDNDYFWseimVnEqCgJzhEAInTkazHQlCAxCQKpeMRim4RsscKoMPmJ/VFrLjEwQWCsEssFcKyRTThAYjkDet+EY5YogEARWjoBsj4Lay8AnE18sCASB8REIoTM+ZrkjCMwUApW+UcYA8RbOOuusNnWnrCiTkA//9Kc/bYNzskhoZ+pRmYnGZoM5E8OURs4JAnnf5mQg040gsEEREHBaUHcxjaQ/HxbkeoN2I80KAuuKQAiddYV/cSqXftfCUMrJSsO6OL2fTk/JVQXJrGw4aq1Ult10vt3WvOQlL2mztqylyRTwsIc9rC0SmTROxqC1bEfKmk8EZOph73rXu+azg+lVENhACPhuv/rVr24DXseCQBAIAmuNgKD6t7rVrdpA1JXpaq3rSHlBYN4RCKEz7yM8Rv+k7V0u/aLUpwIvPv7xj2922223MUpumre+9a1takjpd//xH/9x4L0IiPVm52VBuO51rztW/9b74n//939vMy9IwXnKKacsNUe6zkc96lFL/5dukysU0qVInuOPP35JUbMW/Tj22GOb5z//+W1R8YleC0RTRheBEDp5HoLA9BAIoTM9rFNTEFhUBBA5UqJTjm9EQzZZexx++OHN7W9/+43YxLRpwREIobPgD0B1vxZtRx55ZPOIRzxiM1RMtBh0NoyU6Qfpm970puZFL3pR85jHPKadEHtNVoHHPe5xbVYBhMR6ESpIrZve9KbNW97yluYf/uEfZuLp+PznP99+aFgvofOEJzyhJVXY2972tmaPPfZo/33xxRc3Bx10UPMf//EfbVydtVQ7GGfjzT784Q83N77xjWcCxzRyNhAIoTMb45RWzgcCIXTmYxzTiyAQBFaOgAPQPffcsz3QPvTQQ1deUO4MAhNCIITOhICdtWJNVCYscVZOP/30zZpfizqkzgknnND81V/91VhdfM1rXtO86lWvan1kd99991a+jTy5/OUv3zz96U9vbnKTm7RECtMGRMMVr3jFsepYi4v/8Ic/tG0ZRDytRR1rWYZTgzvc4Q7N//zP/7TFdgmd3/3ud83Nbnaz9ufve9/72jS2XYM/pZV7pb3dcsst16RpFEGUQewLX/hCc81rXnNNyk0hQQACIXTyHASB6SHgfXNoEJer6WGemoLARkNg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QCKQCCQCicCwCCShMyxief3IEZChRerw/ffffy5by8gfkgVOBQKR6neSAyNPBZAzWskkdGa0Y7NZE4tAEjoT2zVZsUQgEUgEEoFEoCCQhE4OhGVH4PDDD28+9KEPzUt5O8pKSWXuGdLYjjoz0zD1FGj46U9/esmyIuV32qoIyBAjM8zOO+9c0pWnJQI1Akno5HhIBJYWgSR0lhbvfFoikAgkAolAIjAsAknoDItYXj9yBL7zne8UV6sXv/jFJfPTqO0jH/lI8/rXv7456aSTmsc+9rGjLr5zeRH093nPe16pT1oikAgMh0ASOsPhlVcnAquLQBI6q4tg3p8IJAKJQCKQCIwXgSR0xotvlj4BCHz4wx9uZM865phjmr322mvkNbr55pubO93pTs1aa621YNmXX355s/vuuxeVztvf/vaR16Mu8P/9v//XvOUtb2muueaaRlyaF73oRY1gw2mJwDQjkITONPde1n0aEUhCZxp7LeucCCQCiUAisJIQSEJnJfX2Cm0rdy4uV+94xzuapz3taSNFQUrh7bffvtlyyy2bj3/8450InSc/+ckl5fK47Pe//33z0Ic+tOHiFXaf+9yn+ehHP9r8n//zf8b12Cw3ERg7AknojB3ifEAiMA+BJHRyQCQCiUAikAgkApONQBI6k90/WbsRIPD+97+/xOd573vf2+yxxx4jKPF/i7jqqquaXXbZpdl2222bs88+e8GyL7vssuZJT3pS86xnPavUZ1z2rW99q9lzzz1L8S95yUuaL33pS813v/vdQuacccYZzUYbbTSuRy97uWeddVZz/vnnFzXWHe5wh2WvT1ZgtAgkoTNaPLO0RGAQAknoDEIov08EEoFEIBFIBJYXgSR0lhf/fPoSIIDIoc4RFBn5wv77v/+7+dM//dOhn96+L8iTXXfdtUEcLWSXXnpps/feezcveMELmkMOOWToZ3e9QVBhLlbPf/7zm0MPPbS5/fbbm9e97nWFzJl1UkebP//5zzeyZf31X/91T8gW2/dd8c/rxodAEjrjwzZLTgR6IZCETo6LRCARSAQSgURgshFIQmey+ydrNwIEqDW4OP3zP/9zs8MOOzTvfOc7m/e85z3NiSee2Oy2226dnyAezbOf/eySKYsb153vfOfmy1/+clHcdFHdfO1rXyvxbA444IDm5S9/eefnDnsh4urII48sf/7h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zDKhs99++zX/9m//1nzyk59s1l133RnqtdE15fnPf35zww03NGefffboCh1RSbvttlvzX//1X81XvvKV5tZbb21++9vfNmuvvXZz2223NbvuumuzzTbbNMcff/zUEXZJ6IxogGQxnRFIdU5nqPLCRCARSAQKAkno5ED4/9g7E7ibqvb93+Z5nsdCKZIxCg0koUSD+kmzZlRvkyRRQiUalKRJJY1UhkpKk6EkQ3iJkHmeh8z8/9/lXaf9HOd5zj7zPufc9+fjg+esvYZr7X2es65z3detCCgCUUeAQ8A555zjI3WiPoB2mJYIpDKh8/jjj8t7770nF154oYwYMSIt9zfYomvWrClXXHGF9O/fP1hTV6+jisqWLZurtoEabd68WRYvXixr1qyRvn37GkKHtCv+Ji644AJ57bXXpGPHjvLHH39Ir169BFIqmUIJnWTareSfq78655dfftHKmcm/rboCRUARiDECSujEGGDtXhFIRwRs6hUmhhyY1MwwHe+C6K85XoROZgd9Umd++OEHWbp0qRQpUsQc2CtUqHDCQletWiV58uSRMmXK+F4jJQd/laFDh2Zov2vXLnPQv++++yR//vxGgVK3bt2ogrd27VqZPn26zJgxQ2bNmiVNmjSJGininCg+MozBIezPP/+Ue+65R6655pqAawmEEQ23bdsmP//8szDnihUrGoKrUKFCpg9MUgcMGCDXXXed+f/OnTsNafzbb7/JtGnTTLu3337b1z4rEOm/ZcuW8thjj/n6s+3nzZtnSLVnnnnG7GOg4F5AdWPJG9umVq1a0rBhQ4F8atSokc9TBzzy5s2bdB47SuhE9VHUzoIg4FTn8Lzz2UGrXOltowgoAopA1ggooaN3iCKgCMQEAZt6Red8IEOxo6EIRIJArAkdDukvvfSSDBkyRKpWrSpPPvmknH/++b4pd+/e/YRviwcOHCjWDHzFihWGnCHthjjrrLPkzTfflIIFC8opp5xifrZ8+fIMaTcvv/yy8YSBQIC8sIGfzqeffiqjR482hEXlypUNOdKpUycpUaKEaQYeEDR33323ZM+e3XftP//8Y8aATL3zzjvl+++/972GgoQ1oRxxGx999JFRnlx22WUZLtm+fbsUK1bMeF3cfPPNZm02mCPz8lekZIYR/UCOXHXVVSb1zEa5cuVMqlK9evXkoosukldeeUVOP/10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roQ4J610FSJg2lwRUAQShsD+OXNk4623GjIHZU6eunUzncuGW281pczLffBBwuarA4eGAATc/hkz5MDCharQCQ26pG+tKp2k30JdgCIQVQQwQ+46qadcedolMnvDfE23iiq62pki8C8CSuikwN2wtm1bKf7oo8K32VrpKgU2VJegCKQoArZEOalWRe64Q4redVemK6XtqqZNNd0qye6FrU89JdkKFJD9U6dK+c8+S7LZ63QjRWD27NnyxBNPSL9+/aRr164yRY2xI4VUr1cEkhYBm241dc1vMveWb5N2HTpxRcDrCCih4/UdcjG/LY8/bsr5bu3fXytducBLmygCikBiEEBxA5mTr1kzKf3881lO4sD8+bLhpps03SoxWxX2qD5CZ9o0KT9mTNj96IXJicCxY8ekTZs28uKLL8q9994rLVq0kEceeSQ5F6OzVgQUgbAROCbHpN6Ii+X5C/vIi7+/KeOueifsvvRCRUARyBoBJXRS4A7ZO3Gi7Pv5Z9n/229a6SoF9lOXoAikIgLOEuUVxo4NusTdH30ke7/91pi9ayQPAhA6xKG//pKy772XPBPXmUYNgXnz5kmvXr2MSueGG26QP/74I2p9a0eKgCKQHAiQbtXt28ekVsnTpGnFhnJptYuSY+I6S0UgCRFQQicJN81/yofXrJGNXbqIHD0qZV5/XXKWL58Cq9IlKAKKQKogcHj1alnbvr0r3xy75s09ekiBNm0k/wUXpAoMabEOzK4lZ045tHixEjppseMnLlJVOmm68bpsRcCBwGM/PyOl8peUd+d/InNumaTYKAKKQAwRUEInhuDGs2sqXeU66SQpfP31UuDSS+M5tI6lCCgCikCmCDh9c0r2729IGo3URWD9dddJ7tNPl2w5ckjxnj1Td6G6siwRUJWO3iCKQPoiQLpV/RGtjAnywBmvarpV+t4KuvI4IaCETpyAjvUway66SPI2aiQ5K1aUoqh1NBQBRUAR8AACu959V7a/9JIr3xwPTFenECEChtCpXl2y5cqlhE6EWCbz5arSSebd07krApEhsGDLn9Lzp2ekZonqcn6ls+WSai0i61CvVgQUgSwRUEInRW4QSpcXu/9+2f3++ypzT5E91WUoAsmOwL5ff5VNXbpInnr11Asn2TfT5fwhdIiCHTpIoSuucHmVNktFBFSlk4q7qmtSBIIj0OPHAXJy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pS+6/N5cckKv76tTaPw77Fx1MDAwZoPm+MpOXHUwMDE0XHLC//z5Z3zH8q1cdTAwMWJdXHUwMDA3jueHy4Pu8Mbzn6twu27P9aNQ3fS3+m1Z/1x1MDAxYv+vzngtXdEgai9cdTAwMWbuNO5C4fLh94Hrh7srp+Nbx1x1MDAxN722LHJHUVxcOlJFlIHJ70f1XHUwMDFigvj3g9eKOqqMXGJic1x0OVx1MDAxMYAwwenkgo7r3XRcInVcdTAwMDWOy1x1MDAxY/+mq59cdTAwMTVXXHUwMDEzRkH/h7vW7/ZcdTAwMDPdhn9BV/+Lm3HtNH/cXHUwMDA0/aHfmlxcXHUwMDEzXHUwMDA1jlx1MDAxZlx1MDAwZZxAdTm+ru11uyfR47h2NYZqvJYyzzh7aTDKlE+7Sz30puO7oVx1MDAxZVY4Ke1cdTAwMGacplx1MDAxN2VcdTAwMDdDt3Cw3Vx1MDAxYb+B/8ZtXG6cnrutX4E/7HaTXHUwMDE1+62XilMnQtdtPY86gVTiSXksXCKSZlx1MDAwYvf7/lhaMMFSXHUwMDEwXHUwMDFhn/fCdSVcdTAwMTfRuMa201xy3XhI9fM3YplJtWE4aDnPN0FcdTAwMGVcdTAwMDVcdTAwMDREXHUwMDAyVSuZnO96/o/sPd1+84fhOWN5UuO6osd50Fx1MDAwZlwia6PnhbrRobXW91Vht+tcdTAwMDZWu9t/SLxHdeLEe9JdQlwiVbrp9LzuY+pl6CesdL1cdTAwMWI9NEtdt52QXHUwMDA3NTyRp3AzOVx1MDAxZPVcdTAwMDdJqVxuXdWT8VPiIWuqpyhwuUH+nfVcdTAwMDNPwc3pns7TJ/2krVc0QFx1MDAxYtHxiZ9fTFjdfbiHYln8OOdHI+JcdTAwMDY7p39cdTAwMWRcXJE8Vlx1MDAwM7dcdTAwMTk9gylcdTAwMDVYJNKA5ShcdTAwMGZYlJDbXHRGXHUwMDEz9y1cdTAwMDJGYab8QzH6+obi4ccvJT+z0IVcdTAwMDJjSoWU0lx1MDAwMF6CsoWv4IWIXHUwMDAzgqGQfFx1MDAwZfj+PWle3NBpWj0haPLRP3KCq2/O/v5wq1x1MDAxYkFwLMOvS5Prfn4xV/t88+1gdLBcIkffmjfNg550V8+3V9a+pZ/y+nwnXGJcdTAwMTRcdTAwMTZK1ttcdTAwMTd8537v/HYoj25cdTAwMDdsXHUwMDEwyEZn0ylX78tf8ZvNaTKmVGRCp8zWZFx1MDAwNTA1j15cdJOKJLdcdTAwMDFcdTAwMDGAQUlcdTAwMDFcdTAwMTOYpfFKuE1ziIWM2lx1MDAxMkrBXHUwMDE5kILhRFx1MDAxZib4RYtlY1x1MDAxN1xuvyZcdTAwMWKrKsZcdTAwMWNDZWdcckClYFx1MDAxYVBcdTAwMTlcdTAwMTRcYlCM6zezXFy/XlpBOHNm9n60cnJ6fGCFkfPDXHLMXHUwMDE2XHUwMDE1pEpnWdSmanmyppxN7XmtVtc1mlVcdTAwMThcdTAwMGJp2qzOsnBZSzutW1WMarG6yqiUXHUwMDE0XFxJ1qAyg0FFXHUwMDA2g/ppTyvYUzTVnlx1MDAxMsEgkZKYYIpcdTAwMTnMlk5cZqrEXHUwMDFhqoLEV7y7QY3W/9o+bXX5Y9hiO6PDzdH9rfutmoHiXHUwMDAwXHUwMDEznpTwsjpg0Peybf870UKQbC6Y/P3fL8arXHUwMDEz8py5PteFrlx1MDAxM0Zr/V7Pi1RHXHUwMDBldSNy+jZygmjV81uef5N+9y9ea1x1MDAxOaUw1lfNYZhEkZZAZ6BcdTAwMDcvJzyu35r9xLvG6GZzn5+vXlxyn1x1MDAxZZdcdTAwMTlcdTAwMDVHbr9teuIysGOvtuCp456uaFx1MDAxNdJxnVx1MDAxY1x1MDAwMFSbkueS9MWss8ziVIJhUMRtwVx1MDAwMKBcdTAwMDRAXHUwMDA2IY79yLFcdTAwMDKjXHUwMDA2foGIjZRcdTAwMDFSXHUwMDFjg3HOMYndvYk+o59cbq1cdTAwMWHBIJhTgpjI6ShNI3K+/URxKZpHXGInYlx1MDAxZcVVzDCI0qXsTY58o1x1MDAxZrlWu1x1MDAxZljDgXXpR32LWoN+v1x1MDAxYn401yDxcGa4xlxmw5/lXHUwMDFhsztYjnVcdTAwMTR46bFcdTAwMTBcYjFdXGKwhEBQVsZcdTAwMWT0SmizXHUwMDBmXHUwMDA2Vl5haD3FQZpaJalWXVxuJUHRbJA8YomJw5RcdIl9XHUwMDA1MWeEQ4Bw3MN3jVwilGRcIu/rg2927lx1MDAxYld723s7K1s7o1x1MDAwYrftPDlbpIZ6w4vb3dtvq8Hx+vd9/+vx/ujrU69ZQ73b7mNcdTAwMDDXO731s6fW0cnGVUdcdTAwMDF6tYZ6T64v1n5cdTAwMWNuj1qjldWvjYuzXHUwMDE1dLa2/aZ6ZzHI7X0mR52rh/tcdTAwMTNvr3Pd2TpprH/vlG3u6XJvXHUwMDFkXHUwMDFj7O4+nZ5cdTAwMGXDzdX9TdnuXHUwMDBl61xunXCIKIhDXHUwMDE1b1xunZh7WYLYcCBsxWtcdTAwMDCVXHUwMDE0SKR4Skp9KLqThz1ANpdSXGIgXHUwMDA1JIRcdTAwMWFQj1xu9dQnsclcdTAwMTNcdTAwMWJKMKNKM8bGKbZpubKJSaNcdTAwMTJcbopcdTAwMTNx0bpoXHLVU1x1MDAxNKCCaOZozZqj2ti1XHUwMDA2gXvv9Yehda9YQPjl0r9cdTAwMWK6waPlu1x1MDAwZrpEOVx1MDAxM4pcdTAwMGI8uMGl7/gty1x1MDAxOVxmuo/PV1qaibSsvn/p62tbnnpx3vUwUr23L1Xhtm811Vx1MDAxNVa/rTiFboqq6s/AfflLlWnOXHUwMDEyOM3IXHUwMDFhhqq2S3/SXHUwMDEw9Tv4d/j6dHVCPz/1XHUwMDAwI/OCLFX6rlFcdTAwMWVcdTAwMTlcdTAwMTPKXGb1msFIstTr172Ed3tcdTAwMDVVXCJSxUa2KFwiJVGaNlx0w5wsM1x1MDAwNKTQ51x1MDAxNE8tISmqmDlcdTAwMDNcdTAwMDSY1Fx1MDAxZk74V1x1MDAxOVx1MDAwNYjUQTBC80SO51xmSHH2w9tpXHUwMDBlwL7DNiXYxP7BQfuoWkBcblx1MDAxM1xiPz4gRVKXQzTzhuV0XHUwMDA0S4l+fEeu17XFsKpElDBhXHUwMDEyXHUwMDBizqlcdTAwMTInXHUwMDA04jCl9caoVuPrUWv57mq5XHUwMDAznPNbvrZxddU+3DXG0WyunExcZlxiJlxcIKZkukRcdTAwMTNqXHUwMDBlcZlcdTAwMDW0XHUwMDA0XHUwMDEzVFx1MDAwMLSxUnxUICEoT0ysjjWiNIS4oLRcdTAwMTFWTFx1MDAxMFx1MDAxM4RcdTAwMDSFNLaRnyGuOZmgXHUwMDEyIEZcdTAwMTlcIiZNmOAgWSaonVx0pN5FTMZcdTAwMTcnwnU0Zlx1MDAxYtroX/pJvrG4XHUwMDExrlx1MDAxOURcIkuzZnewXG6LKdY2s5JVslx1MDAwMSBlWD+zVd6Bylx1MDAxNGSrKFx1MDAwNYolxyZcdTAwMDSzXFxcbkucrYJcdTAwMDDkiOF5XHUwMDEwXFxcdTAwMTjSeq/Q07uFiDptXHUwMDEwLD/wjevjle3dx9M2XHUwMDBlLuSip8FU1pJcdTAwMDVcbqC4oUVuXGbm0tZMmknIXHUwMDA04oSmXHUwMDEzYVx1MDAwNIA5VYBcdTAwMTGwheIsXHUwMDE4XGKs5I+apqkwlFx1MDAxNVSDXHUwMDBiMMTwN1ZcctMn3qHEytOUyDR9hdDUqVx1MDAwYspcdTAwMThgVPJfmMg2aPhcdTAwMGbscFx1MDAxZvBtjlrnXHUwMDFiK53t3dtm9cQwWVx1MDAwMVx1MDAxMe/j5jCk3Fx1MDAwMoCgokZcdTAwMTBcbkXKU16Pwlx1MDAwYpBcZkhcdTAwMDI5YEA1eFZ10+EyPlx1MDAwYmVcXEFuiD5gXt/WLZVUXHUwMDAxXHUwMDFmXCKo9EKpOfd6fVwi5ZcpZ0Iy5ZdcdMYhJOxcdTAwMDOcXCKzOJdwirCktpJcZoApXHUwMDA3lCZVvFYg0jDvL6QtuJTKXHUwMDFmXHUwMDEyQnc4hsB8PtFvpk5NPpHy5Vx1MDAwNVx1MDAwNtiUu1/gXHUwMDEyKS9cdTAwMWNBXHLuXHUwMDA1dImO3buhXHUwMDFiRtalP3yNXi+uNzSDj2S9ocK+VXGEwt7wvruMXHUwMDBlh8NlfFx1MDAxYp4quex/Xy41p5VdYWNaYiOhUo5SYoGVXHUwMDEyVybZgNPP/N+q6TlqpCWDwoTUxExiXHUwMDE2qUzdpszUO8xjKd7E8ZuQ+pIpW1x1MDAwNzrnXFxLMzXpt3AtTa7dlZBXmFVQ5IFwXHUwMDAwbFx0KKZCcSrFuWJcdTAwMDS9XHUwMDA0I6BNKFwiilxmQJaIcU2Mp3GOJXlcdTAwMGaTiVx1MDAxONen9XyDM0IphkxiYMpcdTAwMDJGfGoyXHUwMDFkR4BKRkGNcYpZzojXvI/cXHUwMDA2ok+d4Fx1MDAxYVx1MDAxYz/s3kR77buKcy6ciCqrVN7HXHUwMDE5WVx1MDAxNqnrlyGZfUtcdTAwMDZPXHUwMDAw0lRcdTAwMTV5dMQ15salslx1MDAwN5Jl3TXO1yjfXHUwMDA0IKm8XHUwMDAxXCJ1LowoN19Ts19glqxcdTAwMTJcdTAwMTSDXHUwMDEyZlNBXHTiXHUwMDEyKt8qXHUwMDExWFx1MDAxOVx1MDAwN1q4sGXypeRcdTAwMTRcdTAwMWQkwmZYXG6GlHehKlx1MDAxM4blR7BcdTAwMTL/+M00nYl/KC4nXGImxrmTfDh2Qj9cdTAwMDSCSGFcZi1idrAm017gvqZMvCRVvGRJaIo9fqR96e+7XHUwMDBmVuNQlVxcu81+z1xyrSc36NtcdTAwMWbtVMDpK5ZmcFxmk1dRy0BU4UHFIe5cIlx1MDAxZaTUmcJ34kjrXHUwMDA3QyA2cz0yzKUqZfOpXHUwMDEw6qA+XHUwMDE4U04pyK9cdTAwMTjQd6Gpmlx1MDAwMiOEfm1cdTAwMTj2Pmo1Ro2jweboqNU7PFx1MDAwMU/bg1x1MDAwZazGfFx1MDAxMIVwriWQ9TKfqeKtXHUwMDBmmIybzlOBQkadtKeOZFx1MDAxNFx1MDAwMDhniq1cdCApJCyxZKiA3tScjvLrXHSVWWBLXHUwMDEwKk6lrVxcXHUwMDBioDgu44zT2G6MXHUwMDAzrchcdTAwMTBoXHUwMDA1NqVSKuOtKFx1MDAxNlx1MDAxMW9eX/Wb6UtjXHUwMDAwXHUwMDA3XHSkRFx1MDAxNVx1MDAxMdNcdTAwMDRVflX3xCWEXGJiXHUwMDAwXHUwMDE2MtK61m97Qe9cdTAwMWZcdTAwMTFnncE2csm903tW44IqXlx1MDAxMLZcdTAwMTNcdTAwMThBJkvx5mftcLz52I3WRqdcdTAwMDP8sH+8R5/2XHUwMDAz7+v1oiDJvKBcbqJMQk1y95faV1TNyMt5hamyKIhcdTAwMTCYWHqxXGIrqGaRmVx1MDAxYnrUaNLD60d6do7OXHUwMDFhXHUwMDE3PSQ67kdnhbxhQVJcdPJFOUnYoDmzQsxGM7GzxlTCqlx1MDAxZi8ww2VcdTAwMTRz0TtacITC8ru/1I3YarvIvOCXcMx1fjB9O3xTJ3I4LVx1MDAxME1cdTAwMDEwS8RwK1jWymZzKZ6OyG048jazmjGgM8zL3JugmMw4XHUwMDE0XHUwMDA1LLhYWVx1MDAxNcVccnQoUW80oFRcdTAwMDeETLk1ucBBasmtIZ8re79pUS5cdTAwMTS2TFx1MDAxZVx1MDAxNVgyakuXkN+GJVeIKkjMoWRcdTAwMDSYdilLxsGya1iAYDp9nv/CXVVO299XXHUwMDFm8fG34/vGxfna1875195WpUUsXHUwMDE0KL9WzEXM61xyK8Cp4q5cdTAwMGaUXHUwMDE19Jn1TYePPnLAievLXHJZbUGHYq1mJWdUWOowRVx1MDAxY9LaXHUwMDAzYp6T5rpXxND08Vx1MDAwMclfZmnPaeN8TEJnM9hMXHUwMDAygChGklxumVx0Slxi075yTL1cdTAwMDYoJZGYXHUwMDAwgUBil4P58kp+M4VrXFxcdTAwMTODMFKIpMZcdTAwMDSw6Vx1MDAxOfWEMEZcdTAwMTUrq39fuTrmdfRcdTAwMDVcdTAwMWZcdTAwMWSLmD47M4PB5Gdn0t2pMslS7IFcdTAwMTWSJZigLs9cdTAwMWLJIZsqpYZcdTAwMTlcdTAwMTmHvmOBmXjY3JTnlb6JVEkwgZLDJvpt4FllTa+CLafMXHUwMDFjS8zvPVx1MDAxN8NcdTAwMTZcdTAwMDDJmFx1MDAwML9y29aTo/5cdTAwMWVuuD/27u7E6snRw1x1MDAxZbqg1Tx9RtlcIkyzJOT7+XdWtFx1MDAxN4qx6GlcdTAwMTKBOeBMSopcdTAwMDFlmJThXHUwMDA3dSeicCxcdTAwMTFSXHUwMDEyx7ThXHUwMDE2XGaVaELNXHUwMDE0xSyAJSiKZNxmWCOGYEpcdMvqQ1x1MDAxM0NRpER3WSFcZlwiIVx1MDAwMUvQkTlTX38vXHUwMDFkaJw5gZTrzTGlMfdk6lx1MDAwNlx1MDAwNlx1MDAwMmnnb1FTT9qBXHUwMDFidqxFWrJbQFiKWUSesFx1MDAxNHWu0nLd44iebJyu4PPTzeD+5jpC59Sw6ci4XHUwMDAzafZcdTAwMDIyaercuNlSXHUwMDFlm6TKrOZvlpZeJVx1MDAwYlx1MDAwNECdXHUwMDAxi43b4E5PXHUwMDAykVxmcEEpmoeezNh0SWliPFdItt6datM5qqTWbT6mJ5Tm7yprXvW5XHUwMDAygPrDlev9a/5trdk4ulx1MDAxMuvB9fJcdTAwMWVbL+X+g8w+1dSwjFx1MDAwNHGq15FwXHUwMDBlqJJcdTAwMGJotKWfu6FVXFzvRVx1MDAwNcdcYlLjMlk63XNcdTAwMTgvdaRU1m9NXHUwMDA1kVxmVdmqL2dNv4due9i1vLbljLc/tS79wF1cdTAwMWWbQrdlPXS8SNm4MLr0r4dRZoOsprq81X/QWY5cdTAwMWQn9P9cdTAwMWRZ7mjg6dtcdTAwMWXdaLxcdTAwMTHXgW9cdTAwMWRcZqNBP4wsT1/2uivX9uqa5SqkXHUwMDA0f0Zez+1cdTAwMGbHi7JU3VbT8a3gxVx1MDAwNHa80GpcdTAwMWN+5Fx1MDAwMlx1MDAxNzktJaJwhcs/Z0Sr0Fx0/oRcIme1O7zdulx1MDAwN97BWvNcdH/7elVGW5FMnlx1MDAwMjV9pVx1MDAwNkqbKGUlhIBcdTAwMDJTid+cdf6prVx1MDAxNCdVzjVXXHUwMDFlmHHiXHUwMDA3TP8+jdQz1oSR+tOmXHUwMDA05XS+Pe9cdTAwMTO7UmugKHSoLlgt5zHUXHUwMDE518+oeVx1MDAwMUj3MVx1MDAwNplCznhPZ4VcdTAwMDb1oi1VduNdd5+3fX7G3Efql/lW0L3jXHUwMDEwVFFcYlx1MDAwM//2+G6j3z29u7lcdTAwMDDiKlxcO9tcXC63syuazV6gRONcdTAwMWS86lx1MDAwYlx1MDAwNXzqXHUwMDAzpmd0lWNvYi84V5jYo1x1MDAxZUmeXGZZ11x1MDAxNlx0XHUwMDEwVGCUlL2qysD0NbaPxPNcXHAu7kS1jMm0M18mXHUwMDEzS+ht8lxiK/Utsmfcf9/d2tg8uYBdp7lJnvZcXLpcdTAwMTNcdTAwMWXDRVx1MDAwMYw5XHUwMDEzK/tly/dMlSSGwEji8ZOwXHUwMDFjY0hcIiHicX/X0EKVXGYsKlx1MDAwMZzv41Tz+dQlffUqXvfkXFzNa0XSXHUwMDExXGJmiEBcdTAwMTTCs/hcdTAwMGJcdTAwMTFTVbDeNotcYlxmy+czw+2bnafr1tnpwbrvXHUwMDFm7lx1MDAxZN6wq6OFMWrlPlx1MDAxMJFcZk58UDozU961oCDxYv5cdNnM+OJcdTAwMDZcdTAwMDVk+7S1I+lfW3crXGJcdTAwMWb2wP+j71xuzMo65lNcdTAwMWRcdTAwMWJcZqD+Rk+pz4RcdTAwMTWN5WLjiFx0W7ncQOfhk1x1MDAxNHFcdTAwMWWjXG69Z9IxwzaUUlKoX7jp49D5lGNIXHRcdTAwMDJcXJKEs/lcdTAwMTE5x3zOKNpcXDnHK/5jP8nTas01nqH6c582zrTl7TnGxSqkeG1yxlx1MDAwNFx1MDAxOHaINe3D8lx1MDAxOcauZd5JXHUwMDEwQjin+e8spl1cdTAwMWLD14xcdTAwMTEnbK5vmM1p4rafeoONldXTv1xcXHUwMDE0ON/5yV24t3VfzVx1MDAxNFx1MDAxMf3d9XlcdTAwMTBfK6NMb1xcXHUwMDBmfsnG9cVcbsL64I8vXHUwMDAymf5XolxyNae8mIUrp79cZnEtSPWWnkBvljyOYqeUXHUwMDE5psj4sWdpY6hYJuFYr9KBMXmZ6Df+XHUwMDE556r4tWeh+Fx1MDAwNFx1MDAxNcRcdTAwMTT3plx1MDAwNfsyXHUwMDAyhNR7Y/XP0nFcdTAwMDIgf9Ms3dlrNPdlTsm2Lv2XnUdCVdR23edPXHUwMDE4XrvWwPFaXHUwMDFmvr9cbpuaXHUwMDBmM4NcdTAwMWVk+Um+4+W6PSVu9seLSl1yXHUwMDA2g5NIvaWJ2lIy6LUyXHUwMDAztXTvuVx1MDAwZqt5XHUwMDA0/as9PrRcdTAwMTZcdTAwMTnrXHUwMDEwjVx1MDAxZXdsbX/+8fP/XHUwMDAwXHUwMDAzyIBFIn1F8PskAAAAAElFTkSuQmCC \ No newline at end of file diff --git a/assets/emissions_controller_general.png b/assets/emissions_controller_general.png new file mode 100644 index 0000000000000000000000000000000000000000..8d9868c8203d6fe5f09bd2e569fd0267541794cc GIT binary patch literal 181446 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

vr7a0CCBV||9qh$ zIi~ZI4O(&9{p}pq=;VX}K%FN(HJsh_*_P~PxFS~@k4)N@bn?H|YJBz1)IkACWQH=% z>oo+N_D10@DLWV6d@(o`$jbfI#?%HB8De;-d`j=5^)WGNeL}q8mAXl2KGMP~<#E43 z$_@!3X+`j4kyeI*DOQT7Qmud@{VM`bD>8VSj$j44KVvH8mEoi21Ozs_)z;nT(QvcY z5n2;G&Ow`$cLLAr>oj+r2$u8PyI<6jHk2Gzg|O^LkI8tuZgFvCYX)>$czjlzsY5@L zEzgz9CwX;hHIWoq=>%p{8J%it9!?XzE-rvJxiY$PI zpRa~37JfFZ(9&iQ2lGuN&l`ruVbUix>N?mdy0s#Xt9IjD-EVrZr|)Gw&fm1qsj#J~ zbQ7T2hqzzBJ>hX!^Slm5j@`q@&F8iXFqz0vH<$gl4y(C0ul!FaL9-SFcC~@I zpl1&qHd@K2#3T)E`N7L4VzKdQ#(P_m`61cl@2dEUD~i8m`_T`cNs!;xS!E=xSt;gD z!O0iGtoACh(_bq?Z6_R`=ilAtK^mDcDdR^;(f)p&0{Kxz45m*#jVMLa1JTNu|IWlQ zO`mk|$CGBtY%c%O#(k?-xSyTqnw!xl_C#zCK&npqS*atO2ZD2>fEJ<1-t5|yp?z%L zpU!!&HyERV%`y$$?IMxRAAKmHvFEfR*L_dnQF+{k+G_E%)MPB92TYaEYts0b%=Si6 zQSoLmjX&Cp5U#=@f>6(n9^G0Il6(BOvXO|M9Rnn&N=PYz4&6iJ{g;vN(tox<`sc6T zKCnnWo`J}XOl7xlxaa3l`s3*LBx3PlW{0!n_syqK9?t?>{tuR2kKVAT06o!=q~Bl8 zESO8Y8sTmb_z{;vNBh-Z$6e*0)=6h_Z#ZeO40?EctMSL)S~KbUXIDci>aId5Bqx

E&=AMK#wl4QXzSDeQ)MgR?qOz>H3 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

Olv${pjj(vuxM?!$vx_>^{aX1XLS! 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 R`k26= 14 days ago. /// Then it calculates voting power per each pool. -/// Filters all non-whitelisted pools, /// takes top X pools by voting power, where X is /// 'config.pools_per_outpost' * number of outposts, /// calculates total ASTRO emission amount for upcoming epoch, diff --git a/contracts/emissions_controller_outpost/README.md b/contracts/emissions_controller_outpost/README.md index bc3d804b..9bc6efd9 100644 --- a/contracts/emissions_controller_outpost/README.md +++ b/contracts/emissions_controller_outpost/README.md @@ -1,282 +1,35 @@ -# Emissions Controller - -The Emissions Controller allows vxASTRO holders to vote on changing `alloc_point`s in the Generator contract every 2 -weeks. Note that the Controller contract uses the word "pool" when referring to LP tokens. - -## InstantiateMsg - -Initialize the contract with the initial owner, the addresses of the xvASTRO, the Generator and the Factory contracts -and the max amount of pools that can receive ASTRO emissions at the same time. - -```json -{ - "owner": "wasm...", - "escrow_addr": "wasm...", - "generator_addr": "wasm...", - "factory_addr": "wasm...", - "pools_limit": 5 -} -``` - -## ExecuteMsg - -### `kick_blacklisted_voters` - -Remove votes of voters that are blacklisted. - -```json -{ - "kick_blacklisted_voters": { - "blacklisted_voters": [ - "wasm...", - "wasm..." - ] - } -} -``` - -### `update_config` - -Sets various configuration parameters. Any of them can be omitted. - -```json -{ - "update_config": { - "blacklisted_voters_limit": 22, - "main_pool": "wasm...", - "main_pool_min_alloc": "0.3" - } -} -``` - -### `vote` - -Vote on pools that will start to get an ASTRO distribution in the next period. For example, assume an address has voting -power `100`. Then, following the example below, pools will receive voting power 10, 50, 40 respectively. Note that all -values are scaled so they sum to 10,000. - -```json -{ - "vote": { - "votes": [ - [ - "wasm...", - 1000 - ], - [ - "wasm...", - 5000 - ], - [ - "wasm...", - 4000 - ] - ] - } -} -``` - -### `tune_pools` - -Calculate voting power for all pools and apply new allocation points in generator contract. - -```json -{ - "tune_pools": {} -} -``` - -### `change_pool_limit` - -Only contract owner can call this function. Change max number of pools that can receive an ASTRO allocation. - -```json -{ - "change_pool_limit": { - "limit": 6 - } -} -``` - -### `propose_new_owner` - -Create a request to change contract ownership. The validity period of the offer is set by the `expires_in` variable. -Only the current contract owner can execute this method. - -```json -{ - "propose_new_owner": { - "owner": "wasm...", - "expires_in": 1234567 - } -} -``` - -### `drop_ownership_proposal` - -Delete the contract ownership transfer proposal. Only the current contract owner can execute this method. - -```json -{ - "drop_ownership_proposal": {} -} -``` - -### `claim_ownership` - -Used to claim contract ownership. Only the newly proposed contract owner can execute this method. - -```json -{ - "claim_ownership": {} -} -``` - -### `update_whitelist` - -Adds or removes lp tokens which are eligible to receive votes. - -```json -{ - "update_whitelist": { - "add": [ - "wasm...", - "wasm..." - ], - "remove": [ - "wasm...", - "wasm..." - ] - } -} -``` - -## QueryMsg - -All query messages are described below. A custom struct is defined for each query response. - -### `user_info` - -Request: - -```json -{ - "user_info": { - "user": "wasm..." - } -} -``` - -Returns last user's voting parameters. - -```json -{ - "user_info_response": { - "vote_ts": 1234567, - "voting_power": 100, - "slope": 15.45, - "lock_end": 10, - "votes": [ - [ - "wasm...", - 1000 - ], - [ - "wasm...", - 5000 - ], - [ - "wasm...", - 4000 - ] - ] - } -} -``` - -### `tune_info` - -Returns last tune information. - -```json -{ - "tune_info_response": { - "tune_ts": 1234567, - "pool_alloc_points": [ - [ - "wasm...", - 4000 - ], - [ - "wasm...", - 6000 - ] - ] - } -} -``` - -### `pool_info` - -Returns pool voting parameters at the current block period. - -Request: - -```json -{ - "pool_info": { - "pool_addr": "wasm..." - } -} -``` - -Response: - -```json -{ - "voted_pool_info_response": { - "vxastro_amount": 1000, - "slope": 10.2 - } -} -``` - -### `pool_info_at_period` - -Returns pool voting parameters at specified period. - -Request: - -```json -{ - "pool_info_at_period": { - "pool_addr": "wasm...", - "period": 10 - } -} -``` - -Response: - -```json -{ - "voted_pool_info_response": { - "vxastro_amount": 1000, - "slope": 10.2 - } -} -``` - -### `config` - -Returns the contract's config. - -```json -{ - "owner": "wasm...", - "escrow_addr": "wasm...", - "generator_addr": "wasm...", - "factory_addr": "wasm...", - "pools_limit": 5 -} -``` +# Emissions Controller (Outpost) + +The Emissions Controller Outpost is a lightweight satellite for the main Emissions Controller located on the Hub. +For the vxASTRO staker perspective, this contract has the same API as the main Emissions Controller. +However, Outpost can't perform fine-grained sanity checks for voted LP tokens. +Same restrictions as on the Hub are applied, like voting every 10 days for up to 5 pools at once. +The contract composes a special internal IBC message to the Hub with the user's vote. +If sanity checks passed on the Hub, the vote is accepted. +In case of IBC failure or timeouts, the user can try to vote again. + +## Emissions Setting + +This endpoint is meant to be called during IBC hook processing. +It might be a gas extensive transaction, thus Astroport devs must settle it with supporting relayer operators prior to +the vxASTRO launch. +The contract has a permissionless endpoint which allows setting ASTRO emissions in the incentives contract for the next +epoch. +It filters out invalid LP tokens, checks that schedules have >= 1 uASTRO per second, sets reward schedules, and +IBC sends leftover funds back to the Hub. +Contract call must supply the exact ASTRO amount contained in the schedules. + +## Permissioned Emissions Setting + +In case the chain (for example, Sei) doesn't support IBC hooks, emissions message from the Hub might end up with ASTRO +bridged to the chain but not distributed. +In that case, the contract owner can call this endpoint along with the emissions voting outcome (schedules) for this +specific chain. +Same as in permissionless endpoint, this endpoint performs sanity checks, sets reward schedules, and IBC sends leftover +funds back to the Hub. + +## Governance voting + +vxASTRO stakers are allowed to vote on registered governance proposals from the Hub. +Proposal registration sets proposal start time so contract knows user's voting power at that time. +Only Hub's Emissions Contrller can initiate proposal registration via IBC messages. \ No newline at end of file diff --git a/contracts/voting_escrow/README.md b/contracts/voting_escrow/README.md index 19a80ffc..63187faa 100644 --- a/contracts/voting_escrow/README.md +++ b/contracts/voting_escrow/README.md @@ -1,527 +1,21 @@ -# Vote Escrowed Staked ASTRO +# Vote Escrowed Staked ASTRO (vxASTRO) -[//]: # (TODO: Rewrite README) +The **vxASTRO** contract enables **xASTRO** token holders to stake their tokens and participate in Emissions Voting. -[//]: # (The vxASTRO contract allows xASTRO token holders to stake their tokens in order to boost their governance power as well as the amount of ASTRO they can get from Generator emissions. Voting power is boosted according to how long someone locks their xASTRO for.) +## Features -[//]: # () +- **Indefinite Locking:** Tokens are locked indefinitely, with an option for users to request unlocking at any time. +- **Unlocking Period:** The unlocking process takes 14 days. +- **Voting Power:** vxASTRO voting power is always equivalent to the underlying xASTRO share. +- **Governance:** vxASTRO holders retain their voting power in Astroport Governance while their xASTRO is locked. +- **Non-Transferable:** vxASTRO tokens are not transferable. +- **CW20 Queries:** The contract implements several CW20 queries to function like a CW20 token for query purposes, + useful for wallet views. +- **Emissions Controller Interaction:** The contract interacts with the Emissions Controller whenever a user's balance + changes, updating the user's contribution in Emissions Voting. -[//]: # (Maximum lock time is 2 years, which gives the maximum possible boost of 2.5. For example, if a token holder locks 100 xASTRO for 2 years, they) +## Usage -[//]: # (get 250 vxASTRO. Their vxASTRO balance then goes down every week for the next 2 years (unless they relock) until it reaches zero.) +vxASTRO allows holders to influence liquidity flows and participate in the governance of the Astroport. -[//]: # () - -[//]: # (## InstantiateMsg) - -[//]: # () - -[//]: # (Initialize the contract with the initial owner and the address of the xASTRO token.) - -[//]: # () - -[//]: # (```json) - -[//]: # ({) - -[//]: # ( "owner": "terra...",) - -[//]: # ( "deposit_token_addr": "terra...") - -[//]: # (}) - -[//]: # (```) - -[//]: # () - -[//]: # (## ExecuteMsg) - -[//]: # () - -[//]: # (### `receive`) - -[//]: # () - -[//]: # (Create new lock/vxASTRO position, deposit more xASTRO in the user's vxASTRO position or deposit on behalf of another address.) - -[//]: # () - -[//]: # (```json) - -[//]: # ({) - -[//]: # ( "receive": {) - -[//]: # ( "sender": "terra...",) - -[//]: # ( "amount": "123",) - -[//]: # ( "msg": "") - -[//]: # ( }) - -[//]: # (}) - -[//]: # (```) - -[//]: # () - -[//]: # (### `extend_lock_time`) - -[//]: # () - -[//]: # (An example of extending the lock time for a vxASTRO position by 1 week.) - -[//]: # () - -[//]: # (```json) - -[//]: # ({) - -[//]: # ( "extend_lock_time": {) - -[//]: # ( "time": 604800) - -[//]: # ( }) - -[//]: # (}) - -[//]: # (```) - -[//]: # () - -[//]: # (### `withdraw`) - -[//]: # () - -[//]: # (Withdraw the whole amount of xASTRO if the lock for a vxASTRO position expired.) - -[//]: # () - -[//]: # (```json) - -[//]: # ({) - -[//]: # ( "withdraw": {}) - -[//]: # (}) - -[//]: # (```) - -[//]: # () - -[//]: # (### `propose_new_owner`) - -[//]: # () - -[//]: # (Create a request to change contract ownership. The validity period of the offer is set by the `expires_in` variable.) - -[//]: # (Only the current contract owner can execute this method.) - -[//]: # () - -[//]: # (```json) - -[//]: # ({) - -[//]: # ( "propose_new_owner": {) - -[//]: # ( "owner": "terra...",) - -[//]: # ( "expires_in": 1234567) - -[//]: # ( }) - -[//]: # (}) - -[//]: # (```) - -[//]: # () - -[//]: # (### `drop_ownership_proposal`) - -[//]: # () - -[//]: # (Delete the contract ownership transfer proposal. Only the current contract owner can execute this method.) - -[//]: # () - -[//]: # (```json) - -[//]: # ({) - -[//]: # ( "drop_ownership_proposal": {}) - -[//]: # (}) - -[//]: # (```) - -[//]: # () - -[//]: # (### `claim_ownership`) - -[//]: # () - -[//]: # (Used to claim contract ownership. Only the newly proposed contract owner can execute this method.) - -[//]: # () - -[//]: # (```json) - -[//]: # ({) - -[//]: # ( "claim_ownership": {}) - -[//]: # (}) - -[//]: # (```) - -[//]: # () - -[//]: # (### `update_blacklist`) - -[//]: # () - -[//]: # (Updates the list of addresses that are prohibited from staking in vxASTRO or if they are already staked, from voting with their vxASTRO in the Astral Assembly. Only the contract owner can execute this method.) - -[//]: # () - -[//]: # (```json) - -[//]: # ({) - -[//]: # ( "append_addrs": ["terra...", "terra...", "terra..."],) - -[//]: # ( "remove_addrs": ["terra...", "terra..."]) - -[//]: # (}) - -[//]: # (```) - -[//]: # () - -[//]: # (### `update_config`) - -[//]: # () - -[//]: # (Updates contract parameters.) - -[//]: # () - -[//]: # (```json) - -[//]: # ({) - -[//]: # ( "new_guardian": "terra...") - -[//]: # (}) - -[//]: # (```) - -[//]: # () - -[//]: # (## QueryMsg) - -[//]: # () - -[//]: # (All query messages are described below. A custom struct is defined for each query response.) - -[//]: # () - -[//]: # (### `total_voting_power`) - -[//]: # () - -[//]: # (Returns the total supply of vxASTRO at the current block.) - -[//]: # () - -[//]: # (```json) - -[//]: # ({) - -[//]: # ( "voting_power_response": {) - -[//]: # ( "voting_power": 100) - -[//]: # ( }) - -[//]: # (}) - -[//]: # (```) - -[//]: # () - -[//]: # (### `user_voting_power`) - -[//]: # () - -[//]: # (Returns a user's vxASTRO balance at the current block.) - -[//]: # () - -[//]: # (Request:) - -[//]: # () - -[//]: # (```json) - -[//]: # ({) - -[//]: # ( "user_voting_power": {) - -[//]: # ( "user": "terra...") - -[//]: # ( }) - -[//]: # (}) - -[//]: # (```) - -[//]: # () - -[//]: # (Response:) - -[//]: # () - -[//]: # (```json) - -[//]: # ({) - -[//]: # ( "voting_power_response": {) - -[//]: # ( "voting_power": 10) - -[//]: # ( }) - -[//]: # (}) - -[//]: # (```) - -[//]: # () - -[//]: # (### `total_voting_power_at`) - -[//]: # () - -[//]: # (Returns the total vxASTRO supply at a specific timestamp (in seconds).) - -[//]: # () - -[//]: # (Request:) - -[//]: # () - -[//]: # (```json) - -[//]: # ({) - -[//]: # ( "total_voting_power_at": {) - -[//]: # ( "time": 1234567) - -[//]: # ( }) - -[//]: # (}) - -[//]: # (```) - -[//]: # () - -[//]: # (Response:) - -[//]: # () - -[//]: # (```json) - -[//]: # ({) - -[//]: # ( "voting_power_response": {) - -[//]: # ( "voting_power": 10) - -[//]: # ( }) - -[//]: # (}) - -[//]: # (```) - -[//]: # () - -[//]: # (### `user_voting_power_at`) - -[//]: # () - -[//]: # (Returns the user's vxASTRO balance at a specific timestamp (in seconds).) - -[//]: # () - -[//]: # (Request:) - -[//]: # () - -[//]: # (```json) - -[//]: # ({) - -[//]: # ( "user_voting_power_at": {) - -[//]: # ( "user": "terra...",) - -[//]: # ( "time": 1234567) - -[//]: # ( }) - -[//]: # (}) - -[//]: # (```) - -[//]: # () - -[//]: # (Response:) - -[//]: # () - -[//]: # (```json) - -[//]: # ({) - -[//]: # ( "voting_power_response": {) - -[//]: # ( "voting_power": 10) - -[//]: # ( }) - -[//]: # (}) - -[//]: # (```) - -[//]: # () - -[//]: # (### `lock_info`) - -[//]: # () - -[//]: # (Returns the information about a user's vxASTRO position.) - -[//]: # () - -[//]: # (Request:) - -[//]: # () - -[//]: # (```json) - -[//]: # ({) - -[//]: # ( "lock_info": {) - -[//]: # ( "user": "terra...") - -[//]: # ( }) - -[//]: # (}) - -[//]: # (```) - -[//]: # () - -[//]: # (Response:) - -[//]: # () - -[//]: # (```json) - -[//]: # ({) - -[//]: # ( "lock_info_response": {) - -[//]: # ( "amount": 10,) - -[//]: # ( "coefficient": 2.5,) - -[//]: # ( "start": 2600,) - -[//]: # ( "end": 2704) - -[//]: # ( }) - -[//]: # (}) - -[//]: # (```) - -[//]: # () - -[//]: # (### `config`) - -[//]: # () - -[//]: # (Returns the contract's config.) - -[//]: # () - -[//]: # (```json) - -[//]: # ({) - -[//]: # ( "config_response": {) - -[//]: # ( "owner": "terra...",) - -[//]: # ( "deposit_token_addr" : "terra...") - -[//]: # ( }) - -[//]: # (}) - -[//]: # (```) - -[//]: # () - -[//]: # (### `blacklisted_voters`) - -[//]: # () - -[//]: # (Returns blacklisted voters.) - -[//]: # () - -[//]: # (```json) - -[//]: # ({) - -[//]: # ( "blacklisted_voters": {) - -[//]: # ( "start_after": "terra...",) - -[//]: # ( "limit": 5) - -[//]: # ( }) - -[//]: # (}) - -[//]: # (```) - -[//]: # () - -[//]: # (### `check_voters_are_blacklisted`) - -[//]: # () - -[//]: # (Checks if specified addresses are blacklisted) - -[//]: # () - -[//]: # (```json) - -[//]: # ({) - -[//]: # ( "check_voters_are_blacklisted": {) - -[//]: # ( "voters": ["terra...", "terra..."]) - -[//]: # ( }) - -[//]: # (}) - -[//]: # (```) \ No newline at end of file +![vxASTRO diagram](../../assets/vxastro_flow.png) From 4e1540c4046be62f784fbec5367dec409b3b4b13 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Thu, 20 Jun 2024 13:33:29 +0300 Subject: [PATCH 20/32] publish rc crates --- Cargo.lock | 115 ++++++++++++++---- Cargo.toml | 2 +- contracts/assembly/Cargo.toml | 6 +- contracts/builder_unlock/Cargo.toml | 2 +- contracts/emissions_controller/Cargo.toml | 10 +- contracts/emissions_controller/src/execute.rs | 9 +- .../emissions_controller_outpost/Cargo.toml | 8 +- contracts/voting_escrow/Cargo.toml | 4 +- packages/astroport-governance/Cargo.toml | 2 +- schemas/astro-assembly/astro-assembly.json | 2 +- ...stroport-emissions-controller-outpost.json | 2 +- .../astroport-emissions-controller.json | 2 +- .../astroport-voting-escrow.json | 2 +- scripts/publish_crates.sh | 2 +- 14 files changed, 117 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dc333ac8..60378ee1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,11 +21,11 @@ checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" [[package]] name = "astro-assembly" -version = "3.0.0" +version = "3.0.0-rc.1" dependencies = [ "anyhow", - "astroport 4.0.3", - "astroport-governance 4.0.0", + "astroport 5.1.0-vxastro-rc.1", + "astroport-governance 4.0.0-rc.1", "astroport-staking 2.1.0 (git+https://github.com/astroport-fi/hidden_astroport_core)", "astroport-tokenfactory-tracker 1.0.0 (git+https://github.com/astroport-fi/hidden_astroport_core)", "astroport-voting-escrow", @@ -94,9 +94,27 @@ dependencies = [ [[package]] name = "astroport" version = "4.0.3" -source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#7610b906facf2f2cc5a2bd7f5198a3533b63ee2e" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5ec8dd4298b362361b0e118107a060e5e58501a68273b3257059c96b723b57c" +dependencies = [ + "astroport-circular-buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cosmwasm-schema", + "cosmwasm-std", + "cw-asset", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw20 1.1.2", + "itertools 0.12.1", + "uint", +] + +[[package]] +name = "astroport" +version = "5.1.0-vxastro-rc.1" +source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#d2990d817b840cc5071cdb65db9a9e45db52daf5" dependencies = [ "astroport-circular-buffer 0.2.0 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "cosmos-sdk-proto 0.19.0", "cosmwasm-schema", "cosmwasm-std", "cw-asset", @@ -104,6 +122,7 @@ dependencies = [ "cw-utils 1.0.3", "cw20 1.1.2", "itertools 0.12.1", + "prost 0.11.9", "uint", ] @@ -122,7 +141,7 @@ dependencies = [ [[package]] name = "astroport-circular-buffer" version = "0.2.0" -source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#7610b906facf2f2cc5a2bd7f5198a3533b63ee2e" +source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#d2990d817b840cc5071cdb65db9a9e45db52daf5" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -143,13 +162,13 @@ dependencies = [ [[package]] name = "astroport-emissions-controller" -version = "1.0.0" +version = "1.0.0-rc.1" dependencies = [ "anyhow", "astro-assembly", - "astroport 4.0.3", + "astroport 5.1.0-vxastro-rc.1", "astroport-factory", - "astroport-governance 4.0.0", + "astroport-governance 4.0.0-rc.1", "astroport-incentives", "astroport-pair", "astroport-staking 2.1.0 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", @@ -173,11 +192,11 @@ dependencies = [ [[package]] name = "astroport-emissions-controller-outpost" -version = "1.0.0" +version = "1.0.0-rc.1" dependencies = [ - "astroport 4.0.3", + "astroport 5.1.0-vxastro-rc.1", "astroport-factory", - "astroport-governance 4.0.0", + "astroport-governance 4.0.0-rc.1", "astroport-incentives", "astroport-pair", "astroport-voting-escrow", @@ -226,9 +245,22 @@ dependencies = [ [[package]] name = "astroport-governance" -version = "4.0.0" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f16e7f7084051683f1012666b082895c4a5345f2fc38961ce4a67de46730c6bf" dependencies = [ - "astroport 4.0.3", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw20 1.1.2", + "thiserror", +] + +[[package]] +name = "astroport-governance" +version = "4.0.0-rc.1" +dependencies = [ + "astroport 5.1.0-vxastro-rc.1", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", @@ -248,10 +280,10 @@ dependencies = [ [[package]] name = "astroport-incentives" -version = "1.2.0" -source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#ada68324a4cca143008646d3c81baa715cba12d2" +version = "1.2.0-vxastro-rc.1" +source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#d2990d817b840cc5071cdb65db9a9e45db52daf5" dependencies = [ - "astroport 4.0.3", + "astroport 5.1.0-vxastro-rc.1", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", @@ -283,7 +315,7 @@ dependencies = [ [[package]] name = "astroport-staking" version = "2.1.0" -source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#ada68324a4cca143008646d3c81baa715cba12d2" +source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#d2990d817b840cc5071cdb65db9a9e45db52daf5" dependencies = [ "astroport 4.0.3", "cosmwasm-std", @@ -311,7 +343,7 @@ dependencies = [ [[package]] name = "astroport-tokenfactory-tracker" version = "1.0.0" -source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#ada68324a4cca143008646d3c81baa715cba12d2" +source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#d2990d817b840cc5071cdb65db9a9e45db52daf5" dependencies = [ "astroport 4.0.3", "cosmwasm-schema", @@ -336,10 +368,10 @@ dependencies = [ [[package]] name = "astroport-voting-escrow" -version = "1.0.0" +version = "1.0.0-rc.1" dependencies = [ - "astroport 4.0.3", - "astroport-governance 4.0.0", + "astroport 5.1.0-vxastro-rc.1", + "astroport-governance 4.0.0-rc.1", "cosmwasm-schema", "cosmwasm-std", "cw-multi-test 1.1.0", @@ -415,8 +447,8 @@ checksum = "56953345e39537a3e18bdaeba4cb0c58a78c1f61f361dc0fa7c5c7340ae87c5f" name = "builder-unlock" version = "3.0.0" dependencies = [ - "astroport 4.0.3", - "astroport-governance 4.0.0", + "astroport 5.1.0-vxastro-rc.1", + "astroport-governance 3.0.0", "cosmwasm-schema", "cosmwasm-std", "cw-multi-test 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -462,6 +494,17 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "cosmos-sdk-proto" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73c9d2043a9e617b0d602fbc0a0ecd621568edbf3a9774890a6d562389bd8e1c" +dependencies = [ + "prost 0.11.9", + "prost-types 0.11.9", + "tendermint-proto 0.32.2", +] + [[package]] name = "cosmos-sdk-proto" version = "0.20.0" @@ -470,7 +513,7 @@ checksum = "32560304ab4c365791fd307282f76637213d8083c1a98490c35159cd67852237" dependencies = [ "prost 0.12.4", "prost-types 0.12.4", - "tendermint-proto", + "tendermint-proto 0.34.1", ] [[package]] @@ -1102,7 +1145,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26fb513aae0c82b3185228e96664d8312e79c3aa763f6ebdc70cf4b8d513d533" dependencies = [ "bech32 0.9.1", - "cosmos-sdk-proto", + "cosmos-sdk-proto 0.20.0", "cosmwasm-schema", "cosmwasm-std", "prost 0.12.4", @@ -1113,7 +1156,7 @@ dependencies = [ "serde-json-wasm 1.0.1", "serde_json", "speedate", - "tendermint-proto", + "tendermint-proto 0.34.1", "thiserror", ] @@ -1255,7 +1298,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9554e3ab233f0a932403704f1a1d08c30d5ccd931adfdfa1e8b5a19b52c1d55a" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.61", @@ -1595,6 +1638,24 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tendermint-proto" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cec054567d16d85e8c3f6a3139963d1a66d9d3051ed545d31562550e9bcc3d" +dependencies = [ + "bytes", + "flex-error", + "num-derive", + "num-traits", + "prost 0.11.9", + "prost-types 0.11.9", + "serde", + "serde_bytes", + "subtle-encoding", + "time", +] + [[package]] name = "tendermint-proto" version = "0.34.1" diff --git a/Cargo.toml b/Cargo.toml index 2a32324a..ab09987e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ thiserror = "1.0" itertools = "0.12" cosmwasm-schema = "1.5" cw-utils = "1" -astroport = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "4" } +astroport = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "5.1.0-vxastro-rc.1" } [profile.release] opt-level = "z" diff --git a/contracts/assembly/Cargo.toml b/contracts/assembly/Cargo.toml index 01a89679..cb0c4b1d 100644 --- a/contracts/assembly/Cargo.toml +++ b/contracts/assembly/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astro-assembly" -version = "3.0.0" +version = "3.0.0-rc.1" authors = ["Astroport"] edition = "2021" description = "Astroport DAO Contract" @@ -23,7 +23,7 @@ cw-storage-plus.workspace = true thiserror.workspace = true cosmwasm-schema.workspace = true cw-utils.workspace = true -astroport-governance = { path = "../../packages/astroport-governance", version = "4" } +astroport-governance = { path = "../../packages/astroport-governance", version = "4.0.0-rc.1" } astroport.workspace = true ibc-controller-package = "1.0.0" @@ -32,7 +32,7 @@ cw-multi-test = { git = "https://github.com/astroport-fi/cw-multi-test", branch osmosis-std = "0.21" astroport-staking = { git = "https://github.com/astroport-fi/hidden_astroport_core", version = "2" } astroport-tokenfactory-tracker = { git = "https://github.com/astroport-fi/hidden_astroport_core", version = "1" } -astroport-voting-escrow = { path = "../voting_escrow", version = "1.0", features = ["library"] } +astroport-voting-escrow = { path = "../voting_escrow", version = "1.0.0-rc.1", features = ["library"] } builder-unlock = { path = "../builder_unlock", version = "3" } anyhow = "1" test-case = "3.3.1" \ No newline at end of file diff --git a/contracts/builder_unlock/Cargo.toml b/contracts/builder_unlock/Cargo.toml index 8d6a63de..7649a6a1 100644 --- a/contracts/builder_unlock/Cargo.toml +++ b/contracts/builder_unlock/Cargo.toml @@ -22,7 +22,7 @@ cosmwasm-std.workspace = true cw-storage-plus.workspace = true cosmwasm-schema.workspace = true thiserror.workspace = true -astroport-governance = { path = "../../packages/astroport-governance", version = "4" } +astroport-governance = "3" astroport.workspace = true [dev-dependencies] diff --git a/contracts/emissions_controller/Cargo.toml b/contracts/emissions_controller/Cargo.toml index dcfe0ec2..f81eb9aa 100644 --- a/contracts/emissions_controller/Cargo.toml +++ b/contracts/emissions_controller/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astroport-emissions-controller" -version = "1.0.0" +version = "1.0.0-rc.1" authors = ["Astroport"] edition = "2021" description = "Astroport vxASTRO Emissions Voting Contract" @@ -23,20 +23,20 @@ cw-storage-plus.workspace = true cosmwasm-schema.workspace = true thiserror.workspace = true itertools.workspace = true -astroport-governance = { path = "../../packages/astroport-governance", version = "4" } +astroport-governance = { path = "../../packages/astroport-governance", version = "4.0.0-rc.1" } astroport.workspace = true neutron-sdk = "0.10.0" serde_json = "1" [dev-dependencies] cw-multi-test = { git = "https://github.com/astroport-fi/cw-multi-test", branch = "feat/bank_with_send_hooks_1_0", features = ["cosmwasm_1_1"] } -astroport-voting-escrow = { path = "../voting_escrow", version = "1", features = ["library"] } -astro-assembly = { path = "../assembly", version = "3", features = ["library"] } +astroport-voting-escrow = { path = "../voting_escrow", version = "1.0.0-rc.1", features = ["library"] } +astro-assembly = { path = "../assembly", version = "3.0.0-rc.1", features = ["library"] } builder-unlock = { path = "../builder_unlock", version = "3", features = ["library"] } astroport-factory = { version = "1.7", features = ["library"] } astroport-pair = { version = "1.5", features = ["library"] } cw20-base = { version = "1", features = ["library"] } -astroport-incentives = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "1.2", features = ["library"] } +astroport-incentives = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "1.2.0-vxastro-rc.1", features = ["library"] } astroport-staking = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "2.1" } astroport-tokenfactory-tracker = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "1" } osmosis-std = "0.21.0" diff --git a/contracts/emissions_controller/src/execute.rs b/contracts/emissions_controller/src/execute.rs index 58fa4379..cc396fea 100644 --- a/contracts/emissions_controller/src/execute.rs +++ b/contracts/emissions_controller/src/execute.rs @@ -448,7 +448,7 @@ pub fn handle_vote( } // Cancel previous user votes. Filter non-whitelisted pools. - let cache = user_info + let mut cache = user_info .votes .into_iter() .filter(|(pool, _)| whitelist.contains(pool)) @@ -473,7 +473,7 @@ pub fn handle_vote( let pool_dedicated_vp = voting_power.multiply_ratio(weight.numerator(), weight.denominator()); - let pool_info = if let Some(pool_info) = cache.get(pool).cloned() { + let pool_info = if let Some(pool_info) = cache.remove(pool) { pool_info } else { VOTED_POOLS.load(deps.storage, pool)? @@ -487,6 +487,11 @@ pub fn handle_vote( ) })?; + // Save pool changes which are not part of the new votes + cache.into_iter().try_for_each(|(pool, pool_info)| { + VOTED_POOLS.save(deps.storage, &pool, &pool_info, block_ts) + })?; + USER_INFO.save( deps.storage, voter, diff --git a/contracts/emissions_controller_outpost/Cargo.toml b/contracts/emissions_controller_outpost/Cargo.toml index 660837b1..503cf716 100644 --- a/contracts/emissions_controller_outpost/Cargo.toml +++ b/contracts/emissions_controller_outpost/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astroport-emissions-controller-outpost" -version = "1.0.0" +version = "1.0.0-rc.1" authors = ["Astroport"] edition = "2021" description = "Astroport vxASTRO Emissions Voting Contract. Outpost version" @@ -23,15 +23,15 @@ cw-storage-plus.workspace = true cosmwasm-schema.workspace = true thiserror.workspace = true itertools.workspace = true -astroport-governance = { path = "../../packages/astroport-governance", version = "4" } +astroport-governance = { path = "../../packages/astroport-governance", version = "4.0.0-rc.1" } astroport.workspace = true serde_json = "1" [dev-dependencies] cw-multi-test = "1" -astroport-voting-escrow = { path = "../voting_escrow", version = "1", features = ["library"] } +astroport-voting-escrow = { path = "../voting_escrow", version = "1.0.0-rc.1", features = ["library"] } astroport-factory = { version = "1.7", features = ["library"] } astroport-pair = { version = "1.5", features = ["library"] } cw20-base = { version = "1", features = ["library"] } -astroport-incentives = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "1.2" } +astroport-incentives = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "1.2.0-vxastro-rc.1" } derivative = "2.2" diff --git a/contracts/voting_escrow/Cargo.toml b/contracts/voting_escrow/Cargo.toml index 1c11b47e..65437e7f 100644 --- a/contracts/voting_escrow/Cargo.toml +++ b/contracts/voting_escrow/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astroport-voting-escrow" -version = "1.0.0" +version = "1.0.0-rc.1" authors = ["Astroport"] edition = "2021" description = "Astroport Vote Escrowed xASTRO (vxASTRO)" @@ -24,7 +24,7 @@ cosmwasm-std.workspace = true cw-storage-plus.workspace = true thiserror.workspace = true cosmwasm-schema.workspace = true -astroport-governance = { path = "../../packages/astroport-governance", version = "4" } +astroport-governance = { path = "../../packages/astroport-governance", version = "4.0.0-rc.1" } astroport.workspace = true [dev-dependencies] diff --git a/packages/astroport-governance/Cargo.toml b/packages/astroport-governance/Cargo.toml index 29e6e811..eb9dc05d 100644 --- a/packages/astroport-governance/Cargo.toml +++ b/packages/astroport-governance/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astroport-governance" -version = "4.0.0" +version = "4.0.0-rc.1" authors = ["Astroport"] edition = "2021" repository = "https://github.com/astroport-fi/astroport-governance" diff --git a/schemas/astro-assembly/astro-assembly.json b/schemas/astro-assembly/astro-assembly.json index 39f2813e..4604b9d6 100644 --- a/schemas/astro-assembly/astro-assembly.json +++ b/schemas/astro-assembly/astro-assembly.json @@ -1,6 +1,6 @@ { "contract_name": "astro-assembly", - "contract_version": "3.0.0", + "contract_version": "3.0.0-rc.1", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json b/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json index 6d0423f0..f274cd41 100644 --- a/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json +++ b/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json @@ -1,6 +1,6 @@ { "contract_name": "astroport-emissions-controller-outpost", - "contract_version": "1.0.0", + "contract_version": "1.0.0-rc.1", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/schemas/astroport-emissions-controller/astroport-emissions-controller.json b/schemas/astroport-emissions-controller/astroport-emissions-controller.json index bf1503c7..84d66759 100644 --- a/schemas/astroport-emissions-controller/astroport-emissions-controller.json +++ b/schemas/astroport-emissions-controller/astroport-emissions-controller.json @@ -1,6 +1,6 @@ { "contract_name": "astroport-emissions-controller", - "contract_version": "1.0.0", + "contract_version": "1.0.0-rc.1", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/schemas/astroport-voting-escrow/astroport-voting-escrow.json b/schemas/astroport-voting-escrow/astroport-voting-escrow.json index 00a938ea..1fe3f7f2 100644 --- a/schemas/astroport-voting-escrow/astroport-voting-escrow.json +++ b/schemas/astroport-voting-escrow/astroport-voting-escrow.json @@ -1,6 +1,6 @@ { "contract_name": "astroport-voting-escrow", - "contract_version": "1.0.0", + "contract_version": "1.0.0-rc.1", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/scripts/publish_crates.sh b/scripts/publish_crates.sh index a6b53c4e..3727e43a 100755 --- a/scripts/publish_crates.sh +++ b/scripts/publish_crates.sh @@ -66,7 +66,7 @@ publish() { ROOT_DIR="$(realpath "$1")" FIRST_CRATES="astroport-governance" -SKIP_CRATES="ALL" +SKIP_CRATES="" main() { for contract in $FIRST_CRATES; do From cf0ebacefee7bdb0f614cdee3b8346b3e1c94003 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Thu, 20 Jun 2024 13:34:57 +0300 Subject: [PATCH 21/32] fix astroport governance toml --- packages/astroport-governance/Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/astroport-governance/Cargo.toml b/packages/astroport-governance/Cargo.toml index eb9dc05d..cb69f773 100644 --- a/packages/astroport-governance/Cargo.toml +++ b/packages/astroport-governance/Cargo.toml @@ -3,6 +3,8 @@ name = "astroport-governance" version = "4.0.0-rc.1" authors = ["Astroport"] edition = "2021" +description = "Astroport Governance common types, queriers and other utils" +license = "GPL-3.0-only" repository = "https://github.com/astroport-fi/astroport-governance" homepage = "https://astroport.fi" From df1fdead033852eceda97d34d49e94e81c9564b6 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Thu, 20 Jun 2024 13:35:28 +0300 Subject: [PATCH 22/32] fix license --- packages/astroport-governance/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/astroport-governance/Cargo.toml b/packages/astroport-governance/Cargo.toml index cb69f773..38ae7faf 100644 --- a/packages/astroport-governance/Cargo.toml +++ b/packages/astroport-governance/Cargo.toml @@ -4,7 +4,7 @@ version = "4.0.0-rc.1" authors = ["Astroport"] edition = "2021" description = "Astroport Governance common types, queriers and other utils" -license = "GPL-3.0-only" +license = "Apache-2.0" repository = "https://github.com/astroport-fi/astroport-governance" homepage = "https://astroport.fi" From 3ddd5aa03c423f029bfb605d3f1c2ad0755f1585 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Tue, 25 Jun 2024 11:44:09 +0300 Subject: [PATCH 23/32] bump astroport version; support TF LP tokens in tests --- Cargo.lock | 101 ++++++--------- contracts/emissions_controller/Cargo.toml | 8 +- contracts/emissions_controller/src/utils.rs | 3 +- .../tests/common/helper.rs | 43 ++----- .../emissions_controller_outpost/Cargo.toml | 6 +- .../tests/common/helper.rs | 11 +- .../tests/common/mod.rs | 1 + .../tests/common/stargate.rs | 121 ++++++++++++++++++ .../src/emissions_controller/consts.rs | 3 - .../src/emissions_controller/utils.rs | 2 +- 10 files changed, 193 insertions(+), 106 deletions(-) create mode 100644 contracts/emissions_controller_outpost/tests/common/stargate.rs diff --git a/Cargo.lock b/Cargo.lock index 60378ee1..eae8712b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,24 +57,6 @@ dependencies = [ "uint", ] -[[package]] -name = "astroport" -version = "3.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdebdf96895f363e121710cb84bbbfa659cea1bb1470260d4976d1a7206b3b16" -dependencies = [ - "astroport-circular-buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cosmwasm-schema", - "cosmwasm-std", - "cw-asset", - "cw-storage-plus 0.15.1", - "cw-utils 1.0.3", - "cw20 0.15.1", - "cw3", - "itertools 0.10.5", - "uint", -] - [[package]] name = "astroport" version = "4.0.2" @@ -126,6 +108,25 @@ dependencies = [ "uint", ] +[[package]] +name = "astroport" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "314b84869c5e8cce3c9a7ec7d0a280350f8d2e8e05a772d8b14622da7263784e" +dependencies = [ + "astroport-circular-buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cosmos-sdk-proto 0.19.0", + "cosmwasm-schema", + "cosmwasm-std", + "cw-asset", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw20 1.1.2", + "itertools 0.12.1", + "prost 0.11.9", + "uint", +] + [[package]] name = "astroport-circular-buffer" version = "0.2.0" @@ -171,8 +172,8 @@ dependencies = [ "astroport-governance 4.0.0-rc.1", "astroport-incentives", "astroport-pair", - "astroport-staking 2.1.0 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", - "astroport-tokenfactory-tracker 1.0.0 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "astroport-staking 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "astroport-tokenfactory-tracker 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "astroport-voting-escrow", "builder-unlock", "cosmwasm-schema", @@ -194,6 +195,7 @@ dependencies = [ name = "astroport-emissions-controller-outpost" version = "1.0.0-rc.1" dependencies = [ + "anyhow", "astroport 5.1.0-vxastro-rc.1", "astroport-factory", "astroport-governance 4.0.0-rc.1", @@ -209,24 +211,24 @@ dependencies = [ "cw20-base", "derivative", "itertools 0.12.1", + "osmosis-std", "serde_json", "thiserror", ] [[package]] name = "astroport-factory" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc4250f45bd4697476921a3533f151a73f7352c271b68ff411321006d5a4977" +checksum = "68abcc896255acba2f4aa39f239a1e8361a6035d7bc712442da122dc31f6f959" dependencies = [ - "astroport 3.12.2", + "astroport 5.1.0", "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.15.1", + "cw-storage-plus 1.2.0", "cw-utils 1.0.3", - "cw2 0.15.1", - "itertools 0.10.5", - "protobuf 2.28.0", + "cw2 1.1.2", + "itertools 0.12.1", "thiserror", ] @@ -296,26 +298,26 @@ dependencies = [ [[package]] name = "astroport-pair" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4336db82506bc3aaf52e194711ed0785896ad92825eb18d870d535023e33b666" +checksum = "9a5e6cd4508d45d89f9f44b607eb482aa614fa54274c46746d6f251bf10b27ae" dependencies = [ - "astroport 3.12.2", + "astroport 5.1.0", "cosmwasm-schema", "cosmwasm-std", - "cw-storage-plus 0.15.1", + "cw-storage-plus 1.2.0", "cw-utils 1.0.3", - "cw2 0.15.1", - "cw20 0.15.1", + "cw2 1.1.2", + "cw20 1.1.2", "integer-sqrt", - "protobuf 2.28.0", "thiserror", ] [[package]] name = "astroport-staking" version = "2.1.0" -source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#d2990d817b840cc5071cdb65db9a9e45db52daf5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "315e43350f20d04f8d65bbb4d29ae423eea2fb12e292f0185b983a210d1c6d1a" dependencies = [ "astroport 4.0.3", "cosmwasm-std", @@ -343,7 +345,8 @@ dependencies = [ [[package]] name = "astroport-tokenfactory-tracker" version = "1.0.0" -source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#d2990d817b840cc5071cdb65db9a9e45db52daf5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0586ec0e015b5d9da5c94a5c9d7bd8a6fabdb38fb66905d61caad796a98b0650" dependencies = [ "astroport 4.0.3", "cosmwasm-schema", @@ -859,21 +862,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cw3" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2967fbd073d4b626dd9e7148e05a84a3bebd9794e71342e12351110ffbb12395" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-utils 1.0.3", - "cw20 1.1.2", - "schemars", - "serde", - "thiserror", -] - [[package]] name = "der" version = "0.7.9" @@ -1150,7 +1138,7 @@ dependencies = [ "cosmwasm-std", "prost 0.12.4", "prost-types 0.12.4", - "protobuf 3.4.0", + "protobuf", "schemars", "serde", "serde-json-wasm 1.0.1", @@ -1322,15 +1310,6 @@ dependencies = [ "prost 0.12.4", ] -[[package]] -name = "protobuf" -version = "2.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" -dependencies = [ - "bytes", -] - [[package]] name = "protobuf" version = "3.4.0" diff --git a/contracts/emissions_controller/Cargo.toml b/contracts/emissions_controller/Cargo.toml index f81eb9aa..8a3dfd3e 100644 --- a/contracts/emissions_controller/Cargo.toml +++ b/contracts/emissions_controller/Cargo.toml @@ -33,12 +33,12 @@ cw-multi-test = { git = "https://github.com/astroport-fi/cw-multi-test", branch astroport-voting-escrow = { path = "../voting_escrow", version = "1.0.0-rc.1", features = ["library"] } astro-assembly = { path = "../assembly", version = "3.0.0-rc.1", features = ["library"] } builder-unlock = { path = "../builder_unlock", version = "3", features = ["library"] } -astroport-factory = { version = "1.7", features = ["library"] } -astroport-pair = { version = "1.5", features = ["library"] } +astroport-factory = { version = "1.8", features = ["library"] } +astroport-pair = { version = "2", features = ["library"] } cw20-base = { version = "1", features = ["library"] } astroport-incentives = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "1.2.0-vxastro-rc.1", features = ["library"] } -astroport-staking = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "2.1" } -astroport-tokenfactory-tracker = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "1" } +astroport-staking = "2.1" +astroport-tokenfactory-tracker = "1" osmosis-std = "0.21.0" derivative = "2.2" anyhow = "1" diff --git a/contracts/emissions_controller/src/utils.rs b/contracts/emissions_controller/src/utils.rs index a7db9b86..48fd064a 100644 --- a/contracts/emissions_controller/src/utils.rs +++ b/contracts/emissions_controller/src/utils.rs @@ -1,6 +1,7 @@ use std::collections::{HashMap, HashSet}; use astroport::asset::{determine_asset_info, Asset}; +use astroport::common::LP_SUBDENOM; use astroport::incentives::{IncentivesSchedule, InputSchedule}; use cosmwasm_schema::cw_serde; use cosmwasm_schema::serde::Serialize; @@ -15,7 +16,7 @@ use neutron_sdk::query::min_ibc_fee::query_min_ibc_fee; use neutron_sdk::sudo::msg::RequestPacketTimeoutHeight; use astroport_governance::emissions_controller::consts::{ - EPOCHS_START, EPOCH_LENGTH, FEE_DENOM, IBC_TIMEOUT, LP_SUBDENOM, + EPOCHS_START, EPOCH_LENGTH, FEE_DENOM, IBC_TIMEOUT, }; use astroport_governance::emissions_controller::hub::{ Config, EmissionsState, OutpostInfo, OutpostParams, diff --git a/contracts/emissions_controller/tests/common/helper.rs b/contracts/emissions_controller/tests/common/helper.rs index 99227860..7ebd8024 100644 --- a/contracts/emissions_controller/tests/common/helper.rs +++ b/contracts/emissions_controller/tests/common/helper.rs @@ -1,7 +1,7 @@ use astroport::asset::{AssetInfo, PairInfo}; use astroport::factory::{PairConfig, PairType}; use astroport::incentives::RewardInfo; -use astroport::token::{Logo, MinterResponse}; +use astroport::token::Logo; use astroport::{factory, incentives, staking}; use cosmwasm_std::{ coin, coins, from_json, to_json_binary, Addr, BlockInfo, Coin, Decimal, Empty, IbcEndpoint, @@ -124,6 +124,7 @@ impl ControllerHelper { owner: owner.to_string(), whitelist_code_id: 0, coin_registry_address: app.api().addr_make("coin_registry").to_string(), + tracker_config: None, }, &[], "label", @@ -420,7 +421,7 @@ impl ControllerHelper { ) } - pub fn create_pair(&mut self, denom1: &str, denom2: &str) -> Addr { + pub fn create_pair(&mut self, denom1: &str, denom2: &str) -> String { let asset_infos = vec![AssetInfo::native(denom1), AssetInfo::native(denom2)]; self.app .execute_contract( @@ -636,39 +637,19 @@ impl ControllerHelper { ) } - pub fn reset_astro_reward(&mut self, lp_token: &Addr) -> AnyResult { + pub fn reset_astro_reward(&mut self, lp_token: &str) -> AnyResult { // Mocking LP provide and depositing to incentives contract // NOTE: // it doesn't really provide assets to the pair // but this is fine in the context of emissions controller - self.app - .wrap() - .query_wasm_smart(lp_token, &cw20_base::msg::QueryMsg::Minter {}) - .map_err(Into::into) - .and_then(|info: MinterResponse| { - self.app.execute_contract( - Addr::unchecked(info.minter), - lp_token.clone(), - &cw20_base::msg::ExecuteMsg::Mint { - recipient: self.owner.to_string(), - amount: 10000u128.into(), - }, - &[], - ) - }) - .and_then(|_| { - self.app.execute_contract( - self.owner.clone(), - lp_token.clone(), - &cw20_base::msg::ExecuteMsg::Send { - contract: self.incentives.to_string(), - amount: 10000u128.into(), - msg: to_json_binary(&incentives::Cw20Msg::Deposit { recipient: None }) - .unwrap(), - }, - &[], - ) - }) + let lp_coins = coins(10000, lp_token); + self.mint_tokens(&self.owner.clone(), &lp_coins).unwrap(); + self.app.execute_contract( + self.owner.clone(), + self.incentives.clone(), + &incentives::ExecuteMsg::Deposit { recipient: None }, + &lp_coins, + ) } pub fn query_rewards(&self, pool: impl Into) -> StdResult> { diff --git a/contracts/emissions_controller_outpost/Cargo.toml b/contracts/emissions_controller_outpost/Cargo.toml index 503cf716..5f145f22 100644 --- a/contracts/emissions_controller_outpost/Cargo.toml +++ b/contracts/emissions_controller_outpost/Cargo.toml @@ -30,8 +30,10 @@ serde_json = "1" [dev-dependencies] cw-multi-test = "1" astroport-voting-escrow = { path = "../voting_escrow", version = "1.0.0-rc.1", features = ["library"] } -astroport-factory = { version = "1.7", features = ["library"] } -astroport-pair = { version = "1.5", features = ["library"] } +astroport-factory = { version = "1.8", features = ["library"] } +astroport-pair = { version = "2", features = ["library"] } cw20-base = { version = "1", features = ["library"] } astroport-incentives = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "1.2.0-vxastro-rc.1" } derivative = "2.2" +osmosis-std = "0.21.0" +anyhow = "1" diff --git a/contracts/emissions_controller_outpost/tests/common/helper.rs b/contracts/emissions_controller_outpost/tests/common/helper.rs index 3c1d440c..75655d46 100644 --- a/contracts/emissions_controller_outpost/tests/common/helper.rs +++ b/contracts/emissions_controller_outpost/tests/common/helper.rs @@ -3,7 +3,6 @@ use astroport::factory::{PairConfig, PairType}; use astroport::incentives::{InputSchedule, RewardInfo}; use astroport::token::Logo; use astroport::{factory, incentives}; -use astroport_emissions_controller_outpost::state::REGISTERED_PROPOSALS; use cosmwasm_std::{ coin, coins, to_json_binary, Addr, BlockInfo, Coin, Decimal, Empty, IbcAcknowledgement, IbcEndpoint, IbcPacket, IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg, @@ -12,10 +11,11 @@ use cosmwasm_std::{ use cw_multi_test::error::AnyResult; use cw_multi_test::{ no_init, App, AppBuilder, AppResponse, BankKeeper, BankSudo, DistributionKeeper, Executor, - FailingModule, MockAddressGenerator, MockApiBech32, StakeKeeper, WasmKeeper, + FailingModule, GovFailingModule, MockAddressGenerator, MockApiBech32, StakeKeeper, WasmKeeper, }; use derivative::Derivative; +use astroport_emissions_controller_outpost::state::REGISTERED_PROPOSALS; use astroport_governance::assembly::ProposalVoteOption; use astroport_governance::emissions_controller::consts::{EPOCHS_START, EPOCH_LENGTH}; use astroport_governance::emissions_controller::msg::{ExecuteMsg, IbcAckResult, VxAstroIbcMsg}; @@ -25,6 +25,7 @@ use astroport_governance::{emissions_controller, voting_escrow}; use crate::common::contracts::*; use crate::common::ibc_module::IbcMockModule; +use crate::common::stargate::StargateModule; pub type OutpostApp = App< BankKeeper, @@ -35,6 +36,8 @@ pub type OutpostApp = App< StakeKeeper, DistributionKeeper, IbcMockModule, + GovFailingModule, + StargateModule, >; fn mock_app() -> OutpostApp { @@ -42,6 +45,7 @@ fn mock_app() -> OutpostApp { .with_ibc(IbcMockModule) .with_api(MockApiBech32::new("osmo")) .with_wasm(WasmKeeper::new().with_address_generator(MockAddressGenerator)) + .with_stargate(StargateModule) .with_block(BlockInfo { height: 1, time: Timestamp::from_seconds(EPOCHS_START), @@ -110,6 +114,7 @@ impl ControllerHelper { owner: owner.to_string(), whitelist_code_id: 0, coin_registry_address: app.api().addr_make("coin_registry").to_string(), + tracker_config: None, }, &[], "label", @@ -238,7 +243,7 @@ impl ControllerHelper { ) } - pub fn create_pair(&mut self, denom1: &str, denom2: &str) -> Addr { + pub fn create_pair(&mut self, denom1: &str, denom2: &str) -> String { let asset_infos = vec![AssetInfo::native(denom1), AssetInfo::native(denom2)]; self.app .execute_contract( diff --git a/contracts/emissions_controller_outpost/tests/common/mod.rs b/contracts/emissions_controller_outpost/tests/common/mod.rs index 6074b843..7ec7ef5e 100644 --- a/contracts/emissions_controller_outpost/tests/common/mod.rs +++ b/contracts/emissions_controller_outpost/tests/common/mod.rs @@ -1,3 +1,4 @@ pub mod contracts; pub mod helper; pub mod ibc_module; +pub mod stargate; diff --git a/contracts/emissions_controller_outpost/tests/common/stargate.rs b/contracts/emissions_controller_outpost/tests/common/stargate.rs new file mode 100644 index 00000000..ae9e99a2 --- /dev/null +++ b/contracts/emissions_controller_outpost/tests/common/stargate.rs @@ -0,0 +1,121 @@ +use anyhow::anyhow; +use cosmwasm_schema::serde::de::DeserializeOwned; +use cosmwasm_std::{ + coin, Addr, Api, BankMsg, Binary, BlockInfo, CustomMsg, CustomQuery, Empty, Querier, Storage, + SubMsgResponse, +}; +use cw_multi_test::error::AnyResult; +use cw_multi_test::{ + AppResponse, BankSudo, CosmosRouter, Module, Stargate, StargateMsg, StargateQuery, +}; +use osmosis_std::types::osmosis::tokenfactory::v1beta1::{ + MsgBurn, MsgCreateDenom, MsgCreateDenomResponse, MsgMint, MsgSetBeforeSendHook, + MsgSetDenomMetadata, +}; + +#[derive(Default)] +pub struct StargateModule; + +impl Stargate for StargateModule {} + +impl Module for StargateModule { + type ExecT = StargateMsg; + type QueryT = StargateQuery; + type SudoT = Empty; + + fn execute( + &self, + api: &dyn Api, + storage: &mut dyn Storage, + router: &dyn CosmosRouter, + block: &BlockInfo, + sender: Addr, + msg: Self::ExecT, + ) -> AnyResult + where + ExecC: CustomMsg + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, + { + match msg.type_url.as_str() { + MsgCreateDenom::TYPE_URL => { + let tf_msg: MsgCreateDenom = msg.value.try_into()?; + let submsg_response = SubMsgResponse { + events: vec![], + data: Some( + MsgCreateDenomResponse { + new_token_denom: format!( + "factory/{}/{}", + tf_msg.sender, tf_msg.subdenom + ), + } + .into(), + ), + }; + Ok(submsg_response.into()) + } + MsgMint::TYPE_URL => { + let tf_msg: MsgMint = msg.value.try_into()?; + let mint_coins = tf_msg + .amount + .expect("Empty amount in tokenfactory MsgMint!"); + let cw_coin = coin(mint_coins.amount.parse()?, mint_coins.denom); + let bank_sudo = BankSudo::Mint { + to_address: tf_msg.mint_to_address.clone(), + amount: vec![cw_coin.clone()], + }; + + router.sudo(api, storage, block, bank_sudo.into()) + } + MsgBurn::TYPE_URL => { + let tf_msg: MsgBurn = msg.value.try_into()?; + let burn_coins = tf_msg + .amount + .expect("Empty amount in tokenfactory MsgBurn!"); + let cw_coin = coin(burn_coins.amount.parse()?, burn_coins.denom); + let burn_msg = BankMsg::Burn { + amount: vec![cw_coin.clone()], + }; + + router.execute( + api, + storage, + block, + Addr::unchecked(&tf_msg.sender), + burn_msg.into(), + ) + } + MsgSetDenomMetadata::TYPE_URL => Ok(AppResponse::default()), + MsgSetBeforeSendHook::TYPE_URL => Ok(AppResponse::default()), + _ => Err(anyhow!( + "Unexpected exec msg {} from {sender:?}", + msg.type_url + )), + } + } + + fn query( + &self, + _api: &dyn Api, + _storage: &dyn Storage, + _querier: &dyn Querier, + _block: &BlockInfo, + _request: Self::QueryT, + ) -> AnyResult { + unimplemented!("Stargate queries are not implemented") + } + + fn sudo( + &self, + _api: &dyn Api, + _storage: &mut dyn Storage, + _router: &dyn CosmosRouter, + _block: &BlockInfo, + _msg: Self::SudoT, + ) -> AnyResult + where + ExecC: CustomMsg + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, + { + unimplemented!("Stargate sudo is not implemented") + } +} diff --git a/packages/astroport-governance/src/emissions_controller/consts.rs b/packages/astroport-governance/src/emissions_controller/consts.rs index 8c145599..9ebd98a0 100644 --- a/packages/astroport-governance/src/emissions_controller/consts.rs +++ b/packages/astroport-governance/src/emissions_controller/consts.rs @@ -7,9 +7,6 @@ pub const EPOCHS_START: u64 = 1716163200; pub const DAY: u64 = 86400; /// vxASTRO voting epoch lasts 14 days pub const EPOCH_LENGTH: u64 = DAY * 14; -// TODO: import from the main astroport crate? -/// Astroport token factory LP token subdenom -pub const LP_SUBDENOM: &str = "/astroport/share"; /// Timeout for IBC messages in seconds. Used for both `ics20` and `vxastro-ibc-v1` packets. pub const IBC_TIMEOUT: u64 = 3600; /// Denom used to pay IBC fees diff --git a/packages/astroport-governance/src/emissions_controller/utils.rs b/packages/astroport-governance/src/emissions_controller/utils.rs index 9621b222..6f034d32 100644 --- a/packages/astroport-governance/src/emissions_controller/utils.rs +++ b/packages/astroport-governance/src/emissions_controller/utils.rs @@ -1,8 +1,8 @@ use astroport::asset::{pair_info_by_pool, AssetInfo, PairInfo}; +use astroport::common::LP_SUBDENOM; use astroport::{factory, pair}; use cosmwasm_std::{Addr, QuerierWrapper, StdError, StdResult, Uint128}; -use crate::emissions_controller::consts::LP_SUBDENOM; use crate::voting_escrow; /// Queries pair info corresponding to given LP token. From 94abce69a571f9ac2e5e61c8f0c7a90bda11a5a3 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Tue, 25 Jun 2024 11:55:32 +0300 Subject: [PATCH 24/32] bump deps --- Cargo.lock | 106 +++++++----------- contracts/assembly/Cargo.toml | 8 +- contracts/emissions_controller/Cargo.toml | 6 +- .../emissions_controller_outpost/Cargo.toml | 4 +- 4 files changed, 51 insertions(+), 73 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eae8712b..1da49f6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,10 +26,10 @@ dependencies = [ "anyhow", "astroport 5.1.0-vxastro-rc.1", "astroport-governance 4.0.0-rc.1", - "astroport-staking 2.1.0 (git+https://github.com/astroport-fi/hidden_astroport_core)", - "astroport-tokenfactory-tracker 1.0.0 (git+https://github.com/astroport-fi/hidden_astroport_core)", + "astroport-staking", + "astroport-tokenfactory-tracker", "astroport-voting-escrow", - "builder-unlock", + "builder-unlock 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "cosmwasm-schema", "cosmwasm-std", "cw-multi-test 0.20.0 (git+https://github.com/astroport-fi/cw-multi-test?branch=feat/bank_with_send_hooks)", @@ -37,7 +37,7 @@ dependencies = [ "cw-utils 1.0.3", "cw2 1.1.2", "ibc-controller-package", - "osmosis-std", + "osmosis-std 0.25.0", "test-case", "thiserror", ] @@ -57,22 +57,6 @@ dependencies = [ "uint", ] -[[package]] -name = "astroport" -version = "4.0.2" -source = "git+https://github.com/astroport-fi/hidden_astroport_core#02eafc677d9c402a55e45e956fd185d67ac3a8a6" -dependencies = [ - "astroport-circular-buffer 0.2.0 (git+https://github.com/astroport-fi/hidden_astroport_core)", - "cosmwasm-schema", - "cosmwasm-std", - "cw-asset", - "cw-storage-plus 1.2.0", - "cw-utils 1.0.3", - "cw20 1.1.2", - "itertools 0.12.1", - "uint", -] - [[package]] name = "astroport" version = "4.0.3" @@ -150,17 +134,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "astroport-circular-buffer" -version = "0.2.0" -source = "git+https://github.com/astroport-fi/hidden_astroport_core#02eafc677d9c402a55e45e956fd185d67ac3a8a6" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 1.2.0", - "thiserror", -] - [[package]] name = "astroport-emissions-controller" version = "1.0.0-rc.1" @@ -172,10 +145,10 @@ dependencies = [ "astroport-governance 4.0.0-rc.1", "astroport-incentives", "astroport-pair", - "astroport-staking 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "astroport-tokenfactory-tracker 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "astroport-staking", + "astroport-tokenfactory-tracker", "astroport-voting-escrow", - "builder-unlock", + "builder-unlock 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "cosmwasm-schema", "cosmwasm-std", "cw-multi-test 1.0.0", @@ -186,7 +159,7 @@ dependencies = [ "derivative", "itertools 0.12.1", "neutron-sdk", - "osmosis-std", + "osmosis-std 0.25.0", "serde_json", "thiserror", ] @@ -211,7 +184,7 @@ dependencies = [ "cw20-base", "derivative", "itertools 0.12.1", - "osmosis-std", + "osmosis-std 0.25.0", "serde_json", "thiserror", ] @@ -324,21 +297,7 @@ dependencies = [ "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", - "osmosis-std", - "thiserror", -] - -[[package]] -name = "astroport-staking" -version = "2.1.0" -source = "git+https://github.com/astroport-fi/hidden_astroport_core#02eafc677d9c402a55e45e956fd185d67ac3a8a6" -dependencies = [ - "astroport 4.0.2", - "cosmwasm-std", - "cw-storage-plus 1.2.0", - "cw-utils 1.0.3", - "cw2 1.1.2", - "osmosis-std", + "osmosis-std 0.21.0", "thiserror", ] @@ -356,19 +315,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "astroport-tokenfactory-tracker" -version = "1.0.0" -source = "git+https://github.com/astroport-fi/hidden_astroport_core#02eafc677d9c402a55e45e956fd185d67ac3a8a6" -dependencies = [ - "astroport 4.0.2", - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 1.2.0", - "cw2 1.1.2", - "thiserror", -] - [[package]] name = "astroport-voting-escrow" version = "1.0.0-rc.1" @@ -461,6 +407,22 @@ dependencies = [ "thiserror", ] +[[package]] +name = "builder-unlock" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3519354b74cda2f39528f852bfb9c1683ba141b387a4e09f8b581d6a09b0490b" +dependencies = [ + "astroport 5.1.0", + "astroport-governance 3.0.0", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw2 1.1.2", + "thiserror", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -1202,6 +1164,22 @@ dependencies = [ "serde-cw-value", ] +[[package]] +name = "osmosis-std" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca66dca7e8c9b11b995cd41a44c038134ccca4469894d663d8a9452d6e716241" +dependencies = [ + "chrono", + "cosmwasm-std", + "osmosis-std-derive", + "prost 0.12.4", + "prost-types 0.12.4", + "schemars", + "serde", + "serde-cw-value", +] + [[package]] name = "osmosis-std-derive" version = "0.20.1" diff --git a/contracts/assembly/Cargo.toml b/contracts/assembly/Cargo.toml index cb0c4b1d..2930068c 100644 --- a/contracts/assembly/Cargo.toml +++ b/contracts/assembly/Cargo.toml @@ -29,10 +29,10 @@ ibc-controller-package = "1.0.0" [dev-dependencies] cw-multi-test = { git = "https://github.com/astroport-fi/cw-multi-test", branch = "feat/bank_with_send_hooks", features = ["cosmwasm_1_1"] } -osmosis-std = "0.21" -astroport-staking = { git = "https://github.com/astroport-fi/hidden_astroport_core", version = "2" } -astroport-tokenfactory-tracker = { git = "https://github.com/astroport-fi/hidden_astroport_core", version = "1" } +osmosis-std = "0.25.0" +astroport-staking = "2.1" +astroport-tokenfactory-tracker = { version = "1", features = ["library"] } astroport-voting-escrow = { path = "../voting_escrow", version = "1.0.0-rc.1", features = ["library"] } -builder-unlock = { path = "../builder_unlock", version = "3" } +builder-unlock = { version = "3", features = ["library"] } anyhow = "1" test-case = "3.3.1" \ No newline at end of file diff --git a/contracts/emissions_controller/Cargo.toml b/contracts/emissions_controller/Cargo.toml index 8a3dfd3e..944833f3 100644 --- a/contracts/emissions_controller/Cargo.toml +++ b/contracts/emissions_controller/Cargo.toml @@ -32,13 +32,13 @@ serde_json = "1" cw-multi-test = { git = "https://github.com/astroport-fi/cw-multi-test", branch = "feat/bank_with_send_hooks_1_0", features = ["cosmwasm_1_1"] } astroport-voting-escrow = { path = "../voting_escrow", version = "1.0.0-rc.1", features = ["library"] } astro-assembly = { path = "../assembly", version = "3.0.0-rc.1", features = ["library"] } -builder-unlock = { path = "../builder_unlock", version = "3", features = ["library"] } +builder-unlock = { version = "3", features = ["library"] } astroport-factory = { version = "1.8", features = ["library"] } astroport-pair = { version = "2", features = ["library"] } cw20-base = { version = "1", features = ["library"] } astroport-incentives = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "1.2.0-vxastro-rc.1", features = ["library"] } astroport-staking = "2.1" -astroport-tokenfactory-tracker = "1" -osmosis-std = "0.21.0" +astroport-tokenfactory-tracker = { version = "1", features = ["library"] } +osmosis-std = "0.25.0" derivative = "2.2" anyhow = "1" diff --git a/contracts/emissions_controller_outpost/Cargo.toml b/contracts/emissions_controller_outpost/Cargo.toml index 5f145f22..f1e01209 100644 --- a/contracts/emissions_controller_outpost/Cargo.toml +++ b/contracts/emissions_controller_outpost/Cargo.toml @@ -31,9 +31,9 @@ serde_json = "1" cw-multi-test = "1" astroport-voting-escrow = { path = "../voting_escrow", version = "1.0.0-rc.1", features = ["library"] } astroport-factory = { version = "1.8", features = ["library"] } -astroport-pair = { version = "2", features = ["library"] } +astroport-pair = { version = "2.0.1", features = ["library"] } cw20-base = { version = "1", features = ["library"] } astroport-incentives = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "1.2.0-vxastro-rc.1" } derivative = "2.2" -osmosis-std = "0.21.0" +osmosis-std = "0.25.0" anyhow = "1" From 0236871cbdd58db92cbceaaa973f8728d3eceb2f Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Tue, 25 Jun 2024 13:04:47 +0300 Subject: [PATCH 25/32] bump deps --- Cargo.lock | 171 ++++++++++++++++++---------------- contracts/assembly/Cargo.toml | 2 +- 2 files changed, 91 insertions(+), 82 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1da49f6b..e81d2288 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,16 +15,16 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.83" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "astro-assembly" version = "3.0.0-rc.1" dependencies = [ "anyhow", - "astroport 5.1.0-vxastro-rc.1", + "astroport 4.0.3", "astroport-governance 4.0.0-rc.1", "astroport-staking", "astroport-tokenfactory-tracker", @@ -32,7 +32,7 @@ dependencies = [ "builder-unlock 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 0.20.0 (git+https://github.com/astroport-fi/cw-multi-test?branch=feat/bank_with_send_hooks)", + "cw-multi-test 0.20.0", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -77,7 +77,7 @@ dependencies = [ [[package]] name = "astroport" version = "5.1.0-vxastro-rc.1" -source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#d2990d817b840cc5071cdb65db9a9e45db52daf5" +source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#ee689c03375f896d59f0f36345177eddb727ca63" dependencies = [ "astroport-circular-buffer 0.2.0 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", "cosmos-sdk-proto 0.19.0", @@ -126,7 +126,7 @@ dependencies = [ [[package]] name = "astroport-circular-buffer" version = "0.2.0" -source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#d2990d817b840cc5071cdb65db9a9e45db52daf5" +source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#ee689c03375f896d59f0f36345177eddb727ca63" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -177,7 +177,7 @@ dependencies = [ "astroport-voting-escrow", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 1.1.0", + "cw-multi-test 1.2.0", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -256,7 +256,7 @@ dependencies = [ [[package]] name = "astroport-incentives" version = "1.2.0-vxastro-rc.1" -source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#d2990d817b840cc5071cdb65db9a9e45db52daf5" +source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#ee689c03375f896d59f0f36345177eddb727ca63" dependencies = [ "astroport 5.1.0-vxastro-rc.1", "cosmwasm-schema", @@ -323,7 +323,7 @@ dependencies = [ "astroport-governance 4.0.0-rc.1", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 1.1.0", + "cw-multi-test 1.2.0", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -400,7 +400,7 @@ dependencies = [ "astroport-governance 3.0.0", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cw-multi-test 0.20.1", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -476,8 +476,8 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32560304ab4c365791fd307282f76637213d8083c1a98490c35159cd67852237" dependencies = [ - "prost 0.12.4", - "prost-types 0.12.4", + "prost 0.12.6", + "prost-types 0.12.6", "tendermint-proto 0.34.1", ] @@ -506,9 +506,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8467874827d384c131955ff6f4d47d02e72a956a08eb3c0ff24f8c903a5517b4" +checksum = "7879036156092ad1c22fe0d7316efc5a5eceec2bc3906462a2560215f2a2f929" dependencies = [ "cosmwasm-schema-derive", "schemars", @@ -519,9 +519,9 @@ dependencies = [ [[package]] name = "cosmwasm-schema-derive" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6db85d98ac80922aef465e564d5b21fa9cfac5058cb62df7f116c3682337393" +checksum = "0bb57855fbfc83327f8445ae0d413b1a05ac0d68c396ab4d122b2abd7bb82cb6" dependencies = [ "proc-macro2", "quote", @@ -626,8 +626,7 @@ dependencies = [ [[package]] name = "cw-multi-test" version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fff029689ae89127cf6d7655809a68d712f3edbdb9686c70b018ba438b26ca" +source = "git+https://github.com/astroport-fi/cw-multi-test?branch=feat/bank_with_send_hooks#3220f4cd126b5a8da2ce8b00152afef046a9b391" dependencies = [ "anyhow", "bech32 0.9.1", @@ -636,7 +635,7 @@ dependencies = [ "cw-utils 1.0.3", "derivative", "itertools 0.12.1", - "prost 0.12.4", + "prost 0.12.6", "schemars", "serde", "sha2 0.10.8", @@ -645,8 +644,9 @@ dependencies = [ [[package]] name = "cw-multi-test" -version = "0.20.0" -source = "git+https://github.com/astroport-fi/cw-multi-test?branch=feat/bank_with_send_hooks#3220f4cd126b5a8da2ce8b00152afef046a9b391" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc392a5cb7e778e3f90adbf7faa43c4db7f35b6623224b08886d796718edb875" dependencies = [ "anyhow", "bech32 0.9.1", @@ -655,7 +655,7 @@ dependencies = [ "cw-utils 1.0.3", "derivative", "itertools 0.12.1", - "prost 0.12.4", + "prost 0.12.6", "schemars", "serde", "sha2 0.10.8", @@ -674,7 +674,7 @@ dependencies = [ "cw-utils 1.0.3", "derivative", "itertools 0.12.1", - "prost 0.12.4", + "prost 0.12.6", "schemars", "serde", "sha2 0.10.8", @@ -683,9 +683,9 @@ dependencies = [ [[package]] name = "cw-multi-test" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ffa9e3bae206540c084198e5be5aea2ecb1f2597f79dc09263b528ea0604788" +checksum = "91fc33b1d65c102d72f46548c64dca423c337e528d6747d0c595316aa65f887b" dependencies = [ "anyhow", "bech32 0.11.0", @@ -693,8 +693,8 @@ dependencies = [ "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "derivative", - "itertools 0.12.1", - "prost 0.12.4", + "itertools 0.13.0", + "prost 0.12.6", "schemars", "serde", "sha2 0.10.8", @@ -912,9 +912,9 @@ dependencies = [ [[package]] name = "either" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" [[package]] name = "elliptic-curve" @@ -1062,6 +1062,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -1084,9 +1093,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.154" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "neutron-sdk" @@ -1098,8 +1107,8 @@ dependencies = [ "cosmos-sdk-proto 0.20.0", "cosmwasm-schema", "cosmwasm-std", - "prost 0.12.4", - "prost-types 0.12.4", + "prost 0.12.6", + "prost-types 0.12.6", "protobuf", "schemars", "serde", @@ -1157,8 +1166,8 @@ dependencies = [ "chrono", "cosmwasm-std", "osmosis-std-derive", - "prost 0.12.4", - "prost-types 0.12.4", + "prost 0.12.6", + "prost-types 0.12.6", "schemars", "serde", "serde-cw-value", @@ -1173,8 +1182,8 @@ dependencies = [ "chrono", "cosmwasm-std", "osmosis-std-derive", - "prost 0.12.4", - "prost-types 0.12.4", + "prost 0.12.6", + "prost-types 0.12.6", "schemars", "serde", "serde-cw-value", @@ -1217,9 +1226,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro2" -version = "1.0.82" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -1236,12 +1245,12 @@ dependencies = [ [[package]] name = "prost" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f5d036824e4761737860779c906171497f6d55681139d8312388f8fe398922" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ "bytes", - "prost-derive 0.12.5", + "prost-derive 0.12.6", ] [[package]] @@ -1259,15 +1268,15 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.12.5" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9554e3ab233f0a932403704f1a1d08c30d5ccd931adfdfa1e8b5a19b52c1d55a" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.68", ] [[package]] @@ -1281,11 +1290,11 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3235c33eb02c1f1e212abdbe34c78b264b038fb58ca612664343271e36e55ffe" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" dependencies = [ - "prost 0.12.4", + "prost 0.12.6", ] [[package]] @@ -1356,9 +1365,9 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "schemars" -version = "0.8.19" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6e7ed6919cb46507fb01ff1654309219f62b4d603822501b0b80d42f6f21ef" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" dependencies = [ "dyn-clone", "schemars_derive", @@ -1368,14 +1377,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.19" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185f2b7aa7e02d418e453790dde16890256bbd2bcd04b7dc5348811052b53f49" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.61", + "syn 2.0.68", ] [[package]] @@ -1400,9 +1409,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.201" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] @@ -1436,40 +1445,40 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.14" +version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.201" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.68", ] [[package]] name = "serde_derive_internals" -version = "0.29.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.68", ] [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4" dependencies = [ "itoa", "ryu", @@ -1555,14 +1564,14 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.61", + "syn 2.0.68", ] [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "subtle-encoding" @@ -1586,9 +1595,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.61" +version = "2.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" +checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" dependencies = [ "proc-macro2", "quote", @@ -1623,8 +1632,8 @@ dependencies = [ "flex-error", "num-derive", "num-traits", - "prost 0.12.4", - "prost-types 0.12.4", + "prost 0.12.6", + "prost-types 0.12.6", "serde", "serde_bytes", "subtle-encoding", @@ -1649,7 +1658,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.68", ] [[package]] @@ -1660,28 +1669,28 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.68", "test-case-core", ] [[package]] name = "thiserror" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.60" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.68", ] [[package]] @@ -1751,6 +1760,6 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/contracts/assembly/Cargo.toml b/contracts/assembly/Cargo.toml index 2930068c..70cd4396 100644 --- a/contracts/assembly/Cargo.toml +++ b/contracts/assembly/Cargo.toml @@ -24,7 +24,7 @@ thiserror.workspace = true cosmwasm-schema.workspace = true cw-utils.workspace = true astroport-governance = { path = "../../packages/astroport-governance", version = "4.0.0-rc.1" } -astroport.workspace = true +astroport = "4" ibc-controller-package = "1.0.0" [dev-dependencies] From f02b4b00918733126d4019d82ee16ad9643fffa7 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:12:38 +0400 Subject: [PATCH 26/32] fix(controller): remove ASTRO pool from whitelist and votable pools --- contracts/emissions_controller/src/execute.rs | 11 +++- .../tests/emissions_controller_integration.rs | 56 +++++++++++++++++-- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/contracts/emissions_controller/src/execute.rs b/contracts/emissions_controller/src/execute.rs index cc396fea..42320915 100644 --- a/contracts/emissions_controller/src/execute.rs +++ b/contracts/emissions_controller/src/execute.rs @@ -272,7 +272,16 @@ pub fn update_outpost( ensure!( !conf.constant_emissions.is_zero(), ContractError::ZeroAstroEmissions {} - ) + ); + + // Remove this pool from whitelist + POOLS_WHITELIST.update::<_, StdError>(deps.storage, |mut pools| { + pools.retain(|pool| pool != &conf.astro_pool); + Ok(pools) + })?; + + // And remove from votable pools + VOTED_POOLS.remove(deps.storage, &conf.astro_pool, env.block.time.seconds())?; } if let Some(params) = &outpost_params { diff --git a/contracts/emissions_controller/tests/emissions_controller_integration.rs b/contracts/emissions_controller/tests/emissions_controller_integration.rs index c6712784..a783f626 100644 --- a/contracts/emissions_controller/tests/emissions_controller_integration.rs +++ b/contracts/emissions_controller/tests/emissions_controller_integration.rs @@ -1,13 +1,13 @@ -use std::collections::HashMap; -use std::str::FromStr; - use astroport::asset::AssetInfo; +use astroport::common::LP_SUBDENOM; use astroport::incentives::RewardType; use cosmwasm_std::{coin, coins, Decimal, Decimal256, Empty, Event, Uint128}; use cw_multi_test::Executor; use cw_utils::PaymentError; use itertools::Itertools; use neutron_sdk::sudo::msg::{RequestPacket, TransferSudoMsg}; +use std::collections::HashMap; +use std::str::FromStr; use astroport_emissions_controller::error::ContractError; use astroport_emissions_controller::utils::get_epoch_start; @@ -382,7 +382,7 @@ fn test_outpost_management() { outposts, vec![ ("neutron".to_string(), neutron), - ("osmo".to_string(), osmosis) + ("osmo".to_string(), osmosis.clone()) ] ); @@ -400,6 +400,54 @@ fn test_outpost_management() { .vote(&user, &[(lp_token.to_string(), Decimal::one())]) .unwrap(); + // Whitelist astro pool on Osmosis before marking it as ASTRO pool with flat emissions + let osmosis_astro_pool = format!("factory/osmo1pool/{LP_SUBDENOM}"); + helper + .mint_tokens(&user, &[helper.whitelisting_fee.clone()]) + .unwrap(); + helper + .whitelist( + &user, + &osmosis_astro_pool, + &[helper.whitelisting_fee.clone()], + ) + .unwrap(); + + // Confirm it has been included + let whitelist = helper + .app + .wrap() + .query_wasm_smart::>( + helper.emission_controller.clone(), + &emissions_controller::hub::QueryMsg::QueryWhitelist {}, + ) + .unwrap() + .into_iter() + .sorted() + .collect_vec(); + assert_eq!( + whitelist, + vec![lp_token.to_string(), osmosis_astro_pool.clone()] + ); + + // Mark 'osmosis_astro_pool' as ASTRO pool + osmosis.astro_pool_config = Some(AstroPoolConfig { + astro_pool: osmosis_astro_pool, + constant_emissions: Uint128::from(100000u128), + }); + helper.add_outpost("osmo", osmosis.clone()).unwrap(); + + // Confirm it has been excluded from whitelist + let whitelist = helper + .app + .wrap() + .query_wasm_smart::>( + helper.emission_controller.clone(), + &emissions_controller::hub::QueryMsg::QueryWhitelist {}, + ) + .unwrap(); + assert_eq!(whitelist, vec![lp_token.to_string()]); + // Remove neutron outpost let rand_user = helper.app.api().addr_make("random"); let err = helper From 8dc58e86e8ba652188f8aca6555d2ff465afc7ab Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:51:50 +0400 Subject: [PATCH 27/32] fix(controller): paginate whitelist query --- contracts/emissions_controller/src/query.rs | 24 ++++++++++++- .../tests/common/helper.rs | 10 ++++++ .../tests/emissions_controller_integration.rs | 34 +++---------------- .../src/emissions_controller/hub.rs | 9 +++-- .../astroport-emissions-controller.json | 18 +++++++++- .../raw/query.json | 18 +++++++++- 6 files changed, 78 insertions(+), 35 deletions(-) diff --git a/contracts/emissions_controller/src/query.rs b/contracts/emissions_controller/src/query.rs index f0b51e4c..e334abc3 100644 --- a/contracts/emissions_controller/src/query.rs +++ b/contracts/emissions_controller/src/query.rs @@ -98,7 +98,29 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result>>()?; Ok(to_json_binary(&outposts)?) } - QueryMsg::QueryWhitelist {} => Ok(to_json_binary(&POOLS_WHITELIST.load(deps.storage)?)?), + QueryMsg::QueryWhitelist { limit, start_after } => { + let limit = limit.unwrap_or(MAX_PAGE_LIMIT) as usize; + let pools_whitelist = POOLS_WHITELIST + .load(deps.storage)? + .into_iter() + .skip_while(|pool| { + if let Some(start_after) = &start_after { + pool != start_after + } else { + false + } + }) + .take(limit) + .collect_vec(); + + let pools_whitelist = if start_after.is_some() { + &pools_whitelist[1..] + } else { + &pools_whitelist + }; + + Ok(to_json_binary(pools_whitelist)?) + } QueryMsg::SimulateTune {} => { let deps = deps.into_empty(); diff --git a/contracts/emissions_controller/tests/common/helper.rs b/contracts/emissions_controller/tests/common/helper.rs index 7ebd8024..f5cba6f1 100644 --- a/contracts/emissions_controller/tests/common/helper.rs +++ b/contracts/emissions_controller/tests/common/helper.rs @@ -528,6 +528,16 @@ impl ControllerHelper { ) } + pub fn query_whitelist(&self) -> StdResult> { + self.app.wrap().query_wasm_smart( + &self.emission_controller, + &emissions_controller::hub::QueryMsg::QueryWhitelist { + limit: Some(100), + start_after: None, + }, + ) + } + pub fn query_pools_vp(&self, limit: Option) -> StdResult> { self.query_voted_pools(limit).map(|res| { res.into_iter() diff --git a/contracts/emissions_controller/tests/emissions_controller_integration.rs b/contracts/emissions_controller/tests/emissions_controller_integration.rs index a783f626..cae95bf2 100644 --- a/contracts/emissions_controller/tests/emissions_controller_integration.rs +++ b/contracts/emissions_controller/tests/emissions_controller_integration.rs @@ -238,14 +238,7 @@ fn test_whitelist() { ContractError::PoolAlreadyWhitelisted(lp_token.to_string()) ); - let whitelist = helper - .app - .wrap() - .query_wasm_smart::>( - helper.emission_controller.clone(), - &emissions_controller::hub::QueryMsg::QueryWhitelist {}, - ) - .unwrap(); + let whitelist = helper.query_whitelist().unwrap(); assert_eq!(whitelist, vec![lp_token.to_string()]); } @@ -415,12 +408,7 @@ fn test_outpost_management() { // Confirm it has been included let whitelist = helper - .app - .wrap() - .query_wasm_smart::>( - helper.emission_controller.clone(), - &emissions_controller::hub::QueryMsg::QueryWhitelist {}, - ) + .query_whitelist() .unwrap() .into_iter() .sorted() @@ -438,14 +426,7 @@ fn test_outpost_management() { helper.add_outpost("osmo", osmosis.clone()).unwrap(); // Confirm it has been excluded from whitelist - let whitelist = helper - .app - .wrap() - .query_wasm_smart::>( - helper.emission_controller.clone(), - &emissions_controller::hub::QueryMsg::QueryWhitelist {}, - ) - .unwrap(); + let whitelist = helper.query_whitelist().unwrap(); assert_eq!(whitelist, vec![lp_token.to_string()]); // Remove neutron outpost @@ -1232,14 +1213,7 @@ fn test_some_epochs() { assert_eq!(voted_pools, [(pool1.to_string(), 1_000000u128.into())]); // And from whitelist - let whitelist = helper - .app - .wrap() - .query_wasm_smart::>( - helper.emission_controller.clone(), - &emissions_controller::hub::QueryMsg::QueryWhitelist {}, - ) - .unwrap(); + let whitelist = helper.query_whitelist().unwrap(); assert_eq!(whitelist, vec![pool1.to_string()]); // If user2 relocks his votes won't be restored as pool2 must be whitelisted again diff --git a/packages/astroport-governance/src/emissions_controller/hub.rs b/packages/astroport-governance/src/emissions_controller/hub.rs index 5487c160..9d52aecb 100644 --- a/packages/astroport-governance/src/emissions_controller/hub.rs +++ b/packages/astroport-governance/src/emissions_controller/hub.rs @@ -118,9 +118,14 @@ pub enum QueryMsg { /// ListOutposts returns all outposts registered in the contract #[returns(Vec<(String, OutpostInfo)>)] ListOutposts {}, - /// QueryWhitelist returns the list of pools that are allowed to be voted for + /// QueryWhitelist returns the list of pools that are allowed to be voted for. + /// The query is paginated. + /// If 'start_after' is provided, it yields a list **excluding** 'start_after'. #[returns(Vec)] - QueryWhitelist {}, + QueryWhitelist { + limit: Option, + start_after: Option, + }, /// SimulateTune simulates the ASTRO amount that will be emitted in the next epoch per pool /// considering if the next epoch starts right now. /// This query is useful for the UI to show the expected ASTRO emissions diff --git a/schemas/astroport-emissions-controller/astroport-emissions-controller.json b/schemas/astroport-emissions-controller/astroport-emissions-controller.json index 84d66759..65aceee0 100644 --- a/schemas/astroport-emissions-controller/astroport-emissions-controller.json +++ b/schemas/astroport-emissions-controller/astroport-emissions-controller.json @@ -831,7 +831,7 @@ "additionalProperties": false }, { - "description": "QueryWhitelist returns the list of pools that are allowed to be voted for", + "description": "QueryWhitelist returns the list of pools that are allowed to be voted for. The query is paginated. If 'start_after' is provided, it yields a list **excluding** 'start_after'.", "type": "object", "required": [ "query_whitelist" @@ -839,6 +839,22 @@ "properties": { "query_whitelist": { "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint8", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, "additionalProperties": false } }, diff --git a/schemas/astroport-emissions-controller/raw/query.json b/schemas/astroport-emissions-controller/raw/query.json index 6b0489c2..86bda0fe 100644 --- a/schemas/astroport-emissions-controller/raw/query.json +++ b/schemas/astroport-emissions-controller/raw/query.json @@ -146,7 +146,7 @@ "additionalProperties": false }, { - "description": "QueryWhitelist returns the list of pools that are allowed to be voted for", + "description": "QueryWhitelist returns the list of pools that are allowed to be voted for. The query is paginated. If 'start_after' is provided, it yields a list **excluding** 'start_after'.", "type": "object", "required": [ "query_whitelist" @@ -154,6 +154,22 @@ "properties": { "query_whitelist": { "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint8", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, "additionalProperties": false } }, From c8d65e6409b7cbb60f55430bd2ea6ffd59798cff Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Mon, 29 Jul 2024 14:00:39 +0400 Subject: [PATCH 28/32] fix(hub controller): set assembly as vxASTRO cw admin --- contracts/emissions_controller/src/instantiate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/emissions_controller/src/instantiate.rs b/contracts/emissions_controller/src/instantiate.rs index 5631940b..5f7e3cef 100644 --- a/contracts/emissions_controller/src/instantiate.rs +++ b/contracts/emissions_controller/src/instantiate.rs @@ -95,7 +95,7 @@ pub fn instantiate( // Instantiate vxASTRO contract let init_vxastro_msg = WasmMsg::Instantiate { - admin: Some(msg.owner), + admin: Some(msg.assembly), code_id: msg.vxastro_code_id, msg: to_json_binary(&voting_escrow::InstantiateMsg { deposit_denom: msg.xastro_denom.to_string(), From a5c5ea7502eed20dd772271ccda2dab34fb33f22 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Mon, 29 Jul 2024 14:28:23 +0400 Subject: [PATCH 29/32] outpost controller: expose registered proposals and proposal voters queries --- .../emissions_controller_outpost/src/query.rs | 42 ++++++- .../tests/common/helper.rs | 20 ++-- ...missions_controller_outpost_integration.rs | 15 +++ .../src/emissions_controller/outpost.rs | 21 ++++ ...stroport-emissions-controller-outpost.json | 109 ++++++++++++++++++ .../raw/query.json | 70 +++++++++++ 6 files changed, 266 insertions(+), 11 deletions(-) diff --git a/contracts/emissions_controller_outpost/src/query.rs b/contracts/emissions_controller_outpost/src/query.rs index 55afa152..22fd5f33 100644 --- a/contracts/emissions_controller_outpost/src/query.rs +++ b/contracts/emissions_controller_outpost/src/query.rs @@ -1,10 +1,17 @@ +use astroport_governance::emissions_controller::consts::MAX_PAGE_LIMIT; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{to_json_binary, Binary, Deps, Env, StdResult}; +use cosmwasm_std::{to_json_binary, Binary, Deps, Env, Order, StdResult}; +use cw_storage_plus::Bound; +use itertools::Itertools; -use astroport_governance::emissions_controller::outpost::{QueryMsg, UserIbcStatus}; +use astroport_governance::emissions_controller::outpost::{ + QueryMsg, RegisteredProposal, UserIbcStatus, +}; -use crate::state::{CONFIG, PENDING_MESSAGES, USER_IBC_ERROR}; +use crate::state::{ + CONFIG, PENDING_MESSAGES, PROPOSAL_VOTERS, REGISTERED_PROPOSALS, USER_IBC_ERROR, +}; /// Expose available contract queries. #[cfg_attr(not(feature = "library"), entry_point)] @@ -15,5 +22,34 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { pending_msg: PENDING_MESSAGES.may_load(deps.storage, &user)?, error: USER_IBC_ERROR.may_load(deps.storage, &user)?, }), + QueryMsg::QueryRegisteredProposals { limit, start_after } => REGISTERED_PROPOSALS + .range( + deps.storage, + start_after.map(Bound::exclusive), + None, + Order::Ascending, + ) + .take(limit.unwrap_or(MAX_PAGE_LIMIT) as usize) + .map(|item| item.map(|(id, start_time)| RegisteredProposal { id, start_time })) + .collect::>>() + .and_then(|proposals| to_json_binary(&proposals)), + QueryMsg::QueryProposalVoters { + proposal_id, + limit, + start_after, + } => PROPOSAL_VOTERS + .prefix(proposal_id) + .range( + deps.storage, + start_after.map(Bound::exclusive), + None, + Order::Ascending, + ) + .take(limit.unwrap_or(MAX_PAGE_LIMIT) as usize) + .collect::>>() + .and_then(|voters| { + let voters = voters.into_iter().map(|(voter, _)| voter).collect_vec(); + to_json_binary(&voters) + }), } } diff --git a/contracts/emissions_controller_outpost/tests/common/helper.rs b/contracts/emissions_controller_outpost/tests/common/helper.rs index 75655d46..b66d7b4c 100644 --- a/contracts/emissions_controller_outpost/tests/common/helper.rs +++ b/contracts/emissions_controller_outpost/tests/common/helper.rs @@ -15,11 +15,12 @@ use cw_multi_test::{ }; use derivative::Derivative; -use astroport_emissions_controller_outpost::state::REGISTERED_PROPOSALS; use astroport_governance::assembly::ProposalVoteOption; use astroport_governance::emissions_controller::consts::{EPOCHS_START, EPOCH_LENGTH}; use astroport_governance::emissions_controller::msg::{ExecuteMsg, IbcAckResult, VxAstroIbcMsg}; -use astroport_governance::emissions_controller::outpost::{OutpostInstantiateMsg, OutpostMsg}; +use astroport_governance::emissions_controller::outpost::{ + OutpostInstantiateMsg, OutpostMsg, RegisteredProposal, +}; use astroport_governance::voting_escrow::{LockInfoResponse, UpdateMarketingInfo}; use astroport_governance::{emissions_controller, voting_escrow}; @@ -505,13 +506,16 @@ impl ControllerHelper { } pub fn is_prop_registered(&self, proposal_id: u64) -> bool { - REGISTERED_PROPOSALS - .query( - &self.app.wrap(), - self.emission_controller.clone(), - proposal_id, + self.app + .wrap() + .query_wasm_smart::>( + &self.emission_controller, + &emissions_controller::outpost::QueryMsg::QueryRegisteredProposals { + limit: Some(100), + start_after: None, + }, ) + .map(|proposals| proposals.iter().any(|p| p.id == proposal_id)) .unwrap() - .is_some() } } diff --git a/contracts/emissions_controller_outpost/tests/emissions_controller_outpost_integration.rs b/contracts/emissions_controller_outpost/tests/emissions_controller_outpost_integration.rs index 8131ca89..e6ca4edf 100644 --- a/contracts/emissions_controller_outpost/tests/emissions_controller_outpost_integration.rs +++ b/contracts/emissions_controller_outpost/tests/emissions_controller_outpost_integration.rs @@ -6,6 +6,7 @@ use cw_utils::PaymentError; use astroport_emissions_controller_outpost::error::ContractError; use astroport_governance::assembly::ProposalVoteOption; +use astroport_governance::emissions_controller; use astroport_governance::emissions_controller::consts::{EPOCH_LENGTH, IBC_TIMEOUT}; use astroport_governance::emissions_controller::msg::{ExecuteMsg, VxAstroIbcMsg}; use astroport_governance::emissions_controller::outpost::UserIbcError; @@ -677,6 +678,20 @@ fn test_interchain_governance() { err.downcast::().unwrap(), ContractError::AlreadyVoted {} ); + + let voters = helper + .app + .wrap() + .query_wasm_smart::>( + &helper.emission_controller, + &emissions_controller::outpost::QueryMsg::QueryProposalVoters { + proposal_id: 2, + limit: Some(100), + start_after: None, + }, + ) + .unwrap(); + assert_eq!(voters, vec![user.to_string()]); } #[test] diff --git a/packages/astroport-governance/src/emissions_controller/outpost.rs b/packages/astroport-governance/src/emissions_controller/outpost.rs index f390c0c8..03d6190e 100644 --- a/packages/astroport-governance/src/emissions_controller/outpost.rs +++ b/packages/astroport-governance/src/emissions_controller/outpost.rs @@ -70,6 +70,19 @@ pub enum QueryMsg { /// Whether they have a pending request or an error. #[returns(UserIbcStatus)] QueryUserIbcStatus { user: String }, + /// QueryRegisteredProposals returns the list of registered proposals. + #[returns(Vec)] + QueryRegisteredProposals { + limit: Option, + start_after: Option, + }, + /// QueryProposalVoters returns the list of voters for the proposal. + #[returns(Vec)] + QueryProposalVoters { + proposal_id: u64, + limit: Option, + start_after: Option, + }, } /// Contains failed IBC along with the error message @@ -106,3 +119,11 @@ pub struct Config { /// ICS20 IBC channel from this outpost to the Hub pub ics20_channel: String, } + +/// Contains the proposal id and the start time. +/// Used exclusively in query response. +#[cw_serde] +pub struct RegisteredProposal { + pub id: u64, + pub start_time: u64, +} diff --git a/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json b/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json index f274cd41..382d5cad 100644 --- a/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json +++ b/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json @@ -618,6 +618,76 @@ } }, "additionalProperties": false + }, + { + "description": "QueryRegisteredProposals returns the list of registered proposals.", + "type": "object", + "required": [ + "query_registered_proposals" + ], + "properties": { + "query_registered_proposals": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint8", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "QueryProposalVoters returns the list of voters for the proposal.", + "type": "object", + "required": [ + "query_proposal_voters" + ], + "properties": { + "query_proposal_voters": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint8", + "minimum": 0.0 + }, + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ] }, @@ -697,6 +767,45 @@ } } }, + "query_proposal_voters": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_String", + "type": "array", + "items": { + "type": "string" + } + }, + "query_registered_proposals": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_RegisteredProposal", + "type": "array", + "items": { + "$ref": "#/definitions/RegisteredProposal" + }, + "definitions": { + "RegisteredProposal": { + "description": "Contains the proposal id and the start time. Used exclusively in query response.", + "type": "object", + "required": [ + "id", + "start_time" + ], + "properties": { + "id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "start_time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, "query_user_ibc_status": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "UserIbcStatus", diff --git a/schemas/astroport-emissions-controller-outpost/raw/query.json b/schemas/astroport-emissions-controller-outpost/raw/query.json index 3a3a8301..6945c3a2 100644 --- a/schemas/astroport-emissions-controller-outpost/raw/query.json +++ b/schemas/astroport-emissions-controller-outpost/raw/query.json @@ -38,6 +38,76 @@ } }, "additionalProperties": false + }, + { + "description": "QueryRegisteredProposals returns the list of registered proposals.", + "type": "object", + "required": [ + "query_registered_proposals" + ], + "properties": { + "query_registered_proposals": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint8", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "QueryProposalVoters returns the list of voters for the proposal.", + "type": "object", + "required": [ + "query_proposal_voters" + ], + "properties": { + "query_proposal_voters": { + "type": "object", + "required": [ + "proposal_id" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint8", + "minimum": 0.0 + }, + "proposal_id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ] } From af6ea1d827a1d6038fe9c6db92c711395a87f7a0 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Mon, 29 Jul 2024 14:32:22 +0400 Subject: [PATCH 30/32] fix comments --- contracts/assembly/src/contract.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/assembly/src/contract.rs b/contracts/assembly/src/contract.rs index a9b994ad..b847eef8 100644 --- a/contracts/assembly/src/contract.rs +++ b/contracts/assembly/src/contract.rs @@ -82,9 +82,6 @@ pub fn instantiate( /// Exposes all the execute functions available in the contract. /// /// ## Execute messages -/// * **ExecuteMsg::Receive(cw20_msg)** Receives a message of type [`Cw20ReceiveMsg`] and processes -/// it depending on the received template. -/// /// * **ExecuteMsg::SubmitProposal { title, description, link, messages, ibc_channel }** Submits a new proposal. /// /// * **ExecuteMsg::CheckMessages { messages }** Checks if the messages are correct. @@ -94,6 +91,9 @@ pub fn instantiate( /// /// * **ExecuteMsg::CastVote { proposal_id, vote }** Cast a vote on a specific proposal. /// +/// * **ExecuteMsg::CastVoteOutpost { voter, voting_power, proposal_id, vote }** Applies a vote on a specific proposal from outpost. +/// Only emissions controller is allowed to call this endpoint. +/// /// * **ExecuteMsg::EndProposal { proposal_id }** Sets the status of an expired/finalized proposal. /// /// * **ExecuteMsg::ExecuteProposal { proposal_id }** Executes a successful proposal. From c827e7337436d12f6cfd31f13866d7e0c8a9db8a Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Mon, 29 Jul 2024 14:43:26 +0400 Subject: [PATCH 31/32] add schemas --- .../response_to_query_proposal_voters.json | 8 +++++ ...esponse_to_query_registered_proposals.json | 31 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 schemas/astroport-emissions-controller-outpost/raw/response_to_query_proposal_voters.json create mode 100644 schemas/astroport-emissions-controller-outpost/raw/response_to_query_registered_proposals.json diff --git a/schemas/astroport-emissions-controller-outpost/raw/response_to_query_proposal_voters.json b/schemas/astroport-emissions-controller-outpost/raw/response_to_query_proposal_voters.json new file mode 100644 index 00000000..4290cb1a --- /dev/null +++ b/schemas/astroport-emissions-controller-outpost/raw/response_to_query_proposal_voters.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_String", + "type": "array", + "items": { + "type": "string" + } +} diff --git a/schemas/astroport-emissions-controller-outpost/raw/response_to_query_registered_proposals.json b/schemas/astroport-emissions-controller-outpost/raw/response_to_query_registered_proposals.json new file mode 100644 index 00000000..cade969c --- /dev/null +++ b/schemas/astroport-emissions-controller-outpost/raw/response_to_query_registered_proposals.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_RegisteredProposal", + "type": "array", + "items": { + "$ref": "#/definitions/RegisteredProposal" + }, + "definitions": { + "RegisteredProposal": { + "description": "Contains the proposal id and the start time. Used exclusively in query response.", + "type": "object", + "required": [ + "id", + "start_time" + ], + "properties": { + "id": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "start_time": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} From 16c093e19140a95d948b497999a9666ad0b07489 Mon Sep 17 00:00:00 2001 From: Timofey <5527315+epanchee@users.noreply.github.com> Date: Tue, 20 Aug 2024 18:17:51 +0400 Subject: [PATCH 32/32] feat(vxASTRO): set release versions --- Cargo.lock | 56 +++++++++---------- Cargo.toml | 2 +- contracts/assembly/Cargo.toml | 6 +- contracts/emissions_controller/Cargo.toml | 10 ++-- .../emissions_controller_outpost/Cargo.toml | 8 +-- contracts/voting_escrow/Cargo.toml | 4 +- packages/astroport-governance/Cargo.toml | 2 +- schemas/astro-assembly/astro-assembly.json | 2 +- ...stroport-emissions-controller-outpost.json | 2 +- .../astroport-emissions-controller.json | 2 +- .../astroport-voting-escrow.json | 2 +- 11 files changed, 48 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e81d2288..ac3246e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,11 +21,11 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "astro-assembly" -version = "3.0.0-rc.1" +version = "3.0.0" dependencies = [ "anyhow", "astroport 4.0.3", - "astroport-governance 4.0.0-rc.1", + "astroport-governance 4.0.0", "astroport-staking", "astroport-tokenfactory-tracker", "astroport-voting-escrow", @@ -76,10 +76,11 @@ dependencies = [ [[package]] name = "astroport" -version = "5.1.0-vxastro-rc.1" -source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#ee689c03375f896d59f0f36345177eddb727ca63" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c4af74ada17a13fe7b75d0d529367eb5dcb15e06fd7e744892191856eedc742" dependencies = [ - "astroport-circular-buffer 0.2.0 (git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many)", + "astroport-circular-buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "cosmos-sdk-proto 0.19.0", "cosmwasm-schema", "cosmwasm-std", @@ -94,11 +95,10 @@ dependencies = [ [[package]] name = "astroport" -version = "5.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "314b84869c5e8cce3c9a7ec7d0a280350f8d2e8e05a772d8b14622da7263784e" +version = "5.3.0" +source = "git+https://github.com/astroport-fi/hidden_astroport_core#55f38f8bc49d131431c24fccd7346957601cce7b" dependencies = [ - "astroport-circular-buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "astroport-circular-buffer 0.2.0 (git+https://github.com/astroport-fi/hidden_astroport_core)", "cosmos-sdk-proto 0.19.0", "cosmwasm-schema", "cosmwasm-std", @@ -126,7 +126,7 @@ dependencies = [ [[package]] name = "astroport-circular-buffer" version = "0.2.0" -source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#ee689c03375f896d59f0f36345177eddb727ca63" +source = "git+https://github.com/astroport-fi/hidden_astroport_core#55f38f8bc49d131431c24fccd7346957601cce7b" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -136,13 +136,13 @@ dependencies = [ [[package]] name = "astroport-emissions-controller" -version = "1.0.0-rc.1" +version = "1.0.0" dependencies = [ "anyhow", "astro-assembly", - "astroport 5.1.0-vxastro-rc.1", + "astroport 5.3.0", "astroport-factory", - "astroport-governance 4.0.0-rc.1", + "astroport-governance 4.0.0", "astroport-incentives", "astroport-pair", "astroport-staking", @@ -166,12 +166,12 @@ dependencies = [ [[package]] name = "astroport-emissions-controller-outpost" -version = "1.0.0-rc.1" +version = "1.0.0" dependencies = [ "anyhow", - "astroport 5.1.0-vxastro-rc.1", + "astroport 5.3.0", "astroport-factory", - "astroport-governance 4.0.0-rc.1", + "astroport-governance 4.0.0", "astroport-incentives", "astroport-pair", "astroport-voting-escrow", @@ -195,7 +195,7 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68abcc896255acba2f4aa39f239a1e8361a6035d7bc712442da122dc31f6f959" dependencies = [ - "astroport 5.1.0", + "astroport 5.2.0", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", @@ -233,9 +233,9 @@ dependencies = [ [[package]] name = "astroport-governance" -version = "4.0.0-rc.1" +version = "4.0.0" dependencies = [ - "astroport 5.1.0-vxastro-rc.1", + "astroport 5.3.0", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", @@ -255,10 +255,10 @@ dependencies = [ [[package]] name = "astroport-incentives" -version = "1.2.0-vxastro-rc.1" -source = "git+https://github.com/astroport-fi/hidden_astroport_core?branch=feat/incentivize_many#ee689c03375f896d59f0f36345177eddb727ca63" +version = "1.2.0" +source = "git+https://github.com/astroport-fi/hidden_astroport_core#55f38f8bc49d131431c24fccd7346957601cce7b" dependencies = [ - "astroport 5.1.0-vxastro-rc.1", + "astroport 5.3.0", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", @@ -275,7 +275,7 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a5e6cd4508d45d89f9f44b607eb482aa614fa54274c46746d6f251bf10b27ae" dependencies = [ - "astroport 5.1.0", + "astroport 5.2.0", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", @@ -317,10 +317,10 @@ dependencies = [ [[package]] name = "astroport-voting-escrow" -version = "1.0.0-rc.1" +version = "1.0.0" dependencies = [ - "astroport 5.1.0-vxastro-rc.1", - "astroport-governance 4.0.0-rc.1", + "astroport 5.3.0", + "astroport-governance 4.0.0", "cosmwasm-schema", "cosmwasm-std", "cw-multi-test 1.2.0", @@ -396,7 +396,7 @@ checksum = "56953345e39537a3e18bdaeba4cb0c58a78c1f61f361dc0fa7c5c7340ae87c5f" name = "builder-unlock" version = "3.0.0" dependencies = [ - "astroport 5.1.0-vxastro-rc.1", + "astroport 5.3.0", "astroport-governance 3.0.0", "cosmwasm-schema", "cosmwasm-std", @@ -413,7 +413,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3519354b74cda2f39528f852bfb9c1683ba141b387a4e09f8b581d6a09b0490b" dependencies = [ - "astroport 5.1.0", + "astroport 5.2.0", "astroport-governance 3.0.0", "cosmwasm-schema", "cosmwasm-std", diff --git a/Cargo.toml b/Cargo.toml index ab09987e..d2977c35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ thiserror = "1.0" itertools = "0.12" cosmwasm-schema = "1.5" cw-utils = "1" -astroport = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "5.1.0-vxastro-rc.1" } +astroport = { git = "https://github.com/astroport-fi/hidden_astroport_core", version = "5.3.0" } [profile.release] opt-level = "z" diff --git a/contracts/assembly/Cargo.toml b/contracts/assembly/Cargo.toml index 70cd4396..5b487109 100644 --- a/contracts/assembly/Cargo.toml +++ b/contracts/assembly/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astro-assembly" -version = "3.0.0-rc.1" +version = "3.0.0" authors = ["Astroport"] edition = "2021" description = "Astroport DAO Contract" @@ -23,7 +23,7 @@ cw-storage-plus.workspace = true thiserror.workspace = true cosmwasm-schema.workspace = true cw-utils.workspace = true -astroport-governance = { path = "../../packages/astroport-governance", version = "4.0.0-rc.1" } +astroport-governance = { path = "../../packages/astroport-governance", version = "4.0.0" } astroport = "4" ibc-controller-package = "1.0.0" @@ -32,7 +32,7 @@ cw-multi-test = { git = "https://github.com/astroport-fi/cw-multi-test", branch osmosis-std = "0.25.0" astroport-staking = "2.1" astroport-tokenfactory-tracker = { version = "1", features = ["library"] } -astroport-voting-escrow = { path = "../voting_escrow", version = "1.0.0-rc.1", features = ["library"] } +astroport-voting-escrow = { path = "../voting_escrow", version = "1.0.0", features = ["library"] } builder-unlock = { version = "3", features = ["library"] } anyhow = "1" test-case = "3.3.1" \ No newline at end of file diff --git a/contracts/emissions_controller/Cargo.toml b/contracts/emissions_controller/Cargo.toml index 944833f3..f56d263e 100644 --- a/contracts/emissions_controller/Cargo.toml +++ b/contracts/emissions_controller/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astroport-emissions-controller" -version = "1.0.0-rc.1" +version = "1.0.0" authors = ["Astroport"] edition = "2021" description = "Astroport vxASTRO Emissions Voting Contract" @@ -23,20 +23,20 @@ cw-storage-plus.workspace = true cosmwasm-schema.workspace = true thiserror.workspace = true itertools.workspace = true -astroport-governance = { path = "../../packages/astroport-governance", version = "4.0.0-rc.1" } +astroport-governance = { path = "../../packages/astroport-governance", version = "4.0.0" } astroport.workspace = true neutron-sdk = "0.10.0" serde_json = "1" [dev-dependencies] cw-multi-test = { git = "https://github.com/astroport-fi/cw-multi-test", branch = "feat/bank_with_send_hooks_1_0", features = ["cosmwasm_1_1"] } -astroport-voting-escrow = { path = "../voting_escrow", version = "1.0.0-rc.1", features = ["library"] } -astro-assembly = { path = "../assembly", version = "3.0.0-rc.1", features = ["library"] } +astroport-voting-escrow = { path = "../voting_escrow", version = "1.0.0", features = ["library"] } +astro-assembly = { path = "../assembly", version = "3.0.0", features = ["library"] } builder-unlock = { version = "3", features = ["library"] } astroport-factory = { version = "1.8", features = ["library"] } astroport-pair = { version = "2", features = ["library"] } cw20-base = { version = "1", features = ["library"] } -astroport-incentives = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "1.2.0-vxastro-rc.1", features = ["library"] } +astroport-incentives = { git = "https://github.com/astroport-fi/hidden_astroport_core", version = "1.2.0", features = ["library"] } astroport-staking = "2.1" astroport-tokenfactory-tracker = { version = "1", features = ["library"] } osmosis-std = "0.25.0" diff --git a/contracts/emissions_controller_outpost/Cargo.toml b/contracts/emissions_controller_outpost/Cargo.toml index f1e01209..d96299c8 100644 --- a/contracts/emissions_controller_outpost/Cargo.toml +++ b/contracts/emissions_controller_outpost/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astroport-emissions-controller-outpost" -version = "1.0.0-rc.1" +version = "1.0.0" authors = ["Astroport"] edition = "2021" description = "Astroport vxASTRO Emissions Voting Contract. Outpost version" @@ -23,17 +23,17 @@ cw-storage-plus.workspace = true cosmwasm-schema.workspace = true thiserror.workspace = true itertools.workspace = true -astroport-governance = { path = "../../packages/astroport-governance", version = "4.0.0-rc.1" } +astroport-governance = { path = "../../packages/astroport-governance", version = "4.0.0" } astroport.workspace = true serde_json = "1" [dev-dependencies] cw-multi-test = "1" -astroport-voting-escrow = { path = "../voting_escrow", version = "1.0.0-rc.1", features = ["library"] } +astroport-voting-escrow = { path = "../voting_escrow", version = "1.0.0", features = ["library"] } astroport-factory = { version = "1.8", features = ["library"] } astroport-pair = { version = "2.0.1", features = ["library"] } cw20-base = { version = "1", features = ["library"] } -astroport-incentives = { git = "https://github.com/astroport-fi/hidden_astroport_core", branch = "feat/incentivize_many", version = "1.2.0-vxastro-rc.1" } +astroport-incentives = { git = "https://github.com/astroport-fi/hidden_astroport_core", version = "1.2.0" } derivative = "2.2" osmosis-std = "0.25.0" anyhow = "1" diff --git a/contracts/voting_escrow/Cargo.toml b/contracts/voting_escrow/Cargo.toml index 65437e7f..73828fa4 100644 --- a/contracts/voting_escrow/Cargo.toml +++ b/contracts/voting_escrow/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astroport-voting-escrow" -version = "1.0.0-rc.1" +version = "1.0.0" authors = ["Astroport"] edition = "2021" description = "Astroport Vote Escrowed xASTRO (vxASTRO)" @@ -24,7 +24,7 @@ cosmwasm-std.workspace = true cw-storage-plus.workspace = true thiserror.workspace = true cosmwasm-schema.workspace = true -astroport-governance = { path = "../../packages/astroport-governance", version = "4.0.0-rc.1" } +astroport-governance = { path = "../../packages/astroport-governance", version = "4.0.0" } astroport.workspace = true [dev-dependencies] diff --git a/packages/astroport-governance/Cargo.toml b/packages/astroport-governance/Cargo.toml index 38ae7faf..55844447 100644 --- a/packages/astroport-governance/Cargo.toml +++ b/packages/astroport-governance/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astroport-governance" -version = "4.0.0-rc.1" +version = "4.0.0" authors = ["Astroport"] edition = "2021" description = "Astroport Governance common types, queriers and other utils" diff --git a/schemas/astro-assembly/astro-assembly.json b/schemas/astro-assembly/astro-assembly.json index 4604b9d6..39f2813e 100644 --- a/schemas/astro-assembly/astro-assembly.json +++ b/schemas/astro-assembly/astro-assembly.json @@ -1,6 +1,6 @@ { "contract_name": "astro-assembly", - "contract_version": "3.0.0-rc.1", + "contract_version": "3.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json b/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json index 382d5cad..d280b07a 100644 --- a/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json +++ b/schemas/astroport-emissions-controller-outpost/astroport-emissions-controller-outpost.json @@ -1,6 +1,6 @@ { "contract_name": "astroport-emissions-controller-outpost", - "contract_version": "1.0.0-rc.1", + "contract_version": "1.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/schemas/astroport-emissions-controller/astroport-emissions-controller.json b/schemas/astroport-emissions-controller/astroport-emissions-controller.json index 65aceee0..119fbba8 100644 --- a/schemas/astroport-emissions-controller/astroport-emissions-controller.json +++ b/schemas/astroport-emissions-controller/astroport-emissions-controller.json @@ -1,6 +1,6 @@ { "contract_name": "astroport-emissions-controller", - "contract_version": "1.0.0-rc.1", + "contract_version": "1.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/schemas/astroport-voting-escrow/astroport-voting-escrow.json b/schemas/astroport-voting-escrow/astroport-voting-escrow.json index 1fe3f7f2..00a938ea 100644 --- a/schemas/astroport-voting-escrow/astroport-voting-escrow.json +++ b/schemas/astroport-voting-escrow/astroport-voting-escrow.json @@ -1,6 +1,6 @@ { "contract_name": "astroport-voting-escrow", - "contract_version": "1.0.0-rc.1", + "contract_version": "1.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#",