From 9ba7e6396f308983524ab64fc0e1243ad514676b Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Tue, 16 Jan 2024 14:01:03 +0300 Subject: [PATCH 01/30] add copy of lockdrop contract as lockdrop_pcl for comparison --- Cargo.lock | 15 + Cargo.toml | 3 +- contracts/lockdrop-pcl/.cargo/config | 4 + contracts/lockdrop-pcl/Cargo.toml | 38 + contracts/lockdrop-pcl/README.md | 61 + .../lockdrop-pcl/examples/lockdrop_schema.rs | 12 + .../lockdrop-pcl/schema/neutron-lockdrop.json | 1632 +++++++++++++++ .../lockdrop-pcl/schema/raw/execute.json | 450 ++++ .../lockdrop-pcl/schema/raw/instantiate.json | 104 + .../lockdrop-pcl/schema/raw/migrate.json | 5 + contracts/lockdrop-pcl/schema/raw/query.json | 193 ++ .../schema/raw/response_to_config.json | 146 ++ .../schema/raw/response_to_lock_up_info.json | 198 ++ .../schema/raw/response_to_pool.json | 142 ++ ...ponse_to_query_lockup_total_at_height.json | 18 + ..._to_query_user_lockup_total_at_height.json | 18 + .../schema/raw/response_to_state.json | 39 + .../schema/raw/response_to_user_info.json | 243 +++ ...sponse_to_user_info_with_lockups_list.json | 68 + contracts/lockdrop-pcl/src/contract.rs | 1853 +++++++++++++++++ contracts/lockdrop-pcl/src/lib.rs | 6 + contracts/lockdrop-pcl/src/migration.rs | 77 + contracts/lockdrop-pcl/src/raw_queries.rs | 53 + contracts/lockdrop-pcl/src/state.rs | 113 + contracts/lockdrop-pcl/src/testing.rs | 149 ++ packages/astroport_periphery/src/lib.rs | 1 + .../astroport_periphery/src/lockdrop_pcl.rs | 426 ++++ 27 files changed, 6066 insertions(+), 1 deletion(-) create mode 100644 contracts/lockdrop-pcl/.cargo/config create mode 100644 contracts/lockdrop-pcl/Cargo.toml create mode 100644 contracts/lockdrop-pcl/README.md create mode 100644 contracts/lockdrop-pcl/examples/lockdrop_schema.rs create mode 100644 contracts/lockdrop-pcl/schema/neutron-lockdrop.json create mode 100644 contracts/lockdrop-pcl/schema/raw/execute.json create mode 100644 contracts/lockdrop-pcl/schema/raw/instantiate.json create mode 100644 contracts/lockdrop-pcl/schema/raw/migrate.json create mode 100644 contracts/lockdrop-pcl/schema/raw/query.json create mode 100644 contracts/lockdrop-pcl/schema/raw/response_to_config.json create mode 100644 contracts/lockdrop-pcl/schema/raw/response_to_lock_up_info.json create mode 100644 contracts/lockdrop-pcl/schema/raw/response_to_pool.json create mode 100644 contracts/lockdrop-pcl/schema/raw/response_to_query_lockup_total_at_height.json create mode 100644 contracts/lockdrop-pcl/schema/raw/response_to_query_user_lockup_total_at_height.json create mode 100644 contracts/lockdrop-pcl/schema/raw/response_to_state.json create mode 100644 contracts/lockdrop-pcl/schema/raw/response_to_user_info.json create mode 100644 contracts/lockdrop-pcl/schema/raw/response_to_user_info_with_lockups_list.json create mode 100644 contracts/lockdrop-pcl/src/contract.rs create mode 100644 contracts/lockdrop-pcl/src/lib.rs create mode 100644 contracts/lockdrop-pcl/src/migration.rs create mode 100644 contracts/lockdrop-pcl/src/raw_queries.rs create mode 100644 contracts/lockdrop-pcl/src/state.rs create mode 100644 contracts/lockdrop-pcl/src/testing.rs create mode 100644 packages/astroport_periphery/src/lockdrop_pcl.rs diff --git a/Cargo.lock b/Cargo.lock index e1863ae8..cca8d87b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -993,6 +993,21 @@ dependencies = [ "serde", ] +[[package]] +name = "neutron-lockdrop-pcl" +version = "1.2.1" +dependencies = [ + "astroport 2.5.0 (git+https://github.com/astroport-fi/astroport-core.git?tag=v2.5.0)", + "astroport-periphery", + "cosmwasm-schema", + "cosmwasm-std", + "credits", + "cw-storage-plus 0.15.1", + "cw2 1.1.1", + "cw20 1.1.1", + "serde", +] + [[package]] name = "neutron-price-feed" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 037caaaf..23499fe1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,13 +2,14 @@ members = [ "contracts/auction", "contracts/lockdrop", + "contracts/lockdrop-pcl", "contracts/credits", "contracts/vesting-lp", "contracts/vesting-lti", "contracts/vesting-investors", "contracts/cw20-merkle-airdrop", "contracts/price-feed", - "contracts/astroport/*" + "contracts/astroport/*", ] [profile.release] diff --git a/contracts/lockdrop-pcl/.cargo/config b/contracts/lockdrop-pcl/.cargo/config new file mode 100644 index 00000000..386a66e5 --- /dev/null +++ b/contracts/lockdrop-pcl/.cargo/config @@ -0,0 +1,4 @@ +[alias] +wasm = "build --release --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --example lockdrop_schema" diff --git a/contracts/lockdrop-pcl/Cargo.toml b/contracts/lockdrop-pcl/Cargo.toml new file mode 100644 index 00000000..d3167f93 --- /dev/null +++ b/contracts/lockdrop-pcl/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "neutron-lockdrop" +version = "1.2.1" +authors = ["_astromartian"] +edition = "2021" + + +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 more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + + +[dependencies] +# we have to keep it 0.15.1 because it is the same version as in astroport +cw-storage-plus = { version = "0.15.1" } +astroport = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v2.5.0" } +credits = { path = "../credits" } +astroport-periphery = { workspace = true } +cosmwasm-std = { workspace = true } +cw20 = { workspace = true } +cw2 = { workspace = true } +serde = { workspace = true } + +[dev-dependencies] +cosmwasm-schema = { workspace = true } diff --git a/contracts/lockdrop-pcl/README.md b/contracts/lockdrop-pcl/README.md new file mode 100644 index 00000000..0bc572b1 --- /dev/null +++ b/contracts/lockdrop-pcl/README.md @@ -0,0 +1,61 @@ +# Lockdrop + +The lockdrop contract allows users to lock any of the supported Terraswap LP tokens locked for a selected duration against which they will receive ASTRO tokens pro-rata to their weighted share of the LP tokens to the total deposited LP tokens for that particular pool in the contract. + +- Upon lockup expiration, users will receive Astroport LP tokens on an equivalent weight basis as per their initial Terraswap LP token deposits. + +Note - Users can open muliple lockup positions with different lockup duration for each LP Token pool + +## Contract Design + +### Handle Messages + +| Message | Description | +|-----------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `ExecuteMsg::UpdateConfig` | Can only be called by the admin. Facilitates updating configuration parameters | +| `ExecuteMsg::EnableClaims` | Executed by the Bootstrap auction contract when liquidity is added to the ASTRO-UST pool. Enables ASTRO withdrawals by the lockdrop recipients. | +| `ExecuteMsg::InitializePool` | Admin function. Facilitates addition of new Pool (Terraswap Pools) whose LP tokens can then be locked in the lockdrop contract | +| `ExecuteMsg::UpdatePool` | Admin function to update any configuraton parameter for a terraswap pool whose LP tokens are currently accepted for the lockdrop | +| `ExecuteMsg::IncreaseLockup` | Facilitates opening a new user position or adding to an existing position | +| `ExecuteMsg::IncreaseAstroIncentives` | Admin function to increase the ASTRO incentives that are to be distributed | +| `ExecuteMsg::WithdrawFromLockup` | Facilitates LP token withdrawals from lockup positions by users. 100% amount can be withdrawn during deposit window, which is then limited to 50% during 1st half of deposit window which then decreases linearly during 2nd half of deposit window. Only 1 withdrawal can be made by a user during the withdrawal windows | +| `ExecuteMsg::MigrateLiquidity` | Admin function. Facilitates migration of liquidity (locked terraswap LP tokens) from Terraswap to Astroport (Astroport LP tokens) | +| `ExecuteMsg::StakeLpTokens` | Admin function. Facilitates staking of Astroport LP tokens for a particular LP pool with the generator contract | +| `ExecuteMsg::DelegateAstroToAuction` | This function facilitates ASTRO tokens delegation to the Bootstrap auction contract during the bootstrap auction phase. Delegated ASTRO tokens are added to the user's position in the bootstrap auction contract | +| `ExecuteMsg::ClaimRewardsAndOptionallyUnlock` | Facilitates rewards claim by users for a particular lockup position along with unlock when possible | +| `ExecuteMsg::ClaimAssetReward` | Collects assets reward from LP and distribute reward to user if all requirements are met | +| `ExecuteMsg::TogglePoolRewards` | Admin function. Enables assets reward for specified LP | +| `ExecuteMsg::ProposeNewOwner` | Admin function. Creates an offer to change the contract ownership. The validity period of the offer is set in the `expires_in` variable. After `expires_in` seconds pass, the proposal expires and cannot be accepted anymore. | +| `ExecuteMsg::DropOwnershipProposal` | Admin function. Removes an existing offer to change the contract owner. | +| `ExecuteMsg::ClaimOwnership` | Admin function. Used to claim contract ownership. | + +### Handle Messages :: Callback + +| Message | Description | +|-------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------| +| `CallbackMsg::UpdatePoolOnDualRewardsClaim` | Callback function to update contract state after pending dual staking rewards are claimed from the generator contract | +| `CallbackMsg::WithdrawUserLockupRewardsCallback` | Callback function to withdraw user rewards for a particular lockcup position along with optional LP tokens withdrawal (upon lockup duration expiration) | +| `CallbackMsg::WithdrawLiquidityFromTerraswapCallback` | Callback function used during liquidity migration to update state after liquidity is removed from terraswap | +| `CallbackMsg::DistributeAssetReward` | Callback function used for assets reward distribution after rewards claiming from LP | + +### Query Messages + +| Message | Description | +|---------------------------------|------------------------------------------------------------------------------------------------------------------| +| `QueryMsg::Config` | Returns the config info | +| `QueryMsg::State` | Returns the contract's global state | +| `QueryMsg::Pool` | Returns info regarding a certain supported LP token pool | +| `QueryMsg::UserInfo` | Returns info regarding a user (total ASTRO rewards, list of lockup positions) | +| `QueryMsg::LockUpInfo` | Returns info regarding a particular lockup position with a given duration and identifer for the LP tokens locked | +| `QueryMsg::PendingAssetReward` | Returns the amount of pending asset rewards for the specified recipient and for a specific lockup position | + +## Build schema and run unit-tests + +``` +cargo schema +cargo test +``` + +## License + +TBD diff --git a/contracts/lockdrop-pcl/examples/lockdrop_schema.rs b/contracts/lockdrop-pcl/examples/lockdrop_schema.rs new file mode 100644 index 00000000..ddfb898f --- /dev/null +++ b/contracts/lockdrop-pcl/examples/lockdrop_schema.rs @@ -0,0 +1,12 @@ +use cosmwasm_schema::write_api; + +use astroport_periphery::lockdrop::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + migrate: MigrateMsg + } +} diff --git a/contracts/lockdrop-pcl/schema/neutron-lockdrop.json b/contracts/lockdrop-pcl/schema/neutron-lockdrop.json new file mode 100644 index 00000000..cf59d5f9 --- /dev/null +++ b/contracts/lockdrop-pcl/schema/neutron-lockdrop.json @@ -0,0 +1,1632 @@ +{ + "contract_name": "neutron-lockdrop", + "contract_version": "1.2.1", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "auction_contract", + "credits_contract", + "init_timestamp", + "lock_window", + "lockup_rewards_info", + "max_lock_duration", + "max_positions_per_user", + "min_lock_duration", + "token_info_manager", + "withdrawal_window" + ], + "properties": { + "auction_contract": { + "description": "Auction contract address", + "type": "string" + }, + "credits_contract": { + "description": "Credits contract address", + "type": "string" + }, + "init_timestamp": { + "description": "Timestamp when Contract will start accepting LP Token deposits", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "lock_window": { + "description": "Number of seconds during which lockup deposits will be accepted", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "lockup_rewards_info": { + "description": "Describes rewards coefficients for each lockup duration", + "type": "array", + "items": { + "$ref": "#/definitions/LockupRewardsInfo" + } + }, + "max_lock_duration": { + "description": "Max. no. of weeks allowed for lockup", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "max_positions_per_user": { + "description": "Max lockup positions a user can have", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "min_lock_duration": { + "description": "Min. no. of weeks allowed for lockup", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "owner": { + "description": "Account which can update config", + "type": [ + "string", + "null" + ] + }, + "token_info_manager": { + "description": "Account which can update token addresses and generator", + "type": "string" + }, + "withdrawal_window": { + "description": "Withdrawal Window Length :: Post the deposit window", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "definitions": { + "Decimal256": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal256(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 115792089237316195423570985008687907853269984665640564039457.584007913129639935 (which is (2^256 - 1) / 10^18)", + "type": "string" + }, + "LockupRewardsInfo": { + "type": "object", + "required": [ + "coefficient", + "duration" + ], + "properties": { + "coefficient": { + "$ref": "#/definitions/Decimal256" + }, + "duration": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + } + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "increase_lockup_for" + ], + "properties": { + "increase_lockup_for": { + "type": "object", + "required": [ + "amount", + "duration", + "pool_type", + "user_address" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "duration": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "pool_type": { + "$ref": "#/definitions/PoolType" + }, + "user_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "receive" + ], + "properties": { + "receive": { + "$ref": "#/definitions/Cw20ReceiveMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "increase_ntrn_incentives" + ], + "properties": { + "increase_ntrn_incentives": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "required": [ + "new_config" + ], + "properties": { + "new_config": { + "$ref": "#/definitions/UpdateConfigMsg" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "set_token_info" + ], + "properties": { + "set_token_info": { + "type": "object", + "required": [ + "atom_token", + "generator", + "usdc_token" + ], + "properties": { + "atom_token": { + "type": "string" + }, + "generator": { + "type": "string" + }, + "usdc_token": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "withdraw_from_lockup" + ], + "properties": { + "withdraw_from_lockup": { + "type": "object", + "required": [ + "amount", + "duration", + "pool_type", + "user_address" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "duration": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "pool_type": { + "$ref": "#/definitions/PoolType" + }, + "user_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "claim_rewards_and_optionally_unlock" + ], + "properties": { + "claim_rewards_and_optionally_unlock": { + "type": "object", + "required": [ + "duration", + "pool_type", + "withdraw_lp_stake" + ], + "properties": { + "duration": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "pool_type": { + "$ref": "#/definitions/PoolType" + }, + "withdraw_lp_stake": { + "type": "boolean" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Callbacks; only callable by the contract itself.", + "type": "object", + "required": [ + "callback" + ], + "properties": { + "callback": { + "$ref": "#/definitions/CallbackMsg" + } + }, + "additionalProperties": false + }, + { + "description": "ProposeNewOwner creates a proposal to change contract ownership. The validity period for the proposal is set in the `expires_in` variable.", + "type": "object", + "required": [ + "propose_new_owner" + ], + "properties": { + "propose_new_owner": { + "type": "object", + "required": [ + "expires_in", + "owner" + ], + "properties": { + "expires_in": { + "description": "The date after which this proposal expires", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "owner": { + "description": "Newly proposed contract owner", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "DropOwnershipProposal removes the existing offer to change contract ownership.", + "type": "object", + "required": [ + "drop_ownership_proposal" + ], + "properties": { + "drop_ownership_proposal": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Used to claim contract ownership.", + "type": "object", + "required": [ + "claim_ownership" + ], + "properties": { + "claim_ownership": { + "type": "object" + } + }, + "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 + } + ] + }, + "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" + }, + "CallbackMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "update_pool_on_dual_rewards_claim" + ], + "properties": { + "update_pool_on_dual_rewards_claim": { + "type": "object", + "required": [ + "pool_type", + "prev_ntrn_balance", + "prev_proxy_reward_balances" + ], + "properties": { + "pool_type": { + "$ref": "#/definitions/PoolType" + }, + "prev_ntrn_balance": { + "$ref": "#/definitions/Uint128" + }, + "prev_proxy_reward_balances": { + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "withdraw_user_lockup_rewards_callback" + ], + "properties": { + "withdraw_user_lockup_rewards_callback": { + "type": "object", + "required": [ + "duration", + "pool_type", + "user_address", + "withdraw_lp_stake" + ], + "properties": { + "duration": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "pool_type": { + "$ref": "#/definitions/PoolType" + }, + "user_address": { + "$ref": "#/definitions/Addr" + }, + "withdraw_lp_stake": { + "type": "boolean" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Cw20ReceiveMsg": { + "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", + "type": "object", + "required": [ + "amount", + "msg", + "sender" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "sender": { + "type": "string" + } + }, + "additionalProperties": false + }, + "PoolType": { + "type": "string", + "enum": [ + "USDC", + "ATOM" + ] + }, + "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" + }, + "UpdateConfigMsg": { + "type": "object", + "properties": { + "auction_contract_address": { + "description": "Bootstrap Auction contract address", + "type": [ + "string", + "null" + ] + }, + "generator_address": { + "description": "Generator (Staking for dual rewards) contract address", + "type": [ + "string", + "null" + ] + } + } + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "state" + ], + "properties": { + "state": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "pool" + ], + "properties": { + "pool": { + "type": "object", + "required": [ + "pool_type" + ], + "properties": { + "pool_type": { + "$ref": "#/definitions/PoolType" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "user_info" + ], + "properties": { + "user_info": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "user_info_with_lockups_list" + ], + "properties": { + "user_info_with_lockups_list": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "lock_up_info" + ], + "properties": { + "lock_up_info": { + "type": "object", + "required": [ + "duration", + "pool_type", + "user_address" + ], + "properties": { + "duration": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "pool_type": { + "$ref": "#/definitions/PoolType" + }, + "user_address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "query_user_lockup_total_at_height" + ], + "properties": { + "query_user_lockup_total_at_height": { + "type": "object", + "required": [ + "height", + "pool_type", + "user_address" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "pool_type": { + "$ref": "#/definitions/PoolType" + }, + "user_address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "query_lockup_total_at_height" + ], + "properties": { + "query_lockup_total_at_height": { + "type": "object", + "required": [ + "height", + "pool_type" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "pool_type": { + "$ref": "#/definitions/PoolType" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "PoolType": { + "type": "string", + "enum": [ + "USDC", + "ATOM" + ] + } + } + }, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object" + }, + "sudo": null, + "responses": { + "config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config", + "type": "object", + "required": [ + "auction_contract", + "credits_contract", + "init_timestamp", + "lock_window", + "lockdrop_incentives", + "lockup_rewards_info", + "max_lock_duration", + "max_positions_per_user", + "min_lock_duration", + "owner", + "token_info_manager", + "withdrawal_window" + ], + "properties": { + "auction_contract": { + "description": "Bootstrap Auction contract address", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "credits_contract": { + "description": "Credits contract address", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "generator": { + "description": "Generator (Staking for dual rewards) contract address", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "init_timestamp": { + "description": "Timestamp when Contract will start accepting LP Token deposits", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "lock_window": { + "description": "Number of seconds during which lockup positions be accepted", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "lockdrop_incentives": { + "description": "Total NTRN lockdrop incentives to be distributed among the users", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "lockup_rewards_info": { + "description": "Describes rewards coefficients for each lockup duration", + "type": "array", + "items": { + "$ref": "#/definitions/LockupRewardsInfo" + } + }, + "max_lock_duration": { + "description": "Max. no. of weeks allowed for lockup", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "max_positions_per_user": { + "description": "Max lockup positions a user can have", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "min_lock_duration": { + "description": "Min. no. of weeks allowed for lockup", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "owner": { + "description": "Account which can update the config", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "token_info_manager": { + "description": "Account which can update the generator and token addresses", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "withdrawal_window": { + "description": "Withdrawal Window Length :: Post the deposit window", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "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" + }, + "Decimal256": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal256(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 115792089237316195423570985008687907853269984665640564039457.584007913129639935 (which is (2^256 - 1) / 10^18)", + "type": "string" + }, + "LockupRewardsInfo": { + "type": "object", + "required": [ + "coefficient", + "duration" + ], + "properties": { + "coefficient": { + "$ref": "#/definitions/Decimal256" + }, + "duration": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "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" + } + } + }, + "lock_up_info": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "LockUpInfoResponse", + "type": "object", + "required": [ + "astroport_lp_token", + "claimable_generator_astro_debt", + "claimable_generator_proxy_debt", + "duration", + "generator_ntrn_debt", + "generator_proxy_debt", + "lp_units_locked", + "ntrn_rewards", + "pool_type", + "unlock_timestamp", + "withdrawal_flag" + ], + "properties": { + "astroport_lp_token": { + "$ref": "#/definitions/Addr" + }, + "astroport_lp_transferred": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "astroport_lp_units": { + "description": "User's Astroport LP units, calculated as lp_units_locked (terraswap) / total LP units locked (terraswap) * Astroport LP units minted post migration", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "claimable_generator_astro_debt": { + "description": "ASTRO tokens receivable as generator rewards that user can claim", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "claimable_generator_proxy_debt": { + "description": "Proxy tokens receivable as generator rewards that user can claim", + "allOf": [ + { + "$ref": "#/definitions/RestrictedVector_for_AssetInfo_and_Uint128" + } + ] + }, + "duration": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "generator_ntrn_debt": { + "description": "Generator NTRN tokens lockup received as generator rewards", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "generator_proxy_debt": { + "description": "Generator Proxy tokens lockup received as generator rewards", + "allOf": [ + { + "$ref": "#/definitions/RestrictedVector_for_AssetInfo_and_Uint128" + } + ] + }, + "lp_units_locked": { + "description": "Terraswap LP units locked by the user", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "ntrn_rewards": { + "description": "NTRN tokens received as rewards for participation in the lockdrop", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "pool_type": { + "description": "Terraswap LP token", + "allOf": [ + { + "$ref": "#/definitions/PoolType" + } + ] + }, + "unlock_timestamp": { + "description": "Timestamp beyond which this position can be unlocked", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "withdrawal_flag": { + "description": "Boolean value indicating if the user's has withdrawn funds post the only 1 withdrawal limit cutoff", + "type": "boolean" + } + }, + "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" + }, + "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 + } + ] + }, + "PoolType": { + "type": "string", + "enum": [ + "USDC", + "ATOM" + ] + }, + "RestrictedVector_for_AssetInfo_and_Uint128": { + "description": "Vec wrapper for internal use. Some business logic relies on an order of this vector, thus it is forbidden to sort it or remove elements. New values can be added using .update() ONLY.", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "$ref": "#/definitions/AssetInfo" + }, + { + "$ref": "#/definitions/Uint128" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "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" + } + } + }, + "pool": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PoolInfo", + "type": "object", + "required": [ + "amount_in_lockups", + "generator_ntrn_per_share", + "generator_proxy_per_share", + "incentives_share", + "is_staked", + "lp_token", + "weighted_amount" + ], + "properties": { + "amount_in_lockups": { + "$ref": "#/definitions/Uint128" + }, + "generator_ntrn_per_share": { + "description": "Ratio of Generator NTRN rewards accured to astroport pool share", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "generator_proxy_per_share": { + "description": "Ratio of Generator Proxy rewards accured to astroport pool share", + "allOf": [ + { + "$ref": "#/definitions/RestrictedVector_for_AssetInfo_and_Decimal" + } + ] + }, + "incentives_share": { + "description": "Share of total NTRN incentives allocated to this pool", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "is_staked": { + "description": "Boolean value indicating if the LP Tokens are staked with the Generator contract or not", + "type": "boolean" + }, + "lp_token": { + "$ref": "#/definitions/Addr" + }, + "weighted_amount": { + "description": "Weighted LP Token balance used to calculate NTRN rewards a particular user can claim", + "allOf": [ + { + "$ref": "#/definitions/Uint256" + } + ] + } + }, + "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" + }, + "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" + }, + "RestrictedVector_for_AssetInfo_and_Decimal": { + "description": "Vec wrapper for internal use. Some business logic relies on an order of this vector, thus it is forbidden to sort it or remove elements. New values can be added using .update() ONLY.", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "$ref": "#/definitions/AssetInfo" + }, + { + "$ref": "#/definitions/Decimal" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "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" + }, + "Uint256": { + "description": "An implementation of u256 that is using strings for JSON encoding/decoding, such that the full u256 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 out of primitive uint types or `new` to provide big endian bytes:\n\n``` # use cosmwasm_std::Uint256; let a = Uint256::from(258u128); let b = Uint256::new([ 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 1u8, 2u8, ]); assert_eq!(a, b); ```", + "type": "string" + } + } + }, + "query_lockup_total_at_height": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_Uint128", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ], + "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" + } + } + }, + "query_user_lockup_total_at_height": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_Uint128", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ], + "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": "StateResponse", + "type": "object", + "required": [ + "supported_pairs_list", + "total_incentives_share" + ], + "properties": { + "supported_pairs_list": { + "description": "Vector containing LP addresses for all the supported LP Pools", + "type": "array", + "items": { + "$ref": "#/definitions/PoolType" + } + }, + "total_incentives_share": { + "description": "Total NTRN incentives share", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "definitions": { + "PoolType": { + "type": "string", + "enum": [ + "USDC", + "ATOM" + ] + }, + "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": [ + "claimable_generator_ntrn_debt", + "lockup_infos", + "lockup_positions_index", + "ntrn_transferred", + "total_ntrn_rewards" + ], + "properties": { + "claimable_generator_ntrn_debt": { + "description": "NTRN tokens receivable as generator rewards that user can claim", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "lockup_infos": { + "description": "Lockup positions", + "type": "array", + "items": { + "$ref": "#/definitions/LockUpInfoResponse" + } + }, + "lockup_positions_index": { + "description": "Number of lockup positions the user is having", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "ntrn_transferred": { + "description": "NTRN tokens transferred to user", + "type": "boolean" + }, + "total_ntrn_rewards": { + "description": "Total NTRN tokens user received as rewards for participation in the lockdrop", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "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" + }, + "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 + } + ] + }, + "LockUpInfoResponse": { + "type": "object", + "required": [ + "astroport_lp_token", + "claimable_generator_astro_debt", + "claimable_generator_proxy_debt", + "duration", + "generator_ntrn_debt", + "generator_proxy_debt", + "lp_units_locked", + "ntrn_rewards", + "pool_type", + "unlock_timestamp", + "withdrawal_flag" + ], + "properties": { + "astroport_lp_token": { + "$ref": "#/definitions/Addr" + }, + "astroport_lp_transferred": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "astroport_lp_units": { + "description": "User's Astroport LP units, calculated as lp_units_locked (terraswap) / total LP units locked (terraswap) * Astroport LP units minted post migration", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "claimable_generator_astro_debt": { + "description": "ASTRO tokens receivable as generator rewards that user can claim", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "claimable_generator_proxy_debt": { + "description": "Proxy tokens receivable as generator rewards that user can claim", + "allOf": [ + { + "$ref": "#/definitions/RestrictedVector_for_AssetInfo_and_Uint128" + } + ] + }, + "duration": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "generator_ntrn_debt": { + "description": "Generator NTRN tokens lockup received as generator rewards", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "generator_proxy_debt": { + "description": "Generator Proxy tokens lockup received as generator rewards", + "allOf": [ + { + "$ref": "#/definitions/RestrictedVector_for_AssetInfo_and_Uint128" + } + ] + }, + "lp_units_locked": { + "description": "Terraswap LP units locked by the user", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "ntrn_rewards": { + "description": "NTRN tokens received as rewards for participation in the lockdrop", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "pool_type": { + "description": "Terraswap LP token", + "allOf": [ + { + "$ref": "#/definitions/PoolType" + } + ] + }, + "unlock_timestamp": { + "description": "Timestamp beyond which this position can be unlocked", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "withdrawal_flag": { + "description": "Boolean value indicating if the user's has withdrawn funds post the only 1 withdrawal limit cutoff", + "type": "boolean" + } + } + }, + "PoolType": { + "type": "string", + "enum": [ + "USDC", + "ATOM" + ] + }, + "RestrictedVector_for_AssetInfo_and_Uint128": { + "description": "Vec wrapper for internal use. Some business logic relies on an order of this vector, thus it is forbidden to sort it or remove elements. New values can be added using .update() ONLY.", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "$ref": "#/definitions/AssetInfo" + }, + { + "$ref": "#/definitions/Uint128" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "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_with_lockups_list": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "UserInfoWithListResponse", + "type": "object", + "required": [ + "lockup_infos", + "lockup_positions_index", + "ntrn_transferred", + "total_ntrn_rewards" + ], + "properties": { + "lockup_infos": { + "description": "Lockup positions", + "type": "array", + "items": { + "$ref": "#/definitions/LockUpInfoSummary" + } + }, + "lockup_positions_index": { + "description": "Number of lockup positions the user is having", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "ntrn_transferred": { + "description": "NTRN tokens transferred to user", + "type": "boolean" + }, + "total_ntrn_rewards": { + "description": "Total NTRN tokens user received as rewards for participation in the lockdrop", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "definitions": { + "LockUpInfoSummary": { + "type": "object", + "required": [ + "duration", + "pool_type" + ], + "properties": { + "duration": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "pool_type": { + "$ref": "#/definitions/PoolType" + } + } + }, + "PoolType": { + "type": "string", + "enum": [ + "USDC", + "ATOM" + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + } + } +} diff --git a/contracts/lockdrop-pcl/schema/raw/execute.json b/contracts/lockdrop-pcl/schema/raw/execute.json new file mode 100644 index 00000000..97932b6d --- /dev/null +++ b/contracts/lockdrop-pcl/schema/raw/execute.json @@ -0,0 +1,450 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "increase_lockup_for" + ], + "properties": { + "increase_lockup_for": { + "type": "object", + "required": [ + "amount", + "duration", + "pool_type", + "user_address" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "duration": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "pool_type": { + "$ref": "#/definitions/PoolType" + }, + "user_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "receive" + ], + "properties": { + "receive": { + "$ref": "#/definitions/Cw20ReceiveMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "increase_ntrn_incentives" + ], + "properties": { + "increase_ntrn_incentives": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_config" + ], + "properties": { + "update_config": { + "type": "object", + "required": [ + "new_config" + ], + "properties": { + "new_config": { + "$ref": "#/definitions/UpdateConfigMsg" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "set_token_info" + ], + "properties": { + "set_token_info": { + "type": "object", + "required": [ + "atom_token", + "generator", + "usdc_token" + ], + "properties": { + "atom_token": { + "type": "string" + }, + "generator": { + "type": "string" + }, + "usdc_token": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "withdraw_from_lockup" + ], + "properties": { + "withdraw_from_lockup": { + "type": "object", + "required": [ + "amount", + "duration", + "pool_type", + "user_address" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "duration": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "pool_type": { + "$ref": "#/definitions/PoolType" + }, + "user_address": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "claim_rewards_and_optionally_unlock" + ], + "properties": { + "claim_rewards_and_optionally_unlock": { + "type": "object", + "required": [ + "duration", + "pool_type", + "withdraw_lp_stake" + ], + "properties": { + "duration": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "pool_type": { + "$ref": "#/definitions/PoolType" + }, + "withdraw_lp_stake": { + "type": "boolean" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Callbacks; only callable by the contract itself.", + "type": "object", + "required": [ + "callback" + ], + "properties": { + "callback": { + "$ref": "#/definitions/CallbackMsg" + } + }, + "additionalProperties": false + }, + { + "description": "ProposeNewOwner creates a proposal to change contract ownership. The validity period for the proposal is set in the `expires_in` variable.", + "type": "object", + "required": [ + "propose_new_owner" + ], + "properties": { + "propose_new_owner": { + "type": "object", + "required": [ + "expires_in", + "owner" + ], + "properties": { + "expires_in": { + "description": "The date after which this proposal expires", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "owner": { + "description": "Newly proposed contract owner", + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "description": "DropOwnershipProposal removes the existing offer to change contract ownership.", + "type": "object", + "required": [ + "drop_ownership_proposal" + ], + "properties": { + "drop_ownership_proposal": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "description": "Used to claim contract ownership.", + "type": "object", + "required": [ + "claim_ownership" + ], + "properties": { + "claim_ownership": { + "type": "object" + } + }, + "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 + } + ] + }, + "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" + }, + "CallbackMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "update_pool_on_dual_rewards_claim" + ], + "properties": { + "update_pool_on_dual_rewards_claim": { + "type": "object", + "required": [ + "pool_type", + "prev_ntrn_balance", + "prev_proxy_reward_balances" + ], + "properties": { + "pool_type": { + "$ref": "#/definitions/PoolType" + }, + "prev_ntrn_balance": { + "$ref": "#/definitions/Uint128" + }, + "prev_proxy_reward_balances": { + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "withdraw_user_lockup_rewards_callback" + ], + "properties": { + "withdraw_user_lockup_rewards_callback": { + "type": "object", + "required": [ + "duration", + "pool_type", + "user_address", + "withdraw_lp_stake" + ], + "properties": { + "duration": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "pool_type": { + "$ref": "#/definitions/PoolType" + }, + "user_address": { + "$ref": "#/definitions/Addr" + }, + "withdraw_lp_stake": { + "type": "boolean" + } + } + } + }, + "additionalProperties": false + } + ] + }, + "Cw20ReceiveMsg": { + "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", + "type": "object", + "required": [ + "amount", + "msg", + "sender" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "sender": { + "type": "string" + } + }, + "additionalProperties": false + }, + "PoolType": { + "type": "string", + "enum": [ + "USDC", + "ATOM" + ] + }, + "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" + }, + "UpdateConfigMsg": { + "type": "object", + "properties": { + "auction_contract_address": { + "description": "Bootstrap Auction contract address", + "type": [ + "string", + "null" + ] + }, + "generator_address": { + "description": "Generator (Staking for dual rewards) contract address", + "type": [ + "string", + "null" + ] + } + } + } + } +} diff --git a/contracts/lockdrop-pcl/schema/raw/instantiate.json b/contracts/lockdrop-pcl/schema/raw/instantiate.json new file mode 100644 index 00000000..f91cea0f --- /dev/null +++ b/contracts/lockdrop-pcl/schema/raw/instantiate.json @@ -0,0 +1,104 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "required": [ + "auction_contract", + "credits_contract", + "init_timestamp", + "lock_window", + "lockup_rewards_info", + "max_lock_duration", + "max_positions_per_user", + "min_lock_duration", + "token_info_manager", + "withdrawal_window" + ], + "properties": { + "auction_contract": { + "description": "Auction contract address", + "type": "string" + }, + "credits_contract": { + "description": "Credits contract address", + "type": "string" + }, + "init_timestamp": { + "description": "Timestamp when Contract will start accepting LP Token deposits", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "lock_window": { + "description": "Number of seconds during which lockup deposits will be accepted", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "lockup_rewards_info": { + "description": "Describes rewards coefficients for each lockup duration", + "type": "array", + "items": { + "$ref": "#/definitions/LockupRewardsInfo" + } + }, + "max_lock_duration": { + "description": "Max. no. of weeks allowed for lockup", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "max_positions_per_user": { + "description": "Max lockup positions a user can have", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "min_lock_duration": { + "description": "Min. no. of weeks allowed for lockup", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "owner": { + "description": "Account which can update config", + "type": [ + "string", + "null" + ] + }, + "token_info_manager": { + "description": "Account which can update token addresses and generator", + "type": "string" + }, + "withdrawal_window": { + "description": "Withdrawal Window Length :: Post the deposit window", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "definitions": { + "Decimal256": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal256(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 115792089237316195423570985008687907853269984665640564039457.584007913129639935 (which is (2^256 - 1) / 10^18)", + "type": "string" + }, + "LockupRewardsInfo": { + "type": "object", + "required": [ + "coefficient", + "duration" + ], + "properties": { + "coefficient": { + "$ref": "#/definitions/Decimal256" + }, + "duration": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + } +} diff --git a/contracts/lockdrop-pcl/schema/raw/migrate.json b/contracts/lockdrop-pcl/schema/raw/migrate.json new file mode 100644 index 00000000..87b18ea7 --- /dev/null +++ b/contracts/lockdrop-pcl/schema/raw/migrate.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "type": "object" +} diff --git a/contracts/lockdrop-pcl/schema/raw/query.json b/contracts/lockdrop-pcl/schema/raw/query.json new file mode 100644 index 00000000..9f3ee6a9 --- /dev/null +++ b/contracts/lockdrop-pcl/schema/raw/query.json @@ -0,0 +1,193 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "state" + ], + "properties": { + "state": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "pool" + ], + "properties": { + "pool": { + "type": "object", + "required": [ + "pool_type" + ], + "properties": { + "pool_type": { + "$ref": "#/definitions/PoolType" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "user_info" + ], + "properties": { + "user_info": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "user_info_with_lockups_list" + ], + "properties": { + "user_info_with_lockups_list": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "lock_up_info" + ], + "properties": { + "lock_up_info": { + "type": "object", + "required": [ + "duration", + "pool_type", + "user_address" + ], + "properties": { + "duration": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "pool_type": { + "$ref": "#/definitions/PoolType" + }, + "user_address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "query_user_lockup_total_at_height" + ], + "properties": { + "query_user_lockup_total_at_height": { + "type": "object", + "required": [ + "height", + "pool_type", + "user_address" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "pool_type": { + "$ref": "#/definitions/PoolType" + }, + "user_address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "query_lockup_total_at_height" + ], + "properties": { + "query_lockup_total_at_height": { + "type": "object", + "required": [ + "height", + "pool_type" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "pool_type": { + "$ref": "#/definitions/PoolType" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "PoolType": { + "type": "string", + "enum": [ + "USDC", + "ATOM" + ] + } + } +} diff --git a/contracts/lockdrop-pcl/schema/raw/response_to_config.json b/contracts/lockdrop-pcl/schema/raw/response_to_config.json new file mode 100644 index 00000000..946da11d --- /dev/null +++ b/contracts/lockdrop-pcl/schema/raw/response_to_config.json @@ -0,0 +1,146 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config", + "type": "object", + "required": [ + "auction_contract", + "credits_contract", + "init_timestamp", + "lock_window", + "lockdrop_incentives", + "lockup_rewards_info", + "max_lock_duration", + "max_positions_per_user", + "min_lock_duration", + "owner", + "token_info_manager", + "withdrawal_window" + ], + "properties": { + "auction_contract": { + "description": "Bootstrap Auction contract address", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "credits_contract": { + "description": "Credits contract address", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "generator": { + "description": "Generator (Staking for dual rewards) contract address", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + }, + "init_timestamp": { + "description": "Timestamp when Contract will start accepting LP Token deposits", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "lock_window": { + "description": "Number of seconds during which lockup positions be accepted", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "lockdrop_incentives": { + "description": "Total NTRN lockdrop incentives to be distributed among the users", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "lockup_rewards_info": { + "description": "Describes rewards coefficients for each lockup duration", + "type": "array", + "items": { + "$ref": "#/definitions/LockupRewardsInfo" + } + }, + "max_lock_duration": { + "description": "Max. no. of weeks allowed for lockup", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "max_positions_per_user": { + "description": "Max lockup positions a user can have", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "min_lock_duration": { + "description": "Min. no. of weeks allowed for lockup", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "owner": { + "description": "Account which can update the config", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "token_info_manager": { + "description": "Account which can update the generator and token addresses", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "withdrawal_window": { + "description": "Withdrawal Window Length :: Post the deposit window", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "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" + }, + "Decimal256": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal256(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 115792089237316195423570985008687907853269984665640564039457.584007913129639935 (which is (2^256 - 1) / 10^18)", + "type": "string" + }, + "LockupRewardsInfo": { + "type": "object", + "required": [ + "coefficient", + "duration" + ], + "properties": { + "coefficient": { + "$ref": "#/definitions/Decimal256" + }, + "duration": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/lockdrop-pcl/schema/raw/response_to_lock_up_info.json b/contracts/lockdrop-pcl/schema/raw/response_to_lock_up_info.json new file mode 100644 index 00000000..89a125d8 --- /dev/null +++ b/contracts/lockdrop-pcl/schema/raw/response_to_lock_up_info.json @@ -0,0 +1,198 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "LockUpInfoResponse", + "type": "object", + "required": [ + "astroport_lp_token", + "claimable_generator_astro_debt", + "claimable_generator_proxy_debt", + "duration", + "generator_ntrn_debt", + "generator_proxy_debt", + "lp_units_locked", + "ntrn_rewards", + "pool_type", + "unlock_timestamp", + "withdrawal_flag" + ], + "properties": { + "astroport_lp_token": { + "$ref": "#/definitions/Addr" + }, + "astroport_lp_transferred": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "astroport_lp_units": { + "description": "User's Astroport LP units, calculated as lp_units_locked (terraswap) / total LP units locked (terraswap) * Astroport LP units minted post migration", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "claimable_generator_astro_debt": { + "description": "ASTRO tokens receivable as generator rewards that user can claim", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "claimable_generator_proxy_debt": { + "description": "Proxy tokens receivable as generator rewards that user can claim", + "allOf": [ + { + "$ref": "#/definitions/RestrictedVector_for_AssetInfo_and_Uint128" + } + ] + }, + "duration": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "generator_ntrn_debt": { + "description": "Generator NTRN tokens lockup received as generator rewards", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "generator_proxy_debt": { + "description": "Generator Proxy tokens lockup received as generator rewards", + "allOf": [ + { + "$ref": "#/definitions/RestrictedVector_for_AssetInfo_and_Uint128" + } + ] + }, + "lp_units_locked": { + "description": "Terraswap LP units locked by the user", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "ntrn_rewards": { + "description": "NTRN tokens received as rewards for participation in the lockdrop", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "pool_type": { + "description": "Terraswap LP token", + "allOf": [ + { + "$ref": "#/definitions/PoolType" + } + ] + }, + "unlock_timestamp": { + "description": "Timestamp beyond which this position can be unlocked", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "withdrawal_flag": { + "description": "Boolean value indicating if the user's has withdrawn funds post the only 1 withdrawal limit cutoff", + "type": "boolean" + } + }, + "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" + }, + "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 + } + ] + }, + "PoolType": { + "type": "string", + "enum": [ + "USDC", + "ATOM" + ] + }, + "RestrictedVector_for_AssetInfo_and_Uint128": { + "description": "Vec wrapper for internal use. Some business logic relies on an order of this vector, thus it is forbidden to sort it or remove elements. New values can be added using .update() ONLY.", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "$ref": "#/definitions/AssetInfo" + }, + { + "$ref": "#/definitions/Uint128" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/lockdrop-pcl/schema/raw/response_to_pool.json b/contracts/lockdrop-pcl/schema/raw/response_to_pool.json new file mode 100644 index 00000000..865a4fe2 --- /dev/null +++ b/contracts/lockdrop-pcl/schema/raw/response_to_pool.json @@ -0,0 +1,142 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PoolInfo", + "type": "object", + "required": [ + "amount_in_lockups", + "generator_ntrn_per_share", + "generator_proxy_per_share", + "incentives_share", + "is_staked", + "lp_token", + "weighted_amount" + ], + "properties": { + "amount_in_lockups": { + "$ref": "#/definitions/Uint128" + }, + "generator_ntrn_per_share": { + "description": "Ratio of Generator NTRN rewards accured to astroport pool share", + "allOf": [ + { + "$ref": "#/definitions/Decimal" + } + ] + }, + "generator_proxy_per_share": { + "description": "Ratio of Generator Proxy rewards accured to astroport pool share", + "allOf": [ + { + "$ref": "#/definitions/RestrictedVector_for_AssetInfo_and_Decimal" + } + ] + }, + "incentives_share": { + "description": "Share of total NTRN incentives allocated to this pool", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "is_staked": { + "description": "Boolean value indicating if the LP Tokens are staked with the Generator contract or not", + "type": "boolean" + }, + "lp_token": { + "$ref": "#/definitions/Addr" + }, + "weighted_amount": { + "description": "Weighted LP Token balance used to calculate NTRN rewards a particular user can claim", + "allOf": [ + { + "$ref": "#/definitions/Uint256" + } + ] + } + }, + "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" + }, + "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" + }, + "RestrictedVector_for_AssetInfo_and_Decimal": { + "description": "Vec wrapper for internal use. Some business logic relies on an order of this vector, thus it is forbidden to sort it or remove elements. New values can be added using .update() ONLY.", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "$ref": "#/definitions/AssetInfo" + }, + { + "$ref": "#/definitions/Decimal" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "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" + }, + "Uint256": { + "description": "An implementation of u256 that is using strings for JSON encoding/decoding, such that the full u256 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 out of primitive uint types or `new` to provide big endian bytes:\n\n``` # use cosmwasm_std::Uint256; let a = Uint256::from(258u128); let b = Uint256::new([ 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 1u8, 2u8, ]); assert_eq!(a, b); ```", + "type": "string" + } + } +} diff --git a/contracts/lockdrop-pcl/schema/raw/response_to_query_lockup_total_at_height.json b/contracts/lockdrop-pcl/schema/raw/response_to_query_lockup_total_at_height.json new file mode 100644 index 00000000..2eaf6e96 --- /dev/null +++ b/contracts/lockdrop-pcl/schema/raw/response_to_query_lockup_total_at_height.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_Uint128", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ], + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/lockdrop-pcl/schema/raw/response_to_query_user_lockup_total_at_height.json b/contracts/lockdrop-pcl/schema/raw/response_to_query_user_lockup_total_at_height.json new file mode 100644 index 00000000..2eaf6e96 --- /dev/null +++ b/contracts/lockdrop-pcl/schema/raw/response_to_query_user_lockup_total_at_height.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Nullable_Uint128", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ], + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/lockdrop-pcl/schema/raw/response_to_state.json b/contracts/lockdrop-pcl/schema/raw/response_to_state.json new file mode 100644 index 00000000..cdb65ce0 --- /dev/null +++ b/contracts/lockdrop-pcl/schema/raw/response_to_state.json @@ -0,0 +1,39 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "StateResponse", + "type": "object", + "required": [ + "supported_pairs_list", + "total_incentives_share" + ], + "properties": { + "supported_pairs_list": { + "description": "Vector containing LP addresses for all the supported LP Pools", + "type": "array", + "items": { + "$ref": "#/definitions/PoolType" + } + }, + "total_incentives_share": { + "description": "Total NTRN incentives share", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "definitions": { + "PoolType": { + "type": "string", + "enum": [ + "USDC", + "ATOM" + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/lockdrop-pcl/schema/raw/response_to_user_info.json b/contracts/lockdrop-pcl/schema/raw/response_to_user_info.json new file mode 100644 index 00000000..a15339ce --- /dev/null +++ b/contracts/lockdrop-pcl/schema/raw/response_to_user_info.json @@ -0,0 +1,243 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "UserInfoResponse", + "type": "object", + "required": [ + "claimable_generator_ntrn_debt", + "lockup_infos", + "lockup_positions_index", + "ntrn_transferred", + "total_ntrn_rewards" + ], + "properties": { + "claimable_generator_ntrn_debt": { + "description": "NTRN tokens receivable as generator rewards that user can claim", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "lockup_infos": { + "description": "Lockup positions", + "type": "array", + "items": { + "$ref": "#/definitions/LockUpInfoResponse" + } + }, + "lockup_positions_index": { + "description": "Number of lockup positions the user is having", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "ntrn_transferred": { + "description": "NTRN tokens transferred to user", + "type": "boolean" + }, + "total_ntrn_rewards": { + "description": "Total NTRN tokens user received as rewards for participation in the lockdrop", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "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" + }, + "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 + } + ] + }, + "LockUpInfoResponse": { + "type": "object", + "required": [ + "astroport_lp_token", + "claimable_generator_astro_debt", + "claimable_generator_proxy_debt", + "duration", + "generator_ntrn_debt", + "generator_proxy_debt", + "lp_units_locked", + "ntrn_rewards", + "pool_type", + "unlock_timestamp", + "withdrawal_flag" + ], + "properties": { + "astroport_lp_token": { + "$ref": "#/definitions/Addr" + }, + "astroport_lp_transferred": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "astroport_lp_units": { + "description": "User's Astroport LP units, calculated as lp_units_locked (terraswap) / total LP units locked (terraswap) * Astroport LP units minted post migration", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "claimable_generator_astro_debt": { + "description": "ASTRO tokens receivable as generator rewards that user can claim", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "claimable_generator_proxy_debt": { + "description": "Proxy tokens receivable as generator rewards that user can claim", + "allOf": [ + { + "$ref": "#/definitions/RestrictedVector_for_AssetInfo_and_Uint128" + } + ] + }, + "duration": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "generator_ntrn_debt": { + "description": "Generator NTRN tokens lockup received as generator rewards", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "generator_proxy_debt": { + "description": "Generator Proxy tokens lockup received as generator rewards", + "allOf": [ + { + "$ref": "#/definitions/RestrictedVector_for_AssetInfo_and_Uint128" + } + ] + }, + "lp_units_locked": { + "description": "Terraswap LP units locked by the user", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "ntrn_rewards": { + "description": "NTRN tokens received as rewards for participation in the lockdrop", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "pool_type": { + "description": "Terraswap LP token", + "allOf": [ + { + "$ref": "#/definitions/PoolType" + } + ] + }, + "unlock_timestamp": { + "description": "Timestamp beyond which this position can be unlocked", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "withdrawal_flag": { + "description": "Boolean value indicating if the user's has withdrawn funds post the only 1 withdrawal limit cutoff", + "type": "boolean" + } + } + }, + "PoolType": { + "type": "string", + "enum": [ + "USDC", + "ATOM" + ] + }, + "RestrictedVector_for_AssetInfo_and_Uint128": { + "description": "Vec wrapper for internal use. Some business logic relies on an order of this vector, thus it is forbidden to sort it or remove elements. New values can be added using .update() ONLY.", + "type": "array", + "items": { + "type": "array", + "items": [ + { + "$ref": "#/definitions/AssetInfo" + }, + { + "$ref": "#/definitions/Uint128" + } + ], + "maxItems": 2, + "minItems": 2 + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/lockdrop-pcl/schema/raw/response_to_user_info_with_lockups_list.json b/contracts/lockdrop-pcl/schema/raw/response_to_user_info_with_lockups_list.json new file mode 100644 index 00000000..0809050c --- /dev/null +++ b/contracts/lockdrop-pcl/schema/raw/response_to_user_info_with_lockups_list.json @@ -0,0 +1,68 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "UserInfoWithListResponse", + "type": "object", + "required": [ + "lockup_infos", + "lockup_positions_index", + "ntrn_transferred", + "total_ntrn_rewards" + ], + "properties": { + "lockup_infos": { + "description": "Lockup positions", + "type": "array", + "items": { + "$ref": "#/definitions/LockUpInfoSummary" + } + }, + "lockup_positions_index": { + "description": "Number of lockup positions the user is having", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "ntrn_transferred": { + "description": "NTRN tokens transferred to user", + "type": "boolean" + }, + "total_ntrn_rewards": { + "description": "Total NTRN tokens user received as rewards for participation in the lockdrop", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + } + }, + "definitions": { + "LockUpInfoSummary": { + "type": "object", + "required": [ + "duration", + "pool_type" + ], + "properties": { + "duration": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "pool_type": { + "$ref": "#/definitions/PoolType" + } + } + }, + "PoolType": { + "type": "string", + "enum": [ + "USDC", + "ATOM" + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/lockdrop-pcl/src/contract.rs b/contracts/lockdrop-pcl/src/contract.rs new file mode 100644 index 00000000..50aee229 --- /dev/null +++ b/contracts/lockdrop-pcl/src/contract.rs @@ -0,0 +1,1853 @@ +use std::cmp::min; +use std::convert::TryInto; +use std::str::FromStr; + +use astroport::asset::{Asset, AssetInfo}; +use astroport::common::{claim_ownership, drop_ownership_proposal, propose_new_owner}; +use astroport::cosmwasm_ext::IntegerToDecimal; +use astroport::generator::{ + ExecuteMsg as GenExecuteMsg, PendingTokenResponse, QueryMsg as GenQueryMsg, RewardInfoResponse, +}; +use astroport::restricted_vector::RestrictedVector; +use astroport::DecimalCheckedOps; +use astroport_periphery::utils::Decimal256CheckedOps; +use cosmwasm_std::{ + attr, coins, entry_point, from_binary, to_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, + Decimal, Decimal256, Deps, DepsMut, Env, MessageInfo, Order, Response, StdError, StdResult, + Uint128, Uint256, WasmMsg, +}; +use cw2::set_contract_version; +use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg, Cw20ReceiveMsg}; + +use crate::raw_queries::{raw_balance, raw_generator_deposit}; +use astroport_periphery::lockdrop::{ + CallbackMsg, Config, Cw20HookMsg, ExecuteMsg, InstantiateMsg, LockUpInfoResponse, + LockUpInfoSummary, LockupInfoV2, MigrateMsg, PoolInfo, PoolType, QueryMsg, State, + StateResponse, UpdateConfigMsg, UserInfoResponse, UserInfoWithListResponse, +}; + +use crate::state::{ + CompatibleLoader, ASSET_POOLS, CONFIG, LOCKUP_INFO, OWNERSHIP_PROPOSAL, STATE, + TOTAL_USER_LOCKUP_AMOUNT, USER_INFO, +}; + +const AIRDROP_REWARDS_MULTIPLIER: &str = "1.0"; + +pub const UNTRN_DENOM: &str = "untrn"; + +/// Contract name that is used for migration. +const CONTRACT_NAME: &str = "neutron_lockdrop"; +/// Contract version that is used for migration. +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +/// Minimum lockup positions for user. +const MIN_POSITIONS_PER_USER: u32 = 1; + +/// Creates a new contract with the specified parameters packed in the `msg` variable. +/// Returns a [`Response`] with the specified attributes if the operation was successful, or a [`ContractError`] if the contract was not created +/// ## Params +/// * **deps** is an object of type [`DepsMut`]. +/// +/// * **env** is an object of type [`Env`]. +/// +/// * **info** is an object of type [`MessageInfo`]. +/// +/// * **msg** is a message of type [`InstantiateMsg`] which contains the parameters used for creating the contract. +#[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)?; + // CHECK :: init_timestamp needs to be valid + if env.block.time.seconds() > msg.init_timestamp { + return Err(StdError::generic_err(format!( + "Invalid init_timestamp. Current timestamp : {}", + env.block.time.seconds() + ))); + } + + // CHECK :: min_lock_duration , max_lock_duration need to be valid (min_lock_duration < max_lock_duration) + if msg.max_lock_duration < msg.min_lock_duration || msg.min_lock_duration == 0u64 { + return Err(StdError::generic_err("Invalid Lockup durations")); + } + + if msg.lockup_rewards_info.is_empty() { + return Err(StdError::generic_err("Invalid lockup rewards info")); + } + for lr_info in &msg.lockup_rewards_info { + if lr_info.duration == 0 { + return Err(StdError::generic_err( + "Invalid Lockup info rewards duration", + )); + } + } + + if msg.max_positions_per_user < MIN_POSITIONS_PER_USER { + return Err(StdError::generic_err( + "The maximum number of locked positions per user cannot be lower than a minimum acceptable value." + )); + } + + let config = Config { + owner: msg + .owner + .map(|v| deps.api.addr_validate(&v)) + .transpose()? + .unwrap_or(info.sender), + token_info_manager: deps.api.addr_validate(&msg.token_info_manager)?, + credits_contract: deps.api.addr_validate(&msg.credits_contract)?, + auction_contract: deps.api.addr_validate(&msg.auction_contract)?, + generator: None, + init_timestamp: msg.init_timestamp, + lock_window: msg.lock_window, + withdrawal_window: msg.withdrawal_window, + min_lock_duration: msg.min_lock_duration, + max_lock_duration: msg.max_lock_duration, + lockdrop_incentives: Uint128::zero(), + max_positions_per_user: msg.max_positions_per_user, + lockup_rewards_info: msg.lockup_rewards_info, + }; + + CONFIG.save(deps.storage, &config)?; + STATE.save(deps.storage, &State::default())?; + Ok(Response::default()) +} + +/// ## Description +/// Exposes all the execute functions available in the contract. +/// ## Params +/// * **deps** is an object of type [`DepsMut`]. +/// +/// * **env** is an object of type [`Env`]. +/// +/// * **info** is an object of type [`MessageInfo`]. +/// +/// * **msg** is an object of type [`ExecuteMsg`]. +/// +/// ## Execute messages +/// +/// * **ExecuteMsg::Receive(msg)** Parse incoming messages from the cNTRN token. +/// +/// * **ExecuteMsg::UpdateConfig { new_config }** Admin function to update configuration parameters. +/// +/// * **ExecuteMsg::InitializePool { +/// pool_type, +/// incentives_share, +/// }** Facilitates addition of new Pool (axlrUSDC/NTRN or ATOM/NTRN) whose LP tokens can then be locked in the lockdrop contract. +/// +/// * **ExecuteMsg::ClaimRewardsAndOptionallyUnlock { +/// terraswap_lp_token, +/// duration, +/// withdraw_lp_stake, +/// }** Claims user Rewards for a particular Lockup position. +/// +/// * **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. +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { + match msg { + ExecuteMsg::Receive(msg) => receive_cw20(deps, env, info, msg), + ExecuteMsg::ClaimRewardsAndOptionallyUnlock { + pool_type, + duration, + withdraw_lp_stake, + } => handle_claim_rewards_and_unlock_for_lockup( + deps, + env, + info, + pool_type, + duration, + withdraw_lp_stake, + ), + ExecuteMsg::Callback(msg) => _handle_callback(deps, env, info, msg), + ExecuteMsg::ProposeNewOwner { owner, expires_in } => { + let config: Config = CONFIG.load(deps.storage)?; + propose_new_owner( + deps, + info, + env, + owner, + expires_in, + config.owner, + OWNERSHIP_PROPOSAL, + ) + } + ExecuteMsg::DropOwnershipProposal {} => { + let config: Config = CONFIG.load(deps.storage)?; + drop_ownership_proposal(deps, info, config.owner, OWNERSHIP_PROPOSAL) + } + 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) + })?; + Ok(()) + }) + } + ExecuteMsg::IncreaseNTRNIncentives {} => handle_increasing_ntrn_incentives(deps, env, info), + ExecuteMsg::IncreaseLockupFor { + user_address, + pool_type, + amount, + duration, + } => handle_increase_lockup(deps, env, info, user_address, pool_type, duration, amount), + ExecuteMsg::WithdrawFromLockup { + user_address, + pool_type, + duration, + amount, + } => { + handle_withdraw_from_lockup(deps, env, info, user_address, pool_type, duration, amount) + } + ExecuteMsg::UpdateConfig { new_config } => handle_update_config(deps, info, new_config), + ExecuteMsg::SetTokenInfo { + usdc_token, + atom_token, + generator, + } => handle_set_token_info(deps, env, info, usdc_token, atom_token, generator), + } +} + +/// Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template. +/// If the template is not found in the received message, then an [`StdError`] is returned, +/// otherwise it returns the [`Response`] with the specified attributes if the operation was successful. +/// ## Params +/// * **deps** is an object of type [`DepsMut`]. +/// +/// * **env** is an object of type [`Env`]. +/// +/// * **info** is an object of type [`MessageInfo`]. +/// +/// * **cw20_msg** is an object of type [`Cw20ReceiveMsg`]. This is the CW20 message that has to be processed. +pub fn receive_cw20( + deps: DepsMut, + env: Env, + info: MessageInfo, + cw20_msg: Cw20ReceiveMsg, +) -> Result { + let cw20_sender_addr = deps.api.addr_validate(&cw20_msg.sender)?; + // CHECK :: Tokens sent > 0 + if cw20_msg.amount == Uint128::zero() { + return Err(StdError::generic_err( + "Number of tokens sent should be > 0 ", + )); + } + + match from_binary(&cw20_msg.msg)? { + Cw20HookMsg::InitializePool { + pool_type, + incentives_share, + } => handle_initialize_pool( + deps, + env, + info, + pool_type, + cw20_sender_addr, + incentives_share, + cw20_msg.amount, + ), + } +} + +/// ## Description +/// Handles callback. Returns a [`ContractError`] on failure. +/// ## Params +/// * **deps** is an object of type [`DepsMut`]. +/// +/// * **env** is an object of type [`Env`]. +/// +/// * **info** is an object of type [`MessageInfo`]. +/// +/// * **msg** is an object of type [`CallbackMsg`]. +fn _handle_callback( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: CallbackMsg, +) -> StdResult { + // Only the contract itself can call callbacks + if info.sender != env.contract.address { + return Err(StdError::generic_err( + "callbacks cannot be invoked externally", + )); + } + match msg { + CallbackMsg::UpdatePoolOnDualRewardsClaim { + pool_type, + prev_ntrn_balance, + prev_proxy_reward_balances, + } => update_pool_on_dual_rewards_claim( + deps, + env, + pool_type, + prev_ntrn_balance, + prev_proxy_reward_balances, + ), + CallbackMsg::WithdrawUserLockupRewardsCallback { + pool_type, + user_address, + duration, + withdraw_lp_stake, + } => callback_withdraw_user_rewards_for_lockup_optional_withdraw( + deps, + env, + pool_type, + user_address, + duration, + withdraw_lp_stake, + ), + } +} + +/// Exposes all the queries available in the contract. +/// ## Params +/// * **deps** is an object of type [`Deps`]. +/// +/// * **_env** is an object of type [`Env`]. +/// +/// * **msg** is an object of type [`QueryMsg`]. +/// +/// ## Queries +/// * **QueryMsg::Config {}** Returns the config info. +/// +/// * **QueryMsg::State {}** Returns the contract's state info. +/// +/// * **QueryMsg::Pool { terraswap_lp_token }** Returns info regarding a certain supported LP token pool. +/// +/// * **QueryMsg::UserInfo { address }** Returns info regarding a user (total NTRN rewards, list of lockup positions). +/// +/// * **QueryMsg::UserInfoWithLockupsList { address }** Returns info regarding a user with lockups. +/// +/// * **QueryMsg::LockUpInfo { +/// user_address, +/// terraswap_lp_token, +/// duration, +/// }** Returns info regarding a particular lockup position with a given duration and identifer for the LP tokens locked. +/// +/// * **QueryMsg::PendingAssetReward { +/// user_address, +/// terraswap_lp_token, +/// duration, +/// }** Returns the amount of pending asset rewards for the specified recipient and for a specific lockup position. +/// +/// * **QueryUserLockupTotalAtHeight { +/// pool_type: PoolType, +/// user_address: String, +/// height: u64, +/// }** Returns locked amount of LP tokens for the specified user for the specified pool at a specific height. +/// +/// * **QueryLockupTotalAtHeight { +/// pool_type: PoolType, +/// height: u64, +/// }** Returns a total amount of LP tokens for the specified pool at a specific height. +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Config {} => to_binary(&CONFIG.load(deps.storage)?), + QueryMsg::State {} => to_binary(&query_state(deps)?), + QueryMsg::Pool { pool_type } => to_binary(&query_pool(deps, pool_type)?), + QueryMsg::UserInfo { address } => to_binary(&query_user_info(deps, env, address)?), + QueryMsg::UserInfoWithLockupsList { address } => { + to_binary(&query_user_info_with_lockups_list(deps, env, address)?) + } + QueryMsg::LockUpInfo { + user_address, + pool_type, + duration, + } => to_binary(&query_lockup_info( + deps, + &env, + &user_address, + pool_type, + duration, + )?), + QueryMsg::QueryUserLockupTotalAtHeight { + pool_type, + user_address, + height, + } => to_binary(&query_user_lockup_total_at_height( + deps, + pool_type, + deps.api.addr_validate(&user_address)?, + height, + )?), + QueryMsg::QueryLockupTotalAtHeight { pool_type, height } => { + to_binary(&query_lockup_total_at_height(deps, pool_type, height)?) + } + } +} + +/// Used for contract migration. Returns a default object of type [`Response`]. +/// ## Params +/// * **_deps** is an object of type [`DepsMut`]. +/// +/// * **_env** is an object of type [`Env`]. +/// +/// * **_msg** is an object of type [`MigrateMsg`]. +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult { + Ok(Response::default()) +} + +/// Admin function to update Configuration parameters. Returns a default object of type [`Response`]. +/// ## Params +/// * **deps** is an object of type [`DepsMut`]. +/// +/// * **info** is an object of type [`MessageInfo`]. +/// +/// * **new_config** is an object of type [`UpdateConfigMsg`]. Same as UpdateConfigMsg struct +pub fn handle_update_config( + deps: DepsMut, + info: MessageInfo, + new_config: UpdateConfigMsg, +) -> StdResult { + let mut config = CONFIG.load(deps.storage)?; + let mut attributes = vec![attr("action", "update_config")]; + + // CHECK :: Only owner can call this function + if info.sender != config.owner { + return Err(StdError::generic_err("Unauthorized")); + } + + if let Some(auction) = new_config.auction_contract_address { + config.auction_contract = deps.api.addr_validate(&auction)?; + attributes.push(attr("auction_contract", auction)); + }; + + if let Some(generator) = new_config.generator_address { + // If generator is set, we check is any LP tokens are currently staked before updating generator address + if config.generator.is_some() { + for pool_type in ASSET_POOLS + .keys(deps.storage, None, None, Order::Ascending) + .collect::, StdError>>()? + { + let pool_info = ASSET_POOLS.load(deps.storage, pool_type)?; + if pool_info.is_staked { + return Err(StdError::generic_err(format!( + "{:?} astro LP tokens already staked. Unstake them before updating generator", + pool_type + ))); + } + } + } + + config.generator = Some(deps.api.addr_validate(&generator)?); + attributes.push(attr("new_generator", generator)) + } + + CONFIG.save(deps.storage, &config)?; + Ok(Response::new().add_attributes(attributes)) +} + +pub fn handle_set_token_info( + deps: DepsMut, + env: Env, + info: MessageInfo, + usdc_token: String, + atom_token: String, + generator: String, +) -> Result { + let mut config = CONFIG.load(deps.storage)?; + + // CHECK :: Only owner and token info manager can call this function + if info.sender != config.owner && info.sender != config.token_info_manager { + return Err(StdError::generic_err("Unauthorized")); + } + + // POOL INFO :: Initialize new pool + let pool_info = PoolInfo { + lp_token: deps.api.addr_validate(&atom_token)?, + amount_in_lockups: Default::default(), + incentives_share: Uint128::zero(), + weighted_amount: Default::default(), + generator_ntrn_per_share: Default::default(), + generator_proxy_per_share: RestrictedVector::default(), + is_staked: false, + }; + ASSET_POOLS.save(deps.storage, PoolType::ATOM, &pool_info, env.block.height)?; + + // POOL INFO :: Initialize new pool + let pool_info = PoolInfo { + lp_token: deps.api.addr_validate(&usdc_token)?, + amount_in_lockups: Default::default(), + incentives_share: Uint128::zero(), + weighted_amount: Default::default(), + generator_ntrn_per_share: Default::default(), + generator_proxy_per_share: RestrictedVector::default(), + is_staked: false, + }; + ASSET_POOLS.save(deps.storage, PoolType::USDC, &pool_info, env.block.height)?; + + config.generator = Some(deps.api.addr_validate(&generator)?); + CONFIG.save(deps.storage, &config)?; + + let attributes = vec![ + attr("action", "update_config"), + attr("usdc_token", usdc_token), + attr("atom_token", atom_token), + attr("generator", generator), + ]; + + Ok(Response::new().add_attributes(attributes)) +} + +/// Facilitates increasing NTRN incentives that are to be distributed as Lockdrop participation reward. Returns a default object of type [`Response`]. +/// ## Params +/// * **deps** is an object of type [`DepsMut`]. +/// +/// * **env** is an object of type [`Env`]. +/// +/// * **info** is an object of type [`MessageInfo`]. +pub fn handle_increasing_ntrn_incentives( + deps: DepsMut, + env: Env, + info: MessageInfo, +) -> Result { + let mut config = CONFIG.load(deps.storage)?; + + if env.block.time.seconds() + >= config.init_timestamp + config.lock_window + config.withdrawal_window + { + return Err(StdError::generic_err("Lock window is closed")); + }; + + let incentive = info.funds.iter().find(|c| c.denom == UNTRN_DENOM); + let amount = if let Some(coin) = incentive { + coin.amount + } else { + return Err(StdError::generic_err(format!( + "{} is not found", + UNTRN_DENOM + ))); + }; + // Anyone can increase ntrn incentives + config.lockdrop_incentives = config.lockdrop_incentives.checked_add(amount)?; + + CONFIG.save(deps.storage, &config)?; + Ok(Response::new() + .add_attribute("action", "ntrn_incentives_increased") + .add_attribute("amount", amount)) +} + +/// Admin function to initialize new LP Pool. Returns a default object of type [`Response`]. +/// ## Params +/// * **deps** is an object of type [`DepsMut`]. +/// +/// * **env** is an object of type [`Env`]. +/// +/// * **info** is an object of type [`MessageInfo`]. +/// +/// * **pool_type** is an object of type [`PoolType`]. LiquidPool type - USDC or ATOM +/// +/// * **cw20_sender_addr** is an object of type [`Addr`]. Address caller cw20 contract +/// +/// * **incentives_share** is an object of type [`u64`]. Parameter defining share of total NTRN incentives are allocated for this pool +/// +/// * **amount** amount of LP tokens of `pool_type` to be staked in the Generator Contract. +pub fn handle_initialize_pool( + deps: DepsMut, + env: Env, + info: MessageInfo, + pool_type: PoolType, + cw20_sender_addr: Addr, + incentives_share: Uint128, + amount: Uint128, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + let mut state = STATE.load(deps.storage)?; + + // CHECK ::: Only auction can call this function + if cw20_sender_addr != config.auction_contract { + return Err(StdError::generic_err("Unauthorized")); + } + + // CHECK ::: Is LP Token Pool initialized + let mut pool_info = ASSET_POOLS.load(deps.storage, pool_type)?; + + if info.sender != pool_info.lp_token { + return Err(StdError::generic_err("Unknown cw20 token address")); + } + + // Set Pool Incentives + pool_info.incentives_share = incentives_share; + + let stake_msgs = stake_messages( + config, + env.block.height + 1u64, + pool_info.lp_token.clone(), + amount, + )?; + pool_info.is_staked = true; + + ASSET_POOLS.save(deps.storage, pool_type, &pool_info, env.block.height)?; + + state.total_incentives_share = state.total_incentives_share.checked_add(incentives_share)?; + STATE.save(deps.storage, &state)?; + + Ok(Response::new() + .add_messages(stake_msgs) + .add_attributes(vec![ + attr("action", "initialize_pool"), + attr("pool_type", format!("{:?}", pool_type)), + attr("lp_token", info.sender), + attr("lp_amount", amount), + attr("incentives_share", incentives_share.to_string()), + ])) +} + +fn stake_messages( + config: Config, + height: u64, + lp_token_address: Addr, + amount: Uint128, +) -> StdResult> { + let mut cosmos_msgs = vec![]; + + let generator = config + .generator + .as_ref() + .ok_or_else(|| StdError::generic_err("Generator address hasn't set yet!"))?; + + // TODO: why do we need allowance here, when next message is "send" to a pool + cosmos_msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: lp_token_address.to_string(), + funds: vec![], + msg: to_binary(&Cw20ExecuteMsg::IncreaseAllowance { + spender: generator.to_string(), + amount, + expires: Some(cw20::Expiration::AtHeight(height)), + })?, + })); + + cosmos_msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: lp_token_address.to_string(), + funds: vec![], + msg: to_binary(&Cw20ExecuteMsg::Send { + contract: generator.to_string(), + msg: to_binary(&astroport::generator::Cw20HookMsg::Deposit {})?, + amount, + })?, + })); + + Ok(cosmos_msgs) +} + +/// Hook function to increase Lockup position size when any of the supported LP Tokens are sent to the contract by the user. Returns a default object of type [`Response`]. +/// ## Params +/// * **deps** is an object of type [`DepsMut`]. +/// +/// * **env** is an object of type [`Env`]. +/// +/// * **info** is an object of type [`MessageInfo`]. +/// +/// * **user_address_raw** is an object of type [`Addr`]. User we increase lockup position for +/// +/// * **pool_type** is an object of type [`PoolType`]. LiquidPool type - USDC or ATOM +/// +/// * **duration** is an object of type [`u64`]. Number of seconds the LP token is locked for (lockup period begins post the withdrawal window closure). +/// +/// * **amount** is an object of type [`Uint128`]. Number of LP tokens sent by the user. +pub fn handle_increase_lockup( + deps: DepsMut, + env: Env, + info: MessageInfo, + user_address_raw: String, + pool_type: PoolType, + duration: u64, + amount: Uint128, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + if info.sender != config.auction_contract { + return Err(StdError::generic_err("Unauthorized")); + } + + if env.block.time.seconds() >= config.init_timestamp + config.lock_window { + return Err(StdError::generic_err("Lock window is closed")); + }; + + let user_address = deps.api.addr_validate(&user_address_raw)?; + + if !config + .lockup_rewards_info + .iter() + .any(|i| i.duration == duration) + { + return Err(StdError::generic_err("invalid duration")); + } + + // CHECK ::: LP Token supported or not ? + let mut pool_info = ASSET_POOLS.load(deps.storage, pool_type)?; + let mut user_info = USER_INFO + .may_load(deps.storage, &user_address)? + .unwrap_or_default(); + + // CHECK :: Valid Lockup Duration + if duration > config.max_lock_duration || duration < config.min_lock_duration { + return Err(StdError::generic_err(format!( + "Lockup duration needs to be between {} and {}", + config.min_lock_duration, config.max_lock_duration + ))); + } + + pool_info.weighted_amount = pool_info + .weighted_amount + .checked_add(calculate_weight(amount, duration, &config)?)?; + pool_info.amount_in_lockups = pool_info.amount_in_lockups.checked_add(amount)?; + + let lockup_key = (pool_type, &user_address, duration); + + let lockup_info = + match LOCKUP_INFO.compatible_may_load(deps.as_ref(), lockup_key, &config.generator)? { + Some(mut li) => { + li.lp_units_locked = li.lp_units_locked.checked_add(amount)?; + li + } + None => { + if config.max_positions_per_user == user_info.lockup_positions_index { + return Err(StdError::generic_err(format!( + "Users can only have max {} lockup positions", + config.max_positions_per_user + ))); + } + // Update number of lockup positions the user is having + user_info.lockup_positions_index += 1; + + LockupInfoV2 { + lp_units_locked: amount, + astroport_lp_transferred: None, + ntrn_rewards: Uint128::zero(), + unlock_timestamp: config.init_timestamp + + config.lock_window + + duration + + config.withdrawal_window, + generator_ntrn_debt: Uint128::zero(), + generator_proxy_debt: Default::default(), + withdrawal_flag: false, + } + } + }; + + // SAVE UPDATED STATE + LOCKUP_INFO.save(deps.storage, lockup_key, &lockup_info)?; + + TOTAL_USER_LOCKUP_AMOUNT.update( + deps.storage, + (pool_type, &user_address), + env.block.height, + |lockup_amount| -> StdResult { + if let Some(la) = lockup_amount { + Ok(la.checked_add(amount)?) + } else { + Ok(amount) + } + }, + )?; + + ASSET_POOLS.save(deps.storage, pool_type, &pool_info, env.block.height)?; + USER_INFO.save(deps.storage, &user_address, &user_info)?; + + Ok(Response::new().add_attributes(vec![ + attr("action", "increase_lockup_position"), + attr("pool_type", format!("{:?}", pool_type)), + attr("user", user_address), + attr("duration", duration.to_string()), + attr("amount", amount), + ])) +} + +/// Withdraws LP Tokens from an existing Lockup position. Returns a default object of type [`Response`]. +/// ## Params +/// * **deps** is an object of type [`DepsMut`]. +/// +/// * **env** is an object of type [`Env`]. +/// +/// * **info** is an object of type [`MessageInfo`]. +/// +/// * **pool_type** is an object of type [`PoolType`]. LiquidPool type - USDC or ATOM +/// +/// * **duration** is an object of type [`u64`]. Duration of the lockup position from which withdrawal is to be made. +/// +/// * **amount** is an object of type [`Uint128`]. Number of LP tokens to be withdrawn. +pub fn handle_withdraw_from_lockup( + deps: DepsMut, + env: Env, + info: MessageInfo, + user_address: String, + pool_type: PoolType, + duration: u64, + amount: Uint128, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + if info.sender != config.auction_contract { + return Err(StdError::generic_err("Unauthorized")); + } + + if env.block.time.seconds() + >= config.init_timestamp + config.lock_window + config.withdrawal_window + { + return Err(StdError::generic_err("Withdrawal window is closed")); + }; + + // CHECK :: Valid Withdraw Amount + if amount.is_zero() { + return Err(StdError::generic_err("Invalid withdrawal request")); + } + + let mut pool_info = ASSET_POOLS.load(deps.storage, pool_type)?; + + let user_address = deps.api.addr_validate(&user_address)?; + + // Retrieve Lockup position + let lockup_key = (pool_type, &user_address, duration); + let mut lockup_info = + LOCKUP_INFO.compatible_load(deps.as_ref(), lockup_key, &config.generator)?; + + // CHECK :: Has user already withdrawn LP tokens once post the deposit window closure state + if lockup_info.withdrawal_flag { + return Err(StdError::generic_err( + "Withdrawal already happened. No more withdrawals accepted", + )); + } + + // Check :: Amount should be within the allowed withdrawal limit bounds + let max_withdrawal_percent = + calculate_max_withdrawal_percent_allowed(env.block.time.seconds(), &config); + let max_withdrawal_allowed = lockup_info + .lp_units_locked + .to_decimal() + .checked_mul(max_withdrawal_percent)? + .to_uint_floor(); + if amount > max_withdrawal_allowed { + return Err(StdError::generic_err(format!( + "Amount exceeds maximum allowed withdrawal limit of {}", + max_withdrawal_allowed + ))); + } + + // Update withdrawal flag after the deposit window + if env.block.time.seconds() >= config.init_timestamp + config.lock_window { + lockup_info.withdrawal_flag = true; + } + + // STATE :: RETRIEVE --> UPDATE + lockup_info.lp_units_locked = lockup_info.lp_units_locked.checked_sub(amount)?; + pool_info.weighted_amount = pool_info + .weighted_amount + .checked_sub(calculate_weight(amount, duration, &config)?)?; + pool_info.amount_in_lockups = pool_info.amount_in_lockups.checked_sub(amount)?; + + // Remove Lockup position from the list of user positions if Lp_Locked balance == 0 + if lockup_info.lp_units_locked.is_zero() { + LOCKUP_INFO.remove(deps.storage, lockup_key); + // decrement number of user's lockup positions + let mut user_info = USER_INFO + .may_load(deps.storage, &user_address)? + .unwrap_or_default(); + user_info.lockup_positions_index -= 1; + USER_INFO.save(deps.storage, &user_address, &user_info)?; + } else { + LOCKUP_INFO.save(deps.storage, lockup_key, &lockup_info)?; + } + TOTAL_USER_LOCKUP_AMOUNT.update( + deps.storage, + (pool_type, &user_address), + env.block.height, + |lockup_amount| -> StdResult { + if let Some(la) = lockup_amount { + Ok(la.checked_sub(amount)?) + } else { + Ok(Uint128::zero()) + } + }, + )?; + + // SAVE Updated States + ASSET_POOLS.save(deps.storage, pool_type, &pool_info, env.block.height)?; + + Ok(Response::new().add_attributes(vec![ + attr("action", "withdraw_from_lockup"), + attr("pool_type", pool_type), + attr("user_address", user_address), + attr("duration", duration.to_string()), + attr("amount", amount), + ])) +} + +/// Calculates maximum % of LP balances deposited that can be withdrawn +/// ## Params +/// * **current_timestamp** is an object of type [`u64`]. Current block timestamp +/// +/// * **config** is an object of type [`Config`]. Contract configuration +fn calculate_max_withdrawal_percent_allowed(current_timestamp: u64, config: &Config) -> Decimal { + let withdrawal_cutoff_init_point = config.init_timestamp + config.lock_window; + + // Deposit window :: 100% withdrawals allowed + if current_timestamp < withdrawal_cutoff_init_point { + return Decimal::from_ratio(100u32, 100u32); + } + + let withdrawal_cutoff_second_point = + withdrawal_cutoff_init_point + (config.withdrawal_window / 2u64); + // Deposit window closed, 1st half of withdrawal window :: 50% withdrawals allowed + if current_timestamp <= withdrawal_cutoff_second_point { + return Decimal::from_ratio(50u32, 100u32); + } + + // max withdrawal allowed decreasing linearly from 50% to 0% vs time elapsed + let withdrawal_cutoff_final = withdrawal_cutoff_init_point + config.withdrawal_window; + // Deposit window closed, 2nd half of withdrawal window :: max withdrawal allowed decreases linearly from 50% to 0% vs time elapsed + if current_timestamp < withdrawal_cutoff_final { + let time_left = withdrawal_cutoff_final - current_timestamp; + Decimal::from_ratio( + 50u64 * time_left, + 100u64 * (withdrawal_cutoff_final - withdrawal_cutoff_second_point), + ) + } + // Withdrawals not allowed + else { + Decimal::from_ratio(0u32, 100u32) + } +} + +/// Claims user Rewards for a particular Lockup position. Returns a default object of type [`Response`]. +/// ## Params +/// * **deps** is an object of type [`DepsMut`]. +/// +/// * **env** is an object of type [`Env`]. +/// +/// * **info** is an object of type [`MessageInfo`]. +/// +/// * **pool_type** is an object of type [`PoolType`]. LiquidPool type - USDC or ATOM +/// +/// * **duration** is an object of type [`u64`]. Lockup duration (number of weeks). +/// +/// * **withdraw_lp_stake** is an object of type [`bool`]. Boolean value indicating if the LP tokens are to be withdrawn or not. +pub fn handle_claim_rewards_and_unlock_for_lockup( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + pool_type: PoolType, + duration: u64, + withdraw_lp_stake: bool, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + let state = STATE.load(deps.storage)?; + + if env.block.time.seconds() + < config.init_timestamp + config.lock_window + config.withdrawal_window + { + return Err(StdError::generic_err( + "Lock/withdrawal window is still open", + )); + } + + let user_address = info.sender; + + // CHECK ::: Is LP Token Pool supported or not ? + let pool_info = ASSET_POOLS.load(deps.storage, pool_type)?; + + let mut user_info = USER_INFO + .may_load(deps.storage, &user_address)? + .unwrap_or_default(); + + // If user's total NTRN rewards == 0 :: We update all of the user's lockup positions to calculate NTRN rewards and for each alongwith their equivalent Astroport LP Shares + if user_info.total_ntrn_rewards == Uint128::zero() { + user_info.total_ntrn_rewards = update_user_lockup_positions_and_calc_rewards( + deps.branch(), + &config, + &state, + &user_address, + )?; + } + + USER_INFO.save(deps.storage, &user_address, &user_info)?; + + // Check is there lockup or not ? + let lockup_key = (pool_type, &user_address, duration); + let lockup_info = LOCKUP_INFO.compatible_load(deps.as_ref(), lockup_key, &config.generator)?; + + // CHECK :: Can the Lockup position be unlocked or not ? + if withdraw_lp_stake && env.block.time.seconds() < lockup_info.unlock_timestamp { + return Err(StdError::generic_err(format!( + "{} seconds to unlock", + lockup_info.unlock_timestamp - env.block.time.seconds() + ))); + } + + if lockup_info.astroport_lp_transferred.is_some() { + return Err(StdError::generic_err( + "Astro LP Tokens have already been claimed!", + )); + } + + let mut cosmos_msgs = vec![]; + + let astroport_lp_token = pool_info.lp_token; + + if pool_info.is_staked { + let generator = config + .generator + .as_ref() + .ok_or_else(|| StdError::generic_err("Generator should be set at this moment!"))?; + + // QUERY :: Check if there are any pending staking rewards + let pending_rewards: PendingTokenResponse = deps.querier.query_wasm_smart( + generator, + &GenQueryMsg::PendingToken { + lp_token: astroport_lp_token.to_string(), + user: env.contract.address.to_string(), + }, + )?; + + let pending_on_proxy = &pending_rewards.pending_on_proxy.unwrap_or_default(); + + if !pending_rewards.pending.is_zero() + || pending_on_proxy.iter().any(|asset| !asset.amount.is_zero()) + { + let rwi: RewardInfoResponse = deps.querier.query_wasm_smart( + generator, + &GenQueryMsg::RewardInfo { + lp_token: astroport_lp_token.to_string(), + }, + )?; + + let reward_token_balance = deps + .querier + .query_balance( + env.contract.address.clone(), + rwi.base_reward_token.to_string(), + )? + .amount; + + let prev_proxy_reward_balances: Vec = pending_on_proxy + .iter() + .map(|asset| { + let balance = asset + .info + .query_pool(&deps.querier, env.contract.address.clone()) + .unwrap_or_default(); + + Asset { + info: asset.info.clone(), + amount: balance, + } + }) + .collect(); + + cosmos_msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: generator.to_string(), + funds: vec![], + msg: to_binary(&GenExecuteMsg::ClaimRewards { + lp_tokens: vec![astroport_lp_token.to_string()], + })?, + })); + + cosmos_msgs.push( + CallbackMsg::UpdatePoolOnDualRewardsClaim { + pool_type, + prev_ntrn_balance: reward_token_balance, + prev_proxy_reward_balances, + } + .to_cosmos_msg(&env)?, + ); + } + } else if user_info.ntrn_transferred && !withdraw_lp_stake { + return Err(StdError::generic_err("No rewards available to claim!")); + } + + cosmos_msgs.push( + CallbackMsg::WithdrawUserLockupRewardsCallback { + pool_type, + user_address, + duration, + withdraw_lp_stake, + } + .to_cosmos_msg(&env)?, + ); + + Ok(Response::new().add_messages(cosmos_msgs)) +} + +/// Claims unvested user's airdrop rewards from the Credits Contract plus part of vested tokens (NTRN Lockdrop rewards amount * AIDROP_REWARDS_MULTIPLIER) Returns a [`CosmosMsg`]. +/// ## Params +/// * **deps** is an object of type [`DepsMut`]. +/// +/// * **env** is an object of type [`Env`]. +/// +/// * **credits_contract** is an object of type [`Addr`]. Address of the Credits Contract. +/// +/// * **user_addr** is an object of type [`Addr`]. Address of the user for who claims rewards. +/// +/// * **ntrn_lockdrop_rewards** is an object of type [`Addr`]. Amount of Lockdrop rewards in uNTRN. +pub fn claim_airdrop_tokens_with_multiplier_msg( + deps: Deps, + credits_contract: Addr, + user_addr: Addr, + ntrn_lockdrop_rewards: Uint128, +) -> StdResult { + // unvested tokens amount + let unvested_tokens_amount: credits::msg::WithdrawableAmountResponse = + deps.querier.query_wasm_smart( + &credits_contract, + &credits::msg::QueryMsg::WithdrawableAmount { + address: user_addr.to_string(), + }, + )?; + // vested tokens amount + let vested_tokens_amount: credits::msg::VestedAmountResponse = deps.querier.query_wasm_smart( + &credits_contract, + &credits::msg::QueryMsg::VestedAmount { + address: user_addr.to_string(), + }, + )?; + + let airdrop_rewards_multiplier = Decimal::from_str(AIRDROP_REWARDS_MULTIPLIER)?; + + // either we claim whole vested amount or NTRN lockdrop rewards + let claimable_vested_amount = min( + vested_tokens_amount.amount, + ntrn_lockdrop_rewards + .to_decimal() + .checked_mul(airdrop_rewards_multiplier)? + .to_uint_floor(), + ); + + Ok(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: credits_contract.to_string(), + msg: to_binary(&Cw20ExecuteMsg::BurnFrom { + owner: user_addr.to_string(), + amount: claimable_vested_amount.checked_add(unvested_tokens_amount.amount)?, + })?, + funds: vec![], + })) +} + +/// Updates contract state after dual staking rewards are claimed from the generator contract. Returns a default object of type [`Response`]. +/// ## Params +/// * **deps** is an object of type [`DepsMut`]. +/// +/// * **env** is an object of type [`Env`]. +/// +/// * **pool_type** is an object of type [`PoolType`]. LiquidPool type - USDC or ATOM +/// +/// * **prev_ntrn_balance** is an object of type [`Uint128`]. Contract's NTRN token balance before claim. +/// +/// * **prev_proxy_reward_balances** is a vector of type [`Asset`]. Contract's Generator Proxy reward token balance before claim. +pub fn update_pool_on_dual_rewards_claim( + deps: DepsMut, + env: Env, + pool_type: PoolType, + prev_ntrn_balance: Uint128, + prev_proxy_reward_balances: Vec, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + let mut pool_info = ASSET_POOLS.load(deps.storage, pool_type)?; + + let generator = config + .generator + .as_ref() + .ok_or_else(|| StdError::generic_err("Generator hasn't been set yet!"))?; + let astroport_lp_token = pool_info.lp_token.clone(); + + let rwi: RewardInfoResponse = deps.querier.query_wasm_smart( + generator, + &GenQueryMsg::RewardInfo { + lp_token: astroport_lp_token.to_string(), + }, + )?; + + let lp_balance: Uint128 = deps.querier.query_wasm_smart( + generator, + &GenQueryMsg::Deposit { + lp_token: astroport_lp_token.to_string(), + user: env.contract.address.to_string(), + }, + )?; + + let base_reward_received; + // Increment claimed rewards per LP share + pool_info.generator_ntrn_per_share = pool_info.generator_ntrn_per_share.checked_add({ + let reward_token_balance = deps + .querier + .query_balance( + env.contract.address.clone(), + rwi.base_reward_token.to_string(), + )? + .amount; + base_reward_received = reward_token_balance.checked_sub(prev_ntrn_balance)?; + Decimal::from_ratio(base_reward_received, lp_balance) + })?; + + // Increment claimed Proxy rewards per LP share + for prev_balance in prev_proxy_reward_balances { + let current_balance = prev_balance + .info + .query_pool(&deps.querier, env.contract.address.clone())?; + let received_amount = current_balance.checked_sub(prev_balance.amount)?; + pool_info.generator_proxy_per_share.update( + &prev_balance.info, + Decimal::from_ratio(received_amount, lp_balance), + )?; + } + + // SAVE UPDATED STATE OF THE POOL + ASSET_POOLS.save(deps.storage, pool_type, &pool_info, env.block.height)?; + + Ok(Response::new().add_attributes(vec![ + attr("action", "update_generator_dual_rewards"), + attr("pool_type", format!("{:?}", pool_type)), + attr("NTRN_reward_received", base_reward_received), + attr( + "generator_ntrn_per_share", + pool_info.generator_ntrn_per_share.to_string(), + ), + ])) +} + +/// Withdraws user rewards and LP Tokens after claims / unlocks. Returns a default object of type [`Response`]. +/// ## Params +/// * **deps** is an object of type [`DepsMut`]. +/// +/// * **env** is an object of type [`Env`]. +/// +/// * **pool_type** is an object of type [`PoolType`]. LiquidPool type - USDC or ATOM +/// +/// * **user_address** is an object of type [`Addr`]. User address who is claiming the rewards / unlocking his lockup position. +/// +/// * **duration** is a vector of type [`u64`]. Duration of the lockup for which rewards have been claimed / position unlocked. +/// +/// * **withdraw_lp_stake** is an object of type [`bool`]. Boolean value indicating if the ASTRO LP Tokens are to be sent to the user or not. +pub fn callback_withdraw_user_rewards_for_lockup_optional_withdraw( + deps: DepsMut, + env: Env, + pool_type: PoolType, + user_address: Addr, + duration: u64, + withdraw_lp_stake: bool, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + let mut pool_info = ASSET_POOLS.load(deps.storage, pool_type)?; + let lockup_key = (pool_type, &user_address, duration); + let mut lockup_info = + LOCKUP_INFO.compatible_load(deps.as_ref(), lockup_key, &config.generator)?; + + let mut user_info = USER_INFO + .may_load(deps.storage, &user_address)? + .unwrap_or_default(); + + let mut cosmos_msgs = vec![]; + let mut attributes = vec![ + attr("action", "withdraw_rewards_and_or_unlock"), + attr("pool_type", format!("{:?}", pool_type)), + attr("user_address", &user_address), + attr("duration", duration.to_string()), + ]; + + let astroport_lp_token = pool_info.lp_token.clone(); + + let generator = config + .generator + .as_ref() + .ok_or_else(|| StdError::generic_err("Generator should be set"))?; + + // Calculate Astro LP share for the lockup position + let astroport_lp_amount: Uint128 = { + let balance: Uint128 = if pool_info.is_staked { + deps.querier.query_wasm_smart( + generator, + &GenQueryMsg::Deposit { + lp_token: astroport_lp_token.to_string(), + user: env.contract.address.to_string(), + }, + )? + } else { + let res: BalanceResponse = deps.querier.query_wasm_smart( + astroport_lp_token.clone(), + &Cw20QueryMsg::Balance { + address: env.contract.address.to_string(), + }, + )?; + res.balance + }; + + (lockup_info + .lp_units_locked + .full_mul(balance) + .checked_div(Uint256::from(pool_info.amount_in_lockups))?) + .try_into()? + }; + + // If Astro LP tokens are staked with Astro generator + if pool_info.is_staked { + let rwi: RewardInfoResponse = deps.querier.query_wasm_smart( + generator, + &GenQueryMsg::RewardInfo { + lp_token: astroport_lp_token.to_string(), + }, + )?; + + // Calculate claimable staking rewards for this lockup + let total_lockup_astro_rewards = pool_info + .generator_ntrn_per_share + .checked_mul(astroport_lp_amount.to_decimal())? + .to_uint_floor(); + let pending_astro_rewards = + total_lockup_astro_rewards.checked_sub(lockup_info.generator_ntrn_debt)?; + lockup_info.generator_ntrn_debt = total_lockup_astro_rewards; + + // If claimable staking rewards > 0, claim them + if pending_astro_rewards > Uint128::zero() { + cosmos_msgs.push(CosmosMsg::Bank(BankMsg::Send { + to_address: user_address.to_string(), + amount: vec![Coin { + denom: rwi.base_reward_token.to_string(), + amount: pending_astro_rewards, + }], + })); + } + attributes.push(attr("generator_astro_reward", pending_astro_rewards)); + + let mut pending_proxy_rewards: Vec = vec![]; + // If this LP token is getting dual incentives + // Calculate claimable proxy staking rewards for this lockup + lockup_info.generator_proxy_debt = lockup_info + .generator_proxy_debt + .inner_ref() + .iter() + .map(|(asset, debt)| { + let generator_proxy_per_share = pool_info + .generator_proxy_per_share + .load(asset) + .unwrap_or_default(); + let total_lockup_proxy_reward = + generator_proxy_per_share.checked_mul_uint128(astroport_lp_amount)?; + let pending_proxy_reward: Uint128 = total_lockup_proxy_reward.checked_sub(*debt)?; + + if !pending_proxy_reward.is_zero() { + pending_proxy_rewards.push(Asset { + info: asset.clone(), + amount: pending_proxy_reward, + }); + } + Ok((asset.clone(), total_lockup_proxy_reward)) + }) + .collect::>>()? + .into(); + + // If this is a void transaction (no state change), then return error. + // Void tx scenario = ASTRO already claimed, 0 pending ASTRO staking reward, 0 pending proxy rewards, not unlocking LP tokens in this tx + if !withdraw_lp_stake + && user_info.ntrn_transferred + && pending_astro_rewards == Uint128::zero() + && pending_proxy_rewards.is_empty() + { + return Err(StdError::generic_err("No rewards available to claim!")); + } + + // If claimable proxy staking rewards > 0, claim them + for pending_proxy_reward in pending_proxy_rewards { + cosmos_msgs.push(pending_proxy_reward.into_msg(&deps.querier, user_address.clone())?); + } + + // COSMOSMSG :: If LP Tokens are staked, we unstake the amount which needs to be returned to the user + if withdraw_lp_stake { + cosmos_msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: generator.to_string(), + funds: vec![], + msg: to_binary(&GenExecuteMsg::Withdraw { + lp_token: astroport_lp_token.to_string(), + amount: astroport_lp_amount, + })?, + })); + } + } + + if withdraw_lp_stake { + // COSMOSMSG :: Returns LP units locked by the user in the current lockup position + cosmos_msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: astroport_lp_token.to_string(), + msg: to_binary(&Cw20ExecuteMsg::Transfer { + recipient: user_address.to_string(), + amount: astroport_lp_amount, + })?, + funds: vec![], + })); + pool_info.amount_in_lockups = pool_info + .amount_in_lockups + .checked_sub(lockup_info.lp_units_locked)?; + ASSET_POOLS.save(deps.storage, pool_type, &pool_info, env.block.height)?; + + attributes.push(attr("astroport_lp_unlocked", astroport_lp_amount)); + lockup_info.astroport_lp_transferred = Some(astroport_lp_amount); + TOTAL_USER_LOCKUP_AMOUNT.update( + deps.storage, + (pool_type, &user_address), + env.block.height, + |lockup_amount| -> StdResult { + if let Some(la) = lockup_amount { + Ok(la.checked_sub(lockup_info.lp_units_locked)?) + } else { + Ok(Uint128::zero()) + } + }, + )?; + } + LOCKUP_INFO.save(deps.storage, lockup_key, &lockup_info)?; + + // Transfers claimable one time NTRN rewards to the user that the user gets for all his lock + if !user_info.ntrn_transferred { + // Calculating how much NTRN user can claim (from total one time reward) + let total_claimable_ntrn_rewards = user_info.total_ntrn_rewards; + if total_claimable_ntrn_rewards > Uint128::zero() { + cosmos_msgs.push(CosmosMsg::Bank(BankMsg::Send { + to_address: user_address.to_string(), + amount: coins(total_claimable_ntrn_rewards.u128(), UNTRN_DENOM), + })) + } + + // claim airdrop rewards for airdrop participants + let res: BalanceResponse = deps.querier.query_wasm_smart( + config.credits_contract.clone(), + &Cw20QueryMsg::Balance { + address: user_address.to_string(), + }, + )?; + if res.balance > Uint128::zero() { + cosmos_msgs.push(claim_airdrop_tokens_with_multiplier_msg( + deps.as_ref(), + config.credits_contract, + user_address.clone(), + total_claimable_ntrn_rewards, + )?); + } + + user_info.ntrn_transferred = true; + attributes.push(attr( + "total_claimable_ntrn_reward", + total_claimable_ntrn_rewards, + )); + USER_INFO.save(deps.storage, &user_address, &user_info)?; + } + + Ok(Response::new() + .add_messages(cosmos_msgs) + .add_attributes(attributes)) +} + +/// Returns the contract's State. +/// ## Params +/// * **deps** is an object of type [`Deps`]. +pub fn query_state(deps: Deps) -> StdResult { + let state: State = STATE.load(deps.storage)?; + Ok(StateResponse { + total_incentives_share: state.total_incentives_share, + supported_pairs_list: ASSET_POOLS + .keys(deps.storage, None, None, Order::Ascending) + .collect::, StdError>>()?, + }) +} + +/// Returns the pool's State. +/// ## Params +/// * **deps** is an object of type [`Deps`]. +/// +/// * **pool_type** is an object of type [`PoolType`]. LiquidPool type - USDC or ATOM +pub fn query_pool(deps: Deps, pool_type: PoolType) -> StdResult { + let pool_info: PoolInfo = ASSET_POOLS.load(deps.storage, pool_type)?; + Ok(pool_info) +} + +/// Returns summarized details regarding the user. +/// ## Params +/// * **deps** is an object of type [`Deps`]. +/// +/// * **env** is an object of type [`Env`]. +/// +/// * **user** is an object of type [`String`]. +pub fn query_user_info(deps: Deps, env: Env, user: String) -> StdResult { + let user_address = deps.api.addr_validate(&user)?; + let user_info = USER_INFO + .may_load(deps.storage, &user_address)? + .unwrap_or_default(); + + let mut total_astro_rewards = Uint128::zero(); + let mut lockup_infos = vec![]; + + let mut claimable_generator_astro_debt = Uint128::zero(); + for pool_type in ASSET_POOLS + .keys(deps.storage, None, None, Order::Ascending) + .collect::, StdError>>()? + { + for duration in LOCKUP_INFO + .prefix((pool_type, &user_address)) + .keys(deps.storage, None, None, Order::Ascending) + .collect::, StdError>>()? + { + let lockup_info = query_lockup_info(deps, &env, &user, pool_type, duration)?; + total_astro_rewards = total_astro_rewards.checked_add(lockup_info.ntrn_rewards)?; + claimable_generator_astro_debt = claimable_generator_astro_debt + .checked_add(lockup_info.claimable_generator_astro_debt)?; + lockup_infos.push(lockup_info); + } + } + + Ok(UserInfoResponse { + total_ntrn_rewards: total_astro_rewards, + ntrn_transferred: user_info.ntrn_transferred, + lockup_infos, + claimable_generator_ntrn_debt: claimable_generator_astro_debt, + lockup_positions_index: user_info.lockup_positions_index, + }) +} + +/// Returns summarized details regarding the user with lockups list. +/// ## Params +/// * **deps** is an object of type [`Deps`]. +/// +/// * **_env** is an object of type [`Env`]. +/// +/// * **user** is an object of type [`String`]. +pub fn query_user_info_with_lockups_list( + deps: Deps, + _env: Env, + user: String, +) -> StdResult { + let user_address = deps.api.addr_validate(&user)?; + let user_info = USER_INFO + .may_load(deps.storage, &user_address)? + .unwrap_or_default(); + + let mut lockup_infos = vec![]; + + for pool_type in ASSET_POOLS + .keys(deps.storage, None, None, Order::Ascending) + .collect::, StdError>>()? + { + for duration in LOCKUP_INFO + .prefix((pool_type, &user_address)) + .keys(deps.storage, None, None, Order::Ascending) + .collect::, StdError>>()? + { + lockup_infos.push(LockUpInfoSummary { + pool_type, + duration, + }); + } + } + + Ok(UserInfoWithListResponse { + total_ntrn_rewards: user_info.total_ntrn_rewards, + ntrn_transferred: user_info.ntrn_transferred, + lockup_infos, + lockup_positions_index: user_info.lockup_positions_index, + }) +} + +/// Returns locked amount of LP tokens for the specified user for the specified pool at a specific height. +/// ## Params +/// * **deps** is an object of type [`Deps`]. +/// +/// * **pool_type** is an object of type [`PoolType`]. +/// +/// * **user** is an object of type [`Addr`]. +/// +/// * **height** is an object of type [`u64`]. +pub fn query_user_lockup_total_at_height( + deps: Deps, + pool: PoolType, + user: Addr, + height: u64, +) -> StdResult> { + TOTAL_USER_LOCKUP_AMOUNT.may_load_at_height(deps.storage, (pool, &user), height) +} + +/// Returns a total amount of LP tokens for the specified pool at a specific height. +/// ## Params +/// * **deps** is an object of type [`Deps`]. +/// +/// * **pool_type** is an object of type [`PoolType`]. +/// +/// * **height** is an object of type [`u64`]. +pub fn query_lockup_total_at_height( + deps: Deps, + pool: PoolType, + height: u64, +) -> StdResult> { + if let Some(pool) = ASSET_POOLS.may_load_at_height(deps.storage, pool, height)? { + return Ok(Some(pool.amount_in_lockups)); + } + Ok(Some(Uint128::zero())) +} + +/// Returns summarized details regarding the user +/// ## Params +/// * **deps** is an object of type [`Deps`]. +/// +/// * **env** is an object of type [`Env`]. +/// +/// * **user_address** is an object of type [`&str`]. +/// +/// * **pool_type** is an object of type [`PoolType`]. LiquidPool type - USDC or ATOM +/// +/// * **duration** is an object of type [`u64`]. +pub fn query_lockup_info( + deps: Deps, + env: &Env, + user_address: &str, + pool_type: PoolType, + duration: u64, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + let state = STATE.load(deps.storage)?; + + let user_address = deps.api.addr_validate(user_address)?; + + let lockup_key = (pool_type, &user_address, duration); + let mut pool_info = ASSET_POOLS.load(deps.storage, pool_type)?; + let mut lockup_info = LOCKUP_INFO.compatible_load(deps, lockup_key, &config.generator)?; + + let lockup_astroport_lp_units_opt: Option; + let astroport_lp_token_opt: Addr; + let mut claimable_generator_astro_debt = Uint128::zero(); + let mut claimable_generator_proxy_debt: RestrictedVector = + RestrictedVector::default(); + if let Some(astroport_lp_transferred) = lockup_info.astroport_lp_transferred { + lockup_astroport_lp_units_opt = Some(astroport_lp_transferred); + astroport_lp_token_opt = pool_info.lp_token; + } else { + let astroport_lp_token = pool_info.lp_token; + let pool_astroport_lp_units; + let lockup_astroport_lp_units = { + // Query Astro LP Tokens balance for the pool + pool_astroport_lp_units = if pool_info.is_staked { + raw_generator_deposit( + deps.querier, + config + .generator + .as_ref() + .ok_or_else(|| StdError::generic_err("Should be set!"))?, + astroport_lp_token.as_bytes(), + env.contract.address.as_bytes(), + )? + } else { + raw_balance( + deps.querier, + &astroport_lp_token, + env.contract.address.as_bytes(), + )? + }; + // Calculate Lockup Astro LP shares + (lockup_info + .lp_units_locked + .full_mul(pool_astroport_lp_units) + .checked_div(Uint256::from(pool_info.amount_in_lockups))?) + .try_into()? + }; + lockup_astroport_lp_units_opt = Some(lockup_astroport_lp_units); + astroport_lp_token_opt = astroport_lp_token.clone(); + // If LP tokens are staked, calculate the rewards claimable by the user for this lockup position + if pool_info.is_staked && !lockup_astroport_lp_units.is_zero() { + let generator = config + .generator + .clone() + .ok_or_else(|| StdError::generic_err("Generator should be set at this moment!"))?; + + // QUERY :: Check if there are any pending staking rewards + let pending_rewards: PendingTokenResponse = deps.querier.query_wasm_smart( + &generator, + &GenQueryMsg::PendingToken { + lp_token: astroport_lp_token.to_string(), + user: env.contract.address.to_string(), + }, + )?; + + // Calculate claimable Astro staking rewards for this lockup + pool_info.generator_ntrn_per_share = + pool_info + .generator_ntrn_per_share + .checked_add(Decimal::from_ratio( + pending_rewards.pending, + pool_astroport_lp_units, + ))?; + + let total_lockup_astro_rewards = pool_info + .generator_ntrn_per_share + .checked_mul(lockup_astroport_lp_units.to_decimal())? + .to_uint_floor(); + claimable_generator_astro_debt = + total_lockup_astro_rewards.checked_sub(lockup_info.generator_ntrn_debt)?; + + // Calculate claimable Proxy staking rewards for this lockup + if let Some(pending_on_proxy) = pending_rewards.pending_on_proxy { + for reward in pending_on_proxy { + let generator_proxy_per_share = pool_info.generator_proxy_per_share.update( + &reward.info, + Decimal::from_ratio(reward.amount, pool_astroport_lp_units), + )?; + + let debt = generator_proxy_per_share + .checked_mul_uint128(lockup_astroport_lp_units)? + .checked_sub( + lockup_info + .generator_proxy_debt + .inner_ref() + .iter() + .find_map(|a| if reward.info == a.0 { Some(a.1) } else { None }) + .unwrap_or_default(), + )?; + + claimable_generator_proxy_debt.update(&reward.info, debt)?; + } + } + } + } + // Calculate currently expected ASTRO Rewards if not finalized + if lockup_info.ntrn_rewards == Uint128::zero() { + let weighted_lockup_balance = + calculate_weight(lockup_info.lp_units_locked, duration, &config)?; + lockup_info.ntrn_rewards = calculate_astro_incentives_for_lockup( + weighted_lockup_balance, + pool_info.weighted_amount, + pool_info.incentives_share, + state.total_incentives_share, + config.lockdrop_incentives, + )?; + } + + Ok(LockUpInfoResponse { + pool_type, + lp_units_locked: lockup_info.lp_units_locked, + withdrawal_flag: lockup_info.withdrawal_flag, + ntrn_rewards: lockup_info.ntrn_rewards, + generator_ntrn_debt: lockup_info.generator_ntrn_debt, + claimable_generator_astro_debt, + generator_proxy_debt: lockup_info.generator_proxy_debt, + claimable_generator_proxy_debt, + unlock_timestamp: lockup_info.unlock_timestamp, + astroport_lp_units: lockup_astroport_lp_units_opt, + astroport_lp_token: astroport_lp_token_opt, + astroport_lp_transferred: lockup_info.astroport_lp_transferred, + duration, + }) +} + +/// Calculates ASTRO rewards for a particular Lockup position +/// ## Params +/// * **lockup_weighted_balance** is an object of type [`Uint256`]. Lockup position's weighted terraswap LP balance +/// +/// * **total_weighted_amount** is an object of type [`Uint256`]. Total weighted terraswap LP balance of the Pool +/// +/// * **pool_incentives_share** is an object of type [`u64`]. Share of total ASTRO incentives allocated to this pool +/// +/// * **total_incentives_share** is an object of type [`u64`]. Calculated total incentives share for allocating among pools +/// +/// * **total_lockdrop_incentives** is an object of type [`Uint128`]. Total ASTRO incentives to be distributed among Lockdrop participants +pub fn calculate_astro_incentives_for_lockup( + lockup_weighted_balance: Uint256, + total_weighted_amount: Uint256, + pool_incentives_share: Uint128, + total_incentives_share: Uint128, + total_lockdrop_incentives: Uint128, +) -> StdResult { + if total_incentives_share.is_zero() || total_weighted_amount.is_zero() { + Ok(Uint128::zero()) + } else { + Ok(Decimal256::from_ratio( + Uint256::from(pool_incentives_share).checked_mul(lockup_weighted_balance)?, + Uint256::from(total_incentives_share).checked_mul(total_weighted_amount)?, + ) + .checked_mul_uint256(total_lockdrop_incentives.into())?) + } +} + +/// Returns effective weight for the amount to be used for calculating lockdrop rewards. +/// ## Params +/// * **amount** is an object of type [`Uint128`]. Number of LP tokens. +/// +/// * **duration** is an object of type [`u64`]. Number of seconds. +/// +/// * **config** is an object of type [`Config`]. Config with weekly multiplier and divider. +fn calculate_weight(amount: Uint128, duration: u64, config: &Config) -> StdResult { + if let Some(info) = config + .lockup_rewards_info + .iter() + .find(|info| info.duration == duration) + { + let lock_weight = Decimal256::one() + info.coefficient; + Ok(lock_weight.checked_mul_uint256(amount.into())?.into()) + } else { + Err(StdError::generic_err("invalid duration")) + } +} + +/// Calculates ASTRO rewards for each of the user position. +/// ## Params +/// * **deps** is an object of type [`DepsMut`]. +/// +/// * **config** is an object of type [`Config`]. +/// +/// * **state** is an object of type [`State`]. +/// +/// * **user_address** is an object of type [`Addr`] +fn update_user_lockup_positions_and_calc_rewards( + deps: DepsMut, + config: &Config, + state: &State, + user_address: &Addr, +) -> StdResult { + let mut total_astro_rewards = Uint128::zero(); + + let mut keys: Vec<(PoolType, u64)> = vec![]; + + for pool_type in ASSET_POOLS + .keys(deps.storage, None, None, Order::Ascending) + .collect::, StdError>>()? + { + for duration in LOCKUP_INFO + .prefix((pool_type, user_address)) + .keys(deps.storage, None, None, Order::Ascending) + .collect::, StdError>>()? + { + keys.push((pool_type, duration)); + } + } + for (pool_type, duration) in keys { + let pool_info = ASSET_POOLS.load(deps.storage, pool_type)?; + let lockup_key = (pool_type, user_address, duration); + let mut lockup_info = + LOCKUP_INFO.compatible_load(deps.as_ref(), lockup_key, &config.generator)?; + + if lockup_info.ntrn_rewards == Uint128::zero() { + // Weighted lockup balance (using terraswap LP units to calculate as pool's total weighted balance is calculated on terraswap LP deposits summed over each deposit tx) + let weighted_lockup_balance = + calculate_weight(lockup_info.lp_units_locked, duration, config)?; + + // Calculate ASTRO Lockdrop rewards for the lockup position + lockup_info.ntrn_rewards = calculate_astro_incentives_for_lockup( + weighted_lockup_balance, + pool_info.weighted_amount, + pool_info.incentives_share, + state.total_incentives_share, + config.lockdrop_incentives, + )?; + + LOCKUP_INFO.save(deps.storage, lockup_key, &lockup_info)?; + }; + + let lockup_astro_rewards = lockup_info.ntrn_rewards; + + // Save updated Lockup state + total_astro_rewards = total_astro_rewards.checked_add(lockup_astro_rewards)?; + } + + Ok(total_astro_rewards) +} diff --git a/contracts/lockdrop-pcl/src/lib.rs b/contracts/lockdrop-pcl/src/lib.rs new file mode 100644 index 00000000..43f4f742 --- /dev/null +++ b/contracts/lockdrop-pcl/src/lib.rs @@ -0,0 +1,6 @@ +pub mod contract; +pub mod raw_queries; +pub mod state; + +#[cfg(test)] +mod testing; diff --git a/contracts/lockdrop-pcl/src/migration.rs b/contracts/lockdrop-pcl/src/migration.rs new file mode 100644 index 00000000..6c1544b1 --- /dev/null +++ b/contracts/lockdrop-pcl/src/migration.rs @@ -0,0 +1,77 @@ +use astroport::asset::AssetInfo; +use astroport::generator::{PoolInfoResponse, QueryMsg as GenQueryMsg}; +use astroport_periphery::lockdrop::MigrationInfo; +use cosmwasm_std::{Addr, Decimal, DepsMut, StdResult, Uint128, Uint256}; +use cw_storage_plus::Map; + +use crate::raw_queries::raw_proxy_asset; +use astroport::restricted_vector::RestrictedVector; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct PoolInfoV101 { + pub terraswap_pool: Addr, + pub terraswap_amount_in_lockups: Uint128, + pub migration_info: Option, + /// Share of total ASTRO incentives allocated to this pool + pub incentives_share: u64, + /// Weighted LP Token balance used to calculate ASTRO rewards a particular user can claim + pub weighted_amount: Uint256, + /// Ratio of Generator ASTRO rewards accured to astroport pool share + pub generator_astro_per_share: Decimal, + /// Ratio of Generator Proxy rewards accured to astroport pool share + pub generator_proxy_per_share: Decimal, + /// Boolean value indicating if the LP Tokens are staked with the Generator contract or not + pub is_staked: bool, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct PoolInfoV111 { + pub terraswap_pool: Addr, + pub terraswap_amount_in_lockups: Uint128, + pub migration_info: Option, + /// Share of total ASTRO incentives allocated to this pool + pub incentives_share: u64, + /// Weighted LP Token balance used to calculate ASTRO rewards a particular user can claim + pub weighted_amount: Uint256, + /// Ratio of Generator ASTRO rewards accured to astroport pool share + pub generator_astro_per_share: Decimal, + /// Ratio of Generator Proxy rewards accured to astroport pool share + pub generator_proxy_per_share: Decimal, + /// Boolean value indicating if the LP Tokens are staked with the Generator contract or not + pub is_staked: bool, + /// Flag defines whether the asset has rewards or not + pub has_asset_rewards: bool, +} + +pub const ASSET_POOLS_V101: Map<&Addr, PoolInfoV101> = Map::new("LiquidityPools"); +pub const ASSET_POOLS_V111: Map<&Addr, PoolInfoV111> = Map::new("LiquidityPools"); + +pub fn migrate_generator_proxy_per_share_to_v120( + deps: &DepsMut, + generator_proxy_per_share_old: Decimal, + generator: &Addr, + migration_info: Option, +) -> StdResult> { + let mut generator_proxy_per_share = RestrictedVector::default(); + if !generator_proxy_per_share_old.is_zero() { + let pool_info: PoolInfoResponse = deps.querier.query_wasm_smart( + generator, + &GenQueryMsg::PoolInfo { + lp_token: migration_info + .expect("Should be migrated!") + .astroport_lp_token + .to_string(), + }, + )?; + let (proxy, _) = pool_info + .accumulated_proxy_rewards_per_share + .first() + .expect("Proxy reward should be set!"); + let reward_asset = raw_proxy_asset(deps.querier, generator, proxy.as_bytes())?; + generator_proxy_per_share.update(&reward_asset, generator_proxy_per_share_old)?; + } + + Ok(generator_proxy_per_share) +} diff --git a/contracts/lockdrop-pcl/src/raw_queries.rs b/contracts/lockdrop-pcl/src/raw_queries.rs new file mode 100644 index 00000000..e491fdf4 --- /dev/null +++ b/contracts/lockdrop-pcl/src/raw_queries.rs @@ -0,0 +1,53 @@ +use astroport::asset::AssetInfo; +use cosmwasm_std::{from_slice, Addr, Empty, QuerierWrapper, StdError, StdResult, Uint128}; +use cw_storage_plus::Path; +use serde::Deserialize; + +/// Returns generator deposit of tokens for the specified address +pub fn raw_generator_deposit( + querier: QuerierWrapper, + generator: &Addr, + lp_token: &[u8], + address: &[u8], +) -> StdResult { + #[derive(Deserialize)] + struct UserInfo { + amount: Uint128, + } + + let key: Path = Path::new(b"user_info", &[lp_token, address]); + if let Some(res) = &querier.query_wasm_raw(generator, key.to_vec())? { + let UserInfo { amount } = from_slice(res)?; + Ok(amount) + } else { + Ok(Uint128::zero()) + } +} + +/// Returns balance of tokens for the specified address +pub fn raw_balance(querier: QuerierWrapper, token: &Addr, address: &[u8]) -> StdResult { + let key: Path = Path::new(b"balance", &[address]); + if let Some(res) = &querier.query_wasm_raw(token, key.to_vec())? { + let res: Uint128 = from_slice(res)?; + Ok(res) + } else { + Ok(Uint128::zero()) + } +} + +/// Returns AssetInfo for the specified proxy address from generator storage +pub fn raw_proxy_asset( + querier: QuerierWrapper, + generator: &Addr, + address: &[u8], +) -> StdResult { + let key: Path = Path::new(b"proxy_reward_asset", &[address]); + if let Some(res) = &querier.query_wasm_raw(generator, key.to_vec())? { + let res: AssetInfo = from_slice(res)?; + return Ok(res); + } + Err(StdError::generic_err(format!( + "Proxy asset not found: {}", + String::from_utf8(address.to_vec())? + ))) +} diff --git a/contracts/lockdrop-pcl/src/state.rs b/contracts/lockdrop-pcl/src/state.rs new file mode 100644 index 00000000..5b817721 --- /dev/null +++ b/contracts/lockdrop-pcl/src/state.rs @@ -0,0 +1,113 @@ +use astroport::common::OwnershipProposal; +use astroport::generator::PoolInfoResponse; +use astroport::generator::QueryMsg as GenQueryMsg; +use astroport::restricted_vector::RestrictedVector; +use astroport_periphery::lockdrop::PoolType; +use astroport_periphery::lockdrop::{ + Config, LockupInfoV1, LockupInfoV2, PoolInfo, State, UserInfo, +}; +use astroport_periphery::U64Key; +use cosmwasm_std::{Addr, Deps, StdError, StdResult, Uint128}; +use cw_storage_plus::{Item, Map, SnapshotMap, Strategy}; + +use crate::raw_queries::raw_proxy_asset; + +pub const CONFIG: Item = Item::new("config"); +pub const STATE: Item = Item::new("state"); + +/// Key is an Terraswap LP token address +pub const ASSET_POOLS: SnapshotMap = SnapshotMap::new( + "LiquidityPools", + "LiquitidyPools_checkpoints", + "LiquidityPools_changelog", + Strategy::EveryBlock, +); +/// Key is an user address +pub const USER_INFO: Map<&Addr, UserInfo> = Map::new("users"); +/// Key consists of an Terraswap LP token address, an user address, and a duration +pub const LOCKUP_INFO: Map<(PoolType, &Addr, U64Key), LockupInfoV2> = Map::new("lockup_position"); + +pub const TOTAL_USER_LOCKUP_AMOUNT: SnapshotMap<(PoolType, &Addr), Uint128> = SnapshotMap::new( + "total_user_lockup_info", + "total_user_lockup_info_checkpoints", + "total_lockup_info_changelog", + Strategy::EveryBlock, +); + +/// Old LOCKUP_INFO storage interface for backward compatibility +pub const OLD_LOCKUP_INFO: Map<(PoolType, &Addr, U64Key), LockupInfoV1> = + Map::new("lockup_position"); + +pub trait CompatibleLoader { + fn compatible_load(&self, deps: Deps, key: K, generator: &Option) -> StdResult; + + fn compatible_may_load( + &self, + deps: Deps, + key: K, + generator: &Option, + ) -> StdResult>; +} + +impl CompatibleLoader<(PoolType, &Addr, U64Key), LockupInfoV2> + for Map<'_, (PoolType, &Addr, U64Key), LockupInfoV2> +{ + fn compatible_load( + &self, + deps: Deps, + key: (PoolType, &Addr, U64Key), + generator: &Option, + ) -> StdResult { + self.load(deps.storage, key).or_else(|_| { + let old_lockup_info = OLD_LOCKUP_INFO.load(deps.storage, key)?; + let mut generator_proxy_debt = RestrictedVector::default(); + let generator = generator.as_ref().expect("Generator should be set!"); + + if !old_lockup_info.generator_proxy_debt.is_zero() { + let asset = ASSET_POOLS.load(deps.storage, key.0)?; + let astro_lp = asset.lp_token; + let pool_info: PoolInfoResponse = deps.querier.query_wasm_smart( + generator, + &GenQueryMsg::PoolInfo { + lp_token: astro_lp.to_string(), + }, + )?; + let (proxy, _) = pool_info + .accumulated_proxy_rewards_per_share + .first() + .ok_or_else(|| { + StdError::generic_err(format!("Proxy rewards not found: {}", astro_lp)) + })?; + let reward_asset = raw_proxy_asset(deps.querier, generator, proxy.as_bytes())?; + + generator_proxy_debt.update(&reward_asset, old_lockup_info.generator_proxy_debt)?; + } + + let lockup_info = LockupInfoV2 { + lp_units_locked: old_lockup_info.lp_units_locked, + astroport_lp_transferred: old_lockup_info.astroport_lp_transferred, + withdrawal_flag: old_lockup_info.withdrawal_flag, + ntrn_rewards: old_lockup_info.ntrn_rewards, + generator_ntrn_debt: old_lockup_info.generator_ntrn_debt, + generator_proxy_debt, + unlock_timestamp: old_lockup_info.unlock_timestamp, + }; + + Ok(lockup_info) + }) + } + + fn compatible_may_load( + &self, + deps: Deps, + key: (PoolType, &Addr, U64Key), + generator: &Option, + ) -> StdResult> { + if !OLD_LOCKUP_INFO.has(deps.storage, key) { + return Ok(None); + } + Some(self.compatible_load(deps, key, generator)).transpose() + } +} + +pub const OWNERSHIP_PROPOSAL: Item = Item::new("ownership_proposal"); diff --git a/contracts/lockdrop-pcl/src/testing.rs b/contracts/lockdrop-pcl/src/testing.rs new file mode 100644 index 00000000..526c0362 --- /dev/null +++ b/contracts/lockdrop-pcl/src/testing.rs @@ -0,0 +1,149 @@ +use crate::contract::{execute, instantiate, query, UNTRN_DENOM}; +use astroport_periphery::lockdrop::{ + Config, ExecuteMsg, InstantiateMsg, LockupRewardsInfo, QueryMsg, +}; +use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; +use cosmwasm_std::{coin, from_binary, Addr, Decimal256, StdError, Uint128}; + +#[test] +fn update_owner() { + let mut deps = mock_dependencies(); + let info = mock_info("addr0000", &[]); + + let owner = Addr::unchecked("owner"); + let token_info_manager = Addr::unchecked("token_info_manager"); + + let env = mock_env(); + + let msg = InstantiateMsg { + owner: Some(owner.to_string()), + token_info_manager: token_info_manager.to_string(), + init_timestamp: env.block.time.seconds(), + lock_window: 10_000_000, + withdrawal_window: 500_000, + min_lock_duration: 1u64, + max_lock_duration: 52u64, + max_positions_per_user: 14, + credits_contract: "credit_contract".to_string(), + lockup_rewards_info: vec![LockupRewardsInfo { + duration: 1, + coefficient: Decimal256::zero(), + }], + auction_contract: "auction_contract".to_string(), + }; + + // We can just call .unwrap() to assert this was a success + instantiate(deps.as_mut(), env, info, msg).unwrap(); + + let new_owner = String::from("new_owner"); + + // BNew owner + let env = mock_env(); + let msg = ExecuteMsg::ProposeNewOwner { + owner: new_owner.clone(), + expires_in: 100, // seconds + }; + + let info = mock_info(new_owner.as_str(), &[]); + + // Unauthorized check + let err = execute(deps.as_mut(), env.clone(), info, msg.clone()).unwrap_err(); + assert_eq!(err.to_string(), "Generic error: Unauthorized"); + + // Claim before a proposal + let info = mock_info(new_owner.as_str(), &[]); + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::ClaimOwnership {}, + ) + .unwrap_err(); + + // Propose new owner + let info = mock_info(owner.as_str(), &[]); + let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap(); + assert_eq!(0, res.messages.len()); + + // Unauthorized ownership claim + let info = mock_info("invalid_addr", &[]); + let err = execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::ClaimOwnership {}, + ) + .unwrap_err(); + assert_eq!(err.to_string(), "Generic error: Unauthorized"); + + // Claim ownership + let info = mock_info(new_owner.as_str(), &[]); + let res = execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::ClaimOwnership {}, + ) + .unwrap(); + assert_eq!(0, res.messages.len()); + + // Let's query the state + let config: Config = + from_binary(&query(deps.as_ref(), env, QueryMsg::Config {}).unwrap()).unwrap(); + assert_eq!(new_owner, config.owner); +} + +#[test] +fn increase_ntrn_incentives() { + let mut deps = mock_dependencies(); + let info = mock_info("addr0000", &[]); + + let owner = Addr::unchecked("owner"); + let token_info_manager = Addr::unchecked("token_info_manager"); + + let env = mock_env(); + + let msg = InstantiateMsg { + owner: Some(owner.to_string()), + token_info_manager: token_info_manager.to_string(), + init_timestamp: env.block.time.seconds(), + lock_window: 10_000_000, + withdrawal_window: 500_000, + min_lock_duration: 1u64, + max_lock_duration: 52u64, + max_positions_per_user: 14, + credits_contract: "credit_contract".to_string(), + auction_contract: "auction_contract".to_string(), + lockup_rewards_info: vec![LockupRewardsInfo { + duration: 1, + coefficient: Decimal256::zero(), + }], + }; + + // We can just call .unwrap() to assert this was a success + instantiate(deps.as_mut(), env, info, msg).unwrap(); + + let env = mock_env(); + let msg = ExecuteMsg::IncreaseNTRNIncentives {}; + + let info = mock_info(owner.as_str(), &[coin(100u128, UNTRN_DENOM)]); + + let res = execute(deps.as_mut(), env.clone(), info, msg); + assert!(res.is_ok()); + + let config: Config = + from_binary(&query(deps.as_ref(), env, QueryMsg::Config {}).unwrap()).unwrap(); + assert_eq!(Uint128::new(100u128), config.lockdrop_incentives); + + // invalid coin + let env = mock_env(); + let msg = ExecuteMsg::IncreaseNTRNIncentives {}; + + let info = mock_info(owner.as_str(), &[coin(100u128, "DENOM")]); + + let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); + assert_eq!( + err, + StdError::generic_err(format!("{} is not found", UNTRN_DENOM)) + ); +} diff --git a/packages/astroport_periphery/src/lib.rs b/packages/astroport_periphery/src/lib.rs index 37ab064d..9cd6ad7f 100644 --- a/packages/astroport_periphery/src/lib.rs +++ b/packages/astroport_periphery/src/lib.rs @@ -2,6 +2,7 @@ pub mod airdrop; pub mod auction; pub mod helpers; pub mod lockdrop; +pub mod lockdrop_pcl; pub mod pricefeed; pub mod simple_airdrop; pub mod utils; diff --git a/packages/astroport_periphery/src/lockdrop_pcl.rs b/packages/astroport_periphery/src/lockdrop_pcl.rs new file mode 100644 index 00000000..6dcbd342 --- /dev/null +++ b/packages/astroport_periphery/src/lockdrop_pcl.rs @@ -0,0 +1,426 @@ +use astroport::asset::{Asset, AssetInfo}; +use astroport::restricted_vector::RestrictedVector; +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{ + to_binary, Addr, CosmosMsg, Decimal, Decimal256, Env, StdError, StdResult, Uint128, Uint256, + WasmMsg, +}; +use cw20::Cw20ReceiveMsg; +use cw_storage_plus::{Key, KeyDeserialize, Prefixer, PrimaryKey}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +// TODO: implement display trait +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, Copy)] +pub enum PoolType { + USDC, + ATOM, +} + +#[allow(clippy::from_over_into)] +impl Into for PoolType { + fn into(self) -> String { + match self { + PoolType::USDC => "usdc".to_string(), + PoolType::ATOM => "atom".to_string(), + } + } +} + +impl PoolType { + fn bytes(&self) -> &[u8] { + match self { + PoolType::USDC => "usdc".as_bytes(), + PoolType::ATOM => "atom".as_bytes(), + } + } +} + +impl KeyDeserialize for PoolType { + type Output = PoolType; + + fn from_vec(value: Vec) -> StdResult { + match value.as_slice() { + b"usdc" => Ok(PoolType::USDC), + b"atom" => Ok(PoolType::ATOM), + _ => Err(StdError::generic_err("Invalid PoolType")), + } + } +} + +impl<'a> PrimaryKey<'a> for PoolType { + type Prefix = (); + type SubPrefix = (); + type Suffix = Self; + type SuperSuffix = Self; + + fn key(&self) -> Vec { + vec![Key::Ref(self.bytes())] + } +} + +impl<'a> Prefixer<'a> for PoolType { + fn prefix(&self) -> Vec { + vec![Key::Ref(self.bytes())] + } +} + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct InstantiateMsg { + /// Account which can update config + pub owner: Option, + /// Account which can update token addresses and generator + pub token_info_manager: String, + /// Credits contract address + pub credits_contract: String, + /// Auction contract address + pub auction_contract: String, + /// Timestamp when Contract will start accepting LP Token deposits + pub init_timestamp: u64, + /// Number of seconds during which lockup deposits will be accepted + pub lock_window: u64, + /// Withdrawal Window Length :: Post the deposit window + pub withdrawal_window: u64, + /// Min. no. of weeks allowed for lockup + pub min_lock_duration: u64, + /// Max. no. of weeks allowed for lockup + pub max_lock_duration: u64, + /// Max lockup positions a user can have + pub max_positions_per_user: u32, + /// Describes rewards coefficients for each lockup duration + pub lockup_rewards_info: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct UpdateConfigMsg { + /// Bootstrap Auction contract address + pub auction_contract_address: Option, + /// Generator (Staking for dual rewards) contract address + pub generator_address: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + IncreaseLockupFor { + user_address: String, + pool_type: PoolType, + amount: Uint128, + duration: u64, + }, + // Receive hook used to accept LP Token deposits + Receive(Cw20ReceiveMsg), + #[serde(rename = "increase_ntrn_incentives")] + IncreaseNTRNIncentives {}, + // ADMIN Function ::: To update configuration + UpdateConfig { + new_config: UpdateConfigMsg, + }, + SetTokenInfo { + atom_token: String, + usdc_token: String, + generator: String, + }, + // Function to facilitate LP Token withdrawals from lockups + WithdrawFromLockup { + user_address: String, + pool_type: PoolType, + duration: u64, + amount: Uint128, + }, + + // ADMIN Function ::: To Migrate liquidity from terraswap to astroport + // MigrateLiquidity { + // terraswap_lp_token: String, + // astroport_pool_addr: String, + // slippage_tolerance: Option, + // }, + // ADMIN Function ::: To stake LP Tokens with the generator contract + // StakeLpTokens { + // pool_type: PoolType, + // }, + // Facilitates ASTRO reward withdrawal which have not been delegated to bootstrap auction along with optional Unlock (can be forceful) + // If withdraw_lp_stake is true and force_unlock is false, it Unlocks the lockup position if its lockup duration has concluded + // If both withdraw_lp_stake and force_unlock are true, it forcefully unlocks the positon. user needs to approve ASTRO Token to + // be transferred by the lockdrop contract to itself for forceful unlock + ClaimRewardsAndOptionallyUnlock { + pool_type: PoolType, + duration: u64, + withdraw_lp_stake: bool, + }, + /// Callbacks; only callable by the contract itself. + Callback(CallbackMsg), + /// ProposeNewOwner creates a proposal to change contract ownership. + /// The validity period for the proposal is set in the `expires_in` variable. + ProposeNewOwner { + /// Newly proposed contract owner + owner: String, + /// The date after which this proposal expires + expires_in: u64, + }, + /// DropOwnershipProposal removes the existing offer to change contract ownership. + DropOwnershipProposal {}, + /// Used to claim contract ownership. + ClaimOwnership {}, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum Cw20HookMsg { + // Called by the bootstrap auction contract when liquidity is added to the + // Pool to enable ASTRO withdrawals by users + // EnableClaims {}, + // ADMIN Function ::: Add new Pool (Only Terraswap Pools) + InitializePool { + pool_type: PoolType, + incentives_share: Uint128, + }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum CallbackMsg { + UpdatePoolOnDualRewardsClaim { + pool_type: PoolType, + prev_ntrn_balance: Uint128, + prev_proxy_reward_balances: Vec, + }, + WithdrawUserLockupRewardsCallback { + pool_type: PoolType, + user_address: Addr, + duration: u64, + withdraw_lp_stake: bool, + }, + // WithdrawLiquidityFromTerraswapCallback { + // terraswap_lp_token: Addr, + // astroport_pool: Addr, + // prev_assets: [terraswap::asset::Asset; 2], + // slippage_tolerance: Option, + // }, +} + +// Modified from +// https://github.com/CosmWasm/cosmwasm-plus/blob/v0.2.3/packages/cw20/src/receiver.rs#L15 +impl CallbackMsg { + pub fn to_cosmos_msg(self, env: &Env) -> StdResult { + Ok(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: env.contract.address.to_string(), + msg: to_binary(&ExecuteMsg::Callback(self))?, + funds: vec![], + })) + } +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(Config)] + Config {}, + #[returns(StateResponse)] + State {}, + #[returns(PoolInfo)] + Pool { pool_type: PoolType }, + #[returns(UserInfoResponse)] + UserInfo { address: String }, + #[returns(UserInfoWithListResponse)] + UserInfoWithLockupsList { address: String }, + #[returns(LockUpInfoResponse)] + LockUpInfo { + user_address: String, + pool_type: PoolType, + duration: u64, + }, + #[returns(Option)] + QueryUserLockupTotalAtHeight { + pool_type: PoolType, + user_address: String, + height: u64, + }, + #[returns(Option)] + QueryLockupTotalAtHeight { pool_type: PoolType, height: u64 }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct MigrateMsg {} + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema)] +pub struct MigrationInfo { + pub terraswap_migrated_amount: Uint128, + pub astroport_lp_token: Addr, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct LockupRewardsInfo { + pub duration: u64, + pub coefficient: Decimal256, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct Config { + /// Account which can update the config + pub owner: Addr, + /// Account which can update the generator and token addresses + pub token_info_manager: Addr, + /// Credits contract address + pub credits_contract: Addr, + /// Bootstrap Auction contract address + pub auction_contract: Addr, + /// Generator (Staking for dual rewards) contract address + pub generator: Option, + /// Timestamp when Contract will start accepting LP Token deposits + pub init_timestamp: u64, + /// Number of seconds during which lockup positions be accepted + pub lock_window: u64, + /// Withdrawal Window Length :: Post the deposit window + pub withdrawal_window: u64, + /// Min. no. of weeks allowed for lockup + pub min_lock_duration: u64, + /// Max. no. of weeks allowed for lockup + pub max_lock_duration: u64, + /// Total NTRN lockdrop incentives to be distributed among the users + pub lockdrop_incentives: Uint128, + /// Max lockup positions a user can have + pub max_positions_per_user: u32, + /// Describes rewards coefficients for each lockup duration + pub lockup_rewards_info: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, Default)] +pub struct State { + /// Total NTRN incentives share + pub total_incentives_share: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct PoolInfo { + pub lp_token: Addr, + pub amount_in_lockups: Uint128, + // pub migration_info: Option, + /// Share of total NTRN incentives allocated to this pool + pub incentives_share: Uint128, + /// Weighted LP Token balance used to calculate NTRN rewards a particular user can claim + pub weighted_amount: Uint256, + /// Ratio of Generator NTRN rewards accured to astroport pool share + pub generator_ntrn_per_share: Decimal, + /// Ratio of Generator Proxy rewards accured to astroport pool share + pub generator_proxy_per_share: RestrictedVector, + /// Boolean value indicating if the LP Tokens are staked with the Generator contract or not + pub is_staked: bool, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, Default)] +pub struct UserInfo { + /// Total NTRN tokens user received as rewards for participation in the lockdrop + pub total_ntrn_rewards: Uint128, + /// NTRN tokens transferred to user + pub ntrn_transferred: bool, + /// Number of lockup positions the user is having + pub lockup_positions_index: u32, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct LockupInfoV1 { + /// Terraswap LP units locked by the user + pub lp_units_locked: Uint128, + pub astroport_lp_transferred: Option, + /// Boolean value indicating if the user's has withdrawn funds post the only 1 withdrawal limit cutoff + pub withdrawal_flag: bool, + /// NTRN tokens received as rewards for participation in the lockdrop + pub ntrn_rewards: Uint128, + /// Generator NTRN tokens loockup received as generator rewards + pub generator_ntrn_debt: Uint128, + /// Generator Proxy tokens lockup received as generator rewards + pub generator_proxy_debt: Uint128, + /// Timestamp beyond which this position can be unlocked + pub unlock_timestamp: u64, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct LockupInfoV2 { + /// Terraswap LP units locked by the user + pub lp_units_locked: Uint128, + pub astroport_lp_transferred: Option, + /// Boolean value indicating if the user's has withdrawn funds post the only 1 withdrawal limit cutoff + pub withdrawal_flag: bool, + /// NTRN tokens received as rewards for participation in the lockdrop + pub ntrn_rewards: Uint128, + /// Generator NTRN tokens loockup received as generator rewards + pub generator_ntrn_debt: Uint128, + /// Generator Proxy tokens lockup received as generator rewards + pub generator_proxy_debt: RestrictedVector, + /// Timestamp beyond which this position can be unlocked + pub unlock_timestamp: u64, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct StateResponse { + /// Total NTRN incentives share + pub total_incentives_share: Uint128, + /// Vector containing LP addresses for all the supported LP Pools + pub supported_pairs_list: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct UserInfoResponse { + /// Total NTRN tokens user received as rewards for participation in the lockdrop + pub total_ntrn_rewards: Uint128, + /// NTRN tokens transferred to user + pub ntrn_transferred: bool, + /// Lockup positions + pub lockup_infos: Vec, + /// NTRN tokens receivable as generator rewards that user can claim + pub claimable_generator_ntrn_debt: Uint128, + /// Number of lockup positions the user is having + pub lockup_positions_index: u32, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct UserInfoWithListResponse { + /// Total NTRN tokens user received as rewards for participation in the lockdrop + pub total_ntrn_rewards: Uint128, + /// NTRN tokens transferred to user + pub ntrn_transferred: bool, + /// Lockup positions + pub lockup_infos: Vec, + /// Number of lockup positions the user is having + pub lockup_positions_index: u32, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct LockUpInfoSummary { + pub pool_type: PoolType, + pub duration: u64, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct LockUpInfoResponse { + /// Terraswap LP token + pub pool_type: PoolType, + /// Terraswap LP units locked by the user + pub lp_units_locked: Uint128, + /// Boolean value indicating if the user's has withdrawn funds post the only 1 withdrawal limit cutoff + pub withdrawal_flag: bool, + /// NTRN tokens received as rewards for participation in the lockdrop + pub ntrn_rewards: Uint128, + pub duration: u64, + /// Generator NTRN tokens lockup received as generator rewards + pub generator_ntrn_debt: Uint128, + /// ASTRO tokens receivable as generator rewards that user can claim + pub claimable_generator_astro_debt: Uint128, + /// Generator Proxy tokens lockup received as generator rewards + pub generator_proxy_debt: RestrictedVector, + /// Proxy tokens receivable as generator rewards that user can claim + pub claimable_generator_proxy_debt: RestrictedVector, + /// Timestamp beyond which this position can be unlocked + pub unlock_timestamp: u64, + /// User's Astroport LP units, calculated as lp_units_locked (terraswap) / total LP units locked (terraswap) * Astroport LP units minted post migration + pub astroport_lp_units: Option, + pub astroport_lp_token: Addr, + pub astroport_lp_transferred: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct PendingAssetRewardResponse { + pub amount: Uint128, +} From 44542c8bac16047f8f0d484e29d2c682aec1b1fd Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Mon, 29 Jan 2024 11:04:16 -0300 Subject: [PATCH 02/30] recommit migration mechanism w proper base branch --- Cargo.lock | 90 +- Cargo.toml | 2 + contracts/vesting-lp-pcl/.cargo/config | 6 + contracts/vesting-lp-pcl/Cargo.toml | 28 + .../examples/vesting-lp_schema.rs | 12 + .../vesting-lp-pcl/schema/raw/execute.json | 480 ++++++ .../schema/raw/instantiate.json | 29 + .../vesting-lp-pcl/schema/raw/migrate.json | 7 + .../vesting-lp-pcl/schema/raw/query.json | 290 ++++ .../raw/response_to_available_amount.json | 6 + .../schema/raw/response_to_config.json | 128 ++ .../raw/response_to_historical_extension.json | 59 + .../raw/response_to_managed_extension.json | 6 + .../schema/raw/response_to_timestamp.json | 7 + .../raw/response_to_vesting_account.json | 119 ++ .../raw/response_to_vesting_accounts.json | 136 ++ .../schema/raw/response_to_vesting_state.json | 35 + .../response_to_with_managers_extension.json | 21 + .../vesting-lp-pcl/schema/vesting-lp.json | 1331 +++++++++++++++++ contracts/vesting-lp-pcl/src/contract.rs | 46 + contracts/vesting-lp-pcl/src/lib.rs | 5 + contracts/vesting-lp-pcl/src/msg.rs | 12 + .../vesting-lp-pcl/src/tests/integration.rs | 1281 ++++++++++++++++ contracts/vesting-lp-pcl/src/tests/mod.rs | 1 + contracts/vesting-lp/.cargo/config | 2 +- contracts/vesting-lp/Cargo.toml | 18 +- .../vesting-lp/examples/vesting_schema.rs | 12 + contracts/vesting-lp/src/contract.rs | 15 +- contracts/vesting-lp/src/lib.rs | 3 - packages/vesting-base-lp/Cargo.toml | 26 + packages/vesting-base-lp/NOTICE | 14 + packages/vesting-base-lp/README.md | 251 ++++ packages/vesting-base-lp/src/builder.rs | 60 + packages/vesting-base-lp/src/error.rs | 65 + .../vesting-base-lp/src/ext_historical.rs | 101 ++ packages/vesting-base-lp/src/ext_managed.rs | 121 ++ .../vesting-base-lp/src/ext_with_managers.rs | 105 ++ packages/vesting-base-lp/src/handlers.rs | 819 ++++++++++ packages/vesting-base-lp/src/lib.rs | 10 + packages/vesting-base-lp/src/msg.rs | 210 +++ packages/vesting-base-lp/src/state.rs | 166 ++ packages/vesting-base-lp/src/types.rs | 130 ++ packages/vesting-base-pcl/Cargo.toml | 23 + packages/vesting-base-pcl/NOTICE | 14 + packages/vesting-base-pcl/README.md | 251 ++++ packages/vesting-base-pcl/src/builder.rs | 60 + packages/vesting-base-pcl/src/error.rs | 47 + .../vesting-base-pcl/src/ext_historical.rs | 101 ++ packages/vesting-base-pcl/src/ext_managed.rs | 121 ++ .../vesting-base-pcl/src/ext_with_managers.rs | 105 ++ packages/vesting-base-pcl/src/handlers.rs | 508 +++++++ packages/vesting-base-pcl/src/lib.rs | 13 + packages/vesting-base-pcl/src/msg.rs | 166 ++ packages/vesting-base-pcl/src/state.rs | 162 ++ packages/vesting-base-pcl/src/testing.rs | 484 ++++++ packages/vesting-base-pcl/src/types.rs | 107 ++ 56 files changed, 8406 insertions(+), 21 deletions(-) create mode 100644 contracts/vesting-lp-pcl/.cargo/config create mode 100644 contracts/vesting-lp-pcl/Cargo.toml create mode 100644 contracts/vesting-lp-pcl/examples/vesting-lp_schema.rs create mode 100644 contracts/vesting-lp-pcl/schema/raw/execute.json create mode 100644 contracts/vesting-lp-pcl/schema/raw/instantiate.json create mode 100644 contracts/vesting-lp-pcl/schema/raw/migrate.json create mode 100644 contracts/vesting-lp-pcl/schema/raw/query.json create mode 100644 contracts/vesting-lp-pcl/schema/raw/response_to_available_amount.json create mode 100644 contracts/vesting-lp-pcl/schema/raw/response_to_config.json create mode 100644 contracts/vesting-lp-pcl/schema/raw/response_to_historical_extension.json create mode 100644 contracts/vesting-lp-pcl/schema/raw/response_to_managed_extension.json create mode 100644 contracts/vesting-lp-pcl/schema/raw/response_to_timestamp.json create mode 100644 contracts/vesting-lp-pcl/schema/raw/response_to_vesting_account.json create mode 100644 contracts/vesting-lp-pcl/schema/raw/response_to_vesting_accounts.json create mode 100644 contracts/vesting-lp-pcl/schema/raw/response_to_vesting_state.json create mode 100644 contracts/vesting-lp-pcl/schema/raw/response_to_with_managers_extension.json create mode 100644 contracts/vesting-lp-pcl/schema/vesting-lp.json create mode 100644 contracts/vesting-lp-pcl/src/contract.rs create mode 100644 contracts/vesting-lp-pcl/src/lib.rs create mode 100644 contracts/vesting-lp-pcl/src/msg.rs create mode 100644 contracts/vesting-lp-pcl/src/tests/integration.rs create mode 100644 contracts/vesting-lp-pcl/src/tests/mod.rs create mode 100644 contracts/vesting-lp/examples/vesting_schema.rs create mode 100644 packages/vesting-base-lp/Cargo.toml create mode 100644 packages/vesting-base-lp/NOTICE create mode 100644 packages/vesting-base-lp/README.md create mode 100644 packages/vesting-base-lp/src/builder.rs create mode 100644 packages/vesting-base-lp/src/error.rs create mode 100644 packages/vesting-base-lp/src/ext_historical.rs create mode 100644 packages/vesting-base-lp/src/ext_managed.rs create mode 100644 packages/vesting-base-lp/src/ext_with_managers.rs create mode 100644 packages/vesting-base-lp/src/handlers.rs create mode 100644 packages/vesting-base-lp/src/lib.rs create mode 100644 packages/vesting-base-lp/src/msg.rs create mode 100644 packages/vesting-base-lp/src/state.rs create mode 100644 packages/vesting-base-lp/src/types.rs create mode 100644 packages/vesting-base-pcl/Cargo.toml create mode 100644 packages/vesting-base-pcl/NOTICE create mode 100644 packages/vesting-base-pcl/README.md create mode 100644 packages/vesting-base-pcl/src/builder.rs create mode 100644 packages/vesting-base-pcl/src/error.rs create mode 100644 packages/vesting-base-pcl/src/ext_historical.rs create mode 100644 packages/vesting-base-pcl/src/ext_managed.rs create mode 100644 packages/vesting-base-pcl/src/ext_with_managers.rs create mode 100644 packages/vesting-base-pcl/src/handlers.rs create mode 100644 packages/vesting-base-pcl/src/lib.rs create mode 100644 packages/vesting-base-pcl/src/msg.rs create mode 100644 packages/vesting-base-pcl/src/state.rs create mode 100644 packages/vesting-base-pcl/src/testing.rs create mode 100644 packages/vesting-base-pcl/src/types.rs diff --git a/Cargo.lock b/Cargo.lock index cca8d87b..bad6b6a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,6 +59,20 @@ dependencies = [ "uint", ] +[[package]] +name = "astroport" +version = "2.8.0" +source = "git+https://github.com/astroport-fi/astroport-core.git?tag=v2.8.0#3b44a4044b823a145730f66ffaf7ae4205b2cd35" +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-factory" version = "1.5.0" @@ -101,7 +115,7 @@ dependencies = [ "astroport-token", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test", + "cw-multi-test 0.16.5", "cw-storage-plus 1.1.0", "cw2 1.1.1", "cw20 1.1.1", @@ -416,6 +430,25 @@ dependencies = [ "obi", ] +[[package]] +name = "cw-multi-test" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8e81b4a7821d5eeba0d23f737c16027b39a600742ca8c32eb980895ffd270f4" +dependencies = [ + "anyhow", + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus 0.15.1", + "cw-utils 0.15.1", + "derivative", + "itertools 0.10.5", + "prost", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "cw-multi-test" version = "0.16.5" @@ -642,7 +675,7 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "credits", - "cw-multi-test", + "cw-multi-test 0.16.5", "cw-storage-plus 1.1.0", "cw2 1.1.1", "cw20 1.1.1", @@ -1492,6 +1525,36 @@ dependencies = [ "thiserror", ] +[[package]] +name = "vesting-base-lp" +version = "1.1.0" +dependencies = [ + "astroport 2.8.0", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "cw-utils 0.15.1", + "cw2 0.15.1", + "cw20 0.15.1", + "serde", + "thiserror", + "vesting-base", + "vesting-base-pcl", +] + +[[package]] +name = "vesting-base-pcl" +version = "1.1.0" +dependencies = [ + "astroport 2.0.0", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.1.0", + "cw-utils 0.15.1", + "cw20 1.1.1", + "thiserror", +] + [[package]] name = "vesting-investors" version = "1.1.1" @@ -1500,7 +1563,7 @@ dependencies = [ "astroport-token", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test", + "cw-multi-test 0.16.5", "cw-utils 0.15.1", "cw2 1.1.1", "cw20 1.1.1", @@ -1510,12 +1573,29 @@ dependencies = [ [[package]] name = "vesting-lp" version = "1.1.0" +dependencies = [ + "astroport 2.8.0", + "astroport-token", + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test 0.15.1", + "cw-storage-plus 0.15.1", + "cw-utils 0.15.1", + "cw2 0.15.1", + "cw20 0.15.1", + "vesting-base", + "vesting-base-lp", +] + +[[package]] +name = "vesting-lp-pcl" +version = "1.1.0" dependencies = [ "astroport 2.0.0", "astroport-token", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test", + "cw-multi-test 0.16.5", "cw-utils 0.15.1", "cw2 1.1.1", "cw20 1.1.1", @@ -1530,7 +1610,7 @@ dependencies = [ "astroport-token", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test", + "cw-multi-test 0.16.5", "cw-utils 0.15.1", "cw2 1.1.1", "cw20 1.1.1", diff --git a/Cargo.toml b/Cargo.toml index 23499fe1..a0156b36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "contracts/lockdrop-pcl", "contracts/credits", "contracts/vesting-lp", + "contracts/vesting-lp-pcl", "contracts/vesting-lti", "contracts/vesting-investors", "contracts/cw20-merkle-airdrop", @@ -34,6 +35,7 @@ cosmwasm-schema = { version = "1.4.1", default-features = false } astroport = { path = "packages/astroport", default-features = false } astroport-periphery = { path = "packages/astroport_periphery" } vesting-base = { path = "packages/vesting-base" } +vesting-base-pcl = { path = "packages/vesting-base-pcl" } # setting cw-multi-test to 0.17.0 enables cosmwasm_1_1, we don't want that cw-multi-test = "0.16.5" itertools = "0.11.0" diff --git a/contracts/vesting-lp-pcl/.cargo/config b/contracts/vesting-lp-pcl/.cargo/config new file mode 100644 index 00000000..f2fbc6a9 --- /dev/null +++ b/contracts/vesting-lp-pcl/.cargo/config @@ -0,0 +1,6 @@ +[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 vesting-lp_schema" diff --git a/contracts/vesting-lp-pcl/Cargo.toml b/contracts/vesting-lp-pcl/Cargo.toml new file mode 100644 index 00000000..5e42fd04 --- /dev/null +++ b/contracts/vesting-lp-pcl/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "vesting-lp-pcl" +version = "1.1.0" +authors = ["Neutron"] +edition = "2021" +description = "Vesting contract with a voting capabilities. Provides queries to get the amount of tokens are being held by user at certain height." + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all init/handle/query exports +library = [] + +[dependencies] +cw2 = { workspace = true } +cw20 = { workspace = true } +astroport = { workspace = true } +vesting-base = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } + +[dev-dependencies] +cw-multi-test = { workspace = true } +astroport-token = {git = "https://github.com/astroport-fi/astroport-core.git", rev = "65ce7d1879cc5d95b09fa14202f0423bba52ae0e" } +cw-utils = "0.15" diff --git a/contracts/vesting-lp-pcl/examples/vesting-lp_schema.rs b/contracts/vesting-lp-pcl/examples/vesting-lp_schema.rs new file mode 100644 index 00000000..90284134 --- /dev/null +++ b/contracts/vesting-lp-pcl/examples/vesting-lp_schema.rs @@ -0,0 +1,12 @@ +use cosmwasm_schema::write_api; +use vesting_base::msg::{ExecuteMsg, MigrateMsg, QueryMsg}; +use vesting_lp::msg::InstantiateMsg; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + migrate: MigrateMsg + } +} diff --git a/contracts/vesting-lp-pcl/schema/raw/execute.json b/contracts/vesting-lp-pcl/schema/raw/execute.json new file mode 100644 index 00000000..e5aa732d --- /dev/null +++ b/contracts/vesting-lp-pcl/schema/raw/execute.json @@ -0,0 +1,480 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "description": "This structure describes the execute messages available in a vesting contract.", + "oneOf": [ + { + "description": "Claim claims vested tokens and sends them to a recipient", + "type": "object", + "required": [ + "claim" + ], + "properties": { + "claim": { + "type": "object", + "properties": { + "amount": { + "description": "The amount of tokens to claim", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "recipient": { + "description": "The address that receives the vested tokens", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template", + "type": "object", + "required": [ + "receive" + ], + "properties": { + "receive": { + "$ref": "#/definitions/Cw20ReceiveMsg" + } + }, + "additionalProperties": false + }, + { + "description": "RegisterVestingAccounts registers vesting targets/accounts", + "type": "object", + "required": [ + "register_vesting_accounts" + ], + "properties": { + "register_vesting_accounts": { + "type": "object", + "required": [ + "vesting_accounts" + ], + "properties": { + "vesting_accounts": { + "type": "array", + "items": { + "$ref": "#/definitions/VestingAccount" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Creates a request to change contract ownership ## Executor Only the current owner can execute this", + "type": "object", + "required": [ + "propose_new_owner" + ], + "properties": { + "propose_new_owner": { + "type": "object", + "required": [ + "expires_in", + "owner" + ], + "properties": { + "expires_in": { + "description": "The validity period of the offer to change the owner", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "owner": { + "description": "The newly proposed owner", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Removes a request to change contract ownership ## Executor Only the current owner can execute this", + "type": "object", + "required": [ + "drop_ownership_proposal" + ], + "properties": { + "drop_ownership_proposal": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Claims contract ownership ## Executor Only the newly proposed owner can execute this", + "type": "object", + "required": [ + "claim_ownership" + ], + "properties": { + "claim_ownership": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Sets vesting token ## Executor Only the current owner or token info manager can execute this", + "type": "object", + "required": [ + "set_vesting_token" + ], + "properties": { + "set_vesting_token": { + "type": "object", + "required": [ + "vesting_token" + ], + "properties": { + "vesting_token": { + "$ref": "#/definitions/AssetInfo" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Contains messages associated with the managed extension for vesting contracts.", + "type": "object", + "required": [ + "managed_extension" + ], + "properties": { + "managed_extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/ExecuteMsgManaged" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Contains messages associated with the with_managers extension for vesting contracts.", + "type": "object", + "required": [ + "with_managers_extension" + ], + "properties": { + "with_managers_extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/ExecuteMsgWithManagers" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Contains messages associated with the historical extension for vesting contracts.", + "type": "object", + "required": [ + "historical_extension" + ], + "properties": { + "historical_extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/ExecuteMsgHistorical" + } + }, + "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" + }, + "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 + } + ] + }, + "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" + }, + "Cw20ReceiveMsg": { + "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", + "type": "object", + "required": [ + "amount", + "msg", + "sender" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "sender": { + "type": "string" + } + }, + "additionalProperties": false + }, + "ExecuteMsgHistorical": { + "description": "This structure describes the execute messages available in a historical vesting contract.", + "type": "string", + "enum": [] + }, + "ExecuteMsgManaged": { + "description": "This structure describes the execute messages available in a managed vesting contract.", + "oneOf": [ + { + "description": "Removes vesting targets/accounts. ## Executor Only the current owner can execute this", + "type": "object", + "required": [ + "remove_vesting_accounts" + ], + "properties": { + "remove_vesting_accounts": { + "type": "object", + "required": [ + "clawback_account", + "vesting_accounts" + ], + "properties": { + "clawback_account": { + "description": "Specifies the account that will receive the funds taken from the vesting accounts.", + "type": "string" + }, + "vesting_accounts": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "ExecuteMsgWithManagers": { + "description": "This structure describes the execute messages available in a with_managers vesting contract.", + "oneOf": [ + { + "description": "Adds vesting managers ## Executor Only the current owner can execute this", + "type": "object", + "required": [ + "add_vesting_managers" + ], + "properties": { + "add_vesting_managers": { + "type": "object", + "required": [ + "managers" + ], + "properties": { + "managers": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Removes vesting managers ## Executor Only the current owner can execute this", + "type": "object", + "required": [ + "remove_vesting_managers" + ], + "properties": { + "remove_vesting_managers": { + "type": "object", + "required": [ + "managers" + ], + "properties": { + "managers": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "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" + }, + "VestingAccount": { + "description": "This structure stores vesting information for a specific address that is getting tokens.", + "type": "object", + "required": [ + "address", + "schedules" + ], + "properties": { + "address": { + "description": "The address that is getting tokens", + "type": "string" + }, + "schedules": { + "description": "The vesting schedules targeted at the `address`", + "type": "array", + "items": { + "$ref": "#/definitions/VestingSchedule" + } + } + }, + "additionalProperties": false + }, + "VestingSchedule": { + "description": "This structure stores parameters for a specific vesting schedule", + "type": "object", + "required": [ + "start_point" + ], + "properties": { + "end_point": { + "description": "The end point for the vesting schedule", + "anyOf": [ + { + "$ref": "#/definitions/VestingSchedulePoint" + }, + { + "type": "null" + } + ] + }, + "start_point": { + "description": "The start date for the vesting schedule", + "allOf": [ + { + "$ref": "#/definitions/VestingSchedulePoint" + } + ] + } + }, + "additionalProperties": false + }, + "VestingSchedulePoint": { + "description": "This structure stores the parameters used to create a vesting schedule.", + "type": "object", + "required": [ + "amount", + "time" + ], + "properties": { + "amount": { + "description": "The amount of tokens being vested", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "time": { + "description": "The start time for the vesting schedule", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/vesting-lp-pcl/schema/raw/instantiate.json b/contracts/vesting-lp-pcl/schema/raw/instantiate.json new file mode 100644 index 00000000..044bc78c --- /dev/null +++ b/contracts/vesting-lp-pcl/schema/raw/instantiate.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "description": "This structure describes the parameters used for creating a contract.", + "type": "object", + "required": [ + "owner", + "token_info_manager", + "vesting_managers" + ], + "properties": { + "owner": { + "description": "Address allowed to change contract parameters", + "type": "string" + }, + "token_info_manager": { + "description": "Token info manager address", + "type": "string" + }, + "vesting_managers": { + "description": "Initial list of whitelisted vesting managers", + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false +} diff --git a/contracts/vesting-lp-pcl/schema/raw/migrate.json b/contracts/vesting-lp-pcl/schema/raw/migrate.json new file mode 100644 index 00000000..1b9dcecf --- /dev/null +++ b/contracts/vesting-lp-pcl/schema/raw/migrate.json @@ -0,0 +1,7 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "description": "This structure describes a migration message. We currently take no arguments for migrations.", + "type": "object", + "additionalProperties": false +} diff --git a/contracts/vesting-lp-pcl/schema/raw/query.json b/contracts/vesting-lp-pcl/schema/raw/query.json new file mode 100644 index 00000000..64a0b398 --- /dev/null +++ b/contracts/vesting-lp-pcl/schema/raw/query.json @@ -0,0 +1,290 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "description": "This structure describes the query messages available in a vesting contract.", + "oneOf": [ + { + "description": "Returns the configuration for the contract using a [`ConfigResponse`] object.", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns information about an address vesting tokens using a [`VestingAccountResponse`] object.", + "type": "object", + "required": [ + "vesting_account" + ], + "properties": { + "vesting_account": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns a list of addresses that are vesting tokens using a [`VestingAccountsResponse`] object.", + "type": "object", + "required": [ + "vesting_accounts" + ], + "properties": { + "vesting_accounts": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "order_by": { + "anyOf": [ + { + "$ref": "#/definitions/OrderBy" + }, + { + "type": "null" + } + ] + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the total unvested amount of tokens for a specific address.", + "type": "object", + "required": [ + "available_amount" + ], + "properties": { + "available_amount": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Timestamp returns the current timestamp", + "type": "object", + "required": [ + "timestamp" + ], + "properties": { + "timestamp": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "VestingState returns the current vesting state.", + "type": "object", + "required": [ + "vesting_state" + ], + "properties": { + "vesting_state": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Contains messages associated with the managed extension for vesting contracts.", + "type": "object", + "required": [ + "managed_extension" + ], + "properties": { + "managed_extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/QueryMsgManaged" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Contains messages associated with the with_managers extension for vesting contracts.", + "type": "object", + "required": [ + "with_managers_extension" + ], + "properties": { + "with_managers_extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/QueryMsgWithManagers" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Contains messages associated with the historical extension for vesting contracts.", + "type": "object", + "required": [ + "historical_extension" + ], + "properties": { + "historical_extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/QueryMsgHistorical" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "OrderBy": { + "description": "This enum describes the types of sorting that can be applied to some piece of data", + "type": "string", + "enum": [ + "asc", + "desc" + ] + }, + "QueryMsgHistorical": { + "description": "This structure describes the query messages available in a historical vesting contract.", + "oneOf": [ + { + "description": "Returns the total unclaimed amount of tokens for a specific address at certain height.", + "type": "object", + "required": [ + "unclaimed_amount_at_height" + ], + "properties": { + "unclaimed_amount_at_height": { + "type": "object", + "required": [ + "address", + "height" + ], + "properties": { + "address": { + "type": "string" + }, + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the total unclaimed amount of tokens for all the users at certain height.", + "type": "object", + "required": [ + "unclaimed_total_amount_at_height" + ], + "properties": { + "unclaimed_total_amount_at_height": { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "QueryMsgManaged": { + "description": "This structure describes the query messages available in a managed vesting contract.", + "type": "string", + "enum": [] + }, + "QueryMsgWithManagers": { + "description": "This structure describes the query messages available in a with_managers vesting contract.", + "oneOf": [ + { + "description": "Returns list of vesting managers (the persons who are able to add/remove vesting schedules)", + "type": "object", + "required": [ + "vesting_managers" + ], + "properties": { + "vesting_managers": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/contracts/vesting-lp-pcl/schema/raw/response_to_available_amount.json b/contracts/vesting-lp-pcl/schema/raw/response_to_available_amount.json new file mode 100644 index 00000000..25b73e8f --- /dev/null +++ b/contracts/vesting-lp-pcl/schema/raw/response_to_available_amount.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/contracts/vesting-lp-pcl/schema/raw/response_to_config.json b/contracts/vesting-lp-pcl/schema/raw/response_to_config.json new file mode 100644 index 00000000..de35d970 --- /dev/null +++ b/contracts/vesting-lp-pcl/schema/raw/response_to_config.json @@ -0,0 +1,128 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config", + "description": "This structure stores the main parameters for the generator vesting contract.", + "type": "object", + "required": [ + "extensions", + "owner", + "token_info_manager" + ], + "properties": { + "extensions": { + "description": "Contains extensions information of the contract", + "allOf": [ + { + "$ref": "#/definitions/Extensions" + } + ] + }, + "owner": { + "description": "Address that's allowed to change contract parameters", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "token_info_manager": { + "description": "Address that's allowed to change vesting token", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "vesting_token": { + "description": "[`AssetInfo`] of the vested token", + "anyOf": [ + { + "$ref": "#/definitions/AssetInfo" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "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 + } + ] + }, + "Extensions": { + "description": "Contains extensions information for the contract.", + "type": "object", + "required": [ + "historical", + "managed", + "with_managers" + ], + "properties": { + "historical": { + "description": "Whether the historical extension is enabled for the contract.", + "type": "boolean" + }, + "managed": { + "description": "Whether the managed extension is enabled for the contract.", + "type": "boolean" + }, + "with_managers": { + "description": "Whether the with_managers extension is enabled for the contract.", + "type": "boolean" + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/vesting-lp-pcl/schema/raw/response_to_historical_extension.json b/contracts/vesting-lp-pcl/schema/raw/response_to_historical_extension.json new file mode 100644 index 00000000..83edad79 --- /dev/null +++ b/contracts/vesting-lp-pcl/schema/raw/response_to_historical_extension.json @@ -0,0 +1,59 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsgHistorical", + "description": "This structure describes the query messages available in a historical vesting contract.", + "oneOf": [ + { + "description": "Returns the total unclaimed amount of tokens for a specific address at certain height.", + "type": "object", + "required": [ + "unclaimed_amount_at_height" + ], + "properties": { + "unclaimed_amount_at_height": { + "type": "object", + "required": [ + "address", + "height" + ], + "properties": { + "address": { + "type": "string" + }, + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the total unclaimed amount of tokens for all the users at certain height.", + "type": "object", + "required": [ + "unclaimed_total_amount_at_height" + ], + "properties": { + "unclaimed_total_amount_at_height": { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/vesting-lp-pcl/schema/raw/response_to_managed_extension.json b/contracts/vesting-lp-pcl/schema/raw/response_to_managed_extension.json new file mode 100644 index 00000000..a46573aa --- /dev/null +++ b/contracts/vesting-lp-pcl/schema/raw/response_to_managed_extension.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "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" +} diff --git a/contracts/vesting-lp-pcl/schema/raw/response_to_timestamp.json b/contracts/vesting-lp-pcl/schema/raw/response_to_timestamp.json new file mode 100644 index 00000000..7b729a7b --- /dev/null +++ b/contracts/vesting-lp-pcl/schema/raw/response_to_timestamp.json @@ -0,0 +1,7 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "uint64", + "type": "integer", + "format": "uint64", + "minimum": 0.0 +} diff --git a/contracts/vesting-lp-pcl/schema/raw/response_to_vesting_account.json b/contracts/vesting-lp-pcl/schema/raw/response_to_vesting_account.json new file mode 100644 index 00000000..b85fc3d3 --- /dev/null +++ b/contracts/vesting-lp-pcl/schema/raw/response_to_vesting_account.json @@ -0,0 +1,119 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VestingAccountResponse", + "description": "This structure describes a custom struct used to return vesting data about a specific vesting target.", + "type": "object", + "required": [ + "address", + "info" + ], + "properties": { + "address": { + "description": "The address that's vesting tokens", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "info": { + "description": "Vesting information", + "allOf": [ + { + "$ref": "#/definitions/VestingInfo" + } + ] + } + }, + "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" + }, + "VestingInfo": { + "description": "This structure stores parameters for a batch of vesting schedules.", + "type": "object", + "required": [ + "released_amount", + "schedules" + ], + "properties": { + "released_amount": { + "description": "The total amount of vested tokens already claimed", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "schedules": { + "description": "The vesting schedules", + "type": "array", + "items": { + "$ref": "#/definitions/VestingSchedule" + } + } + }, + "additionalProperties": false + }, + "VestingSchedule": { + "description": "This structure stores parameters for a specific vesting schedule", + "type": "object", + "required": [ + "start_point" + ], + "properties": { + "end_point": { + "description": "The end point for the vesting schedule", + "anyOf": [ + { + "$ref": "#/definitions/VestingSchedulePoint" + }, + { + "type": "null" + } + ] + }, + "start_point": { + "description": "The start date for the vesting schedule", + "allOf": [ + { + "$ref": "#/definitions/VestingSchedulePoint" + } + ] + } + }, + "additionalProperties": false + }, + "VestingSchedulePoint": { + "description": "This structure stores the parameters used to create a vesting schedule.", + "type": "object", + "required": [ + "amount", + "time" + ], + "properties": { + "amount": { + "description": "The amount of tokens being vested", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "time": { + "description": "The start time for the vesting schedule", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/vesting-lp-pcl/schema/raw/response_to_vesting_accounts.json b/contracts/vesting-lp-pcl/schema/raw/response_to_vesting_accounts.json new file mode 100644 index 00000000..6d2af531 --- /dev/null +++ b/contracts/vesting-lp-pcl/schema/raw/response_to_vesting_accounts.json @@ -0,0 +1,136 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VestingAccountsResponse", + "description": "This structure describes a custom struct used to return vesting data for multiple vesting targets.", + "type": "object", + "required": [ + "vesting_accounts" + ], + "properties": { + "vesting_accounts": { + "description": "A list of accounts that are vesting tokens", + "type": "array", + "items": { + "$ref": "#/definitions/VestingAccountResponse" + } + } + }, + "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" + }, + "VestingAccountResponse": { + "description": "This structure describes a custom struct used to return vesting data about a specific vesting target.", + "type": "object", + "required": [ + "address", + "info" + ], + "properties": { + "address": { + "description": "The address that's vesting tokens", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "info": { + "description": "Vesting information", + "allOf": [ + { + "$ref": "#/definitions/VestingInfo" + } + ] + } + }, + "additionalProperties": false + }, + "VestingInfo": { + "description": "This structure stores parameters for a batch of vesting schedules.", + "type": "object", + "required": [ + "released_amount", + "schedules" + ], + "properties": { + "released_amount": { + "description": "The total amount of vested tokens already claimed", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "schedules": { + "description": "The vesting schedules", + "type": "array", + "items": { + "$ref": "#/definitions/VestingSchedule" + } + } + }, + "additionalProperties": false + }, + "VestingSchedule": { + "description": "This structure stores parameters for a specific vesting schedule", + "type": "object", + "required": [ + "start_point" + ], + "properties": { + "end_point": { + "description": "The end point for the vesting schedule", + "anyOf": [ + { + "$ref": "#/definitions/VestingSchedulePoint" + }, + { + "type": "null" + } + ] + }, + "start_point": { + "description": "The start date for the vesting schedule", + "allOf": [ + { + "$ref": "#/definitions/VestingSchedulePoint" + } + ] + } + }, + "additionalProperties": false + }, + "VestingSchedulePoint": { + "description": "This structure stores the parameters used to create a vesting schedule.", + "type": "object", + "required": [ + "amount", + "time" + ], + "properties": { + "amount": { + "description": "The amount of tokens being vested", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "time": { + "description": "The start time for the vesting schedule", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } +} diff --git a/contracts/vesting-lp-pcl/schema/raw/response_to_vesting_state.json b/contracts/vesting-lp-pcl/schema/raw/response_to_vesting_state.json new file mode 100644 index 00000000..b38701e4 --- /dev/null +++ b/contracts/vesting-lp-pcl/schema/raw/response_to_vesting_state.json @@ -0,0 +1,35 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VestingState", + "description": "This structure stores the accumulated vesting information for all addresses.", + "type": "object", + "required": [ + "total_granted", + "total_released" + ], + "properties": { + "total_granted": { + "description": "The total amount of tokens granted to the users", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "total_released": { + "description": "The total amount of tokens already claimed", + "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/contracts/vesting-lp-pcl/schema/raw/response_to_with_managers_extension.json b/contracts/vesting-lp-pcl/schema/raw/response_to_with_managers_extension.json new file mode 100644 index 00000000..bcacc2ac --- /dev/null +++ b/contracts/vesting-lp-pcl/schema/raw/response_to_with_managers_extension.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsgWithManagers", + "description": "This structure describes the query messages available in a with_managers vesting contract.", + "oneOf": [ + { + "description": "Returns list of vesting managers (the persons who are able to add/remove vesting schedules)", + "type": "object", + "required": [ + "vesting_managers" + ], + "properties": { + "vesting_managers": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/vesting-lp-pcl/schema/vesting-lp.json b/contracts/vesting-lp-pcl/schema/vesting-lp.json new file mode 100644 index 00000000..395775f8 --- /dev/null +++ b/contracts/vesting-lp-pcl/schema/vesting-lp.json @@ -0,0 +1,1331 @@ +{ + "contract_name": "vesting-lp", + "contract_version": "1.1.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "description": "This structure describes the parameters used for creating a contract.", + "type": "object", + "required": [ + "owner", + "token_info_manager", + "vesting_managers" + ], + "properties": { + "owner": { + "description": "Address allowed to change contract parameters", + "type": "string" + }, + "token_info_manager": { + "description": "Token info manager address", + "type": "string" + }, + "vesting_managers": { + "description": "Initial list of whitelisted vesting managers", + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "description": "This structure describes the execute messages available in a vesting contract.", + "oneOf": [ + { + "description": "Claim claims vested tokens and sends them to a recipient", + "type": "object", + "required": [ + "claim" + ], + "properties": { + "claim": { + "type": "object", + "properties": { + "amount": { + "description": "The amount of tokens to claim", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "recipient": { + "description": "The address that receives the vested tokens", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template", + "type": "object", + "required": [ + "receive" + ], + "properties": { + "receive": { + "$ref": "#/definitions/Cw20ReceiveMsg" + } + }, + "additionalProperties": false + }, + { + "description": "RegisterVestingAccounts registers vesting targets/accounts", + "type": "object", + "required": [ + "register_vesting_accounts" + ], + "properties": { + "register_vesting_accounts": { + "type": "object", + "required": [ + "vesting_accounts" + ], + "properties": { + "vesting_accounts": { + "type": "array", + "items": { + "$ref": "#/definitions/VestingAccount" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Creates a request to change contract ownership ## Executor Only the current owner can execute this", + "type": "object", + "required": [ + "propose_new_owner" + ], + "properties": { + "propose_new_owner": { + "type": "object", + "required": [ + "expires_in", + "owner" + ], + "properties": { + "expires_in": { + "description": "The validity period of the offer to change the owner", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "owner": { + "description": "The newly proposed owner", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Removes a request to change contract ownership ## Executor Only the current owner can execute this", + "type": "object", + "required": [ + "drop_ownership_proposal" + ], + "properties": { + "drop_ownership_proposal": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Claims contract ownership ## Executor Only the newly proposed owner can execute this", + "type": "object", + "required": [ + "claim_ownership" + ], + "properties": { + "claim_ownership": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Sets vesting token ## Executor Only the current owner or token info manager can execute this", + "type": "object", + "required": [ + "set_vesting_token" + ], + "properties": { + "set_vesting_token": { + "type": "object", + "required": [ + "vesting_token" + ], + "properties": { + "vesting_token": { + "$ref": "#/definitions/AssetInfo" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Contains messages associated with the managed extension for vesting contracts.", + "type": "object", + "required": [ + "managed_extension" + ], + "properties": { + "managed_extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/ExecuteMsgManaged" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Contains messages associated with the with_managers extension for vesting contracts.", + "type": "object", + "required": [ + "with_managers_extension" + ], + "properties": { + "with_managers_extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/ExecuteMsgWithManagers" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Contains messages associated with the historical extension for vesting contracts.", + "type": "object", + "required": [ + "historical_extension" + ], + "properties": { + "historical_extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/ExecuteMsgHistorical" + } + }, + "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" + }, + "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 + } + ] + }, + "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" + }, + "Cw20ReceiveMsg": { + "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", + "type": "object", + "required": [ + "amount", + "msg", + "sender" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "sender": { + "type": "string" + } + }, + "additionalProperties": false + }, + "ExecuteMsgHistorical": { + "description": "This structure describes the execute messages available in a historical vesting contract.", + "type": "string", + "enum": [] + }, + "ExecuteMsgManaged": { + "description": "This structure describes the execute messages available in a managed vesting contract.", + "oneOf": [ + { + "description": "Removes vesting targets/accounts. ## Executor Only the current owner can execute this", + "type": "object", + "required": [ + "remove_vesting_accounts" + ], + "properties": { + "remove_vesting_accounts": { + "type": "object", + "required": [ + "clawback_account", + "vesting_accounts" + ], + "properties": { + "clawback_account": { + "description": "Specifies the account that will receive the funds taken from the vesting accounts.", + "type": "string" + }, + "vesting_accounts": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "ExecuteMsgWithManagers": { + "description": "This structure describes the execute messages available in a with_managers vesting contract.", + "oneOf": [ + { + "description": "Adds vesting managers ## Executor Only the current owner can execute this", + "type": "object", + "required": [ + "add_vesting_managers" + ], + "properties": { + "add_vesting_managers": { + "type": "object", + "required": [ + "managers" + ], + "properties": { + "managers": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Removes vesting managers ## Executor Only the current owner can execute this", + "type": "object", + "required": [ + "remove_vesting_managers" + ], + "properties": { + "remove_vesting_managers": { + "type": "object", + "required": [ + "managers" + ], + "properties": { + "managers": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "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" + }, + "VestingAccount": { + "description": "This structure stores vesting information for a specific address that is getting tokens.", + "type": "object", + "required": [ + "address", + "schedules" + ], + "properties": { + "address": { + "description": "The address that is getting tokens", + "type": "string" + }, + "schedules": { + "description": "The vesting schedules targeted at the `address`", + "type": "array", + "items": { + "$ref": "#/definitions/VestingSchedule" + } + } + }, + "additionalProperties": false + }, + "VestingSchedule": { + "description": "This structure stores parameters for a specific vesting schedule", + "type": "object", + "required": [ + "start_point" + ], + "properties": { + "end_point": { + "description": "The end point for the vesting schedule", + "anyOf": [ + { + "$ref": "#/definitions/VestingSchedulePoint" + }, + { + "type": "null" + } + ] + }, + "start_point": { + "description": "The start date for the vesting schedule", + "allOf": [ + { + "$ref": "#/definitions/VestingSchedulePoint" + } + ] + } + }, + "additionalProperties": false + }, + "VestingSchedulePoint": { + "description": "This structure stores the parameters used to create a vesting schedule.", + "type": "object", + "required": [ + "amount", + "time" + ], + "properties": { + "amount": { + "description": "The amount of tokens being vested", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "time": { + "description": "The start time for the vesting schedule", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "description": "This structure describes the query messages available in a vesting contract.", + "oneOf": [ + { + "description": "Returns the configuration for the contract using a [`ConfigResponse`] object.", + "type": "object", + "required": [ + "config" + ], + "properties": { + "config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns information about an address vesting tokens using a [`VestingAccountResponse`] object.", + "type": "object", + "required": [ + "vesting_account" + ], + "properties": { + "vesting_account": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns a list of addresses that are vesting tokens using a [`VestingAccountsResponse`] object.", + "type": "object", + "required": [ + "vesting_accounts" + ], + "properties": { + "vesting_accounts": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "order_by": { + "anyOf": [ + { + "$ref": "#/definitions/OrderBy" + }, + { + "type": "null" + } + ] + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the total unvested amount of tokens for a specific address.", + "type": "object", + "required": [ + "available_amount" + ], + "properties": { + "available_amount": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Timestamp returns the current timestamp", + "type": "object", + "required": [ + "timestamp" + ], + "properties": { + "timestamp": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "VestingState returns the current vesting state.", + "type": "object", + "required": [ + "vesting_state" + ], + "properties": { + "vesting_state": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Contains messages associated with the managed extension for vesting contracts.", + "type": "object", + "required": [ + "managed_extension" + ], + "properties": { + "managed_extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/QueryMsgManaged" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Contains messages associated with the with_managers extension for vesting contracts.", + "type": "object", + "required": [ + "with_managers_extension" + ], + "properties": { + "with_managers_extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/QueryMsgWithManagers" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Contains messages associated with the historical extension for vesting contracts.", + "type": "object", + "required": [ + "historical_extension" + ], + "properties": { + "historical_extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/QueryMsgHistorical" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "OrderBy": { + "description": "This enum describes the types of sorting that can be applied to some piece of data", + "type": "string", + "enum": [ + "asc", + "desc" + ] + }, + "QueryMsgHistorical": { + "description": "This structure describes the query messages available in a historical vesting contract.", + "oneOf": [ + { + "description": "Returns the total unclaimed amount of tokens for a specific address at certain height.", + "type": "object", + "required": [ + "unclaimed_amount_at_height" + ], + "properties": { + "unclaimed_amount_at_height": { + "type": "object", + "required": [ + "address", + "height" + ], + "properties": { + "address": { + "type": "string" + }, + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the total unclaimed amount of tokens for all the users at certain height.", + "type": "object", + "required": [ + "unclaimed_total_amount_at_height" + ], + "properties": { + "unclaimed_total_amount_at_height": { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "QueryMsgManaged": { + "description": "This structure describes the query messages available in a managed vesting contract.", + "type": "string", + "enum": [] + }, + "QueryMsgWithManagers": { + "description": "This structure describes the query messages available in a with_managers vesting contract.", + "oneOf": [ + { + "description": "Returns list of vesting managers (the persons who are able to add/remove vesting schedules)", + "type": "object", + "required": [ + "vesting_managers" + ], + "properties": { + "vesting_managers": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + } + } + }, + "migrate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MigrateMsg", + "description": "This structure describes a migration message. We currently take no arguments for migrations.", + "type": "object", + "additionalProperties": false + }, + "sudo": null, + "responses": { + "available_amount": { + "$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" + }, + "config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config", + "description": "This structure stores the main parameters for the generator vesting contract.", + "type": "object", + "required": [ + "extensions", + "owner", + "token_info_manager" + ], + "properties": { + "extensions": { + "description": "Contains extensions information of the contract", + "allOf": [ + { + "$ref": "#/definitions/Extensions" + } + ] + }, + "owner": { + "description": "Address that's allowed to change contract parameters", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "token_info_manager": { + "description": "Address that's allowed to change vesting token", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "vesting_token": { + "description": "[`AssetInfo`] of the vested token", + "anyOf": [ + { + "$ref": "#/definitions/AssetInfo" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "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 + } + ] + }, + "Extensions": { + "description": "Contains extensions information for the contract.", + "type": "object", + "required": [ + "historical", + "managed", + "with_managers" + ], + "properties": { + "historical": { + "description": "Whether the historical extension is enabled for the contract.", + "type": "boolean" + }, + "managed": { + "description": "Whether the managed extension is enabled for the contract.", + "type": "boolean" + }, + "with_managers": { + "description": "Whether the with_managers extension is enabled for the contract.", + "type": "boolean" + } + }, + "additionalProperties": false + } + } + }, + "historical_extension": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsgHistorical", + "description": "This structure describes the query messages available in a historical vesting contract.", + "oneOf": [ + { + "description": "Returns the total unclaimed amount of tokens for a specific address at certain height.", + "type": "object", + "required": [ + "unclaimed_amount_at_height" + ], + "properties": { + "unclaimed_amount_at_height": { + "type": "object", + "required": [ + "address", + "height" + ], + "properties": { + "address": { + "type": "string" + }, + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns the total unclaimed amount of tokens for all the users at certain height.", + "type": "object", + "required": [ + "unclaimed_total_amount_at_height" + ], + "properties": { + "unclaimed_total_amount_at_height": { + "type": "object", + "required": [ + "height" + ], + "properties": { + "height": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "managed_extension": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "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" + }, + "timestamp": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "uint64", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "vesting_account": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VestingAccountResponse", + "description": "This structure describes a custom struct used to return vesting data about a specific vesting target.", + "type": "object", + "required": [ + "address", + "info" + ], + "properties": { + "address": { + "description": "The address that's vesting tokens", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "info": { + "description": "Vesting information", + "allOf": [ + { + "$ref": "#/definitions/VestingInfo" + } + ] + } + }, + "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" + }, + "VestingInfo": { + "description": "This structure stores parameters for a batch of vesting schedules.", + "type": "object", + "required": [ + "released_amount", + "schedules" + ], + "properties": { + "released_amount": { + "description": "The total amount of vested tokens already claimed", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "schedules": { + "description": "The vesting schedules", + "type": "array", + "items": { + "$ref": "#/definitions/VestingSchedule" + } + } + }, + "additionalProperties": false + }, + "VestingSchedule": { + "description": "This structure stores parameters for a specific vesting schedule", + "type": "object", + "required": [ + "start_point" + ], + "properties": { + "end_point": { + "description": "The end point for the vesting schedule", + "anyOf": [ + { + "$ref": "#/definitions/VestingSchedulePoint" + }, + { + "type": "null" + } + ] + }, + "start_point": { + "description": "The start date for the vesting schedule", + "allOf": [ + { + "$ref": "#/definitions/VestingSchedulePoint" + } + ] + } + }, + "additionalProperties": false + }, + "VestingSchedulePoint": { + "description": "This structure stores the parameters used to create a vesting schedule.", + "type": "object", + "required": [ + "amount", + "time" + ], + "properties": { + "amount": { + "description": "The amount of tokens being vested", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "time": { + "description": "The start time for the vesting schedule", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "vesting_accounts": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VestingAccountsResponse", + "description": "This structure describes a custom struct used to return vesting data for multiple vesting targets.", + "type": "object", + "required": [ + "vesting_accounts" + ], + "properties": { + "vesting_accounts": { + "description": "A list of accounts that are vesting tokens", + "type": "array", + "items": { + "$ref": "#/definitions/VestingAccountResponse" + } + } + }, + "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" + }, + "VestingAccountResponse": { + "description": "This structure describes a custom struct used to return vesting data about a specific vesting target.", + "type": "object", + "required": [ + "address", + "info" + ], + "properties": { + "address": { + "description": "The address that's vesting tokens", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "info": { + "description": "Vesting information", + "allOf": [ + { + "$ref": "#/definitions/VestingInfo" + } + ] + } + }, + "additionalProperties": false + }, + "VestingInfo": { + "description": "This structure stores parameters for a batch of vesting schedules.", + "type": "object", + "required": [ + "released_amount", + "schedules" + ], + "properties": { + "released_amount": { + "description": "The total amount of vested tokens already claimed", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "schedules": { + "description": "The vesting schedules", + "type": "array", + "items": { + "$ref": "#/definitions/VestingSchedule" + } + } + }, + "additionalProperties": false + }, + "VestingSchedule": { + "description": "This structure stores parameters for a specific vesting schedule", + "type": "object", + "required": [ + "start_point" + ], + "properties": { + "end_point": { + "description": "The end point for the vesting schedule", + "anyOf": [ + { + "$ref": "#/definitions/VestingSchedulePoint" + }, + { + "type": "null" + } + ] + }, + "start_point": { + "description": "The start date for the vesting schedule", + "allOf": [ + { + "$ref": "#/definitions/VestingSchedulePoint" + } + ] + } + }, + "additionalProperties": false + }, + "VestingSchedulePoint": { + "description": "This structure stores the parameters used to create a vesting schedule.", + "type": "object", + "required": [ + "amount", + "time" + ], + "properties": { + "amount": { + "description": "The amount of tokens being vested", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "time": { + "description": "The start time for the vesting schedule", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + } + }, + "vesting_state": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "VestingState", + "description": "This structure stores the accumulated vesting information for all addresses.", + "type": "object", + "required": [ + "total_granted", + "total_released" + ], + "properties": { + "total_granted": { + "description": "The total amount of tokens granted to the users", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "total_released": { + "description": "The total amount of tokens already claimed", + "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" + } + } + }, + "with_managers_extension": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsgWithManagers", + "description": "This structure describes the query messages available in a with_managers vesting contract.", + "oneOf": [ + { + "description": "Returns list of vesting managers (the persons who are able to add/remove vesting schedules)", + "type": "object", + "required": [ + "vesting_managers" + ], + "properties": { + "vesting_managers": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/contracts/vesting-lp-pcl/src/contract.rs b/contracts/vesting-lp-pcl/src/contract.rs new file mode 100644 index 00000000..2d3c1c80 --- /dev/null +++ b/contracts/vesting-lp-pcl/src/contract.rs @@ -0,0 +1,46 @@ +use crate::msg::InstantiateMsg; +use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use cw2::set_contract_version; +use vesting_base_pcl::builder::VestingBaseBuilder; +use vesting_base_pcl::error::ContractError; +use vesting_base_pcl::handlers::{execute as base_execute, query as base_query}; +use vesting_base_pcl::msg::{ExecuteMsg, QueryMsg}; + +/// Contract name that is used for migration. +const CONTRACT_NAME: &str = "neutron-vesting-lp"; +/// Contract version that is used for migration. +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +/// Creates a new contract with the specified parameters packed in the `msg` variable. +#[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)?; + + VestingBaseBuilder::default() + .historical() + .with_managers(msg.vesting_managers) + .build(deps, msg.owner, msg.token_info_manager)?; + 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 { + base_execute(deps, env, info, msg) +} + +/// Exposes all the queries available in the contract. +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + base_query(deps, env, msg) +} diff --git a/contracts/vesting-lp-pcl/src/lib.rs b/contracts/vesting-lp-pcl/src/lib.rs new file mode 100644 index 00000000..08d6d688 --- /dev/null +++ b/contracts/vesting-lp-pcl/src/lib.rs @@ -0,0 +1,5 @@ +pub mod contract; +pub mod msg; + +#[cfg(test)] +mod tests; diff --git a/contracts/vesting-lp-pcl/src/msg.rs b/contracts/vesting-lp-pcl/src/msg.rs new file mode 100644 index 00000000..b35ee7ff --- /dev/null +++ b/contracts/vesting-lp-pcl/src/msg.rs @@ -0,0 +1,12 @@ +use cosmwasm_schema::cw_serde; + +/// This structure describes the parameters used for creating a contract. +#[cw_serde] +pub struct InstantiateMsg { + /// Address allowed to change contract parameters + pub owner: String, + /// Initial list of whitelisted vesting managers + pub vesting_managers: Vec, + /// Token info manager address + pub token_info_manager: String, +} diff --git a/contracts/vesting-lp-pcl/src/tests/integration.rs b/contracts/vesting-lp-pcl/src/tests/integration.rs new file mode 100644 index 00000000..5a150e97 --- /dev/null +++ b/contracts/vesting-lp-pcl/src/tests/integration.rs @@ -0,0 +1,1281 @@ +use crate::msg::InstantiateMsg; +use astroport::asset::{native_asset_info, token_asset_info}; +use astroport::querier::query_balance; +use astroport::token::InstantiateMsg as TokenInstantiateMsg; +use cosmwasm_std::{coin, coins, to_binary, Addr, StdResult, Timestamp, Uint128}; +use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg, MinterResponse}; +use cw_multi_test::{App, ContractWrapper, Executor}; +use cw_utils::PaymentError; +use vesting_base::error::ContractError; +use vesting_base::msg::{ + Cw20HookMsg, ExecuteMsg, ExecuteMsgWithManagers, QueryMsg, QueryMsgHistorical, + QueryMsgWithManagers, +}; +use vesting_base::types::{ + Config, VestingAccount, VestingAccountResponse, VestingSchedule, VestingSchedulePoint, +}; + +const OWNER1: &str = "owner1"; +const TOKEN_MANAGER: &str = "token_manager"; +const USER1: &str = "user1"; +const USER2: &str = "user2"; +const TOKEN_INITIAL_AMOUNT: u128 = 1_000_000_000_000_000; +const VESTING_TOKEN: &str = "vesting_token"; +const BLOCK_TIME: u64 = 5; + +#[test] +fn claim() { + let user1 = Addr::unchecked(USER1); + let owner = Addr::unchecked(OWNER1); + + let mut app = mock_app(&owner); + + let token_code_id = store_token_code(&mut app); + + let cw20_token_instance = + instantiate_token(&mut app, token_code_id, "NTRN", Some(1_000_000_000_000_000)); + + let vesting_instance = instantiate_vesting(&mut app, &cw20_token_instance); + + let msg = Cw20ExecuteMsg::Send { + contract: vesting_instance.to_string(), + msg: to_binary(&Cw20HookMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user1.to_string(), + schedules: vec![ + VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(101).seconds(), + amount: Uint128::new(200), + }), + }, + VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(110).seconds(), + amount: Uint128::new(100), + }), + }, + VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(200).seconds(), + amount: Uint128::new(100), + }), + }, + ], + }], + }) + .unwrap(), + amount: Uint128::from(300u128), + }; + + let res = app + .execute_contract(owner.clone(), cw20_token_instance.clone(), &msg, &[]) + .unwrap_err(); + assert_eq!(res.root_cause().to_string(), "Vesting schedule amount error. The total amount should be equal to the CW20 receive amount."); + + let msg = Cw20ExecuteMsg::Send { + contract: vesting_instance.to_string(), + msg: to_binary(&Cw20HookMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user1.to_string(), + schedules: vec![ + VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(101).seconds(), + amount: Uint128::new(100), + }), + }, + VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(110).seconds(), + amount: Uint128::new(100), + }), + }, + VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(200).seconds(), + amount: Uint128::new(100), + }), + }, + ], + }], + }) + .unwrap(), + amount: Uint128::from(300u128), + }; + + app.execute_contract(owner.clone(), cw20_token_instance.clone(), &msg, &[]) + .unwrap(); + + let msg = QueryMsg::AvailableAmount { + address: user1.to_string(), + }; + + let user1_vesting_amount: Uint128 = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &msg) + .unwrap(); + assert_eq!(user1_vesting_amount.clone(), Uint128::new(300u128)); + + // Check owner balance + check_token_balance( + &mut app, + &cw20_token_instance, + &owner, + TOKEN_INITIAL_AMOUNT - 300u128, + ); + + // Check vesting balance + check_token_balance(&mut app, &cw20_token_instance, &vesting_instance, 300u128); + + let msg = ExecuteMsg::Claim { + recipient: None, + amount: None, + }; + let _res = app + .execute_contract(user1.clone(), vesting_instance.clone(), &msg, &[]) + .unwrap(); + + let msg = QueryMsg::VestingAccount { + address: user1.to_string(), + }; + + let vesting_res: VestingAccountResponse = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &msg) + .unwrap(); + assert_eq!(vesting_res.info.released_amount, Uint128::from(300u128)); + + // Check vesting balance + check_token_balance(&mut app, &cw20_token_instance, &vesting_instance, 0u128); + + // Check user balance + check_token_balance(&mut app, &cw20_token_instance, &user1, 300u128); + + // Owner balance mustn't change after claim + check_token_balance( + &mut app, + &cw20_token_instance, + &owner.clone(), + TOKEN_INITIAL_AMOUNT - 300u128, + ); + + let msg = QueryMsg::AvailableAmount { + address: user1.to_string(), + }; + + // Check user balance after claim + let user1_vesting_amount: Uint128 = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &msg) + .unwrap(); + + assert_eq!(user1_vesting_amount.clone(), Uint128::new(0u128)); +} + +#[test] +fn claim_native() { + let user1 = Addr::unchecked(USER1); + let owner = Addr::unchecked(OWNER1); + + let mut app = mock_app(&owner); + + let token_code_id = store_token_code(&mut app); + + let random_token_instance = + instantiate_token(&mut app, token_code_id, "RND", Some(1_000_000_000)); + + mint_tokens(&mut app, &random_token_instance, &owner, 1_000_000_000); + + let vesting_instance = instantiate_vesting_remote_chain(&mut app); + + let msg = Cw20ExecuteMsg::Send { + contract: vesting_instance.to_string(), + msg: to_binary(&Cw20HookMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user1.to_string(), + schedules: vec![VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(101).seconds(), + amount: Uint128::new(200), + }), + }], + }], + }) + .unwrap(), + amount: Uint128::from(300u128), + }; + + let err = app + .execute_contract(owner.clone(), random_token_instance.clone(), &msg, &[]) + .unwrap_err(); + assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()); + + let msg = ExecuteMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user1.to_string(), + schedules: vec![ + VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(101).seconds(), + amount: Uint128::new(100), + }), + }, + VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(110).seconds(), + amount: Uint128::new(100), + }), + }, + VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(200).seconds(), + amount: Uint128::new(100), + }), + }, + ], + }], + }; + + app.execute_contract( + owner.clone(), + vesting_instance.clone(), + &msg, + &coins(300, VESTING_TOKEN), + ) + .unwrap(); + + let msg = QueryMsg::AvailableAmount { + address: user1.to_string(), + }; + + let user1_vesting_amount: Uint128 = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &msg) + .unwrap(); + assert_eq!(user1_vesting_amount.clone(), Uint128::new(300u128)); + + // Check owner balance + let bal = query_balance(&app.wrap(), &owner, VESTING_TOKEN) + .unwrap() + .u128(); + assert_eq!(bal, TOKEN_INITIAL_AMOUNT - 300u128); + + // Check vesting balance + let bal = query_balance(&app.wrap(), &vesting_instance, VESTING_TOKEN) + .unwrap() + .u128(); + assert_eq!(bal, 300u128); + + let msg = ExecuteMsg::Claim { + recipient: None, + amount: None, + }; + app.execute_contract(user1.clone(), vesting_instance.clone(), &msg, &[]) + .unwrap(); + + let vesting_res: VestingAccountResponse = app + .wrap() + .query_wasm_smart( + vesting_instance.clone(), + &QueryMsg::VestingAccount { + address: user1.to_string(), + }, + ) + .unwrap(); + assert_eq!(vesting_res.info.released_amount, Uint128::from(300u128)); + + // Check vesting balance + let bal = query_balance(&app.wrap(), &vesting_instance, VESTING_TOKEN) + .unwrap() + .u128(); + assert_eq!(bal, 0); + + // Check user balance + let bal = query_balance(&app.wrap(), &user1, VESTING_TOKEN) + .unwrap() + .u128(); + assert_eq!(bal, 300); + + // Owner balance mustn't change after claim + let bal = query_balance(&app.wrap(), &owner, VESTING_TOKEN) + .unwrap() + .u128(); + assert_eq!(bal, TOKEN_INITIAL_AMOUNT - 300u128); + + let msg = QueryMsg::AvailableAmount { + address: user1.to_string(), + }; + + // Check user balance after claim + let user1_vesting_amount: Uint128 = + app.wrap().query_wasm_smart(vesting_instance, &msg).unwrap(); + + assert_eq!(user1_vesting_amount.clone(), Uint128::new(0u128)); +} + +#[test] +fn register_vesting_accounts() { + let user1 = Addr::unchecked(USER1); + let user2 = Addr::unchecked(USER2); + let owner = Addr::unchecked(OWNER1); + + let mut app = mock_app(&owner); + + let token_code_id = store_token_code(&mut app); + + let cw20_token_instance = + instantiate_token(&mut app, token_code_id, "NTRN", Some(1_000_000_000_000_000)); + + let noname_token_instance = instantiate_token( + &mut app, + token_code_id, + "NONAME", + Some(1_000_000_000_000_000), + ); + + mint_tokens( + &mut app, + &noname_token_instance, + &owner, + TOKEN_INITIAL_AMOUNT, + ); + + let vesting_instance = instantiate_vesting(&mut app, &cw20_token_instance); + + let msg = Cw20ExecuteMsg::Send { + contract: vesting_instance.to_string(), + msg: to_binary(&Cw20HookMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user1.to_string(), + schedules: vec![VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(150).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::new(100), + }), + }], + }], + }) + .unwrap(), + amount: Uint128::from(100u128), + }; + + let res = app + .execute_contract(owner.clone(), cw20_token_instance.clone(), &msg, &[]) + .unwrap_err(); + assert_eq!(res.root_cause().to_string(), "Vesting schedule error on addr: user1. Should satisfy: (start < end and at_start < total) or (start = end and at_start = total)"); + + let msg = Cw20ExecuteMsg::Send { + contract: vesting_instance.to_string(), + msg: to_binary(&Cw20HookMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user1.to_string(), + schedules: vec![VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(150).seconds(), + amount: Uint128::new(100), + }), + }], + }], + }) + .unwrap(), + amount: Uint128::from(100u128), + }; + + let res = app + .execute_contract( + user1.clone(), + cw20_token_instance.clone(), + &msg.clone(), + &[], + ) + .unwrap_err(); + assert_eq!(res.root_cause().to_string(), "Cannot Sub with 0 and 100"); + + let res = app + .execute_contract(owner.clone(), noname_token_instance.clone(), &msg, &[]) + .unwrap_err(); + assert_eq!(res.root_cause().to_string(), "Unauthorized"); + + // Checking that execute endpoint with native coin is unreachable if the asset is a cw20 token + let native_msg = ExecuteMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user1.to_string(), + schedules: vec![VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(150).seconds(), + amount: Uint128::new(100), + }), + }], + }], + }; + + let err = app + .execute_contract( + owner.clone(), + vesting_instance.clone(), + &native_msg, + &coins(100u128, "random_coin"), + ) + .unwrap_err(); + assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()); + + let _res = app + .execute_contract(owner.clone(), cw20_token_instance.clone(), &msg, &[]) + .unwrap(); + + let msg = QueryMsg::AvailableAmount { + address: user1.to_string(), + }; + + let user1_vesting_amount: Uint128 = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &msg) + .unwrap(); + + assert_eq!(user1_vesting_amount.clone(), Uint128::new(100u128)); + check_token_balance( + &mut app, + &cw20_token_instance, + &owner.clone(), + TOKEN_INITIAL_AMOUNT - 100u128, + ); + check_token_balance(&mut app, &cw20_token_instance, &vesting_instance, 100u128); + + // Let's check user1's final vesting amount after add schedule for a new one + let msg = Cw20ExecuteMsg::Send { + contract: vesting_instance.to_string(), + msg: to_binary(&Cw20HookMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user2.to_string(), + schedules: vec![VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(150).seconds(), + amount: Uint128::new(200), + }), + }], + }], + }) + .unwrap(), + amount: Uint128::from(200u128), + }; + + let _res = app + .execute_contract(owner.clone(), cw20_token_instance.clone(), &msg, &[]) + .unwrap(); + + let msg = QueryMsg::AvailableAmount { + address: user2.to_string(), + }; + + let user2_vesting_amount: Uint128 = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &msg) + .unwrap(); + + check_token_balance( + &mut app, + &cw20_token_instance, + &owner.clone(), + TOKEN_INITIAL_AMOUNT - 300u128, + ); + check_token_balance(&mut app, &cw20_token_instance, &vesting_instance, 300u128); + // A new schedule has been added successfully and an old one hasn't changed. + // The new schedule doesn't have the same value as the old one. + assert_eq!(user2_vesting_amount, Uint128::new(200u128)); + assert_eq!(user1_vesting_amount, Uint128::from(100u128)); + + // Add one more vesting schedule; final amount to vest must increase + let msg = Cw20ExecuteMsg::Send { + contract: vesting_instance.to_string(), + msg: to_binary(&Cw20HookMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user1.to_string(), + schedules: vec![VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(200).seconds(), + amount: Uint128::new(10), + }), + }], + }], + }) + .unwrap(), + amount: Uint128::from(10u128), + }; + + let _res = app + .execute_contract(owner.clone(), cw20_token_instance.clone(), &msg, &[]) + .unwrap(); + + let msg = QueryMsg::AvailableAmount { + address: user1.to_string(), + }; + + let vesting_res: Uint128 = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &msg) + .unwrap(); + + assert_eq!(vesting_res, Uint128::new(110u128)); + check_token_balance( + &mut app, + &cw20_token_instance, + &owner.clone(), + TOKEN_INITIAL_AMOUNT - 310u128, + ); + check_token_balance(&mut app, &cw20_token_instance, &vesting_instance, 310u128); + + let msg = ExecuteMsg::Claim { + recipient: None, + amount: None, + }; + let _res = app + .execute_contract(user1.clone(), vesting_instance.clone(), &msg, &[]) + .unwrap(); + + let msg = QueryMsg::VestingAccount { + address: user1.to_string(), + }; + + let vesting_res: VestingAccountResponse = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &msg) + .unwrap(); + assert_eq!(vesting_res.info.released_amount, Uint128::from(110u128)); + check_token_balance(&mut app, &cw20_token_instance, &vesting_instance, 200u128); + check_token_balance(&mut app, &cw20_token_instance, &user1, 110u128); + + // Owner balance mustn't change after claim + check_token_balance( + &mut app, + &cw20_token_instance, + &owner.clone(), + TOKEN_INITIAL_AMOUNT - 310u128, + ); +} + +#[test] +fn register_vesting_accounts_native() { + let user1 = Addr::unchecked(USER1); + let user2 = Addr::unchecked(USER2); + let owner = Addr::unchecked(OWNER1); + + let mut app = mock_app(&owner); + + let token_code_id = store_token_code(&mut app); + + let random_token_instance = + instantiate_token(&mut app, token_code_id, "RND", Some(1_000_000_000_000_000)); + + mint_tokens( + &mut app, + &random_token_instance, + &owner, + TOKEN_INITIAL_AMOUNT, + ); + + let vesting_instance = instantiate_vesting_remote_chain(&mut app); + + let msg = Cw20ExecuteMsg::Send { + contract: vesting_instance.to_string(), + msg: to_binary(&Cw20HookMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user1.to_string(), + schedules: vec![VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(150).seconds(), + amount: Uint128::new(100), + }), + }], + }], + }) + .unwrap(), + amount: Uint128::from(100u128), + }; + + let err = app + .execute_contract(owner.clone(), random_token_instance.clone(), &msg, &[]) + .unwrap_err(); + assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()); + + // Checking that execute endpoint with random native coin is unreachable + let native_msg = ExecuteMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user1.to_string(), + schedules: vec![VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(150).seconds(), + amount: Uint128::new(100), + }), + }], + }], + }; + + let err = app + .execute_contract( + owner.clone(), + vesting_instance.clone(), + &native_msg, + &coins(100u128, "random_coin"), + ) + .unwrap_err(); + assert_eq!( + ContractError::PaymentError(PaymentError::MissingDenom("vesting_token".to_string())), + err.downcast().unwrap() + ); + + app.execute_contract( + owner.clone(), + vesting_instance.clone(), + &native_msg, + &coins(100u128, VESTING_TOKEN), + ) + .unwrap(); + + let msg = QueryMsg::AvailableAmount { + address: user1.to_string(), + }; + + let user1_vesting_amount: Uint128 = app + .wrap() + .query_wasm_smart(&vesting_instance, &msg) + .unwrap(); + assert_eq!(user1_vesting_amount.u128(), 100u128); + + let bal = query_balance(&app.wrap(), &owner, VESTING_TOKEN) + .unwrap() + .u128(); + assert_eq!(bal, TOKEN_INITIAL_AMOUNT - 100u128); + + let bal = query_balance(&app.wrap(), &vesting_instance, VESTING_TOKEN) + .unwrap() + .u128(); + assert_eq!(bal, 100); + + // Let's check user1's final vesting amount after add schedule for a new one + let msg = ExecuteMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user2.to_string(), + schedules: vec![VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(150).seconds(), + amount: Uint128::new(200), + }), + }], + }], + }; + + app.execute_contract( + owner.clone(), + vesting_instance.clone(), + &msg, + &coins(200, VESTING_TOKEN), + ) + .unwrap(); + + let msg = QueryMsg::AvailableAmount { + address: user2.to_string(), + }; + + let user2_vesting_amount: Uint128 = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &msg) + .unwrap(); + + let bal = query_balance(&app.wrap(), &owner, VESTING_TOKEN) + .unwrap() + .u128(); + assert_eq!(bal, TOKEN_INITIAL_AMOUNT - 300u128); + let bal = query_balance(&app.wrap(), &vesting_instance, VESTING_TOKEN) + .unwrap() + .u128(); + assert_eq!(bal, 300u128); + + // A new schedule has been added successfully and an old one hasn't changed. + // The new schedule doesn't have the same value as the old one. + assert_eq!(user2_vesting_amount, Uint128::new(200u128)); + assert_eq!(user1_vesting_amount, Uint128::from(100u128)); + + // Add one more vesting schedule; final amount to vest must increase + let msg = ExecuteMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user1.to_string(), + schedules: vec![VestingSchedule { + start_point: VestingSchedulePoint { + time: Timestamp::from_seconds(100).seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: Timestamp::from_seconds(200).seconds(), + amount: Uint128::new(10), + }), + }], + }], + }; + + app.execute_contract( + owner.clone(), + vesting_instance.clone(), + &msg, + &coins(10, VESTING_TOKEN), + ) + .unwrap(); + + let msg = QueryMsg::AvailableAmount { + address: user1.to_string(), + }; + + let vesting_res: Uint128 = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &msg) + .unwrap(); + assert_eq!(vesting_res, Uint128::new(110u128)); + + let bal = query_balance(&app.wrap(), &owner, VESTING_TOKEN) + .unwrap() + .u128(); + assert_eq!(bal, TOKEN_INITIAL_AMOUNT - 310u128); + let bal = query_balance(&app.wrap(), &vesting_instance, VESTING_TOKEN) + .unwrap() + .u128(); + assert_eq!(bal, 310u128); + + let msg = ExecuteMsg::Claim { + recipient: None, + amount: None, + }; + let _res = app + .execute_contract(user1.clone(), vesting_instance.clone(), &msg, &[]) + .unwrap(); + + let msg = QueryMsg::VestingAccount { + address: user1.to_string(), + }; + + let vesting_res: VestingAccountResponse = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &msg) + .unwrap(); + assert_eq!(vesting_res.info.released_amount, Uint128::from(110u128)); + + let bal = query_balance(&app.wrap(), &vesting_instance, VESTING_TOKEN) + .unwrap() + .u128(); + assert_eq!(bal, 200); + let bal = query_balance(&app.wrap(), &user1, VESTING_TOKEN) + .unwrap() + .u128(); + assert_eq!(bal, 110u128); + + let bal = query_balance(&app.wrap(), &owner, VESTING_TOKEN) + .unwrap() + .u128(); + assert_eq!(bal, TOKEN_INITIAL_AMOUNT - 310u128); +} + +#[test] +fn query_at_height() { + let user1 = Addr::unchecked(USER1); + let user2 = Addr::unchecked(USER2); + let owner = Addr::unchecked(OWNER1); + + let mut app = mock_app(&owner); + let start_block_height = app.block_info().height; + + let vesting_instance = instantiate_vesting_remote_chain(&mut app); + + let native_msg = ExecuteMsg::RegisterVestingAccounts { + vesting_accounts: vec![ + VestingAccount { + address: user1.to_string(), + schedules: vec![ + VestingSchedule { + start_point: VestingSchedulePoint { + time: app.block_info().time.seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: app + .block_info() + .time + .plus_seconds(100 * BLOCK_TIME) + .seconds(), + amount: Uint128::new(50), + }), + }, + VestingSchedule { + start_point: VestingSchedulePoint { + time: app.block_info().time.seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: app + .block_info() + .time + .plus_seconds(100 * BLOCK_TIME) + .seconds(), + amount: Uint128::new(150), + }), + }, + ], + }, + VestingAccount { + address: user2.to_string(), + schedules: vec![VestingSchedule { + start_point: VestingSchedulePoint { + time: app.block_info().time.seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: app + .block_info() + .time + .plus_seconds(100 * BLOCK_TIME) + .seconds(), + amount: Uint128::new(1000), + }), + }], + }, + ], + }; + + app.execute_contract( + owner, + vesting_instance.clone(), + &native_msg, + &coins(1200, VESTING_TOKEN), + ) + .unwrap(); + + let query = QueryMsg::AvailableAmount { + address: user1.to_string(), + }; + + for _ in 1..=10 { + let vesting_res: Uint128 = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &query) + .unwrap(); + assert_eq!(vesting_res, Uint128::new(0u128)); + + app.update_block(|b| { + b.height += 10; + b.time = b.time.plus_seconds(10 * BLOCK_TIME) + }); + + let vesting_res: Uint128 = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &query) + .unwrap(); + assert_eq!(vesting_res, Uint128::new(20u128)); + + let msg = ExecuteMsg::Claim { + recipient: None, + amount: None, + }; + let _res = app + .execute_contract(user1.clone(), vesting_instance.clone(), &msg, &[]) + .unwrap(); + + let vesting_res: Uint128 = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &query) + .unwrap(); + assert_eq!(vesting_res, Uint128::new(0u128)); + } + app.update_block(|b| { + b.height += 100; + b.time = b.time.plus_seconds(100 * BLOCK_TIME) + }); + let vesting_res: Uint128 = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &query) + .unwrap(); + assert_eq!(vesting_res, Uint128::new(0u128)); + + let query_user_unclamed = QueryMsg::HistoricalExtension { + msg: QueryMsgHistorical::UnclaimedAmountAtHeight { + address: user1.to_string(), + height: start_block_height - 1, + }, + }; + let vesting_res: Uint128 = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &query_user_unclamed) + .unwrap(); + assert_eq!(vesting_res, Uint128::new(0u128)); + + let query_total_unclamed = QueryMsg::HistoricalExtension { + msg: QueryMsgHistorical::UnclaimedTotalAmountAtHeight { + height: start_block_height - 1, + }, + }; + let vesting_res: Uint128 = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &query_total_unclamed) + .unwrap(); + assert_eq!(vesting_res, Uint128::new(0u128)); + let max_unclaimed_user1: u128 = 200; + let max_unclaimed_total: u128 = 1200; + for i in 0..=10 { + let query = QueryMsg::HistoricalExtension { + msg: QueryMsgHistorical::UnclaimedAmountAtHeight { + address: user1.to_string(), + height: start_block_height + 1 + i * 10, + }, + }; + let vesting_res: Uint128 = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &query) + .unwrap(); + assert_eq!( + vesting_res, + Uint128::new(max_unclaimed_user1 - (i as u128) * 20) + ); + + let query_total_unclamed = QueryMsg::HistoricalExtension { + msg: QueryMsgHistorical::UnclaimedTotalAmountAtHeight { + height: start_block_height + 1 + i * 10, + }, + }; + let vesting_res: Uint128 = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &query_total_unclamed) + .unwrap(); + assert_eq!( + vesting_res, + Uint128::new(max_unclaimed_total - (i as u128) * 20) + ); + } +} + +#[test] +fn vesting_managers() { + let user1 = Addr::unchecked(USER1); + let user2 = Addr::unchecked(USER2); + let owner = Addr::unchecked(OWNER1); + + let mut app = mock_app(&owner); + let vesting_instance = instantiate_vesting_remote_chain(&mut app); + + let query = QueryMsg::WithManagersExtension { + msg: QueryMsgWithManagers::VestingManagers {}, + }; + let vesting_res: Vec = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &query) + .unwrap(); + assert_eq!(vesting_res.len(), 0,); + + let native_msg = ExecuteMsg::RegisterVestingAccounts { + vesting_accounts: vec![VestingAccount { + address: user1.to_string(), + schedules: vec![VestingSchedule { + start_point: VestingSchedulePoint { + time: app.block_info().time.seconds(), + amount: Uint128::zero(), + }, + end_point: Some(VestingSchedulePoint { + time: app + .block_info() + .time + .plus_seconds(100 * BLOCK_TIME) + .seconds(), + amount: Uint128::new(50), + }), + }], + }], + }; + let err = app + .execute_contract(user1.clone(), vesting_instance.clone(), &native_msg, &[]) + .unwrap_err(); + assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()); + + let add_manager_msg = ExecuteMsg::WithManagersExtension { + msg: ExecuteMsgWithManagers::AddVestingManagers { + managers: vec![user1.to_string()], + }, + }; + + let err = app + .execute_contract( + user1.clone(), + vesting_instance.clone(), + &add_manager_msg, + &[], + ) + .unwrap_err(); + assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()); + + let _res = app + .execute_contract( + owner.clone(), + vesting_instance.clone(), + &add_manager_msg, + &[], + ) + .unwrap(); + + let vesting_res: Vec = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &query) + .unwrap(); + assert_eq!(vesting_res, vec![Addr::unchecked(user1.clone())]); + + app.send_tokens(owner.clone(), user1.clone(), &coins(50, VESTING_TOKEN)) + .unwrap(); + + let _res = app + .execute_contract( + user1.clone(), + vesting_instance.clone(), + &native_msg, + &coins(50, VESTING_TOKEN), + ) + .unwrap(); + let err = app + .execute_contract(user2, vesting_instance.clone(), &native_msg, &[]) + .unwrap_err(); + assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()); + + let remove_manager_msg = ExecuteMsg::WithManagersExtension { + msg: ExecuteMsgWithManagers::RemoveVestingManagers { + managers: vec![user1.to_string()], + }, + }; + let err = app + .execute_contract(user1, vesting_instance.clone(), &remove_manager_msg, &[]) + .unwrap_err(); + assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()); + + let _res = app + .execute_contract(owner, vesting_instance.clone(), &remove_manager_msg, &[]) + .unwrap(); + + let vesting_res: Vec = app + .wrap() + .query_wasm_smart(vesting_instance, &query) + .unwrap(); + assert_eq!(vesting_res.len(), 0); +} + +fn mock_app(owner: &Addr) -> App { + App::new(|app, _, storage| { + app.bank + .init_balance( + storage, + owner, + vec![ + coin(TOKEN_INITIAL_AMOUNT, VESTING_TOKEN), + coin(10_000_000_000u128, "random_coin"), + ], + ) + .unwrap() + }) +} + +fn store_token_code(app: &mut App) -> u64 { + let cw20_token_contract = Box::new(ContractWrapper::new_with_empty( + astroport_token::contract::execute, + astroport_token::contract::instantiate, + astroport_token::contract::query, + )); + + app.store_code(cw20_token_contract) +} + +fn instantiate_token(app: &mut App, token_code_id: u64, name: &str, cap: Option) -> Addr { + let name = String::from(name); + + let msg = TokenInstantiateMsg { + name: name.clone(), + symbol: name.clone(), + decimals: 6, + initial_balances: vec![], + mint: Some(MinterResponse { + minter: String::from(OWNER1), + cap: cap.map(Uint128::from), + }), + marketing: None, + }; + + app.instantiate_contract( + token_code_id, + Addr::unchecked(OWNER1), + &msg, + &[], + name, + None, + ) + .unwrap() +} + +fn instantiate_vesting(app: &mut App, cw20_token_instance: &Addr) -> Addr { + let vesting_contract = Box::new(ContractWrapper::new_with_empty( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + )); + let owner = Addr::unchecked(OWNER1); + let token_manager = Addr::unchecked(TOKEN_MANAGER); + let vesting_code_id = app.store_code(vesting_contract); + + let init_msg = InstantiateMsg { + owner: OWNER1.to_string(), + token_info_manager: TOKEN_MANAGER.to_string(), + vesting_managers: vec![], + }; + + let vesting_instance = app + .instantiate_contract( + vesting_code_id, + owner.clone(), + &init_msg, + &[], + "Vesting", + None, + ) + .unwrap(); + let set_vesting_token_msg = ExecuteMsg::SetVestingToken { + vesting_token: token_asset_info(cw20_token_instance.clone()), + }; + app.execute_contract( + token_manager, + vesting_instance.clone(), + &set_vesting_token_msg, + &[], + ) + .unwrap(); + + let res: Config = app + .wrap() + .query_wasm_smart(vesting_instance.clone(), &QueryMsg::Config {}) + .unwrap(); + assert_eq!( + cw20_token_instance.to_string(), + res.vesting_token.unwrap().to_string() + ); + + mint_tokens(app, cw20_token_instance, &owner, TOKEN_INITIAL_AMOUNT); + + check_token_balance(app, cw20_token_instance, &owner, TOKEN_INITIAL_AMOUNT); + + vesting_instance +} + +fn instantiate_vesting_remote_chain(app: &mut App) -> Addr { + let vesting_contract = Box::new(ContractWrapper::new_with_empty( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + )); + let owner = Addr::unchecked(OWNER1); + let token_manager = Addr::unchecked(TOKEN_MANAGER); + let vesting_code_id = app.store_code(vesting_contract); + + let init_msg = InstantiateMsg { + owner: OWNER1.to_string(), + token_info_manager: TOKEN_MANAGER.to_string(), + vesting_managers: vec![], + }; + + let res = app + .instantiate_contract(vesting_code_id, owner, &init_msg, &[], "Vesting", None) + .unwrap(); + let msg = ExecuteMsg::SetVestingToken { + vesting_token: native_asset_info(VESTING_TOKEN.to_string()), + }; + app.execute_contract(token_manager, res.clone(), &msg, &[]) + .unwrap(); + res +} + +fn mint_tokens(app: &mut App, token: &Addr, recipient: &Addr, amount: u128) { + let msg = Cw20ExecuteMsg::Mint { + recipient: recipient.to_string(), + amount: Uint128::from(amount), + }; + + app.execute_contract(Addr::unchecked(OWNER1), token.to_owned(), &msg, &[]) + .unwrap(); +} + +fn check_token_balance(app: &mut App, token: &Addr, address: &Addr, expected: u128) { + let msg = Cw20QueryMsg::Balance { + address: address.to_string(), + }; + let res: StdResult = app.wrap().query_wasm_smart(token, &msg); + assert_eq!(res.unwrap().balance, Uint128::from(expected)); +} diff --git a/contracts/vesting-lp-pcl/src/tests/mod.rs b/contracts/vesting-lp-pcl/src/tests/mod.rs new file mode 100644 index 00000000..6d3bbe60 --- /dev/null +++ b/contracts/vesting-lp-pcl/src/tests/mod.rs @@ -0,0 +1 @@ +mod integration; diff --git a/contracts/vesting-lp/.cargo/config b/contracts/vesting-lp/.cargo/config index f2fbc6a9..a79b8fdb 100644 --- a/contracts/vesting-lp/.cargo/config +++ b/contracts/vesting-lp/.cargo/config @@ -3,4 +3,4 @@ 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 vesting-lp_schema" +schema = "run --example vesting_schema" diff --git a/contracts/vesting-lp/Cargo.toml b/contracts/vesting-lp/Cargo.toml index 31f70c7c..ad739e0e 100644 --- a/contracts/vesting-lp/Cargo.toml +++ b/contracts/vesting-lp/Cargo.toml @@ -9,20 +9,20 @@ description = "Vesting contract with a voting capabilities. Provides queries to crate-type = ["cdylib", "rlib"] [features] -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] # use library feature to disable all init/handle/query exports library = [] [dependencies] -cw2 = { workspace = true } -cw20 = { workspace = true } -astroport = { workspace = true } -vesting-base = { workspace = true } -cosmwasm-schema = { workspace = true } -cosmwasm-std = { workspace = true } +cw2 = { version = "0.15" } +vesting-base = {path = "../../packages/vesting-base"} +vesting-base-lp = {path = "../../packages/vesting-base-lp"} +astroport = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v2.8.0" } +cosmwasm-schema = { version = "1.1", default-features = false } +cosmwasm-std = { version = "1.1" } +cw-storage-plus = "0.15" [dev-dependencies] -cw-multi-test = { workspace = true } +cw-multi-test = "0.15" astroport-token = {git = "https://github.com/astroport-fi/astroport-core.git", rev = "65ce7d1879cc5d95b09fa14202f0423bba52ae0e" } +cw20 = { version = "0.15" } cw-utils = "0.15" diff --git a/contracts/vesting-lp/examples/vesting_schema.rs b/contracts/vesting-lp/examples/vesting_schema.rs new file mode 100644 index 00000000..90284134 --- /dev/null +++ b/contracts/vesting-lp/examples/vesting_schema.rs @@ -0,0 +1,12 @@ +use cosmwasm_schema::write_api; +use vesting_base::msg::{ExecuteMsg, MigrateMsg, QueryMsg}; +use vesting_lp::msg::InstantiateMsg; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + query: QueryMsg, + execute: ExecuteMsg, + migrate: MigrateMsg + } +} diff --git a/contracts/vesting-lp/src/contract.rs b/contracts/vesting-lp/src/contract.rs index 4b3a4659..bf18ed13 100644 --- a/contracts/vesting-lp/src/contract.rs +++ b/contracts/vesting-lp/src/contract.rs @@ -2,9 +2,12 @@ use crate::msg::InstantiateMsg; use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; use cw2::set_contract_version; use vesting_base::builder::VestingBaseBuilder; -use vesting_base::error::ContractError; -use vesting_base::handlers::{execute as base_execute, query as base_query}; -use vesting_base::msg::{ExecuteMsg, QueryMsg}; +use vesting_base_lp::error::ContractError; +use vesting_base_lp::handlers::execute as base_execute; +use vesting_base_lp::handlers::migrate as base_migrate; +use vesting_base_lp::handlers::query as base_query; +use vesting_base_lp::msg::QueryMsg; +use vesting_base_lp::msg::{ExecuteMsg, MigrateMsg}; /// Contract name that is used for migration. const CONTRACT_NAME: &str = "neutron-vesting-lp"; @@ -44,3 +47,9 @@ pub fn execute( pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { base_query(deps, env, msg) } + +/// Exposes migrate functions available in the contract. +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Result { + base_migrate(deps, env, msg) +} diff --git a/contracts/vesting-lp/src/lib.rs b/contracts/vesting-lp/src/lib.rs index 08d6d688..112ecadc 100644 --- a/contracts/vesting-lp/src/lib.rs +++ b/contracts/vesting-lp/src/lib.rs @@ -1,5 +1,2 @@ pub mod contract; pub mod msg; - -#[cfg(test)] -mod tests; diff --git a/packages/vesting-base-lp/Cargo.toml b/packages/vesting-base-lp/Cargo.toml new file mode 100644 index 00000000..d5aca69c --- /dev/null +++ b/packages/vesting-base-lp/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "vesting-base-lp" +version = "1.1.0" +authors = ["Astroport"] +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all init/handle/query exports +library = [] + +[dependencies] +serde = { version = "1.0.145", default-features = false, features = ["derive"] } +cw2 = { version = "0.15" } +cw20 = { version = "0.15" } +cosmwasm-std = { version = "1.1" } +cw-storage-plus = "0.15" +thiserror = { version = "1.0" } +cw-utils = "0.15" +cosmwasm-schema = { version = "1.1", default-features = false } +vesting-base = {path = "../vesting-base"} +vesting-base-pcl = {path = "../vesting-base-pcl"} +astroport = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v2.8.0" } diff --git a/packages/vesting-base-lp/NOTICE b/packages/vesting-base-lp/NOTICE new file mode 100644 index 00000000..84b1c210 --- /dev/null +++ b/packages/vesting-base-lp/NOTICE @@ -0,0 +1,14 @@ +CW20-Base: A reference implementation for fungible token on CosmWasm +Copyright (C) 2020 Confio OÜ + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/packages/vesting-base-lp/README.md b/packages/vesting-base-lp/README.md new file mode 100644 index 00000000..57accc63 --- /dev/null +++ b/packages/vesting-base-lp/README.md @@ -0,0 +1,251 @@ +# Neutron Vesting Base + +This library contains basis for configuration and initialisation of vesting contracts. It also contains data models and handlers for interaction with vesting contracts. + +## Usage + +1. To use the library for initialisation of a simple vesting contract just build a default vesting base in its instantiate message: +```rust +use vesting_base::builder::VestingBaseBuilder; + +pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + ... + VestingBaseBuilder::default().build(deps, msg.owner, msg.vesting_token)?; + ... +``` + +Read about more advanced building in the [Extensions](#extensions) section. + +2. Simply pass the execute and query requests to the vesting base's execute and query handlers: +```rust +use vesting_base::handlers::{execute as base_execute, query as base_query}; + +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + base_execute(deps, env, info, msg) +} + +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + base_query(deps, env, msg) +} +``` + +### Messages + +The default version exposes the following messages: + +#### ExecuteMsg + +```rust +/// This structure describes the execute messages available in a vesting contract. +#[cw_serde] +pub enum ExecuteMsg { + /// Claim claims vested tokens and sends them to a recipient + Claim { + /// The address that receives the vested tokens + recipient: Option, + /// The amount of tokens to claim + amount: Option, + }, + /// Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template + Receive(Cw20ReceiveMsg), + /// RegisterVestingAccounts registers vesting targets/accounts + RegisterVestingAccounts { + vesting_accounts: Vec, + }, + /// Creates a request to change contract ownership + /// ## Executor + /// Only the current owner can execute this + ProposeNewOwner { + /// The newly proposed owner + owner: String, + /// The validity period of the offer to change the owner + expires_in: u64, + }, + /// Removes a request to change contract ownership + /// ## Executor + /// Only the current owner can execute this + DropOwnershipProposal {}, + /// Claims contract ownership + /// ## Executor + /// Only the newly proposed owner can execute this + ClaimOwnership {}, + /// Sets vesting token + /// ## Executor + /// Only the current owner or token info manager can execute this + SetVestingToken { vesting_token: AssetInfo }, + /// Contains messages associated with the managed extension for vesting contracts. + ManagedExtension { msg: ExecuteMsgManaged }, + /// Contains messages associated with the with_managers extension for vesting contracts. + WithManagersExtension { msg: ExecuteMsgWithManagers }, + /// Contains messages associated with the historical extension for vesting contracts. + HistoricalExtension { msg: ExecuteMsgHistorical }, +} +``` + +The `ManagedExtension`, `WithManagersExtension`, and `HistoricalExtension` messages are extensiom messages. Read about them in the [Extensions](#extensions) section. + +#### QueryMsg + +```rust +/// This structure describes the query messages available in a vesting contract. +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + /// Returns the configuration for the contract using a [`ConfigResponse`] object. + #[returns(ConfigResponse)] + Config {}, + /// Returns information about an address vesting tokens using a [`VestingAccountResponse`] object. + #[returns(VestingAccountResponse)] + VestingAccount { address: String }, + /// Returns a list of addresses that are vesting tokens using a [`VestingAccountsResponse`] object. + #[returns(VestingAccountsResponse)] + VestingAccounts { + start_after: Option, + limit: Option, + order_by: Option, + }, + /// Returns the total unvested amount of tokens for a specific address. + #[returns(Uint128)] + AvailableAmount { address: String }, + /// Timestamp returns the current timestamp + #[returns(u64)] + Timestamp {}, + /// VestingState returns the current vesting state. + #[returns(VestingState)] + VestingState {}, + /// Contains messages associated with the managed extension for vesting contracts. + #[returns(QueryMsgManaged)] + ManagedExtension { msg: QueryMsgManaged }, + /// Contains messages associated with the with_managers extension for vesting contracts. + #[returns(QueryMsgWithManagers)] + WithManagersExtension { msg: QueryMsgWithManagers }, + /// Contains messages associated with the historical extension for vesting contracts. + #[returns(QueryMsgHistorical)] + HistoricalExtension { msg: QueryMsgHistorical }, +} +``` + +The `ManagedExtension`, `WithManagersExtension`, and `HistoricalExtension` messages are extensiom messages. Read about them in the [Extensions](#extensions) section. + +## Extensions + +Created contracts can be extended with a number of features. + +### Managed + +The `managed` extension allows the owner of the vesting contract to remove registered vesting accounts and redeem the corresponding funds. + +```rust +/// This structure describes the execute messages available in a managed vesting contract. +#[cw_serde] +pub enum ExecuteMsgManaged { + /// Removes vesting targets/accounts. + /// ## Executor + /// Only the current owner can execute this + RemoveVestingAccounts { + vesting_accounts: Vec, + /// Specifies the account that will receive the funds taken from the vesting accounts. + clawback_account: String, + }, +} + +/// This structure describes the query messages available in a managed vesting contract. +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsgManaged {} +``` + +### WithManagers + +The `with_managers` extension allows the owner of the vesting contract to add/remove vesting managers — addresses that just like the owner are capable of registering new vesting accounts. + +```rust +/// This structure describes the execute messages available in a with_managers vesting contract. +#[cw_serde] +pub enum ExecuteMsgWithManagers { + /// Adds vesting managers + /// ## Executor + /// Only the current owner can execute this + AddVestingManagers { managers: Vec }, + /// Removes vesting managers + /// ## Executor + /// Only the current owner can execute this + RemoveVestingManagers { managers: Vec }, +} + +/// This structure describes the query messages available in a with_managers vesting contract. +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsgWithManagers { + /// Returns list of vesting managers + /// (the persons who are able to add/remove vesting schedules) + #[returns(Vec)] + VestingManagers {}, +} +``` + +### Historical + +The `historical` allows to query vesting accounts and total vesting state based on a given height. + +```rust +/// This structure describes the execute messages available in a historical vesting contract. +#[cw_serde] +pub enum ExecuteMsgHistorical {} + +/// This structure describes the query messages available in a historical vesting contract. +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsgHistorical { + /// Returns the total unclaimed amount of tokens for a specific address at certain height. + #[returns(Uint128)] + UnclaimedAmountAtHeight { address: String, height: u64 }, + /// Returns the total unclaimed amount of tokens for all the users at certain height. + #[returns(Uint128)] + UnclaimedTotalAmountAtHeight { height: u64 }, +} +``` + +### Extensions usage + +The following example adds all three extensions to the contract, but it's allowed to combine them in any way. +```rust +use vesting_base::builder::VestingBaseBuilder; +use astroport::asset::AssetInfo; +use cosmwasm_schema::cw_serde; + +/// This structure describes the parameters used for creating a contract. +#[cw_serde] +pub struct InstantiateMsg { + /// Address allowed to change contract parameters + pub owner: String, + /// [`AssetInfo`] of the token that's being vested + pub vesting_token: AssetInfo, + /// Initial list of whitelisted vesting managers + pub vesting_managers: Vec, +} + +pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + ... + VestingBaseBuilder::default() + .historical() + .managed() + .with_managers(msg.vesting_managers) + .build(deps, msg.owner, msg.vesting_token)?; + ... +``` diff --git a/packages/vesting-base-lp/src/builder.rs b/packages/vesting-base-lp/src/builder.rs new file mode 100644 index 00000000..85957245 --- /dev/null +++ b/packages/vesting-base-lp/src/builder.rs @@ -0,0 +1,60 @@ +use crate::state::{CONFIG, VESTING_MANAGERS}; +use crate::types::{Config, Extensions}; +use cosmwasm_std::{DepsMut, StdResult}; + +/// A builder for vesting contracts with different extensions. +#[derive(Default)] +pub struct VestingBaseBuilder { + vesting_managers: Vec, + historical: bool, + managed: bool, + with_managers: bool, +} + +impl VestingBaseBuilder { + /// Appends the `managed` extension to the created vesting contract. + pub fn managed(&mut self) -> &mut VestingBaseBuilder { + self.managed = true; + self + } + + /// Appends the `with_managers` extension to the created vesting contract. + pub fn with_managers(&mut self, managers: Vec) -> &mut VestingBaseBuilder { + self.vesting_managers.extend(managers); + self.with_managers = true; + self + } + + /// Appends the `historical` extension to the created vesting contract. + pub fn historical(&mut self) -> &mut VestingBaseBuilder { + self.historical = true; + self + } + + /// Validates the inputs and initialises the created contract state. + pub fn build(&self, deps: DepsMut, owner: String, token_info_manager: String) -> StdResult<()> { + let owner = deps.api.addr_validate(&owner)?; + CONFIG.save( + deps.storage, + &Config { + owner, + vesting_token: None, + token_info_manager: deps.api.addr_validate(&token_info_manager)?, + extensions: Extensions { + historical: self.historical, + managed: self.managed, + with_managers: self.with_managers, + }, + }, + )?; + + if self.with_managers { + for m in self.vesting_managers.iter() { + let ma = deps.api.addr_validate(m)?; + VESTING_MANAGERS.save(deps.storage, ma, &())?; + } + }; + + Ok(()) + } +} diff --git a/packages/vesting-base-lp/src/error.rs b/packages/vesting-base-lp/src/error.rs new file mode 100644 index 00000000..c394825b --- /dev/null +++ b/packages/vesting-base-lp/src/error.rs @@ -0,0 +1,65 @@ +use cosmwasm_std::{Decimal, OverflowError, StdError}; +use cw_utils::PaymentError; +use thiserror::Error; + +/// This enum describes generator vesting contract errors +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + PaymentError(#[from] PaymentError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("Amount is not available!")] + AmountIsNotAvailable {}, + + #[error("Vesting schedule error on addr: {0}. Should satisfy: (start < end and at_start < total) or (start = end and at_start = total)")] + VestingScheduleError(String), + + #[error("Vesting schedule amount error. The total amount should be equal to the CW20 receive amount.")] + VestingScheduleAmountError {}, + + #[error("Contract can't be migrated!")] + MigrationError {}, + + #[error("Vesting token is not set!")] + VestingTokenIsNotSet {}, + + #[error("Contract is in migration state. Please wait for migration to complete.")] + MigrationIncomplete {}, + + #[error( + "Provided slippage tolerance {slippage_tolerance} is more than the max allowed {max_slippage_tolerance}" + )] + MigrationSlippageToBig { + slippage_tolerance: Decimal, + max_slippage_tolerance: Decimal, + }, + + #[error("Migration is complete")] + MigrationComplete {}, +} + +#[allow(clippy::from_over_into)] +impl Into for ContractError { + fn into(self) -> StdError { + StdError::generic_err(self.to_string()) + } +} + +impl From for ContractError { + fn from(o: OverflowError) -> Self { + StdError::from(o).into() + } +} + +pub fn ext_unsupported_err(extension: impl Into + std::fmt::Display) -> StdError { + StdError::generic_err(format!( + "Extension is not enabled for the contract: {}.", + extension + )) +} diff --git a/packages/vesting-base-lp/src/ext_historical.rs b/packages/vesting-base-lp/src/ext_historical.rs new file mode 100644 index 00000000..d494adf6 --- /dev/null +++ b/packages/vesting-base-lp/src/ext_historical.rs @@ -0,0 +1,101 @@ +use crate::error::{ext_unsupported_err, ContractError}; +use crate::msg::{ExecuteMsgHistorical, QueryMsgHistorical}; +use crate::state::{vesting_info, vesting_state, CONFIG}; +use crate::types::VestingInfo; +use cosmwasm_std::{ + to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Uint128, +}; + +/// Contains the historical extension check and routing of the message. +pub(crate) fn handle_execute_historical_msg( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: ExecuteMsgHistorical, +) -> Result { + let config = CONFIG.load(deps.storage)?; + if !config.extensions.historical { + return Err(ext_unsupported_err("historical").into()); + } + + // empty handler kept for uniformity with other extensions + unimplemented!() +} + +/// Contains the historical extension check and routing of the message. +pub(crate) fn handle_query_historical_msg( + deps: Deps, + _env: Env, + msg: QueryMsgHistorical, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + if !config.extensions.historical { + return Err(ext_unsupported_err("historical")); + } + + match msg { + QueryMsgHistorical::UnclaimedAmountAtHeight { address, height } => { + to_binary(&query_unclaimed_amount_at_height(deps, address, height)?) + } + QueryMsgHistorical::UnclaimedTotalAmountAtHeight { height } => { + to_binary(&query_total_unclaimed_amount_at_height(deps, height)?) + } + } +} + +/// Returns the available amount of distributed and yet to be claimed tokens for a specific vesting recipient at certain height. +/// +/// * **address** vesting recipient for which to return the available amount of tokens to claim. +/// +/// * **height** the height we querying unclaimed amount for +fn query_unclaimed_amount_at_height( + deps: Deps, + address: String, + height: u64, +) -> StdResult { + let address = deps.api.addr_validate(&address)?; + + let config = CONFIG.load(deps.storage)?; + let maybe_info = vesting_info(config.extensions.historical).may_load_at_height( + deps.storage, + address, + height, + )?; + match &maybe_info { + Some(info) => compute_unclaimed_amount(info), + None => Ok(Uint128::zero()), + } +} + +/// Returns the available amount of distributed and yet to be claimed tokens for all the recipients at certain height. +/// +/// * **height** the height we querying unclaimed amount for +fn query_total_unclaimed_amount_at_height(deps: Deps, height: u64) -> StdResult { + let config = CONFIG.load(deps.storage)?; + let maybe_state = + vesting_state(config.extensions.historical).may_load_at_height(deps.storage, height)?; + match &maybe_state { + Some(info) => Ok(info.total_granted.checked_sub(info.total_released)?), + None => Ok(Uint128::zero()), + } +} + +/// Computes the amount of distributed and yet unclaimed tokens for a specific vesting recipient at certain height. +/// Returns the computed amount if the operation is successful. +/// +/// * **vesting_info** vesting schedules for which to compute the amount of tokens +/// that are vested and can be claimed by the recipient. +fn compute_unclaimed_amount(vesting_info: &VestingInfo) -> StdResult { + let mut available_amount: Uint128 = Uint128::zero(); + for sch in &vesting_info.schedules { + if let Some(end_point) = &sch.end_point { + available_amount = available_amount.checked_add(end_point.amount)?; + } else { + available_amount = available_amount.checked_add(sch.start_point.amount)?; + } + } + + available_amount + .checked_sub(vesting_info.released_amount) + .map_err(StdError::from) +} diff --git a/packages/vesting-base-lp/src/ext_managed.rs b/packages/vesting-base-lp/src/ext_managed.rs new file mode 100644 index 00000000..b11cc1b0 --- /dev/null +++ b/packages/vesting-base-lp/src/ext_managed.rs @@ -0,0 +1,121 @@ +use crate::error::{ext_unsupported_err, ContractError}; +use crate::handlers::get_vesting_token; +use crate::msg::{ExecuteMsgManaged, QueryMsgManaged}; +use crate::state::{vesting_info, vesting_state, CONFIG}; +use astroport::asset::AssetInfoExt; +use cosmwasm_std::{ + attr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, SubMsg, Uint128, +}; + +/// Contains the managed extension check and routing of the message. +pub(crate) fn handle_execute_managed_msg( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsgManaged, +) -> Result { + let config = CONFIG.load(deps.storage)?; + if !config.extensions.managed { + return Err(ext_unsupported_err("managed").into()); + } + + match msg { + ExecuteMsgManaged::RemoveVestingAccounts { + vesting_accounts, + clawback_account, + } => remove_vesting_accounts(deps, env, info, vesting_accounts, clawback_account), + } +} + +/// Contains the managed extension check and routing of the message. +pub(crate) fn handle_query_managed_msg( + deps: Deps, + _env: Env, + _msg: QueryMsgManaged, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + if !config.extensions.managed { + return Err(ext_unsupported_err("managed")); + } + + // empty handler kept for uniformity with other extensions + unimplemented!() +} + +#[allow(clippy::too_many_arguments)] +fn remove_vesting_accounts( + deps: DepsMut, + env: Env, + info: MessageInfo, + vesting_accounts: Vec, + clawback_account: String, +) -> Result { + let config = CONFIG.load(deps.storage)?; + if info.sender != config.owner { + return Err(ContractError::Unauthorized {}); + } + let vesting_token = get_vesting_token(&config)?; + + let mut response = Response::new(); + + let clawback_address = deps.api.addr_validate(&clawback_account)?; + + // For each vesting account, calculate the amount of tokens to claw back (unclaimed + still + // vesting), transfer the required amount to the owner, remove the vesting information + // from the storage, and decrease the total granted metric. + for vesting_account in vesting_accounts { + let account_address = deps.api.addr_validate(&vesting_account)?; + + let config = CONFIG.load(deps.storage)?; + let vesting_info = vesting_info(config.extensions.historical); + if let Some(account_info) = vesting_info.may_load(deps.storage, account_address.clone())? { + let mut total_granted_for_user = Uint128::zero(); + for sch in account_info.schedules { + if let Some(end_point) = sch.end_point { + total_granted_for_user = + total_granted_for_user.checked_add(end_point.amount)?; + } else { + total_granted_for_user = + total_granted_for_user.checked_add(sch.start_point.amount)?; + } + } + + let amount_to_claw_back = + total_granted_for_user.checked_sub(account_info.released_amount)?; + + let transfer_msg = vesting_token + .with_balance(amount_to_claw_back) + .into_msg(clawback_address.clone())?; + response = response.add_submessage(SubMsg::new(transfer_msg)); + + vesting_state(config.extensions.historical).update::<_, ContractError>( + deps.storage, + env.block.height, + |s| { + // Here we choose the "forget about everything" strategy. E.g., if we granted a user + // 300 tokens, and they claimed 150 tokens, the vesting state is + // { total_granted: 300, total_released: 150 }. + // If after that we remove the user's vesting account, we set the vesting state to + // { total_granted: 0, total_released: 0 }. + // + // If we decided to set it to { total_granted: 150, total_released: 150 }., the + // .total_released value of the vesting state would not be equal to the sum of the + // .released_amount values of all registered accounts. + let mut state = s.ok_or(ContractError::AmountIsNotAvailable {})?; + state.total_granted = + state.total_granted.checked_sub(total_granted_for_user)?; + state.total_released = state + .total_released + .checked_sub(account_info.released_amount)?; + Ok(state) + }, + )?; + vesting_info.remove(deps.storage, account_address, env.block.height)?; + } + } + + Ok(response.add_attributes(vec![ + attr("action", "remove_vesting_accounts"), + attr("sender", &info.sender), + ])) +} diff --git a/packages/vesting-base-lp/src/ext_with_managers.rs b/packages/vesting-base-lp/src/ext_with_managers.rs new file mode 100644 index 00000000..56573188 --- /dev/null +++ b/packages/vesting-base-lp/src/ext_with_managers.rs @@ -0,0 +1,105 @@ +use crate::error::{ext_unsupported_err, ContractError}; +use crate::msg::{ExecuteMsgWithManagers, QueryMsgWithManagers}; +use crate::state::{CONFIG, VESTING_MANAGERS}; +use cosmwasm_std::{ + attr, to_binary, Addr, Attribute, Binary, Deps, DepsMut, Env, MessageInfo, Order, Response, + StdError, StdResult, +}; + +/// Contains the with_managers extension check and routing of the message. +pub(crate) fn handle_execute_with_managers_msg( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsgWithManagers, +) -> Result { + let config = CONFIG.load(deps.storage)?; + if !config.extensions.with_managers { + return Err(ext_unsupported_err("with_managers").into()); + } + + match msg { + ExecuteMsgWithManagers::AddVestingManagers { managers } => { + add_vesting_managers(deps, env, info, managers) + } + ExecuteMsgWithManagers::RemoveVestingManagers { managers } => { + remove_vesting_managers(deps, env, info, managers) + } + } +} + +/// Contains the with_managers extension check and routing of the message. +pub(crate) fn handle_query_managers_msg( + deps: Deps, + _env: Env, + msg: QueryMsgWithManagers, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + if !config.extensions.with_managers { + return Err(ext_unsupported_err("with_managers")); + } + + match msg { + QueryMsgWithManagers::VestingManagers {} => to_binary(&query_vesting_managers(deps)?), + } +} + +/// Adds new vesting managers, which have a permission to add/remove vesting schedule +/// +/// * **managers** list of accounts to be added to the whitelist. +fn add_vesting_managers( + deps: DepsMut, + _env: Env, + info: MessageInfo, + managers: Vec, +) -> Result { + let config = CONFIG.load(deps.storage)?; + if info.sender != config.owner { + return Err(ContractError::Unauthorized {}); + } + let mut attrs: Vec = vec![]; + for m in managers { + let ma = deps.api.addr_validate(&m)?; + if !VESTING_MANAGERS.has(deps.storage, ma.clone()) { + VESTING_MANAGERS.save(deps.storage, ma, &())?; + attrs.push(attr("vesting_manager", &m)) + } + } + Ok(Response::new() + .add_attribute("action", "add_vesting_managers") + .add_attributes(attrs)) +} + +/// Removes new vesting managers from the whitelist +/// +/// * **managers** list of accounts to be removed from the whitelist. +fn remove_vesting_managers( + deps: DepsMut, + _env: Env, + info: MessageInfo, + managers: Vec, +) -> Result { + let config = CONFIG.load(deps.storage)?; + if info.sender != config.owner { + return Err(ContractError::Unauthorized {}); + } + let mut attrs: Vec = vec![]; + for m in managers { + let ma = deps.api.addr_validate(&m)?; + if VESTING_MANAGERS.has(deps.storage, ma.clone()) { + VESTING_MANAGERS.remove(deps.storage, ma); + attrs.push(attr("vesting_manager", &m)) + } + } + Ok(Response::new() + .add_attribute("action", "remove_vesting_managers") + .add_attributes(attrs)) +} + +/// Returns a list of vesting schedules using a [`VestingAccountsResponse`] object. +fn query_vesting_managers(deps: Deps) -> StdResult> { + let managers = VESTING_MANAGERS + .keys(deps.storage, None, None, Order::Ascending) + .collect::, StdError>>()?; + Ok(managers) +} diff --git a/packages/vesting-base-lp/src/handlers.rs b/packages/vesting-base-lp/src/handlers.rs new file mode 100644 index 00000000..dbb61e36 --- /dev/null +++ b/packages/vesting-base-lp/src/handlers.rs @@ -0,0 +1,819 @@ +use crate::error::ContractError; +use crate::ext_historical::{handle_execute_historical_msg, handle_query_historical_msg}; +use crate::ext_managed::{handle_execute_managed_msg, handle_query_managed_msg}; +use crate::ext_with_managers::{handle_execute_with_managers_msg, handle_query_managers_msg}; +use crate::msg::{CallbackMsg, Cw20HookMsg, ExecuteMsg, MigrateMsg, QueryMsg}; +use crate::state::{ + read_vesting_infos, vesting_info, vesting_state, MIGRATION_STATUS, XYK_TO_CL_MIGRATION_CONFIG, +}; +use crate::state::{CONFIG, OWNERSHIP_PROPOSAL, VESTING_MANAGERS}; +use crate::types::{ + Config, MigrationState, OrderBy, VestingAccount, VestingAccountResponse, + VestingAccountsResponse, VestingInfo, VestingSchedule, VestingSchedulePoint, VestingState, + XykToClMigrationConfig, +}; +use astroport::asset::{ + addr_opt_validate, native_asset, token_asset_info, AssetInfo, AssetInfoExt, PairInfo, +}; +use astroport::common::{claim_ownership, drop_ownership_proposal, propose_new_owner}; +use astroport::pair::{ + Cw20HookMsg as PairCw20HookMsg, ExecuteMsg as PairExecuteMsg, QueryMsg as PairQueryMsg, +}; + +use cosmwasm_std::{ + attr, from_binary, to_binary, Addr, Binary, Coin, CosmosMsg, Decimal, Deps, DepsMut, Env, + MessageInfo, Response, StdError, StdResult, Storage, SubMsg, Uint128, WasmMsg, +}; +use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg, Cw20ReceiveMsg}; +use cw_utils::must_pay; + +/// Exposes execute functions available in the contract. +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + let migration_state: MigrationState = MIGRATION_STATUS.load(deps.storage)?; + if migration_state != MigrationState::Completed { + match msg { + ExecuteMsg::MigrateLiquidityToPCLPool {} => {} + ExecuteMsg::Callback(..) => {} + _ => return Err(ContractError::MigrationIncomplete {}), + } + } + match msg { + ExecuteMsg::Claim { recipient, amount } => claim(deps, env, info, recipient, amount), + ExecuteMsg::Receive(msg) => receive_cw20(deps, env, info, msg), + ExecuteMsg::RegisterVestingAccounts { vesting_accounts } => { + let config = CONFIG.load(deps.storage)?; + let vesting_token = get_vesting_token(&config)?; + + match &vesting_token { + AssetInfo::NativeToken { denom } + if is_sender_whitelisted(deps.storage, &config, &info.sender) => + { + let amount = must_pay(&info, denom)?; + register_vesting_accounts(deps, vesting_accounts, amount, env.block.height) + } + _ => Err(ContractError::Unauthorized {}), + } + } + ExecuteMsg::ProposeNewOwner { owner, expires_in } => { + let config: Config = CONFIG.load(deps.storage)?; + + propose_new_owner( + deps, + info, + env, + 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) + })?; + + Ok(()) + }) + .map_err(Into::into) + } + ExecuteMsg::SetVestingToken { vesting_token } => { + set_vesting_token(deps, env, info, vesting_token) + } + ExecuteMsg::ManagedExtension { msg } => handle_execute_managed_msg(deps, env, info, msg), + ExecuteMsg::WithManagersExtension { msg } => { + handle_execute_with_managers_msg(deps, env, info, msg) + } + ExecuteMsg::HistoricalExtension { msg } => { + handle_execute_historical_msg(deps, env, info, msg) + } + ExecuteMsg::MigrateLiquidityToPCLPool {} => execute_migrate_liquidity(deps, info, env, None), + ExecuteMsg::Callback(msg) => _handle_callback(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.load(deps.storage)?; + let vesting_token = get_vesting_token(&config)?; + + // Permission check + if !is_sender_whitelisted( + deps.storage, + &config, + &deps.api.addr_validate(&cw20_msg.sender)?, + ) || token_asset_info(info.sender) != vesting_token + { + return Err(ContractError::Unauthorized {}); + } + + match from_binary(&cw20_msg.msg)? { + Cw20HookMsg::RegisterVestingAccounts { vesting_accounts } => { + register_vesting_accounts(deps, vesting_accounts, cw20_msg.amount, env.block.height) + } + } +} + +/// Create new vesting schedules. +/// +/// * **vesting_accounts** list of accounts and associated vesting schedules to create. +/// +/// * **cw20_amount** sets the amount that confirms the total amount of all accounts to register. +fn register_vesting_accounts( + deps: DepsMut, + vesting_accounts: Vec, + amount: Uint128, + height: u64, +) -> Result { + let response = Response::new(); + let config = CONFIG.load(deps.storage)?; + let mut to_deposit = Uint128::zero(); + + for mut vesting_account in vesting_accounts { + let mut released_amount = Uint128::zero(); + let account_address = deps.api.addr_validate(&vesting_account.address)?; + + assert_vesting_schedules(&account_address, &vesting_account.schedules)?; + + for sch in &vesting_account.schedules { + let amount = if let Some(end_point) = &sch.end_point { + end_point.amount + } else { + sch.start_point.amount + }; + to_deposit = to_deposit.checked_add(amount)?; + } + + let vesting_info = vesting_info(config.extensions.historical); + if let Some(mut old_info) = vesting_info.may_load(deps.storage, account_address.clone())? { + released_amount = old_info.released_amount; + vesting_account.schedules.append(&mut old_info.schedules); + } + + vesting_info.save( + deps.storage, + account_address, + &VestingInfo { + schedules: vesting_account.schedules, + released_amount, + }, + height, + )?; + } + + if to_deposit != amount { + return Err(ContractError::VestingScheduleAmountError {}); + } + + vesting_state(config.extensions.historical).update::<_, ContractError>( + deps.storage, + height, + |s| { + let mut state = s.unwrap_or_default(); + state.total_granted = state.total_granted.checked_add(to_deposit)?; + Ok(state) + }, + )?; + + Ok(response.add_attributes({ + vec![ + attr("action", "register_vesting_accounts"), + attr("deposited", to_deposit), + ] + })) +} + +/// Claims vested tokens and transfers them to the vesting recipient. +/// +/// * **recipient** vesting recipient for which to claim tokens. +/// +/// * **amount** amount of vested tokens to claim. +fn claim( + deps: DepsMut, + env: Env, + info: MessageInfo, + recipient: Option, + amount: Option, +) -> Result { + let config = CONFIG.load(deps.storage)?; + let vesting_token = get_vesting_token(&config)?; + let vesting_info = vesting_info(config.extensions.historical); + let mut sender_vesting_info = vesting_info.load(deps.storage, info.sender.clone())?; + + let available_amount = + compute_available_amount(env.block.time.seconds(), &sender_vesting_info)?; + + let claim_amount = if let Some(a) = amount { + if a > available_amount { + return Err(ContractError::AmountIsNotAvailable {}); + }; + a + } else { + available_amount + }; + + let mut response = Response::new(); + + if !claim_amount.is_zero() { + let transfer_msg = vesting_token + .with_balance(claim_amount) + .into_msg(recipient.unwrap_or_else(|| info.sender.to_string()))?; + response = response.add_submessage(SubMsg::new(transfer_msg)); + + sender_vesting_info.released_amount = sender_vesting_info + .released_amount + .checked_add(claim_amount)?; + vesting_info.save( + deps.storage, + info.sender.clone(), + &sender_vesting_info, + env.block.height, + )?; + vesting_state(config.extensions.historical).update::<_, ContractError>( + deps.storage, + env.block.height, + |s| { + let mut state = s.ok_or(ContractError::AmountIsNotAvailable {})?; + state.total_released = state.total_released.checked_add(claim_amount)?; + Ok(state) + }, + )?; + }; + + Ok(response.add_attributes(vec![ + attr("action", "claim"), + attr("address", &info.sender), + attr("available_amount", available_amount), + attr("claimed_amount", claim_amount), + ])) +} + +pub(crate) fn set_vesting_token( + deps: DepsMut, + _env: Env, + info: MessageInfo, + token: AssetInfo, +) -> Result { + let mut config = CONFIG.load(deps.storage)?; + if info.sender != config.owner && info.sender != config.token_info_manager { + return Err(ContractError::Unauthorized {}); + } + token.check(deps.api)?; + config.vesting_token = Some(token); + + CONFIG.save(deps.storage, &config)?; + Ok(Response::new()) +} + +pub(crate) fn get_vesting_token(config: &Config) -> Result { + config + .vesting_token + .clone() + .ok_or(ContractError::VestingTokenIsNotSet {}) +} + +fn execute_migrate_liquidity( + deps: DepsMut, + info: MessageInfo, + env: Env, + slippage_tolerance: Option, +) -> Result { + let migration_state: MigrationState = MIGRATION_STATUS.load(deps.storage)?; + if migration_state == MigrationState::Completed { + return Err(ContractError::MigrationComplete {}); + } + let migration_config: XykToClMigrationConfig = XYK_TO_CL_MIGRATION_CONFIG.load(deps.storage)?; + let address = info.sender; + let info = vesting_info(config.extensions.historical).load(deps.storage, address.clone())?; + let mut resp = Response::default(); + let user = VestingAccountResponse{ address, info }; + + // get pairs LP token addresses + let pair_info: PairInfo = deps + .querier + .query_wasm_smart(migration_config.xyk_pair.clone(), &PairQueryMsg::Pair {})?; + + // query max available amounts to be withdrawn from pool + let max_available_amount = { + let resp: BalanceResponse = deps.querier.query_wasm_smart( + pair_info.liquidity_token.clone(), + &Cw20QueryMsg::Balance { + address: env.contract.address.to_string(), + }, + )?; + resp.balance + }; + + if max_available_amount.is_zero() { + return Ok(resp); + } + + let user_amount = compute_share(&user.info)?; + + if let Some(slippage_tolerance) = slippage_tolerance { + if slippage_tolerance.gt(&migration_config.max_slippage) { + return Err(ContractError::MigrationSlippageToBig { + slippage_tolerance, + max_slippage_tolerance: migration_config.max_slippage, + }); + } + } + + let slippage_tolerance = slippage_tolerance.unwrap_or(migration_config.max_slippage); + + resp = resp.add_message( + CallbackMsg::MigrateLiquidityToClPair { + xyk_pair: migration_config.xyk_pair.clone(), + xyk_lp_token: pair_info.liquidity_token.clone(), + amount: user_amount, + slippage_tolerance, + cl_pair: migration_config.cl_pair.clone(), + ntrn_denom: migration_config.ntrn_denom.clone(), + paired_asset_denom: migration_config.paired_denom.clone(), + user, + } + .to_cosmos_msg(&env)?, + ); + + Ok(resp) +} + +fn _handle_callback( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: CallbackMsg, +) -> Result { + // Only the contract itself can call callbacks + if info.sender != env.contract.address { + return Err(ContractError::Unauthorized {}); + } + match msg { + CallbackMsg::MigrateLiquidityToClPair { + xyk_pair, + xyk_lp_token, + amount, + slippage_tolerance, + cl_pair, + ntrn_denom, + paired_asset_denom, + user, + } => migrate_liquidity_to_cl_pair_callback( + deps, + env, + xyk_pair, + xyk_lp_token, + amount, + slippage_tolerance, + cl_pair, + ntrn_denom, + paired_asset_denom, + user, + ), + CallbackMsg::ProvideLiquidityToClPairAfterWithdrawal { + ntrn_denom, + ntrn_init_balance, + paired_asset_denom, + paired_asset_init_balance, + cl_pair, + slippage_tolerance, + user, + } => provide_liquidity_to_cl_pair_after_withdrawal_callback( + deps, + env, + ntrn_denom, + ntrn_init_balance, + paired_asset_denom, + paired_asset_init_balance, + cl_pair, + slippage_tolerance, + user, + ), + CallbackMsg::PostMigrationVestingReschedule { user } => { + post_migration_vesting_reschedule_callback(deps, env, &user) + } + } +} + +#[allow(clippy::too_many_arguments)] +fn migrate_liquidity_to_cl_pair_callback( + deps: DepsMut, + env: Env, + xyk_pair: Addr, + xyk_lp_token: Addr, + amount: Uint128, + slippage_tolerance: Decimal, + cl_pair: Addr, + ntrn_denom: String, + paired_asset_denom: String, + user: VestingAccountResponse, +) -> Result { + let ntrn_init_balance = deps + .querier + .query_balance(env.contract.address.to_string(), ntrn_denom.clone())? + .amount; + let paired_asset_init_balance = deps + .querier + .query_balance(env.contract.address.to_string(), paired_asset_denom.clone())? + .amount; + + let mut msgs: Vec = vec![]; + + // push message to withdraw liquidity from the xyk pair + if !amount.is_zero() { + msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: xyk_lp_token.to_string(), + msg: to_binary(&Cw20ExecuteMsg::Send { + contract: xyk_pair.to_string(), + amount, + msg: to_binary(&PairCw20HookMsg::WithdrawLiquidity { assets: vec![] })?, + })?, + funds: vec![], + })) + } + // push the next migration step as a callback message + msgs.push( + CallbackMsg::ProvideLiquidityToClPairAfterWithdrawal { + ntrn_denom, + ntrn_init_balance, + paired_asset_denom, + paired_asset_init_balance, + cl_pair, + slippage_tolerance, + user, + } + .to_cosmos_msg(&env)?, + ); + + Ok(Response::default().add_messages(msgs)) +} + +#[allow(clippy::too_many_arguments)] +fn provide_liquidity_to_cl_pair_after_withdrawal_callback( + deps: DepsMut, + env: Env, + ntrn_denom: String, + ntrn_init_balance: Uint128, + paired_asset_denom: String, + paired_asset_init_balance: Uint128, + cl_pair_address: Addr, + slippage_tolerance: Decimal, + user: VestingAccountResponse, +) -> Result { + let ntrn_balance_after_withdrawal = deps + .querier + .query_balance(env.contract.address.to_string(), ntrn_denom.clone())? + .amount; + let paired_asset_balance_after_withdrawal = deps + .querier + .query_balance(env.contract.address.to_string(), paired_asset_denom.clone())? + .amount; + + // calc amount of assets that's been withdrawn + let withdrawn_ntrn_amount = ntrn_balance_after_withdrawal.checked_sub(ntrn_init_balance)?; + let withdrawn_paired_asset_amount = + paired_asset_balance_after_withdrawal.checked_sub(paired_asset_init_balance)?; + + let mut msgs: Vec = vec![]; + + if !withdrawn_ntrn_amount.is_zero() && !withdrawn_paired_asset_amount.is_zero() { + msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: cl_pair_address.to_string(), + msg: to_binary(&PairExecuteMsg::ProvideLiquidity { + assets: vec![ + native_asset(ntrn_denom.clone(), withdrawn_ntrn_amount), + native_asset(paired_asset_denom.clone(), withdrawn_paired_asset_amount), + ], + slippage_tolerance: Some(slippage_tolerance), + auto_stake: None, + receiver: None, + })?, + funds: vec![ + Coin::new(withdrawn_ntrn_amount.into(), ntrn_denom), + Coin::new(withdrawn_paired_asset_amount.into(), paired_asset_denom), + ], + })) + } + + msgs.push(CallbackMsg::PostMigrationVestingReschedule { user }.to_cosmos_msg(&env)?); + + Ok(Response::default().add_messages(msgs)) +} + +fn post_migration_vesting_reschedule_callback( + deps: DepsMut, + env: Env, + user: &VestingAccountResponse, +) -> Result { + let config = CONFIG.load(deps.storage)?; + let mut migration_config: XykToClMigrationConfig = + XYK_TO_CL_MIGRATION_CONFIG.load(deps.storage)?; + let balance_response: BalanceResponse = deps.querier.query_wasm_smart( + &migration_config.new_lp_token, + &Cw20QueryMsg::Balance { + address: env.contract.address.to_string(), + }, + )?; + let state = vesting_state(config.extensions.historical).load(deps.storage)?; + let current_balance = balance_response.balance; + + let balance_diff: Uint128 = if !current_balance.is_zero() { + current_balance.checked_sub(state.total_granted)? + } else { + Uint128::zero() + }; + + let schedule = user.info.schedules.last().unwrap(); + + let new_end_point; + if let Some(end_point) = &schedule.end_point { + new_end_point = Option::from(vesting_base_pcl::types::VestingSchedulePoint { + time: end_point.time, + amount: balance_diff, + }) + } else { + new_end_point = None + } + + let new_schedule = vesting_base_pcl::types::VestingSchedule { + start_point: vesting_base_pcl::types::VestingSchedulePoint { + time: schedule.start_point.time, + amount: Uint128::zero(), + }, + end_point: new_end_point, + }; + + + let vesting_info = vesting_info(config.extensions.historical); + + vesting_info.save( + deps.storage, + user.address.clone(), + &VestingInfo { + schedules: vec![], + released_amount: Uint128::zero(), + }, + env.block.height, + )?; + + vesting_state(config.extensions.historical).update::<_, ContractError>( + deps.storage, + env.block.height, + |s| { + let mut state = s.unwrap_or_default(); + state.total_granted = state.total_granted.checked_add(balance_diff)?; + Ok(state) + }, + )?; + + migration_config.last_processed_user = Some(user.address.clone()); + XYK_TO_CL_MIGRATION_CONFIG.save(deps.storage, &migration_config)?; + + msgs.push(&Cw20ExecuteMsg::Send { + contract: migration_config.pcl_vesting.to_string(), + amount: current_balance, + msg: to_binary(&vesting_base_pcl::msg::ExecuteMsg::MigrateXYKLiquidity { + user_address_raw: user.address.to_string(), + user_vesting_info: vesting_base_pcl::types::VestingInfo { + schedules: vec![new_schedule], + released_amount: Uint128::zero() + } + })? + }); + + Ok(Response::default().add_messages(msgs)) +} + +/// Exposes all the queries available in the contract. +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + let migration_state: MigrationState = MIGRATION_STATUS.load(deps.storage)?; + if migration_state != MigrationState::Completed { + return Err(ContractError::MigrationIncomplete {}.into()); + } + + match msg { + QueryMsg::Config {} => Ok(to_binary(&query_config(deps)?)?), + QueryMsg::VestingAccount { address } => { + Ok(to_binary(&query_vesting_account(deps, address)?)?) + } + QueryMsg::VestingAccounts { + start_after, + limit, + order_by, + } => Ok(to_binary(&query_vesting_accounts( + deps, + start_after, + limit, + order_by, + )?)?), + QueryMsg::AvailableAmount { address } => Ok(to_binary(&query_vesting_available_amount( + deps, env, address, + )?)?), + QueryMsg::VestingState {} => Ok(to_binary(&query_vesting_state(deps)?)?), + QueryMsg::Timestamp {} => Ok(to_binary(&query_timestamp(env)?)?), + QueryMsg::ManagedExtension { msg } => handle_query_managed_msg(deps, env, msg), + QueryMsg::WithManagersExtension { msg } => handle_query_managers_msg(deps, env, msg), + QueryMsg::HistoricalExtension { msg } => handle_query_historical_msg(deps, env, msg), + } +} + +/// Returns the vesting contract configuration using a [`Config`] object. +fn query_config(deps: Deps) -> StdResult { + let config = CONFIG.load(deps.storage)?; + Ok(config) +} + +/// Returns the accumulated vesting information for all addresses using a [`VestingState`] object. +fn query_vesting_state(deps: Deps) -> StdResult { + let config = CONFIG.load(deps.storage)?; + let state = vesting_state(config.extensions.historical).load(deps.storage)?; + + Ok(state) +} + +/// Return the current block timestamp (in seconds) +/// * **env** is an object of type [`Env`]. +fn query_timestamp(env: Env) -> StdResult { + Ok(env.block.time.seconds()) +} + +/// Returns the vesting data for a specific vesting recipient using a [`VestingAccountResponse`] object. +/// +/// * **address** vesting recipient for which to return vesting data. +fn query_vesting_account(deps: Deps, address: String) -> StdResult { + let address = deps.api.addr_validate(&address)?; + let config = CONFIG.load(deps.storage)?; + let info = vesting_info(config.extensions.historical).load(deps.storage, address.clone())?; + + Ok(VestingAccountResponse { address, info }) +} + +/// Returns a list of vesting schedules using a [`VestingAccountsResponse`] object. +/// +/// * **start_after** index from which to start reading vesting schedules. +/// +/// * **limit** amount of vesting schedules to return. +/// +/// * **order_by** whether results should be returned in an ascending or descending order. +fn query_vesting_accounts( + deps: Deps, + start_after: Option, + limit: Option, + order_by: Option, +) -> StdResult { + let start_after = addr_opt_validate(deps.api, &start_after)?; + + let vesting_infos = read_vesting_infos(deps, start_after, limit, order_by)?; + + let vesting_accounts: Vec<_> = vesting_infos + .into_iter() + .map(|(address, info)| VestingAccountResponse { address, info }) + .collect(); + + Ok(VestingAccountsResponse { vesting_accounts }) +} + +/// Returns the available amount of vested and yet to be claimed tokens for a specific vesting recipient. +/// +/// * **address** vesting recipient for which to return the available amount of tokens to claim. +fn query_vesting_available_amount(deps: Deps, env: Env, address: String) -> StdResult { + let address = deps.api.addr_validate(&address)?; + + let config = CONFIG.load(deps.storage)?; + let info = vesting_info(config.extensions.historical).load(deps.storage, address)?; + let available_amount = compute_available_amount(env.block.time.seconds(), &info)?; + Ok(available_amount) +} + +/// Manages contract migration. +pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Result { + let mut config = CONFIG.load(deps.storage)?; + XYK_TO_CL_MIGRATION_CONFIG.save( + deps.storage, + &XykToClMigrationConfig { + max_slippage: msg.max_slippage, + ntrn_denom: msg.ntrn_denom, + xyk_pair: deps.api.addr_validate(msg.xyk_pair.as_str())?, + paired_denom: msg.paired_denom, + cl_pair: deps.api.addr_validate(msg.cl_pair.as_str())?, + new_lp_token: deps.api.addr_validate(msg.new_lp_token.as_str())?, + pcl_vesting: deps.api.addr_validate(msg.pcl_vesting.as_str())? + }, + )?; + config.vesting_token = Some(AssetInfo::Token { + contract_addr: deps.api.addr_validate(msg.new_lp_token.as_str())?, + }); + + CONFIG.save(deps.storage, &config)?; + + vesting_state(config.extensions.historical).update::<_, ContractError>( + deps.storage, + env.block.height, + |s| { + let mut state = s.unwrap_or_default(); + state.total_granted = Uint128::zero(); + Ok(state) + }, + )?; + + MIGRATION_STATUS.save(deps.storage, &MigrationState::Started)?; + + Ok(Response::default()) +} + +fn is_sender_whitelisted(store: &mut dyn Storage, config: &Config, sender: &Addr) -> bool { + if *sender == config.owner { + return true; + } + if VESTING_MANAGERS.has(store, sender.clone()) { + return true; + } + false +} + +/// Asserts the validity of a list of vesting schedules. +/// +/// * **addr** receiver of the vested tokens. +/// +/// * **vesting_schedules** vesting schedules to validate. +fn assert_vesting_schedules( + addr: &Addr, + vesting_schedules: &[VestingSchedule], +) -> Result<(), ContractError> { + for sch in vesting_schedules { + if let Some(end_point) = &sch.end_point { + if !(sch.start_point.time < end_point.time && sch.start_point.amount < end_point.amount) + { + return Err(ContractError::VestingScheduleError(addr.to_string())); + } + } + } + + Ok(()) +} + +/// Computes the amount of vested and yet unclaimed tokens for a specific vesting recipient. +/// Returns the computed amount if the operation is successful. +/// +/// * **current_time** timestamp from which to start querying for vesting schedules. +/// Schedules that started later than current_time will be omitted. +/// +/// * **vesting_info** vesting schedules for which to compute the amount of tokens +/// that are vested and can be claimed by the recipient. +fn compute_available_amount(current_time: u64, vesting_info: &VestingInfo) -> StdResult { + let mut available_amount: Uint128 = Uint128::zero(); + for sch in &vesting_info.schedules { + if sch.start_point.time > current_time { + continue; + } + + available_amount = available_amount.checked_add(sch.start_point.amount)?; + + if let Some(end_point) = &sch.end_point { + let passed_time = current_time.min(end_point.time) - sch.start_point.time; + let time_period = end_point.time - sch.start_point.time; + if passed_time != 0 && time_period != 0 { + let release_amount = Uint128::from(passed_time).multiply_ratio( + end_point.amount.checked_sub(sch.start_point.amount)?, + time_period, + ); + available_amount = available_amount.checked_add(release_amount)?; + } + } + } + + available_amount + .checked_sub(vesting_info.released_amount) + .map_err(StdError::from) +} + +fn compute_share(vesting_info: &VestingInfo) -> StdResult { + let mut available_amount: Uint128 = Uint128::zero(); + for sch in &vesting_info.schedules { + if let Some(end_point) = &sch.end_point { + available_amount = available_amount.checked_add(end_point.amount)? + } + } + + Ok(available_amount.checked_sub(vesting_info.released_amount)?) +} diff --git a/packages/vesting-base-lp/src/lib.rs b/packages/vesting-base-lp/src/lib.rs new file mode 100644 index 00000000..594d5e36 --- /dev/null +++ b/packages/vesting-base-lp/src/lib.rs @@ -0,0 +1,10 @@ +pub mod builder; +pub mod error; +pub mod handlers; +pub mod msg; +pub mod state; +pub mod types; + +pub(crate) mod ext_historical; +pub(crate) mod ext_managed; +pub(crate) mod ext_with_managers; diff --git a/packages/vesting-base-lp/src/msg.rs b/packages/vesting-base-lp/src/msg.rs new file mode 100644 index 00000000..7c47cda2 --- /dev/null +++ b/packages/vesting-base-lp/src/msg.rs @@ -0,0 +1,210 @@ +use crate::types::{ + Config, OrderBy, VestingAccount, VestingAccountResponse, VestingAccountsResponse, VestingState, +}; +use astroport::asset::AssetInfo; +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{to_binary, Addr, Binary, CosmosMsg, Decimal, Env, StdResult, Uint128, WasmMsg}; +use cw20::Cw20ReceiveMsg; + +/// This structure describes the execute messages available in a vesting contract. +#[cw_serde] +pub enum ExecuteMsg { + /// Claim claims vested tokens and sends them to a recipient + Claim { + /// The address that receives the vested tokens + recipient: Option, + /// The amount of tokens to claim + amount: Option, + }, + /// Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template + Receive(Cw20ReceiveMsg), + /// RegisterVestingAccounts registers vesting targets/accounts + RegisterVestingAccounts { + vesting_accounts: Vec, + }, + /// Creates a request to change contract ownership + /// ## Executor + /// Only the current owner can execute this + ProposeNewOwner { + /// The newly proposed owner + owner: String, + /// The validity period of the offer to change the owner + expires_in: u64, + }, + /// Removes a request to change contract ownership + /// ## Executor + /// Only the current owner can execute this + DropOwnershipProposal {}, + /// Claims contract ownership + /// ## Executor + /// Only the newly proposed owner can execute this + ClaimOwnership {}, + /// Sets vesting token + /// ## Executor + /// Only the current owner or token info manager can execute this + SetVestingToken { vesting_token: AssetInfo }, + /// Contains messages associated with the managed extension for vesting contracts. + ManagedExtension { msg: ExecuteMsgManaged }, + /// Contains messages associated with the with_managers extension for vesting contracts. + WithManagersExtension { msg: ExecuteMsgWithManagers }, + /// Contains messages associated with the historical extension for vesting contracts. + HistoricalExtension { msg: ExecuteMsgHistorical }, + /// TODO: detailed decription + #[serde(rename = "migrate_liquidity_to_pcl_pool")] + MigrateLiquidityToPCLPool {}, + /// Callbacks; only callable by the contract itself. + Callback(CallbackMsg), +} + +/// This structure describes the execute messages available in a managed vesting contract. +#[cw_serde] +pub enum ExecuteMsgManaged { + /// Removes vesting targets/accounts. + /// ## Executor + /// Only the current owner can execute this + RemoveVestingAccounts { + vesting_accounts: Vec, + /// Specifies the account that will receive the funds taken from the vesting accounts. + clawback_account: String, + }, +} + +/// This structure describes the execute messages available in a with_managers vesting contract. +#[cw_serde] +pub enum ExecuteMsgWithManagers { + /// Adds vesting managers + /// ## Executor + /// Only the current owner can execute this + AddVestingManagers { managers: Vec }, + /// Removes vesting managers + /// ## Executor + /// Only the current owner can execute this + RemoveVestingManagers { managers: Vec }, +} + +/// This structure describes the execute messages available in a historical vesting contract. +#[cw_serde] +pub enum ExecuteMsgHistorical {} + +/// This structure describes the query messages available in a vesting contract. +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + /// Returns the configuration for the contract using a [`ConfigResponse`] object. + #[returns(Config)] + Config {}, + /// Returns information about an address vesting tokens using a [`VestingAccountResponse`] object. + #[returns(VestingAccountResponse)] + VestingAccount { address: String }, + /// Returns a list of addresses that are vesting tokens using a [`VestingAccountsResponse`] object. + #[returns(VestingAccountsResponse)] + VestingAccounts { + start_after: Option, + limit: Option, + order_by: Option, + }, + /// Returns the total unvested amount of tokens for a specific address. + #[returns(Uint128)] + AvailableAmount { address: String }, + /// Timestamp returns the current timestamp + #[returns(u64)] + Timestamp {}, + /// VestingState returns the current vesting state. + #[returns(VestingState)] + VestingState {}, + /// Contains messages associated with the managed extension for vesting contracts. + #[returns(Binary)] + ManagedExtension { msg: QueryMsgManaged }, + /// Contains messages associated with the with_managers extension for vesting contracts. + #[returns(QueryMsgWithManagers)] + WithManagersExtension { msg: QueryMsgWithManagers }, + /// Contains messages associated with the historical extension for vesting contracts. + #[returns(QueryMsgHistorical)] + HistoricalExtension { msg: QueryMsgHistorical }, +} + +/// This structure describes the query messages available in a managed vesting contract. +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsgManaged {} + +/// This structure describes the query messages available in a with_managers vesting contract. +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsgWithManagers { + /// Returns list of vesting managers + /// (the persons who are able to add/remove vesting schedules) + #[returns(Vec)] + VestingManagers {}, +} + +/// This structure describes the query messages available in a historical vesting contract. +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsgHistorical { + /// Returns the total unclaimed amount of tokens for a specific address at certain height. + #[returns(Uint128)] + UnclaimedAmountAtHeight { address: String, height: u64 }, + /// Returns the total unclaimed amount of tokens for all the users at certain height. + #[returns(Uint128)] + UnclaimedTotalAmountAtHeight { height: u64 }, +} + +/// This structure describes a migration message. +/// We currently take no arguments for migrations. +#[cw_serde] +#[serde(rename_all = "snake_case")] +pub struct MigrateMsg { + pub max_slippage: Decimal, + pub ntrn_denom: String, + pub paired_denom: String, + pub xyk_pair: String, + pub cl_pair: String, + pub new_lp_token: String, + pub pcl_vesting: String, +} +/// This structure describes a CW20 hook message. +#[cw_serde] +pub enum Cw20HookMsg { + /// RegisterVestingAccounts registers vesting targets/accounts + RegisterVestingAccounts { + vesting_accounts: Vec, + }, +} +#[cw_serde] +pub enum CallbackMsg { + MigrateLiquidityToClPair { + xyk_pair: Addr, + xyk_lp_token: Addr, + amount: Uint128, + slippage_tolerance: Decimal, + cl_pair: Addr, + ntrn_denom: String, + paired_asset_denom: String, + user: VestingAccountResponse, + }, + ProvideLiquidityToClPairAfterWithdrawal { + ntrn_denom: String, + ntrn_init_balance: Uint128, + paired_asset_denom: String, + paired_asset_init_balance: Uint128, + cl_pair: Addr, + slippage_tolerance: Decimal, + user: VestingAccountResponse, + }, + PostMigrationVestingReschedule { + user: VestingAccountResponse, + }, +} + +// Modified from +// https://github.com/CosmWasm/cosmwasm-plus/blob/v0.2.3/packages/cw20/src/receiver.rs#L15 +impl CallbackMsg { + pub fn to_cosmos_msg(self, env: &Env) -> StdResult { + Ok(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: env.contract.address.to_string(), + msg: to_binary(&ExecuteMsg::Callback(self))?, + funds: vec![], + })) + } +} diff --git a/packages/vesting-base-lp/src/state.rs b/packages/vesting-base-lp/src/state.rs new file mode 100644 index 00000000..28017fc0 --- /dev/null +++ b/packages/vesting-base-lp/src/state.rs @@ -0,0 +1,166 @@ +use crate::types::{ + Config, MigrationState, OrderBy, VestingInfo, VestingState, XykToClMigrationConfig, +}; +use astroport::common::OwnershipProposal; +use cosmwasm_std::{Addr, Deps, StdResult}; +use cw_storage_plus::{Bound, Item, Map, SnapshotItem, SnapshotMap, Strategy}; + +pub(crate) const CONFIG: Item = Item::new("config"); +/// Migration status +pub(crate) const MIGRATION_STATUS: Item = Item::new("migration_status"); +pub(crate) const OWNERSHIP_PROPOSAL: Item = Item::new("ownership_proposal"); +pub(crate) const VESTING_MANAGERS: Map = Map::new("vesting_managers"); +pub(crate) const VESTING_STATE: SnapshotItem = SnapshotItem::new( + "vesting_state", + "vesting_state__checkpoints", + "vesting_state__changelog", + Strategy::Never, +); +pub(crate) const VESTING_INFO: SnapshotMap = SnapshotMap::new( + "vesting_info", + "vesting_info__checkpoints", + "vesting_info__changelog", + Strategy::Never, +); +pub(crate) const VESTING_STATE_HISTORICAL: SnapshotItem = SnapshotItem::new( + "vesting_state", + "vesting_state__checkpoints", + "vesting_state__changelog", + Strategy::EveryBlock, +); +pub(crate) const VESTING_INFO_HISTORICAL: SnapshotMap = SnapshotMap::new( + "vesting_info", + "vesting_info__checkpoints", + "vesting_info__changelog", + Strategy::EveryBlock, +); + +pub(crate) fn vesting_state(historical: bool) -> SnapshotItem<'static, VestingState> { + if historical { + return VESTING_STATE_HISTORICAL; + } + VESTING_STATE +} + +pub(crate) fn vesting_info(historical: bool) -> SnapshotMap<'static, Addr, VestingInfo> { + if historical { + return VESTING_INFO_HISTORICAL; + } + VESTING_INFO +} + +const MAX_LIMIT: u32 = 30; +const DEFAULT_LIMIT: u32 = 10; + +/// Returns an empty vector if it does not find data, otherwise returns a vector that +/// contains objects of type [`VESTING_INFO`]. +/// ## Params +/// +/// * **start_after** index from which to start reading vesting schedules. +/// +/// * **limit** amount of vesting schedules to read. +/// +/// * **order_by** whether results should be returned in an ascending or descending order. +pub(crate) fn read_vesting_infos( + deps: Deps, + start_after: Option, + limit: Option, + order_by: Option, +) -> StdResult> { + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + let start_after = start_after.map(Bound::exclusive); + + let (start, end) = match &order_by { + Some(OrderBy::Asc) => (start_after, None), + _ => (None, start_after), + }; + + let info: Vec<(Addr, VestingInfo)> = VESTING_INFO + .range( + deps.storage, + start, + end, + order_by.unwrap_or(OrderBy::Desc).into(), + ) + .take(limit) + .filter_map(|v| v.ok()) + .collect(); + + Ok(info) +} + +#[cfg(test)] +mod testing { + use super::*; + + #[test] + fn read_vesting_infos_as_expected() { + use cosmwasm_std::{testing::mock_dependencies, Uint128}; + let mut deps = mock_dependencies(); + let historical = false; + + let vi_mock = VestingInfo { + released_amount: Uint128::zero(), + schedules: vec![], + }; + + for i in 1..5 { + let key = Addr::unchecked(format! {"address{}", i}); + + vesting_info(historical) + .save(&mut deps.storage, key, &vi_mock, 1) + .unwrap(); + } + + let res = read_vesting_infos( + deps.as_ref(), + Some(Addr::unchecked("address2")), + None, + Some(OrderBy::Asc), + ) + .unwrap(); + assert_eq!( + res, + vec![ + (Addr::unchecked("address3"), vi_mock.clone()), + (Addr::unchecked("address4"), vi_mock.clone()), + ] + ); + + let res = read_vesting_infos( + deps.as_ref(), + Some(Addr::unchecked("address2")), + Some(1), + Some(OrderBy::Asc), + ) + .unwrap(); + assert_eq!(res, vec![(Addr::unchecked("address3"), vi_mock.clone())]); + + let res = read_vesting_infos( + deps.as_ref(), + Some(Addr::unchecked("address3")), + None, + Some(OrderBy::Desc), + ) + .unwrap(); + assert_eq!( + res, + vec![ + (Addr::unchecked("address2"), vi_mock.clone()), + (Addr::unchecked("address1"), vi_mock.clone()), + ] + ); + + let res = read_vesting_infos( + deps.as_ref(), + Some(Addr::unchecked("address3")), + Some(1), + Some(OrderBy::Desc), + ) + .unwrap(); + assert_eq!(res, vec![(Addr::unchecked("address2"), vi_mock.clone())]); + } +} + +pub const XYK_TO_CL_MIGRATION_CONFIG: Item = + Item::new("xyk_to_cl_migration_config"); diff --git a/packages/vesting-base-lp/src/types.rs b/packages/vesting-base-lp/src/types.rs new file mode 100644 index 00000000..cb89ec3e --- /dev/null +++ b/packages/vesting-base-lp/src/types.rs @@ -0,0 +1,130 @@ +use astroport::asset::AssetInfo; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Decimal, Order, Uint128}; + +/// This structure stores the main parameters for the generator vesting contract. +#[cw_serde] +pub struct Config { + /// Address that's allowed to change contract parameters + pub owner: Addr, + /// [`AssetInfo`] of the vested token + pub vesting_token: Option, + /// Address that's allowed to change vesting token + pub token_info_manager: Addr, + /// Contains extensions information of the contract + pub extensions: Extensions, +} + +/// Contains extensions information for the contract. +#[cw_serde] +pub struct Extensions { + /// Whether the historical extension is enabled for the contract. + pub historical: bool, + /// Whether the managed extension is enabled for the contract. + pub managed: bool, + /// Whether the with_managers extension is enabled for the contract. + pub with_managers: bool, +} + +/// This structure stores the accumulated vesting information for all addresses. +#[cw_serde] +#[derive(Default)] +pub struct VestingState { + /// The total amount of tokens granted to the users + pub total_granted: Uint128, + /// The total amount of tokens already claimed + pub total_released: Uint128, +} + +/// This structure stores vesting information for a specific address that is getting tokens. +#[cw_serde] +pub struct VestingAccount { + /// The address that is getting tokens + pub address: String, + /// The vesting schedules targeted at the `address` + pub schedules: Vec, +} + +/// This structure stores parameters for a batch of vesting schedules. +#[cw_serde] +pub struct VestingInfo { + /// The vesting schedules + pub schedules: Vec, + /// The total amount of vested tokens already claimed + pub released_amount: Uint128, +} + +/// This structure stores parameters for a specific vesting schedule +#[cw_serde] +pub struct VestingSchedule { + /// The start date for the vesting schedule + pub start_point: VestingSchedulePoint, + /// The end point for the vesting schedule + pub end_point: Option, +} + +/// This structure stores the parameters used to create a vesting schedule. +#[cw_serde] +pub struct VestingSchedulePoint { + /// The start time for the vesting schedule + pub time: u64, + /// The amount of tokens being vested + pub amount: Uint128, +} + +/// This structure describes a custom struct used to return vesting data about a specific vesting target. +#[cw_serde] +pub struct VestingAccountResponse { + /// The address that's vesting tokens + pub address: Addr, + /// Vesting information + pub info: VestingInfo, +} + +/// This structure describes a custom struct used to return vesting data for multiple vesting targets. +#[cw_serde] +pub struct VestingAccountsResponse { + /// A list of accounts that are vesting tokens + pub vesting_accounts: Vec, +} + +/// Config for xyk->CL liquidity migration. +#[cw_serde] +pub struct XykToClMigrationConfig { + /// The maximum allowed slippage tolerance for xyk to CL liquidity migration calls. + pub max_slippage: Decimal, + pub ntrn_denom: String, + pub xyk_pair: Addr, + pub paired_denom: String, + pub cl_pair: Addr, + pub new_lp_token: Addr, + pub pcl_vesting: Addr, +} + +#[cw_serde] +pub enum MigrationState { + /// Migration is started + Started, + + Completed, +} + +/// This enum describes the types of sorting that can be applied to some piece of data +#[cw_serde] +pub enum OrderBy { + Asc, + Desc, +} + +// We suppress this clippy warning because Order in cosmwasm doesn't implement Debug and +// PartialEq for usage in QueryMsg. We need to use our own OrderBy and convert the result to cosmwasm's Order +#[allow(clippy::from_over_into)] +impl Into for OrderBy { + fn into(self) -> Order { + if self == OrderBy::Asc { + Order::Ascending + } else { + Order::Descending + } + } +} diff --git a/packages/vesting-base-pcl/Cargo.toml b/packages/vesting-base-pcl/Cargo.toml new file mode 100644 index 00000000..15a0f6ce --- /dev/null +++ b/packages/vesting-base-pcl/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "vesting-base-pcl" +version = "1.1.0" +authors = ["Astroport"] +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all init/handle/query exports +library = [] + +[dependencies] +cw20 = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +astroport = { workspace = true } +thiserror = { workspace = true } +# we keep it at 0.15 instead of latest version just for vesting investors contract +cw-utils = "0.15" +cosmwasm-schema = { workspace = true } diff --git a/packages/vesting-base-pcl/NOTICE b/packages/vesting-base-pcl/NOTICE new file mode 100644 index 00000000..84b1c210 --- /dev/null +++ b/packages/vesting-base-pcl/NOTICE @@ -0,0 +1,14 @@ +CW20-Base: A reference implementation for fungible token on CosmWasm +Copyright (C) 2020 Confio OÜ + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/packages/vesting-base-pcl/README.md b/packages/vesting-base-pcl/README.md new file mode 100644 index 00000000..57accc63 --- /dev/null +++ b/packages/vesting-base-pcl/README.md @@ -0,0 +1,251 @@ +# Neutron Vesting Base + +This library contains basis for configuration and initialisation of vesting contracts. It also contains data models and handlers for interaction with vesting contracts. + +## Usage + +1. To use the library for initialisation of a simple vesting contract just build a default vesting base in its instantiate message: +```rust +use vesting_base::builder::VestingBaseBuilder; + +pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + ... + VestingBaseBuilder::default().build(deps, msg.owner, msg.vesting_token)?; + ... +``` + +Read about more advanced building in the [Extensions](#extensions) section. + +2. Simply pass the execute and query requests to the vesting base's execute and query handlers: +```rust +use vesting_base::handlers::{execute as base_execute, query as base_query}; + +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + base_execute(deps, env, info, msg) +} + +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + base_query(deps, env, msg) +} +``` + +### Messages + +The default version exposes the following messages: + +#### ExecuteMsg + +```rust +/// This structure describes the execute messages available in a vesting contract. +#[cw_serde] +pub enum ExecuteMsg { + /// Claim claims vested tokens and sends them to a recipient + Claim { + /// The address that receives the vested tokens + recipient: Option, + /// The amount of tokens to claim + amount: Option, + }, + /// Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template + Receive(Cw20ReceiveMsg), + /// RegisterVestingAccounts registers vesting targets/accounts + RegisterVestingAccounts { + vesting_accounts: Vec, + }, + /// Creates a request to change contract ownership + /// ## Executor + /// Only the current owner can execute this + ProposeNewOwner { + /// The newly proposed owner + owner: String, + /// The validity period of the offer to change the owner + expires_in: u64, + }, + /// Removes a request to change contract ownership + /// ## Executor + /// Only the current owner can execute this + DropOwnershipProposal {}, + /// Claims contract ownership + /// ## Executor + /// Only the newly proposed owner can execute this + ClaimOwnership {}, + /// Sets vesting token + /// ## Executor + /// Only the current owner or token info manager can execute this + SetVestingToken { vesting_token: AssetInfo }, + /// Contains messages associated with the managed extension for vesting contracts. + ManagedExtension { msg: ExecuteMsgManaged }, + /// Contains messages associated with the with_managers extension for vesting contracts. + WithManagersExtension { msg: ExecuteMsgWithManagers }, + /// Contains messages associated with the historical extension for vesting contracts. + HistoricalExtension { msg: ExecuteMsgHistorical }, +} +``` + +The `ManagedExtension`, `WithManagersExtension`, and `HistoricalExtension` messages are extensiom messages. Read about them in the [Extensions](#extensions) section. + +#### QueryMsg + +```rust +/// This structure describes the query messages available in a vesting contract. +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + /// Returns the configuration for the contract using a [`ConfigResponse`] object. + #[returns(ConfigResponse)] + Config {}, + /// Returns information about an address vesting tokens using a [`VestingAccountResponse`] object. + #[returns(VestingAccountResponse)] + VestingAccount { address: String }, + /// Returns a list of addresses that are vesting tokens using a [`VestingAccountsResponse`] object. + #[returns(VestingAccountsResponse)] + VestingAccounts { + start_after: Option, + limit: Option, + order_by: Option, + }, + /// Returns the total unvested amount of tokens for a specific address. + #[returns(Uint128)] + AvailableAmount { address: String }, + /// Timestamp returns the current timestamp + #[returns(u64)] + Timestamp {}, + /// VestingState returns the current vesting state. + #[returns(VestingState)] + VestingState {}, + /// Contains messages associated with the managed extension for vesting contracts. + #[returns(QueryMsgManaged)] + ManagedExtension { msg: QueryMsgManaged }, + /// Contains messages associated with the with_managers extension for vesting contracts. + #[returns(QueryMsgWithManagers)] + WithManagersExtension { msg: QueryMsgWithManagers }, + /// Contains messages associated with the historical extension for vesting contracts. + #[returns(QueryMsgHistorical)] + HistoricalExtension { msg: QueryMsgHistorical }, +} +``` + +The `ManagedExtension`, `WithManagersExtension`, and `HistoricalExtension` messages are extensiom messages. Read about them in the [Extensions](#extensions) section. + +## Extensions + +Created contracts can be extended with a number of features. + +### Managed + +The `managed` extension allows the owner of the vesting contract to remove registered vesting accounts and redeem the corresponding funds. + +```rust +/// This structure describes the execute messages available in a managed vesting contract. +#[cw_serde] +pub enum ExecuteMsgManaged { + /// Removes vesting targets/accounts. + /// ## Executor + /// Only the current owner can execute this + RemoveVestingAccounts { + vesting_accounts: Vec, + /// Specifies the account that will receive the funds taken from the vesting accounts. + clawback_account: String, + }, +} + +/// This structure describes the query messages available in a managed vesting contract. +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsgManaged {} +``` + +### WithManagers + +The `with_managers` extension allows the owner of the vesting contract to add/remove vesting managers — addresses that just like the owner are capable of registering new vesting accounts. + +```rust +/// This structure describes the execute messages available in a with_managers vesting contract. +#[cw_serde] +pub enum ExecuteMsgWithManagers { + /// Adds vesting managers + /// ## Executor + /// Only the current owner can execute this + AddVestingManagers { managers: Vec }, + /// Removes vesting managers + /// ## Executor + /// Only the current owner can execute this + RemoveVestingManagers { managers: Vec }, +} + +/// This structure describes the query messages available in a with_managers vesting contract. +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsgWithManagers { + /// Returns list of vesting managers + /// (the persons who are able to add/remove vesting schedules) + #[returns(Vec)] + VestingManagers {}, +} +``` + +### Historical + +The `historical` allows to query vesting accounts and total vesting state based on a given height. + +```rust +/// This structure describes the execute messages available in a historical vesting contract. +#[cw_serde] +pub enum ExecuteMsgHistorical {} + +/// This structure describes the query messages available in a historical vesting contract. +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsgHistorical { + /// Returns the total unclaimed amount of tokens for a specific address at certain height. + #[returns(Uint128)] + UnclaimedAmountAtHeight { address: String, height: u64 }, + /// Returns the total unclaimed amount of tokens for all the users at certain height. + #[returns(Uint128)] + UnclaimedTotalAmountAtHeight { height: u64 }, +} +``` + +### Extensions usage + +The following example adds all three extensions to the contract, but it's allowed to combine them in any way. +```rust +use vesting_base::builder::VestingBaseBuilder; +use astroport::asset::AssetInfo; +use cosmwasm_schema::cw_serde; + +/// This structure describes the parameters used for creating a contract. +#[cw_serde] +pub struct InstantiateMsg { + /// Address allowed to change contract parameters + pub owner: String, + /// [`AssetInfo`] of the token that's being vested + pub vesting_token: AssetInfo, + /// Initial list of whitelisted vesting managers + pub vesting_managers: Vec, +} + +pub fn instantiate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> StdResult { + ... + VestingBaseBuilder::default() + .historical() + .managed() + .with_managers(msg.vesting_managers) + .build(deps, msg.owner, msg.vesting_token)?; + ... +``` diff --git a/packages/vesting-base-pcl/src/builder.rs b/packages/vesting-base-pcl/src/builder.rs new file mode 100644 index 00000000..85957245 --- /dev/null +++ b/packages/vesting-base-pcl/src/builder.rs @@ -0,0 +1,60 @@ +use crate::state::{CONFIG, VESTING_MANAGERS}; +use crate::types::{Config, Extensions}; +use cosmwasm_std::{DepsMut, StdResult}; + +/// A builder for vesting contracts with different extensions. +#[derive(Default)] +pub struct VestingBaseBuilder { + vesting_managers: Vec, + historical: bool, + managed: bool, + with_managers: bool, +} + +impl VestingBaseBuilder { + /// Appends the `managed` extension to the created vesting contract. + pub fn managed(&mut self) -> &mut VestingBaseBuilder { + self.managed = true; + self + } + + /// Appends the `with_managers` extension to the created vesting contract. + pub fn with_managers(&mut self, managers: Vec) -> &mut VestingBaseBuilder { + self.vesting_managers.extend(managers); + self.with_managers = true; + self + } + + /// Appends the `historical` extension to the created vesting contract. + pub fn historical(&mut self) -> &mut VestingBaseBuilder { + self.historical = true; + self + } + + /// Validates the inputs and initialises the created contract state. + pub fn build(&self, deps: DepsMut, owner: String, token_info_manager: String) -> StdResult<()> { + let owner = deps.api.addr_validate(&owner)?; + CONFIG.save( + deps.storage, + &Config { + owner, + vesting_token: None, + token_info_manager: deps.api.addr_validate(&token_info_manager)?, + extensions: Extensions { + historical: self.historical, + managed: self.managed, + with_managers: self.with_managers, + }, + }, + )?; + + if self.with_managers { + for m in self.vesting_managers.iter() { + let ma = deps.api.addr_validate(m)?; + VESTING_MANAGERS.save(deps.storage, ma, &())?; + } + }; + + Ok(()) + } +} diff --git a/packages/vesting-base-pcl/src/error.rs b/packages/vesting-base-pcl/src/error.rs new file mode 100644 index 00000000..a1859ac0 --- /dev/null +++ b/packages/vesting-base-pcl/src/error.rs @@ -0,0 +1,47 @@ +use cosmwasm_std::{OverflowError, StdError}; +use cw_utils::PaymentError; +use thiserror::Error; + +/// This enum describes generator vesting contract errors +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + PaymentError(#[from] PaymentError), + + #[error("Unauthorized")] + Unauthorized {}, + + #[error("Amount is not available!")] + AmountIsNotAvailable {}, + + #[error("Vesting schedule error on addr: {0}. Should satisfy: (start < end and at_start < total) or (start = end and at_start = total)")] + VestingScheduleError(String), + + #[error("Vesting schedule amount error. The total amount should be equal to the CW20 receive amount.")] + VestingScheduleAmountError {}, + + #[error("Contract can't be migrated!")] + MigrationError {}, + + #[error("Vesting token is not set!")] + VestingTokenIsNotSet {}, + + #[error("Vesting token is already set!")] + VestingTokenAlreadySet {}, +} + +impl From for ContractError { + fn from(o: OverflowError) -> Self { + StdError::from(o).into() + } +} + +pub fn ext_unsupported_err(extension: impl Into + std::fmt::Display) -> StdError { + StdError::generic_err(format!( + "Extension is not enabled for the contract: {}.", + extension + )) +} diff --git a/packages/vesting-base-pcl/src/ext_historical.rs b/packages/vesting-base-pcl/src/ext_historical.rs new file mode 100644 index 00000000..d494adf6 --- /dev/null +++ b/packages/vesting-base-pcl/src/ext_historical.rs @@ -0,0 +1,101 @@ +use crate::error::{ext_unsupported_err, ContractError}; +use crate::msg::{ExecuteMsgHistorical, QueryMsgHistorical}; +use crate::state::{vesting_info, vesting_state, CONFIG}; +use crate::types::VestingInfo; +use cosmwasm_std::{ + to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Uint128, +}; + +/// Contains the historical extension check and routing of the message. +pub(crate) fn handle_execute_historical_msg( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: ExecuteMsgHistorical, +) -> Result { + let config = CONFIG.load(deps.storage)?; + if !config.extensions.historical { + return Err(ext_unsupported_err("historical").into()); + } + + // empty handler kept for uniformity with other extensions + unimplemented!() +} + +/// Contains the historical extension check and routing of the message. +pub(crate) fn handle_query_historical_msg( + deps: Deps, + _env: Env, + msg: QueryMsgHistorical, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + if !config.extensions.historical { + return Err(ext_unsupported_err("historical")); + } + + match msg { + QueryMsgHistorical::UnclaimedAmountAtHeight { address, height } => { + to_binary(&query_unclaimed_amount_at_height(deps, address, height)?) + } + QueryMsgHistorical::UnclaimedTotalAmountAtHeight { height } => { + to_binary(&query_total_unclaimed_amount_at_height(deps, height)?) + } + } +} + +/// Returns the available amount of distributed and yet to be claimed tokens for a specific vesting recipient at certain height. +/// +/// * **address** vesting recipient for which to return the available amount of tokens to claim. +/// +/// * **height** the height we querying unclaimed amount for +fn query_unclaimed_amount_at_height( + deps: Deps, + address: String, + height: u64, +) -> StdResult { + let address = deps.api.addr_validate(&address)?; + + let config = CONFIG.load(deps.storage)?; + let maybe_info = vesting_info(config.extensions.historical).may_load_at_height( + deps.storage, + address, + height, + )?; + match &maybe_info { + Some(info) => compute_unclaimed_amount(info), + None => Ok(Uint128::zero()), + } +} + +/// Returns the available amount of distributed and yet to be claimed tokens for all the recipients at certain height. +/// +/// * **height** the height we querying unclaimed amount for +fn query_total_unclaimed_amount_at_height(deps: Deps, height: u64) -> StdResult { + let config = CONFIG.load(deps.storage)?; + let maybe_state = + vesting_state(config.extensions.historical).may_load_at_height(deps.storage, height)?; + match &maybe_state { + Some(info) => Ok(info.total_granted.checked_sub(info.total_released)?), + None => Ok(Uint128::zero()), + } +} + +/// Computes the amount of distributed and yet unclaimed tokens for a specific vesting recipient at certain height. +/// Returns the computed amount if the operation is successful. +/// +/// * **vesting_info** vesting schedules for which to compute the amount of tokens +/// that are vested and can be claimed by the recipient. +fn compute_unclaimed_amount(vesting_info: &VestingInfo) -> StdResult { + let mut available_amount: Uint128 = Uint128::zero(); + for sch in &vesting_info.schedules { + if let Some(end_point) = &sch.end_point { + available_amount = available_amount.checked_add(end_point.amount)?; + } else { + available_amount = available_amount.checked_add(sch.start_point.amount)?; + } + } + + available_amount + .checked_sub(vesting_info.released_amount) + .map_err(StdError::from) +} diff --git a/packages/vesting-base-pcl/src/ext_managed.rs b/packages/vesting-base-pcl/src/ext_managed.rs new file mode 100644 index 00000000..94b68d20 --- /dev/null +++ b/packages/vesting-base-pcl/src/ext_managed.rs @@ -0,0 +1,121 @@ +use crate::error::{ext_unsupported_err, ContractError}; +use crate::handlers::get_vesting_token; +use crate::msg::{ExecuteMsgManaged, QueryMsgManaged}; +use crate::state::{vesting_info, vesting_state, CONFIG}; +use astroport::asset::AssetInfoExt; +use cosmwasm_std::{ + attr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, SubMsg, Uint128, +}; + +/// Contains the managed extension check and routing of the message. +pub(crate) fn handle_execute_managed_msg( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsgManaged, +) -> Result { + let config = CONFIG.load(deps.storage)?; + if !config.extensions.managed { + return Err(ext_unsupported_err("managed").into()); + } + + match msg { + ExecuteMsgManaged::RemoveVestingAccounts { + vesting_accounts, + clawback_account, + } => remove_vesting_accounts(deps, env, info, vesting_accounts, clawback_account), + } +} + +/// Contains the managed extension check and routing of the message. +pub(crate) fn handle_query_managed_msg( + deps: Deps, + _env: Env, + _msg: QueryMsgManaged, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + if !config.extensions.managed { + return Err(ext_unsupported_err("managed")); + } + + // empty handler kept for uniformity with other extensions + unimplemented!() +} + +#[allow(clippy::too_many_arguments)] +fn remove_vesting_accounts( + deps: DepsMut, + env: Env, + info: MessageInfo, + vesting_accounts: Vec, + clawback_account: String, +) -> Result { + let config = CONFIG.load(deps.storage)?; + if info.sender != config.owner { + return Err(ContractError::Unauthorized {}); + } + let vesting_token = get_vesting_token(&config)?; + + let mut response = Response::new(); + + let clawback_address = deps.api.addr_validate(&clawback_account)?; + + // For each vesting account, calculate the amount of tokens to claw back (unclaimed + still + // vesting), transfer the required amount to the owner, remove the vesting information + // from the storage, and decrease the total granted metric. + for vesting_account in vesting_accounts { + let account_address = deps.api.addr_validate(&vesting_account)?; + + let config = CONFIG.load(deps.storage)?; + let vesting_info = vesting_info(config.extensions.historical); + if let Some(account_info) = vesting_info.may_load(deps.storage, account_address.clone())? { + let mut total_granted_for_user = Uint128::zero(); + for sch in account_info.schedules { + if let Some(end_point) = sch.end_point { + total_granted_for_user = + total_granted_for_user.checked_add(end_point.amount)?; + } else { + total_granted_for_user = + total_granted_for_user.checked_add(sch.start_point.amount)?; + } + } + + let amount_to_claw_back = + total_granted_for_user.checked_sub(account_info.released_amount)?; + + let transfer_msg = vesting_token + .with_balance(amount_to_claw_back) + .into_msg(&deps.querier, clawback_address.clone())?; + response = response.add_submessage(SubMsg::new(transfer_msg)); + + vesting_state(config.extensions.historical).update::<_, ContractError>( + deps.storage, + env.block.height, + |s| { + // Here we choose the "forget about everything" strategy. E.g., if we granted a user + // 300 tokens, and they claimed 150 tokens, the vesting state is + // { total_granted: 300, total_released: 150 }. + // If after that we remove the user's vesting account, we set the vesting state to + // { total_granted: 0, total_released: 0 }. + // + // If we decided to set it to { total_granted: 150, total_released: 150 }., the + // .total_released value of the vesting state would not be equal to the sum of the + // .released_amount values of all registered accounts. + let mut state = s.ok_or(ContractError::AmountIsNotAvailable {})?; + state.total_granted = + state.total_granted.checked_sub(total_granted_for_user)?; + state.total_released = state + .total_released + .checked_sub(account_info.released_amount)?; + Ok(state) + }, + )?; + vesting_info.remove(deps.storage, account_address, env.block.height)?; + } + } + + Ok(response.add_attributes(vec![ + attr("action", "remove_vesting_accounts"), + attr("sender", &info.sender), + ])) +} diff --git a/packages/vesting-base-pcl/src/ext_with_managers.rs b/packages/vesting-base-pcl/src/ext_with_managers.rs new file mode 100644 index 00000000..56573188 --- /dev/null +++ b/packages/vesting-base-pcl/src/ext_with_managers.rs @@ -0,0 +1,105 @@ +use crate::error::{ext_unsupported_err, ContractError}; +use crate::msg::{ExecuteMsgWithManagers, QueryMsgWithManagers}; +use crate::state::{CONFIG, VESTING_MANAGERS}; +use cosmwasm_std::{ + attr, to_binary, Addr, Attribute, Binary, Deps, DepsMut, Env, MessageInfo, Order, Response, + StdError, StdResult, +}; + +/// Contains the with_managers extension check and routing of the message. +pub(crate) fn handle_execute_with_managers_msg( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsgWithManagers, +) -> Result { + let config = CONFIG.load(deps.storage)?; + if !config.extensions.with_managers { + return Err(ext_unsupported_err("with_managers").into()); + } + + match msg { + ExecuteMsgWithManagers::AddVestingManagers { managers } => { + add_vesting_managers(deps, env, info, managers) + } + ExecuteMsgWithManagers::RemoveVestingManagers { managers } => { + remove_vesting_managers(deps, env, info, managers) + } + } +} + +/// Contains the with_managers extension check and routing of the message. +pub(crate) fn handle_query_managers_msg( + deps: Deps, + _env: Env, + msg: QueryMsgWithManagers, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + if !config.extensions.with_managers { + return Err(ext_unsupported_err("with_managers")); + } + + match msg { + QueryMsgWithManagers::VestingManagers {} => to_binary(&query_vesting_managers(deps)?), + } +} + +/// Adds new vesting managers, which have a permission to add/remove vesting schedule +/// +/// * **managers** list of accounts to be added to the whitelist. +fn add_vesting_managers( + deps: DepsMut, + _env: Env, + info: MessageInfo, + managers: Vec, +) -> Result { + let config = CONFIG.load(deps.storage)?; + if info.sender != config.owner { + return Err(ContractError::Unauthorized {}); + } + let mut attrs: Vec = vec![]; + for m in managers { + let ma = deps.api.addr_validate(&m)?; + if !VESTING_MANAGERS.has(deps.storage, ma.clone()) { + VESTING_MANAGERS.save(deps.storage, ma, &())?; + attrs.push(attr("vesting_manager", &m)) + } + } + Ok(Response::new() + .add_attribute("action", "add_vesting_managers") + .add_attributes(attrs)) +} + +/// Removes new vesting managers from the whitelist +/// +/// * **managers** list of accounts to be removed from the whitelist. +fn remove_vesting_managers( + deps: DepsMut, + _env: Env, + info: MessageInfo, + managers: Vec, +) -> Result { + let config = CONFIG.load(deps.storage)?; + if info.sender != config.owner { + return Err(ContractError::Unauthorized {}); + } + let mut attrs: Vec = vec![]; + for m in managers { + let ma = deps.api.addr_validate(&m)?; + if VESTING_MANAGERS.has(deps.storage, ma.clone()) { + VESTING_MANAGERS.remove(deps.storage, ma); + attrs.push(attr("vesting_manager", &m)) + } + } + Ok(Response::new() + .add_attribute("action", "remove_vesting_managers") + .add_attributes(attrs)) +} + +/// Returns a list of vesting schedules using a [`VestingAccountsResponse`] object. +fn query_vesting_managers(deps: Deps) -> StdResult> { + let managers = VESTING_MANAGERS + .keys(deps.storage, None, None, Order::Ascending) + .collect::, StdError>>()?; + Ok(managers) +} diff --git a/packages/vesting-base-pcl/src/handlers.rs b/packages/vesting-base-pcl/src/handlers.rs new file mode 100644 index 00000000..e401bca4 --- /dev/null +++ b/packages/vesting-base-pcl/src/handlers.rs @@ -0,0 +1,508 @@ +use crate::error::ContractError; +use crate::ext_historical::{handle_execute_historical_msg, handle_query_historical_msg}; +use crate::ext_managed::{handle_execute_managed_msg, handle_query_managed_msg}; +use crate::ext_with_managers::{handle_execute_with_managers_msg, handle_query_managers_msg}; +use crate::msg::{Cw20HookMsg, ExecuteMsg, MigrateMsg, QueryMsg}; +use crate::state::{read_vesting_infos, vesting_info, vesting_state}; +use crate::state::{CONFIG, OWNERSHIP_PROPOSAL, VESTING_MANAGERS, PCL_VESTING_LP_CONTRACT}; +use crate::types::{ + Config, OrderBy, VestingAccount, VestingAccountResponse, VestingAccountsResponse, VestingInfo, + VestingSchedule, VestingState, +}; +use astroport::asset::{addr_opt_validate, token_asset_info, AssetInfo, AssetInfoExt, Asset}; +use astroport::common::{claim_ownership, drop_ownership_proposal, propose_new_owner}; +use cosmwasm_std::{attr, from_binary, to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, SubMsg, Uint128, CosmosMsg, Empty, WasmMsg}; +use cw20::Cw20ReceiveMsg; +use cw_utils::must_pay; +use astroport::pair::ExecuteMsg::ProvideLiquidity; + +/// Exposes execute functions available in the contract. +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::Claim { recipient, amount } => claim(deps, env, info, recipient, amount), + ExecuteMsg::Receive(msg) => receive_cw20(deps, env, info, msg), + ExecuteMsg::RegisterVestingAccounts { vesting_accounts } => { + let config = CONFIG.load(deps.storage)?; + let vesting_token = get_vesting_token(&config)?; + + match &vesting_token { + AssetInfo::NativeToken { denom } + if is_sender_whitelisted(deps.storage, &config, &info.sender) => + { + let amount = must_pay(&info, denom)?; + register_vesting_accounts(deps, vesting_accounts, amount, env.block.height) + } + _ => Err(ContractError::Unauthorized {}), + } + } + ExecuteMsg::ProposeNewOwner { owner, expires_in } => { + let config: Config = CONFIG.load(deps.storage)?; + + propose_new_owner( + deps, + info, + env, + 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) + })?; + + Ok(()) + }) + .map_err(Into::into) + } + ExecuteMsg::SetVestingToken { vesting_token } => { + set_vesting_token(deps, env, info, vesting_token) + } + ExecuteMsg::ManagedExtension { msg } => handle_execute_managed_msg(deps, env, info, msg), + ExecuteMsg::WithManagersExtension { msg } => { + handle_execute_with_managers_msg(deps, env, info, msg) + } + ExecuteMsg::HistoricalExtension { msg } => { + handle_execute_historical_msg(deps, env, info, msg) + } + ExecuteMsg::MigrateXYKLiquidity { user_address_raw, user_vesting_info } => { + handle_migrate_xyk_liquidity(deps, info, env, user_address_raw, user_vesting_info) + } + } +} + +/// 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.load(deps.storage)?; + let vesting_token = get_vesting_token(&config)?; + + // Permission check + if !is_sender_whitelisted( + deps.storage, + &config, + &deps.api.addr_validate(&cw20_msg.sender)?, + ) || token_asset_info(info.sender) != vesting_token + { + return Err(ContractError::Unauthorized {}); + } + + match from_binary(&cw20_msg.msg)? { + Cw20HookMsg::RegisterVestingAccounts { vesting_accounts } => { + register_vesting_accounts(deps, vesting_accounts, cw20_msg.amount, env.block.height) + } + } +} + +/// Create new vesting schedules. +/// +/// * **vesting_accounts** list of accounts and associated vesting schedules to create. +/// +/// * **cw20_amount** sets the amount that confirms the total amount of all accounts to register. +fn register_vesting_accounts( + deps: DepsMut, + vesting_accounts: Vec, + amount: Uint128, + height: u64, +) -> Result { + let response = Response::new(); + let config = CONFIG.load(deps.storage)?; + let mut to_deposit = Uint128::zero(); + + for mut vesting_account in vesting_accounts { + let mut released_amount = Uint128::zero(); + let account_address = deps.api.addr_validate(&vesting_account.address)?; + + assert_vesting_schedules(&account_address, &vesting_account.schedules)?; + + for sch in &vesting_account.schedules { + let amount = if let Some(end_point) = &sch.end_point { + end_point.amount + } else { + sch.start_point.amount + }; + to_deposit = to_deposit.checked_add(amount)?; + } + + let vesting_info = vesting_info(config.extensions.historical); + if let Some(mut old_info) = vesting_info.may_load(deps.storage, account_address.clone())? { + released_amount = old_info.released_amount; + vesting_account.schedules.append(&mut old_info.schedules); + } + + vesting_info.save( + deps.storage, + account_address, + &VestingInfo { + schedules: vesting_account.schedules, + released_amount, + }, + height, + )?; + } + + if to_deposit != amount { + return Err(ContractError::VestingScheduleAmountError {}); + } + + vesting_state(config.extensions.historical).update::<_, ContractError>( + deps.storage, + height, + |s| { + let mut state = s.unwrap_or_default(); + state.total_granted = state.total_granted.checked_add(to_deposit)?; + Ok(state) + }, + )?; + + Ok(response.add_attributes({ + vec![ + attr("action", "register_vesting_accounts"), + attr("deposited", to_deposit), + ] + })) +} + +/// Claims vested tokens and transfers them to the vesting recipient. +/// +/// * **recipient** vesting recipient for which to claim tokens. +/// +/// * **amount** amount of vested tokens to claim. +fn claim( + deps: DepsMut, + env: Env, + info: MessageInfo, + recipient: Option, + amount: Option, +) -> Result { + let config = CONFIG.load(deps.storage)?; + let vesting_token = get_vesting_token(&config)?; + let vesting_info = vesting_info(config.extensions.historical); + let mut sender_vesting_info = vesting_info.load(deps.storage, info.sender.clone())?; + + let available_amount = + compute_available_amount(env.block.time.seconds(), &sender_vesting_info)?; + + let claim_amount = if let Some(a) = amount { + if a > available_amount { + return Err(ContractError::AmountIsNotAvailable {}); + }; + a + } else { + available_amount + }; + + let mut response = Response::new(); + + if !claim_amount.is_zero() { + let transfer_msg = vesting_token.with_balance(claim_amount).into_msg( + &deps.querier, + recipient.unwrap_or_else(|| info.sender.to_string()), + )?; + response = response.add_submessage(SubMsg::new(transfer_msg)); + + sender_vesting_info.released_amount = sender_vesting_info + .released_amount + .checked_add(claim_amount)?; + vesting_info.save( + deps.storage, + info.sender.clone(), + &sender_vesting_info, + env.block.height, + )?; + vesting_state(config.extensions.historical).update::<_, ContractError>( + deps.storage, + env.block.height, + |s| { + let mut state = s.ok_or(ContractError::AmountIsNotAvailable {})?; + state.total_released = state.total_released.checked_add(claim_amount)?; + Ok(state) + }, + )?; + }; + + Ok(response.add_attributes(vec![ + attr("action", "claim"), + attr("address", &info.sender), + attr("available_amount", available_amount), + attr("claimed_amount", claim_amount), + ])) +} + +fn handle_migrate_xyk_liquidity( + deps: DepsMut, + env: Env, + _info: MessageInfo, + user_addr_raw: Addr, + user_vesting_info: VestingInfo, +) -> Result { + let height = env.block.height; + + let account_address = deps.api.addr_validate(&user_addr_raw.address)?; + + assert_vesting_schedules(&account_address, &user_vesting_info.schedules)?; + + for sch in &user_vesting_info.schedules { + let amount = if let Some(end_point) = &sch.end_point { + end_point.amount + } else { + sch.start_point.amount + }; + to_deposit = to_deposit.checked_add(amount)?; + } + + + let vesting_info = vesting_info(config.extensions.historical); + + vesting_info.save( + deps.storage, + account_address, + &user_vesting_info, + height, + )?; + + let mut to_deposit = Uint128::zero(); + for sch in &user_vesting_info.schedules { + let amount = if let Some(end_point) = &sch.end_point { + end_point.amount + } else { + sch.start_point.amount + }; + to_deposit = to_deposit.checked_add(amount)?; + } + + + vesting_state(config.extensions.historical).update::<_, ContractError>( + deps.storage, + height.clone(), + |s| { + let mut state = s.unwrap_or_default(); + state.total_granted = state.total_granted.checked_add(to_deposit)?; + Ok(state) + }, + )?; + + Ok(Response::default()) + +} + +pub(crate) fn set_vesting_token( + deps: DepsMut, + _env: Env, + info: MessageInfo, + token: AssetInfo, +) -> Result { + let mut config = CONFIG.load(deps.storage)?; + if info.sender != config.owner && info.sender != config.token_info_manager { + return Err(ContractError::Unauthorized {}); + } + if config.vesting_token.is_some() { + return Err(ContractError::VestingTokenAlreadySet {}); + } + + token.check(deps.api)?; + config.vesting_token = Some(token.clone()); + CONFIG.save(deps.storage, &config)?; + + let response = Response::new(); + Ok(response.add_attributes(vec![ + attr("action", "set_vesting_token"), + attr("vesting_token", token.to_string()), + ])) +} + +pub(crate) fn get_vesting_token(config: &Config) -> Result { + config + .vesting_token + .clone() + .ok_or(ContractError::VestingTokenIsNotSet {}) +} + +/// Exposes all the queries available in the contract. +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Config {} => Ok(to_binary(&query_config(deps)?)?), + QueryMsg::VestingAccount { address } => { + Ok(to_binary(&query_vesting_account(deps, address)?)?) + } + QueryMsg::VestingAccounts { + start_after, + limit, + order_by, + } => Ok(to_binary(&query_vesting_accounts( + deps, + start_after, + limit, + order_by, + )?)?), + QueryMsg::AvailableAmount { address } => Ok(to_binary(&query_vesting_available_amount( + deps, env, address, + )?)?), + QueryMsg::VestingState {} => Ok(to_binary(&query_vesting_state(deps)?)?), + QueryMsg::Timestamp {} => Ok(to_binary(&query_timestamp(env)?)?), + QueryMsg::ManagedExtension { msg } => handle_query_managed_msg(deps, env, msg), + QueryMsg::WithManagersExtension { msg } => handle_query_managers_msg(deps, env, msg), + QueryMsg::HistoricalExtension { msg } => handle_query_historical_msg(deps, env, msg), + } +} + +/// Returns the vesting contract configuration using a [`Config`] object. +fn query_config(deps: Deps) -> StdResult { + let config = CONFIG.load(deps.storage)?; + Ok(config) +} + +/// Returns the accumulated vesting information for all addresses using a [`VestingState`] object. +fn query_vesting_state(deps: Deps) -> StdResult { + let config = CONFIG.load(deps.storage)?; + let state = vesting_state(config.extensions.historical).load(deps.storage)?; + + Ok(state) +} + +/// Return the current block timestamp (in seconds) +/// * **env** is an object of type [`Env`]. +fn query_timestamp(env: Env) -> StdResult { + Ok(env.block.time.seconds()) +} + +/// Returns the vesting data for a specific vesting recipient using a [`VestingAccountResponse`] object. +/// +/// * **address** vesting recipient for which to return vesting data. +fn query_vesting_account(deps: Deps, address: String) -> StdResult { + let address = deps.api.addr_validate(&address)?; + let config = CONFIG.load(deps.storage)?; + let info = vesting_info(config.extensions.historical).load(deps.storage, address.clone())?; + + Ok(VestingAccountResponse { address, info }) +} + +/// Returns a list of vesting schedules using a [`VestingAccountsResponse`] object. +/// +/// * **start_after** index from which to start reading vesting schedules. +/// +/// * **limit** amount of vesting schedules to return. +/// +/// * **order_by** whether results should be returned in an ascending or descending order. +fn query_vesting_accounts( + deps: Deps, + start_after: Option, + limit: Option, + order_by: Option, +) -> StdResult { + let start_after = addr_opt_validate(deps.api, &start_after)?; + + let vesting_infos = read_vesting_infos(deps, start_after, limit, order_by)?; + + let vesting_accounts: Vec<_> = vesting_infos + .into_iter() + .map(|(address, info)| VestingAccountResponse { address, info }) + .collect(); + + Ok(VestingAccountsResponse { vesting_accounts }) +} + +/// Returns the available amount of vested and yet to be claimed tokens for a specific vesting recipient. +/// +/// * **address** vesting recipient for which to return the available amount of tokens to claim. +fn query_vesting_available_amount(deps: Deps, env: Env, address: String) -> StdResult { + let address = deps.api.addr_validate(&address)?; + + let config = CONFIG.load(deps.storage)?; + let info = vesting_info(config.extensions.historical).load(deps.storage, address)?; + let available_amount = compute_available_amount(env.block.time.seconds(), &info)?; + Ok(available_amount) +} + +/// Manages contract migration. +pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { + Ok(Response::default()) +} + +fn is_sender_whitelisted(store: &mut dyn Storage, config: &Config, sender: &Addr) -> bool { + if *sender == config.owner { + return true; + } + if VESTING_MANAGERS.has(store, sender.clone()) { + return true; + } + false +} + +/// Asserts the validity of a list of vesting schedules. +/// +/// * **addr** receiver of the vested tokens. +/// +/// * **vesting_schedules** vesting schedules to validate. +fn assert_vesting_schedules( + addr: &Addr, + vesting_schedules: &[VestingSchedule], +) -> Result<(), ContractError> { + for sch in vesting_schedules { + if let Some(end_point) = &sch.end_point { + if !(sch.start_point.time < end_point.time && sch.start_point.amount < end_point.amount) + { + return Err(ContractError::VestingScheduleError(addr.to_string())); + } + } + } + + Ok(()) +} + +/// Computes the amount of vested and yet unclaimed tokens for a specific vesting recipient. +/// Returns the computed amount if the operation is successful. +/// +/// * **current_time** timestamp from which to start querying for vesting schedules. +/// Schedules that started later than current_time will be omitted. +/// +/// * **vesting_info** vesting schedules for which to compute the amount of tokens +/// that are vested and can be claimed by the recipient. +fn compute_available_amount(current_time: u64, vesting_info: &VestingInfo) -> StdResult { + let mut available_amount: Uint128 = Uint128::zero(); + for sch in &vesting_info.schedules { + if sch.start_point.time > current_time { + continue; + } + + available_amount = available_amount.checked_add(sch.start_point.amount)?; + + if let Some(end_point) = &sch.end_point { + let passed_time = current_time.min(end_point.time) - sch.start_point.time; + let time_period = end_point.time - sch.start_point.time; + if passed_time != 0 && time_period != 0 { + let release_amount = Uint128::from(passed_time).multiply_ratio( + end_point.amount.checked_sub(sch.start_point.amount)?, + time_period, + ); + available_amount = available_amount.checked_add(release_amount)?; + } + } + } + + available_amount + .checked_sub(vesting_info.released_amount) + .map_err(StdError::from) +} diff --git a/packages/vesting-base-pcl/src/lib.rs b/packages/vesting-base-pcl/src/lib.rs new file mode 100644 index 00000000..2de7b374 --- /dev/null +++ b/packages/vesting-base-pcl/src/lib.rs @@ -0,0 +1,13 @@ +pub mod builder; +pub mod error; +pub mod handlers; +pub mod msg; +pub mod state; +pub mod types; + +pub(crate) mod ext_historical; +pub(crate) mod ext_managed; +pub(crate) mod ext_with_managers; + +#[cfg(test)] +mod testing; diff --git a/packages/vesting-base-pcl/src/msg.rs b/packages/vesting-base-pcl/src/msg.rs new file mode 100644 index 00000000..0b434988 --- /dev/null +++ b/packages/vesting-base-pcl/src/msg.rs @@ -0,0 +1,166 @@ +use crate::types::{Config, OrderBy, VestingAccount, VestingAccountResponse, VestingAccountsResponse, VestingInfo, VestingState}; +use astroport::asset::AssetInfo; +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Addr, Binary, Uint128}; +use cw20::Cw20ReceiveMsg; + +/// This structure describes the execute messages available in a vesting contract. +#[cw_serde] +pub enum ExecuteMsg { + /// Claim claims vested tokens and sends them to a recipient + Claim { + /// The address that receives the vested tokens + recipient: Option, + /// The amount of tokens to claim + amount: Option, + }, + /// Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template + Receive(Cw20ReceiveMsg), + /// RegisterVestingAccounts registers vesting targets/accounts + RegisterVestingAccounts { + vesting_accounts: Vec, + }, + /// Creates a request to change contract ownership + /// ## Executor + /// Only the current owner can execute this + ProposeNewOwner { + /// The newly proposed owner + owner: String, + /// The validity period of the offer to change the owner + expires_in: u64, + }, + /// Removes a request to change contract ownership + /// ## Executor + /// Only the current owner can execute this + DropOwnershipProposal {}, + /// Claims contract ownership + /// ## Executor + /// Only the newly proposed owner can execute this + ClaimOwnership {}, + /// Sets vesting token + /// ## Executor + /// Only the current owner or token info manager can execute this + SetVestingToken { vesting_token: AssetInfo }, + /// Contains messages associated with the managed extension for vesting contracts. + ManagedExtension { msg: ExecuteMsgManaged }, + /// Contains messages associated with the with_managers extension for vesting contracts. + WithManagersExtension { msg: ExecuteMsgWithManagers }, + /// Contains messages associated with the historical extension for vesting contracts. + HistoricalExtension { msg: ExecuteMsgHistorical }, + /// A handler to receive vesting liquidity migrated from xyl pools to PCL ones. Only callable + /// by the original vesting-lp contract. + #[serde(rename = "migrate_xyk_liquidity")] + MigrateXYKLiquidity { + /// The address of the user which owns the lockup. + user_address_raw: String, + user_vesting_info: VestingInfo, + }, +} + +/// This structure describes the execute messages available in a managed vesting contract. +#[cw_serde] +pub enum ExecuteMsgManaged { + /// Removes vesting targets/accounts. + /// ## Executor + /// Only the current owner can execute this + RemoveVestingAccounts { + vesting_accounts: Vec, + /// Specifies the account that will receive the funds taken from the vesting accounts. + clawback_account: String, + }, +} + +/// This structure describes the execute messages available in a with_managers vesting contract. +#[cw_serde] +pub enum ExecuteMsgWithManagers { + /// Adds vesting managers + /// ## Executor + /// Only the current owner can execute this + AddVestingManagers { managers: Vec }, + /// Removes vesting managers + /// ## Executor + /// Only the current owner can execute this + RemoveVestingManagers { managers: Vec }, +} + +/// This structure describes the execute messages available in a historical vesting contract. +#[cw_serde] +pub enum ExecuteMsgHistorical {} + +/// This structure describes the query messages available in a vesting contract. +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + /// Returns the configuration for the contract using a [`ConfigResponse`] object. + #[returns(Config)] + Config {}, + /// Returns information about an address vesting tokens using a [`VestingAccountResponse`] object. + #[returns(VestingAccountResponse)] + VestingAccount { address: String }, + /// Returns a list of addresses that are vesting tokens using a [`VestingAccountsResponse`] object. + #[returns(VestingAccountsResponse)] + VestingAccounts { + start_after: Option, + limit: Option, + order_by: Option, + }, + /// Returns the total unvested amount of tokens for a specific address. + #[returns(Uint128)] + AvailableAmount { address: String }, + /// Timestamp returns the current timestamp + #[returns(u64)] + Timestamp {}, + /// VestingState returns the current vesting state. + #[returns(VestingState)] + VestingState {}, + /// Contains messages associated with the managed extension for vesting contracts. + #[returns(Binary)] + ManagedExtension { msg: QueryMsgManaged }, + /// Contains messages associated with the with_managers extension for vesting contracts. + #[returns(QueryMsgWithManagers)] + WithManagersExtension { msg: QueryMsgWithManagers }, + /// Contains messages associated with the historical extension for vesting contracts. + #[returns(QueryMsgHistorical)] + HistoricalExtension { msg: QueryMsgHistorical }, +} + +/// This structure describes the query messages available in a managed vesting contract. +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsgManaged {} + +/// This structure describes the query messages available in a with_managers vesting contract. +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsgWithManagers { + /// Returns list of vesting managers + /// (the persons who are able to add/remove vesting schedules) + #[returns(Vec)] + VestingManagers {}, +} + +/// This structure describes the query messages available in a historical vesting contract. +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsgHistorical { + /// Returns the total unclaimed amount of tokens for a specific address at certain height. + #[returns(Uint128)] + UnclaimedAmountAtHeight { address: String, height: u64 }, + /// Returns the total unclaimed amount of tokens for all the users at certain height. + #[returns(Uint128)] + UnclaimedTotalAmountAtHeight { height: u64 }, +} + +/// This structure describes a migration message. +/// We currently take no arguments for migrations. +#[cw_serde] +pub struct MigrateMsg {} + +/// This structure describes a CW20 hook message. +#[cw_serde] +pub enum Cw20HookMsg { + /// RegisterVestingAccounts registers vesting targets/accounts + RegisterVestingAccounts { + vesting_accounts: Vec, + }, +} diff --git a/packages/vesting-base-pcl/src/state.rs b/packages/vesting-base-pcl/src/state.rs new file mode 100644 index 00000000..16c819ec --- /dev/null +++ b/packages/vesting-base-pcl/src/state.rs @@ -0,0 +1,162 @@ +use crate::types::{Config, OrderBy, VestingInfo, VestingState}; +use astroport::common::OwnershipProposal; +use cosmwasm_std::{Addr, Deps, StdResult}; +use cw_storage_plus::{Bound, Item, Map, SnapshotItem, SnapshotMap, Strategy}; + +pub(crate) const CONFIG: Item = Item::new("config"); +pub(crate) const OWNERSHIP_PROPOSAL: Item = Item::new("ownership_proposal"); +pub(crate) const VESTING_MANAGERS: Map = Map::new("vesting_managers"); +/// The address of the vesting lp contract working with PCL pools. Used in users' locked liquidity +/// migration from XYK pools to PCL ones. +pub const PCL_VESTING_LP_CONTRACT: Item = Item::new("pcl_vesting_lp_contract"); +pub(crate) const VESTING_STATE: SnapshotItem = SnapshotItem::new( + "vesting_state", + "vesting_state__checkpoints", + "vesting_state__changelog", + Strategy::Never, +); +pub(crate) const VESTING_INFO: SnapshotMap = SnapshotMap::new( + "vesting_info", + "vesting_info__checkpoints", + "vesting_info__changelog", + Strategy::Never, +); +pub(crate) const VESTING_STATE_HISTORICAL: SnapshotItem = SnapshotItem::new( + "vesting_state", + "vesting_state__checkpoints", + "vesting_state__changelog", + Strategy::EveryBlock, +); +pub(crate) const VESTING_INFO_HISTORICAL: SnapshotMap = SnapshotMap::new( + "vesting_info", + "vesting_info__checkpoints", + "vesting_info__changelog", + Strategy::EveryBlock, +); + +pub(crate) fn vesting_state(historical: bool) -> SnapshotItem<'static, VestingState> { + if historical { + return VESTING_STATE_HISTORICAL; + } + VESTING_STATE +} + +pub(crate) fn vesting_info(historical: bool) -> SnapshotMap<'static, Addr, VestingInfo> { + if historical { + return VESTING_INFO_HISTORICAL; + } + VESTING_INFO +} + +const MAX_LIMIT: u32 = 30; +const DEFAULT_LIMIT: u32 = 10; + +/// Returns an empty vector if it does not find data, otherwise returns a vector that +/// contains objects of type [`VESTING_INFO`]. +/// ## Params +/// +/// * **start_after** index from which to start reading vesting schedules. +/// +/// * **limit** amount of vesting schedules to read. +/// +/// * **order_by** whether results should be returned in an ascending or descending order. +pub(crate) fn read_vesting_infos( + deps: Deps, + start_after: Option, + limit: Option, + order_by: Option, +) -> StdResult> { + let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; + let start_after = start_after.map(Bound::exclusive); + + let (start, end) = match &order_by { + Some(OrderBy::Asc) => (start_after, None), + _ => (None, start_after), + }; + + let info: Vec<(Addr, VestingInfo)> = VESTING_INFO + .range( + deps.storage, + start, + end, + order_by.unwrap_or(OrderBy::Desc).into(), + ) + .take(limit) + .filter_map(|v| v.ok()) + .collect(); + + Ok(info) +} + +#[cfg(test)] +mod testing { + use super::*; + + #[test] + fn read_vesting_infos_as_expected() { + use cosmwasm_std::{testing::mock_dependencies, Uint128}; + let mut deps = mock_dependencies(); + let historical = false; + + let vi_mock = VestingInfo { + released_amount: Uint128::zero(), + schedules: vec![], + }; + + for i in 1..5 { + let key = Addr::unchecked(format! {"address{}", i}); + + vesting_info(historical) + .save(&mut deps.storage, key, &vi_mock, 1) + .unwrap(); + } + + let res = read_vesting_infos( + deps.as_ref(), + Some(Addr::unchecked("address2")), + None, + Some(OrderBy::Asc), + ) + .unwrap(); + assert_eq!( + res, + vec![ + (Addr::unchecked("address3"), vi_mock.clone()), + (Addr::unchecked("address4"), vi_mock.clone()), + ] + ); + + let res = read_vesting_infos( + deps.as_ref(), + Some(Addr::unchecked("address2")), + Some(1), + Some(OrderBy::Asc), + ) + .unwrap(); + assert_eq!(res, vec![(Addr::unchecked("address3"), vi_mock.clone())]); + + let res = read_vesting_infos( + deps.as_ref(), + Some(Addr::unchecked("address3")), + None, + Some(OrderBy::Desc), + ) + .unwrap(); + assert_eq!( + res, + vec![ + (Addr::unchecked("address2"), vi_mock.clone()), + (Addr::unchecked("address1"), vi_mock.clone()), + ] + ); + + let res = read_vesting_infos( + deps.as_ref(), + Some(Addr::unchecked("address3")), + Some(1), + Some(OrderBy::Desc), + ) + .unwrap(); + assert_eq!(res, vec![(Addr::unchecked("address2"), vi_mock.clone())]); + } +} diff --git a/packages/vesting-base-pcl/src/testing.rs b/packages/vesting-base-pcl/src/testing.rs new file mode 100644 index 00000000..3d11af41 --- /dev/null +++ b/packages/vesting-base-pcl/src/testing.rs @@ -0,0 +1,484 @@ +use crate::builder::VestingBaseBuilder; +use crate::error::{ext_unsupported_err, ContractError}; +use crate::handlers::{execute, query}; +use crate::msg::{ + ExecuteMsg, ExecuteMsgManaged, QueryMsg, QueryMsgHistorical, QueryMsgWithManagers, +}; +use crate::types::{Config, Extensions}; +use astroport::asset::token_asset_info; +use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; +use cosmwasm_std::{from_binary, Addr}; + +#[test] +fn set_vesting_token() { + let mut deps = mock_dependencies(); + let owner = String::from("owner"); + let token_info_manager = "token_info_manager"; + let env = mock_env(); + VestingBaseBuilder::default() + .build(deps.as_mut(), owner, String::from(token_info_manager)) + .unwrap(); + + // check initialisation + assert_eq!( + from_binary::(&query(deps.as_ref(), env.clone(), QueryMsg::Config {}).unwrap()) + .unwrap(), + Config { + owner: Addr::unchecked("owner"), + token_info_manager: Addr::unchecked(token_info_manager), + vesting_token: None, + extensions: Extensions { + historical: false, + managed: false, + with_managers: false + } + } + ); + + let info = mock_info("stranger", &[]); + // set vesting token by a stranger -> Unauthorized + assert_eq!( + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::SetVestingToken { + vesting_token: token_asset_info(Addr::unchecked("ntrn_token")), + }, + ) + .unwrap_err(), + ContractError::Unauthorized {}, + ); + + // set vesting token by the manager -> Success + let info = mock_info("token_info_manager", &[]); + execute( + deps.as_mut(), + env.clone(), + info.clone(), + ExecuteMsg::SetVestingToken { + vesting_token: token_asset_info(Addr::unchecked("ntrn_token")), + }, + ) + .unwrap(); + + assert_eq!( + from_binary::(&query(deps.as_ref(), env.clone(), QueryMsg::Config {}).unwrap()) + .unwrap(), + Config { + owner: Addr::unchecked("owner"), + token_info_manager: Addr::unchecked(token_info_manager), + vesting_token: Some(token_asset_info(Addr::unchecked("ntrn_token"))), + extensions: Extensions { + historical: false, + managed: false, + with_managers: false + } + } + ); + + // set vesting token second time by the owner -> VestingTokenAlreadySet + assert_eq!( + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::SetVestingToken { + vesting_token: token_asset_info(Addr::unchecked("not_a_ntrn_token")), + }, + ) + .unwrap_err(), + ContractError::VestingTokenAlreadySet {}, + ); + + assert_eq!( + from_binary::(&query(deps.as_ref(), env, QueryMsg::Config {}).unwrap()).unwrap(), + Config { + owner: Addr::unchecked("owner"), + token_info_manager: Addr::unchecked(token_info_manager), + vesting_token: Some(token_asset_info(Addr::unchecked("ntrn_token"))), + extensions: Extensions { + historical: false, + managed: false, + with_managers: false + } + } + ); +} + +#[test] +fn proper_building_standard() { + let mut deps = mock_dependencies(); + let owner = String::from("owner"); + let token_info_manager = "token_info_manager"; + let env = mock_env(); + let info = mock_info("owner", &[]); + VestingBaseBuilder::default() + .build(deps.as_mut(), owner, String::from(token_info_manager)) + .unwrap(); + + // check initialisation + assert_eq!( + from_binary::(&query(deps.as_ref(), env.clone(), QueryMsg::Config {}).unwrap()) + .unwrap(), + Config { + owner: Addr::unchecked("owner"), + token_info_manager: Addr::unchecked(token_info_manager), + vesting_token: None, + extensions: Extensions { + historical: false, + managed: false, + with_managers: false + } + } + ); + + // make sure with_managers extension is not enabled + assert_eq!( + query( + deps.as_ref(), + env.clone(), + QueryMsg::WithManagersExtension { + msg: QueryMsgWithManagers::VestingManagers {} + } + ) + .unwrap_err(), + ext_unsupported_err("with_managers") + ); + + // make sure historical extension is not enabled + assert_eq!( + query( + deps.as_ref(), + env.clone(), + QueryMsg::HistoricalExtension { + msg: QueryMsgHistorical::UnclaimedTotalAmountAtHeight { height: 1000u64 } + } + ) + .unwrap_err(), + ext_unsupported_err("historical") + ); + + // make sure managed extension is not enabled + assert_eq!( + execute( + deps.as_mut(), + env, + info, + ExecuteMsg::ManagedExtension { + msg: ExecuteMsgManaged::RemoveVestingAccounts { + vesting_accounts: vec![], + clawback_account: String::from("clawback") + } + }, + ) + .unwrap_err(), + ext_unsupported_err("managed").into() + ); +} + +#[test] +fn proper_building_managers() { + let mut deps = mock_dependencies(); + let owner = String::from("owner"); + let token_info_manager = "token_info_manager"; + let env = mock_env(); + let info = mock_info("owner", &[]); + let vesting_managers = vec!["manager1".to_string(), "manager2".to_string()]; + VestingBaseBuilder::default() + .with_managers(vesting_managers.clone()) + .build(deps.as_mut(), owner, String::from(token_info_manager)) + .unwrap(); + + // check initialisation + assert_eq!( + from_binary::(&query(deps.as_ref(), env.clone(), QueryMsg::Config {}).unwrap()) + .unwrap(), + Config { + owner: Addr::unchecked("owner"), + token_info_manager: Addr::unchecked(token_info_manager), + vesting_token: None, + extensions: Extensions { + historical: false, + managed: false, + with_managers: true + } + } + ); + + // make sure with_managers extension is enabled + assert_eq!( + from_binary::>( + &query( + deps.as_ref(), + env.clone(), + QueryMsg::WithManagersExtension { + msg: QueryMsgWithManagers::VestingManagers {}, + }, + ) + .unwrap() + ) + .unwrap(), + vesting_managers + ); + + // make sure historical extension is not enabled + assert_eq!( + query( + deps.as_ref(), + env.clone(), + QueryMsg::HistoricalExtension { + msg: QueryMsgHistorical::UnclaimedTotalAmountAtHeight { height: 1000u64 } + } + ) + .unwrap_err(), + ext_unsupported_err("historical") + ); + + // make sure managed extension is not enabled + assert_eq!( + execute( + deps.as_mut(), + env, + info, + ExecuteMsg::ManagedExtension { + msg: ExecuteMsgManaged::RemoveVestingAccounts { + vesting_accounts: vec![], + clawback_account: String::from("clawback"), + }, + }, + ) + .unwrap_err(), + ext_unsupported_err("managed").into() + ); +} + +#[test] +fn proper_building_historical() { + let mut deps = mock_dependencies(); + let owner = String::from("owner"); + let token_info_manager = "token_info_manager"; + let env = mock_env(); + let info = mock_info("owner", &[]); + VestingBaseBuilder::default() + .historical() + .build(deps.as_mut(), owner, String::from(token_info_manager)) + .unwrap(); + + // check initialisation + assert_eq!( + from_binary::(&query(deps.as_ref(), env.clone(), QueryMsg::Config {}).unwrap()) + .unwrap(), + Config { + owner: Addr::unchecked("owner"), + token_info_manager: Addr::unchecked(token_info_manager), + vesting_token: None, + extensions: Extensions { + historical: true, + managed: false, + with_managers: false + } + } + ); + + // make sure with_managers extension is not enabled + assert_eq!( + query( + deps.as_ref(), + env.clone(), + QueryMsg::WithManagersExtension { + msg: QueryMsgWithManagers::VestingManagers {} + } + ) + .unwrap_err(), + ext_unsupported_err("with_managers") + ); + + // make sure historical extension is enabled + query( + deps.as_ref(), + env.clone(), + QueryMsg::HistoricalExtension { + msg: QueryMsgHistorical::UnclaimedTotalAmountAtHeight { height: 1000u64 }, + }, + ) + .unwrap(); + + // make sure managed extension is not enabled + assert_eq!( + execute( + deps.as_mut(), + env, + info, + ExecuteMsg::ManagedExtension { + msg: ExecuteMsgManaged::RemoveVestingAccounts { + vesting_accounts: vec![], + clawback_account: String::from("clawback") + } + }, + ) + .unwrap_err(), + ext_unsupported_err("managed").into() + ); +} + +#[test] +fn proper_building_managed() { + let mut deps = mock_dependencies(); + let owner = String::from("owner"); + let token_info_manager = "token_info_manager"; + let env = mock_env(); + VestingBaseBuilder::default() + .managed() + .build(deps.as_mut(), owner, String::from(token_info_manager)) + .unwrap(); + + // check initialisation and set vesting token + assert_eq!( + from_binary::(&query(deps.as_ref(), env.clone(), QueryMsg::Config {}).unwrap()) + .unwrap(), + Config { + owner: Addr::unchecked("owner"), + token_info_manager: Addr::unchecked(token_info_manager), + vesting_token: None, + extensions: Extensions { + historical: false, + managed: true, + with_managers: false + } + } + ); + let info = mock_info("token_info_manager", &[]); + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::SetVestingToken { + vesting_token: token_asset_info(Addr::unchecked("ntrn_token")), + }, + ) + .unwrap(); + + // make sure with_managers extension is not enabled + assert_eq!( + query( + deps.as_ref(), + env.clone(), + QueryMsg::WithManagersExtension { + msg: QueryMsgWithManagers::VestingManagers {} + } + ) + .unwrap_err(), + ext_unsupported_err("with_managers") + ); + + // make sure historical extension is not enabled + assert_eq!( + query( + deps.as_ref(), + env.clone(), + QueryMsg::HistoricalExtension { + msg: QueryMsgHistorical::UnclaimedTotalAmountAtHeight { height: 1000u64 } + } + ) + .unwrap_err(), + ext_unsupported_err("historical") + ); + + // make sure managed extension is enabled + let info = mock_info("owner", &[]); + execute( + deps.as_mut(), + env, + info, + ExecuteMsg::ManagedExtension { + msg: ExecuteMsgManaged::RemoveVestingAccounts { + vesting_accounts: vec![], + clawback_account: String::from("clawback"), + }, + }, + ) + .unwrap(); +} + +#[test] +fn proper_building_all_extensions() { + let mut deps = mock_dependencies(); + let owner = String::from("owner"); + let token_info_manager = "token_info_manager"; + let env = mock_env(); + let vesting_managers = vec!["manager1".to_string(), "manager2".to_string()]; + VestingBaseBuilder::default() + .historical() + .managed() + .with_managers(vesting_managers.clone()) + .build(deps.as_mut(), owner, String::from(token_info_manager)) + .unwrap(); + + // check initialisation and set vesting token + assert_eq!( + from_binary::(&query(deps.as_ref(), env.clone(), QueryMsg::Config {}).unwrap()) + .unwrap(), + Config { + owner: Addr::unchecked("owner"), + token_info_manager: Addr::unchecked(token_info_manager), + vesting_token: None, + extensions: Extensions { + historical: true, + managed: true, + with_managers: true + } + } + ); + let info = mock_info("token_info_manager", &[]); + execute( + deps.as_mut(), + env.clone(), + info, + ExecuteMsg::SetVestingToken { + vesting_token: token_asset_info(Addr::unchecked("ntrn_token")), + }, + ) + .unwrap(); + + // make sure with_managers extension is enabled + assert_eq!( + from_binary::>( + &query( + deps.as_ref(), + env.clone(), + QueryMsg::WithManagersExtension { + msg: QueryMsgWithManagers::VestingManagers {}, + }, + ) + .unwrap() + ) + .unwrap(), + vesting_managers + ); + + // make sure historical extension is enabled + query( + deps.as_ref(), + env.clone(), + QueryMsg::HistoricalExtension { + msg: QueryMsgHistorical::UnclaimedTotalAmountAtHeight { height: 1000u64 }, + }, + ) + .unwrap(); + + // make sure managed extension is enabled + let info = mock_info("owner", &[]); + execute( + deps.as_mut(), + env, + info, + ExecuteMsg::ManagedExtension { + msg: ExecuteMsgManaged::RemoveVestingAccounts { + vesting_accounts: vec![], + clawback_account: String::from("clawback"), + }, + }, + ) + .unwrap(); +} diff --git a/packages/vesting-base-pcl/src/types.rs b/packages/vesting-base-pcl/src/types.rs new file mode 100644 index 00000000..eb0eb089 --- /dev/null +++ b/packages/vesting-base-pcl/src/types.rs @@ -0,0 +1,107 @@ +use astroport::asset::AssetInfo; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Order, Uint128}; + +/// This structure stores the main parameters for the generator vesting contract. +#[cw_serde] +pub struct Config { + /// Address that's allowed to change contract parameters + pub owner: Addr, + /// [`AssetInfo`] of the vested token + pub vesting_token: Option, + /// Contains extensions information of the contract + pub extensions: Extensions, +} + +/// Contains extensions information for the contract. +#[cw_serde] +pub struct Extensions { + /// Whether the historical extension is enabled for the contract. + pub historical: bool, + /// Whether the managed extension is enabled for the contract. + pub managed: bool, + /// Whether the with_managers extension is enabled for the contract. + pub with_managers: bool, +} + +/// This structure stores the accumulated vesting information for all addresses. +#[cw_serde] +#[derive(Default)] +pub struct VestingState { + /// The total amount of tokens granted to the users + pub total_granted: Uint128, + /// The total amount of tokens already claimed + pub total_released: Uint128, +} + +/// This structure stores vesting information for a specific address that is getting tokens. +#[cw_serde] +pub struct VestingAccount { + /// The address that is getting tokens + pub address: String, + /// The vesting schedules targeted at the `address` + pub schedules: Vec, +} + +/// This structure stores parameters for a batch of vesting schedules. +#[cw_serde] +pub struct VestingInfo { + /// The vesting schedules + pub schedules: Vec, + /// The total amount of vested tokens already claimed + pub released_amount: Uint128, +} + +/// This structure stores parameters for a specific vesting schedule +#[cw_serde] +pub struct VestingSchedule { + /// The start date for the vesting schedule + pub start_point: VestingSchedulePoint, + /// The end point for the vesting schedule + pub end_point: Option, +} + +/// This structure stores the parameters used to create a vesting schedule. +#[cw_serde] +pub struct VestingSchedulePoint { + /// The start time for the vesting schedule + pub time: u64, + /// The amount of tokens being vested + pub amount: Uint128, +} + +/// This structure describes a custom struct used to return vesting data about a specific vesting target. +#[cw_serde] +pub struct VestingAccountResponse { + /// The address that's vesting tokens + pub address: Addr, + /// Vesting information + pub info: VestingInfo, +} + +/// This structure describes a custom struct used to return vesting data for multiple vesting targets. +#[cw_serde] +pub struct VestingAccountsResponse { + /// A list of accounts that are vesting tokens + pub vesting_accounts: Vec, +} + +/// This enum describes the types of sorting that can be applied to some piece of data +#[cw_serde] +pub enum OrderBy { + Asc, + Desc, +} + +// We suppress this clippy warning because Order in cosmwasm doesn't implement Debug and +// PartialEq for usage in QueryMsg. We need to use our own OrderBy and convert the result to cosmwasm's Order +#[allow(clippy::from_over_into)] +impl Into for OrderBy { + fn into(self) -> Order { + if self == OrderBy::Asc { + Order::Ascending + } else { + Order::Descending + } + } +} From 183c903e8f2e9d3c9e07821b339b6965e6aa6364 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Thu, 1 Feb 2024 08:43:16 -0300 Subject: [PATCH 03/30] fixes, fmft, ref --- contracts/lockdrop-pcl/.cargo/config | 4 - contracts/lockdrop-pcl/Cargo.toml | 38 - contracts/lockdrop-pcl/README.md | 61 - .../lockdrop-pcl/examples/lockdrop_schema.rs | 12 - .../lockdrop-pcl/schema/neutron-lockdrop.json | 1632 --------------- .../lockdrop-pcl/schema/raw/execute.json | 450 ---- .../lockdrop-pcl/schema/raw/instantiate.json | 104 - .../lockdrop-pcl/schema/raw/migrate.json | 5 - contracts/lockdrop-pcl/schema/raw/query.json | 193 -- .../schema/raw/response_to_config.json | 146 -- .../schema/raw/response_to_lock_up_info.json | 198 -- .../schema/raw/response_to_pool.json | 142 -- ...ponse_to_query_lockup_total_at_height.json | 18 - ..._to_query_user_lockup_total_at_height.json | 18 - .../schema/raw/response_to_state.json | 39 - .../schema/raw/response_to_user_info.json | 243 --- ...sponse_to_user_info_with_lockups_list.json | 68 - contracts/lockdrop-pcl/src/contract.rs | 1853 ----------------- contracts/lockdrop-pcl/src/lib.rs | 6 - contracts/lockdrop-pcl/src/migration.rs | 77 - contracts/lockdrop-pcl/src/raw_queries.rs | 53 - contracts/lockdrop-pcl/src/state.rs | 113 - contracts/lockdrop-pcl/src/testing.rs | 149 -- contracts/vesting-lp-pcl/Cargo.toml | 2 +- contracts/vesting-lp-pcl/src/contract.rs | 7 +- contracts/vesting-lp-pcl/src/msg.rs | 1 + packages/vesting-base-lp/src/handlers.rs | 66 +- packages/vesting-base-pcl/src/builder.rs | 9 +- packages/vesting-base-pcl/src/handlers.rs | 38 +- packages/vesting-base-pcl/src/msg.rs | 19 +- packages/vesting-base-pcl/src/state.rs | 3 - packages/vesting-base-pcl/src/types.rs | 3 + 32 files changed, 81 insertions(+), 5689 deletions(-) delete mode 100644 contracts/lockdrop-pcl/.cargo/config delete mode 100644 contracts/lockdrop-pcl/Cargo.toml delete mode 100644 contracts/lockdrop-pcl/README.md delete mode 100644 contracts/lockdrop-pcl/examples/lockdrop_schema.rs delete mode 100644 contracts/lockdrop-pcl/schema/neutron-lockdrop.json delete mode 100644 contracts/lockdrop-pcl/schema/raw/execute.json delete mode 100644 contracts/lockdrop-pcl/schema/raw/instantiate.json delete mode 100644 contracts/lockdrop-pcl/schema/raw/migrate.json delete mode 100644 contracts/lockdrop-pcl/schema/raw/query.json delete mode 100644 contracts/lockdrop-pcl/schema/raw/response_to_config.json delete mode 100644 contracts/lockdrop-pcl/schema/raw/response_to_lock_up_info.json delete mode 100644 contracts/lockdrop-pcl/schema/raw/response_to_pool.json delete mode 100644 contracts/lockdrop-pcl/schema/raw/response_to_query_lockup_total_at_height.json delete mode 100644 contracts/lockdrop-pcl/schema/raw/response_to_query_user_lockup_total_at_height.json delete mode 100644 contracts/lockdrop-pcl/schema/raw/response_to_state.json delete mode 100644 contracts/lockdrop-pcl/schema/raw/response_to_user_info.json delete mode 100644 contracts/lockdrop-pcl/schema/raw/response_to_user_info_with_lockups_list.json delete mode 100644 contracts/lockdrop-pcl/src/contract.rs delete mode 100644 contracts/lockdrop-pcl/src/lib.rs delete mode 100644 contracts/lockdrop-pcl/src/migration.rs delete mode 100644 contracts/lockdrop-pcl/src/raw_queries.rs delete mode 100644 contracts/lockdrop-pcl/src/state.rs delete mode 100644 contracts/lockdrop-pcl/src/testing.rs diff --git a/contracts/lockdrop-pcl/.cargo/config b/contracts/lockdrop-pcl/.cargo/config deleted file mode 100644 index 386a66e5..00000000 --- a/contracts/lockdrop-pcl/.cargo/config +++ /dev/null @@ -1,4 +0,0 @@ -[alias] -wasm = "build --release --target wasm32-unknown-unknown" -unit-test = "test --lib" -schema = "run --example lockdrop_schema" diff --git a/contracts/lockdrop-pcl/Cargo.toml b/contracts/lockdrop-pcl/Cargo.toml deleted file mode 100644 index d3167f93..00000000 --- a/contracts/lockdrop-pcl/Cargo.toml +++ /dev/null @@ -1,38 +0,0 @@ -[package] -name = "neutron-lockdrop" -version = "1.2.1" -authors = ["_astromartian"] -edition = "2021" - - -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 more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] -# use library feature to disable all instantiate/execute/query exports -library = [] - - -[dependencies] -# we have to keep it 0.15.1 because it is the same version as in astroport -cw-storage-plus = { version = "0.15.1" } -astroport = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v2.5.0" } -credits = { path = "../credits" } -astroport-periphery = { workspace = true } -cosmwasm-std = { workspace = true } -cw20 = { workspace = true } -cw2 = { workspace = true } -serde = { workspace = true } - -[dev-dependencies] -cosmwasm-schema = { workspace = true } diff --git a/contracts/lockdrop-pcl/README.md b/contracts/lockdrop-pcl/README.md deleted file mode 100644 index 0bc572b1..00000000 --- a/contracts/lockdrop-pcl/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# Lockdrop - -The lockdrop contract allows users to lock any of the supported Terraswap LP tokens locked for a selected duration against which they will receive ASTRO tokens pro-rata to their weighted share of the LP tokens to the total deposited LP tokens for that particular pool in the contract. - -- Upon lockup expiration, users will receive Astroport LP tokens on an equivalent weight basis as per their initial Terraswap LP token deposits. - -Note - Users can open muliple lockup positions with different lockup duration for each LP Token pool - -## Contract Design - -### Handle Messages - -| Message | Description | -|-----------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `ExecuteMsg::UpdateConfig` | Can only be called by the admin. Facilitates updating configuration parameters | -| `ExecuteMsg::EnableClaims` | Executed by the Bootstrap auction contract when liquidity is added to the ASTRO-UST pool. Enables ASTRO withdrawals by the lockdrop recipients. | -| `ExecuteMsg::InitializePool` | Admin function. Facilitates addition of new Pool (Terraswap Pools) whose LP tokens can then be locked in the lockdrop contract | -| `ExecuteMsg::UpdatePool` | Admin function to update any configuraton parameter for a terraswap pool whose LP tokens are currently accepted for the lockdrop | -| `ExecuteMsg::IncreaseLockup` | Facilitates opening a new user position or adding to an existing position | -| `ExecuteMsg::IncreaseAstroIncentives` | Admin function to increase the ASTRO incentives that are to be distributed | -| `ExecuteMsg::WithdrawFromLockup` | Facilitates LP token withdrawals from lockup positions by users. 100% amount can be withdrawn during deposit window, which is then limited to 50% during 1st half of deposit window which then decreases linearly during 2nd half of deposit window. Only 1 withdrawal can be made by a user during the withdrawal windows | -| `ExecuteMsg::MigrateLiquidity` | Admin function. Facilitates migration of liquidity (locked terraswap LP tokens) from Terraswap to Astroport (Astroport LP tokens) | -| `ExecuteMsg::StakeLpTokens` | Admin function. Facilitates staking of Astroport LP tokens for a particular LP pool with the generator contract | -| `ExecuteMsg::DelegateAstroToAuction` | This function facilitates ASTRO tokens delegation to the Bootstrap auction contract during the bootstrap auction phase. Delegated ASTRO tokens are added to the user's position in the bootstrap auction contract | -| `ExecuteMsg::ClaimRewardsAndOptionallyUnlock` | Facilitates rewards claim by users for a particular lockup position along with unlock when possible | -| `ExecuteMsg::ClaimAssetReward` | Collects assets reward from LP and distribute reward to user if all requirements are met | -| `ExecuteMsg::TogglePoolRewards` | Admin function. Enables assets reward for specified LP | -| `ExecuteMsg::ProposeNewOwner` | Admin function. Creates an offer to change the contract ownership. The validity period of the offer is set in the `expires_in` variable. After `expires_in` seconds pass, the proposal expires and cannot be accepted anymore. | -| `ExecuteMsg::DropOwnershipProposal` | Admin function. Removes an existing offer to change the contract owner. | -| `ExecuteMsg::ClaimOwnership` | Admin function. Used to claim contract ownership. | - -### Handle Messages :: Callback - -| Message | Description | -|-------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------| -| `CallbackMsg::UpdatePoolOnDualRewardsClaim` | Callback function to update contract state after pending dual staking rewards are claimed from the generator contract | -| `CallbackMsg::WithdrawUserLockupRewardsCallback` | Callback function to withdraw user rewards for a particular lockcup position along with optional LP tokens withdrawal (upon lockup duration expiration) | -| `CallbackMsg::WithdrawLiquidityFromTerraswapCallback` | Callback function used during liquidity migration to update state after liquidity is removed from terraswap | -| `CallbackMsg::DistributeAssetReward` | Callback function used for assets reward distribution after rewards claiming from LP | - -### Query Messages - -| Message | Description | -|---------------------------------|------------------------------------------------------------------------------------------------------------------| -| `QueryMsg::Config` | Returns the config info | -| `QueryMsg::State` | Returns the contract's global state | -| `QueryMsg::Pool` | Returns info regarding a certain supported LP token pool | -| `QueryMsg::UserInfo` | Returns info regarding a user (total ASTRO rewards, list of lockup positions) | -| `QueryMsg::LockUpInfo` | Returns info regarding a particular lockup position with a given duration and identifer for the LP tokens locked | -| `QueryMsg::PendingAssetReward` | Returns the amount of pending asset rewards for the specified recipient and for a specific lockup position | - -## Build schema and run unit-tests - -``` -cargo schema -cargo test -``` - -## License - -TBD diff --git a/contracts/lockdrop-pcl/examples/lockdrop_schema.rs b/contracts/lockdrop-pcl/examples/lockdrop_schema.rs deleted file mode 100644 index ddfb898f..00000000 --- a/contracts/lockdrop-pcl/examples/lockdrop_schema.rs +++ /dev/null @@ -1,12 +0,0 @@ -use cosmwasm_schema::write_api; - -use astroport_periphery::lockdrop::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; - -fn main() { - write_api! { - instantiate: InstantiateMsg, - query: QueryMsg, - execute: ExecuteMsg, - migrate: MigrateMsg - } -} diff --git a/contracts/lockdrop-pcl/schema/neutron-lockdrop.json b/contracts/lockdrop-pcl/schema/neutron-lockdrop.json deleted file mode 100644 index cf59d5f9..00000000 --- a/contracts/lockdrop-pcl/schema/neutron-lockdrop.json +++ /dev/null @@ -1,1632 +0,0 @@ -{ - "contract_name": "neutron-lockdrop", - "contract_version": "1.2.1", - "idl_version": "1.0.0", - "instantiate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "auction_contract", - "credits_contract", - "init_timestamp", - "lock_window", - "lockup_rewards_info", - "max_lock_duration", - "max_positions_per_user", - "min_lock_duration", - "token_info_manager", - "withdrawal_window" - ], - "properties": { - "auction_contract": { - "description": "Auction contract address", - "type": "string" - }, - "credits_contract": { - "description": "Credits contract address", - "type": "string" - }, - "init_timestamp": { - "description": "Timestamp when Contract will start accepting LP Token deposits", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "lock_window": { - "description": "Number of seconds during which lockup deposits will be accepted", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "lockup_rewards_info": { - "description": "Describes rewards coefficients for each lockup duration", - "type": "array", - "items": { - "$ref": "#/definitions/LockupRewardsInfo" - } - }, - "max_lock_duration": { - "description": "Max. no. of weeks allowed for lockup", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "max_positions_per_user": { - "description": "Max lockup positions a user can have", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "min_lock_duration": { - "description": "Min. no. of weeks allowed for lockup", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "owner": { - "description": "Account which can update config", - "type": [ - "string", - "null" - ] - }, - "token_info_manager": { - "description": "Account which can update token addresses and generator", - "type": "string" - }, - "withdrawal_window": { - "description": "Withdrawal Window Length :: Post the deposit window", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "definitions": { - "Decimal256": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal256(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 115792089237316195423570985008687907853269984665640564039457.584007913129639935 (which is (2^256 - 1) / 10^18)", - "type": "string" - }, - "LockupRewardsInfo": { - "type": "object", - "required": [ - "coefficient", - "duration" - ], - "properties": { - "coefficient": { - "$ref": "#/definitions/Decimal256" - }, - "duration": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - } - }, - "execute": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "oneOf": [ - { - "type": "object", - "required": [ - "increase_lockup_for" - ], - "properties": { - "increase_lockup_for": { - "type": "object", - "required": [ - "amount", - "duration", - "pool_type", - "user_address" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "duration": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "pool_type": { - "$ref": "#/definitions/PoolType" - }, - "user_address": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "receive" - ], - "properties": { - "receive": { - "$ref": "#/definitions/Cw20ReceiveMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "increase_ntrn_incentives" - ], - "properties": { - "increase_ntrn_incentives": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "update_config" - ], - "properties": { - "update_config": { - "type": "object", - "required": [ - "new_config" - ], - "properties": { - "new_config": { - "$ref": "#/definitions/UpdateConfigMsg" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "set_token_info" - ], - "properties": { - "set_token_info": { - "type": "object", - "required": [ - "atom_token", - "generator", - "usdc_token" - ], - "properties": { - "atom_token": { - "type": "string" - }, - "generator": { - "type": "string" - }, - "usdc_token": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "withdraw_from_lockup" - ], - "properties": { - "withdraw_from_lockup": { - "type": "object", - "required": [ - "amount", - "duration", - "pool_type", - "user_address" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "duration": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "pool_type": { - "$ref": "#/definitions/PoolType" - }, - "user_address": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "claim_rewards_and_optionally_unlock" - ], - "properties": { - "claim_rewards_and_optionally_unlock": { - "type": "object", - "required": [ - "duration", - "pool_type", - "withdraw_lp_stake" - ], - "properties": { - "duration": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "pool_type": { - "$ref": "#/definitions/PoolType" - }, - "withdraw_lp_stake": { - "type": "boolean" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Callbacks; only callable by the contract itself.", - "type": "object", - "required": [ - "callback" - ], - "properties": { - "callback": { - "$ref": "#/definitions/CallbackMsg" - } - }, - "additionalProperties": false - }, - { - "description": "ProposeNewOwner creates a proposal to change contract ownership. The validity period for the proposal is set in the `expires_in` variable.", - "type": "object", - "required": [ - "propose_new_owner" - ], - "properties": { - "propose_new_owner": { - "type": "object", - "required": [ - "expires_in", - "owner" - ], - "properties": { - "expires_in": { - "description": "The date after which this proposal expires", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "owner": { - "description": "Newly proposed contract owner", - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "DropOwnershipProposal removes the existing offer to change contract ownership.", - "type": "object", - "required": [ - "drop_ownership_proposal" - ], - "properties": { - "drop_ownership_proposal": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "description": "Used to claim contract ownership.", - "type": "object", - "required": [ - "claim_ownership" - ], - "properties": { - "claim_ownership": { - "type": "object" - } - }, - "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 - } - ] - }, - "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" - }, - "CallbackMsg": { - "oneOf": [ - { - "type": "object", - "required": [ - "update_pool_on_dual_rewards_claim" - ], - "properties": { - "update_pool_on_dual_rewards_claim": { - "type": "object", - "required": [ - "pool_type", - "prev_ntrn_balance", - "prev_proxy_reward_balances" - ], - "properties": { - "pool_type": { - "$ref": "#/definitions/PoolType" - }, - "prev_ntrn_balance": { - "$ref": "#/definitions/Uint128" - }, - "prev_proxy_reward_balances": { - "type": "array", - "items": { - "$ref": "#/definitions/Asset" - } - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "withdraw_user_lockup_rewards_callback" - ], - "properties": { - "withdraw_user_lockup_rewards_callback": { - "type": "object", - "required": [ - "duration", - "pool_type", - "user_address", - "withdraw_lp_stake" - ], - "properties": { - "duration": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "pool_type": { - "$ref": "#/definitions/PoolType" - }, - "user_address": { - "$ref": "#/definitions/Addr" - }, - "withdraw_lp_stake": { - "type": "boolean" - } - } - } - }, - "additionalProperties": false - } - ] - }, - "Cw20ReceiveMsg": { - "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", - "type": "object", - "required": [ - "amount", - "msg", - "sender" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "msg": { - "$ref": "#/definitions/Binary" - }, - "sender": { - "type": "string" - } - }, - "additionalProperties": false - }, - "PoolType": { - "type": "string", - "enum": [ - "USDC", - "ATOM" - ] - }, - "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" - }, - "UpdateConfigMsg": { - "type": "object", - "properties": { - "auction_contract_address": { - "description": "Bootstrap Auction contract address", - "type": [ - "string", - "null" - ] - }, - "generator_address": { - "description": "Generator (Staking for dual rewards) contract address", - "type": [ - "string", - "null" - ] - } - } - } - } - }, - "query": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "oneOf": [ - { - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "state" - ], - "properties": { - "state": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "pool" - ], - "properties": { - "pool": { - "type": "object", - "required": [ - "pool_type" - ], - "properties": { - "pool_type": { - "$ref": "#/definitions/PoolType" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "user_info" - ], - "properties": { - "user_info": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "user_info_with_lockups_list" - ], - "properties": { - "user_info_with_lockups_list": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "lock_up_info" - ], - "properties": { - "lock_up_info": { - "type": "object", - "required": [ - "duration", - "pool_type", - "user_address" - ], - "properties": { - "duration": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "pool_type": { - "$ref": "#/definitions/PoolType" - }, - "user_address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_user_lockup_total_at_height" - ], - "properties": { - "query_user_lockup_total_at_height": { - "type": "object", - "required": [ - "height", - "pool_type", - "user_address" - ], - "properties": { - "height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "pool_type": { - "$ref": "#/definitions/PoolType" - }, - "user_address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_lockup_total_at_height" - ], - "properties": { - "query_lockup_total_at_height": { - "type": "object", - "required": [ - "height", - "pool_type" - ], - "properties": { - "height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "pool_type": { - "$ref": "#/definitions/PoolType" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "PoolType": { - "type": "string", - "enum": [ - "USDC", - "ATOM" - ] - } - } - }, - "migrate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MigrateMsg", - "type": "object" - }, - "sudo": null, - "responses": { - "config": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Config", - "type": "object", - "required": [ - "auction_contract", - "credits_contract", - "init_timestamp", - "lock_window", - "lockdrop_incentives", - "lockup_rewards_info", - "max_lock_duration", - "max_positions_per_user", - "min_lock_duration", - "owner", - "token_info_manager", - "withdrawal_window" - ], - "properties": { - "auction_contract": { - "description": "Bootstrap Auction contract address", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - }, - "credits_contract": { - "description": "Credits contract address", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - }, - "generator": { - "description": "Generator (Staking for dual rewards) contract address", - "anyOf": [ - { - "$ref": "#/definitions/Addr" - }, - { - "type": "null" - } - ] - }, - "init_timestamp": { - "description": "Timestamp when Contract will start accepting LP Token deposits", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "lock_window": { - "description": "Number of seconds during which lockup positions be accepted", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "lockdrop_incentives": { - "description": "Total NTRN lockdrop incentives to be distributed among the users", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "lockup_rewards_info": { - "description": "Describes rewards coefficients for each lockup duration", - "type": "array", - "items": { - "$ref": "#/definitions/LockupRewardsInfo" - } - }, - "max_lock_duration": { - "description": "Max. no. of weeks allowed for lockup", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "max_positions_per_user": { - "description": "Max lockup positions a user can have", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "min_lock_duration": { - "description": "Min. no. of weeks allowed for lockup", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "owner": { - "description": "Account which can update the config", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - }, - "token_info_manager": { - "description": "Account which can update the generator and token addresses", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - }, - "withdrawal_window": { - "description": "Withdrawal Window Length :: Post the deposit window", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "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" - }, - "Decimal256": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal256(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 115792089237316195423570985008687907853269984665640564039457.584007913129639935 (which is (2^256 - 1) / 10^18)", - "type": "string" - }, - "LockupRewardsInfo": { - "type": "object", - "required": [ - "coefficient", - "duration" - ], - "properties": { - "coefficient": { - "$ref": "#/definitions/Decimal256" - }, - "duration": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - }, - "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" - } - } - }, - "lock_up_info": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "LockUpInfoResponse", - "type": "object", - "required": [ - "astroport_lp_token", - "claimable_generator_astro_debt", - "claimable_generator_proxy_debt", - "duration", - "generator_ntrn_debt", - "generator_proxy_debt", - "lp_units_locked", - "ntrn_rewards", - "pool_type", - "unlock_timestamp", - "withdrawal_flag" - ], - "properties": { - "astroport_lp_token": { - "$ref": "#/definitions/Addr" - }, - "astroport_lp_transferred": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "astroport_lp_units": { - "description": "User's Astroport LP units, calculated as lp_units_locked (terraswap) / total LP units locked (terraswap) * Astroport LP units minted post migration", - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "claimable_generator_astro_debt": { - "description": "ASTRO tokens receivable as generator rewards that user can claim", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "claimable_generator_proxy_debt": { - "description": "Proxy tokens receivable as generator rewards that user can claim", - "allOf": [ - { - "$ref": "#/definitions/RestrictedVector_for_AssetInfo_and_Uint128" - } - ] - }, - "duration": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "generator_ntrn_debt": { - "description": "Generator NTRN tokens lockup received as generator rewards", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "generator_proxy_debt": { - "description": "Generator Proxy tokens lockup received as generator rewards", - "allOf": [ - { - "$ref": "#/definitions/RestrictedVector_for_AssetInfo_and_Uint128" - } - ] - }, - "lp_units_locked": { - "description": "Terraswap LP units locked by the user", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "ntrn_rewards": { - "description": "NTRN tokens received as rewards for participation in the lockdrop", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "pool_type": { - "description": "Terraswap LP token", - "allOf": [ - { - "$ref": "#/definitions/PoolType" - } - ] - }, - "unlock_timestamp": { - "description": "Timestamp beyond which this position can be unlocked", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "withdrawal_flag": { - "description": "Boolean value indicating if the user's has withdrawn funds post the only 1 withdrawal limit cutoff", - "type": "boolean" - } - }, - "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" - }, - "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 - } - ] - }, - "PoolType": { - "type": "string", - "enum": [ - "USDC", - "ATOM" - ] - }, - "RestrictedVector_for_AssetInfo_and_Uint128": { - "description": "Vec wrapper for internal use. Some business logic relies on an order of this vector, thus it is forbidden to sort it or remove elements. New values can be added using .update() ONLY.", - "type": "array", - "items": { - "type": "array", - "items": [ - { - "$ref": "#/definitions/AssetInfo" - }, - { - "$ref": "#/definitions/Uint128" - } - ], - "maxItems": 2, - "minItems": 2 - } - }, - "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" - } - } - }, - "pool": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "PoolInfo", - "type": "object", - "required": [ - "amount_in_lockups", - "generator_ntrn_per_share", - "generator_proxy_per_share", - "incentives_share", - "is_staked", - "lp_token", - "weighted_amount" - ], - "properties": { - "amount_in_lockups": { - "$ref": "#/definitions/Uint128" - }, - "generator_ntrn_per_share": { - "description": "Ratio of Generator NTRN rewards accured to astroport pool share", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, - "generator_proxy_per_share": { - "description": "Ratio of Generator Proxy rewards accured to astroport pool share", - "allOf": [ - { - "$ref": "#/definitions/RestrictedVector_for_AssetInfo_and_Decimal" - } - ] - }, - "incentives_share": { - "description": "Share of total NTRN incentives allocated to this pool", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "is_staked": { - "description": "Boolean value indicating if the LP Tokens are staked with the Generator contract or not", - "type": "boolean" - }, - "lp_token": { - "$ref": "#/definitions/Addr" - }, - "weighted_amount": { - "description": "Weighted LP Token balance used to calculate NTRN rewards a particular user can claim", - "allOf": [ - { - "$ref": "#/definitions/Uint256" - } - ] - } - }, - "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" - }, - "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" - }, - "RestrictedVector_for_AssetInfo_and_Decimal": { - "description": "Vec wrapper for internal use. Some business logic relies on an order of this vector, thus it is forbidden to sort it or remove elements. New values can be added using .update() ONLY.", - "type": "array", - "items": { - "type": "array", - "items": [ - { - "$ref": "#/definitions/AssetInfo" - }, - { - "$ref": "#/definitions/Decimal" - } - ], - "maxItems": 2, - "minItems": 2 - } - }, - "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" - }, - "Uint256": { - "description": "An implementation of u256 that is using strings for JSON encoding/decoding, such that the full u256 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 out of primitive uint types or `new` to provide big endian bytes:\n\n``` # use cosmwasm_std::Uint256; let a = Uint256::from(258u128); let b = Uint256::new([ 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 1u8, 2u8, ]); assert_eq!(a, b); ```", - "type": "string" - } - } - }, - "query_lockup_total_at_height": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Nullable_Uint128", - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ], - "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" - } - } - }, - "query_user_lockup_total_at_height": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Nullable_Uint128", - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ], - "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": "StateResponse", - "type": "object", - "required": [ - "supported_pairs_list", - "total_incentives_share" - ], - "properties": { - "supported_pairs_list": { - "description": "Vector containing LP addresses for all the supported LP Pools", - "type": "array", - "items": { - "$ref": "#/definitions/PoolType" - } - }, - "total_incentives_share": { - "description": "Total NTRN incentives share", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - } - }, - "definitions": { - "PoolType": { - "type": "string", - "enum": [ - "USDC", - "ATOM" - ] - }, - "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": [ - "claimable_generator_ntrn_debt", - "lockup_infos", - "lockup_positions_index", - "ntrn_transferred", - "total_ntrn_rewards" - ], - "properties": { - "claimable_generator_ntrn_debt": { - "description": "NTRN tokens receivable as generator rewards that user can claim", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "lockup_infos": { - "description": "Lockup positions", - "type": "array", - "items": { - "$ref": "#/definitions/LockUpInfoResponse" - } - }, - "lockup_positions_index": { - "description": "Number of lockup positions the user is having", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "ntrn_transferred": { - "description": "NTRN tokens transferred to user", - "type": "boolean" - }, - "total_ntrn_rewards": { - "description": "Total NTRN tokens user received as rewards for participation in the lockdrop", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - } - }, - "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" - }, - "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 - } - ] - }, - "LockUpInfoResponse": { - "type": "object", - "required": [ - "astroport_lp_token", - "claimable_generator_astro_debt", - "claimable_generator_proxy_debt", - "duration", - "generator_ntrn_debt", - "generator_proxy_debt", - "lp_units_locked", - "ntrn_rewards", - "pool_type", - "unlock_timestamp", - "withdrawal_flag" - ], - "properties": { - "astroport_lp_token": { - "$ref": "#/definitions/Addr" - }, - "astroport_lp_transferred": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "astroport_lp_units": { - "description": "User's Astroport LP units, calculated as lp_units_locked (terraswap) / total LP units locked (terraswap) * Astroport LP units minted post migration", - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "claimable_generator_astro_debt": { - "description": "ASTRO tokens receivable as generator rewards that user can claim", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "claimable_generator_proxy_debt": { - "description": "Proxy tokens receivable as generator rewards that user can claim", - "allOf": [ - { - "$ref": "#/definitions/RestrictedVector_for_AssetInfo_and_Uint128" - } - ] - }, - "duration": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "generator_ntrn_debt": { - "description": "Generator NTRN tokens lockup received as generator rewards", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "generator_proxy_debt": { - "description": "Generator Proxy tokens lockup received as generator rewards", - "allOf": [ - { - "$ref": "#/definitions/RestrictedVector_for_AssetInfo_and_Uint128" - } - ] - }, - "lp_units_locked": { - "description": "Terraswap LP units locked by the user", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "ntrn_rewards": { - "description": "NTRN tokens received as rewards for participation in the lockdrop", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "pool_type": { - "description": "Terraswap LP token", - "allOf": [ - { - "$ref": "#/definitions/PoolType" - } - ] - }, - "unlock_timestamp": { - "description": "Timestamp beyond which this position can be unlocked", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "withdrawal_flag": { - "description": "Boolean value indicating if the user's has withdrawn funds post the only 1 withdrawal limit cutoff", - "type": "boolean" - } - } - }, - "PoolType": { - "type": "string", - "enum": [ - "USDC", - "ATOM" - ] - }, - "RestrictedVector_for_AssetInfo_and_Uint128": { - "description": "Vec wrapper for internal use. Some business logic relies on an order of this vector, thus it is forbidden to sort it or remove elements. New values can be added using .update() ONLY.", - "type": "array", - "items": { - "type": "array", - "items": [ - { - "$ref": "#/definitions/AssetInfo" - }, - { - "$ref": "#/definitions/Uint128" - } - ], - "maxItems": 2, - "minItems": 2 - } - }, - "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_with_lockups_list": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "UserInfoWithListResponse", - "type": "object", - "required": [ - "lockup_infos", - "lockup_positions_index", - "ntrn_transferred", - "total_ntrn_rewards" - ], - "properties": { - "lockup_infos": { - "description": "Lockup positions", - "type": "array", - "items": { - "$ref": "#/definitions/LockUpInfoSummary" - } - }, - "lockup_positions_index": { - "description": "Number of lockup positions the user is having", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "ntrn_transferred": { - "description": "NTRN tokens transferred to user", - "type": "boolean" - }, - "total_ntrn_rewards": { - "description": "Total NTRN tokens user received as rewards for participation in the lockdrop", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - } - }, - "definitions": { - "LockUpInfoSummary": { - "type": "object", - "required": [ - "duration", - "pool_type" - ], - "properties": { - "duration": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "pool_type": { - "$ref": "#/definitions/PoolType" - } - } - }, - "PoolType": { - "type": "string", - "enum": [ - "USDC", - "ATOM" - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - } - } -} diff --git a/contracts/lockdrop-pcl/schema/raw/execute.json b/contracts/lockdrop-pcl/schema/raw/execute.json deleted file mode 100644 index 97932b6d..00000000 --- a/contracts/lockdrop-pcl/schema/raw/execute.json +++ /dev/null @@ -1,450 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "oneOf": [ - { - "type": "object", - "required": [ - "increase_lockup_for" - ], - "properties": { - "increase_lockup_for": { - "type": "object", - "required": [ - "amount", - "duration", - "pool_type", - "user_address" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "duration": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "pool_type": { - "$ref": "#/definitions/PoolType" - }, - "user_address": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "receive" - ], - "properties": { - "receive": { - "$ref": "#/definitions/Cw20ReceiveMsg" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "increase_ntrn_incentives" - ], - "properties": { - "increase_ntrn_incentives": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "update_config" - ], - "properties": { - "update_config": { - "type": "object", - "required": [ - "new_config" - ], - "properties": { - "new_config": { - "$ref": "#/definitions/UpdateConfigMsg" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "set_token_info" - ], - "properties": { - "set_token_info": { - "type": "object", - "required": [ - "atom_token", - "generator", - "usdc_token" - ], - "properties": { - "atom_token": { - "type": "string" - }, - "generator": { - "type": "string" - }, - "usdc_token": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "withdraw_from_lockup" - ], - "properties": { - "withdraw_from_lockup": { - "type": "object", - "required": [ - "amount", - "duration", - "pool_type", - "user_address" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "duration": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "pool_type": { - "$ref": "#/definitions/PoolType" - }, - "user_address": { - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "claim_rewards_and_optionally_unlock" - ], - "properties": { - "claim_rewards_and_optionally_unlock": { - "type": "object", - "required": [ - "duration", - "pool_type", - "withdraw_lp_stake" - ], - "properties": { - "duration": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "pool_type": { - "$ref": "#/definitions/PoolType" - }, - "withdraw_lp_stake": { - "type": "boolean" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "Callbacks; only callable by the contract itself.", - "type": "object", - "required": [ - "callback" - ], - "properties": { - "callback": { - "$ref": "#/definitions/CallbackMsg" - } - }, - "additionalProperties": false - }, - { - "description": "ProposeNewOwner creates a proposal to change contract ownership. The validity period for the proposal is set in the `expires_in` variable.", - "type": "object", - "required": [ - "propose_new_owner" - ], - "properties": { - "propose_new_owner": { - "type": "object", - "required": [ - "expires_in", - "owner" - ], - "properties": { - "expires_in": { - "description": "The date after which this proposal expires", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "owner": { - "description": "Newly proposed contract owner", - "type": "string" - } - } - } - }, - "additionalProperties": false - }, - { - "description": "DropOwnershipProposal removes the existing offer to change contract ownership.", - "type": "object", - "required": [ - "drop_ownership_proposal" - ], - "properties": { - "drop_ownership_proposal": { - "type": "object" - } - }, - "additionalProperties": false - }, - { - "description": "Used to claim contract ownership.", - "type": "object", - "required": [ - "claim_ownership" - ], - "properties": { - "claim_ownership": { - "type": "object" - } - }, - "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 - } - ] - }, - "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" - }, - "CallbackMsg": { - "oneOf": [ - { - "type": "object", - "required": [ - "update_pool_on_dual_rewards_claim" - ], - "properties": { - "update_pool_on_dual_rewards_claim": { - "type": "object", - "required": [ - "pool_type", - "prev_ntrn_balance", - "prev_proxy_reward_balances" - ], - "properties": { - "pool_type": { - "$ref": "#/definitions/PoolType" - }, - "prev_ntrn_balance": { - "$ref": "#/definitions/Uint128" - }, - "prev_proxy_reward_balances": { - "type": "array", - "items": { - "$ref": "#/definitions/Asset" - } - } - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "withdraw_user_lockup_rewards_callback" - ], - "properties": { - "withdraw_user_lockup_rewards_callback": { - "type": "object", - "required": [ - "duration", - "pool_type", - "user_address", - "withdraw_lp_stake" - ], - "properties": { - "duration": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "pool_type": { - "$ref": "#/definitions/PoolType" - }, - "user_address": { - "$ref": "#/definitions/Addr" - }, - "withdraw_lp_stake": { - "type": "boolean" - } - } - } - }, - "additionalProperties": false - } - ] - }, - "Cw20ReceiveMsg": { - "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", - "type": "object", - "required": [ - "amount", - "msg", - "sender" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "msg": { - "$ref": "#/definitions/Binary" - }, - "sender": { - "type": "string" - } - }, - "additionalProperties": false - }, - "PoolType": { - "type": "string", - "enum": [ - "USDC", - "ATOM" - ] - }, - "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" - }, - "UpdateConfigMsg": { - "type": "object", - "properties": { - "auction_contract_address": { - "description": "Bootstrap Auction contract address", - "type": [ - "string", - "null" - ] - }, - "generator_address": { - "description": "Generator (Staking for dual rewards) contract address", - "type": [ - "string", - "null" - ] - } - } - } - } -} diff --git a/contracts/lockdrop-pcl/schema/raw/instantiate.json b/contracts/lockdrop-pcl/schema/raw/instantiate.json deleted file mode 100644 index f91cea0f..00000000 --- a/contracts/lockdrop-pcl/schema/raw/instantiate.json +++ /dev/null @@ -1,104 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "auction_contract", - "credits_contract", - "init_timestamp", - "lock_window", - "lockup_rewards_info", - "max_lock_duration", - "max_positions_per_user", - "min_lock_duration", - "token_info_manager", - "withdrawal_window" - ], - "properties": { - "auction_contract": { - "description": "Auction contract address", - "type": "string" - }, - "credits_contract": { - "description": "Credits contract address", - "type": "string" - }, - "init_timestamp": { - "description": "Timestamp when Contract will start accepting LP Token deposits", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "lock_window": { - "description": "Number of seconds during which lockup deposits will be accepted", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "lockup_rewards_info": { - "description": "Describes rewards coefficients for each lockup duration", - "type": "array", - "items": { - "$ref": "#/definitions/LockupRewardsInfo" - } - }, - "max_lock_duration": { - "description": "Max. no. of weeks allowed for lockup", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "max_positions_per_user": { - "description": "Max lockup positions a user can have", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "min_lock_duration": { - "description": "Min. no. of weeks allowed for lockup", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "owner": { - "description": "Account which can update config", - "type": [ - "string", - "null" - ] - }, - "token_info_manager": { - "description": "Account which can update token addresses and generator", - "type": "string" - }, - "withdrawal_window": { - "description": "Withdrawal Window Length :: Post the deposit window", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "definitions": { - "Decimal256": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal256(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 115792089237316195423570985008687907853269984665640564039457.584007913129639935 (which is (2^256 - 1) / 10^18)", - "type": "string" - }, - "LockupRewardsInfo": { - "type": "object", - "required": [ - "coefficient", - "duration" - ], - "properties": { - "coefficient": { - "$ref": "#/definitions/Decimal256" - }, - "duration": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - } - } -} diff --git a/contracts/lockdrop-pcl/schema/raw/migrate.json b/contracts/lockdrop-pcl/schema/raw/migrate.json deleted file mode 100644 index 87b18ea7..00000000 --- a/contracts/lockdrop-pcl/schema/raw/migrate.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "MigrateMsg", - "type": "object" -} diff --git a/contracts/lockdrop-pcl/schema/raw/query.json b/contracts/lockdrop-pcl/schema/raw/query.json deleted file mode 100644 index 9f3ee6a9..00000000 --- a/contracts/lockdrop-pcl/schema/raw/query.json +++ /dev/null @@ -1,193 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "oneOf": [ - { - "type": "object", - "required": [ - "config" - ], - "properties": { - "config": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "state" - ], - "properties": { - "state": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "pool" - ], - "properties": { - "pool": { - "type": "object", - "required": [ - "pool_type" - ], - "properties": { - "pool_type": { - "$ref": "#/definitions/PoolType" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "user_info" - ], - "properties": { - "user_info": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "user_info_with_lockups_list" - ], - "properties": { - "user_info_with_lockups_list": { - "type": "object", - "required": [ - "address" - ], - "properties": { - "address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "lock_up_info" - ], - "properties": { - "lock_up_info": { - "type": "object", - "required": [ - "duration", - "pool_type", - "user_address" - ], - "properties": { - "duration": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "pool_type": { - "$ref": "#/definitions/PoolType" - }, - "user_address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_user_lockup_total_at_height" - ], - "properties": { - "query_user_lockup_total_at_height": { - "type": "object", - "required": [ - "height", - "pool_type", - "user_address" - ], - "properties": { - "height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "pool_type": { - "$ref": "#/definitions/PoolType" - }, - "user_address": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "query_lockup_total_at_height" - ], - "properties": { - "query_lockup_total_at_height": { - "type": "object", - "required": [ - "height", - "pool_type" - ], - "properties": { - "height": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "pool_type": { - "$ref": "#/definitions/PoolType" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "PoolType": { - "type": "string", - "enum": [ - "USDC", - "ATOM" - ] - } - } -} diff --git a/contracts/lockdrop-pcl/schema/raw/response_to_config.json b/contracts/lockdrop-pcl/schema/raw/response_to_config.json deleted file mode 100644 index 946da11d..00000000 --- a/contracts/lockdrop-pcl/schema/raw/response_to_config.json +++ /dev/null @@ -1,146 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Config", - "type": "object", - "required": [ - "auction_contract", - "credits_contract", - "init_timestamp", - "lock_window", - "lockdrop_incentives", - "lockup_rewards_info", - "max_lock_duration", - "max_positions_per_user", - "min_lock_duration", - "owner", - "token_info_manager", - "withdrawal_window" - ], - "properties": { - "auction_contract": { - "description": "Bootstrap Auction contract address", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - }, - "credits_contract": { - "description": "Credits contract address", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - }, - "generator": { - "description": "Generator (Staking for dual rewards) contract address", - "anyOf": [ - { - "$ref": "#/definitions/Addr" - }, - { - "type": "null" - } - ] - }, - "init_timestamp": { - "description": "Timestamp when Contract will start accepting LP Token deposits", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "lock_window": { - "description": "Number of seconds during which lockup positions be accepted", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "lockdrop_incentives": { - "description": "Total NTRN lockdrop incentives to be distributed among the users", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "lockup_rewards_info": { - "description": "Describes rewards coefficients for each lockup duration", - "type": "array", - "items": { - "$ref": "#/definitions/LockupRewardsInfo" - } - }, - "max_lock_duration": { - "description": "Max. no. of weeks allowed for lockup", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "max_positions_per_user": { - "description": "Max lockup positions a user can have", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "min_lock_duration": { - "description": "Min. no. of weeks allowed for lockup", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "owner": { - "description": "Account which can update the config", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - }, - "token_info_manager": { - "description": "Account which can update the generator and token addresses", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] - }, - "withdrawal_window": { - "description": "Withdrawal Window Length :: Post the deposit window", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - }, - "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" - }, - "Decimal256": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal256(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 115792089237316195423570985008687907853269984665640564039457.584007913129639935 (which is (2^256 - 1) / 10^18)", - "type": "string" - }, - "LockupRewardsInfo": { - "type": "object", - "required": [ - "coefficient", - "duration" - ], - "properties": { - "coefficient": { - "$ref": "#/definitions/Decimal256" - }, - "duration": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/lockdrop-pcl/schema/raw/response_to_lock_up_info.json b/contracts/lockdrop-pcl/schema/raw/response_to_lock_up_info.json deleted file mode 100644 index 89a125d8..00000000 --- a/contracts/lockdrop-pcl/schema/raw/response_to_lock_up_info.json +++ /dev/null @@ -1,198 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "LockUpInfoResponse", - "type": "object", - "required": [ - "astroport_lp_token", - "claimable_generator_astro_debt", - "claimable_generator_proxy_debt", - "duration", - "generator_ntrn_debt", - "generator_proxy_debt", - "lp_units_locked", - "ntrn_rewards", - "pool_type", - "unlock_timestamp", - "withdrawal_flag" - ], - "properties": { - "astroport_lp_token": { - "$ref": "#/definitions/Addr" - }, - "astroport_lp_transferred": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "astroport_lp_units": { - "description": "User's Astroport LP units, calculated as lp_units_locked (terraswap) / total LP units locked (terraswap) * Astroport LP units minted post migration", - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "claimable_generator_astro_debt": { - "description": "ASTRO tokens receivable as generator rewards that user can claim", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "claimable_generator_proxy_debt": { - "description": "Proxy tokens receivable as generator rewards that user can claim", - "allOf": [ - { - "$ref": "#/definitions/RestrictedVector_for_AssetInfo_and_Uint128" - } - ] - }, - "duration": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "generator_ntrn_debt": { - "description": "Generator NTRN tokens lockup received as generator rewards", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "generator_proxy_debt": { - "description": "Generator Proxy tokens lockup received as generator rewards", - "allOf": [ - { - "$ref": "#/definitions/RestrictedVector_for_AssetInfo_and_Uint128" - } - ] - }, - "lp_units_locked": { - "description": "Terraswap LP units locked by the user", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "ntrn_rewards": { - "description": "NTRN tokens received as rewards for participation in the lockdrop", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "pool_type": { - "description": "Terraswap LP token", - "allOf": [ - { - "$ref": "#/definitions/PoolType" - } - ] - }, - "unlock_timestamp": { - "description": "Timestamp beyond which this position can be unlocked", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "withdrawal_flag": { - "description": "Boolean value indicating if the user's has withdrawn funds post the only 1 withdrawal limit cutoff", - "type": "boolean" - } - }, - "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" - }, - "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 - } - ] - }, - "PoolType": { - "type": "string", - "enum": [ - "USDC", - "ATOM" - ] - }, - "RestrictedVector_for_AssetInfo_and_Uint128": { - "description": "Vec wrapper for internal use. Some business logic relies on an order of this vector, thus it is forbidden to sort it or remove elements. New values can be added using .update() ONLY.", - "type": "array", - "items": { - "type": "array", - "items": [ - { - "$ref": "#/definitions/AssetInfo" - }, - { - "$ref": "#/definitions/Uint128" - } - ], - "maxItems": 2, - "minItems": 2 - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/lockdrop-pcl/schema/raw/response_to_pool.json b/contracts/lockdrop-pcl/schema/raw/response_to_pool.json deleted file mode 100644 index 865a4fe2..00000000 --- a/contracts/lockdrop-pcl/schema/raw/response_to_pool.json +++ /dev/null @@ -1,142 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "PoolInfo", - "type": "object", - "required": [ - "amount_in_lockups", - "generator_ntrn_per_share", - "generator_proxy_per_share", - "incentives_share", - "is_staked", - "lp_token", - "weighted_amount" - ], - "properties": { - "amount_in_lockups": { - "$ref": "#/definitions/Uint128" - }, - "generator_ntrn_per_share": { - "description": "Ratio of Generator NTRN rewards accured to astroport pool share", - "allOf": [ - { - "$ref": "#/definitions/Decimal" - } - ] - }, - "generator_proxy_per_share": { - "description": "Ratio of Generator Proxy rewards accured to astroport pool share", - "allOf": [ - { - "$ref": "#/definitions/RestrictedVector_for_AssetInfo_and_Decimal" - } - ] - }, - "incentives_share": { - "description": "Share of total NTRN incentives allocated to this pool", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "is_staked": { - "description": "Boolean value indicating if the LP Tokens are staked with the Generator contract or not", - "type": "boolean" - }, - "lp_token": { - "$ref": "#/definitions/Addr" - }, - "weighted_amount": { - "description": "Weighted LP Token balance used to calculate NTRN rewards a particular user can claim", - "allOf": [ - { - "$ref": "#/definitions/Uint256" - } - ] - } - }, - "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" - }, - "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" - }, - "RestrictedVector_for_AssetInfo_and_Decimal": { - "description": "Vec wrapper for internal use. Some business logic relies on an order of this vector, thus it is forbidden to sort it or remove elements. New values can be added using .update() ONLY.", - "type": "array", - "items": { - "type": "array", - "items": [ - { - "$ref": "#/definitions/AssetInfo" - }, - { - "$ref": "#/definitions/Decimal" - } - ], - "maxItems": 2, - "minItems": 2 - } - }, - "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" - }, - "Uint256": { - "description": "An implementation of u256 that is using strings for JSON encoding/decoding, such that the full u256 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 out of primitive uint types or `new` to provide big endian bytes:\n\n``` # use cosmwasm_std::Uint256; let a = Uint256::from(258u128); let b = Uint256::new([ 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 1u8, 2u8, ]); assert_eq!(a, b); ```", - "type": "string" - } - } -} diff --git a/contracts/lockdrop-pcl/schema/raw/response_to_query_lockup_total_at_height.json b/contracts/lockdrop-pcl/schema/raw/response_to_query_lockup_total_at_height.json deleted file mode 100644 index 2eaf6e96..00000000 --- a/contracts/lockdrop-pcl/schema/raw/response_to_query_lockup_total_at_height.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Nullable_Uint128", - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ], - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/lockdrop-pcl/schema/raw/response_to_query_user_lockup_total_at_height.json b/contracts/lockdrop-pcl/schema/raw/response_to_query_user_lockup_total_at_height.json deleted file mode 100644 index 2eaf6e96..00000000 --- a/contracts/lockdrop-pcl/schema/raw/response_to_query_user_lockup_total_at_height.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Nullable_Uint128", - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ], - "definitions": { - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/lockdrop-pcl/schema/raw/response_to_state.json b/contracts/lockdrop-pcl/schema/raw/response_to_state.json deleted file mode 100644 index cdb65ce0..00000000 --- a/contracts/lockdrop-pcl/schema/raw/response_to_state.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "StateResponse", - "type": "object", - "required": [ - "supported_pairs_list", - "total_incentives_share" - ], - "properties": { - "supported_pairs_list": { - "description": "Vector containing LP addresses for all the supported LP Pools", - "type": "array", - "items": { - "$ref": "#/definitions/PoolType" - } - }, - "total_incentives_share": { - "description": "Total NTRN incentives share", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - } - }, - "definitions": { - "PoolType": { - "type": "string", - "enum": [ - "USDC", - "ATOM" - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/lockdrop-pcl/schema/raw/response_to_user_info.json b/contracts/lockdrop-pcl/schema/raw/response_to_user_info.json deleted file mode 100644 index a15339ce..00000000 --- a/contracts/lockdrop-pcl/schema/raw/response_to_user_info.json +++ /dev/null @@ -1,243 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "UserInfoResponse", - "type": "object", - "required": [ - "claimable_generator_ntrn_debt", - "lockup_infos", - "lockup_positions_index", - "ntrn_transferred", - "total_ntrn_rewards" - ], - "properties": { - "claimable_generator_ntrn_debt": { - "description": "NTRN tokens receivable as generator rewards that user can claim", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "lockup_infos": { - "description": "Lockup positions", - "type": "array", - "items": { - "$ref": "#/definitions/LockUpInfoResponse" - } - }, - "lockup_positions_index": { - "description": "Number of lockup positions the user is having", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "ntrn_transferred": { - "description": "NTRN tokens transferred to user", - "type": "boolean" - }, - "total_ntrn_rewards": { - "description": "Total NTRN tokens user received as rewards for participation in the lockdrop", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - } - }, - "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" - }, - "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 - } - ] - }, - "LockUpInfoResponse": { - "type": "object", - "required": [ - "astroport_lp_token", - "claimable_generator_astro_debt", - "claimable_generator_proxy_debt", - "duration", - "generator_ntrn_debt", - "generator_proxy_debt", - "lp_units_locked", - "ntrn_rewards", - "pool_type", - "unlock_timestamp", - "withdrawal_flag" - ], - "properties": { - "astroport_lp_token": { - "$ref": "#/definitions/Addr" - }, - "astroport_lp_transferred": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "astroport_lp_units": { - "description": "User's Astroport LP units, calculated as lp_units_locked (terraswap) / total LP units locked (terraswap) * Astroport LP units minted post migration", - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "claimable_generator_astro_debt": { - "description": "ASTRO tokens receivable as generator rewards that user can claim", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "claimable_generator_proxy_debt": { - "description": "Proxy tokens receivable as generator rewards that user can claim", - "allOf": [ - { - "$ref": "#/definitions/RestrictedVector_for_AssetInfo_and_Uint128" - } - ] - }, - "duration": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "generator_ntrn_debt": { - "description": "Generator NTRN tokens lockup received as generator rewards", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "generator_proxy_debt": { - "description": "Generator Proxy tokens lockup received as generator rewards", - "allOf": [ - { - "$ref": "#/definitions/RestrictedVector_for_AssetInfo_and_Uint128" - } - ] - }, - "lp_units_locked": { - "description": "Terraswap LP units locked by the user", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "ntrn_rewards": { - "description": "NTRN tokens received as rewards for participation in the lockdrop", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "pool_type": { - "description": "Terraswap LP token", - "allOf": [ - { - "$ref": "#/definitions/PoolType" - } - ] - }, - "unlock_timestamp": { - "description": "Timestamp beyond which this position can be unlocked", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "withdrawal_flag": { - "description": "Boolean value indicating if the user's has withdrawn funds post the only 1 withdrawal limit cutoff", - "type": "boolean" - } - } - }, - "PoolType": { - "type": "string", - "enum": [ - "USDC", - "ATOM" - ] - }, - "RestrictedVector_for_AssetInfo_and_Uint128": { - "description": "Vec wrapper for internal use. Some business logic relies on an order of this vector, thus it is forbidden to sort it or remove elements. New values can be added using .update() ONLY.", - "type": "array", - "items": { - "type": "array", - "items": [ - { - "$ref": "#/definitions/AssetInfo" - }, - { - "$ref": "#/definitions/Uint128" - } - ], - "maxItems": 2, - "minItems": 2 - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/lockdrop-pcl/schema/raw/response_to_user_info_with_lockups_list.json b/contracts/lockdrop-pcl/schema/raw/response_to_user_info_with_lockups_list.json deleted file mode 100644 index 0809050c..00000000 --- a/contracts/lockdrop-pcl/schema/raw/response_to_user_info_with_lockups_list.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "UserInfoWithListResponse", - "type": "object", - "required": [ - "lockup_infos", - "lockup_positions_index", - "ntrn_transferred", - "total_ntrn_rewards" - ], - "properties": { - "lockup_infos": { - "description": "Lockup positions", - "type": "array", - "items": { - "$ref": "#/definitions/LockUpInfoSummary" - } - }, - "lockup_positions_index": { - "description": "Number of lockup positions the user is having", - "type": "integer", - "format": "uint32", - "minimum": 0.0 - }, - "ntrn_transferred": { - "description": "NTRN tokens transferred to user", - "type": "boolean" - }, - "total_ntrn_rewards": { - "description": "Total NTRN tokens user received as rewards for participation in the lockdrop", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - } - }, - "definitions": { - "LockUpInfoSummary": { - "type": "object", - "required": [ - "duration", - "pool_type" - ], - "properties": { - "duration": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "pool_type": { - "$ref": "#/definitions/PoolType" - } - } - }, - "PoolType": { - "type": "string", - "enum": [ - "USDC", - "ATOM" - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/contracts/lockdrop-pcl/src/contract.rs b/contracts/lockdrop-pcl/src/contract.rs deleted file mode 100644 index 50aee229..00000000 --- a/contracts/lockdrop-pcl/src/contract.rs +++ /dev/null @@ -1,1853 +0,0 @@ -use std::cmp::min; -use std::convert::TryInto; -use std::str::FromStr; - -use astroport::asset::{Asset, AssetInfo}; -use astroport::common::{claim_ownership, drop_ownership_proposal, propose_new_owner}; -use astroport::cosmwasm_ext::IntegerToDecimal; -use astroport::generator::{ - ExecuteMsg as GenExecuteMsg, PendingTokenResponse, QueryMsg as GenQueryMsg, RewardInfoResponse, -}; -use astroport::restricted_vector::RestrictedVector; -use astroport::DecimalCheckedOps; -use astroport_periphery::utils::Decimal256CheckedOps; -use cosmwasm_std::{ - attr, coins, entry_point, from_binary, to_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, - Decimal, Decimal256, Deps, DepsMut, Env, MessageInfo, Order, Response, StdError, StdResult, - Uint128, Uint256, WasmMsg, -}; -use cw2::set_contract_version; -use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg, Cw20ReceiveMsg}; - -use crate::raw_queries::{raw_balance, raw_generator_deposit}; -use astroport_periphery::lockdrop::{ - CallbackMsg, Config, Cw20HookMsg, ExecuteMsg, InstantiateMsg, LockUpInfoResponse, - LockUpInfoSummary, LockupInfoV2, MigrateMsg, PoolInfo, PoolType, QueryMsg, State, - StateResponse, UpdateConfigMsg, UserInfoResponse, UserInfoWithListResponse, -}; - -use crate::state::{ - CompatibleLoader, ASSET_POOLS, CONFIG, LOCKUP_INFO, OWNERSHIP_PROPOSAL, STATE, - TOTAL_USER_LOCKUP_AMOUNT, USER_INFO, -}; - -const AIRDROP_REWARDS_MULTIPLIER: &str = "1.0"; - -pub const UNTRN_DENOM: &str = "untrn"; - -/// Contract name that is used for migration. -const CONTRACT_NAME: &str = "neutron_lockdrop"; -/// Contract version that is used for migration. -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -/// Minimum lockup positions for user. -const MIN_POSITIONS_PER_USER: u32 = 1; - -/// Creates a new contract with the specified parameters packed in the `msg` variable. -/// Returns a [`Response`] with the specified attributes if the operation was successful, or a [`ContractError`] if the contract was not created -/// ## Params -/// * **deps** is an object of type [`DepsMut`]. -/// -/// * **env** is an object of type [`Env`]. -/// -/// * **info** is an object of type [`MessageInfo`]. -/// -/// * **msg** is a message of type [`InstantiateMsg`] which contains the parameters used for creating the contract. -#[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)?; - // CHECK :: init_timestamp needs to be valid - if env.block.time.seconds() > msg.init_timestamp { - return Err(StdError::generic_err(format!( - "Invalid init_timestamp. Current timestamp : {}", - env.block.time.seconds() - ))); - } - - // CHECK :: min_lock_duration , max_lock_duration need to be valid (min_lock_duration < max_lock_duration) - if msg.max_lock_duration < msg.min_lock_duration || msg.min_lock_duration == 0u64 { - return Err(StdError::generic_err("Invalid Lockup durations")); - } - - if msg.lockup_rewards_info.is_empty() { - return Err(StdError::generic_err("Invalid lockup rewards info")); - } - for lr_info in &msg.lockup_rewards_info { - if lr_info.duration == 0 { - return Err(StdError::generic_err( - "Invalid Lockup info rewards duration", - )); - } - } - - if msg.max_positions_per_user < MIN_POSITIONS_PER_USER { - return Err(StdError::generic_err( - "The maximum number of locked positions per user cannot be lower than a minimum acceptable value." - )); - } - - let config = Config { - owner: msg - .owner - .map(|v| deps.api.addr_validate(&v)) - .transpose()? - .unwrap_or(info.sender), - token_info_manager: deps.api.addr_validate(&msg.token_info_manager)?, - credits_contract: deps.api.addr_validate(&msg.credits_contract)?, - auction_contract: deps.api.addr_validate(&msg.auction_contract)?, - generator: None, - init_timestamp: msg.init_timestamp, - lock_window: msg.lock_window, - withdrawal_window: msg.withdrawal_window, - min_lock_duration: msg.min_lock_duration, - max_lock_duration: msg.max_lock_duration, - lockdrop_incentives: Uint128::zero(), - max_positions_per_user: msg.max_positions_per_user, - lockup_rewards_info: msg.lockup_rewards_info, - }; - - CONFIG.save(deps.storage, &config)?; - STATE.save(deps.storage, &State::default())?; - Ok(Response::default()) -} - -/// ## Description -/// Exposes all the execute functions available in the contract. -/// ## Params -/// * **deps** is an object of type [`DepsMut`]. -/// -/// * **env** is an object of type [`Env`]. -/// -/// * **info** is an object of type [`MessageInfo`]. -/// -/// * **msg** is an object of type [`ExecuteMsg`]. -/// -/// ## Execute messages -/// -/// * **ExecuteMsg::Receive(msg)** Parse incoming messages from the cNTRN token. -/// -/// * **ExecuteMsg::UpdateConfig { new_config }** Admin function to update configuration parameters. -/// -/// * **ExecuteMsg::InitializePool { -/// pool_type, -/// incentives_share, -/// }** Facilitates addition of new Pool (axlrUSDC/NTRN or ATOM/NTRN) whose LP tokens can then be locked in the lockdrop contract. -/// -/// * **ExecuteMsg::ClaimRewardsAndOptionallyUnlock { -/// terraswap_lp_token, -/// duration, -/// withdraw_lp_stake, -/// }** Claims user Rewards for a particular Lockup position. -/// -/// * **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. -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - match msg { - ExecuteMsg::Receive(msg) => receive_cw20(deps, env, info, msg), - ExecuteMsg::ClaimRewardsAndOptionallyUnlock { - pool_type, - duration, - withdraw_lp_stake, - } => handle_claim_rewards_and_unlock_for_lockup( - deps, - env, - info, - pool_type, - duration, - withdraw_lp_stake, - ), - ExecuteMsg::Callback(msg) => _handle_callback(deps, env, info, msg), - ExecuteMsg::ProposeNewOwner { owner, expires_in } => { - let config: Config = CONFIG.load(deps.storage)?; - propose_new_owner( - deps, - info, - env, - owner, - expires_in, - config.owner, - OWNERSHIP_PROPOSAL, - ) - } - ExecuteMsg::DropOwnershipProposal {} => { - let config: Config = CONFIG.load(deps.storage)?; - drop_ownership_proposal(deps, info, config.owner, OWNERSHIP_PROPOSAL) - } - 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) - })?; - Ok(()) - }) - } - ExecuteMsg::IncreaseNTRNIncentives {} => handle_increasing_ntrn_incentives(deps, env, info), - ExecuteMsg::IncreaseLockupFor { - user_address, - pool_type, - amount, - duration, - } => handle_increase_lockup(deps, env, info, user_address, pool_type, duration, amount), - ExecuteMsg::WithdrawFromLockup { - user_address, - pool_type, - duration, - amount, - } => { - handle_withdraw_from_lockup(deps, env, info, user_address, pool_type, duration, amount) - } - ExecuteMsg::UpdateConfig { new_config } => handle_update_config(deps, info, new_config), - ExecuteMsg::SetTokenInfo { - usdc_token, - atom_token, - generator, - } => handle_set_token_info(deps, env, info, usdc_token, atom_token, generator), - } -} - -/// Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template. -/// If the template is not found in the received message, then an [`StdError`] is returned, -/// otherwise it returns the [`Response`] with the specified attributes if the operation was successful. -/// ## Params -/// * **deps** is an object of type [`DepsMut`]. -/// -/// * **env** is an object of type [`Env`]. -/// -/// * **info** is an object of type [`MessageInfo`]. -/// -/// * **cw20_msg** is an object of type [`Cw20ReceiveMsg`]. This is the CW20 message that has to be processed. -pub fn receive_cw20( - deps: DepsMut, - env: Env, - info: MessageInfo, - cw20_msg: Cw20ReceiveMsg, -) -> Result { - let cw20_sender_addr = deps.api.addr_validate(&cw20_msg.sender)?; - // CHECK :: Tokens sent > 0 - if cw20_msg.amount == Uint128::zero() { - return Err(StdError::generic_err( - "Number of tokens sent should be > 0 ", - )); - } - - match from_binary(&cw20_msg.msg)? { - Cw20HookMsg::InitializePool { - pool_type, - incentives_share, - } => handle_initialize_pool( - deps, - env, - info, - pool_type, - cw20_sender_addr, - incentives_share, - cw20_msg.amount, - ), - } -} - -/// ## Description -/// Handles callback. Returns a [`ContractError`] on failure. -/// ## Params -/// * **deps** is an object of type [`DepsMut`]. -/// -/// * **env** is an object of type [`Env`]. -/// -/// * **info** is an object of type [`MessageInfo`]. -/// -/// * **msg** is an object of type [`CallbackMsg`]. -fn _handle_callback( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: CallbackMsg, -) -> StdResult { - // Only the contract itself can call callbacks - if info.sender != env.contract.address { - return Err(StdError::generic_err( - "callbacks cannot be invoked externally", - )); - } - match msg { - CallbackMsg::UpdatePoolOnDualRewardsClaim { - pool_type, - prev_ntrn_balance, - prev_proxy_reward_balances, - } => update_pool_on_dual_rewards_claim( - deps, - env, - pool_type, - prev_ntrn_balance, - prev_proxy_reward_balances, - ), - CallbackMsg::WithdrawUserLockupRewardsCallback { - pool_type, - user_address, - duration, - withdraw_lp_stake, - } => callback_withdraw_user_rewards_for_lockup_optional_withdraw( - deps, - env, - pool_type, - user_address, - duration, - withdraw_lp_stake, - ), - } -} - -/// Exposes all the queries available in the contract. -/// ## Params -/// * **deps** is an object of type [`Deps`]. -/// -/// * **_env** is an object of type [`Env`]. -/// -/// * **msg** is an object of type [`QueryMsg`]. -/// -/// ## Queries -/// * **QueryMsg::Config {}** Returns the config info. -/// -/// * **QueryMsg::State {}** Returns the contract's state info. -/// -/// * **QueryMsg::Pool { terraswap_lp_token }** Returns info regarding a certain supported LP token pool. -/// -/// * **QueryMsg::UserInfo { address }** Returns info regarding a user (total NTRN rewards, list of lockup positions). -/// -/// * **QueryMsg::UserInfoWithLockupsList { address }** Returns info regarding a user with lockups. -/// -/// * **QueryMsg::LockUpInfo { -/// user_address, -/// terraswap_lp_token, -/// duration, -/// }** Returns info regarding a particular lockup position with a given duration and identifer for the LP tokens locked. -/// -/// * **QueryMsg::PendingAssetReward { -/// user_address, -/// terraswap_lp_token, -/// duration, -/// }** Returns the amount of pending asset rewards for the specified recipient and for a specific lockup position. -/// -/// * **QueryUserLockupTotalAtHeight { -/// pool_type: PoolType, -/// user_address: String, -/// height: u64, -/// }** Returns locked amount of LP tokens for the specified user for the specified pool at a specific height. -/// -/// * **QueryLockupTotalAtHeight { -/// pool_type: PoolType, -/// height: u64, -/// }** Returns a total amount of LP tokens for the specified pool at a specific height. -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Config {} => to_binary(&CONFIG.load(deps.storage)?), - QueryMsg::State {} => to_binary(&query_state(deps)?), - QueryMsg::Pool { pool_type } => to_binary(&query_pool(deps, pool_type)?), - QueryMsg::UserInfo { address } => to_binary(&query_user_info(deps, env, address)?), - QueryMsg::UserInfoWithLockupsList { address } => { - to_binary(&query_user_info_with_lockups_list(deps, env, address)?) - } - QueryMsg::LockUpInfo { - user_address, - pool_type, - duration, - } => to_binary(&query_lockup_info( - deps, - &env, - &user_address, - pool_type, - duration, - )?), - QueryMsg::QueryUserLockupTotalAtHeight { - pool_type, - user_address, - height, - } => to_binary(&query_user_lockup_total_at_height( - deps, - pool_type, - deps.api.addr_validate(&user_address)?, - height, - )?), - QueryMsg::QueryLockupTotalAtHeight { pool_type, height } => { - to_binary(&query_lockup_total_at_height(deps, pool_type, height)?) - } - } -} - -/// Used for contract migration. Returns a default object of type [`Response`]. -/// ## Params -/// * **_deps** is an object of type [`DepsMut`]. -/// -/// * **_env** is an object of type [`Env`]. -/// -/// * **_msg** is an object of type [`MigrateMsg`]. -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult { - Ok(Response::default()) -} - -/// Admin function to update Configuration parameters. Returns a default object of type [`Response`]. -/// ## Params -/// * **deps** is an object of type [`DepsMut`]. -/// -/// * **info** is an object of type [`MessageInfo`]. -/// -/// * **new_config** is an object of type [`UpdateConfigMsg`]. Same as UpdateConfigMsg struct -pub fn handle_update_config( - deps: DepsMut, - info: MessageInfo, - new_config: UpdateConfigMsg, -) -> StdResult { - let mut config = CONFIG.load(deps.storage)?; - let mut attributes = vec![attr("action", "update_config")]; - - // CHECK :: Only owner can call this function - if info.sender != config.owner { - return Err(StdError::generic_err("Unauthorized")); - } - - if let Some(auction) = new_config.auction_contract_address { - config.auction_contract = deps.api.addr_validate(&auction)?; - attributes.push(attr("auction_contract", auction)); - }; - - if let Some(generator) = new_config.generator_address { - // If generator is set, we check is any LP tokens are currently staked before updating generator address - if config.generator.is_some() { - for pool_type in ASSET_POOLS - .keys(deps.storage, None, None, Order::Ascending) - .collect::, StdError>>()? - { - let pool_info = ASSET_POOLS.load(deps.storage, pool_type)?; - if pool_info.is_staked { - return Err(StdError::generic_err(format!( - "{:?} astro LP tokens already staked. Unstake them before updating generator", - pool_type - ))); - } - } - } - - config.generator = Some(deps.api.addr_validate(&generator)?); - attributes.push(attr("new_generator", generator)) - } - - CONFIG.save(deps.storage, &config)?; - Ok(Response::new().add_attributes(attributes)) -} - -pub fn handle_set_token_info( - deps: DepsMut, - env: Env, - info: MessageInfo, - usdc_token: String, - atom_token: String, - generator: String, -) -> Result { - let mut config = CONFIG.load(deps.storage)?; - - // CHECK :: Only owner and token info manager can call this function - if info.sender != config.owner && info.sender != config.token_info_manager { - return Err(StdError::generic_err("Unauthorized")); - } - - // POOL INFO :: Initialize new pool - let pool_info = PoolInfo { - lp_token: deps.api.addr_validate(&atom_token)?, - amount_in_lockups: Default::default(), - incentives_share: Uint128::zero(), - weighted_amount: Default::default(), - generator_ntrn_per_share: Default::default(), - generator_proxy_per_share: RestrictedVector::default(), - is_staked: false, - }; - ASSET_POOLS.save(deps.storage, PoolType::ATOM, &pool_info, env.block.height)?; - - // POOL INFO :: Initialize new pool - let pool_info = PoolInfo { - lp_token: deps.api.addr_validate(&usdc_token)?, - amount_in_lockups: Default::default(), - incentives_share: Uint128::zero(), - weighted_amount: Default::default(), - generator_ntrn_per_share: Default::default(), - generator_proxy_per_share: RestrictedVector::default(), - is_staked: false, - }; - ASSET_POOLS.save(deps.storage, PoolType::USDC, &pool_info, env.block.height)?; - - config.generator = Some(deps.api.addr_validate(&generator)?); - CONFIG.save(deps.storage, &config)?; - - let attributes = vec![ - attr("action", "update_config"), - attr("usdc_token", usdc_token), - attr("atom_token", atom_token), - attr("generator", generator), - ]; - - Ok(Response::new().add_attributes(attributes)) -} - -/// Facilitates increasing NTRN incentives that are to be distributed as Lockdrop participation reward. Returns a default object of type [`Response`]. -/// ## Params -/// * **deps** is an object of type [`DepsMut`]. -/// -/// * **env** is an object of type [`Env`]. -/// -/// * **info** is an object of type [`MessageInfo`]. -pub fn handle_increasing_ntrn_incentives( - deps: DepsMut, - env: Env, - info: MessageInfo, -) -> Result { - let mut config = CONFIG.load(deps.storage)?; - - if env.block.time.seconds() - >= config.init_timestamp + config.lock_window + config.withdrawal_window - { - return Err(StdError::generic_err("Lock window is closed")); - }; - - let incentive = info.funds.iter().find(|c| c.denom == UNTRN_DENOM); - let amount = if let Some(coin) = incentive { - coin.amount - } else { - return Err(StdError::generic_err(format!( - "{} is not found", - UNTRN_DENOM - ))); - }; - // Anyone can increase ntrn incentives - config.lockdrop_incentives = config.lockdrop_incentives.checked_add(amount)?; - - CONFIG.save(deps.storage, &config)?; - Ok(Response::new() - .add_attribute("action", "ntrn_incentives_increased") - .add_attribute("amount", amount)) -} - -/// Admin function to initialize new LP Pool. Returns a default object of type [`Response`]. -/// ## Params -/// * **deps** is an object of type [`DepsMut`]. -/// -/// * **env** is an object of type [`Env`]. -/// -/// * **info** is an object of type [`MessageInfo`]. -/// -/// * **pool_type** is an object of type [`PoolType`]. LiquidPool type - USDC or ATOM -/// -/// * **cw20_sender_addr** is an object of type [`Addr`]. Address caller cw20 contract -/// -/// * **incentives_share** is an object of type [`u64`]. Parameter defining share of total NTRN incentives are allocated for this pool -/// -/// * **amount** amount of LP tokens of `pool_type` to be staked in the Generator Contract. -pub fn handle_initialize_pool( - deps: DepsMut, - env: Env, - info: MessageInfo, - pool_type: PoolType, - cw20_sender_addr: Addr, - incentives_share: Uint128, - amount: Uint128, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - let mut state = STATE.load(deps.storage)?; - - // CHECK ::: Only auction can call this function - if cw20_sender_addr != config.auction_contract { - return Err(StdError::generic_err("Unauthorized")); - } - - // CHECK ::: Is LP Token Pool initialized - let mut pool_info = ASSET_POOLS.load(deps.storage, pool_type)?; - - if info.sender != pool_info.lp_token { - return Err(StdError::generic_err("Unknown cw20 token address")); - } - - // Set Pool Incentives - pool_info.incentives_share = incentives_share; - - let stake_msgs = stake_messages( - config, - env.block.height + 1u64, - pool_info.lp_token.clone(), - amount, - )?; - pool_info.is_staked = true; - - ASSET_POOLS.save(deps.storage, pool_type, &pool_info, env.block.height)?; - - state.total_incentives_share = state.total_incentives_share.checked_add(incentives_share)?; - STATE.save(deps.storage, &state)?; - - Ok(Response::new() - .add_messages(stake_msgs) - .add_attributes(vec![ - attr("action", "initialize_pool"), - attr("pool_type", format!("{:?}", pool_type)), - attr("lp_token", info.sender), - attr("lp_amount", amount), - attr("incentives_share", incentives_share.to_string()), - ])) -} - -fn stake_messages( - config: Config, - height: u64, - lp_token_address: Addr, - amount: Uint128, -) -> StdResult> { - let mut cosmos_msgs = vec![]; - - let generator = config - .generator - .as_ref() - .ok_or_else(|| StdError::generic_err("Generator address hasn't set yet!"))?; - - // TODO: why do we need allowance here, when next message is "send" to a pool - cosmos_msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: lp_token_address.to_string(), - funds: vec![], - msg: to_binary(&Cw20ExecuteMsg::IncreaseAllowance { - spender: generator.to_string(), - amount, - expires: Some(cw20::Expiration::AtHeight(height)), - })?, - })); - - cosmos_msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: lp_token_address.to_string(), - funds: vec![], - msg: to_binary(&Cw20ExecuteMsg::Send { - contract: generator.to_string(), - msg: to_binary(&astroport::generator::Cw20HookMsg::Deposit {})?, - amount, - })?, - })); - - Ok(cosmos_msgs) -} - -/// Hook function to increase Lockup position size when any of the supported LP Tokens are sent to the contract by the user. Returns a default object of type [`Response`]. -/// ## Params -/// * **deps** is an object of type [`DepsMut`]. -/// -/// * **env** is an object of type [`Env`]. -/// -/// * **info** is an object of type [`MessageInfo`]. -/// -/// * **user_address_raw** is an object of type [`Addr`]. User we increase lockup position for -/// -/// * **pool_type** is an object of type [`PoolType`]. LiquidPool type - USDC or ATOM -/// -/// * **duration** is an object of type [`u64`]. Number of seconds the LP token is locked for (lockup period begins post the withdrawal window closure). -/// -/// * **amount** is an object of type [`Uint128`]. Number of LP tokens sent by the user. -pub fn handle_increase_lockup( - deps: DepsMut, - env: Env, - info: MessageInfo, - user_address_raw: String, - pool_type: PoolType, - duration: u64, - amount: Uint128, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - if info.sender != config.auction_contract { - return Err(StdError::generic_err("Unauthorized")); - } - - if env.block.time.seconds() >= config.init_timestamp + config.lock_window { - return Err(StdError::generic_err("Lock window is closed")); - }; - - let user_address = deps.api.addr_validate(&user_address_raw)?; - - if !config - .lockup_rewards_info - .iter() - .any(|i| i.duration == duration) - { - return Err(StdError::generic_err("invalid duration")); - } - - // CHECK ::: LP Token supported or not ? - let mut pool_info = ASSET_POOLS.load(deps.storage, pool_type)?; - let mut user_info = USER_INFO - .may_load(deps.storage, &user_address)? - .unwrap_or_default(); - - // CHECK :: Valid Lockup Duration - if duration > config.max_lock_duration || duration < config.min_lock_duration { - return Err(StdError::generic_err(format!( - "Lockup duration needs to be between {} and {}", - config.min_lock_duration, config.max_lock_duration - ))); - } - - pool_info.weighted_amount = pool_info - .weighted_amount - .checked_add(calculate_weight(amount, duration, &config)?)?; - pool_info.amount_in_lockups = pool_info.amount_in_lockups.checked_add(amount)?; - - let lockup_key = (pool_type, &user_address, duration); - - let lockup_info = - match LOCKUP_INFO.compatible_may_load(deps.as_ref(), lockup_key, &config.generator)? { - Some(mut li) => { - li.lp_units_locked = li.lp_units_locked.checked_add(amount)?; - li - } - None => { - if config.max_positions_per_user == user_info.lockup_positions_index { - return Err(StdError::generic_err(format!( - "Users can only have max {} lockup positions", - config.max_positions_per_user - ))); - } - // Update number of lockup positions the user is having - user_info.lockup_positions_index += 1; - - LockupInfoV2 { - lp_units_locked: amount, - astroport_lp_transferred: None, - ntrn_rewards: Uint128::zero(), - unlock_timestamp: config.init_timestamp - + config.lock_window - + duration - + config.withdrawal_window, - generator_ntrn_debt: Uint128::zero(), - generator_proxy_debt: Default::default(), - withdrawal_flag: false, - } - } - }; - - // SAVE UPDATED STATE - LOCKUP_INFO.save(deps.storage, lockup_key, &lockup_info)?; - - TOTAL_USER_LOCKUP_AMOUNT.update( - deps.storage, - (pool_type, &user_address), - env.block.height, - |lockup_amount| -> StdResult { - if let Some(la) = lockup_amount { - Ok(la.checked_add(amount)?) - } else { - Ok(amount) - } - }, - )?; - - ASSET_POOLS.save(deps.storage, pool_type, &pool_info, env.block.height)?; - USER_INFO.save(deps.storage, &user_address, &user_info)?; - - Ok(Response::new().add_attributes(vec![ - attr("action", "increase_lockup_position"), - attr("pool_type", format!("{:?}", pool_type)), - attr("user", user_address), - attr("duration", duration.to_string()), - attr("amount", amount), - ])) -} - -/// Withdraws LP Tokens from an existing Lockup position. Returns a default object of type [`Response`]. -/// ## Params -/// * **deps** is an object of type [`DepsMut`]. -/// -/// * **env** is an object of type [`Env`]. -/// -/// * **info** is an object of type [`MessageInfo`]. -/// -/// * **pool_type** is an object of type [`PoolType`]. LiquidPool type - USDC or ATOM -/// -/// * **duration** is an object of type [`u64`]. Duration of the lockup position from which withdrawal is to be made. -/// -/// * **amount** is an object of type [`Uint128`]. Number of LP tokens to be withdrawn. -pub fn handle_withdraw_from_lockup( - deps: DepsMut, - env: Env, - info: MessageInfo, - user_address: String, - pool_type: PoolType, - duration: u64, - amount: Uint128, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - - if info.sender != config.auction_contract { - return Err(StdError::generic_err("Unauthorized")); - } - - if env.block.time.seconds() - >= config.init_timestamp + config.lock_window + config.withdrawal_window - { - return Err(StdError::generic_err("Withdrawal window is closed")); - }; - - // CHECK :: Valid Withdraw Amount - if amount.is_zero() { - return Err(StdError::generic_err("Invalid withdrawal request")); - } - - let mut pool_info = ASSET_POOLS.load(deps.storage, pool_type)?; - - let user_address = deps.api.addr_validate(&user_address)?; - - // Retrieve Lockup position - let lockup_key = (pool_type, &user_address, duration); - let mut lockup_info = - LOCKUP_INFO.compatible_load(deps.as_ref(), lockup_key, &config.generator)?; - - // CHECK :: Has user already withdrawn LP tokens once post the deposit window closure state - if lockup_info.withdrawal_flag { - return Err(StdError::generic_err( - "Withdrawal already happened. No more withdrawals accepted", - )); - } - - // Check :: Amount should be within the allowed withdrawal limit bounds - let max_withdrawal_percent = - calculate_max_withdrawal_percent_allowed(env.block.time.seconds(), &config); - let max_withdrawal_allowed = lockup_info - .lp_units_locked - .to_decimal() - .checked_mul(max_withdrawal_percent)? - .to_uint_floor(); - if amount > max_withdrawal_allowed { - return Err(StdError::generic_err(format!( - "Amount exceeds maximum allowed withdrawal limit of {}", - max_withdrawal_allowed - ))); - } - - // Update withdrawal flag after the deposit window - if env.block.time.seconds() >= config.init_timestamp + config.lock_window { - lockup_info.withdrawal_flag = true; - } - - // STATE :: RETRIEVE --> UPDATE - lockup_info.lp_units_locked = lockup_info.lp_units_locked.checked_sub(amount)?; - pool_info.weighted_amount = pool_info - .weighted_amount - .checked_sub(calculate_weight(amount, duration, &config)?)?; - pool_info.amount_in_lockups = pool_info.amount_in_lockups.checked_sub(amount)?; - - // Remove Lockup position from the list of user positions if Lp_Locked balance == 0 - if lockup_info.lp_units_locked.is_zero() { - LOCKUP_INFO.remove(deps.storage, lockup_key); - // decrement number of user's lockup positions - let mut user_info = USER_INFO - .may_load(deps.storage, &user_address)? - .unwrap_or_default(); - user_info.lockup_positions_index -= 1; - USER_INFO.save(deps.storage, &user_address, &user_info)?; - } else { - LOCKUP_INFO.save(deps.storage, lockup_key, &lockup_info)?; - } - TOTAL_USER_LOCKUP_AMOUNT.update( - deps.storage, - (pool_type, &user_address), - env.block.height, - |lockup_amount| -> StdResult { - if let Some(la) = lockup_amount { - Ok(la.checked_sub(amount)?) - } else { - Ok(Uint128::zero()) - } - }, - )?; - - // SAVE Updated States - ASSET_POOLS.save(deps.storage, pool_type, &pool_info, env.block.height)?; - - Ok(Response::new().add_attributes(vec![ - attr("action", "withdraw_from_lockup"), - attr("pool_type", pool_type), - attr("user_address", user_address), - attr("duration", duration.to_string()), - attr("amount", amount), - ])) -} - -/// Calculates maximum % of LP balances deposited that can be withdrawn -/// ## Params -/// * **current_timestamp** is an object of type [`u64`]. Current block timestamp -/// -/// * **config** is an object of type [`Config`]. Contract configuration -fn calculate_max_withdrawal_percent_allowed(current_timestamp: u64, config: &Config) -> Decimal { - let withdrawal_cutoff_init_point = config.init_timestamp + config.lock_window; - - // Deposit window :: 100% withdrawals allowed - if current_timestamp < withdrawal_cutoff_init_point { - return Decimal::from_ratio(100u32, 100u32); - } - - let withdrawal_cutoff_second_point = - withdrawal_cutoff_init_point + (config.withdrawal_window / 2u64); - // Deposit window closed, 1st half of withdrawal window :: 50% withdrawals allowed - if current_timestamp <= withdrawal_cutoff_second_point { - return Decimal::from_ratio(50u32, 100u32); - } - - // max withdrawal allowed decreasing linearly from 50% to 0% vs time elapsed - let withdrawal_cutoff_final = withdrawal_cutoff_init_point + config.withdrawal_window; - // Deposit window closed, 2nd half of withdrawal window :: max withdrawal allowed decreases linearly from 50% to 0% vs time elapsed - if current_timestamp < withdrawal_cutoff_final { - let time_left = withdrawal_cutoff_final - current_timestamp; - Decimal::from_ratio( - 50u64 * time_left, - 100u64 * (withdrawal_cutoff_final - withdrawal_cutoff_second_point), - ) - } - // Withdrawals not allowed - else { - Decimal::from_ratio(0u32, 100u32) - } -} - -/// Claims user Rewards for a particular Lockup position. Returns a default object of type [`Response`]. -/// ## Params -/// * **deps** is an object of type [`DepsMut`]. -/// -/// * **env** is an object of type [`Env`]. -/// -/// * **info** is an object of type [`MessageInfo`]. -/// -/// * **pool_type** is an object of type [`PoolType`]. LiquidPool type - USDC or ATOM -/// -/// * **duration** is an object of type [`u64`]. Lockup duration (number of weeks). -/// -/// * **withdraw_lp_stake** is an object of type [`bool`]. Boolean value indicating if the LP tokens are to be withdrawn or not. -pub fn handle_claim_rewards_and_unlock_for_lockup( - mut deps: DepsMut, - env: Env, - info: MessageInfo, - pool_type: PoolType, - duration: u64, - withdraw_lp_stake: bool, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - let state = STATE.load(deps.storage)?; - - if env.block.time.seconds() - < config.init_timestamp + config.lock_window + config.withdrawal_window - { - return Err(StdError::generic_err( - "Lock/withdrawal window is still open", - )); - } - - let user_address = info.sender; - - // CHECK ::: Is LP Token Pool supported or not ? - let pool_info = ASSET_POOLS.load(deps.storage, pool_type)?; - - let mut user_info = USER_INFO - .may_load(deps.storage, &user_address)? - .unwrap_or_default(); - - // If user's total NTRN rewards == 0 :: We update all of the user's lockup positions to calculate NTRN rewards and for each alongwith their equivalent Astroport LP Shares - if user_info.total_ntrn_rewards == Uint128::zero() { - user_info.total_ntrn_rewards = update_user_lockup_positions_and_calc_rewards( - deps.branch(), - &config, - &state, - &user_address, - )?; - } - - USER_INFO.save(deps.storage, &user_address, &user_info)?; - - // Check is there lockup or not ? - let lockup_key = (pool_type, &user_address, duration); - let lockup_info = LOCKUP_INFO.compatible_load(deps.as_ref(), lockup_key, &config.generator)?; - - // CHECK :: Can the Lockup position be unlocked or not ? - if withdraw_lp_stake && env.block.time.seconds() < lockup_info.unlock_timestamp { - return Err(StdError::generic_err(format!( - "{} seconds to unlock", - lockup_info.unlock_timestamp - env.block.time.seconds() - ))); - } - - if lockup_info.astroport_lp_transferred.is_some() { - return Err(StdError::generic_err( - "Astro LP Tokens have already been claimed!", - )); - } - - let mut cosmos_msgs = vec![]; - - let astroport_lp_token = pool_info.lp_token; - - if pool_info.is_staked { - let generator = config - .generator - .as_ref() - .ok_or_else(|| StdError::generic_err("Generator should be set at this moment!"))?; - - // QUERY :: Check if there are any pending staking rewards - let pending_rewards: PendingTokenResponse = deps.querier.query_wasm_smart( - generator, - &GenQueryMsg::PendingToken { - lp_token: astroport_lp_token.to_string(), - user: env.contract.address.to_string(), - }, - )?; - - let pending_on_proxy = &pending_rewards.pending_on_proxy.unwrap_or_default(); - - if !pending_rewards.pending.is_zero() - || pending_on_proxy.iter().any(|asset| !asset.amount.is_zero()) - { - let rwi: RewardInfoResponse = deps.querier.query_wasm_smart( - generator, - &GenQueryMsg::RewardInfo { - lp_token: astroport_lp_token.to_string(), - }, - )?; - - let reward_token_balance = deps - .querier - .query_balance( - env.contract.address.clone(), - rwi.base_reward_token.to_string(), - )? - .amount; - - let prev_proxy_reward_balances: Vec = pending_on_proxy - .iter() - .map(|asset| { - let balance = asset - .info - .query_pool(&deps.querier, env.contract.address.clone()) - .unwrap_or_default(); - - Asset { - info: asset.info.clone(), - amount: balance, - } - }) - .collect(); - - cosmos_msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: generator.to_string(), - funds: vec![], - msg: to_binary(&GenExecuteMsg::ClaimRewards { - lp_tokens: vec![astroport_lp_token.to_string()], - })?, - })); - - cosmos_msgs.push( - CallbackMsg::UpdatePoolOnDualRewardsClaim { - pool_type, - prev_ntrn_balance: reward_token_balance, - prev_proxy_reward_balances, - } - .to_cosmos_msg(&env)?, - ); - } - } else if user_info.ntrn_transferred && !withdraw_lp_stake { - return Err(StdError::generic_err("No rewards available to claim!")); - } - - cosmos_msgs.push( - CallbackMsg::WithdrawUserLockupRewardsCallback { - pool_type, - user_address, - duration, - withdraw_lp_stake, - } - .to_cosmos_msg(&env)?, - ); - - Ok(Response::new().add_messages(cosmos_msgs)) -} - -/// Claims unvested user's airdrop rewards from the Credits Contract plus part of vested tokens (NTRN Lockdrop rewards amount * AIDROP_REWARDS_MULTIPLIER) Returns a [`CosmosMsg`]. -/// ## Params -/// * **deps** is an object of type [`DepsMut`]. -/// -/// * **env** is an object of type [`Env`]. -/// -/// * **credits_contract** is an object of type [`Addr`]. Address of the Credits Contract. -/// -/// * **user_addr** is an object of type [`Addr`]. Address of the user for who claims rewards. -/// -/// * **ntrn_lockdrop_rewards** is an object of type [`Addr`]. Amount of Lockdrop rewards in uNTRN. -pub fn claim_airdrop_tokens_with_multiplier_msg( - deps: Deps, - credits_contract: Addr, - user_addr: Addr, - ntrn_lockdrop_rewards: Uint128, -) -> StdResult { - // unvested tokens amount - let unvested_tokens_amount: credits::msg::WithdrawableAmountResponse = - deps.querier.query_wasm_smart( - &credits_contract, - &credits::msg::QueryMsg::WithdrawableAmount { - address: user_addr.to_string(), - }, - )?; - // vested tokens amount - let vested_tokens_amount: credits::msg::VestedAmountResponse = deps.querier.query_wasm_smart( - &credits_contract, - &credits::msg::QueryMsg::VestedAmount { - address: user_addr.to_string(), - }, - )?; - - let airdrop_rewards_multiplier = Decimal::from_str(AIRDROP_REWARDS_MULTIPLIER)?; - - // either we claim whole vested amount or NTRN lockdrop rewards - let claimable_vested_amount = min( - vested_tokens_amount.amount, - ntrn_lockdrop_rewards - .to_decimal() - .checked_mul(airdrop_rewards_multiplier)? - .to_uint_floor(), - ); - - Ok(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: credits_contract.to_string(), - msg: to_binary(&Cw20ExecuteMsg::BurnFrom { - owner: user_addr.to_string(), - amount: claimable_vested_amount.checked_add(unvested_tokens_amount.amount)?, - })?, - funds: vec![], - })) -} - -/// Updates contract state after dual staking rewards are claimed from the generator contract. Returns a default object of type [`Response`]. -/// ## Params -/// * **deps** is an object of type [`DepsMut`]. -/// -/// * **env** is an object of type [`Env`]. -/// -/// * **pool_type** is an object of type [`PoolType`]. LiquidPool type - USDC or ATOM -/// -/// * **prev_ntrn_balance** is an object of type [`Uint128`]. Contract's NTRN token balance before claim. -/// -/// * **prev_proxy_reward_balances** is a vector of type [`Asset`]. Contract's Generator Proxy reward token balance before claim. -pub fn update_pool_on_dual_rewards_claim( - deps: DepsMut, - env: Env, - pool_type: PoolType, - prev_ntrn_balance: Uint128, - prev_proxy_reward_balances: Vec, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - let mut pool_info = ASSET_POOLS.load(deps.storage, pool_type)?; - - let generator = config - .generator - .as_ref() - .ok_or_else(|| StdError::generic_err("Generator hasn't been set yet!"))?; - let astroport_lp_token = pool_info.lp_token.clone(); - - let rwi: RewardInfoResponse = deps.querier.query_wasm_smart( - generator, - &GenQueryMsg::RewardInfo { - lp_token: astroport_lp_token.to_string(), - }, - )?; - - let lp_balance: Uint128 = deps.querier.query_wasm_smart( - generator, - &GenQueryMsg::Deposit { - lp_token: astroport_lp_token.to_string(), - user: env.contract.address.to_string(), - }, - )?; - - let base_reward_received; - // Increment claimed rewards per LP share - pool_info.generator_ntrn_per_share = pool_info.generator_ntrn_per_share.checked_add({ - let reward_token_balance = deps - .querier - .query_balance( - env.contract.address.clone(), - rwi.base_reward_token.to_string(), - )? - .amount; - base_reward_received = reward_token_balance.checked_sub(prev_ntrn_balance)?; - Decimal::from_ratio(base_reward_received, lp_balance) - })?; - - // Increment claimed Proxy rewards per LP share - for prev_balance in prev_proxy_reward_balances { - let current_balance = prev_balance - .info - .query_pool(&deps.querier, env.contract.address.clone())?; - let received_amount = current_balance.checked_sub(prev_balance.amount)?; - pool_info.generator_proxy_per_share.update( - &prev_balance.info, - Decimal::from_ratio(received_amount, lp_balance), - )?; - } - - // SAVE UPDATED STATE OF THE POOL - ASSET_POOLS.save(deps.storage, pool_type, &pool_info, env.block.height)?; - - Ok(Response::new().add_attributes(vec![ - attr("action", "update_generator_dual_rewards"), - attr("pool_type", format!("{:?}", pool_type)), - attr("NTRN_reward_received", base_reward_received), - attr( - "generator_ntrn_per_share", - pool_info.generator_ntrn_per_share.to_string(), - ), - ])) -} - -/// Withdraws user rewards and LP Tokens after claims / unlocks. Returns a default object of type [`Response`]. -/// ## Params -/// * **deps** is an object of type [`DepsMut`]. -/// -/// * **env** is an object of type [`Env`]. -/// -/// * **pool_type** is an object of type [`PoolType`]. LiquidPool type - USDC or ATOM -/// -/// * **user_address** is an object of type [`Addr`]. User address who is claiming the rewards / unlocking his lockup position. -/// -/// * **duration** is a vector of type [`u64`]. Duration of the lockup for which rewards have been claimed / position unlocked. -/// -/// * **withdraw_lp_stake** is an object of type [`bool`]. Boolean value indicating if the ASTRO LP Tokens are to be sent to the user or not. -pub fn callback_withdraw_user_rewards_for_lockup_optional_withdraw( - deps: DepsMut, - env: Env, - pool_type: PoolType, - user_address: Addr, - duration: u64, - withdraw_lp_stake: bool, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - let mut pool_info = ASSET_POOLS.load(deps.storage, pool_type)?; - let lockup_key = (pool_type, &user_address, duration); - let mut lockup_info = - LOCKUP_INFO.compatible_load(deps.as_ref(), lockup_key, &config.generator)?; - - let mut user_info = USER_INFO - .may_load(deps.storage, &user_address)? - .unwrap_or_default(); - - let mut cosmos_msgs = vec![]; - let mut attributes = vec![ - attr("action", "withdraw_rewards_and_or_unlock"), - attr("pool_type", format!("{:?}", pool_type)), - attr("user_address", &user_address), - attr("duration", duration.to_string()), - ]; - - let astroport_lp_token = pool_info.lp_token.clone(); - - let generator = config - .generator - .as_ref() - .ok_or_else(|| StdError::generic_err("Generator should be set"))?; - - // Calculate Astro LP share for the lockup position - let astroport_lp_amount: Uint128 = { - let balance: Uint128 = if pool_info.is_staked { - deps.querier.query_wasm_smart( - generator, - &GenQueryMsg::Deposit { - lp_token: astroport_lp_token.to_string(), - user: env.contract.address.to_string(), - }, - )? - } else { - let res: BalanceResponse = deps.querier.query_wasm_smart( - astroport_lp_token.clone(), - &Cw20QueryMsg::Balance { - address: env.contract.address.to_string(), - }, - )?; - res.balance - }; - - (lockup_info - .lp_units_locked - .full_mul(balance) - .checked_div(Uint256::from(pool_info.amount_in_lockups))?) - .try_into()? - }; - - // If Astro LP tokens are staked with Astro generator - if pool_info.is_staked { - let rwi: RewardInfoResponse = deps.querier.query_wasm_smart( - generator, - &GenQueryMsg::RewardInfo { - lp_token: astroport_lp_token.to_string(), - }, - )?; - - // Calculate claimable staking rewards for this lockup - let total_lockup_astro_rewards = pool_info - .generator_ntrn_per_share - .checked_mul(astroport_lp_amount.to_decimal())? - .to_uint_floor(); - let pending_astro_rewards = - total_lockup_astro_rewards.checked_sub(lockup_info.generator_ntrn_debt)?; - lockup_info.generator_ntrn_debt = total_lockup_astro_rewards; - - // If claimable staking rewards > 0, claim them - if pending_astro_rewards > Uint128::zero() { - cosmos_msgs.push(CosmosMsg::Bank(BankMsg::Send { - to_address: user_address.to_string(), - amount: vec![Coin { - denom: rwi.base_reward_token.to_string(), - amount: pending_astro_rewards, - }], - })); - } - attributes.push(attr("generator_astro_reward", pending_astro_rewards)); - - let mut pending_proxy_rewards: Vec = vec![]; - // If this LP token is getting dual incentives - // Calculate claimable proxy staking rewards for this lockup - lockup_info.generator_proxy_debt = lockup_info - .generator_proxy_debt - .inner_ref() - .iter() - .map(|(asset, debt)| { - let generator_proxy_per_share = pool_info - .generator_proxy_per_share - .load(asset) - .unwrap_or_default(); - let total_lockup_proxy_reward = - generator_proxy_per_share.checked_mul_uint128(astroport_lp_amount)?; - let pending_proxy_reward: Uint128 = total_lockup_proxy_reward.checked_sub(*debt)?; - - if !pending_proxy_reward.is_zero() { - pending_proxy_rewards.push(Asset { - info: asset.clone(), - amount: pending_proxy_reward, - }); - } - Ok((asset.clone(), total_lockup_proxy_reward)) - }) - .collect::>>()? - .into(); - - // If this is a void transaction (no state change), then return error. - // Void tx scenario = ASTRO already claimed, 0 pending ASTRO staking reward, 0 pending proxy rewards, not unlocking LP tokens in this tx - if !withdraw_lp_stake - && user_info.ntrn_transferred - && pending_astro_rewards == Uint128::zero() - && pending_proxy_rewards.is_empty() - { - return Err(StdError::generic_err("No rewards available to claim!")); - } - - // If claimable proxy staking rewards > 0, claim them - for pending_proxy_reward in pending_proxy_rewards { - cosmos_msgs.push(pending_proxy_reward.into_msg(&deps.querier, user_address.clone())?); - } - - // COSMOSMSG :: If LP Tokens are staked, we unstake the amount which needs to be returned to the user - if withdraw_lp_stake { - cosmos_msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: generator.to_string(), - funds: vec![], - msg: to_binary(&GenExecuteMsg::Withdraw { - lp_token: astroport_lp_token.to_string(), - amount: astroport_lp_amount, - })?, - })); - } - } - - if withdraw_lp_stake { - // COSMOSMSG :: Returns LP units locked by the user in the current lockup position - cosmos_msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: astroport_lp_token.to_string(), - msg: to_binary(&Cw20ExecuteMsg::Transfer { - recipient: user_address.to_string(), - amount: astroport_lp_amount, - })?, - funds: vec![], - })); - pool_info.amount_in_lockups = pool_info - .amount_in_lockups - .checked_sub(lockup_info.lp_units_locked)?; - ASSET_POOLS.save(deps.storage, pool_type, &pool_info, env.block.height)?; - - attributes.push(attr("astroport_lp_unlocked", astroport_lp_amount)); - lockup_info.astroport_lp_transferred = Some(astroport_lp_amount); - TOTAL_USER_LOCKUP_AMOUNT.update( - deps.storage, - (pool_type, &user_address), - env.block.height, - |lockup_amount| -> StdResult { - if let Some(la) = lockup_amount { - Ok(la.checked_sub(lockup_info.lp_units_locked)?) - } else { - Ok(Uint128::zero()) - } - }, - )?; - } - LOCKUP_INFO.save(deps.storage, lockup_key, &lockup_info)?; - - // Transfers claimable one time NTRN rewards to the user that the user gets for all his lock - if !user_info.ntrn_transferred { - // Calculating how much NTRN user can claim (from total one time reward) - let total_claimable_ntrn_rewards = user_info.total_ntrn_rewards; - if total_claimable_ntrn_rewards > Uint128::zero() { - cosmos_msgs.push(CosmosMsg::Bank(BankMsg::Send { - to_address: user_address.to_string(), - amount: coins(total_claimable_ntrn_rewards.u128(), UNTRN_DENOM), - })) - } - - // claim airdrop rewards for airdrop participants - let res: BalanceResponse = deps.querier.query_wasm_smart( - config.credits_contract.clone(), - &Cw20QueryMsg::Balance { - address: user_address.to_string(), - }, - )?; - if res.balance > Uint128::zero() { - cosmos_msgs.push(claim_airdrop_tokens_with_multiplier_msg( - deps.as_ref(), - config.credits_contract, - user_address.clone(), - total_claimable_ntrn_rewards, - )?); - } - - user_info.ntrn_transferred = true; - attributes.push(attr( - "total_claimable_ntrn_reward", - total_claimable_ntrn_rewards, - )); - USER_INFO.save(deps.storage, &user_address, &user_info)?; - } - - Ok(Response::new() - .add_messages(cosmos_msgs) - .add_attributes(attributes)) -} - -/// Returns the contract's State. -/// ## Params -/// * **deps** is an object of type [`Deps`]. -pub fn query_state(deps: Deps) -> StdResult { - let state: State = STATE.load(deps.storage)?; - Ok(StateResponse { - total_incentives_share: state.total_incentives_share, - supported_pairs_list: ASSET_POOLS - .keys(deps.storage, None, None, Order::Ascending) - .collect::, StdError>>()?, - }) -} - -/// Returns the pool's State. -/// ## Params -/// * **deps** is an object of type [`Deps`]. -/// -/// * **pool_type** is an object of type [`PoolType`]. LiquidPool type - USDC or ATOM -pub fn query_pool(deps: Deps, pool_type: PoolType) -> StdResult { - let pool_info: PoolInfo = ASSET_POOLS.load(deps.storage, pool_type)?; - Ok(pool_info) -} - -/// Returns summarized details regarding the user. -/// ## Params -/// * **deps** is an object of type [`Deps`]. -/// -/// * **env** is an object of type [`Env`]. -/// -/// * **user** is an object of type [`String`]. -pub fn query_user_info(deps: Deps, env: Env, user: String) -> StdResult { - let user_address = deps.api.addr_validate(&user)?; - let user_info = USER_INFO - .may_load(deps.storage, &user_address)? - .unwrap_or_default(); - - let mut total_astro_rewards = Uint128::zero(); - let mut lockup_infos = vec![]; - - let mut claimable_generator_astro_debt = Uint128::zero(); - for pool_type in ASSET_POOLS - .keys(deps.storage, None, None, Order::Ascending) - .collect::, StdError>>()? - { - for duration in LOCKUP_INFO - .prefix((pool_type, &user_address)) - .keys(deps.storage, None, None, Order::Ascending) - .collect::, StdError>>()? - { - let lockup_info = query_lockup_info(deps, &env, &user, pool_type, duration)?; - total_astro_rewards = total_astro_rewards.checked_add(lockup_info.ntrn_rewards)?; - claimable_generator_astro_debt = claimable_generator_astro_debt - .checked_add(lockup_info.claimable_generator_astro_debt)?; - lockup_infos.push(lockup_info); - } - } - - Ok(UserInfoResponse { - total_ntrn_rewards: total_astro_rewards, - ntrn_transferred: user_info.ntrn_transferred, - lockup_infos, - claimable_generator_ntrn_debt: claimable_generator_astro_debt, - lockup_positions_index: user_info.lockup_positions_index, - }) -} - -/// Returns summarized details regarding the user with lockups list. -/// ## Params -/// * **deps** is an object of type [`Deps`]. -/// -/// * **_env** is an object of type [`Env`]. -/// -/// * **user** is an object of type [`String`]. -pub fn query_user_info_with_lockups_list( - deps: Deps, - _env: Env, - user: String, -) -> StdResult { - let user_address = deps.api.addr_validate(&user)?; - let user_info = USER_INFO - .may_load(deps.storage, &user_address)? - .unwrap_or_default(); - - let mut lockup_infos = vec![]; - - for pool_type in ASSET_POOLS - .keys(deps.storage, None, None, Order::Ascending) - .collect::, StdError>>()? - { - for duration in LOCKUP_INFO - .prefix((pool_type, &user_address)) - .keys(deps.storage, None, None, Order::Ascending) - .collect::, StdError>>()? - { - lockup_infos.push(LockUpInfoSummary { - pool_type, - duration, - }); - } - } - - Ok(UserInfoWithListResponse { - total_ntrn_rewards: user_info.total_ntrn_rewards, - ntrn_transferred: user_info.ntrn_transferred, - lockup_infos, - lockup_positions_index: user_info.lockup_positions_index, - }) -} - -/// Returns locked amount of LP tokens for the specified user for the specified pool at a specific height. -/// ## Params -/// * **deps** is an object of type [`Deps`]. -/// -/// * **pool_type** is an object of type [`PoolType`]. -/// -/// * **user** is an object of type [`Addr`]. -/// -/// * **height** is an object of type [`u64`]. -pub fn query_user_lockup_total_at_height( - deps: Deps, - pool: PoolType, - user: Addr, - height: u64, -) -> StdResult> { - TOTAL_USER_LOCKUP_AMOUNT.may_load_at_height(deps.storage, (pool, &user), height) -} - -/// Returns a total amount of LP tokens for the specified pool at a specific height. -/// ## Params -/// * **deps** is an object of type [`Deps`]. -/// -/// * **pool_type** is an object of type [`PoolType`]. -/// -/// * **height** is an object of type [`u64`]. -pub fn query_lockup_total_at_height( - deps: Deps, - pool: PoolType, - height: u64, -) -> StdResult> { - if let Some(pool) = ASSET_POOLS.may_load_at_height(deps.storage, pool, height)? { - return Ok(Some(pool.amount_in_lockups)); - } - Ok(Some(Uint128::zero())) -} - -/// Returns summarized details regarding the user -/// ## Params -/// * **deps** is an object of type [`Deps`]. -/// -/// * **env** is an object of type [`Env`]. -/// -/// * **user_address** is an object of type [`&str`]. -/// -/// * **pool_type** is an object of type [`PoolType`]. LiquidPool type - USDC or ATOM -/// -/// * **duration** is an object of type [`u64`]. -pub fn query_lockup_info( - deps: Deps, - env: &Env, - user_address: &str, - pool_type: PoolType, - duration: u64, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - let state = STATE.load(deps.storage)?; - - let user_address = deps.api.addr_validate(user_address)?; - - let lockup_key = (pool_type, &user_address, duration); - let mut pool_info = ASSET_POOLS.load(deps.storage, pool_type)?; - let mut lockup_info = LOCKUP_INFO.compatible_load(deps, lockup_key, &config.generator)?; - - let lockup_astroport_lp_units_opt: Option; - let astroport_lp_token_opt: Addr; - let mut claimable_generator_astro_debt = Uint128::zero(); - let mut claimable_generator_proxy_debt: RestrictedVector = - RestrictedVector::default(); - if let Some(astroport_lp_transferred) = lockup_info.astroport_lp_transferred { - lockup_astroport_lp_units_opt = Some(astroport_lp_transferred); - astroport_lp_token_opt = pool_info.lp_token; - } else { - let astroport_lp_token = pool_info.lp_token; - let pool_astroport_lp_units; - let lockup_astroport_lp_units = { - // Query Astro LP Tokens balance for the pool - pool_astroport_lp_units = if pool_info.is_staked { - raw_generator_deposit( - deps.querier, - config - .generator - .as_ref() - .ok_or_else(|| StdError::generic_err("Should be set!"))?, - astroport_lp_token.as_bytes(), - env.contract.address.as_bytes(), - )? - } else { - raw_balance( - deps.querier, - &astroport_lp_token, - env.contract.address.as_bytes(), - )? - }; - // Calculate Lockup Astro LP shares - (lockup_info - .lp_units_locked - .full_mul(pool_astroport_lp_units) - .checked_div(Uint256::from(pool_info.amount_in_lockups))?) - .try_into()? - }; - lockup_astroport_lp_units_opt = Some(lockup_astroport_lp_units); - astroport_lp_token_opt = astroport_lp_token.clone(); - // If LP tokens are staked, calculate the rewards claimable by the user for this lockup position - if pool_info.is_staked && !lockup_astroport_lp_units.is_zero() { - let generator = config - .generator - .clone() - .ok_or_else(|| StdError::generic_err("Generator should be set at this moment!"))?; - - // QUERY :: Check if there are any pending staking rewards - let pending_rewards: PendingTokenResponse = deps.querier.query_wasm_smart( - &generator, - &GenQueryMsg::PendingToken { - lp_token: astroport_lp_token.to_string(), - user: env.contract.address.to_string(), - }, - )?; - - // Calculate claimable Astro staking rewards for this lockup - pool_info.generator_ntrn_per_share = - pool_info - .generator_ntrn_per_share - .checked_add(Decimal::from_ratio( - pending_rewards.pending, - pool_astroport_lp_units, - ))?; - - let total_lockup_astro_rewards = pool_info - .generator_ntrn_per_share - .checked_mul(lockup_astroport_lp_units.to_decimal())? - .to_uint_floor(); - claimable_generator_astro_debt = - total_lockup_astro_rewards.checked_sub(lockup_info.generator_ntrn_debt)?; - - // Calculate claimable Proxy staking rewards for this lockup - if let Some(pending_on_proxy) = pending_rewards.pending_on_proxy { - for reward in pending_on_proxy { - let generator_proxy_per_share = pool_info.generator_proxy_per_share.update( - &reward.info, - Decimal::from_ratio(reward.amount, pool_astroport_lp_units), - )?; - - let debt = generator_proxy_per_share - .checked_mul_uint128(lockup_astroport_lp_units)? - .checked_sub( - lockup_info - .generator_proxy_debt - .inner_ref() - .iter() - .find_map(|a| if reward.info == a.0 { Some(a.1) } else { None }) - .unwrap_or_default(), - )?; - - claimable_generator_proxy_debt.update(&reward.info, debt)?; - } - } - } - } - // Calculate currently expected ASTRO Rewards if not finalized - if lockup_info.ntrn_rewards == Uint128::zero() { - let weighted_lockup_balance = - calculate_weight(lockup_info.lp_units_locked, duration, &config)?; - lockup_info.ntrn_rewards = calculate_astro_incentives_for_lockup( - weighted_lockup_balance, - pool_info.weighted_amount, - pool_info.incentives_share, - state.total_incentives_share, - config.lockdrop_incentives, - )?; - } - - Ok(LockUpInfoResponse { - pool_type, - lp_units_locked: lockup_info.lp_units_locked, - withdrawal_flag: lockup_info.withdrawal_flag, - ntrn_rewards: lockup_info.ntrn_rewards, - generator_ntrn_debt: lockup_info.generator_ntrn_debt, - claimable_generator_astro_debt, - generator_proxy_debt: lockup_info.generator_proxy_debt, - claimable_generator_proxy_debt, - unlock_timestamp: lockup_info.unlock_timestamp, - astroport_lp_units: lockup_astroport_lp_units_opt, - astroport_lp_token: astroport_lp_token_opt, - astroport_lp_transferred: lockup_info.astroport_lp_transferred, - duration, - }) -} - -/// Calculates ASTRO rewards for a particular Lockup position -/// ## Params -/// * **lockup_weighted_balance** is an object of type [`Uint256`]. Lockup position's weighted terraswap LP balance -/// -/// * **total_weighted_amount** is an object of type [`Uint256`]. Total weighted terraswap LP balance of the Pool -/// -/// * **pool_incentives_share** is an object of type [`u64`]. Share of total ASTRO incentives allocated to this pool -/// -/// * **total_incentives_share** is an object of type [`u64`]. Calculated total incentives share for allocating among pools -/// -/// * **total_lockdrop_incentives** is an object of type [`Uint128`]. Total ASTRO incentives to be distributed among Lockdrop participants -pub fn calculate_astro_incentives_for_lockup( - lockup_weighted_balance: Uint256, - total_weighted_amount: Uint256, - pool_incentives_share: Uint128, - total_incentives_share: Uint128, - total_lockdrop_incentives: Uint128, -) -> StdResult { - if total_incentives_share.is_zero() || total_weighted_amount.is_zero() { - Ok(Uint128::zero()) - } else { - Ok(Decimal256::from_ratio( - Uint256::from(pool_incentives_share).checked_mul(lockup_weighted_balance)?, - Uint256::from(total_incentives_share).checked_mul(total_weighted_amount)?, - ) - .checked_mul_uint256(total_lockdrop_incentives.into())?) - } -} - -/// Returns effective weight for the amount to be used for calculating lockdrop rewards. -/// ## Params -/// * **amount** is an object of type [`Uint128`]. Number of LP tokens. -/// -/// * **duration** is an object of type [`u64`]. Number of seconds. -/// -/// * **config** is an object of type [`Config`]. Config with weekly multiplier and divider. -fn calculate_weight(amount: Uint128, duration: u64, config: &Config) -> StdResult { - if let Some(info) = config - .lockup_rewards_info - .iter() - .find(|info| info.duration == duration) - { - let lock_weight = Decimal256::one() + info.coefficient; - Ok(lock_weight.checked_mul_uint256(amount.into())?.into()) - } else { - Err(StdError::generic_err("invalid duration")) - } -} - -/// Calculates ASTRO rewards for each of the user position. -/// ## Params -/// * **deps** is an object of type [`DepsMut`]. -/// -/// * **config** is an object of type [`Config`]. -/// -/// * **state** is an object of type [`State`]. -/// -/// * **user_address** is an object of type [`Addr`] -fn update_user_lockup_positions_and_calc_rewards( - deps: DepsMut, - config: &Config, - state: &State, - user_address: &Addr, -) -> StdResult { - let mut total_astro_rewards = Uint128::zero(); - - let mut keys: Vec<(PoolType, u64)> = vec![]; - - for pool_type in ASSET_POOLS - .keys(deps.storage, None, None, Order::Ascending) - .collect::, StdError>>()? - { - for duration in LOCKUP_INFO - .prefix((pool_type, user_address)) - .keys(deps.storage, None, None, Order::Ascending) - .collect::, StdError>>()? - { - keys.push((pool_type, duration)); - } - } - for (pool_type, duration) in keys { - let pool_info = ASSET_POOLS.load(deps.storage, pool_type)?; - let lockup_key = (pool_type, user_address, duration); - let mut lockup_info = - LOCKUP_INFO.compatible_load(deps.as_ref(), lockup_key, &config.generator)?; - - if lockup_info.ntrn_rewards == Uint128::zero() { - // Weighted lockup balance (using terraswap LP units to calculate as pool's total weighted balance is calculated on terraswap LP deposits summed over each deposit tx) - let weighted_lockup_balance = - calculate_weight(lockup_info.lp_units_locked, duration, config)?; - - // Calculate ASTRO Lockdrop rewards for the lockup position - lockup_info.ntrn_rewards = calculate_astro_incentives_for_lockup( - weighted_lockup_balance, - pool_info.weighted_amount, - pool_info.incentives_share, - state.total_incentives_share, - config.lockdrop_incentives, - )?; - - LOCKUP_INFO.save(deps.storage, lockup_key, &lockup_info)?; - }; - - let lockup_astro_rewards = lockup_info.ntrn_rewards; - - // Save updated Lockup state - total_astro_rewards = total_astro_rewards.checked_add(lockup_astro_rewards)?; - } - - Ok(total_astro_rewards) -} diff --git a/contracts/lockdrop-pcl/src/lib.rs b/contracts/lockdrop-pcl/src/lib.rs deleted file mode 100644 index 43f4f742..00000000 --- a/contracts/lockdrop-pcl/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod contract; -pub mod raw_queries; -pub mod state; - -#[cfg(test)] -mod testing; diff --git a/contracts/lockdrop-pcl/src/migration.rs b/contracts/lockdrop-pcl/src/migration.rs deleted file mode 100644 index 6c1544b1..00000000 --- a/contracts/lockdrop-pcl/src/migration.rs +++ /dev/null @@ -1,77 +0,0 @@ -use astroport::asset::AssetInfo; -use astroport::generator::{PoolInfoResponse, QueryMsg as GenQueryMsg}; -use astroport_periphery::lockdrop::MigrationInfo; -use cosmwasm_std::{Addr, Decimal, DepsMut, StdResult, Uint128, Uint256}; -use cw_storage_plus::Map; - -use crate::raw_queries::raw_proxy_asset; -use astroport::restricted_vector::RestrictedVector; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct PoolInfoV101 { - pub terraswap_pool: Addr, - pub terraswap_amount_in_lockups: Uint128, - pub migration_info: Option, - /// Share of total ASTRO incentives allocated to this pool - pub incentives_share: u64, - /// Weighted LP Token balance used to calculate ASTRO rewards a particular user can claim - pub weighted_amount: Uint256, - /// Ratio of Generator ASTRO rewards accured to astroport pool share - pub generator_astro_per_share: Decimal, - /// Ratio of Generator Proxy rewards accured to astroport pool share - pub generator_proxy_per_share: Decimal, - /// Boolean value indicating if the LP Tokens are staked with the Generator contract or not - pub is_staked: bool, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct PoolInfoV111 { - pub terraswap_pool: Addr, - pub terraswap_amount_in_lockups: Uint128, - pub migration_info: Option, - /// Share of total ASTRO incentives allocated to this pool - pub incentives_share: u64, - /// Weighted LP Token balance used to calculate ASTRO rewards a particular user can claim - pub weighted_amount: Uint256, - /// Ratio of Generator ASTRO rewards accured to astroport pool share - pub generator_astro_per_share: Decimal, - /// Ratio of Generator Proxy rewards accured to astroport pool share - pub generator_proxy_per_share: Decimal, - /// Boolean value indicating if the LP Tokens are staked with the Generator contract or not - pub is_staked: bool, - /// Flag defines whether the asset has rewards or not - pub has_asset_rewards: bool, -} - -pub const ASSET_POOLS_V101: Map<&Addr, PoolInfoV101> = Map::new("LiquidityPools"); -pub const ASSET_POOLS_V111: Map<&Addr, PoolInfoV111> = Map::new("LiquidityPools"); - -pub fn migrate_generator_proxy_per_share_to_v120( - deps: &DepsMut, - generator_proxy_per_share_old: Decimal, - generator: &Addr, - migration_info: Option, -) -> StdResult> { - let mut generator_proxy_per_share = RestrictedVector::default(); - if !generator_proxy_per_share_old.is_zero() { - let pool_info: PoolInfoResponse = deps.querier.query_wasm_smart( - generator, - &GenQueryMsg::PoolInfo { - lp_token: migration_info - .expect("Should be migrated!") - .astroport_lp_token - .to_string(), - }, - )?; - let (proxy, _) = pool_info - .accumulated_proxy_rewards_per_share - .first() - .expect("Proxy reward should be set!"); - let reward_asset = raw_proxy_asset(deps.querier, generator, proxy.as_bytes())?; - generator_proxy_per_share.update(&reward_asset, generator_proxy_per_share_old)?; - } - - Ok(generator_proxy_per_share) -} diff --git a/contracts/lockdrop-pcl/src/raw_queries.rs b/contracts/lockdrop-pcl/src/raw_queries.rs deleted file mode 100644 index e491fdf4..00000000 --- a/contracts/lockdrop-pcl/src/raw_queries.rs +++ /dev/null @@ -1,53 +0,0 @@ -use astroport::asset::AssetInfo; -use cosmwasm_std::{from_slice, Addr, Empty, QuerierWrapper, StdError, StdResult, Uint128}; -use cw_storage_plus::Path; -use serde::Deserialize; - -/// Returns generator deposit of tokens for the specified address -pub fn raw_generator_deposit( - querier: QuerierWrapper, - generator: &Addr, - lp_token: &[u8], - address: &[u8], -) -> StdResult { - #[derive(Deserialize)] - struct UserInfo { - amount: Uint128, - } - - let key: Path = Path::new(b"user_info", &[lp_token, address]); - if let Some(res) = &querier.query_wasm_raw(generator, key.to_vec())? { - let UserInfo { amount } = from_slice(res)?; - Ok(amount) - } else { - Ok(Uint128::zero()) - } -} - -/// Returns balance of tokens for the specified address -pub fn raw_balance(querier: QuerierWrapper, token: &Addr, address: &[u8]) -> StdResult { - let key: Path = Path::new(b"balance", &[address]); - if let Some(res) = &querier.query_wasm_raw(token, key.to_vec())? { - let res: Uint128 = from_slice(res)?; - Ok(res) - } else { - Ok(Uint128::zero()) - } -} - -/// Returns AssetInfo for the specified proxy address from generator storage -pub fn raw_proxy_asset( - querier: QuerierWrapper, - generator: &Addr, - address: &[u8], -) -> StdResult { - let key: Path = Path::new(b"proxy_reward_asset", &[address]); - if let Some(res) = &querier.query_wasm_raw(generator, key.to_vec())? { - let res: AssetInfo = from_slice(res)?; - return Ok(res); - } - Err(StdError::generic_err(format!( - "Proxy asset not found: {}", - String::from_utf8(address.to_vec())? - ))) -} diff --git a/contracts/lockdrop-pcl/src/state.rs b/contracts/lockdrop-pcl/src/state.rs deleted file mode 100644 index 5b817721..00000000 --- a/contracts/lockdrop-pcl/src/state.rs +++ /dev/null @@ -1,113 +0,0 @@ -use astroport::common::OwnershipProposal; -use astroport::generator::PoolInfoResponse; -use astroport::generator::QueryMsg as GenQueryMsg; -use astroport::restricted_vector::RestrictedVector; -use astroport_periphery::lockdrop::PoolType; -use astroport_periphery::lockdrop::{ - Config, LockupInfoV1, LockupInfoV2, PoolInfo, State, UserInfo, -}; -use astroport_periphery::U64Key; -use cosmwasm_std::{Addr, Deps, StdError, StdResult, Uint128}; -use cw_storage_plus::{Item, Map, SnapshotMap, Strategy}; - -use crate::raw_queries::raw_proxy_asset; - -pub const CONFIG: Item = Item::new("config"); -pub const STATE: Item = Item::new("state"); - -/// Key is an Terraswap LP token address -pub const ASSET_POOLS: SnapshotMap = SnapshotMap::new( - "LiquidityPools", - "LiquitidyPools_checkpoints", - "LiquidityPools_changelog", - Strategy::EveryBlock, -); -/// Key is an user address -pub const USER_INFO: Map<&Addr, UserInfo> = Map::new("users"); -/// Key consists of an Terraswap LP token address, an user address, and a duration -pub const LOCKUP_INFO: Map<(PoolType, &Addr, U64Key), LockupInfoV2> = Map::new("lockup_position"); - -pub const TOTAL_USER_LOCKUP_AMOUNT: SnapshotMap<(PoolType, &Addr), Uint128> = SnapshotMap::new( - "total_user_lockup_info", - "total_user_lockup_info_checkpoints", - "total_lockup_info_changelog", - Strategy::EveryBlock, -); - -/// Old LOCKUP_INFO storage interface for backward compatibility -pub const OLD_LOCKUP_INFO: Map<(PoolType, &Addr, U64Key), LockupInfoV1> = - Map::new("lockup_position"); - -pub trait CompatibleLoader { - fn compatible_load(&self, deps: Deps, key: K, generator: &Option) -> StdResult; - - fn compatible_may_load( - &self, - deps: Deps, - key: K, - generator: &Option, - ) -> StdResult>; -} - -impl CompatibleLoader<(PoolType, &Addr, U64Key), LockupInfoV2> - for Map<'_, (PoolType, &Addr, U64Key), LockupInfoV2> -{ - fn compatible_load( - &self, - deps: Deps, - key: (PoolType, &Addr, U64Key), - generator: &Option, - ) -> StdResult { - self.load(deps.storage, key).or_else(|_| { - let old_lockup_info = OLD_LOCKUP_INFO.load(deps.storage, key)?; - let mut generator_proxy_debt = RestrictedVector::default(); - let generator = generator.as_ref().expect("Generator should be set!"); - - if !old_lockup_info.generator_proxy_debt.is_zero() { - let asset = ASSET_POOLS.load(deps.storage, key.0)?; - let astro_lp = asset.lp_token; - let pool_info: PoolInfoResponse = deps.querier.query_wasm_smart( - generator, - &GenQueryMsg::PoolInfo { - lp_token: astro_lp.to_string(), - }, - )?; - let (proxy, _) = pool_info - .accumulated_proxy_rewards_per_share - .first() - .ok_or_else(|| { - StdError::generic_err(format!("Proxy rewards not found: {}", astro_lp)) - })?; - let reward_asset = raw_proxy_asset(deps.querier, generator, proxy.as_bytes())?; - - generator_proxy_debt.update(&reward_asset, old_lockup_info.generator_proxy_debt)?; - } - - let lockup_info = LockupInfoV2 { - lp_units_locked: old_lockup_info.lp_units_locked, - astroport_lp_transferred: old_lockup_info.astroport_lp_transferred, - withdrawal_flag: old_lockup_info.withdrawal_flag, - ntrn_rewards: old_lockup_info.ntrn_rewards, - generator_ntrn_debt: old_lockup_info.generator_ntrn_debt, - generator_proxy_debt, - unlock_timestamp: old_lockup_info.unlock_timestamp, - }; - - Ok(lockup_info) - }) - } - - fn compatible_may_load( - &self, - deps: Deps, - key: (PoolType, &Addr, U64Key), - generator: &Option, - ) -> StdResult> { - if !OLD_LOCKUP_INFO.has(deps.storage, key) { - return Ok(None); - } - Some(self.compatible_load(deps, key, generator)).transpose() - } -} - -pub const OWNERSHIP_PROPOSAL: Item = Item::new("ownership_proposal"); diff --git a/contracts/lockdrop-pcl/src/testing.rs b/contracts/lockdrop-pcl/src/testing.rs deleted file mode 100644 index 526c0362..00000000 --- a/contracts/lockdrop-pcl/src/testing.rs +++ /dev/null @@ -1,149 +0,0 @@ -use crate::contract::{execute, instantiate, query, UNTRN_DENOM}; -use astroport_periphery::lockdrop::{ - Config, ExecuteMsg, InstantiateMsg, LockupRewardsInfo, QueryMsg, -}; -use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; -use cosmwasm_std::{coin, from_binary, Addr, Decimal256, StdError, Uint128}; - -#[test] -fn update_owner() { - let mut deps = mock_dependencies(); - let info = mock_info("addr0000", &[]); - - let owner = Addr::unchecked("owner"); - let token_info_manager = Addr::unchecked("token_info_manager"); - - let env = mock_env(); - - let msg = InstantiateMsg { - owner: Some(owner.to_string()), - token_info_manager: token_info_manager.to_string(), - init_timestamp: env.block.time.seconds(), - lock_window: 10_000_000, - withdrawal_window: 500_000, - min_lock_duration: 1u64, - max_lock_duration: 52u64, - max_positions_per_user: 14, - credits_contract: "credit_contract".to_string(), - lockup_rewards_info: vec![LockupRewardsInfo { - duration: 1, - coefficient: Decimal256::zero(), - }], - auction_contract: "auction_contract".to_string(), - }; - - // We can just call .unwrap() to assert this was a success - instantiate(deps.as_mut(), env, info, msg).unwrap(); - - let new_owner = String::from("new_owner"); - - // BNew owner - let env = mock_env(); - let msg = ExecuteMsg::ProposeNewOwner { - owner: new_owner.clone(), - expires_in: 100, // seconds - }; - - let info = mock_info(new_owner.as_str(), &[]); - - // Unauthorized check - let err = execute(deps.as_mut(), env.clone(), info, msg.clone()).unwrap_err(); - assert_eq!(err.to_string(), "Generic error: Unauthorized"); - - // Claim before a proposal - let info = mock_info(new_owner.as_str(), &[]); - execute( - deps.as_mut(), - env.clone(), - info, - ExecuteMsg::ClaimOwnership {}, - ) - .unwrap_err(); - - // Propose new owner - let info = mock_info(owner.as_str(), &[]); - let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap(); - assert_eq!(0, res.messages.len()); - - // Unauthorized ownership claim - let info = mock_info("invalid_addr", &[]); - let err = execute( - deps.as_mut(), - env.clone(), - info, - ExecuteMsg::ClaimOwnership {}, - ) - .unwrap_err(); - assert_eq!(err.to_string(), "Generic error: Unauthorized"); - - // Claim ownership - let info = mock_info(new_owner.as_str(), &[]); - let res = execute( - deps.as_mut(), - env.clone(), - info, - ExecuteMsg::ClaimOwnership {}, - ) - .unwrap(); - assert_eq!(0, res.messages.len()); - - // Let's query the state - let config: Config = - from_binary(&query(deps.as_ref(), env, QueryMsg::Config {}).unwrap()).unwrap(); - assert_eq!(new_owner, config.owner); -} - -#[test] -fn increase_ntrn_incentives() { - let mut deps = mock_dependencies(); - let info = mock_info("addr0000", &[]); - - let owner = Addr::unchecked("owner"); - let token_info_manager = Addr::unchecked("token_info_manager"); - - let env = mock_env(); - - let msg = InstantiateMsg { - owner: Some(owner.to_string()), - token_info_manager: token_info_manager.to_string(), - init_timestamp: env.block.time.seconds(), - lock_window: 10_000_000, - withdrawal_window: 500_000, - min_lock_duration: 1u64, - max_lock_duration: 52u64, - max_positions_per_user: 14, - credits_contract: "credit_contract".to_string(), - auction_contract: "auction_contract".to_string(), - lockup_rewards_info: vec![LockupRewardsInfo { - duration: 1, - coefficient: Decimal256::zero(), - }], - }; - - // We can just call .unwrap() to assert this was a success - instantiate(deps.as_mut(), env, info, msg).unwrap(); - - let env = mock_env(); - let msg = ExecuteMsg::IncreaseNTRNIncentives {}; - - let info = mock_info(owner.as_str(), &[coin(100u128, UNTRN_DENOM)]); - - let res = execute(deps.as_mut(), env.clone(), info, msg); - assert!(res.is_ok()); - - let config: Config = - from_binary(&query(deps.as_ref(), env, QueryMsg::Config {}).unwrap()).unwrap(); - assert_eq!(Uint128::new(100u128), config.lockdrop_incentives); - - // invalid coin - let env = mock_env(); - let msg = ExecuteMsg::IncreaseNTRNIncentives {}; - - let info = mock_info(owner.as_str(), &[coin(100u128, "DENOM")]); - - let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!( - err, - StdError::generic_err(format!("{} is not found", UNTRN_DENOM)) - ); -} diff --git a/contracts/vesting-lp-pcl/Cargo.toml b/contracts/vesting-lp-pcl/Cargo.toml index 5e42fd04..b3067a3a 100644 --- a/contracts/vesting-lp-pcl/Cargo.toml +++ b/contracts/vesting-lp-pcl/Cargo.toml @@ -18,7 +18,7 @@ library = [] cw2 = { workspace = true } cw20 = { workspace = true } astroport = { workspace = true } -vesting-base = { workspace = true } +vesting-base-pcl = { workspace = true } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } diff --git a/contracts/vesting-lp-pcl/src/contract.rs b/contracts/vesting-lp-pcl/src/contract.rs index 2d3c1c80..9698b081 100644 --- a/contracts/vesting-lp-pcl/src/contract.rs +++ b/contracts/vesting-lp-pcl/src/contract.rs @@ -24,7 +24,12 @@ pub fn instantiate( VestingBaseBuilder::default() .historical() .with_managers(msg.vesting_managers) - .build(deps, msg.owner, msg.token_info_manager)?; + .build( + deps, + msg.owner, + msg.token_info_manager, + msg.xyk_vesting_lp_contract, + )?; Ok(Response::default()) } diff --git a/contracts/vesting-lp-pcl/src/msg.rs b/contracts/vesting-lp-pcl/src/msg.rs index b35ee7ff..6aa028ef 100644 --- a/contracts/vesting-lp-pcl/src/msg.rs +++ b/contracts/vesting-lp-pcl/src/msg.rs @@ -9,4 +9,5 @@ pub struct InstantiateMsg { pub vesting_managers: Vec, /// Token info manager address pub token_info_manager: String, + pub xyk_vesting_lp_contract: String, } diff --git a/packages/vesting-base-lp/src/handlers.rs b/packages/vesting-base-lp/src/handlers.rs index dbb61e36..f5a0fb38 100644 --- a/packages/vesting-base-lp/src/handlers.rs +++ b/packages/vesting-base-lp/src/handlers.rs @@ -9,8 +9,7 @@ use crate::state::{ use crate::state::{CONFIG, OWNERSHIP_PROPOSAL, VESTING_MANAGERS}; use crate::types::{ Config, MigrationState, OrderBy, VestingAccount, VestingAccountResponse, - VestingAccountsResponse, VestingInfo, VestingSchedule, VestingSchedulePoint, VestingState, - XykToClMigrationConfig, + VestingAccountsResponse, VestingInfo, VestingSchedule, VestingState, XykToClMigrationConfig, }; use astroport::asset::{ addr_opt_validate, native_asset, token_asset_info, AssetInfo, AssetInfoExt, PairInfo, @@ -51,11 +50,11 @@ pub fn execute( match &vesting_token { AssetInfo::NativeToken { denom } - if is_sender_whitelisted(deps.storage, &config, &info.sender) => - { - let amount = must_pay(&info, denom)?; - register_vesting_accounts(deps, vesting_accounts, amount, env.block.height) - } + if is_sender_whitelisted(deps.storage, &config, &info.sender) => + { + let amount = must_pay(&info, denom)?; + register_vesting_accounts(deps, vesting_accounts, amount, env.block.height) + } _ => Err(ContractError::Unauthorized {}), } } @@ -71,7 +70,7 @@ pub fn execute( config.owner, OWNERSHIP_PROPOSAL, ) - .map_err(Into::into) + .map_err(Into::into) } ExecuteMsg::DropOwnershipProposal {} => { let config: Config = CONFIG.load(deps.storage)?; @@ -88,7 +87,7 @@ pub fn execute( Ok(()) }) - .map_err(Into::into) + .map_err(Into::into) } ExecuteMsg::SetVestingToken { vesting_token } => { set_vesting_token(deps, env, info, vesting_token) @@ -100,7 +99,9 @@ pub fn execute( ExecuteMsg::HistoricalExtension { msg } => { handle_execute_historical_msg(deps, env, info, msg) } - ExecuteMsg::MigrateLiquidityToPCLPool {} => execute_migrate_liquidity(deps, info, env, None), + ExecuteMsg::MigrateLiquidityToPCLPool {} => { + execute_migrate_liquidity(deps, info, env, None) + } ExecuteMsg::Callback(msg) => _handle_callback(deps, env, info, msg), } } @@ -302,11 +303,12 @@ fn execute_migrate_liquidity( if migration_state == MigrationState::Completed { return Err(ContractError::MigrationComplete {}); } + let config = CONFIG.load(deps.storage)?; let migration_config: XykToClMigrationConfig = XYK_TO_CL_MIGRATION_CONFIG.load(deps.storage)?; let address = info.sender; let info = vesting_info(config.extensions.historical).load(deps.storage, address.clone())?; let mut resp = Response::default(); - let user = VestingAccountResponse{ address, info }; + let user = VestingAccountResponse { address, info }; // get pairs LP token addresses let pair_info: PairInfo = deps @@ -352,7 +354,7 @@ fn execute_migrate_liquidity( paired_asset_denom: migration_config.paired_denom.clone(), user, } - .to_cosmos_msg(&env)?, + .to_cosmos_msg(&env)?, ); Ok(resp) @@ -462,7 +464,7 @@ fn migrate_liquidity_to_cl_pair_callback( slippage_tolerance, user, } - .to_cosmos_msg(&env)?, + .to_cosmos_msg(&env)?, ); Ok(Response::default().add_messages(msgs)) @@ -526,8 +528,7 @@ fn post_migration_vesting_reschedule_callback( user: &VestingAccountResponse, ) -> Result { let config = CONFIG.load(deps.storage)?; - let mut migration_config: XykToClMigrationConfig = - XYK_TO_CL_MIGRATION_CONFIG.load(deps.storage)?; + let migration_config: XykToClMigrationConfig = XYK_TO_CL_MIGRATION_CONFIG.load(deps.storage)?; let balance_response: BalanceResponse = deps.querier.query_wasm_smart( &migration_config.new_lp_token, &Cw20QueryMsg::Balance { @@ -563,7 +564,6 @@ fn post_migration_vesting_reschedule_callback( end_point: new_end_point, }; - let vesting_info = vesting_info(config.extensions.historical); vesting_info.save( @@ -585,23 +585,23 @@ fn post_migration_vesting_reschedule_callback( Ok(state) }, )?; + let msgs = vec![CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: migration_config.new_lp_token.to_string(), + funds: vec![], + msg: to_binary(&Cw20ExecuteMsg::Send { + contract: migration_config.pcl_vesting.to_string(), + amount: current_balance, + msg: to_binary(&vesting_base_pcl::msg::Cw20HookMsg::MigrateXYKLiquidity { + user_address_raw: user.address.clone(), + user_vesting_info: vesting_base_pcl::types::VestingInfo { + schedules: vec![new_schedule], + released_amount: Uint128::zero(), + }, + })?, + })?, + })]; - migration_config.last_processed_user = Some(user.address.clone()); - XYK_TO_CL_MIGRATION_CONFIG.save(deps.storage, &migration_config)?; - - msgs.push(&Cw20ExecuteMsg::Send { - contract: migration_config.pcl_vesting.to_string(), - amount: current_balance, - msg: to_binary(&vesting_base_pcl::msg::ExecuteMsg::MigrateXYKLiquidity { - user_address_raw: user.address.to_string(), - user_vesting_info: vesting_base_pcl::types::VestingInfo { - schedules: vec![new_schedule], - released_amount: Uint128::zero() - } - })? - }); - - Ok(Response::default().add_messages(msgs)) + Ok(Response::new().add_messages(msgs)) } /// Exposes all the queries available in the contract. @@ -717,7 +717,7 @@ pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Result StdResult<()> { + pub fn build( + &self, + deps: DepsMut, + owner: String, + token_info_manager: String, + xyk_vesting_lp_contract: String, + ) -> StdResult<()> { let owner = deps.api.addr_validate(&owner)?; CONFIG.save( deps.storage, @@ -45,6 +51,7 @@ impl VestingBaseBuilder { managed: self.managed, with_managers: self.with_managers, }, + xyk_vesting_lp_contract: deps.api.addr_validate(&xyk_vesting_lp_contract)?, }, )?; diff --git a/packages/vesting-base-pcl/src/handlers.rs b/packages/vesting-base-pcl/src/handlers.rs index e401bca4..feca65e2 100644 --- a/packages/vesting-base-pcl/src/handlers.rs +++ b/packages/vesting-base-pcl/src/handlers.rs @@ -4,17 +4,19 @@ use crate::ext_managed::{handle_execute_managed_msg, handle_query_managed_msg}; use crate::ext_with_managers::{handle_execute_with_managers_msg, handle_query_managers_msg}; use crate::msg::{Cw20HookMsg, ExecuteMsg, MigrateMsg, QueryMsg}; use crate::state::{read_vesting_infos, vesting_info, vesting_state}; -use crate::state::{CONFIG, OWNERSHIP_PROPOSAL, VESTING_MANAGERS, PCL_VESTING_LP_CONTRACT}; +use crate::state::{CONFIG, OWNERSHIP_PROPOSAL, VESTING_MANAGERS}; use crate::types::{ Config, OrderBy, VestingAccount, VestingAccountResponse, VestingAccountsResponse, VestingInfo, VestingSchedule, VestingState, }; -use astroport::asset::{addr_opt_validate, token_asset_info, AssetInfo, AssetInfoExt, Asset}; +use astroport::asset::{addr_opt_validate, token_asset_info, AssetInfo, AssetInfoExt}; use astroport::common::{claim_ownership, drop_ownership_proposal, propose_new_owner}; -use cosmwasm_std::{attr, from_binary, to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Storage, SubMsg, Uint128, CosmosMsg, Empty, WasmMsg}; +use cosmwasm_std::{ + attr, from_binary, to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, + StdError, StdResult, Storage, SubMsg, Uint128, +}; use cw20::Cw20ReceiveMsg; use cw_utils::must_pay; -use astroport::pair::ExecuteMsg::ProvideLiquidity; /// Exposes execute functions available in the contract. pub fn execute( @@ -81,9 +83,6 @@ pub fn execute( ExecuteMsg::HistoricalExtension { msg } => { handle_execute_historical_msg(deps, env, info, msg) } - ExecuteMsg::MigrateXYKLiquidity { user_address_raw, user_vesting_info } => { - handle_migrate_xyk_liquidity(deps, info, env, user_address_raw, user_vesting_info) - } } } @@ -113,6 +112,10 @@ fn receive_cw20( Cw20HookMsg::RegisterVestingAccounts { vesting_accounts } => { register_vesting_accounts(deps, vesting_accounts, cw20_msg.amount, env.block.height) } + Cw20HookMsg::MigrateXYKLiquidity { + user_address_raw, + user_vesting_info, + } => handle_migrate_xyk_liquidity(deps, env, user_address_raw, user_vesting_info), } } @@ -254,16 +257,17 @@ fn claim( fn handle_migrate_xyk_liquidity( deps: DepsMut, env: Env, - _info: MessageInfo, user_addr_raw: Addr, user_vesting_info: VestingInfo, ) -> Result { let height = env.block.height; + let config = CONFIG.load(deps.storage)?; - let account_address = deps.api.addr_validate(&user_addr_raw.address)?; + let account_address = user_addr_raw; assert_vesting_schedules(&account_address, &user_vesting_info.schedules)?; + let mut to_deposit = Uint128::zero(); for sch in &user_vesting_info.schedules { let amount = if let Some(end_point) = &sch.end_point { end_point.amount @@ -273,15 +277,9 @@ fn handle_migrate_xyk_liquidity( to_deposit = to_deposit.checked_add(amount)?; } - let vesting_info = vesting_info(config.extensions.historical); - vesting_info.save( - deps.storage, - account_address, - &user_vesting_info, - height, - )?; + vesting_info.save(deps.storage, account_address, &user_vesting_info, height)?; let mut to_deposit = Uint128::zero(); for sch in &user_vesting_info.schedules { @@ -293,10 +291,9 @@ fn handle_migrate_xyk_liquidity( to_deposit = to_deposit.checked_add(amount)?; } - vesting_state(config.extensions.historical).update::<_, ContractError>( deps.storage, - height.clone(), + height, |s| { let mut state = s.unwrap_or_default(); state.total_granted = state.total_granted.checked_add(to_deposit)?; @@ -305,7 +302,6 @@ fn handle_migrate_xyk_liquidity( )?; Ok(Response::default()) - } pub(crate) fn set_vesting_token( @@ -445,9 +441,13 @@ fn is_sender_whitelisted(store: &mut dyn Storage, config: &Config, sender: &Addr if *sender == config.owner { return true; } + if *sender == config.xyk_vesting_lp_contract { + return true; + } if VESTING_MANAGERS.has(store, sender.clone()) { return true; } + false } diff --git a/packages/vesting-base-pcl/src/msg.rs b/packages/vesting-base-pcl/src/msg.rs index 0b434988..c3607001 100644 --- a/packages/vesting-base-pcl/src/msg.rs +++ b/packages/vesting-base-pcl/src/msg.rs @@ -1,4 +1,7 @@ -use crate::types::{Config, OrderBy, VestingAccount, VestingAccountResponse, VestingAccountsResponse, VestingInfo, VestingState}; +use crate::types::{ + Config, OrderBy, VestingAccount, VestingAccountResponse, VestingAccountsResponse, VestingInfo, + VestingState, +}; use astroport::asset::AssetInfo; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, Binary, Uint128}; @@ -47,14 +50,6 @@ pub enum ExecuteMsg { WithManagersExtension { msg: ExecuteMsgWithManagers }, /// Contains messages associated with the historical extension for vesting contracts. HistoricalExtension { msg: ExecuteMsgHistorical }, - /// A handler to receive vesting liquidity migrated from xyl pools to PCL ones. Only callable - /// by the original vesting-lp contract. - #[serde(rename = "migrate_xyk_liquidity")] - MigrateXYKLiquidity { - /// The address of the user which owns the lockup. - user_address_raw: String, - user_vesting_info: VestingInfo, - }, } /// This structure describes the execute messages available in a managed vesting contract. @@ -163,4 +158,10 @@ pub enum Cw20HookMsg { RegisterVestingAccounts { vesting_accounts: Vec, }, + #[serde(rename = "migrate_xyk_liquidity")] + MigrateXYKLiquidity { + /// The address of the user which owns the vested tokens. + user_address_raw: Addr, + user_vesting_info: VestingInfo, + }, } diff --git a/packages/vesting-base-pcl/src/state.rs b/packages/vesting-base-pcl/src/state.rs index 16c819ec..978fd8c6 100644 --- a/packages/vesting-base-pcl/src/state.rs +++ b/packages/vesting-base-pcl/src/state.rs @@ -6,9 +6,6 @@ use cw_storage_plus::{Bound, Item, Map, SnapshotItem, SnapshotMap, Strategy}; pub(crate) const CONFIG: Item = Item::new("config"); pub(crate) const OWNERSHIP_PROPOSAL: Item = Item::new("ownership_proposal"); pub(crate) const VESTING_MANAGERS: Map = Map::new("vesting_managers"); -/// The address of the vesting lp contract working with PCL pools. Used in users' locked liquidity -/// migration from XYK pools to PCL ones. -pub const PCL_VESTING_LP_CONTRACT: Item = Item::new("pcl_vesting_lp_contract"); pub(crate) const VESTING_STATE: SnapshotItem = SnapshotItem::new( "vesting_state", "vesting_state__checkpoints", diff --git a/packages/vesting-base-pcl/src/types.rs b/packages/vesting-base-pcl/src/types.rs index eb0eb089..578aaf03 100644 --- a/packages/vesting-base-pcl/src/types.rs +++ b/packages/vesting-base-pcl/src/types.rs @@ -9,8 +9,11 @@ pub struct Config { pub owner: Addr, /// [`AssetInfo`] of the vested token pub vesting_token: Option, + /// Address that's allowed to change vesting token + pub token_info_manager: Addr, /// Contains extensions information of the contract pub extensions: Extensions, + pub xyk_vesting_lp_contract: Addr, } /// Contains extensions information for the contract. From 06df7cc86a39632b5f331e8f867d8658704f9002 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Thu, 1 Feb 2024 10:58:28 -0300 Subject: [PATCH 04/30] rm unused, commit updated sttuff, fmt --- Cargo.lock | 17 +------------ Cargo.toml | 1 - contracts/auction/README.md | 12 ++++----- contracts/lockdrop/README.md | 12 ++++----- contracts/vesting-investors/Cargo.toml | 2 +- contracts/vesting-lp-pcl/Cargo.toml | 2 +- contracts/vesting-lp-pcl/src/contract.rs | 1 + contracts/vesting-lp-pcl/src/msg.rs | 2 ++ contracts/vesting-lp/Cargo.toml | 2 +- packages/vesting-base-lp/src/handlers.rs | 32 +++++++----------------- packages/vesting-base-lp/src/msg.rs | 2 +- packages/vesting-base-lp/src/state.rs | 4 +-- packages/vesting-base-lp/src/types.rs | 8 ------ packages/vesting-base-pcl/src/builder.rs | 4 ++- 14 files changed, 33 insertions(+), 68 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bad6b6a8..8c3b50ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1026,21 +1026,6 @@ dependencies = [ "serde", ] -[[package]] -name = "neutron-lockdrop-pcl" -version = "1.2.1" -dependencies = [ - "astroport 2.5.0 (git+https://github.com/astroport-fi/astroport-core.git?tag=v2.5.0)", - "astroport-periphery", - "cosmwasm-schema", - "cosmwasm-std", - "credits", - "cw-storage-plus 0.15.1", - "cw2 1.1.1", - "cw20 1.1.1", - "serde", -] - [[package]] name = "neutron-price-feed" version = "0.1.0" @@ -1599,7 +1584,7 @@ dependencies = [ "cw-utils 0.15.1", "cw2 1.1.1", "cw20 1.1.1", - "vesting-base", + "vesting-base-pcl", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a0156b36..d688c45e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,6 @@ members = [ "contracts/auction", "contracts/lockdrop", - "contracts/lockdrop-pcl", "contracts/credits", "contracts/vesting-lp", "contracts/vesting-lp-pcl", diff --git a/contracts/auction/README.md b/contracts/auction/README.md index c43ae90c..b96204d5 100644 --- a/contracts/auction/README.md +++ b/contracts/auction/README.md @@ -5,8 +5,8 @@ The LP Bootstrap via auction contract facilitates cNTRN-NATIVE Neutron pool init **Phase 1 :: Bootstrapping cNTRN and NATIVE Side of the LP Pool** - Airdrop recipients and lockdrop participants can delegate part / all of their cNTRN rewards to the auction contract. -- Any user can deposit UST directly to the auction contract to participate in the LP bootstrap auction. -- Both UST deposited & cNTRN delegated (if any) balances are used to calculate user's LP token shares and additional cNTRN incentives that he will receive for participating in the auction. +- Any _user can deposit UST directly to the auction contract to participate in the LP bootstrap auction. +- Both UST deposited & cNTRN delegated (if any) balances are used to calculate _user's LP token shares and additional cNTRN incentives that he will receive for participating in the auction. **Phase 2 :: Post cNTRN-NATIVE Pool initialization** @@ -24,10 +24,10 @@ The LP Bootstrap via auction contract facilitates cNTRN-NATIVE Neutron pool init | `ExecuteMsg::Receive` | ReceiveCW20 Hook which facilitates cNTRN tokens delegation by lockdrop participants / airdrop recipients | | `ExecuteMsg::UpdateConfig` | Admin function to update any of the configuration parameters. | | `ExecuteMsg::DepositUst` | Facilitates UST deposits by users | -| `ExecuteMsg::WithdrawUst` | Facilitates UST withdrawals by users. 100% amount can be withdrawn during deposit window, which is then limited to 50% during 1st half of deposit window which then decreases linearly during 2nd half of deposit window. Only 1 withdrawal can be made by a user during the withdrawal window | +| `ExecuteMsg::WithdrawUst` | Facilitates UST withdrawals by users. 100% amount can be withdrawn during deposit window, which is then limited to 50% during 1st half of deposit window which then decreases linearly during 2nd half of deposit window. Only 1 withdrawal can be made by a _user during the withdrawal window | | `ExecuteMsg::InitPool` | Admin function which facilitates Liquidity addtion to the Astroport cNTRN-UST Pool. Uses CallbackMsg to update state post liquidity addition to the pool | | `ExecuteMsg::StakeLpTokens` | Admin function to stake cNTRN-UST LP tokens with the generator contract | -| `ExecuteMsg::ClaimRewards` | Facilitates cNTRN rewards claim (staking incentives from generator) for users and the withdrawal of LP shares which have been unlocked for the user. | +| `ExecuteMsg::ClaimRewards` | Facilitates cNTRN rewards claim (staking incentives from generator) for users and the withdrawal of LP shares which have been unlocked for the _user. | ### Handle Messages :: Callback @@ -35,7 +35,7 @@ The LP Bootstrap via auction contract facilitates cNTRN-NATIVE Neutron pool init | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | `CallbackMsg::UpdateStateOnLiquidityAdditionToPool` | Callback function to update state after liquidity is added to the cNTRN-UST Pool | | `CallbackMsg::UpdateStateOnRewardClaim` | Callback function to update state after cNTRN rewards are claimed from the generator | -| `CallbackMsg::WithdrawUserRewardsCallback` | Callback function to facilitate cNTRN reward claiming and unlocked LP tokens withdrawal for the user | +| `CallbackMsg::WithdrawUserRewardsCallback` | Callback function to facilitate cNTRN reward claiming and unlocked LP tokens withdrawal for the _user | ### Query Messages @@ -43,7 +43,7 @@ The LP Bootstrap via auction contract facilitates cNTRN-NATIVE Neutron pool init | -------------------- | ----------------------------- | | `QueryMsg::Config` | Returns the config info | | `QueryMsg::State` | Returns state of the contract | -| `QueryMsg::UserInfo` | Returns user position details | +| `QueryMsg::UserInfo` | Returns _user position details | ## Build schema and run unit-tests diff --git a/contracts/lockdrop/README.md b/contracts/lockdrop/README.md index 0bc572b1..2bea03c0 100644 --- a/contracts/lockdrop/README.md +++ b/contracts/lockdrop/README.md @@ -16,14 +16,14 @@ Note - Users can open muliple lockup positions with different lockup duration fo | `ExecuteMsg::EnableClaims` | Executed by the Bootstrap auction contract when liquidity is added to the ASTRO-UST pool. Enables ASTRO withdrawals by the lockdrop recipients. | | `ExecuteMsg::InitializePool` | Admin function. Facilitates addition of new Pool (Terraswap Pools) whose LP tokens can then be locked in the lockdrop contract | | `ExecuteMsg::UpdatePool` | Admin function to update any configuraton parameter for a terraswap pool whose LP tokens are currently accepted for the lockdrop | -| `ExecuteMsg::IncreaseLockup` | Facilitates opening a new user position or adding to an existing position | +| `ExecuteMsg::IncreaseLockup` | Facilitates opening a new _user position or adding to an existing position | | `ExecuteMsg::IncreaseAstroIncentives` | Admin function to increase the ASTRO incentives that are to be distributed | -| `ExecuteMsg::WithdrawFromLockup` | Facilitates LP token withdrawals from lockup positions by users. 100% amount can be withdrawn during deposit window, which is then limited to 50% during 1st half of deposit window which then decreases linearly during 2nd half of deposit window. Only 1 withdrawal can be made by a user during the withdrawal windows | +| `ExecuteMsg::WithdrawFromLockup` | Facilitates LP token withdrawals from lockup positions by users. 100% amount can be withdrawn during deposit window, which is then limited to 50% during 1st half of deposit window which then decreases linearly during 2nd half of deposit window. Only 1 withdrawal can be made by a _user during the withdrawal windows | | `ExecuteMsg::MigrateLiquidity` | Admin function. Facilitates migration of liquidity (locked terraswap LP tokens) from Terraswap to Astroport (Astroport LP tokens) | | `ExecuteMsg::StakeLpTokens` | Admin function. Facilitates staking of Astroport LP tokens for a particular LP pool with the generator contract | -| `ExecuteMsg::DelegateAstroToAuction` | This function facilitates ASTRO tokens delegation to the Bootstrap auction contract during the bootstrap auction phase. Delegated ASTRO tokens are added to the user's position in the bootstrap auction contract | +| `ExecuteMsg::DelegateAstroToAuction` | This function facilitates ASTRO tokens delegation to the Bootstrap auction contract during the bootstrap auction phase. Delegated ASTRO tokens are added to the _user's position in the bootstrap auction contract | | `ExecuteMsg::ClaimRewardsAndOptionallyUnlock` | Facilitates rewards claim by users for a particular lockup position along with unlock when possible | -| `ExecuteMsg::ClaimAssetReward` | Collects assets reward from LP and distribute reward to user if all requirements are met | +| `ExecuteMsg::ClaimAssetReward` | Collects assets reward from LP and distribute reward to _user if all requirements are met | | `ExecuteMsg::TogglePoolRewards` | Admin function. Enables assets reward for specified LP | | `ExecuteMsg::ProposeNewOwner` | Admin function. Creates an offer to change the contract ownership. The validity period of the offer is set in the `expires_in` variable. After `expires_in` seconds pass, the proposal expires and cannot be accepted anymore. | | `ExecuteMsg::DropOwnershipProposal` | Admin function. Removes an existing offer to change the contract owner. | @@ -34,7 +34,7 @@ Note - Users can open muliple lockup positions with different lockup duration fo | Message | Description | |-------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------| | `CallbackMsg::UpdatePoolOnDualRewardsClaim` | Callback function to update contract state after pending dual staking rewards are claimed from the generator contract | -| `CallbackMsg::WithdrawUserLockupRewardsCallback` | Callback function to withdraw user rewards for a particular lockcup position along with optional LP tokens withdrawal (upon lockup duration expiration) | +| `CallbackMsg::WithdrawUserLockupRewardsCallback` | Callback function to withdraw _user rewards for a particular lockcup position along with optional LP tokens withdrawal (upon lockup duration expiration) | | `CallbackMsg::WithdrawLiquidityFromTerraswapCallback` | Callback function used during liquidity migration to update state after liquidity is removed from terraswap | | `CallbackMsg::DistributeAssetReward` | Callback function used for assets reward distribution after rewards claiming from LP | @@ -45,7 +45,7 @@ Note - Users can open muliple lockup positions with different lockup duration fo | `QueryMsg::Config` | Returns the config info | | `QueryMsg::State` | Returns the contract's global state | | `QueryMsg::Pool` | Returns info regarding a certain supported LP token pool | -| `QueryMsg::UserInfo` | Returns info regarding a user (total ASTRO rewards, list of lockup positions) | +| `QueryMsg::UserInfo` | Returns info regarding a _user (total ASTRO rewards, list of lockup positions) | | `QueryMsg::LockUpInfo` | Returns info regarding a particular lockup position with a given duration and identifer for the LP tokens locked | | `QueryMsg::PendingAssetReward` | Returns the amount of pending asset rewards for the specified recipient and for a specific lockup position | diff --git a/contracts/vesting-investors/Cargo.toml b/contracts/vesting-investors/Cargo.toml index d05d5262..b6f70eab 100644 --- a/contracts/vesting-investors/Cargo.toml +++ b/contracts/vesting-investors/Cargo.toml @@ -3,7 +3,7 @@ name = "vesting-investors" version = "1.1.1" authors = ["Neutron"] edition = "2021" -description = "Vesting contract which provides queries to get the amount of tokens that are being held by user at certain height and allows to remove vesting accounts to the contract's owner." +description = "Vesting contract which provides queries to get the amount of tokens that are being held by _user at certain height and allows to remove vesting accounts to the contract's owner." [lib] crate-type = ["cdylib", "rlib"] diff --git a/contracts/vesting-lp-pcl/Cargo.toml b/contracts/vesting-lp-pcl/Cargo.toml index b3067a3a..4046c5b3 100644 --- a/contracts/vesting-lp-pcl/Cargo.toml +++ b/contracts/vesting-lp-pcl/Cargo.toml @@ -3,7 +3,7 @@ name = "vesting-lp-pcl" version = "1.1.0" authors = ["Neutron"] edition = "2021" -description = "Vesting contract with a voting capabilities. Provides queries to get the amount of tokens are being held by user at certain height." +description = "Vesting contract with a voting capabilities. Provides queries to get the amount of tokens are being held by _user at certain height." [lib] crate-type = ["cdylib", "rlib"] diff --git a/contracts/vesting-lp-pcl/src/contract.rs b/contracts/vesting-lp-pcl/src/contract.rs index 9698b081..0eaecbf1 100644 --- a/contracts/vesting-lp-pcl/src/contract.rs +++ b/contracts/vesting-lp-pcl/src/contract.rs @@ -29,6 +29,7 @@ pub fn instantiate( msg.owner, msg.token_info_manager, msg.xyk_vesting_lp_contract, + msg.vesting_token, )?; Ok(Response::default()) } diff --git a/contracts/vesting-lp-pcl/src/msg.rs b/contracts/vesting-lp-pcl/src/msg.rs index 6aa028ef..4b40f2eb 100644 --- a/contracts/vesting-lp-pcl/src/msg.rs +++ b/contracts/vesting-lp-pcl/src/msg.rs @@ -1,4 +1,5 @@ use cosmwasm_schema::cw_serde; +use astroport::asset::AssetInfo; /// This structure describes the parameters used for creating a contract. #[cw_serde] @@ -10,4 +11,5 @@ pub struct InstantiateMsg { /// Token info manager address pub token_info_manager: String, pub xyk_vesting_lp_contract: String, + pub vesting_token: AssetInfo, } diff --git a/contracts/vesting-lp/Cargo.toml b/contracts/vesting-lp/Cargo.toml index ad739e0e..93d7020a 100644 --- a/contracts/vesting-lp/Cargo.toml +++ b/contracts/vesting-lp/Cargo.toml @@ -3,7 +3,7 @@ name = "vesting-lp" version = "1.1.0" authors = ["Neutron"] edition = "2021" -description = "Vesting contract with a voting capabilities. Provides queries to get the amount of tokens are being held by user at certain height." +description = "Vesting contract with a voting capabilities. Provides queries to get the amount of tokens are being held by _user at certain height." [lib] crate-type = ["cdylib", "rlib"] diff --git a/packages/vesting-base-lp/src/handlers.rs b/packages/vesting-base-lp/src/handlers.rs index f5a0fb38..4fb43d5c 100644 --- a/packages/vesting-base-lp/src/handlers.rs +++ b/packages/vesting-base-lp/src/handlers.rs @@ -4,11 +4,11 @@ use crate::ext_managed::{handle_execute_managed_msg, handle_query_managed_msg}; use crate::ext_with_managers::{handle_execute_with_managers_msg, handle_query_managers_msg}; use crate::msg::{CallbackMsg, Cw20HookMsg, ExecuteMsg, MigrateMsg, QueryMsg}; use crate::state::{ - read_vesting_infos, vesting_info, vesting_state, MIGRATION_STATUS, XYK_TO_CL_MIGRATION_CONFIG, + read_vesting_infos, vesting_info, vesting_state, XYK_TO_CL_MIGRATION_CONFIG, }; use crate::state::{CONFIG, OWNERSHIP_PROPOSAL, VESTING_MANAGERS}; use crate::types::{ - Config, MigrationState, OrderBy, VestingAccount, VestingAccountResponse, + Config, OrderBy, VestingAccount, VestingAccountResponse, VestingAccountsResponse, VestingInfo, VestingSchedule, VestingState, XykToClMigrationConfig, }; use astroport::asset::{ @@ -33,14 +33,6 @@ pub fn execute( info: MessageInfo, msg: ExecuteMsg, ) -> Result { - let migration_state: MigrationState = MIGRATION_STATUS.load(deps.storage)?; - if migration_state != MigrationState::Completed { - match msg { - ExecuteMsg::MigrateLiquidityToPCLPool {} => {} - ExecuteMsg::Callback(..) => {} - _ => return Err(ContractError::MigrationIncomplete {}), - } - } match msg { ExecuteMsg::Claim { recipient, amount } => claim(deps, env, info, recipient, amount), ExecuteMsg::Receive(msg) => receive_cw20(deps, env, info, msg), @@ -99,8 +91,8 @@ pub fn execute( ExecuteMsg::HistoricalExtension { msg } => { handle_execute_historical_msg(deps, env, info, msg) } - ExecuteMsg::MigrateLiquidityToPCLPool {} => { - execute_migrate_liquidity(deps, info, env, None) + ExecuteMsg::MigrateLiquidityToPCLPool { user } => { + execute_migrate_liquidity(deps, info, env, None, user) } ExecuteMsg::Callback(msg) => _handle_callback(deps, env, info, msg), } @@ -298,14 +290,14 @@ fn execute_migrate_liquidity( info: MessageInfo, env: Env, slippage_tolerance: Option, + user: Option ) -> Result { - let migration_state: MigrationState = MIGRATION_STATUS.load(deps.storage)?; - if migration_state == MigrationState::Completed { - return Err(ContractError::MigrationComplete {}); - } let config = CONFIG.load(deps.storage)?; let migration_config: XykToClMigrationConfig = XYK_TO_CL_MIGRATION_CONFIG.load(deps.storage)?; - let address = info.sender; + let address = match user { + Some(val) => deps.api.addr_validate(&val)?, + None => info.sender, + }; let info = vesting_info(config.extensions.historical).load(deps.storage, address.clone())?; let mut resp = Response::default(); let user = VestingAccountResponse { address, info }; @@ -606,10 +598,6 @@ fn post_migration_vesting_reschedule_callback( /// Exposes all the queries available in the contract. pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - let migration_state: MigrationState = MIGRATION_STATUS.load(deps.storage)?; - if migration_state != MigrationState::Completed { - return Err(ContractError::MigrationIncomplete {}.into()); - } match msg { QueryMsg::Config {} => Ok(to_binary(&query_config(deps)?)?), @@ -736,8 +724,6 @@ pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Result }, /// Callbacks; only callable by the contract itself. Callback(CallbackMsg), } diff --git a/packages/vesting-base-lp/src/state.rs b/packages/vesting-base-lp/src/state.rs index 28017fc0..de7c443a 100644 --- a/packages/vesting-base-lp/src/state.rs +++ b/packages/vesting-base-lp/src/state.rs @@ -1,13 +1,11 @@ use crate::types::{ - Config, MigrationState, OrderBy, VestingInfo, VestingState, XykToClMigrationConfig, + Config, OrderBy, VestingInfo, VestingState, XykToClMigrationConfig, }; use astroport::common::OwnershipProposal; use cosmwasm_std::{Addr, Deps, StdResult}; use cw_storage_plus::{Bound, Item, Map, SnapshotItem, SnapshotMap, Strategy}; pub(crate) const CONFIG: Item = Item::new("config"); -/// Migration status -pub(crate) const MIGRATION_STATUS: Item = Item::new("migration_status"); pub(crate) const OWNERSHIP_PROPOSAL: Item = Item::new("ownership_proposal"); pub(crate) const VESTING_MANAGERS: Map = Map::new("vesting_managers"); pub(crate) const VESTING_STATE: SnapshotItem = SnapshotItem::new( diff --git a/packages/vesting-base-lp/src/types.rs b/packages/vesting-base-lp/src/types.rs index cb89ec3e..feb834ff 100644 --- a/packages/vesting-base-lp/src/types.rs +++ b/packages/vesting-base-lp/src/types.rs @@ -101,14 +101,6 @@ pub struct XykToClMigrationConfig { pub pcl_vesting: Addr, } -#[cw_serde] -pub enum MigrationState { - /// Migration is started - Started, - - Completed, -} - /// This enum describes the types of sorting that can be applied to some piece of data #[cw_serde] pub enum OrderBy { diff --git a/packages/vesting-base-pcl/src/builder.rs b/packages/vesting-base-pcl/src/builder.rs index 6775d6d0..46dc0e7d 100644 --- a/packages/vesting-base-pcl/src/builder.rs +++ b/packages/vesting-base-pcl/src/builder.rs @@ -1,6 +1,7 @@ use crate::state::{CONFIG, VESTING_MANAGERS}; use crate::types::{Config, Extensions}; use cosmwasm_std::{DepsMut, StdResult}; +use astroport::asset::AssetInfo; /// A builder for vesting contracts with different extensions. #[derive(Default)] @@ -38,13 +39,14 @@ impl VestingBaseBuilder { owner: String, token_info_manager: String, xyk_vesting_lp_contract: String, + vesting_token: AssetInfo, ) -> StdResult<()> { let owner = deps.api.addr_validate(&owner)?; CONFIG.save( deps.storage, &Config { owner, - vesting_token: None, + vesting_token: Option::from(vesting_token), token_info_manager: deps.api.addr_validate(&token_info_manager)?, extensions: Extensions { historical: self.historical, From 18cbcceba064c763e007837d9b50cca4d8571f33 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Thu, 1 Feb 2024 11:27:19 -0300 Subject: [PATCH 05/30] fix int-tests --- .../examples/vesting-lp_schema.rs | 4 +- contracts/vesting-lp-pcl/src/msg.rs | 2 +- .../vesting-lp-pcl/src/tests/integration.rs | 26 +- packages/vesting-base-lp/src/handlers.rs | 11 +- packages/vesting-base-lp/src/state.rs | 4 +- packages/vesting-base-pcl/src/builder.rs | 2 +- packages/vesting-base-pcl/src/lib.rs | 3 - packages/vesting-base-pcl/src/testing.rs | 484 ------------------ 8 files changed, 16 insertions(+), 520 deletions(-) delete mode 100644 packages/vesting-base-pcl/src/testing.rs diff --git a/contracts/vesting-lp-pcl/examples/vesting-lp_schema.rs b/contracts/vesting-lp-pcl/examples/vesting-lp_schema.rs index 90284134..6f1967f3 100644 --- a/contracts/vesting-lp-pcl/examples/vesting-lp_schema.rs +++ b/contracts/vesting-lp-pcl/examples/vesting-lp_schema.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::write_api; -use vesting_base::msg::{ExecuteMsg, MigrateMsg, QueryMsg}; -use vesting_lp::msg::InstantiateMsg; +use vesting_base_pcl::msg::{ExecuteMsg, MigrateMsg, QueryMsg}; +use vesting_lp_pcl::msg::InstantiateMsg; fn main() { write_api! { diff --git a/contracts/vesting-lp-pcl/src/msg.rs b/contracts/vesting-lp-pcl/src/msg.rs index 4b40f2eb..69bebd7e 100644 --- a/contracts/vesting-lp-pcl/src/msg.rs +++ b/contracts/vesting-lp-pcl/src/msg.rs @@ -1,5 +1,5 @@ -use cosmwasm_schema::cw_serde; use astroport::asset::AssetInfo; +use cosmwasm_schema::cw_serde; /// This structure describes the parameters used for creating a contract. #[cw_serde] diff --git a/contracts/vesting-lp-pcl/src/tests/integration.rs b/contracts/vesting-lp-pcl/src/tests/integration.rs index 5a150e97..a8f2a6a4 100644 --- a/contracts/vesting-lp-pcl/src/tests/integration.rs +++ b/contracts/vesting-lp-pcl/src/tests/integration.rs @@ -6,12 +6,12 @@ use cosmwasm_std::{coin, coins, to_binary, Addr, StdResult, Timestamp, Uint128}; use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg, MinterResponse}; use cw_multi_test::{App, ContractWrapper, Executor}; use cw_utils::PaymentError; -use vesting_base::error::ContractError; -use vesting_base::msg::{ +use vesting_base_pcl::error::ContractError; +use vesting_base_pcl::msg::{ Cw20HookMsg, ExecuteMsg, ExecuteMsgWithManagers, QueryMsg, QueryMsgHistorical, QueryMsgWithManagers, }; -use vesting_base::types::{ +use vesting_base_pcl::types::{ Config, VestingAccount, VestingAccountResponse, VestingSchedule, VestingSchedulePoint, }; @@ -1195,7 +1195,9 @@ fn instantiate_vesting(app: &mut App, cw20_token_instance: &Addr) -> Addr { let init_msg = InstantiateMsg { owner: OWNER1.to_string(), token_info_manager: TOKEN_MANAGER.to_string(), + xyk_vesting_lp_contract: "test".to_string(), vesting_managers: vec![], + vesting_token: token_asset_info(cw20_token_instance.clone()), }; let vesting_instance = app @@ -1208,17 +1210,6 @@ fn instantiate_vesting(app: &mut App, cw20_token_instance: &Addr) -> Addr { None, ) .unwrap(); - let set_vesting_token_msg = ExecuteMsg::SetVestingToken { - vesting_token: token_asset_info(cw20_token_instance.clone()), - }; - app.execute_contract( - token_manager, - vesting_instance.clone(), - &set_vesting_token_msg, - &[], - ) - .unwrap(); - let res: Config = app .wrap() .query_wasm_smart(vesting_instance.clone(), &QueryMsg::Config {}) @@ -1249,16 +1240,13 @@ fn instantiate_vesting_remote_chain(app: &mut App) -> Addr { owner: OWNER1.to_string(), token_info_manager: TOKEN_MANAGER.to_string(), vesting_managers: vec![], + vesting_token: native_asset_info(VESTING_TOKEN.to_string()), + xyk_vesting_lp_contract: "test".to_string(), }; let res = app .instantiate_contract(vesting_code_id, owner, &init_msg, &[], "Vesting", None) .unwrap(); - let msg = ExecuteMsg::SetVestingToken { - vesting_token: native_asset_info(VESTING_TOKEN.to_string()), - }; - app.execute_contract(token_manager, res.clone(), &msg, &[]) - .unwrap(); res } diff --git a/packages/vesting-base-lp/src/handlers.rs b/packages/vesting-base-lp/src/handlers.rs index 4fb43d5c..45765510 100644 --- a/packages/vesting-base-lp/src/handlers.rs +++ b/packages/vesting-base-lp/src/handlers.rs @@ -3,13 +3,11 @@ use crate::ext_historical::{handle_execute_historical_msg, handle_query_historic use crate::ext_managed::{handle_execute_managed_msg, handle_query_managed_msg}; use crate::ext_with_managers::{handle_execute_with_managers_msg, handle_query_managers_msg}; use crate::msg::{CallbackMsg, Cw20HookMsg, ExecuteMsg, MigrateMsg, QueryMsg}; -use crate::state::{ - read_vesting_infos, vesting_info, vesting_state, XYK_TO_CL_MIGRATION_CONFIG, -}; +use crate::state::{read_vesting_infos, vesting_info, vesting_state, XYK_TO_CL_MIGRATION_CONFIG}; use crate::state::{CONFIG, OWNERSHIP_PROPOSAL, VESTING_MANAGERS}; use crate::types::{ - Config, OrderBy, VestingAccount, VestingAccountResponse, - VestingAccountsResponse, VestingInfo, VestingSchedule, VestingState, XykToClMigrationConfig, + Config, OrderBy, VestingAccount, VestingAccountResponse, VestingAccountsResponse, VestingInfo, + VestingSchedule, VestingState, XykToClMigrationConfig, }; use astroport::asset::{ addr_opt_validate, native_asset, token_asset_info, AssetInfo, AssetInfoExt, PairInfo, @@ -290,7 +288,7 @@ fn execute_migrate_liquidity( info: MessageInfo, env: Env, slippage_tolerance: Option, - user: Option + user: Option, ) -> Result { let config = CONFIG.load(deps.storage)?; let migration_config: XykToClMigrationConfig = XYK_TO_CL_MIGRATION_CONFIG.load(deps.storage)?; @@ -598,7 +596,6 @@ fn post_migration_vesting_reschedule_callback( /// Exposes all the queries available in the contract. pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - match msg { QueryMsg::Config {} => Ok(to_binary(&query_config(deps)?)?), QueryMsg::VestingAccount { address } => { diff --git a/packages/vesting-base-lp/src/state.rs b/packages/vesting-base-lp/src/state.rs index de7c443a..db3cac06 100644 --- a/packages/vesting-base-lp/src/state.rs +++ b/packages/vesting-base-lp/src/state.rs @@ -1,6 +1,4 @@ -use crate::types::{ - Config, OrderBy, VestingInfo, VestingState, XykToClMigrationConfig, -}; +use crate::types::{Config, OrderBy, VestingInfo, VestingState, XykToClMigrationConfig}; use astroport::common::OwnershipProposal; use cosmwasm_std::{Addr, Deps, StdResult}; use cw_storage_plus::{Bound, Item, Map, SnapshotItem, SnapshotMap, Strategy}; diff --git a/packages/vesting-base-pcl/src/builder.rs b/packages/vesting-base-pcl/src/builder.rs index 46dc0e7d..ccb84162 100644 --- a/packages/vesting-base-pcl/src/builder.rs +++ b/packages/vesting-base-pcl/src/builder.rs @@ -1,7 +1,7 @@ use crate::state::{CONFIG, VESTING_MANAGERS}; use crate::types::{Config, Extensions}; -use cosmwasm_std::{DepsMut, StdResult}; use astroport::asset::AssetInfo; +use cosmwasm_std::{DepsMut, StdResult}; /// A builder for vesting contracts with different extensions. #[derive(Default)] diff --git a/packages/vesting-base-pcl/src/lib.rs b/packages/vesting-base-pcl/src/lib.rs index 2de7b374..594d5e36 100644 --- a/packages/vesting-base-pcl/src/lib.rs +++ b/packages/vesting-base-pcl/src/lib.rs @@ -8,6 +8,3 @@ pub mod types; pub(crate) mod ext_historical; pub(crate) mod ext_managed; pub(crate) mod ext_with_managers; - -#[cfg(test)] -mod testing; diff --git a/packages/vesting-base-pcl/src/testing.rs b/packages/vesting-base-pcl/src/testing.rs deleted file mode 100644 index 3d11af41..00000000 --- a/packages/vesting-base-pcl/src/testing.rs +++ /dev/null @@ -1,484 +0,0 @@ -use crate::builder::VestingBaseBuilder; -use crate::error::{ext_unsupported_err, ContractError}; -use crate::handlers::{execute, query}; -use crate::msg::{ - ExecuteMsg, ExecuteMsgManaged, QueryMsg, QueryMsgHistorical, QueryMsgWithManagers, -}; -use crate::types::{Config, Extensions}; -use astroport::asset::token_asset_info; -use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; -use cosmwasm_std::{from_binary, Addr}; - -#[test] -fn set_vesting_token() { - let mut deps = mock_dependencies(); - let owner = String::from("owner"); - let token_info_manager = "token_info_manager"; - let env = mock_env(); - VestingBaseBuilder::default() - .build(deps.as_mut(), owner, String::from(token_info_manager)) - .unwrap(); - - // check initialisation - assert_eq!( - from_binary::(&query(deps.as_ref(), env.clone(), QueryMsg::Config {}).unwrap()) - .unwrap(), - Config { - owner: Addr::unchecked("owner"), - token_info_manager: Addr::unchecked(token_info_manager), - vesting_token: None, - extensions: Extensions { - historical: false, - managed: false, - with_managers: false - } - } - ); - - let info = mock_info("stranger", &[]); - // set vesting token by a stranger -> Unauthorized - assert_eq!( - execute( - deps.as_mut(), - env.clone(), - info, - ExecuteMsg::SetVestingToken { - vesting_token: token_asset_info(Addr::unchecked("ntrn_token")), - }, - ) - .unwrap_err(), - ContractError::Unauthorized {}, - ); - - // set vesting token by the manager -> Success - let info = mock_info("token_info_manager", &[]); - execute( - deps.as_mut(), - env.clone(), - info.clone(), - ExecuteMsg::SetVestingToken { - vesting_token: token_asset_info(Addr::unchecked("ntrn_token")), - }, - ) - .unwrap(); - - assert_eq!( - from_binary::(&query(deps.as_ref(), env.clone(), QueryMsg::Config {}).unwrap()) - .unwrap(), - Config { - owner: Addr::unchecked("owner"), - token_info_manager: Addr::unchecked(token_info_manager), - vesting_token: Some(token_asset_info(Addr::unchecked("ntrn_token"))), - extensions: Extensions { - historical: false, - managed: false, - with_managers: false - } - } - ); - - // set vesting token second time by the owner -> VestingTokenAlreadySet - assert_eq!( - execute( - deps.as_mut(), - env.clone(), - info, - ExecuteMsg::SetVestingToken { - vesting_token: token_asset_info(Addr::unchecked("not_a_ntrn_token")), - }, - ) - .unwrap_err(), - ContractError::VestingTokenAlreadySet {}, - ); - - assert_eq!( - from_binary::(&query(deps.as_ref(), env, QueryMsg::Config {}).unwrap()).unwrap(), - Config { - owner: Addr::unchecked("owner"), - token_info_manager: Addr::unchecked(token_info_manager), - vesting_token: Some(token_asset_info(Addr::unchecked("ntrn_token"))), - extensions: Extensions { - historical: false, - managed: false, - with_managers: false - } - } - ); -} - -#[test] -fn proper_building_standard() { - let mut deps = mock_dependencies(); - let owner = String::from("owner"); - let token_info_manager = "token_info_manager"; - let env = mock_env(); - let info = mock_info("owner", &[]); - VestingBaseBuilder::default() - .build(deps.as_mut(), owner, String::from(token_info_manager)) - .unwrap(); - - // check initialisation - assert_eq!( - from_binary::(&query(deps.as_ref(), env.clone(), QueryMsg::Config {}).unwrap()) - .unwrap(), - Config { - owner: Addr::unchecked("owner"), - token_info_manager: Addr::unchecked(token_info_manager), - vesting_token: None, - extensions: Extensions { - historical: false, - managed: false, - with_managers: false - } - } - ); - - // make sure with_managers extension is not enabled - assert_eq!( - query( - deps.as_ref(), - env.clone(), - QueryMsg::WithManagersExtension { - msg: QueryMsgWithManagers::VestingManagers {} - } - ) - .unwrap_err(), - ext_unsupported_err("with_managers") - ); - - // make sure historical extension is not enabled - assert_eq!( - query( - deps.as_ref(), - env.clone(), - QueryMsg::HistoricalExtension { - msg: QueryMsgHistorical::UnclaimedTotalAmountAtHeight { height: 1000u64 } - } - ) - .unwrap_err(), - ext_unsupported_err("historical") - ); - - // make sure managed extension is not enabled - assert_eq!( - execute( - deps.as_mut(), - env, - info, - ExecuteMsg::ManagedExtension { - msg: ExecuteMsgManaged::RemoveVestingAccounts { - vesting_accounts: vec![], - clawback_account: String::from("clawback") - } - }, - ) - .unwrap_err(), - ext_unsupported_err("managed").into() - ); -} - -#[test] -fn proper_building_managers() { - let mut deps = mock_dependencies(); - let owner = String::from("owner"); - let token_info_manager = "token_info_manager"; - let env = mock_env(); - let info = mock_info("owner", &[]); - let vesting_managers = vec!["manager1".to_string(), "manager2".to_string()]; - VestingBaseBuilder::default() - .with_managers(vesting_managers.clone()) - .build(deps.as_mut(), owner, String::from(token_info_manager)) - .unwrap(); - - // check initialisation - assert_eq!( - from_binary::(&query(deps.as_ref(), env.clone(), QueryMsg::Config {}).unwrap()) - .unwrap(), - Config { - owner: Addr::unchecked("owner"), - token_info_manager: Addr::unchecked(token_info_manager), - vesting_token: None, - extensions: Extensions { - historical: false, - managed: false, - with_managers: true - } - } - ); - - // make sure with_managers extension is enabled - assert_eq!( - from_binary::>( - &query( - deps.as_ref(), - env.clone(), - QueryMsg::WithManagersExtension { - msg: QueryMsgWithManagers::VestingManagers {}, - }, - ) - .unwrap() - ) - .unwrap(), - vesting_managers - ); - - // make sure historical extension is not enabled - assert_eq!( - query( - deps.as_ref(), - env.clone(), - QueryMsg::HistoricalExtension { - msg: QueryMsgHistorical::UnclaimedTotalAmountAtHeight { height: 1000u64 } - } - ) - .unwrap_err(), - ext_unsupported_err("historical") - ); - - // make sure managed extension is not enabled - assert_eq!( - execute( - deps.as_mut(), - env, - info, - ExecuteMsg::ManagedExtension { - msg: ExecuteMsgManaged::RemoveVestingAccounts { - vesting_accounts: vec![], - clawback_account: String::from("clawback"), - }, - }, - ) - .unwrap_err(), - ext_unsupported_err("managed").into() - ); -} - -#[test] -fn proper_building_historical() { - let mut deps = mock_dependencies(); - let owner = String::from("owner"); - let token_info_manager = "token_info_manager"; - let env = mock_env(); - let info = mock_info("owner", &[]); - VestingBaseBuilder::default() - .historical() - .build(deps.as_mut(), owner, String::from(token_info_manager)) - .unwrap(); - - // check initialisation - assert_eq!( - from_binary::(&query(deps.as_ref(), env.clone(), QueryMsg::Config {}).unwrap()) - .unwrap(), - Config { - owner: Addr::unchecked("owner"), - token_info_manager: Addr::unchecked(token_info_manager), - vesting_token: None, - extensions: Extensions { - historical: true, - managed: false, - with_managers: false - } - } - ); - - // make sure with_managers extension is not enabled - assert_eq!( - query( - deps.as_ref(), - env.clone(), - QueryMsg::WithManagersExtension { - msg: QueryMsgWithManagers::VestingManagers {} - } - ) - .unwrap_err(), - ext_unsupported_err("with_managers") - ); - - // make sure historical extension is enabled - query( - deps.as_ref(), - env.clone(), - QueryMsg::HistoricalExtension { - msg: QueryMsgHistorical::UnclaimedTotalAmountAtHeight { height: 1000u64 }, - }, - ) - .unwrap(); - - // make sure managed extension is not enabled - assert_eq!( - execute( - deps.as_mut(), - env, - info, - ExecuteMsg::ManagedExtension { - msg: ExecuteMsgManaged::RemoveVestingAccounts { - vesting_accounts: vec![], - clawback_account: String::from("clawback") - } - }, - ) - .unwrap_err(), - ext_unsupported_err("managed").into() - ); -} - -#[test] -fn proper_building_managed() { - let mut deps = mock_dependencies(); - let owner = String::from("owner"); - let token_info_manager = "token_info_manager"; - let env = mock_env(); - VestingBaseBuilder::default() - .managed() - .build(deps.as_mut(), owner, String::from(token_info_manager)) - .unwrap(); - - // check initialisation and set vesting token - assert_eq!( - from_binary::(&query(deps.as_ref(), env.clone(), QueryMsg::Config {}).unwrap()) - .unwrap(), - Config { - owner: Addr::unchecked("owner"), - token_info_manager: Addr::unchecked(token_info_manager), - vesting_token: None, - extensions: Extensions { - historical: false, - managed: true, - with_managers: false - } - } - ); - let info = mock_info("token_info_manager", &[]); - execute( - deps.as_mut(), - env.clone(), - info, - ExecuteMsg::SetVestingToken { - vesting_token: token_asset_info(Addr::unchecked("ntrn_token")), - }, - ) - .unwrap(); - - // make sure with_managers extension is not enabled - assert_eq!( - query( - deps.as_ref(), - env.clone(), - QueryMsg::WithManagersExtension { - msg: QueryMsgWithManagers::VestingManagers {} - } - ) - .unwrap_err(), - ext_unsupported_err("with_managers") - ); - - // make sure historical extension is not enabled - assert_eq!( - query( - deps.as_ref(), - env.clone(), - QueryMsg::HistoricalExtension { - msg: QueryMsgHistorical::UnclaimedTotalAmountAtHeight { height: 1000u64 } - } - ) - .unwrap_err(), - ext_unsupported_err("historical") - ); - - // make sure managed extension is enabled - let info = mock_info("owner", &[]); - execute( - deps.as_mut(), - env, - info, - ExecuteMsg::ManagedExtension { - msg: ExecuteMsgManaged::RemoveVestingAccounts { - vesting_accounts: vec![], - clawback_account: String::from("clawback"), - }, - }, - ) - .unwrap(); -} - -#[test] -fn proper_building_all_extensions() { - let mut deps = mock_dependencies(); - let owner = String::from("owner"); - let token_info_manager = "token_info_manager"; - let env = mock_env(); - let vesting_managers = vec!["manager1".to_string(), "manager2".to_string()]; - VestingBaseBuilder::default() - .historical() - .managed() - .with_managers(vesting_managers.clone()) - .build(deps.as_mut(), owner, String::from(token_info_manager)) - .unwrap(); - - // check initialisation and set vesting token - assert_eq!( - from_binary::(&query(deps.as_ref(), env.clone(), QueryMsg::Config {}).unwrap()) - .unwrap(), - Config { - owner: Addr::unchecked("owner"), - token_info_manager: Addr::unchecked(token_info_manager), - vesting_token: None, - extensions: Extensions { - historical: true, - managed: true, - with_managers: true - } - } - ); - let info = mock_info("token_info_manager", &[]); - execute( - deps.as_mut(), - env.clone(), - info, - ExecuteMsg::SetVestingToken { - vesting_token: token_asset_info(Addr::unchecked("ntrn_token")), - }, - ) - .unwrap(); - - // make sure with_managers extension is enabled - assert_eq!( - from_binary::>( - &query( - deps.as_ref(), - env.clone(), - QueryMsg::WithManagersExtension { - msg: QueryMsgWithManagers::VestingManagers {}, - }, - ) - .unwrap() - ) - .unwrap(), - vesting_managers - ); - - // make sure historical extension is enabled - query( - deps.as_ref(), - env.clone(), - QueryMsg::HistoricalExtension { - msg: QueryMsgHistorical::UnclaimedTotalAmountAtHeight { height: 1000u64 }, - }, - ) - .unwrap(); - - // make sure managed extension is enabled - let info = mock_info("owner", &[]); - execute( - deps.as_mut(), - env, - info, - ExecuteMsg::ManagedExtension { - msg: ExecuteMsgManaged::RemoveVestingAccounts { - vesting_accounts: vec![], - clawback_account: String::from("clawback"), - }, - }, - ) - .unwrap(); -} From 0fe360ee6ea45bffb25800b331214abf1a7c258a Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Thu, 1 Feb 2024 11:30:41 -0300 Subject: [PATCH 06/30] satisfy clippy --- .../vesting-lp-pcl/src/tests/integration.rs | 8 +--- packages/vesting-base-lp/src/handlers.rs | 47 +++++-------------- 2 files changed, 14 insertions(+), 41 deletions(-) diff --git a/contracts/vesting-lp-pcl/src/tests/integration.rs b/contracts/vesting-lp-pcl/src/tests/integration.rs index a8f2a6a4..70e164ac 100644 --- a/contracts/vesting-lp-pcl/src/tests/integration.rs +++ b/contracts/vesting-lp-pcl/src/tests/integration.rs @@ -1189,7 +1189,6 @@ fn instantiate_vesting(app: &mut App, cw20_token_instance: &Addr) -> Addr { crate::contract::query, )); let owner = Addr::unchecked(OWNER1); - let token_manager = Addr::unchecked(TOKEN_MANAGER); let vesting_code_id = app.store_code(vesting_contract); let init_msg = InstantiateMsg { @@ -1233,7 +1232,6 @@ fn instantiate_vesting_remote_chain(app: &mut App) -> Addr { crate::contract::query, )); let owner = Addr::unchecked(OWNER1); - let token_manager = Addr::unchecked(TOKEN_MANAGER); let vesting_code_id = app.store_code(vesting_contract); let init_msg = InstantiateMsg { @@ -1244,10 +1242,8 @@ fn instantiate_vesting_remote_chain(app: &mut App) -> Addr { xyk_vesting_lp_contract: "test".to_string(), }; - let res = app - .instantiate_contract(vesting_code_id, owner, &init_msg, &[], "Vesting", None) - .unwrap(); - res + app.instantiate_contract(vesting_code_id, owner, &init_msg, &[], "Vesting", None) + .unwrap() } fn mint_tokens(app: &mut App, token: &Addr, recipient: &Addr, amount: u128) { diff --git a/packages/vesting-base-lp/src/handlers.rs b/packages/vesting-base-lp/src/handlers.rs index 45765510..1605afbe 100644 --- a/packages/vesting-base-lp/src/handlers.rs +++ b/packages/vesting-base-lp/src/handlers.rs @@ -443,6 +443,16 @@ fn migrate_liquidity_to_cl_pair_callback( funds: vec![], })) } + let config = CONFIG.load(deps.storage)?; + vesting_state(config.extensions.historical).update::<_, ContractError>( + deps.storage, + env.block.height, + |s| { + let mut state = s.unwrap_or_default(); + state.total_released = state.total_released.checked_add(amount)?; + Ok(state) + }, + )?; // push the next migration step as a callback message msgs.push( CallbackMsg::ProvideLiquidityToClPairAfterWithdrawal { @@ -525,22 +535,15 @@ fn post_migration_vesting_reschedule_callback( address: env.contract.address.to_string(), }, )?; - let state = vesting_state(config.extensions.historical).load(deps.storage)?; let current_balance = balance_response.balance; - let balance_diff: Uint128 = if !current_balance.is_zero() { - current_balance.checked_sub(state.total_granted)? - } else { - Uint128::zero() - }; - let schedule = user.info.schedules.last().unwrap(); let new_end_point; if let Some(end_point) = &schedule.end_point { new_end_point = Option::from(vesting_base_pcl::types::VestingSchedulePoint { time: end_point.time, - amount: balance_diff, + amount: current_balance, }) } else { new_end_point = None @@ -565,16 +568,6 @@ fn post_migration_vesting_reschedule_callback( }, env.block.height, )?; - - vesting_state(config.extensions.historical).update::<_, ContractError>( - deps.storage, - env.block.height, - |s| { - let mut state = s.unwrap_or_default(); - state.total_granted = state.total_granted.checked_add(balance_diff)?; - Ok(state) - }, - )?; let msgs = vec![CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: migration_config.new_lp_token.to_string(), funds: vec![], @@ -691,8 +684,7 @@ fn query_vesting_available_amount(deps: Deps, env: Env, address: String) -> StdR } /// Manages contract migration. -pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Result { - let mut config = CONFIG.load(deps.storage)?; +pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result { XYK_TO_CL_MIGRATION_CONFIG.save( deps.storage, &XykToClMigrationConfig { @@ -705,21 +697,6 @@ pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Result( - deps.storage, - env.block.height, - |s| { - let mut state = s.unwrap_or_default(); - state.total_granted = Uint128::zero(); - Ok(state) - }, - )?; Ok(Response::default()) } From fbe0792432cbe22b9ad5bfea00fdb3a8680da317 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Wed, 7 Feb 2024 18:33:46 -0300 Subject: [PATCH 07/30] move xyk lp addr out of config --- packages/vesting-base-pcl/src/builder.rs | 8 ++++++-- packages/vesting-base-pcl/src/handlers.rs | 5 +++-- packages/vesting-base-pcl/src/state.rs | 1 + packages/vesting-base-pcl/src/types.rs | 1 - 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/vesting-base-pcl/src/builder.rs b/packages/vesting-base-pcl/src/builder.rs index ccb84162..35550efa 100644 --- a/packages/vesting-base-pcl/src/builder.rs +++ b/packages/vesting-base-pcl/src/builder.rs @@ -1,4 +1,4 @@ -use crate::state::{CONFIG, VESTING_MANAGERS}; +use crate::state::{CONFIG, VESTING_MANAGERS, XYK_VESTING_LP_CONTRACT}; use crate::types::{Config, Extensions}; use astroport::asset::AssetInfo; use cosmwasm_std::{DepsMut, StdResult}; @@ -53,10 +53,14 @@ impl VestingBaseBuilder { managed: self.managed, with_managers: self.with_managers, }, - xyk_vesting_lp_contract: deps.api.addr_validate(&xyk_vesting_lp_contract)?, }, )?; + XYK_VESTING_LP_CONTRACT.save( + deps.storage, + &deps.api.addr_validate(&xyk_vesting_lp_contract)?, + )?; + if self.with_managers { for m in self.vesting_managers.iter() { let ma = deps.api.addr_validate(m)?; diff --git a/packages/vesting-base-pcl/src/handlers.rs b/packages/vesting-base-pcl/src/handlers.rs index feca65e2..d85a5c75 100644 --- a/packages/vesting-base-pcl/src/handlers.rs +++ b/packages/vesting-base-pcl/src/handlers.rs @@ -3,7 +3,7 @@ use crate::ext_historical::{handle_execute_historical_msg, handle_query_historic use crate::ext_managed::{handle_execute_managed_msg, handle_query_managed_msg}; use crate::ext_with_managers::{handle_execute_with_managers_msg, handle_query_managers_msg}; use crate::msg::{Cw20HookMsg, ExecuteMsg, MigrateMsg, QueryMsg}; -use crate::state::{read_vesting_infos, vesting_info, vesting_state}; +use crate::state::{read_vesting_infos, vesting_info, vesting_state, XYK_VESTING_LP_CONTRACT}; use crate::state::{CONFIG, OWNERSHIP_PROPOSAL, VESTING_MANAGERS}; use crate::types::{ Config, OrderBy, VestingAccount, VestingAccountResponse, VestingAccountsResponse, VestingInfo, @@ -441,7 +441,8 @@ fn is_sender_whitelisted(store: &mut dyn Storage, config: &Config, sender: &Addr if *sender == config.owner { return true; } - if *sender == config.xyk_vesting_lp_contract { + let xyk_vesting_lp_contract = XYK_VESTING_LP_CONTRACT.load(store).unwrap(); + if *sender == xyk_vesting_lp_contract { return true; } if VESTING_MANAGERS.has(store, sender.clone()) { diff --git a/packages/vesting-base-pcl/src/state.rs b/packages/vesting-base-pcl/src/state.rs index 978fd8c6..0a7bc89f 100644 --- a/packages/vesting-base-pcl/src/state.rs +++ b/packages/vesting-base-pcl/src/state.rs @@ -5,6 +5,7 @@ use cw_storage_plus::{Bound, Item, Map, SnapshotItem, SnapshotMap, Strategy}; pub(crate) const CONFIG: Item = Item::new("config"); pub(crate) const OWNERSHIP_PROPOSAL: Item = Item::new("ownership_proposal"); +pub(crate) const XYK_VESTING_LP_CONTRACT: Item = Item::new("xyk_vesting_lp_contract"); pub(crate) const VESTING_MANAGERS: Map = Map::new("vesting_managers"); pub(crate) const VESTING_STATE: SnapshotItem = SnapshotItem::new( "vesting_state", diff --git a/packages/vesting-base-pcl/src/types.rs b/packages/vesting-base-pcl/src/types.rs index 578aaf03..f6081025 100644 --- a/packages/vesting-base-pcl/src/types.rs +++ b/packages/vesting-base-pcl/src/types.rs @@ -13,7 +13,6 @@ pub struct Config { pub token_info_manager: Addr, /// Contains extensions information of the contract pub extensions: Extensions, - pub xyk_vesting_lp_contract: Addr, } /// Contains extensions information for the contract. From 87141340f07e47de7b64021a74fead0855c6d9bc Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Mon, 12 Feb 2024 21:08:26 -0300 Subject: [PATCH 08/30] massive refactor --- Cargo.lock | 35 +- contracts/vesting-lp-pcl/Cargo.toml | 3 +- contracts/vesting-lp-pcl/src/contract.rs | 162 +++- contracts/vesting-lp-pcl/src/lib.rs | 1 + contracts/vesting-lp-pcl/src/msg.rs | 24 + contracts/vesting-lp-pcl/src/state.rs | 3 + contracts/vesting-lp/Cargo.toml | 3 +- contracts/vesting-lp/src/contract.rs | 370 ++++++++- contracts/vesting-lp/src/lib.rs | 1 + contracts/vesting-lp/src/msg.rs | 66 ++ contracts/vesting-lp/src/state.rs | 19 + packages/vesting-base-lp/Cargo.toml | 26 - packages/vesting-base-lp/NOTICE | 14 - packages/vesting-base-lp/README.md | 251 ------ packages/vesting-base-lp/src/builder.rs | 60 -- packages/vesting-base-lp/src/error.rs | 65 -- .../vesting-base-lp/src/ext_historical.rs | 101 --- packages/vesting-base-lp/src/ext_managed.rs | 121 --- .../vesting-base-lp/src/ext_with_managers.rs | 105 --- packages/vesting-base-lp/src/handlers.rs | 779 ------------------ packages/vesting-base-lp/src/lib.rs | 10 - packages/vesting-base-lp/src/msg.rs | 210 ----- packages/vesting-base-lp/src/state.rs | 162 ---- packages/vesting-base-lp/src/types.rs | 122 --- packages/vesting-base-pcl/Cargo.toml | 23 - packages/vesting-base-pcl/NOTICE | 14 - packages/vesting-base-pcl/README.md | 251 ------ packages/vesting-base-pcl/src/builder.rs | 73 -- packages/vesting-base-pcl/src/error.rs | 47 -- .../vesting-base-pcl/src/ext_historical.rs | 101 --- packages/vesting-base-pcl/src/ext_managed.rs | 121 --- .../vesting-base-pcl/src/ext_with_managers.rs | 105 --- packages/vesting-base-pcl/src/handlers.rs | 509 ------------ packages/vesting-base-pcl/src/lib.rs | 10 - packages/vesting-base-pcl/src/msg.rs | 167 ---- packages/vesting-base-pcl/src/state.rs | 160 ---- packages/vesting-base-pcl/src/types.rs | 109 --- packages/vesting-base/src/handlers.rs | 6 +- packages/vesting-base/src/state.rs | 8 +- 39 files changed, 630 insertions(+), 3787 deletions(-) create mode 100644 contracts/vesting-lp-pcl/src/state.rs create mode 100644 contracts/vesting-lp/src/state.rs delete mode 100644 packages/vesting-base-lp/Cargo.toml delete mode 100644 packages/vesting-base-lp/NOTICE delete mode 100644 packages/vesting-base-lp/README.md delete mode 100644 packages/vesting-base-lp/src/builder.rs delete mode 100644 packages/vesting-base-lp/src/error.rs delete mode 100644 packages/vesting-base-lp/src/ext_historical.rs delete mode 100644 packages/vesting-base-lp/src/ext_managed.rs delete mode 100644 packages/vesting-base-lp/src/ext_with_managers.rs delete mode 100644 packages/vesting-base-lp/src/handlers.rs delete mode 100644 packages/vesting-base-lp/src/lib.rs delete mode 100644 packages/vesting-base-lp/src/msg.rs delete mode 100644 packages/vesting-base-lp/src/state.rs delete mode 100644 packages/vesting-base-lp/src/types.rs delete mode 100644 packages/vesting-base-pcl/Cargo.toml delete mode 100644 packages/vesting-base-pcl/NOTICE delete mode 100644 packages/vesting-base-pcl/README.md delete mode 100644 packages/vesting-base-pcl/src/builder.rs delete mode 100644 packages/vesting-base-pcl/src/error.rs delete mode 100644 packages/vesting-base-pcl/src/ext_historical.rs delete mode 100644 packages/vesting-base-pcl/src/ext_managed.rs delete mode 100644 packages/vesting-base-pcl/src/ext_with_managers.rs delete mode 100644 packages/vesting-base-pcl/src/handlers.rs delete mode 100644 packages/vesting-base-pcl/src/lib.rs delete mode 100644 packages/vesting-base-pcl/src/msg.rs delete mode 100644 packages/vesting-base-pcl/src/state.rs delete mode 100644 packages/vesting-base-pcl/src/types.rs diff --git a/Cargo.lock b/Cargo.lock index 8c3b50ec..d3d8d135 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1510,36 +1510,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "vesting-base-lp" -version = "1.1.0" -dependencies = [ - "astroport 2.8.0", - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 0.15.1", - "cw-utils 0.15.1", - "cw2 0.15.1", - "cw20 0.15.1", - "serde", - "thiserror", - "vesting-base", - "vesting-base-pcl", -] - -[[package]] -name = "vesting-base-pcl" -version = "1.1.0" -dependencies = [ - "astroport 2.0.0", - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus 1.1.0", - "cw-utils 0.15.1", - "cw20 1.1.1", - "thiserror", -] - [[package]] name = "vesting-investors" version = "1.1.1" @@ -1569,7 +1539,7 @@ dependencies = [ "cw2 0.15.1", "cw20 0.15.1", "vesting-base", - "vesting-base-lp", + "vesting-lp-pcl", ] [[package]] @@ -1581,10 +1551,11 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cw-multi-test 0.16.5", + "cw-storage-plus 0.15.1", "cw-utils 0.15.1", "cw2 1.1.1", "cw20 1.1.1", - "vesting-base-pcl", + "vesting-base", ] [[package]] diff --git a/contracts/vesting-lp-pcl/Cargo.toml b/contracts/vesting-lp-pcl/Cargo.toml index 4046c5b3..55ec4334 100644 --- a/contracts/vesting-lp-pcl/Cargo.toml +++ b/contracts/vesting-lp-pcl/Cargo.toml @@ -18,9 +18,10 @@ library = [] cw2 = { workspace = true } cw20 = { workspace = true } astroport = { workspace = true } -vesting-base-pcl = { workspace = true } +vesting-base = {path = "../../packages/vesting-base"} cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } +cw-storage-plus = "0.15" [dev-dependencies] cw-multi-test = { workspace = true } diff --git a/contracts/vesting-lp-pcl/src/contract.rs b/contracts/vesting-lp-pcl/src/contract.rs index 0eaecbf1..d4612947 100644 --- a/contracts/vesting-lp-pcl/src/contract.rs +++ b/contracts/vesting-lp-pcl/src/contract.rs @@ -1,10 +1,20 @@ -use crate::msg::InstantiateMsg; -use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use crate::msg::{Cw20HookMsg, ExecuteMsg, InstantiateMsg}; +use crate::state::XYK_VESTING_LP_CONTRACT; +use astroport::asset::token_asset_info; +use cosmwasm_std::{ + entry_point, from_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, + Storage, Uint128, +}; use cw2::set_contract_version; -use vesting_base_pcl::builder::VestingBaseBuilder; -use vesting_base_pcl::error::ContractError; -use vesting_base_pcl::handlers::{execute as base_execute, query as base_query}; -use vesting_base_pcl::msg::{ExecuteMsg, QueryMsg}; +use cw20::Cw20ReceiveMsg; +use vesting_base::error::ContractError; +use vesting_base::handlers::{ + assert_vesting_schedules, get_vesting_token, register_vesting_accounts, +}; +use vesting_base::handlers::{execute as base_execute, query as base_query}; +use vesting_base::msg::QueryMsg; +use vesting_base::state::{vesting_info, vesting_state, CONFIG, VESTING_MANAGERS}; +use vesting_base::types::{Config, Extensions, VestingInfo}; /// Contract name that is used for migration. const CONTRACT_NAME: &str = "neutron-vesting-lp"; @@ -20,17 +30,31 @@ pub fn instantiate( msg: InstantiateMsg, ) -> StdResult { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + let owner = deps.api.addr_validate(&msg.owner)?; + CONFIG.save( + deps.storage, + &Config { + owner, + vesting_token: Option::from(msg.vesting_token), + token_info_manager: deps.api.addr_validate(&msg.token_info_manager)?, - VestingBaseBuilder::default() - .historical() - .with_managers(msg.vesting_managers) - .build( - deps, - msg.owner, - msg.token_info_manager, - msg.xyk_vesting_lp_contract, - msg.vesting_token, - )?; + extensions: Extensions { + historical: true, + managed: true, + with_managers: true, + }, + }, + )?; + for m in msg.vesting_managers.iter() { + let ma = deps.api.addr_validate(m)?; + VESTING_MANAGERS.save(deps.storage, ma, &())?; + } + XYK_VESTING_LP_CONTRACT.save( + deps.storage, + &deps + .api + .addr_validate(&msg.xyk_vesting_lp_contract.clone())?, + )?; Ok(Response::default()) } @@ -42,7 +66,111 @@ pub fn execute( info: MessageInfo, msg: ExecuteMsg, ) -> Result { - base_execute(deps, env, info, msg) + match msg { + ExecuteMsg::Base(base_msg) => { + // Delegate handling of the original message types to the base execute function + base_execute(deps, env, info, base_msg) + } + ExecuteMsg::Receive(msg) => receive_cw20(deps, env, info, msg), + } +} + +/// Receives a message of type [`Cw20HookMsg`] 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.load(deps.storage)?; + let vesting_token = get_vesting_token(&config)?; + + // Permission check + if !is_sender_whitelisted( + deps.storage, + &config, + &deps.api.addr_validate(&cw20_msg.sender)?, + ) || token_asset_info(info.sender) != vesting_token + { + return Err(ContractError::Unauthorized {}); + } + + match from_binary(&cw20_msg.msg)? { + Cw20HookMsg::RegisterVestingAccounts { vesting_accounts } => { + register_vesting_accounts(deps, vesting_accounts, cw20_msg.amount, env.block.height) + } + Cw20HookMsg::MigrateXYKLiquidity { + user_address_raw, + user_vesting_info, + } => handle_migrate_xyk_liquidity(deps, env, user_address_raw, user_vesting_info), + } +} + +fn is_sender_whitelisted(store: &mut dyn Storage, config: &Config, sender: &Addr) -> bool { + if *sender == config.owner { + return true; + } + let xyk_vesting_lp_contract = XYK_VESTING_LP_CONTRACT.load(store).unwrap(); + if *sender == xyk_vesting_lp_contract { + return true; + } + if VESTING_MANAGERS.has(store, sender.clone()) { + return true; + } + + false +} + +fn handle_migrate_xyk_liquidity( + deps: DepsMut, + env: Env, + user_addr_raw: Addr, + user_vesting_info: VestingInfo, +) -> Result { + let height = env.block.height; + let config = CONFIG.load(deps.storage)?; + + let account_address = user_addr_raw; + + assert_vesting_schedules(&account_address, &user_vesting_info.schedules)?; + + let mut to_deposit = Uint128::zero(); + for sch in &user_vesting_info.schedules { + let amount = if let Some(end_point) = &sch.end_point { + end_point.amount + } else { + sch.start_point.amount + }; + to_deposit = to_deposit.checked_add(amount)?; + } + + let vesting_info = vesting_info(config.extensions.historical); + + vesting_info.save(deps.storage, account_address, &user_vesting_info, height)?; + + let mut to_deposit = Uint128::zero(); + for sch in &user_vesting_info.schedules { + let amount = if let Some(end_point) = &sch.end_point { + end_point.amount + } else { + sch.start_point.amount + }; + to_deposit = to_deposit.checked_add(amount)?; + } + + vesting_state(config.extensions.historical).update::<_, ContractError>( + deps.storage, + height, + |s| { + let mut state = s.unwrap_or_default(); + state.total_granted = state.total_granted.checked_add(to_deposit)?; + Ok(state) + }, + )?; + + Ok(Response::default()) } /// Exposes all the queries available in the contract. diff --git a/contracts/vesting-lp-pcl/src/lib.rs b/contracts/vesting-lp-pcl/src/lib.rs index 08d6d688..b88f5887 100644 --- a/contracts/vesting-lp-pcl/src/lib.rs +++ b/contracts/vesting-lp-pcl/src/lib.rs @@ -1,5 +1,6 @@ pub mod contract; pub mod msg; +pub mod state; #[cfg(test)] mod tests; diff --git a/contracts/vesting-lp-pcl/src/msg.rs b/contracts/vesting-lp-pcl/src/msg.rs index 69bebd7e..c6e3a529 100644 --- a/contracts/vesting-lp-pcl/src/msg.rs +++ b/contracts/vesting-lp-pcl/src/msg.rs @@ -1,5 +1,9 @@ use astroport::asset::AssetInfo; use cosmwasm_schema::cw_serde; +use cosmwasm_std::Addr; +use cw20::Cw20ReceiveMsg; +use vesting_base::msg::ExecuteMsg as BaseExecute; +use vesting_base::types::{VestingAccount, VestingInfo}; /// This structure describes the parameters used for creating a contract. #[cw_serde] @@ -13,3 +17,23 @@ pub struct InstantiateMsg { pub xyk_vesting_lp_contract: String, pub vesting_token: AssetInfo, } + +pub enum ExecuteMsg { + Base(BaseExecute), + Receive(Cw20ReceiveMsg), +} + +/// This structure describes a CW20 hook message. +#[cw_serde] +pub enum Cw20HookMsg { + /// RegisterVestingAccounts registers vesting targets/accounts + RegisterVestingAccounts { + vesting_accounts: Vec, + }, + #[serde(rename = "migrate_xyk_liquidity")] + MigrateXYKLiquidity { + /// The address of the user which owns the vested tokens. + user_address_raw: Addr, + user_vesting_info: VestingInfo, + }, +} diff --git a/contracts/vesting-lp-pcl/src/state.rs b/contracts/vesting-lp-pcl/src/state.rs new file mode 100644 index 00000000..e3fb2078 --- /dev/null +++ b/contracts/vesting-lp-pcl/src/state.rs @@ -0,0 +1,3 @@ +use cosmwasm_std::Addr; +use cw_storage_plus::Item; +pub(crate) const XYK_VESTING_LP_CONTRACT: Item = Item::new("xyk_vesting_lp_contract"); diff --git a/contracts/vesting-lp/Cargo.toml b/contracts/vesting-lp/Cargo.toml index 93d7020a..1153e1a0 100644 --- a/contracts/vesting-lp/Cargo.toml +++ b/contracts/vesting-lp/Cargo.toml @@ -15,11 +15,12 @@ library = [] [dependencies] cw2 = { version = "0.15" } vesting-base = {path = "../../packages/vesting-base"} -vesting-base-lp = {path = "../../packages/vesting-base-lp"} +vesting-lp-pcl = {path = "../vesting-lp-pcl"} astroport = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v2.8.0" } cosmwasm-schema = { version = "1.1", default-features = false } cosmwasm-std = { version = "1.1" } cw-storage-plus = "0.15" +cw20 = { version = "0.15" } [dev-dependencies] cw-multi-test = "0.15" diff --git a/contracts/vesting-lp/src/contract.rs b/contracts/vesting-lp/src/contract.rs index bf18ed13..b06d12f6 100644 --- a/contracts/vesting-lp/src/contract.rs +++ b/contracts/vesting-lp/src/contract.rs @@ -1,13 +1,24 @@ -use crate::msg::InstantiateMsg; -use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use crate::msg::{CallbackMsg, ExecuteMsg, InstantiateMsg, MigrateMsg}; +use crate::state::{XykToClMigrationConfig, XYK_TO_CL_MIGRATION_CONFIG}; +use astroport::asset::{native_asset, PairInfo}; +use astroport::pair::{ + Cw20HookMsg as PairCw20HookMsg, ExecuteMsg as PairExecuteMsg, QueryMsg as PairQueryMsg, +}; +use cosmwasm_std::{ + entry_point, to_binary, Addr, Binary, Coin, CosmosMsg, Decimal, Deps, DepsMut, Env, + MessageInfo, Response, StdResult, Uint128, WasmMsg, +}; use cw2::set_contract_version; +use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg}; use vesting_base::builder::VestingBaseBuilder; -use vesting_base_lp::error::ContractError; -use vesting_base_lp::handlers::execute as base_execute; -use vesting_base_lp::handlers::migrate as base_migrate; -use vesting_base_lp::handlers::query as base_query; -use vesting_base_lp::msg::QueryMsg; -use vesting_base_lp::msg::{ExecuteMsg, MigrateMsg}; +use vesting_base::error::ContractError; +use vesting_base::handlers::execute as base_execute; +use vesting_base::handlers::query as base_query; +use vesting_base::msg::QueryMsg; +use vesting_base::state::{vesting_info, vesting_state, CONFIG}; +use vesting_base::types::{ + VestingAccountResponse, VestingInfo, VestingSchedule, VestingSchedulePoint, +}; /// Contract name that is used for migration. const CONTRACT_NAME: &str = "neutron-vesting-lp"; @@ -39,7 +50,16 @@ pub fn execute( info: MessageInfo, msg: ExecuteMsg, ) -> Result { - base_execute(deps, env, info, msg) + match msg { + ExecuteMsg::Base(base_msg) => { + // Delegate handling of the original message types to the base execute function + base_execute(deps, env, info, base_msg) + } + ExecuteMsg::MigrateLiquidityToPCLPool { user } => { + execute_migrate_liquidity(deps, info, env, None, user) + } + ExecuteMsg::Callback(msg) => _handle_callback(deps, env, info, msg), + } } /// Exposes all the queries available in the contract. @@ -48,8 +68,332 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { base_query(deps, env, msg) } -/// Exposes migrate functions available in the contract. -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Result { - base_migrate(deps, env, msg) +/// Manages contract migration. +pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result { + XYK_TO_CL_MIGRATION_CONFIG.save( + deps.storage, + &XykToClMigrationConfig { + max_slippage: msg.max_slippage, + ntrn_denom: msg.ntrn_denom, + xyk_pair: deps.api.addr_validate(msg.xyk_pair.as_str())?, + paired_denom: msg.paired_denom, + cl_pair: deps.api.addr_validate(msg.cl_pair.as_str())?, + new_lp_token: deps.api.addr_validate(msg.new_lp_token.as_str())?, + pcl_vesting: deps.api.addr_validate(msg.pcl_vesting.as_str())?, + }, + )?; + + Ok(Response::default()) +} + +fn execute_migrate_liquidity( + deps: DepsMut, + info: MessageInfo, + env: Env, + slippage_tolerance: Option, + user: Option, +) -> Result { + let config = CONFIG.load(deps.storage)?; + let migration_config: XykToClMigrationConfig = XYK_TO_CL_MIGRATION_CONFIG.load(deps.storage)?; + let address = match user { + Some(val) => deps.api.addr_validate(&val)?, + None => info.sender, + }; + let info = vesting_info(config.extensions.historical).load(deps.storage, address.clone())?; + let mut resp = Response::default(); + let user = VestingAccountResponse { address, info }; + + // get pairs LP token addresses + let pair_info: PairInfo = deps + .querier + .query_wasm_smart(migration_config.xyk_pair.clone(), &PairQueryMsg::Pair {})?; + + // query max available amounts to be withdrawn from pool + let max_available_amount = { + let resp: BalanceResponse = deps.querier.query_wasm_smart( + pair_info.liquidity_token.clone(), + &Cw20QueryMsg::Balance { + address: env.contract.address.to_string(), + }, + )?; + resp.balance + }; + + if max_available_amount.is_zero() { + return Ok(resp); + } + + let user_amount = compute_share(&user.info)?; + + if let Some(slippage_tolerance) = slippage_tolerance { + if slippage_tolerance.gt(&migration_config.max_slippage) { + return Err(ContractError::MigrationError {}); + } + } + + let slippage_tolerance = slippage_tolerance.unwrap_or(migration_config.max_slippage); + + resp = resp.add_message( + CallbackMsg::MigrateLiquidityToClPair { + xyk_pair: migration_config.xyk_pair.clone(), + xyk_lp_token: pair_info.liquidity_token.clone(), + amount: user_amount, + slippage_tolerance, + cl_pair: migration_config.cl_pair.clone(), + ntrn_denom: migration_config.ntrn_denom.clone(), + paired_asset_denom: migration_config.paired_denom.clone(), + user, + } + .to_cosmos_msg(&env)?, + ); + + Ok(resp) +} + +fn _handle_callback( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: CallbackMsg, +) -> Result { + // Only the contract itself can call callbacks + if info.sender != env.contract.address { + return Err(ContractError::Unauthorized {}); + } + match msg { + CallbackMsg::MigrateLiquidityToClPair { + xyk_pair, + xyk_lp_token, + amount, + slippage_tolerance, + cl_pair, + ntrn_denom, + paired_asset_denom, + user, + } => migrate_liquidity_to_cl_pair_callback( + deps, + env, + xyk_pair, + xyk_lp_token, + amount, + slippage_tolerance, + cl_pair, + ntrn_denom, + paired_asset_denom, + user, + ), + CallbackMsg::ProvideLiquidityToClPairAfterWithdrawal { + ntrn_denom, + ntrn_init_balance, + paired_asset_denom, + paired_asset_init_balance, + cl_pair, + slippage_tolerance, + user, + } => provide_liquidity_to_cl_pair_after_withdrawal_callback( + deps, + env, + ntrn_denom, + ntrn_init_balance, + paired_asset_denom, + paired_asset_init_balance, + cl_pair, + slippage_tolerance, + user, + ), + CallbackMsg::PostMigrationVestingReschedule { user } => { + post_migration_vesting_reschedule_callback(deps, env, &user) + } + } +} + +#[allow(clippy::too_many_arguments)] +fn migrate_liquidity_to_cl_pair_callback( + deps: DepsMut, + env: Env, + xyk_pair: Addr, + xyk_lp_token: Addr, + amount: Uint128, + slippage_tolerance: Decimal, + cl_pair: Addr, + ntrn_denom: String, + paired_asset_denom: String, + user: VestingAccountResponse, +) -> Result { + let ntrn_init_balance = deps + .querier + .query_balance(env.contract.address.to_string(), ntrn_denom.clone())? + .amount; + let paired_asset_init_balance = deps + .querier + .query_balance(env.contract.address.to_string(), paired_asset_denom.clone())? + .amount; + + let mut msgs: Vec = vec![]; + + // push message to withdraw liquidity from the xyk pair + if !amount.is_zero() { + msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: xyk_lp_token.to_string(), + msg: to_binary(&Cw20ExecuteMsg::Send { + contract: xyk_pair.to_string(), + amount, + msg: to_binary(&PairCw20HookMsg::WithdrawLiquidity { assets: vec![] })?, + })?, + funds: vec![], + })) + } + let config = CONFIG.load(deps.storage)?; + vesting_state(config.extensions.historical).update::<_, ContractError>( + deps.storage, + env.block.height, + |s| { + let mut state = s.unwrap_or_default(); + state.total_released = state.total_released.checked_add(amount)?; + Ok(state) + }, + )?; + // push the next migration step as a callback message + msgs.push( + CallbackMsg::ProvideLiquidityToClPairAfterWithdrawal { + ntrn_denom, + ntrn_init_balance, + paired_asset_denom, + paired_asset_init_balance, + cl_pair, + slippage_tolerance, + user, + } + .to_cosmos_msg(&env)?, + ); + + Ok(Response::default().add_messages(msgs)) +} + +#[allow(clippy::too_many_arguments)] +fn provide_liquidity_to_cl_pair_after_withdrawal_callback( + deps: DepsMut, + env: Env, + ntrn_denom: String, + ntrn_init_balance: Uint128, + paired_asset_denom: String, + paired_asset_init_balance: Uint128, + cl_pair_address: Addr, + slippage_tolerance: Decimal, + user: VestingAccountResponse, +) -> Result { + let ntrn_balance_after_withdrawal = deps + .querier + .query_balance(env.contract.address.to_string(), ntrn_denom.clone())? + .amount; + let paired_asset_balance_after_withdrawal = deps + .querier + .query_balance(env.contract.address.to_string(), paired_asset_denom.clone())? + .amount; + + // calc amount of assets that's been withdrawn + let withdrawn_ntrn_amount = ntrn_balance_after_withdrawal.checked_sub(ntrn_init_balance)?; + let withdrawn_paired_asset_amount = + paired_asset_balance_after_withdrawal.checked_sub(paired_asset_init_balance)?; + + let mut msgs: Vec = vec![]; + + if !withdrawn_ntrn_amount.is_zero() && !withdrawn_paired_asset_amount.is_zero() { + msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: cl_pair_address.to_string(), + msg: to_binary(&PairExecuteMsg::ProvideLiquidity { + assets: vec![ + native_asset(ntrn_denom.clone(), withdrawn_ntrn_amount), + native_asset(paired_asset_denom.clone(), withdrawn_paired_asset_amount), + ], + slippage_tolerance: Some(slippage_tolerance), + auto_stake: None, + receiver: None, + })?, + funds: vec![ + Coin::new(withdrawn_ntrn_amount.into(), ntrn_denom), + Coin::new(withdrawn_paired_asset_amount.into(), paired_asset_denom), + ], + })) + } + + msgs.push(CallbackMsg::PostMigrationVestingReschedule { user }.to_cosmos_msg(&env)?); + + Ok(Response::default().add_messages(msgs)) +} + +fn post_migration_vesting_reschedule_callback( + deps: DepsMut, + env: Env, + user: &VestingAccountResponse, +) -> Result { + let config = CONFIG.load(deps.storage)?; + let migration_config: XykToClMigrationConfig = XYK_TO_CL_MIGRATION_CONFIG.load(deps.storage)?; + let balance_response: BalanceResponse = deps.querier.query_wasm_smart( + &migration_config.new_lp_token, + &Cw20QueryMsg::Balance { + address: env.contract.address.to_string(), + }, + )?; + let current_balance = balance_response.balance; + + let schedule = user.info.schedules.last().unwrap(); + + let new_end_point; + if let Some(end_point) = &schedule.end_point { + new_end_point = Option::from(VestingSchedulePoint { + time: end_point.time, + amount: current_balance, + }) + } else { + new_end_point = None + } + + let new_schedule = VestingSchedule { + start_point: VestingSchedulePoint { + time: schedule.start_point.time, + amount: Uint128::zero(), + }, + end_point: new_end_point, + }; + + let vesting_info = vesting_info(config.extensions.historical); + + vesting_info.save( + deps.storage, + user.address.clone(), + &VestingInfo { + schedules: vec![], + released_amount: Uint128::zero(), + }, + env.block.height, + )?; + let msgs = vec![CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: migration_config.new_lp_token.to_string(), + funds: vec![], + msg: to_binary(&Cw20ExecuteMsg::Send { + contract: migration_config.pcl_vesting.to_string(), + amount: current_balance, + msg: to_binary(&vesting_lp_pcl::msg::Cw20HookMsg::MigrateXYKLiquidity { + user_address_raw: user.address.clone(), + user_vesting_info: VestingInfo { + schedules: vec![new_schedule], + released_amount: Uint128::zero(), + }, + })?, + })?, + })]; + + Ok(Response::new().add_messages(msgs)) +} + +fn compute_share(vesting_info: &VestingInfo) -> StdResult { + let mut available_amount: Uint128 = Uint128::zero(); + for sch in &vesting_info.schedules { + if let Some(end_point) = &sch.end_point { + available_amount = available_amount.checked_add(end_point.amount)? + } + } + + Ok(available_amount.checked_sub(vesting_info.released_amount)?) } diff --git a/contracts/vesting-lp/src/lib.rs b/contracts/vesting-lp/src/lib.rs index 112ecadc..4934c19d 100644 --- a/contracts/vesting-lp/src/lib.rs +++ b/contracts/vesting-lp/src/lib.rs @@ -1,2 +1,3 @@ pub mod contract; pub mod msg; +pub mod state; diff --git a/contracts/vesting-lp/src/msg.rs b/contracts/vesting-lp/src/msg.rs index b35ee7ff..717b9985 100644 --- a/contracts/vesting-lp/src/msg.rs +++ b/contracts/vesting-lp/src/msg.rs @@ -1,4 +1,7 @@ use cosmwasm_schema::cw_serde; +use cosmwasm_std::{to_binary, Addr, CosmosMsg, Decimal, Env, StdResult, Uint128, WasmMsg}; +use vesting_base::msg::ExecuteMsg as BaseExecute; +use vesting_base::types::VestingAccountResponse; /// This structure describes the parameters used for creating a contract. #[cw_serde] @@ -10,3 +13,66 @@ pub struct InstantiateMsg { /// Token info manager address pub token_info_manager: String, } + +#[cw_serde] +pub enum ExecuteMsg { + Base(BaseExecute), + #[serde(rename = "migrate_liquidity_to_pcl_pool")] + MigrateLiquidityToPCLPool { + user: Option, + }, + /// Callbacks; only callable by the contract itself. + Callback(CallbackMsg), +} + +#[cw_serde] +pub enum CallbackMsg { + MigrateLiquidityToClPair { + xyk_pair: Addr, + xyk_lp_token: Addr, + amount: Uint128, + slippage_tolerance: Decimal, + cl_pair: Addr, + ntrn_denom: String, + paired_asset_denom: String, + user: VestingAccountResponse, + }, + ProvideLiquidityToClPairAfterWithdrawal { + ntrn_denom: String, + ntrn_init_balance: Uint128, + paired_asset_denom: String, + paired_asset_init_balance: Uint128, + cl_pair: Addr, + slippage_tolerance: Decimal, + user: VestingAccountResponse, + }, + PostMigrationVestingReschedule { + user: VestingAccountResponse, + }, +} + +// Modified from +// https://github.com/CosmWasm/cosmwasm-plus/blob/v0.2.3/packages/cw20/src/receiver.rs#L15 +impl CallbackMsg { + pub fn to_cosmos_msg(self, env: &Env) -> StdResult { + Ok(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: env.contract.address.to_string(), + msg: to_binary(&ExecuteMsg::Callback(self))?, + funds: vec![], + })) + } +} + +/// This structure describes a migration message. +/// We currently take no arguments for migrations. +#[cw_serde] +#[serde(rename_all = "snake_case")] +pub struct MigrateMsg { + pub max_slippage: Decimal, + pub ntrn_denom: String, + pub paired_denom: String, + pub xyk_pair: String, + pub cl_pair: String, + pub new_lp_token: String, + pub pcl_vesting: String, +} diff --git a/contracts/vesting-lp/src/state.rs b/contracts/vesting-lp/src/state.rs new file mode 100644 index 00000000..9cf45b80 --- /dev/null +++ b/contracts/vesting-lp/src/state.rs @@ -0,0 +1,19 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Decimal}; +use cw_storage_plus::Item; + +/// Config for xyk->CL liquidity migration. +#[cw_serde] +pub struct XykToClMigrationConfig { + /// The maximum allowed slippage tolerance for xyk to CL liquidity migration calls. + pub max_slippage: Decimal, + pub ntrn_denom: String, + pub xyk_pair: Addr, + pub paired_denom: String, + pub cl_pair: Addr, + pub new_lp_token: Addr, + pub pcl_vesting: Addr, +} + +pub const XYK_TO_CL_MIGRATION_CONFIG: Item = + Item::new("xyk_to_cl_migration_config"); diff --git a/packages/vesting-base-lp/Cargo.toml b/packages/vesting-base-lp/Cargo.toml deleted file mode 100644 index d5aca69c..00000000 --- a/packages/vesting-base-lp/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "vesting-base-lp" -version = "1.1.0" -authors = ["Astroport"] -edition = "2021" - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -backtraces = ["cosmwasm-std/backtraces"] -# use library feature to disable all init/handle/query exports -library = [] - -[dependencies] -serde = { version = "1.0.145", default-features = false, features = ["derive"] } -cw2 = { version = "0.15" } -cw20 = { version = "0.15" } -cosmwasm-std = { version = "1.1" } -cw-storage-plus = "0.15" -thiserror = { version = "1.0" } -cw-utils = "0.15" -cosmwasm-schema = { version = "1.1", default-features = false } -vesting-base = {path = "../vesting-base"} -vesting-base-pcl = {path = "../vesting-base-pcl"} -astroport = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v2.8.0" } diff --git a/packages/vesting-base-lp/NOTICE b/packages/vesting-base-lp/NOTICE deleted file mode 100644 index 84b1c210..00000000 --- a/packages/vesting-base-lp/NOTICE +++ /dev/null @@ -1,14 +0,0 @@ -CW20-Base: A reference implementation for fungible token on CosmWasm -Copyright (C) 2020 Confio OÜ - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/packages/vesting-base-lp/README.md b/packages/vesting-base-lp/README.md deleted file mode 100644 index 57accc63..00000000 --- a/packages/vesting-base-lp/README.md +++ /dev/null @@ -1,251 +0,0 @@ -# Neutron Vesting Base - -This library contains basis for configuration and initialisation of vesting contracts. It also contains data models and handlers for interaction with vesting contracts. - -## Usage - -1. To use the library for initialisation of a simple vesting contract just build a default vesting base in its instantiate message: -```rust -use vesting_base::builder::VestingBaseBuilder; - -pub fn instantiate( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - ... - VestingBaseBuilder::default().build(deps, msg.owner, msg.vesting_token)?; - ... -``` - -Read about more advanced building in the [Extensions](#extensions) section. - -2. Simply pass the execute and query requests to the vesting base's execute and query handlers: -```rust -use vesting_base::handlers::{execute as base_execute, query as base_query}; - -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - base_execute(deps, env, info, msg) -} - -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - base_query(deps, env, msg) -} -``` - -### Messages - -The default version exposes the following messages: - -#### ExecuteMsg - -```rust -/// This structure describes the execute messages available in a vesting contract. -#[cw_serde] -pub enum ExecuteMsg { - /// Claim claims vested tokens and sends them to a recipient - Claim { - /// The address that receives the vested tokens - recipient: Option, - /// The amount of tokens to claim - amount: Option, - }, - /// Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template - Receive(Cw20ReceiveMsg), - /// RegisterVestingAccounts registers vesting targets/accounts - RegisterVestingAccounts { - vesting_accounts: Vec, - }, - /// Creates a request to change contract ownership - /// ## Executor - /// Only the current owner can execute this - ProposeNewOwner { - /// The newly proposed owner - owner: String, - /// The validity period of the offer to change the owner - expires_in: u64, - }, - /// Removes a request to change contract ownership - /// ## Executor - /// Only the current owner can execute this - DropOwnershipProposal {}, - /// Claims contract ownership - /// ## Executor - /// Only the newly proposed owner can execute this - ClaimOwnership {}, - /// Sets vesting token - /// ## Executor - /// Only the current owner or token info manager can execute this - SetVestingToken { vesting_token: AssetInfo }, - /// Contains messages associated with the managed extension for vesting contracts. - ManagedExtension { msg: ExecuteMsgManaged }, - /// Contains messages associated with the with_managers extension for vesting contracts. - WithManagersExtension { msg: ExecuteMsgWithManagers }, - /// Contains messages associated with the historical extension for vesting contracts. - HistoricalExtension { msg: ExecuteMsgHistorical }, -} -``` - -The `ManagedExtension`, `WithManagersExtension`, and `HistoricalExtension` messages are extensiom messages. Read about them in the [Extensions](#extensions) section. - -#### QueryMsg - -```rust -/// This structure describes the query messages available in a vesting contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Returns the configuration for the contract using a [`ConfigResponse`] object. - #[returns(ConfigResponse)] - Config {}, - /// Returns information about an address vesting tokens using a [`VestingAccountResponse`] object. - #[returns(VestingAccountResponse)] - VestingAccount { address: String }, - /// Returns a list of addresses that are vesting tokens using a [`VestingAccountsResponse`] object. - #[returns(VestingAccountsResponse)] - VestingAccounts { - start_after: Option, - limit: Option, - order_by: Option, - }, - /// Returns the total unvested amount of tokens for a specific address. - #[returns(Uint128)] - AvailableAmount { address: String }, - /// Timestamp returns the current timestamp - #[returns(u64)] - Timestamp {}, - /// VestingState returns the current vesting state. - #[returns(VestingState)] - VestingState {}, - /// Contains messages associated with the managed extension for vesting contracts. - #[returns(QueryMsgManaged)] - ManagedExtension { msg: QueryMsgManaged }, - /// Contains messages associated with the with_managers extension for vesting contracts. - #[returns(QueryMsgWithManagers)] - WithManagersExtension { msg: QueryMsgWithManagers }, - /// Contains messages associated with the historical extension for vesting contracts. - #[returns(QueryMsgHistorical)] - HistoricalExtension { msg: QueryMsgHistorical }, -} -``` - -The `ManagedExtension`, `WithManagersExtension`, and `HistoricalExtension` messages are extensiom messages. Read about them in the [Extensions](#extensions) section. - -## Extensions - -Created contracts can be extended with a number of features. - -### Managed - -The `managed` extension allows the owner of the vesting contract to remove registered vesting accounts and redeem the corresponding funds. - -```rust -/// This structure describes the execute messages available in a managed vesting contract. -#[cw_serde] -pub enum ExecuteMsgManaged { - /// Removes vesting targets/accounts. - /// ## Executor - /// Only the current owner can execute this - RemoveVestingAccounts { - vesting_accounts: Vec, - /// Specifies the account that will receive the funds taken from the vesting accounts. - clawback_account: String, - }, -} - -/// This structure describes the query messages available in a managed vesting contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsgManaged {} -``` - -### WithManagers - -The `with_managers` extension allows the owner of the vesting contract to add/remove vesting managers — addresses that just like the owner are capable of registering new vesting accounts. - -```rust -/// This structure describes the execute messages available in a with_managers vesting contract. -#[cw_serde] -pub enum ExecuteMsgWithManagers { - /// Adds vesting managers - /// ## Executor - /// Only the current owner can execute this - AddVestingManagers { managers: Vec }, - /// Removes vesting managers - /// ## Executor - /// Only the current owner can execute this - RemoveVestingManagers { managers: Vec }, -} - -/// This structure describes the query messages available in a with_managers vesting contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsgWithManagers { - /// Returns list of vesting managers - /// (the persons who are able to add/remove vesting schedules) - #[returns(Vec)] - VestingManagers {}, -} -``` - -### Historical - -The `historical` allows to query vesting accounts and total vesting state based on a given height. - -```rust -/// This structure describes the execute messages available in a historical vesting contract. -#[cw_serde] -pub enum ExecuteMsgHistorical {} - -/// This structure describes the query messages available in a historical vesting contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsgHistorical { - /// Returns the total unclaimed amount of tokens for a specific address at certain height. - #[returns(Uint128)] - UnclaimedAmountAtHeight { address: String, height: u64 }, - /// Returns the total unclaimed amount of tokens for all the users at certain height. - #[returns(Uint128)] - UnclaimedTotalAmountAtHeight { height: u64 }, -} -``` - -### Extensions usage - -The following example adds all three extensions to the contract, but it's allowed to combine them in any way. -```rust -use vesting_base::builder::VestingBaseBuilder; -use astroport::asset::AssetInfo; -use cosmwasm_schema::cw_serde; - -/// This structure describes the parameters used for creating a contract. -#[cw_serde] -pub struct InstantiateMsg { - /// Address allowed to change contract parameters - pub owner: String, - /// [`AssetInfo`] of the token that's being vested - pub vesting_token: AssetInfo, - /// Initial list of whitelisted vesting managers - pub vesting_managers: Vec, -} - -pub fn instantiate( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - ... - VestingBaseBuilder::default() - .historical() - .managed() - .with_managers(msg.vesting_managers) - .build(deps, msg.owner, msg.vesting_token)?; - ... -``` diff --git a/packages/vesting-base-lp/src/builder.rs b/packages/vesting-base-lp/src/builder.rs deleted file mode 100644 index 85957245..00000000 --- a/packages/vesting-base-lp/src/builder.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::state::{CONFIG, VESTING_MANAGERS}; -use crate::types::{Config, Extensions}; -use cosmwasm_std::{DepsMut, StdResult}; - -/// A builder for vesting contracts with different extensions. -#[derive(Default)] -pub struct VestingBaseBuilder { - vesting_managers: Vec, - historical: bool, - managed: bool, - with_managers: bool, -} - -impl VestingBaseBuilder { - /// Appends the `managed` extension to the created vesting contract. - pub fn managed(&mut self) -> &mut VestingBaseBuilder { - self.managed = true; - self - } - - /// Appends the `with_managers` extension to the created vesting contract. - pub fn with_managers(&mut self, managers: Vec) -> &mut VestingBaseBuilder { - self.vesting_managers.extend(managers); - self.with_managers = true; - self - } - - /// Appends the `historical` extension to the created vesting contract. - pub fn historical(&mut self) -> &mut VestingBaseBuilder { - self.historical = true; - self - } - - /// Validates the inputs and initialises the created contract state. - pub fn build(&self, deps: DepsMut, owner: String, token_info_manager: String) -> StdResult<()> { - let owner = deps.api.addr_validate(&owner)?; - CONFIG.save( - deps.storage, - &Config { - owner, - vesting_token: None, - token_info_manager: deps.api.addr_validate(&token_info_manager)?, - extensions: Extensions { - historical: self.historical, - managed: self.managed, - with_managers: self.with_managers, - }, - }, - )?; - - if self.with_managers { - for m in self.vesting_managers.iter() { - let ma = deps.api.addr_validate(m)?; - VESTING_MANAGERS.save(deps.storage, ma, &())?; - } - }; - - Ok(()) - } -} diff --git a/packages/vesting-base-lp/src/error.rs b/packages/vesting-base-lp/src/error.rs deleted file mode 100644 index c394825b..00000000 --- a/packages/vesting-base-lp/src/error.rs +++ /dev/null @@ -1,65 +0,0 @@ -use cosmwasm_std::{Decimal, OverflowError, StdError}; -use cw_utils::PaymentError; -use thiserror::Error; - -/// This enum describes generator vesting contract errors -#[derive(Error, Debug, PartialEq)] -pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("{0}")] - PaymentError(#[from] PaymentError), - - #[error("Unauthorized")] - Unauthorized {}, - - #[error("Amount is not available!")] - AmountIsNotAvailable {}, - - #[error("Vesting schedule error on addr: {0}. Should satisfy: (start < end and at_start < total) or (start = end and at_start = total)")] - VestingScheduleError(String), - - #[error("Vesting schedule amount error. The total amount should be equal to the CW20 receive amount.")] - VestingScheduleAmountError {}, - - #[error("Contract can't be migrated!")] - MigrationError {}, - - #[error("Vesting token is not set!")] - VestingTokenIsNotSet {}, - - #[error("Contract is in migration state. Please wait for migration to complete.")] - MigrationIncomplete {}, - - #[error( - "Provided slippage tolerance {slippage_tolerance} is more than the max allowed {max_slippage_tolerance}" - )] - MigrationSlippageToBig { - slippage_tolerance: Decimal, - max_slippage_tolerance: Decimal, - }, - - #[error("Migration is complete")] - MigrationComplete {}, -} - -#[allow(clippy::from_over_into)] -impl Into for ContractError { - fn into(self) -> StdError { - StdError::generic_err(self.to_string()) - } -} - -impl From for ContractError { - fn from(o: OverflowError) -> Self { - StdError::from(o).into() - } -} - -pub fn ext_unsupported_err(extension: impl Into + std::fmt::Display) -> StdError { - StdError::generic_err(format!( - "Extension is not enabled for the contract: {}.", - extension - )) -} diff --git a/packages/vesting-base-lp/src/ext_historical.rs b/packages/vesting-base-lp/src/ext_historical.rs deleted file mode 100644 index d494adf6..00000000 --- a/packages/vesting-base-lp/src/ext_historical.rs +++ /dev/null @@ -1,101 +0,0 @@ -use crate::error::{ext_unsupported_err, ContractError}; -use crate::msg::{ExecuteMsgHistorical, QueryMsgHistorical}; -use crate::state::{vesting_info, vesting_state, CONFIG}; -use crate::types::VestingInfo; -use cosmwasm_std::{ - to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Uint128, -}; - -/// Contains the historical extension check and routing of the message. -pub(crate) fn handle_execute_historical_msg( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - _msg: ExecuteMsgHistorical, -) -> Result { - let config = CONFIG.load(deps.storage)?; - if !config.extensions.historical { - return Err(ext_unsupported_err("historical").into()); - } - - // empty handler kept for uniformity with other extensions - unimplemented!() -} - -/// Contains the historical extension check and routing of the message. -pub(crate) fn handle_query_historical_msg( - deps: Deps, - _env: Env, - msg: QueryMsgHistorical, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - if !config.extensions.historical { - return Err(ext_unsupported_err("historical")); - } - - match msg { - QueryMsgHistorical::UnclaimedAmountAtHeight { address, height } => { - to_binary(&query_unclaimed_amount_at_height(deps, address, height)?) - } - QueryMsgHistorical::UnclaimedTotalAmountAtHeight { height } => { - to_binary(&query_total_unclaimed_amount_at_height(deps, height)?) - } - } -} - -/// Returns the available amount of distributed and yet to be claimed tokens for a specific vesting recipient at certain height. -/// -/// * **address** vesting recipient for which to return the available amount of tokens to claim. -/// -/// * **height** the height we querying unclaimed amount for -fn query_unclaimed_amount_at_height( - deps: Deps, - address: String, - height: u64, -) -> StdResult { - let address = deps.api.addr_validate(&address)?; - - let config = CONFIG.load(deps.storage)?; - let maybe_info = vesting_info(config.extensions.historical).may_load_at_height( - deps.storage, - address, - height, - )?; - match &maybe_info { - Some(info) => compute_unclaimed_amount(info), - None => Ok(Uint128::zero()), - } -} - -/// Returns the available amount of distributed and yet to be claimed tokens for all the recipients at certain height. -/// -/// * **height** the height we querying unclaimed amount for -fn query_total_unclaimed_amount_at_height(deps: Deps, height: u64) -> StdResult { - let config = CONFIG.load(deps.storage)?; - let maybe_state = - vesting_state(config.extensions.historical).may_load_at_height(deps.storage, height)?; - match &maybe_state { - Some(info) => Ok(info.total_granted.checked_sub(info.total_released)?), - None => Ok(Uint128::zero()), - } -} - -/// Computes the amount of distributed and yet unclaimed tokens for a specific vesting recipient at certain height. -/// Returns the computed amount if the operation is successful. -/// -/// * **vesting_info** vesting schedules for which to compute the amount of tokens -/// that are vested and can be claimed by the recipient. -fn compute_unclaimed_amount(vesting_info: &VestingInfo) -> StdResult { - let mut available_amount: Uint128 = Uint128::zero(); - for sch in &vesting_info.schedules { - if let Some(end_point) = &sch.end_point { - available_amount = available_amount.checked_add(end_point.amount)?; - } else { - available_amount = available_amount.checked_add(sch.start_point.amount)?; - } - } - - available_amount - .checked_sub(vesting_info.released_amount) - .map_err(StdError::from) -} diff --git a/packages/vesting-base-lp/src/ext_managed.rs b/packages/vesting-base-lp/src/ext_managed.rs deleted file mode 100644 index b11cc1b0..00000000 --- a/packages/vesting-base-lp/src/ext_managed.rs +++ /dev/null @@ -1,121 +0,0 @@ -use crate::error::{ext_unsupported_err, ContractError}; -use crate::handlers::get_vesting_token; -use crate::msg::{ExecuteMsgManaged, QueryMsgManaged}; -use crate::state::{vesting_info, vesting_state, CONFIG}; -use astroport::asset::AssetInfoExt; -use cosmwasm_std::{ - attr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, SubMsg, Uint128, -}; - -/// Contains the managed extension check and routing of the message. -pub(crate) fn handle_execute_managed_msg( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsgManaged, -) -> Result { - let config = CONFIG.load(deps.storage)?; - if !config.extensions.managed { - return Err(ext_unsupported_err("managed").into()); - } - - match msg { - ExecuteMsgManaged::RemoveVestingAccounts { - vesting_accounts, - clawback_account, - } => remove_vesting_accounts(deps, env, info, vesting_accounts, clawback_account), - } -} - -/// Contains the managed extension check and routing of the message. -pub(crate) fn handle_query_managed_msg( - deps: Deps, - _env: Env, - _msg: QueryMsgManaged, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - if !config.extensions.managed { - return Err(ext_unsupported_err("managed")); - } - - // empty handler kept for uniformity with other extensions - unimplemented!() -} - -#[allow(clippy::too_many_arguments)] -fn remove_vesting_accounts( - deps: DepsMut, - env: Env, - info: MessageInfo, - vesting_accounts: Vec, - clawback_account: String, -) -> Result { - let config = CONFIG.load(deps.storage)?; - if info.sender != config.owner { - return Err(ContractError::Unauthorized {}); - } - let vesting_token = get_vesting_token(&config)?; - - let mut response = Response::new(); - - let clawback_address = deps.api.addr_validate(&clawback_account)?; - - // For each vesting account, calculate the amount of tokens to claw back (unclaimed + still - // vesting), transfer the required amount to the owner, remove the vesting information - // from the storage, and decrease the total granted metric. - for vesting_account in vesting_accounts { - let account_address = deps.api.addr_validate(&vesting_account)?; - - let config = CONFIG.load(deps.storage)?; - let vesting_info = vesting_info(config.extensions.historical); - if let Some(account_info) = vesting_info.may_load(deps.storage, account_address.clone())? { - let mut total_granted_for_user = Uint128::zero(); - for sch in account_info.schedules { - if let Some(end_point) = sch.end_point { - total_granted_for_user = - total_granted_for_user.checked_add(end_point.amount)?; - } else { - total_granted_for_user = - total_granted_for_user.checked_add(sch.start_point.amount)?; - } - } - - let amount_to_claw_back = - total_granted_for_user.checked_sub(account_info.released_amount)?; - - let transfer_msg = vesting_token - .with_balance(amount_to_claw_back) - .into_msg(clawback_address.clone())?; - response = response.add_submessage(SubMsg::new(transfer_msg)); - - vesting_state(config.extensions.historical).update::<_, ContractError>( - deps.storage, - env.block.height, - |s| { - // Here we choose the "forget about everything" strategy. E.g., if we granted a user - // 300 tokens, and they claimed 150 tokens, the vesting state is - // { total_granted: 300, total_released: 150 }. - // If after that we remove the user's vesting account, we set the vesting state to - // { total_granted: 0, total_released: 0 }. - // - // If we decided to set it to { total_granted: 150, total_released: 150 }., the - // .total_released value of the vesting state would not be equal to the sum of the - // .released_amount values of all registered accounts. - let mut state = s.ok_or(ContractError::AmountIsNotAvailable {})?; - state.total_granted = - state.total_granted.checked_sub(total_granted_for_user)?; - state.total_released = state - .total_released - .checked_sub(account_info.released_amount)?; - Ok(state) - }, - )?; - vesting_info.remove(deps.storage, account_address, env.block.height)?; - } - } - - Ok(response.add_attributes(vec![ - attr("action", "remove_vesting_accounts"), - attr("sender", &info.sender), - ])) -} diff --git a/packages/vesting-base-lp/src/ext_with_managers.rs b/packages/vesting-base-lp/src/ext_with_managers.rs deleted file mode 100644 index 56573188..00000000 --- a/packages/vesting-base-lp/src/ext_with_managers.rs +++ /dev/null @@ -1,105 +0,0 @@ -use crate::error::{ext_unsupported_err, ContractError}; -use crate::msg::{ExecuteMsgWithManagers, QueryMsgWithManagers}; -use crate::state::{CONFIG, VESTING_MANAGERS}; -use cosmwasm_std::{ - attr, to_binary, Addr, Attribute, Binary, Deps, DepsMut, Env, MessageInfo, Order, Response, - StdError, StdResult, -}; - -/// Contains the with_managers extension check and routing of the message. -pub(crate) fn handle_execute_with_managers_msg( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsgWithManagers, -) -> Result { - let config = CONFIG.load(deps.storage)?; - if !config.extensions.with_managers { - return Err(ext_unsupported_err("with_managers").into()); - } - - match msg { - ExecuteMsgWithManagers::AddVestingManagers { managers } => { - add_vesting_managers(deps, env, info, managers) - } - ExecuteMsgWithManagers::RemoveVestingManagers { managers } => { - remove_vesting_managers(deps, env, info, managers) - } - } -} - -/// Contains the with_managers extension check and routing of the message. -pub(crate) fn handle_query_managers_msg( - deps: Deps, - _env: Env, - msg: QueryMsgWithManagers, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - if !config.extensions.with_managers { - return Err(ext_unsupported_err("with_managers")); - } - - match msg { - QueryMsgWithManagers::VestingManagers {} => to_binary(&query_vesting_managers(deps)?), - } -} - -/// Adds new vesting managers, which have a permission to add/remove vesting schedule -/// -/// * **managers** list of accounts to be added to the whitelist. -fn add_vesting_managers( - deps: DepsMut, - _env: Env, - info: MessageInfo, - managers: Vec, -) -> Result { - let config = CONFIG.load(deps.storage)?; - if info.sender != config.owner { - return Err(ContractError::Unauthorized {}); - } - let mut attrs: Vec = vec![]; - for m in managers { - let ma = deps.api.addr_validate(&m)?; - if !VESTING_MANAGERS.has(deps.storage, ma.clone()) { - VESTING_MANAGERS.save(deps.storage, ma, &())?; - attrs.push(attr("vesting_manager", &m)) - } - } - Ok(Response::new() - .add_attribute("action", "add_vesting_managers") - .add_attributes(attrs)) -} - -/// Removes new vesting managers from the whitelist -/// -/// * **managers** list of accounts to be removed from the whitelist. -fn remove_vesting_managers( - deps: DepsMut, - _env: Env, - info: MessageInfo, - managers: Vec, -) -> Result { - let config = CONFIG.load(deps.storage)?; - if info.sender != config.owner { - return Err(ContractError::Unauthorized {}); - } - let mut attrs: Vec = vec![]; - for m in managers { - let ma = deps.api.addr_validate(&m)?; - if VESTING_MANAGERS.has(deps.storage, ma.clone()) { - VESTING_MANAGERS.remove(deps.storage, ma); - attrs.push(attr("vesting_manager", &m)) - } - } - Ok(Response::new() - .add_attribute("action", "remove_vesting_managers") - .add_attributes(attrs)) -} - -/// Returns a list of vesting schedules using a [`VestingAccountsResponse`] object. -fn query_vesting_managers(deps: Deps) -> StdResult> { - let managers = VESTING_MANAGERS - .keys(deps.storage, None, None, Order::Ascending) - .collect::, StdError>>()?; - Ok(managers) -} diff --git a/packages/vesting-base-lp/src/handlers.rs b/packages/vesting-base-lp/src/handlers.rs deleted file mode 100644 index 1605afbe..00000000 --- a/packages/vesting-base-lp/src/handlers.rs +++ /dev/null @@ -1,779 +0,0 @@ -use crate::error::ContractError; -use crate::ext_historical::{handle_execute_historical_msg, handle_query_historical_msg}; -use crate::ext_managed::{handle_execute_managed_msg, handle_query_managed_msg}; -use crate::ext_with_managers::{handle_execute_with_managers_msg, handle_query_managers_msg}; -use crate::msg::{CallbackMsg, Cw20HookMsg, ExecuteMsg, MigrateMsg, QueryMsg}; -use crate::state::{read_vesting_infos, vesting_info, vesting_state, XYK_TO_CL_MIGRATION_CONFIG}; -use crate::state::{CONFIG, OWNERSHIP_PROPOSAL, VESTING_MANAGERS}; -use crate::types::{ - Config, OrderBy, VestingAccount, VestingAccountResponse, VestingAccountsResponse, VestingInfo, - VestingSchedule, VestingState, XykToClMigrationConfig, -}; -use astroport::asset::{ - addr_opt_validate, native_asset, token_asset_info, AssetInfo, AssetInfoExt, PairInfo, -}; -use astroport::common::{claim_ownership, drop_ownership_proposal, propose_new_owner}; -use astroport::pair::{ - Cw20HookMsg as PairCw20HookMsg, ExecuteMsg as PairExecuteMsg, QueryMsg as PairQueryMsg, -}; - -use cosmwasm_std::{ - attr, from_binary, to_binary, Addr, Binary, Coin, CosmosMsg, Decimal, Deps, DepsMut, Env, - MessageInfo, Response, StdError, StdResult, Storage, SubMsg, Uint128, WasmMsg, -}; -use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg, Cw20ReceiveMsg}; -use cw_utils::must_pay; - -/// Exposes execute functions available in the contract. -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - match msg { - ExecuteMsg::Claim { recipient, amount } => claim(deps, env, info, recipient, amount), - ExecuteMsg::Receive(msg) => receive_cw20(deps, env, info, msg), - ExecuteMsg::RegisterVestingAccounts { vesting_accounts } => { - let config = CONFIG.load(deps.storage)?; - let vesting_token = get_vesting_token(&config)?; - - match &vesting_token { - AssetInfo::NativeToken { denom } - if is_sender_whitelisted(deps.storage, &config, &info.sender) => - { - let amount = must_pay(&info, denom)?; - register_vesting_accounts(deps, vesting_accounts, amount, env.block.height) - } - _ => Err(ContractError::Unauthorized {}), - } - } - ExecuteMsg::ProposeNewOwner { owner, expires_in } => { - let config: Config = CONFIG.load(deps.storage)?; - - propose_new_owner( - deps, - info, - env, - 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) - })?; - - Ok(()) - }) - .map_err(Into::into) - } - ExecuteMsg::SetVestingToken { vesting_token } => { - set_vesting_token(deps, env, info, vesting_token) - } - ExecuteMsg::ManagedExtension { msg } => handle_execute_managed_msg(deps, env, info, msg), - ExecuteMsg::WithManagersExtension { msg } => { - handle_execute_with_managers_msg(deps, env, info, msg) - } - ExecuteMsg::HistoricalExtension { msg } => { - handle_execute_historical_msg(deps, env, info, msg) - } - ExecuteMsg::MigrateLiquidityToPCLPool { user } => { - execute_migrate_liquidity(deps, info, env, None, user) - } - ExecuteMsg::Callback(msg) => _handle_callback(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.load(deps.storage)?; - let vesting_token = get_vesting_token(&config)?; - - // Permission check - if !is_sender_whitelisted( - deps.storage, - &config, - &deps.api.addr_validate(&cw20_msg.sender)?, - ) || token_asset_info(info.sender) != vesting_token - { - return Err(ContractError::Unauthorized {}); - } - - match from_binary(&cw20_msg.msg)? { - Cw20HookMsg::RegisterVestingAccounts { vesting_accounts } => { - register_vesting_accounts(deps, vesting_accounts, cw20_msg.amount, env.block.height) - } - } -} - -/// Create new vesting schedules. -/// -/// * **vesting_accounts** list of accounts and associated vesting schedules to create. -/// -/// * **cw20_amount** sets the amount that confirms the total amount of all accounts to register. -fn register_vesting_accounts( - deps: DepsMut, - vesting_accounts: Vec, - amount: Uint128, - height: u64, -) -> Result { - let response = Response::new(); - let config = CONFIG.load(deps.storage)?; - let mut to_deposit = Uint128::zero(); - - for mut vesting_account in vesting_accounts { - let mut released_amount = Uint128::zero(); - let account_address = deps.api.addr_validate(&vesting_account.address)?; - - assert_vesting_schedules(&account_address, &vesting_account.schedules)?; - - for sch in &vesting_account.schedules { - let amount = if let Some(end_point) = &sch.end_point { - end_point.amount - } else { - sch.start_point.amount - }; - to_deposit = to_deposit.checked_add(amount)?; - } - - let vesting_info = vesting_info(config.extensions.historical); - if let Some(mut old_info) = vesting_info.may_load(deps.storage, account_address.clone())? { - released_amount = old_info.released_amount; - vesting_account.schedules.append(&mut old_info.schedules); - } - - vesting_info.save( - deps.storage, - account_address, - &VestingInfo { - schedules: vesting_account.schedules, - released_amount, - }, - height, - )?; - } - - if to_deposit != amount { - return Err(ContractError::VestingScheduleAmountError {}); - } - - vesting_state(config.extensions.historical).update::<_, ContractError>( - deps.storage, - height, - |s| { - let mut state = s.unwrap_or_default(); - state.total_granted = state.total_granted.checked_add(to_deposit)?; - Ok(state) - }, - )?; - - Ok(response.add_attributes({ - vec![ - attr("action", "register_vesting_accounts"), - attr("deposited", to_deposit), - ] - })) -} - -/// Claims vested tokens and transfers them to the vesting recipient. -/// -/// * **recipient** vesting recipient for which to claim tokens. -/// -/// * **amount** amount of vested tokens to claim. -fn claim( - deps: DepsMut, - env: Env, - info: MessageInfo, - recipient: Option, - amount: Option, -) -> Result { - let config = CONFIG.load(deps.storage)?; - let vesting_token = get_vesting_token(&config)?; - let vesting_info = vesting_info(config.extensions.historical); - let mut sender_vesting_info = vesting_info.load(deps.storage, info.sender.clone())?; - - let available_amount = - compute_available_amount(env.block.time.seconds(), &sender_vesting_info)?; - - let claim_amount = if let Some(a) = amount { - if a > available_amount { - return Err(ContractError::AmountIsNotAvailable {}); - }; - a - } else { - available_amount - }; - - let mut response = Response::new(); - - if !claim_amount.is_zero() { - let transfer_msg = vesting_token - .with_balance(claim_amount) - .into_msg(recipient.unwrap_or_else(|| info.sender.to_string()))?; - response = response.add_submessage(SubMsg::new(transfer_msg)); - - sender_vesting_info.released_amount = sender_vesting_info - .released_amount - .checked_add(claim_amount)?; - vesting_info.save( - deps.storage, - info.sender.clone(), - &sender_vesting_info, - env.block.height, - )?; - vesting_state(config.extensions.historical).update::<_, ContractError>( - deps.storage, - env.block.height, - |s| { - let mut state = s.ok_or(ContractError::AmountIsNotAvailable {})?; - state.total_released = state.total_released.checked_add(claim_amount)?; - Ok(state) - }, - )?; - }; - - Ok(response.add_attributes(vec![ - attr("action", "claim"), - attr("address", &info.sender), - attr("available_amount", available_amount), - attr("claimed_amount", claim_amount), - ])) -} - -pub(crate) fn set_vesting_token( - deps: DepsMut, - _env: Env, - info: MessageInfo, - token: AssetInfo, -) -> Result { - let mut config = CONFIG.load(deps.storage)?; - if info.sender != config.owner && info.sender != config.token_info_manager { - return Err(ContractError::Unauthorized {}); - } - token.check(deps.api)?; - config.vesting_token = Some(token); - - CONFIG.save(deps.storage, &config)?; - Ok(Response::new()) -} - -pub(crate) fn get_vesting_token(config: &Config) -> Result { - config - .vesting_token - .clone() - .ok_or(ContractError::VestingTokenIsNotSet {}) -} - -fn execute_migrate_liquidity( - deps: DepsMut, - info: MessageInfo, - env: Env, - slippage_tolerance: Option, - user: Option, -) -> Result { - let config = CONFIG.load(deps.storage)?; - let migration_config: XykToClMigrationConfig = XYK_TO_CL_MIGRATION_CONFIG.load(deps.storage)?; - let address = match user { - Some(val) => deps.api.addr_validate(&val)?, - None => info.sender, - }; - let info = vesting_info(config.extensions.historical).load(deps.storage, address.clone())?; - let mut resp = Response::default(); - let user = VestingAccountResponse { address, info }; - - // get pairs LP token addresses - let pair_info: PairInfo = deps - .querier - .query_wasm_smart(migration_config.xyk_pair.clone(), &PairQueryMsg::Pair {})?; - - // query max available amounts to be withdrawn from pool - let max_available_amount = { - let resp: BalanceResponse = deps.querier.query_wasm_smart( - pair_info.liquidity_token.clone(), - &Cw20QueryMsg::Balance { - address: env.contract.address.to_string(), - }, - )?; - resp.balance - }; - - if max_available_amount.is_zero() { - return Ok(resp); - } - - let user_amount = compute_share(&user.info)?; - - if let Some(slippage_tolerance) = slippage_tolerance { - if slippage_tolerance.gt(&migration_config.max_slippage) { - return Err(ContractError::MigrationSlippageToBig { - slippage_tolerance, - max_slippage_tolerance: migration_config.max_slippage, - }); - } - } - - let slippage_tolerance = slippage_tolerance.unwrap_or(migration_config.max_slippage); - - resp = resp.add_message( - CallbackMsg::MigrateLiquidityToClPair { - xyk_pair: migration_config.xyk_pair.clone(), - xyk_lp_token: pair_info.liquidity_token.clone(), - amount: user_amount, - slippage_tolerance, - cl_pair: migration_config.cl_pair.clone(), - ntrn_denom: migration_config.ntrn_denom.clone(), - paired_asset_denom: migration_config.paired_denom.clone(), - user, - } - .to_cosmos_msg(&env)?, - ); - - Ok(resp) -} - -fn _handle_callback( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: CallbackMsg, -) -> Result { - // Only the contract itself can call callbacks - if info.sender != env.contract.address { - return Err(ContractError::Unauthorized {}); - } - match msg { - CallbackMsg::MigrateLiquidityToClPair { - xyk_pair, - xyk_lp_token, - amount, - slippage_tolerance, - cl_pair, - ntrn_denom, - paired_asset_denom, - user, - } => migrate_liquidity_to_cl_pair_callback( - deps, - env, - xyk_pair, - xyk_lp_token, - amount, - slippage_tolerance, - cl_pair, - ntrn_denom, - paired_asset_denom, - user, - ), - CallbackMsg::ProvideLiquidityToClPairAfterWithdrawal { - ntrn_denom, - ntrn_init_balance, - paired_asset_denom, - paired_asset_init_balance, - cl_pair, - slippage_tolerance, - user, - } => provide_liquidity_to_cl_pair_after_withdrawal_callback( - deps, - env, - ntrn_denom, - ntrn_init_balance, - paired_asset_denom, - paired_asset_init_balance, - cl_pair, - slippage_tolerance, - user, - ), - CallbackMsg::PostMigrationVestingReschedule { user } => { - post_migration_vesting_reschedule_callback(deps, env, &user) - } - } -} - -#[allow(clippy::too_many_arguments)] -fn migrate_liquidity_to_cl_pair_callback( - deps: DepsMut, - env: Env, - xyk_pair: Addr, - xyk_lp_token: Addr, - amount: Uint128, - slippage_tolerance: Decimal, - cl_pair: Addr, - ntrn_denom: String, - paired_asset_denom: String, - user: VestingAccountResponse, -) -> Result { - let ntrn_init_balance = deps - .querier - .query_balance(env.contract.address.to_string(), ntrn_denom.clone())? - .amount; - let paired_asset_init_balance = deps - .querier - .query_balance(env.contract.address.to_string(), paired_asset_denom.clone())? - .amount; - - let mut msgs: Vec = vec![]; - - // push message to withdraw liquidity from the xyk pair - if !amount.is_zero() { - msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: xyk_lp_token.to_string(), - msg: to_binary(&Cw20ExecuteMsg::Send { - contract: xyk_pair.to_string(), - amount, - msg: to_binary(&PairCw20HookMsg::WithdrawLiquidity { assets: vec![] })?, - })?, - funds: vec![], - })) - } - let config = CONFIG.load(deps.storage)?; - vesting_state(config.extensions.historical).update::<_, ContractError>( - deps.storage, - env.block.height, - |s| { - let mut state = s.unwrap_or_default(); - state.total_released = state.total_released.checked_add(amount)?; - Ok(state) - }, - )?; - // push the next migration step as a callback message - msgs.push( - CallbackMsg::ProvideLiquidityToClPairAfterWithdrawal { - ntrn_denom, - ntrn_init_balance, - paired_asset_denom, - paired_asset_init_balance, - cl_pair, - slippage_tolerance, - user, - } - .to_cosmos_msg(&env)?, - ); - - Ok(Response::default().add_messages(msgs)) -} - -#[allow(clippy::too_many_arguments)] -fn provide_liquidity_to_cl_pair_after_withdrawal_callback( - deps: DepsMut, - env: Env, - ntrn_denom: String, - ntrn_init_balance: Uint128, - paired_asset_denom: String, - paired_asset_init_balance: Uint128, - cl_pair_address: Addr, - slippage_tolerance: Decimal, - user: VestingAccountResponse, -) -> Result { - let ntrn_balance_after_withdrawal = deps - .querier - .query_balance(env.contract.address.to_string(), ntrn_denom.clone())? - .amount; - let paired_asset_balance_after_withdrawal = deps - .querier - .query_balance(env.contract.address.to_string(), paired_asset_denom.clone())? - .amount; - - // calc amount of assets that's been withdrawn - let withdrawn_ntrn_amount = ntrn_balance_after_withdrawal.checked_sub(ntrn_init_balance)?; - let withdrawn_paired_asset_amount = - paired_asset_balance_after_withdrawal.checked_sub(paired_asset_init_balance)?; - - let mut msgs: Vec = vec![]; - - if !withdrawn_ntrn_amount.is_zero() && !withdrawn_paired_asset_amount.is_zero() { - msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: cl_pair_address.to_string(), - msg: to_binary(&PairExecuteMsg::ProvideLiquidity { - assets: vec![ - native_asset(ntrn_denom.clone(), withdrawn_ntrn_amount), - native_asset(paired_asset_denom.clone(), withdrawn_paired_asset_amount), - ], - slippage_tolerance: Some(slippage_tolerance), - auto_stake: None, - receiver: None, - })?, - funds: vec![ - Coin::new(withdrawn_ntrn_amount.into(), ntrn_denom), - Coin::new(withdrawn_paired_asset_amount.into(), paired_asset_denom), - ], - })) - } - - msgs.push(CallbackMsg::PostMigrationVestingReschedule { user }.to_cosmos_msg(&env)?); - - Ok(Response::default().add_messages(msgs)) -} - -fn post_migration_vesting_reschedule_callback( - deps: DepsMut, - env: Env, - user: &VestingAccountResponse, -) -> Result { - let config = CONFIG.load(deps.storage)?; - let migration_config: XykToClMigrationConfig = XYK_TO_CL_MIGRATION_CONFIG.load(deps.storage)?; - let balance_response: BalanceResponse = deps.querier.query_wasm_smart( - &migration_config.new_lp_token, - &Cw20QueryMsg::Balance { - address: env.contract.address.to_string(), - }, - )?; - let current_balance = balance_response.balance; - - let schedule = user.info.schedules.last().unwrap(); - - let new_end_point; - if let Some(end_point) = &schedule.end_point { - new_end_point = Option::from(vesting_base_pcl::types::VestingSchedulePoint { - time: end_point.time, - amount: current_balance, - }) - } else { - new_end_point = None - } - - let new_schedule = vesting_base_pcl::types::VestingSchedule { - start_point: vesting_base_pcl::types::VestingSchedulePoint { - time: schedule.start_point.time, - amount: Uint128::zero(), - }, - end_point: new_end_point, - }; - - let vesting_info = vesting_info(config.extensions.historical); - - vesting_info.save( - deps.storage, - user.address.clone(), - &VestingInfo { - schedules: vec![], - released_amount: Uint128::zero(), - }, - env.block.height, - )?; - let msgs = vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: migration_config.new_lp_token.to_string(), - funds: vec![], - msg: to_binary(&Cw20ExecuteMsg::Send { - contract: migration_config.pcl_vesting.to_string(), - amount: current_balance, - msg: to_binary(&vesting_base_pcl::msg::Cw20HookMsg::MigrateXYKLiquidity { - user_address_raw: user.address.clone(), - user_vesting_info: vesting_base_pcl::types::VestingInfo { - schedules: vec![new_schedule], - released_amount: Uint128::zero(), - }, - })?, - })?, - })]; - - Ok(Response::new().add_messages(msgs)) -} - -/// Exposes all the queries available in the contract. -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Config {} => Ok(to_binary(&query_config(deps)?)?), - QueryMsg::VestingAccount { address } => { - Ok(to_binary(&query_vesting_account(deps, address)?)?) - } - QueryMsg::VestingAccounts { - start_after, - limit, - order_by, - } => Ok(to_binary(&query_vesting_accounts( - deps, - start_after, - limit, - order_by, - )?)?), - QueryMsg::AvailableAmount { address } => Ok(to_binary(&query_vesting_available_amount( - deps, env, address, - )?)?), - QueryMsg::VestingState {} => Ok(to_binary(&query_vesting_state(deps)?)?), - QueryMsg::Timestamp {} => Ok(to_binary(&query_timestamp(env)?)?), - QueryMsg::ManagedExtension { msg } => handle_query_managed_msg(deps, env, msg), - QueryMsg::WithManagersExtension { msg } => handle_query_managers_msg(deps, env, msg), - QueryMsg::HistoricalExtension { msg } => handle_query_historical_msg(deps, env, msg), - } -} - -/// Returns the vesting contract configuration using a [`Config`] object. -fn query_config(deps: Deps) -> StdResult { - let config = CONFIG.load(deps.storage)?; - Ok(config) -} - -/// Returns the accumulated vesting information for all addresses using a [`VestingState`] object. -fn query_vesting_state(deps: Deps) -> StdResult { - let config = CONFIG.load(deps.storage)?; - let state = vesting_state(config.extensions.historical).load(deps.storage)?; - - Ok(state) -} - -/// Return the current block timestamp (in seconds) -/// * **env** is an object of type [`Env`]. -fn query_timestamp(env: Env) -> StdResult { - Ok(env.block.time.seconds()) -} - -/// Returns the vesting data for a specific vesting recipient using a [`VestingAccountResponse`] object. -/// -/// * **address** vesting recipient for which to return vesting data. -fn query_vesting_account(deps: Deps, address: String) -> StdResult { - let address = deps.api.addr_validate(&address)?; - let config = CONFIG.load(deps.storage)?; - let info = vesting_info(config.extensions.historical).load(deps.storage, address.clone())?; - - Ok(VestingAccountResponse { address, info }) -} - -/// Returns a list of vesting schedules using a [`VestingAccountsResponse`] object. -/// -/// * **start_after** index from which to start reading vesting schedules. -/// -/// * **limit** amount of vesting schedules to return. -/// -/// * **order_by** whether results should be returned in an ascending or descending order. -fn query_vesting_accounts( - deps: Deps, - start_after: Option, - limit: Option, - order_by: Option, -) -> StdResult { - let start_after = addr_opt_validate(deps.api, &start_after)?; - - let vesting_infos = read_vesting_infos(deps, start_after, limit, order_by)?; - - let vesting_accounts: Vec<_> = vesting_infos - .into_iter() - .map(|(address, info)| VestingAccountResponse { address, info }) - .collect(); - - Ok(VestingAccountsResponse { vesting_accounts }) -} - -/// Returns the available amount of vested and yet to be claimed tokens for a specific vesting recipient. -/// -/// * **address** vesting recipient for which to return the available amount of tokens to claim. -fn query_vesting_available_amount(deps: Deps, env: Env, address: String) -> StdResult { - let address = deps.api.addr_validate(&address)?; - - let config = CONFIG.load(deps.storage)?; - let info = vesting_info(config.extensions.historical).load(deps.storage, address)?; - let available_amount = compute_available_amount(env.block.time.seconds(), &info)?; - Ok(available_amount) -} - -/// Manages contract migration. -pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result { - XYK_TO_CL_MIGRATION_CONFIG.save( - deps.storage, - &XykToClMigrationConfig { - max_slippage: msg.max_slippage, - ntrn_denom: msg.ntrn_denom, - xyk_pair: deps.api.addr_validate(msg.xyk_pair.as_str())?, - paired_denom: msg.paired_denom, - cl_pair: deps.api.addr_validate(msg.cl_pair.as_str())?, - new_lp_token: deps.api.addr_validate(msg.new_lp_token.as_str())?, - pcl_vesting: deps.api.addr_validate(msg.pcl_vesting.as_str())?, - }, - )?; - - Ok(Response::default()) -} - -fn is_sender_whitelisted(store: &mut dyn Storage, config: &Config, sender: &Addr) -> bool { - if *sender == config.owner { - return true; - } - if VESTING_MANAGERS.has(store, sender.clone()) { - return true; - } - false -} - -/// Asserts the validity of a list of vesting schedules. -/// -/// * **addr** receiver of the vested tokens. -/// -/// * **vesting_schedules** vesting schedules to validate. -fn assert_vesting_schedules( - addr: &Addr, - vesting_schedules: &[VestingSchedule], -) -> Result<(), ContractError> { - for sch in vesting_schedules { - if let Some(end_point) = &sch.end_point { - if !(sch.start_point.time < end_point.time && sch.start_point.amount < end_point.amount) - { - return Err(ContractError::VestingScheduleError(addr.to_string())); - } - } - } - - Ok(()) -} - -/// Computes the amount of vested and yet unclaimed tokens for a specific vesting recipient. -/// Returns the computed amount if the operation is successful. -/// -/// * **current_time** timestamp from which to start querying for vesting schedules. -/// Schedules that started later than current_time will be omitted. -/// -/// * **vesting_info** vesting schedules for which to compute the amount of tokens -/// that are vested and can be claimed by the recipient. -fn compute_available_amount(current_time: u64, vesting_info: &VestingInfo) -> StdResult { - let mut available_amount: Uint128 = Uint128::zero(); - for sch in &vesting_info.schedules { - if sch.start_point.time > current_time { - continue; - } - - available_amount = available_amount.checked_add(sch.start_point.amount)?; - - if let Some(end_point) = &sch.end_point { - let passed_time = current_time.min(end_point.time) - sch.start_point.time; - let time_period = end_point.time - sch.start_point.time; - if passed_time != 0 && time_period != 0 { - let release_amount = Uint128::from(passed_time).multiply_ratio( - end_point.amount.checked_sub(sch.start_point.amount)?, - time_period, - ); - available_amount = available_amount.checked_add(release_amount)?; - } - } - } - - available_amount - .checked_sub(vesting_info.released_amount) - .map_err(StdError::from) -} - -fn compute_share(vesting_info: &VestingInfo) -> StdResult { - let mut available_amount: Uint128 = Uint128::zero(); - for sch in &vesting_info.schedules { - if let Some(end_point) = &sch.end_point { - available_amount = available_amount.checked_add(end_point.amount)? - } - } - - Ok(available_amount.checked_sub(vesting_info.released_amount)?) -} diff --git a/packages/vesting-base-lp/src/lib.rs b/packages/vesting-base-lp/src/lib.rs deleted file mode 100644 index 594d5e36..00000000 --- a/packages/vesting-base-lp/src/lib.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub mod builder; -pub mod error; -pub mod handlers; -pub mod msg; -pub mod state; -pub mod types; - -pub(crate) mod ext_historical; -pub(crate) mod ext_managed; -pub(crate) mod ext_with_managers; diff --git a/packages/vesting-base-lp/src/msg.rs b/packages/vesting-base-lp/src/msg.rs deleted file mode 100644 index b4a58ea7..00000000 --- a/packages/vesting-base-lp/src/msg.rs +++ /dev/null @@ -1,210 +0,0 @@ -use crate::types::{ - Config, OrderBy, VestingAccount, VestingAccountResponse, VestingAccountsResponse, VestingState, -}; -use astroport::asset::AssetInfo; -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{to_binary, Addr, Binary, CosmosMsg, Decimal, Env, StdResult, Uint128, WasmMsg}; -use cw20::Cw20ReceiveMsg; - -/// This structure describes the execute messages available in a vesting contract. -#[cw_serde] -pub enum ExecuteMsg { - /// Claim claims vested tokens and sends them to a recipient - Claim { - /// The address that receives the vested tokens - recipient: Option, - /// The amount of tokens to claim - amount: Option, - }, - /// Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template - Receive(Cw20ReceiveMsg), - /// RegisterVestingAccounts registers vesting targets/accounts - RegisterVestingAccounts { - vesting_accounts: Vec, - }, - /// Creates a request to change contract ownership - /// ## Executor - /// Only the current owner can execute this - ProposeNewOwner { - /// The newly proposed owner - owner: String, - /// The validity period of the offer to change the owner - expires_in: u64, - }, - /// Removes a request to change contract ownership - /// ## Executor - /// Only the current owner can execute this - DropOwnershipProposal {}, - /// Claims contract ownership - /// ## Executor - /// Only the newly proposed owner can execute this - ClaimOwnership {}, - /// Sets vesting token - /// ## Executor - /// Only the current owner or token info manager can execute this - SetVestingToken { vesting_token: AssetInfo }, - /// Contains messages associated with the managed extension for vesting contracts. - ManagedExtension { msg: ExecuteMsgManaged }, - /// Contains messages associated with the with_managers extension for vesting contracts. - WithManagersExtension { msg: ExecuteMsgWithManagers }, - /// Contains messages associated with the historical extension for vesting contracts. - HistoricalExtension { msg: ExecuteMsgHistorical }, - /// TODO: detailed decription - #[serde(rename = "migrate_liquidity_to_pcl_pool")] - MigrateLiquidityToPCLPool { user: Option }, - /// Callbacks; only callable by the contract itself. - Callback(CallbackMsg), -} - -/// This structure describes the execute messages available in a managed vesting contract. -#[cw_serde] -pub enum ExecuteMsgManaged { - /// Removes vesting targets/accounts. - /// ## Executor - /// Only the current owner can execute this - RemoveVestingAccounts { - vesting_accounts: Vec, - /// Specifies the account that will receive the funds taken from the vesting accounts. - clawback_account: String, - }, -} - -/// This structure describes the execute messages available in a with_managers vesting contract. -#[cw_serde] -pub enum ExecuteMsgWithManagers { - /// Adds vesting managers - /// ## Executor - /// Only the current owner can execute this - AddVestingManagers { managers: Vec }, - /// Removes vesting managers - /// ## Executor - /// Only the current owner can execute this - RemoveVestingManagers { managers: Vec }, -} - -/// This structure describes the execute messages available in a historical vesting contract. -#[cw_serde] -pub enum ExecuteMsgHistorical {} - -/// This structure describes the query messages available in a vesting contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Returns the configuration for the contract using a [`ConfigResponse`] object. - #[returns(Config)] - Config {}, - /// Returns information about an address vesting tokens using a [`VestingAccountResponse`] object. - #[returns(VestingAccountResponse)] - VestingAccount { address: String }, - /// Returns a list of addresses that are vesting tokens using a [`VestingAccountsResponse`] object. - #[returns(VestingAccountsResponse)] - VestingAccounts { - start_after: Option, - limit: Option, - order_by: Option, - }, - /// Returns the total unvested amount of tokens for a specific address. - #[returns(Uint128)] - AvailableAmount { address: String }, - /// Timestamp returns the current timestamp - #[returns(u64)] - Timestamp {}, - /// VestingState returns the current vesting state. - #[returns(VestingState)] - VestingState {}, - /// Contains messages associated with the managed extension for vesting contracts. - #[returns(Binary)] - ManagedExtension { msg: QueryMsgManaged }, - /// Contains messages associated with the with_managers extension for vesting contracts. - #[returns(QueryMsgWithManagers)] - WithManagersExtension { msg: QueryMsgWithManagers }, - /// Contains messages associated with the historical extension for vesting contracts. - #[returns(QueryMsgHistorical)] - HistoricalExtension { msg: QueryMsgHistorical }, -} - -/// This structure describes the query messages available in a managed vesting contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsgManaged {} - -/// This structure describes the query messages available in a with_managers vesting contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsgWithManagers { - /// Returns list of vesting managers - /// (the persons who are able to add/remove vesting schedules) - #[returns(Vec)] - VestingManagers {}, -} - -/// This structure describes the query messages available in a historical vesting contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsgHistorical { - /// Returns the total unclaimed amount of tokens for a specific address at certain height. - #[returns(Uint128)] - UnclaimedAmountAtHeight { address: String, height: u64 }, - /// Returns the total unclaimed amount of tokens for all the users at certain height. - #[returns(Uint128)] - UnclaimedTotalAmountAtHeight { height: u64 }, -} - -/// This structure describes a migration message. -/// We currently take no arguments for migrations. -#[cw_serde] -#[serde(rename_all = "snake_case")] -pub struct MigrateMsg { - pub max_slippage: Decimal, - pub ntrn_denom: String, - pub paired_denom: String, - pub xyk_pair: String, - pub cl_pair: String, - pub new_lp_token: String, - pub pcl_vesting: String, -} -/// This structure describes a CW20 hook message. -#[cw_serde] -pub enum Cw20HookMsg { - /// RegisterVestingAccounts registers vesting targets/accounts - RegisterVestingAccounts { - vesting_accounts: Vec, - }, -} -#[cw_serde] -pub enum CallbackMsg { - MigrateLiquidityToClPair { - xyk_pair: Addr, - xyk_lp_token: Addr, - amount: Uint128, - slippage_tolerance: Decimal, - cl_pair: Addr, - ntrn_denom: String, - paired_asset_denom: String, - user: VestingAccountResponse, - }, - ProvideLiquidityToClPairAfterWithdrawal { - ntrn_denom: String, - ntrn_init_balance: Uint128, - paired_asset_denom: String, - paired_asset_init_balance: Uint128, - cl_pair: Addr, - slippage_tolerance: Decimal, - user: VestingAccountResponse, - }, - PostMigrationVestingReschedule { - user: VestingAccountResponse, - }, -} - -// Modified from -// https://github.com/CosmWasm/cosmwasm-plus/blob/v0.2.3/packages/cw20/src/receiver.rs#L15 -impl CallbackMsg { - pub fn to_cosmos_msg(self, env: &Env) -> StdResult { - Ok(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: env.contract.address.to_string(), - msg: to_binary(&ExecuteMsg::Callback(self))?, - funds: vec![], - })) - } -} diff --git a/packages/vesting-base-lp/src/state.rs b/packages/vesting-base-lp/src/state.rs deleted file mode 100644 index db3cac06..00000000 --- a/packages/vesting-base-lp/src/state.rs +++ /dev/null @@ -1,162 +0,0 @@ -use crate::types::{Config, OrderBy, VestingInfo, VestingState, XykToClMigrationConfig}; -use astroport::common::OwnershipProposal; -use cosmwasm_std::{Addr, Deps, StdResult}; -use cw_storage_plus::{Bound, Item, Map, SnapshotItem, SnapshotMap, Strategy}; - -pub(crate) const CONFIG: Item = Item::new("config"); -pub(crate) const OWNERSHIP_PROPOSAL: Item = Item::new("ownership_proposal"); -pub(crate) const VESTING_MANAGERS: Map = Map::new("vesting_managers"); -pub(crate) const VESTING_STATE: SnapshotItem = SnapshotItem::new( - "vesting_state", - "vesting_state__checkpoints", - "vesting_state__changelog", - Strategy::Never, -); -pub(crate) const VESTING_INFO: SnapshotMap = SnapshotMap::new( - "vesting_info", - "vesting_info__checkpoints", - "vesting_info__changelog", - Strategy::Never, -); -pub(crate) const VESTING_STATE_HISTORICAL: SnapshotItem = SnapshotItem::new( - "vesting_state", - "vesting_state__checkpoints", - "vesting_state__changelog", - Strategy::EveryBlock, -); -pub(crate) const VESTING_INFO_HISTORICAL: SnapshotMap = SnapshotMap::new( - "vesting_info", - "vesting_info__checkpoints", - "vesting_info__changelog", - Strategy::EveryBlock, -); - -pub(crate) fn vesting_state(historical: bool) -> SnapshotItem<'static, VestingState> { - if historical { - return VESTING_STATE_HISTORICAL; - } - VESTING_STATE -} - -pub(crate) fn vesting_info(historical: bool) -> SnapshotMap<'static, Addr, VestingInfo> { - if historical { - return VESTING_INFO_HISTORICAL; - } - VESTING_INFO -} - -const MAX_LIMIT: u32 = 30; -const DEFAULT_LIMIT: u32 = 10; - -/// Returns an empty vector if it does not find data, otherwise returns a vector that -/// contains objects of type [`VESTING_INFO`]. -/// ## Params -/// -/// * **start_after** index from which to start reading vesting schedules. -/// -/// * **limit** amount of vesting schedules to read. -/// -/// * **order_by** whether results should be returned in an ascending or descending order. -pub(crate) fn read_vesting_infos( - deps: Deps, - start_after: Option, - limit: Option, - order_by: Option, -) -> StdResult> { - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let start_after = start_after.map(Bound::exclusive); - - let (start, end) = match &order_by { - Some(OrderBy::Asc) => (start_after, None), - _ => (None, start_after), - }; - - let info: Vec<(Addr, VestingInfo)> = VESTING_INFO - .range( - deps.storage, - start, - end, - order_by.unwrap_or(OrderBy::Desc).into(), - ) - .take(limit) - .filter_map(|v| v.ok()) - .collect(); - - Ok(info) -} - -#[cfg(test)] -mod testing { - use super::*; - - #[test] - fn read_vesting_infos_as_expected() { - use cosmwasm_std::{testing::mock_dependencies, Uint128}; - let mut deps = mock_dependencies(); - let historical = false; - - let vi_mock = VestingInfo { - released_amount: Uint128::zero(), - schedules: vec![], - }; - - for i in 1..5 { - let key = Addr::unchecked(format! {"address{}", i}); - - vesting_info(historical) - .save(&mut deps.storage, key, &vi_mock, 1) - .unwrap(); - } - - let res = read_vesting_infos( - deps.as_ref(), - Some(Addr::unchecked("address2")), - None, - Some(OrderBy::Asc), - ) - .unwrap(); - assert_eq!( - res, - vec![ - (Addr::unchecked("address3"), vi_mock.clone()), - (Addr::unchecked("address4"), vi_mock.clone()), - ] - ); - - let res = read_vesting_infos( - deps.as_ref(), - Some(Addr::unchecked("address2")), - Some(1), - Some(OrderBy::Asc), - ) - .unwrap(); - assert_eq!(res, vec![(Addr::unchecked("address3"), vi_mock.clone())]); - - let res = read_vesting_infos( - deps.as_ref(), - Some(Addr::unchecked("address3")), - None, - Some(OrderBy::Desc), - ) - .unwrap(); - assert_eq!( - res, - vec![ - (Addr::unchecked("address2"), vi_mock.clone()), - (Addr::unchecked("address1"), vi_mock.clone()), - ] - ); - - let res = read_vesting_infos( - deps.as_ref(), - Some(Addr::unchecked("address3")), - Some(1), - Some(OrderBy::Desc), - ) - .unwrap(); - assert_eq!(res, vec![(Addr::unchecked("address2"), vi_mock.clone())]); - } -} - -pub const XYK_TO_CL_MIGRATION_CONFIG: Item = - Item::new("xyk_to_cl_migration_config"); diff --git a/packages/vesting-base-lp/src/types.rs b/packages/vesting-base-lp/src/types.rs deleted file mode 100644 index feb834ff..00000000 --- a/packages/vesting-base-lp/src/types.rs +++ /dev/null @@ -1,122 +0,0 @@ -use astroport::asset::AssetInfo; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Decimal, Order, Uint128}; - -/// This structure stores the main parameters for the generator vesting contract. -#[cw_serde] -pub struct Config { - /// Address that's allowed to change contract parameters - pub owner: Addr, - /// [`AssetInfo`] of the vested token - pub vesting_token: Option, - /// Address that's allowed to change vesting token - pub token_info_manager: Addr, - /// Contains extensions information of the contract - pub extensions: Extensions, -} - -/// Contains extensions information for the contract. -#[cw_serde] -pub struct Extensions { - /// Whether the historical extension is enabled for the contract. - pub historical: bool, - /// Whether the managed extension is enabled for the contract. - pub managed: bool, - /// Whether the with_managers extension is enabled for the contract. - pub with_managers: bool, -} - -/// This structure stores the accumulated vesting information for all addresses. -#[cw_serde] -#[derive(Default)] -pub struct VestingState { - /// The total amount of tokens granted to the users - pub total_granted: Uint128, - /// The total amount of tokens already claimed - pub total_released: Uint128, -} - -/// This structure stores vesting information for a specific address that is getting tokens. -#[cw_serde] -pub struct VestingAccount { - /// The address that is getting tokens - pub address: String, - /// The vesting schedules targeted at the `address` - pub schedules: Vec, -} - -/// This structure stores parameters for a batch of vesting schedules. -#[cw_serde] -pub struct VestingInfo { - /// The vesting schedules - pub schedules: Vec, - /// The total amount of vested tokens already claimed - pub released_amount: Uint128, -} - -/// This structure stores parameters for a specific vesting schedule -#[cw_serde] -pub struct VestingSchedule { - /// The start date for the vesting schedule - pub start_point: VestingSchedulePoint, - /// The end point for the vesting schedule - pub end_point: Option, -} - -/// This structure stores the parameters used to create a vesting schedule. -#[cw_serde] -pub struct VestingSchedulePoint { - /// The start time for the vesting schedule - pub time: u64, - /// The amount of tokens being vested - pub amount: Uint128, -} - -/// This structure describes a custom struct used to return vesting data about a specific vesting target. -#[cw_serde] -pub struct VestingAccountResponse { - /// The address that's vesting tokens - pub address: Addr, - /// Vesting information - pub info: VestingInfo, -} - -/// This structure describes a custom struct used to return vesting data for multiple vesting targets. -#[cw_serde] -pub struct VestingAccountsResponse { - /// A list of accounts that are vesting tokens - pub vesting_accounts: Vec, -} - -/// Config for xyk->CL liquidity migration. -#[cw_serde] -pub struct XykToClMigrationConfig { - /// The maximum allowed slippage tolerance for xyk to CL liquidity migration calls. - pub max_slippage: Decimal, - pub ntrn_denom: String, - pub xyk_pair: Addr, - pub paired_denom: String, - pub cl_pair: Addr, - pub new_lp_token: Addr, - pub pcl_vesting: Addr, -} - -/// This enum describes the types of sorting that can be applied to some piece of data -#[cw_serde] -pub enum OrderBy { - Asc, - Desc, -} - -// We suppress this clippy warning because Order in cosmwasm doesn't implement Debug and -// PartialEq for usage in QueryMsg. We need to use our own OrderBy and convert the result to cosmwasm's Order -#[allow(clippy::from_over_into)] -impl Into for OrderBy { - fn into(self) -> Order { - if self == OrderBy::Asc { - Order::Ascending - } else { - Order::Descending - } - } -} diff --git a/packages/vesting-base-pcl/Cargo.toml b/packages/vesting-base-pcl/Cargo.toml deleted file mode 100644 index 15a0f6ce..00000000 --- a/packages/vesting-base-pcl/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "vesting-base-pcl" -version = "1.1.0" -authors = ["Astroport"] -edition = "2021" - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -backtraces = ["cosmwasm-std/backtraces"] -# use library feature to disable all init/handle/query exports -library = [] - -[dependencies] -cw20 = { workspace = true } -cosmwasm-std = { workspace = true } -cw-storage-plus = { workspace = true } -astroport = { workspace = true } -thiserror = { workspace = true } -# we keep it at 0.15 instead of latest version just for vesting investors contract -cw-utils = "0.15" -cosmwasm-schema = { workspace = true } diff --git a/packages/vesting-base-pcl/NOTICE b/packages/vesting-base-pcl/NOTICE deleted file mode 100644 index 84b1c210..00000000 --- a/packages/vesting-base-pcl/NOTICE +++ /dev/null @@ -1,14 +0,0 @@ -CW20-Base: A reference implementation for fungible token on CosmWasm -Copyright (C) 2020 Confio OÜ - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/packages/vesting-base-pcl/README.md b/packages/vesting-base-pcl/README.md deleted file mode 100644 index 57accc63..00000000 --- a/packages/vesting-base-pcl/README.md +++ /dev/null @@ -1,251 +0,0 @@ -# Neutron Vesting Base - -This library contains basis for configuration and initialisation of vesting contracts. It also contains data models and handlers for interaction with vesting contracts. - -## Usage - -1. To use the library for initialisation of a simple vesting contract just build a default vesting base in its instantiate message: -```rust -use vesting_base::builder::VestingBaseBuilder; - -pub fn instantiate( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - ... - VestingBaseBuilder::default().build(deps, msg.owner, msg.vesting_token)?; - ... -``` - -Read about more advanced building in the [Extensions](#extensions) section. - -2. Simply pass the execute and query requests to the vesting base's execute and query handlers: -```rust -use vesting_base::handlers::{execute as base_execute, query as base_query}; - -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - base_execute(deps, env, info, msg) -} - -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - base_query(deps, env, msg) -} -``` - -### Messages - -The default version exposes the following messages: - -#### ExecuteMsg - -```rust -/// This structure describes the execute messages available in a vesting contract. -#[cw_serde] -pub enum ExecuteMsg { - /// Claim claims vested tokens and sends them to a recipient - Claim { - /// The address that receives the vested tokens - recipient: Option, - /// The amount of tokens to claim - amount: Option, - }, - /// Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template - Receive(Cw20ReceiveMsg), - /// RegisterVestingAccounts registers vesting targets/accounts - RegisterVestingAccounts { - vesting_accounts: Vec, - }, - /// Creates a request to change contract ownership - /// ## Executor - /// Only the current owner can execute this - ProposeNewOwner { - /// The newly proposed owner - owner: String, - /// The validity period of the offer to change the owner - expires_in: u64, - }, - /// Removes a request to change contract ownership - /// ## Executor - /// Only the current owner can execute this - DropOwnershipProposal {}, - /// Claims contract ownership - /// ## Executor - /// Only the newly proposed owner can execute this - ClaimOwnership {}, - /// Sets vesting token - /// ## Executor - /// Only the current owner or token info manager can execute this - SetVestingToken { vesting_token: AssetInfo }, - /// Contains messages associated with the managed extension for vesting contracts. - ManagedExtension { msg: ExecuteMsgManaged }, - /// Contains messages associated with the with_managers extension for vesting contracts. - WithManagersExtension { msg: ExecuteMsgWithManagers }, - /// Contains messages associated with the historical extension for vesting contracts. - HistoricalExtension { msg: ExecuteMsgHistorical }, -} -``` - -The `ManagedExtension`, `WithManagersExtension`, and `HistoricalExtension` messages are extensiom messages. Read about them in the [Extensions](#extensions) section. - -#### QueryMsg - -```rust -/// This structure describes the query messages available in a vesting contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Returns the configuration for the contract using a [`ConfigResponse`] object. - #[returns(ConfigResponse)] - Config {}, - /// Returns information about an address vesting tokens using a [`VestingAccountResponse`] object. - #[returns(VestingAccountResponse)] - VestingAccount { address: String }, - /// Returns a list of addresses that are vesting tokens using a [`VestingAccountsResponse`] object. - #[returns(VestingAccountsResponse)] - VestingAccounts { - start_after: Option, - limit: Option, - order_by: Option, - }, - /// Returns the total unvested amount of tokens for a specific address. - #[returns(Uint128)] - AvailableAmount { address: String }, - /// Timestamp returns the current timestamp - #[returns(u64)] - Timestamp {}, - /// VestingState returns the current vesting state. - #[returns(VestingState)] - VestingState {}, - /// Contains messages associated with the managed extension for vesting contracts. - #[returns(QueryMsgManaged)] - ManagedExtension { msg: QueryMsgManaged }, - /// Contains messages associated with the with_managers extension for vesting contracts. - #[returns(QueryMsgWithManagers)] - WithManagersExtension { msg: QueryMsgWithManagers }, - /// Contains messages associated with the historical extension for vesting contracts. - #[returns(QueryMsgHistorical)] - HistoricalExtension { msg: QueryMsgHistorical }, -} -``` - -The `ManagedExtension`, `WithManagersExtension`, and `HistoricalExtension` messages are extensiom messages. Read about them in the [Extensions](#extensions) section. - -## Extensions - -Created contracts can be extended with a number of features. - -### Managed - -The `managed` extension allows the owner of the vesting contract to remove registered vesting accounts and redeem the corresponding funds. - -```rust -/// This structure describes the execute messages available in a managed vesting contract. -#[cw_serde] -pub enum ExecuteMsgManaged { - /// Removes vesting targets/accounts. - /// ## Executor - /// Only the current owner can execute this - RemoveVestingAccounts { - vesting_accounts: Vec, - /// Specifies the account that will receive the funds taken from the vesting accounts. - clawback_account: String, - }, -} - -/// This structure describes the query messages available in a managed vesting contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsgManaged {} -``` - -### WithManagers - -The `with_managers` extension allows the owner of the vesting contract to add/remove vesting managers — addresses that just like the owner are capable of registering new vesting accounts. - -```rust -/// This structure describes the execute messages available in a with_managers vesting contract. -#[cw_serde] -pub enum ExecuteMsgWithManagers { - /// Adds vesting managers - /// ## Executor - /// Only the current owner can execute this - AddVestingManagers { managers: Vec }, - /// Removes vesting managers - /// ## Executor - /// Only the current owner can execute this - RemoveVestingManagers { managers: Vec }, -} - -/// This structure describes the query messages available in a with_managers vesting contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsgWithManagers { - /// Returns list of vesting managers - /// (the persons who are able to add/remove vesting schedules) - #[returns(Vec)] - VestingManagers {}, -} -``` - -### Historical - -The `historical` allows to query vesting accounts and total vesting state based on a given height. - -```rust -/// This structure describes the execute messages available in a historical vesting contract. -#[cw_serde] -pub enum ExecuteMsgHistorical {} - -/// This structure describes the query messages available in a historical vesting contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsgHistorical { - /// Returns the total unclaimed amount of tokens for a specific address at certain height. - #[returns(Uint128)] - UnclaimedAmountAtHeight { address: String, height: u64 }, - /// Returns the total unclaimed amount of tokens for all the users at certain height. - #[returns(Uint128)] - UnclaimedTotalAmountAtHeight { height: u64 }, -} -``` - -### Extensions usage - -The following example adds all three extensions to the contract, but it's allowed to combine them in any way. -```rust -use vesting_base::builder::VestingBaseBuilder; -use astroport::asset::AssetInfo; -use cosmwasm_schema::cw_serde; - -/// This structure describes the parameters used for creating a contract. -#[cw_serde] -pub struct InstantiateMsg { - /// Address allowed to change contract parameters - pub owner: String, - /// [`AssetInfo`] of the token that's being vested - pub vesting_token: AssetInfo, - /// Initial list of whitelisted vesting managers - pub vesting_managers: Vec, -} - -pub fn instantiate( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - ... - VestingBaseBuilder::default() - .historical() - .managed() - .with_managers(msg.vesting_managers) - .build(deps, msg.owner, msg.vesting_token)?; - ... -``` diff --git a/packages/vesting-base-pcl/src/builder.rs b/packages/vesting-base-pcl/src/builder.rs deleted file mode 100644 index 35550efa..00000000 --- a/packages/vesting-base-pcl/src/builder.rs +++ /dev/null @@ -1,73 +0,0 @@ -use crate::state::{CONFIG, VESTING_MANAGERS, XYK_VESTING_LP_CONTRACT}; -use crate::types::{Config, Extensions}; -use astroport::asset::AssetInfo; -use cosmwasm_std::{DepsMut, StdResult}; - -/// A builder for vesting contracts with different extensions. -#[derive(Default)] -pub struct VestingBaseBuilder { - vesting_managers: Vec, - historical: bool, - managed: bool, - with_managers: bool, -} - -impl VestingBaseBuilder { - /// Appends the `managed` extension to the created vesting contract. - pub fn managed(&mut self) -> &mut VestingBaseBuilder { - self.managed = true; - self - } - - /// Appends the `with_managers` extension to the created vesting contract. - pub fn with_managers(&mut self, managers: Vec) -> &mut VestingBaseBuilder { - self.vesting_managers.extend(managers); - self.with_managers = true; - self - } - - /// Appends the `historical` extension to the created vesting contract. - pub fn historical(&mut self) -> &mut VestingBaseBuilder { - self.historical = true; - self - } - - /// Validates the inputs and initialises the created contract state. - pub fn build( - &self, - deps: DepsMut, - owner: String, - token_info_manager: String, - xyk_vesting_lp_contract: String, - vesting_token: AssetInfo, - ) -> StdResult<()> { - let owner = deps.api.addr_validate(&owner)?; - CONFIG.save( - deps.storage, - &Config { - owner, - vesting_token: Option::from(vesting_token), - token_info_manager: deps.api.addr_validate(&token_info_manager)?, - extensions: Extensions { - historical: self.historical, - managed: self.managed, - with_managers: self.with_managers, - }, - }, - )?; - - XYK_VESTING_LP_CONTRACT.save( - deps.storage, - &deps.api.addr_validate(&xyk_vesting_lp_contract)?, - )?; - - if self.with_managers { - for m in self.vesting_managers.iter() { - let ma = deps.api.addr_validate(m)?; - VESTING_MANAGERS.save(deps.storage, ma, &())?; - } - }; - - Ok(()) - } -} diff --git a/packages/vesting-base-pcl/src/error.rs b/packages/vesting-base-pcl/src/error.rs deleted file mode 100644 index a1859ac0..00000000 --- a/packages/vesting-base-pcl/src/error.rs +++ /dev/null @@ -1,47 +0,0 @@ -use cosmwasm_std::{OverflowError, StdError}; -use cw_utils::PaymentError; -use thiserror::Error; - -/// This enum describes generator vesting contract errors -#[derive(Error, Debug, PartialEq)] -pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("{0}")] - PaymentError(#[from] PaymentError), - - #[error("Unauthorized")] - Unauthorized {}, - - #[error("Amount is not available!")] - AmountIsNotAvailable {}, - - #[error("Vesting schedule error on addr: {0}. Should satisfy: (start < end and at_start < total) or (start = end and at_start = total)")] - VestingScheduleError(String), - - #[error("Vesting schedule amount error. The total amount should be equal to the CW20 receive amount.")] - VestingScheduleAmountError {}, - - #[error("Contract can't be migrated!")] - MigrationError {}, - - #[error("Vesting token is not set!")] - VestingTokenIsNotSet {}, - - #[error("Vesting token is already set!")] - VestingTokenAlreadySet {}, -} - -impl From for ContractError { - fn from(o: OverflowError) -> Self { - StdError::from(o).into() - } -} - -pub fn ext_unsupported_err(extension: impl Into + std::fmt::Display) -> StdError { - StdError::generic_err(format!( - "Extension is not enabled for the contract: {}.", - extension - )) -} diff --git a/packages/vesting-base-pcl/src/ext_historical.rs b/packages/vesting-base-pcl/src/ext_historical.rs deleted file mode 100644 index d494adf6..00000000 --- a/packages/vesting-base-pcl/src/ext_historical.rs +++ /dev/null @@ -1,101 +0,0 @@ -use crate::error::{ext_unsupported_err, ContractError}; -use crate::msg::{ExecuteMsgHistorical, QueryMsgHistorical}; -use crate::state::{vesting_info, vesting_state, CONFIG}; -use crate::types::VestingInfo; -use cosmwasm_std::{ - to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Uint128, -}; - -/// Contains the historical extension check and routing of the message. -pub(crate) fn handle_execute_historical_msg( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - _msg: ExecuteMsgHistorical, -) -> Result { - let config = CONFIG.load(deps.storage)?; - if !config.extensions.historical { - return Err(ext_unsupported_err("historical").into()); - } - - // empty handler kept for uniformity with other extensions - unimplemented!() -} - -/// Contains the historical extension check and routing of the message. -pub(crate) fn handle_query_historical_msg( - deps: Deps, - _env: Env, - msg: QueryMsgHistorical, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - if !config.extensions.historical { - return Err(ext_unsupported_err("historical")); - } - - match msg { - QueryMsgHistorical::UnclaimedAmountAtHeight { address, height } => { - to_binary(&query_unclaimed_amount_at_height(deps, address, height)?) - } - QueryMsgHistorical::UnclaimedTotalAmountAtHeight { height } => { - to_binary(&query_total_unclaimed_amount_at_height(deps, height)?) - } - } -} - -/// Returns the available amount of distributed and yet to be claimed tokens for a specific vesting recipient at certain height. -/// -/// * **address** vesting recipient for which to return the available amount of tokens to claim. -/// -/// * **height** the height we querying unclaimed amount for -fn query_unclaimed_amount_at_height( - deps: Deps, - address: String, - height: u64, -) -> StdResult { - let address = deps.api.addr_validate(&address)?; - - let config = CONFIG.load(deps.storage)?; - let maybe_info = vesting_info(config.extensions.historical).may_load_at_height( - deps.storage, - address, - height, - )?; - match &maybe_info { - Some(info) => compute_unclaimed_amount(info), - None => Ok(Uint128::zero()), - } -} - -/// Returns the available amount of distributed and yet to be claimed tokens for all the recipients at certain height. -/// -/// * **height** the height we querying unclaimed amount for -fn query_total_unclaimed_amount_at_height(deps: Deps, height: u64) -> StdResult { - let config = CONFIG.load(deps.storage)?; - let maybe_state = - vesting_state(config.extensions.historical).may_load_at_height(deps.storage, height)?; - match &maybe_state { - Some(info) => Ok(info.total_granted.checked_sub(info.total_released)?), - None => Ok(Uint128::zero()), - } -} - -/// Computes the amount of distributed and yet unclaimed tokens for a specific vesting recipient at certain height. -/// Returns the computed amount if the operation is successful. -/// -/// * **vesting_info** vesting schedules for which to compute the amount of tokens -/// that are vested and can be claimed by the recipient. -fn compute_unclaimed_amount(vesting_info: &VestingInfo) -> StdResult { - let mut available_amount: Uint128 = Uint128::zero(); - for sch in &vesting_info.schedules { - if let Some(end_point) = &sch.end_point { - available_amount = available_amount.checked_add(end_point.amount)?; - } else { - available_amount = available_amount.checked_add(sch.start_point.amount)?; - } - } - - available_amount - .checked_sub(vesting_info.released_amount) - .map_err(StdError::from) -} diff --git a/packages/vesting-base-pcl/src/ext_managed.rs b/packages/vesting-base-pcl/src/ext_managed.rs deleted file mode 100644 index 94b68d20..00000000 --- a/packages/vesting-base-pcl/src/ext_managed.rs +++ /dev/null @@ -1,121 +0,0 @@ -use crate::error::{ext_unsupported_err, ContractError}; -use crate::handlers::get_vesting_token; -use crate::msg::{ExecuteMsgManaged, QueryMsgManaged}; -use crate::state::{vesting_info, vesting_state, CONFIG}; -use astroport::asset::AssetInfoExt; -use cosmwasm_std::{ - attr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, SubMsg, Uint128, -}; - -/// Contains the managed extension check and routing of the message. -pub(crate) fn handle_execute_managed_msg( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsgManaged, -) -> Result { - let config = CONFIG.load(deps.storage)?; - if !config.extensions.managed { - return Err(ext_unsupported_err("managed").into()); - } - - match msg { - ExecuteMsgManaged::RemoveVestingAccounts { - vesting_accounts, - clawback_account, - } => remove_vesting_accounts(deps, env, info, vesting_accounts, clawback_account), - } -} - -/// Contains the managed extension check and routing of the message. -pub(crate) fn handle_query_managed_msg( - deps: Deps, - _env: Env, - _msg: QueryMsgManaged, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - if !config.extensions.managed { - return Err(ext_unsupported_err("managed")); - } - - // empty handler kept for uniformity with other extensions - unimplemented!() -} - -#[allow(clippy::too_many_arguments)] -fn remove_vesting_accounts( - deps: DepsMut, - env: Env, - info: MessageInfo, - vesting_accounts: Vec, - clawback_account: String, -) -> Result { - let config = CONFIG.load(deps.storage)?; - if info.sender != config.owner { - return Err(ContractError::Unauthorized {}); - } - let vesting_token = get_vesting_token(&config)?; - - let mut response = Response::new(); - - let clawback_address = deps.api.addr_validate(&clawback_account)?; - - // For each vesting account, calculate the amount of tokens to claw back (unclaimed + still - // vesting), transfer the required amount to the owner, remove the vesting information - // from the storage, and decrease the total granted metric. - for vesting_account in vesting_accounts { - let account_address = deps.api.addr_validate(&vesting_account)?; - - let config = CONFIG.load(deps.storage)?; - let vesting_info = vesting_info(config.extensions.historical); - if let Some(account_info) = vesting_info.may_load(deps.storage, account_address.clone())? { - let mut total_granted_for_user = Uint128::zero(); - for sch in account_info.schedules { - if let Some(end_point) = sch.end_point { - total_granted_for_user = - total_granted_for_user.checked_add(end_point.amount)?; - } else { - total_granted_for_user = - total_granted_for_user.checked_add(sch.start_point.amount)?; - } - } - - let amount_to_claw_back = - total_granted_for_user.checked_sub(account_info.released_amount)?; - - let transfer_msg = vesting_token - .with_balance(amount_to_claw_back) - .into_msg(&deps.querier, clawback_address.clone())?; - response = response.add_submessage(SubMsg::new(transfer_msg)); - - vesting_state(config.extensions.historical).update::<_, ContractError>( - deps.storage, - env.block.height, - |s| { - // Here we choose the "forget about everything" strategy. E.g., if we granted a user - // 300 tokens, and they claimed 150 tokens, the vesting state is - // { total_granted: 300, total_released: 150 }. - // If after that we remove the user's vesting account, we set the vesting state to - // { total_granted: 0, total_released: 0 }. - // - // If we decided to set it to { total_granted: 150, total_released: 150 }., the - // .total_released value of the vesting state would not be equal to the sum of the - // .released_amount values of all registered accounts. - let mut state = s.ok_or(ContractError::AmountIsNotAvailable {})?; - state.total_granted = - state.total_granted.checked_sub(total_granted_for_user)?; - state.total_released = state - .total_released - .checked_sub(account_info.released_amount)?; - Ok(state) - }, - )?; - vesting_info.remove(deps.storage, account_address, env.block.height)?; - } - } - - Ok(response.add_attributes(vec![ - attr("action", "remove_vesting_accounts"), - attr("sender", &info.sender), - ])) -} diff --git a/packages/vesting-base-pcl/src/ext_with_managers.rs b/packages/vesting-base-pcl/src/ext_with_managers.rs deleted file mode 100644 index 56573188..00000000 --- a/packages/vesting-base-pcl/src/ext_with_managers.rs +++ /dev/null @@ -1,105 +0,0 @@ -use crate::error::{ext_unsupported_err, ContractError}; -use crate::msg::{ExecuteMsgWithManagers, QueryMsgWithManagers}; -use crate::state::{CONFIG, VESTING_MANAGERS}; -use cosmwasm_std::{ - attr, to_binary, Addr, Attribute, Binary, Deps, DepsMut, Env, MessageInfo, Order, Response, - StdError, StdResult, -}; - -/// Contains the with_managers extension check and routing of the message. -pub(crate) fn handle_execute_with_managers_msg( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsgWithManagers, -) -> Result { - let config = CONFIG.load(deps.storage)?; - if !config.extensions.with_managers { - return Err(ext_unsupported_err("with_managers").into()); - } - - match msg { - ExecuteMsgWithManagers::AddVestingManagers { managers } => { - add_vesting_managers(deps, env, info, managers) - } - ExecuteMsgWithManagers::RemoveVestingManagers { managers } => { - remove_vesting_managers(deps, env, info, managers) - } - } -} - -/// Contains the with_managers extension check and routing of the message. -pub(crate) fn handle_query_managers_msg( - deps: Deps, - _env: Env, - msg: QueryMsgWithManagers, -) -> StdResult { - let config = CONFIG.load(deps.storage)?; - if !config.extensions.with_managers { - return Err(ext_unsupported_err("with_managers")); - } - - match msg { - QueryMsgWithManagers::VestingManagers {} => to_binary(&query_vesting_managers(deps)?), - } -} - -/// Adds new vesting managers, which have a permission to add/remove vesting schedule -/// -/// * **managers** list of accounts to be added to the whitelist. -fn add_vesting_managers( - deps: DepsMut, - _env: Env, - info: MessageInfo, - managers: Vec, -) -> Result { - let config = CONFIG.load(deps.storage)?; - if info.sender != config.owner { - return Err(ContractError::Unauthorized {}); - } - let mut attrs: Vec = vec![]; - for m in managers { - let ma = deps.api.addr_validate(&m)?; - if !VESTING_MANAGERS.has(deps.storage, ma.clone()) { - VESTING_MANAGERS.save(deps.storage, ma, &())?; - attrs.push(attr("vesting_manager", &m)) - } - } - Ok(Response::new() - .add_attribute("action", "add_vesting_managers") - .add_attributes(attrs)) -} - -/// Removes new vesting managers from the whitelist -/// -/// * **managers** list of accounts to be removed from the whitelist. -fn remove_vesting_managers( - deps: DepsMut, - _env: Env, - info: MessageInfo, - managers: Vec, -) -> Result { - let config = CONFIG.load(deps.storage)?; - if info.sender != config.owner { - return Err(ContractError::Unauthorized {}); - } - let mut attrs: Vec = vec![]; - for m in managers { - let ma = deps.api.addr_validate(&m)?; - if VESTING_MANAGERS.has(deps.storage, ma.clone()) { - VESTING_MANAGERS.remove(deps.storage, ma); - attrs.push(attr("vesting_manager", &m)) - } - } - Ok(Response::new() - .add_attribute("action", "remove_vesting_managers") - .add_attributes(attrs)) -} - -/// Returns a list of vesting schedules using a [`VestingAccountsResponse`] object. -fn query_vesting_managers(deps: Deps) -> StdResult> { - let managers = VESTING_MANAGERS - .keys(deps.storage, None, None, Order::Ascending) - .collect::, StdError>>()?; - Ok(managers) -} diff --git a/packages/vesting-base-pcl/src/handlers.rs b/packages/vesting-base-pcl/src/handlers.rs deleted file mode 100644 index d85a5c75..00000000 --- a/packages/vesting-base-pcl/src/handlers.rs +++ /dev/null @@ -1,509 +0,0 @@ -use crate::error::ContractError; -use crate::ext_historical::{handle_execute_historical_msg, handle_query_historical_msg}; -use crate::ext_managed::{handle_execute_managed_msg, handle_query_managed_msg}; -use crate::ext_with_managers::{handle_execute_with_managers_msg, handle_query_managers_msg}; -use crate::msg::{Cw20HookMsg, ExecuteMsg, MigrateMsg, QueryMsg}; -use crate::state::{read_vesting_infos, vesting_info, vesting_state, XYK_VESTING_LP_CONTRACT}; -use crate::state::{CONFIG, OWNERSHIP_PROPOSAL, VESTING_MANAGERS}; -use crate::types::{ - Config, OrderBy, VestingAccount, VestingAccountResponse, VestingAccountsResponse, VestingInfo, - VestingSchedule, VestingState, -}; -use astroport::asset::{addr_opt_validate, token_asset_info, AssetInfo, AssetInfoExt}; -use astroport::common::{claim_ownership, drop_ownership_proposal, propose_new_owner}; -use cosmwasm_std::{ - attr, from_binary, to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, - StdError, StdResult, Storage, SubMsg, Uint128, -}; -use cw20::Cw20ReceiveMsg; -use cw_utils::must_pay; - -/// Exposes execute functions available in the contract. -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - match msg { - ExecuteMsg::Claim { recipient, amount } => claim(deps, env, info, recipient, amount), - ExecuteMsg::Receive(msg) => receive_cw20(deps, env, info, msg), - ExecuteMsg::RegisterVestingAccounts { vesting_accounts } => { - let config = CONFIG.load(deps.storage)?; - let vesting_token = get_vesting_token(&config)?; - - match &vesting_token { - AssetInfo::NativeToken { denom } - if is_sender_whitelisted(deps.storage, &config, &info.sender) => - { - let amount = must_pay(&info, denom)?; - register_vesting_accounts(deps, vesting_accounts, amount, env.block.height) - } - _ => Err(ContractError::Unauthorized {}), - } - } - ExecuteMsg::ProposeNewOwner { owner, expires_in } => { - let config: Config = CONFIG.load(deps.storage)?; - - propose_new_owner( - deps, - info, - env, - 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) - })?; - - Ok(()) - }) - .map_err(Into::into) - } - ExecuteMsg::SetVestingToken { vesting_token } => { - set_vesting_token(deps, env, info, vesting_token) - } - ExecuteMsg::ManagedExtension { msg } => handle_execute_managed_msg(deps, env, info, msg), - ExecuteMsg::WithManagersExtension { msg } => { - handle_execute_with_managers_msg(deps, env, info, msg) - } - ExecuteMsg::HistoricalExtension { msg } => { - handle_execute_historical_msg(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.load(deps.storage)?; - let vesting_token = get_vesting_token(&config)?; - - // Permission check - if !is_sender_whitelisted( - deps.storage, - &config, - &deps.api.addr_validate(&cw20_msg.sender)?, - ) || token_asset_info(info.sender) != vesting_token - { - return Err(ContractError::Unauthorized {}); - } - - match from_binary(&cw20_msg.msg)? { - Cw20HookMsg::RegisterVestingAccounts { vesting_accounts } => { - register_vesting_accounts(deps, vesting_accounts, cw20_msg.amount, env.block.height) - } - Cw20HookMsg::MigrateXYKLiquidity { - user_address_raw, - user_vesting_info, - } => handle_migrate_xyk_liquidity(deps, env, user_address_raw, user_vesting_info), - } -} - -/// Create new vesting schedules. -/// -/// * **vesting_accounts** list of accounts and associated vesting schedules to create. -/// -/// * **cw20_amount** sets the amount that confirms the total amount of all accounts to register. -fn register_vesting_accounts( - deps: DepsMut, - vesting_accounts: Vec, - amount: Uint128, - height: u64, -) -> Result { - let response = Response::new(); - let config = CONFIG.load(deps.storage)?; - let mut to_deposit = Uint128::zero(); - - for mut vesting_account in vesting_accounts { - let mut released_amount = Uint128::zero(); - let account_address = deps.api.addr_validate(&vesting_account.address)?; - - assert_vesting_schedules(&account_address, &vesting_account.schedules)?; - - for sch in &vesting_account.schedules { - let amount = if let Some(end_point) = &sch.end_point { - end_point.amount - } else { - sch.start_point.amount - }; - to_deposit = to_deposit.checked_add(amount)?; - } - - let vesting_info = vesting_info(config.extensions.historical); - if let Some(mut old_info) = vesting_info.may_load(deps.storage, account_address.clone())? { - released_amount = old_info.released_amount; - vesting_account.schedules.append(&mut old_info.schedules); - } - - vesting_info.save( - deps.storage, - account_address, - &VestingInfo { - schedules: vesting_account.schedules, - released_amount, - }, - height, - )?; - } - - if to_deposit != amount { - return Err(ContractError::VestingScheduleAmountError {}); - } - - vesting_state(config.extensions.historical).update::<_, ContractError>( - deps.storage, - height, - |s| { - let mut state = s.unwrap_or_default(); - state.total_granted = state.total_granted.checked_add(to_deposit)?; - Ok(state) - }, - )?; - - Ok(response.add_attributes({ - vec![ - attr("action", "register_vesting_accounts"), - attr("deposited", to_deposit), - ] - })) -} - -/// Claims vested tokens and transfers them to the vesting recipient. -/// -/// * **recipient** vesting recipient for which to claim tokens. -/// -/// * **amount** amount of vested tokens to claim. -fn claim( - deps: DepsMut, - env: Env, - info: MessageInfo, - recipient: Option, - amount: Option, -) -> Result { - let config = CONFIG.load(deps.storage)?; - let vesting_token = get_vesting_token(&config)?; - let vesting_info = vesting_info(config.extensions.historical); - let mut sender_vesting_info = vesting_info.load(deps.storage, info.sender.clone())?; - - let available_amount = - compute_available_amount(env.block.time.seconds(), &sender_vesting_info)?; - - let claim_amount = if let Some(a) = amount { - if a > available_amount { - return Err(ContractError::AmountIsNotAvailable {}); - }; - a - } else { - available_amount - }; - - let mut response = Response::new(); - - if !claim_amount.is_zero() { - let transfer_msg = vesting_token.with_balance(claim_amount).into_msg( - &deps.querier, - recipient.unwrap_or_else(|| info.sender.to_string()), - )?; - response = response.add_submessage(SubMsg::new(transfer_msg)); - - sender_vesting_info.released_amount = sender_vesting_info - .released_amount - .checked_add(claim_amount)?; - vesting_info.save( - deps.storage, - info.sender.clone(), - &sender_vesting_info, - env.block.height, - )?; - vesting_state(config.extensions.historical).update::<_, ContractError>( - deps.storage, - env.block.height, - |s| { - let mut state = s.ok_or(ContractError::AmountIsNotAvailable {})?; - state.total_released = state.total_released.checked_add(claim_amount)?; - Ok(state) - }, - )?; - }; - - Ok(response.add_attributes(vec![ - attr("action", "claim"), - attr("address", &info.sender), - attr("available_amount", available_amount), - attr("claimed_amount", claim_amount), - ])) -} - -fn handle_migrate_xyk_liquidity( - deps: DepsMut, - env: Env, - user_addr_raw: Addr, - user_vesting_info: VestingInfo, -) -> Result { - let height = env.block.height; - let config = CONFIG.load(deps.storage)?; - - let account_address = user_addr_raw; - - assert_vesting_schedules(&account_address, &user_vesting_info.schedules)?; - - let mut to_deposit = Uint128::zero(); - for sch in &user_vesting_info.schedules { - let amount = if let Some(end_point) = &sch.end_point { - end_point.amount - } else { - sch.start_point.amount - }; - to_deposit = to_deposit.checked_add(amount)?; - } - - let vesting_info = vesting_info(config.extensions.historical); - - vesting_info.save(deps.storage, account_address, &user_vesting_info, height)?; - - let mut to_deposit = Uint128::zero(); - for sch in &user_vesting_info.schedules { - let amount = if let Some(end_point) = &sch.end_point { - end_point.amount - } else { - sch.start_point.amount - }; - to_deposit = to_deposit.checked_add(amount)?; - } - - vesting_state(config.extensions.historical).update::<_, ContractError>( - deps.storage, - height, - |s| { - let mut state = s.unwrap_or_default(); - state.total_granted = state.total_granted.checked_add(to_deposit)?; - Ok(state) - }, - )?; - - Ok(Response::default()) -} - -pub(crate) fn set_vesting_token( - deps: DepsMut, - _env: Env, - info: MessageInfo, - token: AssetInfo, -) -> Result { - let mut config = CONFIG.load(deps.storage)?; - if info.sender != config.owner && info.sender != config.token_info_manager { - return Err(ContractError::Unauthorized {}); - } - if config.vesting_token.is_some() { - return Err(ContractError::VestingTokenAlreadySet {}); - } - - token.check(deps.api)?; - config.vesting_token = Some(token.clone()); - CONFIG.save(deps.storage, &config)?; - - let response = Response::new(); - Ok(response.add_attributes(vec![ - attr("action", "set_vesting_token"), - attr("vesting_token", token.to_string()), - ])) -} - -pub(crate) fn get_vesting_token(config: &Config) -> Result { - config - .vesting_token - .clone() - .ok_or(ContractError::VestingTokenIsNotSet {}) -} - -/// Exposes all the queries available in the contract. -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Config {} => Ok(to_binary(&query_config(deps)?)?), - QueryMsg::VestingAccount { address } => { - Ok(to_binary(&query_vesting_account(deps, address)?)?) - } - QueryMsg::VestingAccounts { - start_after, - limit, - order_by, - } => Ok(to_binary(&query_vesting_accounts( - deps, - start_after, - limit, - order_by, - )?)?), - QueryMsg::AvailableAmount { address } => Ok(to_binary(&query_vesting_available_amount( - deps, env, address, - )?)?), - QueryMsg::VestingState {} => Ok(to_binary(&query_vesting_state(deps)?)?), - QueryMsg::Timestamp {} => Ok(to_binary(&query_timestamp(env)?)?), - QueryMsg::ManagedExtension { msg } => handle_query_managed_msg(deps, env, msg), - QueryMsg::WithManagersExtension { msg } => handle_query_managers_msg(deps, env, msg), - QueryMsg::HistoricalExtension { msg } => handle_query_historical_msg(deps, env, msg), - } -} - -/// Returns the vesting contract configuration using a [`Config`] object. -fn query_config(deps: Deps) -> StdResult { - let config = CONFIG.load(deps.storage)?; - Ok(config) -} - -/// Returns the accumulated vesting information for all addresses using a [`VestingState`] object. -fn query_vesting_state(deps: Deps) -> StdResult { - let config = CONFIG.load(deps.storage)?; - let state = vesting_state(config.extensions.historical).load(deps.storage)?; - - Ok(state) -} - -/// Return the current block timestamp (in seconds) -/// * **env** is an object of type [`Env`]. -fn query_timestamp(env: Env) -> StdResult { - Ok(env.block.time.seconds()) -} - -/// Returns the vesting data for a specific vesting recipient using a [`VestingAccountResponse`] object. -/// -/// * **address** vesting recipient for which to return vesting data. -fn query_vesting_account(deps: Deps, address: String) -> StdResult { - let address = deps.api.addr_validate(&address)?; - let config = CONFIG.load(deps.storage)?; - let info = vesting_info(config.extensions.historical).load(deps.storage, address.clone())?; - - Ok(VestingAccountResponse { address, info }) -} - -/// Returns a list of vesting schedules using a [`VestingAccountsResponse`] object. -/// -/// * **start_after** index from which to start reading vesting schedules. -/// -/// * **limit** amount of vesting schedules to return. -/// -/// * **order_by** whether results should be returned in an ascending or descending order. -fn query_vesting_accounts( - deps: Deps, - start_after: Option, - limit: Option, - order_by: Option, -) -> StdResult { - let start_after = addr_opt_validate(deps.api, &start_after)?; - - let vesting_infos = read_vesting_infos(deps, start_after, limit, order_by)?; - - let vesting_accounts: Vec<_> = vesting_infos - .into_iter() - .map(|(address, info)| VestingAccountResponse { address, info }) - .collect(); - - Ok(VestingAccountsResponse { vesting_accounts }) -} - -/// Returns the available amount of vested and yet to be claimed tokens for a specific vesting recipient. -/// -/// * **address** vesting recipient for which to return the available amount of tokens to claim. -fn query_vesting_available_amount(deps: Deps, env: Env, address: String) -> StdResult { - let address = deps.api.addr_validate(&address)?; - - let config = CONFIG.load(deps.storage)?; - let info = vesting_info(config.extensions.historical).load(deps.storage, address)?; - let available_amount = compute_available_amount(env.block.time.seconds(), &info)?; - Ok(available_amount) -} - -/// Manages contract migration. -pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { - Ok(Response::default()) -} - -fn is_sender_whitelisted(store: &mut dyn Storage, config: &Config, sender: &Addr) -> bool { - if *sender == config.owner { - return true; - } - let xyk_vesting_lp_contract = XYK_VESTING_LP_CONTRACT.load(store).unwrap(); - if *sender == xyk_vesting_lp_contract { - return true; - } - if VESTING_MANAGERS.has(store, sender.clone()) { - return true; - } - - false -} - -/// Asserts the validity of a list of vesting schedules. -/// -/// * **addr** receiver of the vested tokens. -/// -/// * **vesting_schedules** vesting schedules to validate. -fn assert_vesting_schedules( - addr: &Addr, - vesting_schedules: &[VestingSchedule], -) -> Result<(), ContractError> { - for sch in vesting_schedules { - if let Some(end_point) = &sch.end_point { - if !(sch.start_point.time < end_point.time && sch.start_point.amount < end_point.amount) - { - return Err(ContractError::VestingScheduleError(addr.to_string())); - } - } - } - - Ok(()) -} - -/// Computes the amount of vested and yet unclaimed tokens for a specific vesting recipient. -/// Returns the computed amount if the operation is successful. -/// -/// * **current_time** timestamp from which to start querying for vesting schedules. -/// Schedules that started later than current_time will be omitted. -/// -/// * **vesting_info** vesting schedules for which to compute the amount of tokens -/// that are vested and can be claimed by the recipient. -fn compute_available_amount(current_time: u64, vesting_info: &VestingInfo) -> StdResult { - let mut available_amount: Uint128 = Uint128::zero(); - for sch in &vesting_info.schedules { - if sch.start_point.time > current_time { - continue; - } - - available_amount = available_amount.checked_add(sch.start_point.amount)?; - - if let Some(end_point) = &sch.end_point { - let passed_time = current_time.min(end_point.time) - sch.start_point.time; - let time_period = end_point.time - sch.start_point.time; - if passed_time != 0 && time_period != 0 { - let release_amount = Uint128::from(passed_time).multiply_ratio( - end_point.amount.checked_sub(sch.start_point.amount)?, - time_period, - ); - available_amount = available_amount.checked_add(release_amount)?; - } - } - } - - available_amount - .checked_sub(vesting_info.released_amount) - .map_err(StdError::from) -} diff --git a/packages/vesting-base-pcl/src/lib.rs b/packages/vesting-base-pcl/src/lib.rs deleted file mode 100644 index 594d5e36..00000000 --- a/packages/vesting-base-pcl/src/lib.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub mod builder; -pub mod error; -pub mod handlers; -pub mod msg; -pub mod state; -pub mod types; - -pub(crate) mod ext_historical; -pub(crate) mod ext_managed; -pub(crate) mod ext_with_managers; diff --git a/packages/vesting-base-pcl/src/msg.rs b/packages/vesting-base-pcl/src/msg.rs deleted file mode 100644 index c3607001..00000000 --- a/packages/vesting-base-pcl/src/msg.rs +++ /dev/null @@ -1,167 +0,0 @@ -use crate::types::{ - Config, OrderBy, VestingAccount, VestingAccountResponse, VestingAccountsResponse, VestingInfo, - VestingState, -}; -use astroport::asset::AssetInfo; -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Binary, Uint128}; -use cw20::Cw20ReceiveMsg; - -/// This structure describes the execute messages available in a vesting contract. -#[cw_serde] -pub enum ExecuteMsg { - /// Claim claims vested tokens and sends them to a recipient - Claim { - /// The address that receives the vested tokens - recipient: Option, - /// The amount of tokens to claim - amount: Option, - }, - /// Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template - Receive(Cw20ReceiveMsg), - /// RegisterVestingAccounts registers vesting targets/accounts - RegisterVestingAccounts { - vesting_accounts: Vec, - }, - /// Creates a request to change contract ownership - /// ## Executor - /// Only the current owner can execute this - ProposeNewOwner { - /// The newly proposed owner - owner: String, - /// The validity period of the offer to change the owner - expires_in: u64, - }, - /// Removes a request to change contract ownership - /// ## Executor - /// Only the current owner can execute this - DropOwnershipProposal {}, - /// Claims contract ownership - /// ## Executor - /// Only the newly proposed owner can execute this - ClaimOwnership {}, - /// Sets vesting token - /// ## Executor - /// Only the current owner or token info manager can execute this - SetVestingToken { vesting_token: AssetInfo }, - /// Contains messages associated with the managed extension for vesting contracts. - ManagedExtension { msg: ExecuteMsgManaged }, - /// Contains messages associated with the with_managers extension for vesting contracts. - WithManagersExtension { msg: ExecuteMsgWithManagers }, - /// Contains messages associated with the historical extension for vesting contracts. - HistoricalExtension { msg: ExecuteMsgHistorical }, -} - -/// This structure describes the execute messages available in a managed vesting contract. -#[cw_serde] -pub enum ExecuteMsgManaged { - /// Removes vesting targets/accounts. - /// ## Executor - /// Only the current owner can execute this - RemoveVestingAccounts { - vesting_accounts: Vec, - /// Specifies the account that will receive the funds taken from the vesting accounts. - clawback_account: String, - }, -} - -/// This structure describes the execute messages available in a with_managers vesting contract. -#[cw_serde] -pub enum ExecuteMsgWithManagers { - /// Adds vesting managers - /// ## Executor - /// Only the current owner can execute this - AddVestingManagers { managers: Vec }, - /// Removes vesting managers - /// ## Executor - /// Only the current owner can execute this - RemoveVestingManagers { managers: Vec }, -} - -/// This structure describes the execute messages available in a historical vesting contract. -#[cw_serde] -pub enum ExecuteMsgHistorical {} - -/// This structure describes the query messages available in a vesting contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - /// Returns the configuration for the contract using a [`ConfigResponse`] object. - #[returns(Config)] - Config {}, - /// Returns information about an address vesting tokens using a [`VestingAccountResponse`] object. - #[returns(VestingAccountResponse)] - VestingAccount { address: String }, - /// Returns a list of addresses that are vesting tokens using a [`VestingAccountsResponse`] object. - #[returns(VestingAccountsResponse)] - VestingAccounts { - start_after: Option, - limit: Option, - order_by: Option, - }, - /// Returns the total unvested amount of tokens for a specific address. - #[returns(Uint128)] - AvailableAmount { address: String }, - /// Timestamp returns the current timestamp - #[returns(u64)] - Timestamp {}, - /// VestingState returns the current vesting state. - #[returns(VestingState)] - VestingState {}, - /// Contains messages associated with the managed extension for vesting contracts. - #[returns(Binary)] - ManagedExtension { msg: QueryMsgManaged }, - /// Contains messages associated with the with_managers extension for vesting contracts. - #[returns(QueryMsgWithManagers)] - WithManagersExtension { msg: QueryMsgWithManagers }, - /// Contains messages associated with the historical extension for vesting contracts. - #[returns(QueryMsgHistorical)] - HistoricalExtension { msg: QueryMsgHistorical }, -} - -/// This structure describes the query messages available in a managed vesting contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsgManaged {} - -/// This structure describes the query messages available in a with_managers vesting contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsgWithManagers { - /// Returns list of vesting managers - /// (the persons who are able to add/remove vesting schedules) - #[returns(Vec)] - VestingManagers {}, -} - -/// This structure describes the query messages available in a historical vesting contract. -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsgHistorical { - /// Returns the total unclaimed amount of tokens for a specific address at certain height. - #[returns(Uint128)] - UnclaimedAmountAtHeight { address: String, height: u64 }, - /// Returns the total unclaimed amount of tokens for all the users at certain height. - #[returns(Uint128)] - UnclaimedTotalAmountAtHeight { height: u64 }, -} - -/// This structure describes a migration message. -/// We currently take no arguments for migrations. -#[cw_serde] -pub struct MigrateMsg {} - -/// This structure describes a CW20 hook message. -#[cw_serde] -pub enum Cw20HookMsg { - /// RegisterVestingAccounts registers vesting targets/accounts - RegisterVestingAccounts { - vesting_accounts: Vec, - }, - #[serde(rename = "migrate_xyk_liquidity")] - MigrateXYKLiquidity { - /// The address of the user which owns the vested tokens. - user_address_raw: Addr, - user_vesting_info: VestingInfo, - }, -} diff --git a/packages/vesting-base-pcl/src/state.rs b/packages/vesting-base-pcl/src/state.rs deleted file mode 100644 index 0a7bc89f..00000000 --- a/packages/vesting-base-pcl/src/state.rs +++ /dev/null @@ -1,160 +0,0 @@ -use crate::types::{Config, OrderBy, VestingInfo, VestingState}; -use astroport::common::OwnershipProposal; -use cosmwasm_std::{Addr, Deps, StdResult}; -use cw_storage_plus::{Bound, Item, Map, SnapshotItem, SnapshotMap, Strategy}; - -pub(crate) const CONFIG: Item = Item::new("config"); -pub(crate) const OWNERSHIP_PROPOSAL: Item = Item::new("ownership_proposal"); -pub(crate) const XYK_VESTING_LP_CONTRACT: Item = Item::new("xyk_vesting_lp_contract"); -pub(crate) const VESTING_MANAGERS: Map = Map::new("vesting_managers"); -pub(crate) const VESTING_STATE: SnapshotItem = SnapshotItem::new( - "vesting_state", - "vesting_state__checkpoints", - "vesting_state__changelog", - Strategy::Never, -); -pub(crate) const VESTING_INFO: SnapshotMap = SnapshotMap::new( - "vesting_info", - "vesting_info__checkpoints", - "vesting_info__changelog", - Strategy::Never, -); -pub(crate) const VESTING_STATE_HISTORICAL: SnapshotItem = SnapshotItem::new( - "vesting_state", - "vesting_state__checkpoints", - "vesting_state__changelog", - Strategy::EveryBlock, -); -pub(crate) const VESTING_INFO_HISTORICAL: SnapshotMap = SnapshotMap::new( - "vesting_info", - "vesting_info__checkpoints", - "vesting_info__changelog", - Strategy::EveryBlock, -); - -pub(crate) fn vesting_state(historical: bool) -> SnapshotItem<'static, VestingState> { - if historical { - return VESTING_STATE_HISTORICAL; - } - VESTING_STATE -} - -pub(crate) fn vesting_info(historical: bool) -> SnapshotMap<'static, Addr, VestingInfo> { - if historical { - return VESTING_INFO_HISTORICAL; - } - VESTING_INFO -} - -const MAX_LIMIT: u32 = 30; -const DEFAULT_LIMIT: u32 = 10; - -/// Returns an empty vector if it does not find data, otherwise returns a vector that -/// contains objects of type [`VESTING_INFO`]. -/// ## Params -/// -/// * **start_after** index from which to start reading vesting schedules. -/// -/// * **limit** amount of vesting schedules to read. -/// -/// * **order_by** whether results should be returned in an ascending or descending order. -pub(crate) fn read_vesting_infos( - deps: Deps, - start_after: Option, - limit: Option, - order_by: Option, -) -> StdResult> { - let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let start_after = start_after.map(Bound::exclusive); - - let (start, end) = match &order_by { - Some(OrderBy::Asc) => (start_after, None), - _ => (None, start_after), - }; - - let info: Vec<(Addr, VestingInfo)> = VESTING_INFO - .range( - deps.storage, - start, - end, - order_by.unwrap_or(OrderBy::Desc).into(), - ) - .take(limit) - .filter_map(|v| v.ok()) - .collect(); - - Ok(info) -} - -#[cfg(test)] -mod testing { - use super::*; - - #[test] - fn read_vesting_infos_as_expected() { - use cosmwasm_std::{testing::mock_dependencies, Uint128}; - let mut deps = mock_dependencies(); - let historical = false; - - let vi_mock = VestingInfo { - released_amount: Uint128::zero(), - schedules: vec![], - }; - - for i in 1..5 { - let key = Addr::unchecked(format! {"address{}", i}); - - vesting_info(historical) - .save(&mut deps.storage, key, &vi_mock, 1) - .unwrap(); - } - - let res = read_vesting_infos( - deps.as_ref(), - Some(Addr::unchecked("address2")), - None, - Some(OrderBy::Asc), - ) - .unwrap(); - assert_eq!( - res, - vec![ - (Addr::unchecked("address3"), vi_mock.clone()), - (Addr::unchecked("address4"), vi_mock.clone()), - ] - ); - - let res = read_vesting_infos( - deps.as_ref(), - Some(Addr::unchecked("address2")), - Some(1), - Some(OrderBy::Asc), - ) - .unwrap(); - assert_eq!(res, vec![(Addr::unchecked("address3"), vi_mock.clone())]); - - let res = read_vesting_infos( - deps.as_ref(), - Some(Addr::unchecked("address3")), - None, - Some(OrderBy::Desc), - ) - .unwrap(); - assert_eq!( - res, - vec![ - (Addr::unchecked("address2"), vi_mock.clone()), - (Addr::unchecked("address1"), vi_mock.clone()), - ] - ); - - let res = read_vesting_infos( - deps.as_ref(), - Some(Addr::unchecked("address3")), - Some(1), - Some(OrderBy::Desc), - ) - .unwrap(); - assert_eq!(res, vec![(Addr::unchecked("address2"), vi_mock.clone())]); - } -} diff --git a/packages/vesting-base-pcl/src/types.rs b/packages/vesting-base-pcl/src/types.rs deleted file mode 100644 index f6081025..00000000 --- a/packages/vesting-base-pcl/src/types.rs +++ /dev/null @@ -1,109 +0,0 @@ -use astroport::asset::AssetInfo; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Order, Uint128}; - -/// This structure stores the main parameters for the generator vesting contract. -#[cw_serde] -pub struct Config { - /// Address that's allowed to change contract parameters - pub owner: Addr, - /// [`AssetInfo`] of the vested token - pub vesting_token: Option, - /// Address that's allowed to change vesting token - pub token_info_manager: Addr, - /// Contains extensions information of the contract - pub extensions: Extensions, -} - -/// Contains extensions information for the contract. -#[cw_serde] -pub struct Extensions { - /// Whether the historical extension is enabled for the contract. - pub historical: bool, - /// Whether the managed extension is enabled for the contract. - pub managed: bool, - /// Whether the with_managers extension is enabled for the contract. - pub with_managers: bool, -} - -/// This structure stores the accumulated vesting information for all addresses. -#[cw_serde] -#[derive(Default)] -pub struct VestingState { - /// The total amount of tokens granted to the users - pub total_granted: Uint128, - /// The total amount of tokens already claimed - pub total_released: Uint128, -} - -/// This structure stores vesting information for a specific address that is getting tokens. -#[cw_serde] -pub struct VestingAccount { - /// The address that is getting tokens - pub address: String, - /// The vesting schedules targeted at the `address` - pub schedules: Vec, -} - -/// This structure stores parameters for a batch of vesting schedules. -#[cw_serde] -pub struct VestingInfo { - /// The vesting schedules - pub schedules: Vec, - /// The total amount of vested tokens already claimed - pub released_amount: Uint128, -} - -/// This structure stores parameters for a specific vesting schedule -#[cw_serde] -pub struct VestingSchedule { - /// The start date for the vesting schedule - pub start_point: VestingSchedulePoint, - /// The end point for the vesting schedule - pub end_point: Option, -} - -/// This structure stores the parameters used to create a vesting schedule. -#[cw_serde] -pub struct VestingSchedulePoint { - /// The start time for the vesting schedule - pub time: u64, - /// The amount of tokens being vested - pub amount: Uint128, -} - -/// This structure describes a custom struct used to return vesting data about a specific vesting target. -#[cw_serde] -pub struct VestingAccountResponse { - /// The address that's vesting tokens - pub address: Addr, - /// Vesting information - pub info: VestingInfo, -} - -/// This structure describes a custom struct used to return vesting data for multiple vesting targets. -#[cw_serde] -pub struct VestingAccountsResponse { - /// A list of accounts that are vesting tokens - pub vesting_accounts: Vec, -} - -/// This enum describes the types of sorting that can be applied to some piece of data -#[cw_serde] -pub enum OrderBy { - Asc, - Desc, -} - -// We suppress this clippy warning because Order in cosmwasm doesn't implement Debug and -// PartialEq for usage in QueryMsg. We need to use our own OrderBy and convert the result to cosmwasm's Order -#[allow(clippy::from_over_into)] -impl Into for OrderBy { - fn into(self) -> Order { - if self == OrderBy::Asc { - Order::Ascending - } else { - Order::Descending - } - } -} diff --git a/packages/vesting-base/src/handlers.rs b/packages/vesting-base/src/handlers.rs index 7797db28..d41c3371 100644 --- a/packages/vesting-base/src/handlers.rs +++ b/packages/vesting-base/src/handlers.rs @@ -120,7 +120,7 @@ fn receive_cw20( /// * **vesting_accounts** list of accounts and associated vesting schedules to create. /// /// * **cw20_amount** sets the amount that confirms the total amount of all accounts to register. -fn register_vesting_accounts( +pub fn register_vesting_accounts( deps: DepsMut, vesting_accounts: Vec, amount: Uint128, @@ -275,7 +275,7 @@ pub(crate) fn set_vesting_token( ])) } -pub(crate) fn get_vesting_token(config: &Config) -> Result { +pub fn get_vesting_token(config: &Config) -> Result { config .vesting_token .clone() @@ -398,7 +398,7 @@ fn is_sender_whitelisted(store: &mut dyn Storage, config: &Config, sender: &Addr /// * **addr** receiver of the vested tokens. /// /// * **vesting_schedules** vesting schedules to validate. -fn assert_vesting_schedules( +pub fn assert_vesting_schedules( addr: &Addr, vesting_schedules: &[VestingSchedule], ) -> Result<(), ContractError> { diff --git a/packages/vesting-base/src/state.rs b/packages/vesting-base/src/state.rs index 978fd8c6..9178f7ca 100644 --- a/packages/vesting-base/src/state.rs +++ b/packages/vesting-base/src/state.rs @@ -3,9 +3,9 @@ use astroport::common::OwnershipProposal; use cosmwasm_std::{Addr, Deps, StdResult}; use cw_storage_plus::{Bound, Item, Map, SnapshotItem, SnapshotMap, Strategy}; -pub(crate) const CONFIG: Item = Item::new("config"); +pub const CONFIG: Item = Item::new("config"); pub(crate) const OWNERSHIP_PROPOSAL: Item = Item::new("ownership_proposal"); -pub(crate) const VESTING_MANAGERS: Map = Map::new("vesting_managers"); +pub const VESTING_MANAGERS: Map = Map::new("vesting_managers"); pub(crate) const VESTING_STATE: SnapshotItem = SnapshotItem::new( "vesting_state", "vesting_state__checkpoints", @@ -31,14 +31,14 @@ pub(crate) const VESTING_INFO_HISTORICAL: SnapshotMap = Snaps Strategy::EveryBlock, ); -pub(crate) fn vesting_state(historical: bool) -> SnapshotItem<'static, VestingState> { +pub fn vesting_state(historical: bool) -> SnapshotItem<'static, VestingState> { if historical { return VESTING_STATE_HISTORICAL; } VESTING_STATE } -pub(crate) fn vesting_info(historical: bool) -> SnapshotMap<'static, Addr, VestingInfo> { +pub fn vesting_info(historical: bool) -> SnapshotMap<'static, Addr, VestingInfo> { if historical { return VESTING_INFO_HISTORICAL; } From aedd1bd269c5202e3518c50aafd1dd97a3480225 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Tue, 13 Feb 2024 17:55:19 -0300 Subject: [PATCH 09/30] fix schema --- .../examples/vesting-lp_schema.rs | 4 +- .../vesting-lp-pcl/schema/raw/execute.json | 406 +++--- .../schema/raw/instantiate.json | 67 +- .../{vesting-lp.json => vesting-lp-pcl.json} | 475 ++++--- contracts/vesting-lp-pcl/src/lib.rs | 3 - contracts/vesting-lp-pcl/src/msg.rs | 1 + .../vesting-lp-pcl/src/tests/integration.rs | 1265 ----------------- contracts/vesting-lp-pcl/src/tests/mod.rs | 1 - .../vesting-lp/examples/vesting-lp_schema.rs | 4 +- .../vesting-lp/examples/vesting_schema.rs | 4 +- contracts/vesting-lp/schema/raw/execute.json | 588 +++++--- contracts/vesting-lp/schema/raw/migrate.json | 40 +- contracts/vesting-lp/schema/vesting-lp.json | 628 +++++--- 13 files changed, 1464 insertions(+), 2022 deletions(-) rename contracts/vesting-lp-pcl/schema/{vesting-lp.json => vesting-lp-pcl.json} (82%) delete mode 100644 contracts/vesting-lp-pcl/src/tests/integration.rs delete mode 100644 contracts/vesting-lp-pcl/src/tests/mod.rs diff --git a/contracts/vesting-lp-pcl/examples/vesting-lp_schema.rs b/contracts/vesting-lp-pcl/examples/vesting-lp_schema.rs index 6f1967f3..6f61959b 100644 --- a/contracts/vesting-lp-pcl/examples/vesting-lp_schema.rs +++ b/contracts/vesting-lp-pcl/examples/vesting-lp_schema.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::write_api; -use vesting_base_pcl::msg::{ExecuteMsg, MigrateMsg, QueryMsg}; -use vesting_lp_pcl::msg::InstantiateMsg; +use vesting_base::msg::{MigrateMsg, QueryMsg}; +use vesting_lp_pcl::msg::{InstantiateMsg,ExecuteMsg}; fn main() { write_api! { diff --git a/contracts/vesting-lp-pcl/schema/raw/execute.json b/contracts/vesting-lp-pcl/schema/raw/execute.json index e5aa732d..3a625610 100644 --- a/contracts/vesting-lp-pcl/schema/raw/execute.json +++ b/contracts/vesting-lp-pcl/schema/raw/execute.json @@ -1,44 +1,20 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "ExecuteMsg", - "description": "This structure describes the execute messages available in a vesting contract.", "oneOf": [ { - "description": "Claim claims vested tokens and sends them to a recipient", "type": "object", "required": [ - "claim" + "base" ], "properties": { - "claim": { - "type": "object", - "properties": { - "amount": { - "description": "The amount of tokens to claim", - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "recipient": { - "description": "The address that receives the vested tokens", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false + "base": { + "$ref": "#/definitions/ExecuteMsg" } }, "additionalProperties": false }, { - "description": "Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template", "type": "object", "required": [ "receive" @@ -49,202 +25,235 @@ } }, "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" }, - { - "description": "RegisterVestingAccounts registers vesting targets/accounts", - "type": "object", - "required": [ - "register_vesting_accounts" - ], - "properties": { - "register_vesting_accounts": { + "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": [ - "vesting_accounts" + "token" ], "properties": { - "vesting_accounts": { - "type": "array", - "items": { - "$ref": "#/definitions/VestingAccount" - } + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false } }, "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Creates a request to change contract ownership ## Executor Only the current owner can execute this", - "type": "object", - "required": [ - "propose_new_owner" - ], - "properties": { - "propose_new_owner": { + }, + { + "description": "Native token", "type": "object", "required": [ - "expires_in", - "owner" + "native_token" ], "properties": { - "expires_in": { - "description": "The validity period of the offer to change the owner", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "owner": { - "description": "The newly proposed owner", - "type": "string" + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false } }, "additionalProperties": false } - }, - "additionalProperties": false + ] }, - { - "description": "Removes a request to change contract ownership ## Executor Only the current owner can execute this", + "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" + }, + "Cw20ReceiveMsg": { + "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", "type": "object", "required": [ - "drop_ownership_proposal" + "amount", + "msg", + "sender" ], "properties": { - "drop_ownership_proposal": { - "type": "object", - "additionalProperties": false + "amount": { + "$ref": "#/definitions/Uint128" + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "sender": { + "type": "string" } }, "additionalProperties": false }, - { - "description": "Claims contract ownership ## Executor Only the newly proposed owner can execute this", - "type": "object", - "required": [ - "claim_ownership" - ], - "properties": { - "claim_ownership": { + "ExecuteMsg": { + "description": "This structure describes the execute messages available in a vesting contract.", + "oneOf": [ + { + "description": "Claim claims vested tokens and sends them to a recipient", "type": "object", + "required": [ + "claim" + ], + "properties": { + "claim": { + "type": "object", + "properties": { + "amount": { + "description": "The amount of tokens to claim", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "recipient": { + "description": "The address that receives the vested tokens", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Sets vesting token ## Executor Only the current owner or token info manager can execute this", - "type": "object", - "required": [ - "set_vesting_token" - ], - "properties": { - "set_vesting_token": { + }, + { + "description": "Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template", "type": "object", "required": [ - "vesting_token" + "receive" ], "properties": { - "vesting_token": { - "$ref": "#/definitions/AssetInfo" + "receive": { + "$ref": "#/definitions/Cw20ReceiveMsg" } }, "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Contains messages associated with the managed extension for vesting contracts.", - "type": "object", - "required": [ - "managed_extension" - ], - "properties": { - "managed_extension": { + }, + { + "description": "RegisterVestingAccounts registers vesting targets/accounts", "type": "object", "required": [ - "msg" + "register_vesting_accounts" ], "properties": { - "msg": { - "$ref": "#/definitions/ExecuteMsgManaged" + "register_vesting_accounts": { + "type": "object", + "required": [ + "vesting_accounts" + ], + "properties": { + "vesting_accounts": { + "type": "array", + "items": { + "$ref": "#/definitions/VestingAccount" + } + } + }, + "additionalProperties": false } }, "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Contains messages associated with the with_managers extension for vesting contracts.", - "type": "object", - "required": [ - "with_managers_extension" - ], - "properties": { - "with_managers_extension": { + }, + { + "description": "Creates a request to change contract ownership ## Executor Only the current owner can execute this", "type": "object", "required": [ - "msg" + "propose_new_owner" ], "properties": { - "msg": { - "$ref": "#/definitions/ExecuteMsgWithManagers" + "propose_new_owner": { + "type": "object", + "required": [ + "expires_in", + "owner" + ], + "properties": { + "expires_in": { + "description": "The validity period of the offer to change the owner", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "owner": { + "description": "The newly proposed owner", + "type": "string" + } + }, + "additionalProperties": false } }, "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Contains messages associated with the historical extension for vesting contracts.", - "type": "object", - "required": [ - "historical_extension" - ], - "properties": { - "historical_extension": { + }, + { + "description": "Removes a request to change contract ownership ## Executor Only the current owner can execute this", "type": "object", "required": [ - "msg" + "drop_ownership_proposal" ], "properties": { - "msg": { - "$ref": "#/definitions/ExecuteMsgHistorical" + "drop_ownership_proposal": { + "type": "object", + "additionalProperties": false } }, "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" - }, - "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", + "description": "Claims contract ownership ## Executor Only the newly proposed owner can execute this", "type": "object", "required": [ - "token" + "claim_ownership" ], "properties": { - "token": { + "claim_ownership": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Sets vesting token ## Executor Only the current owner or token info manager can execute this", + "type": "object", + "required": [ + "set_vesting_token" + ], + "properties": { + "set_vesting_token": { "type": "object", "required": [ - "contract_addr" + "vesting_token" ], "properties": { - "contract_addr": { - "$ref": "#/definitions/Addr" + "vesting_token": { + "$ref": "#/definitions/AssetInfo" } }, "additionalProperties": false @@ -253,53 +262,72 @@ "additionalProperties": false }, { - "description": "Native token", + "description": "Contains messages associated with the managed extension for vesting contracts.", "type": "object", "required": [ - "native_token" + "managed_extension" ], "properties": { - "native_token": { + "managed_extension": { "type": "object", "required": [ - "denom" + "msg" ], "properties": { - "denom": { - "type": "string" + "msg": { + "$ref": "#/definitions/ExecuteMsgManaged" } }, "additionalProperties": false } }, "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" - }, - "Cw20ReceiveMsg": { - "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", - "type": "object", - "required": [ - "amount", - "msg", - "sender" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" }, - "msg": { - "$ref": "#/definitions/Binary" + { + "description": "Contains messages associated with the with_managers extension for vesting contracts.", + "type": "object", + "required": [ + "with_managers_extension" + ], + "properties": { + "with_managers_extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/ExecuteMsgWithManagers" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false }, - "sender": { - "type": "string" + { + "description": "Contains messages associated with the historical extension for vesting contracts.", + "type": "object", + "required": [ + "historical_extension" + ], + "properties": { + "historical_extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/ExecuteMsgHistorical" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } - }, - "additionalProperties": false + ] }, "ExecuteMsgHistorical": { "description": "This structure describes the execute messages available in a historical vesting contract.", diff --git a/contracts/vesting-lp-pcl/schema/raw/instantiate.json b/contracts/vesting-lp-pcl/schema/raw/instantiate.json index 044bc78c..cf950c42 100644 --- a/contracts/vesting-lp-pcl/schema/raw/instantiate.json +++ b/contracts/vesting-lp-pcl/schema/raw/instantiate.json @@ -6,7 +6,9 @@ "required": [ "owner", "token_info_manager", - "vesting_managers" + "vesting_managers", + "vesting_token", + "xyk_vesting_lp_contract" ], "properties": { "owner": { @@ -23,7 +25,68 @@ "items": { "type": "string" } + }, + "vesting_token": { + "$ref": "#/definitions/AssetInfo" + }, + "xyk_vesting_lp_contract": { + "type": "string" } }, - "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" + }, + "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 + } + ] + } + } } diff --git a/contracts/vesting-lp-pcl/schema/vesting-lp.json b/contracts/vesting-lp-pcl/schema/vesting-lp-pcl.json similarity index 82% rename from contracts/vesting-lp-pcl/schema/vesting-lp.json rename to contracts/vesting-lp-pcl/schema/vesting-lp-pcl.json index 395775f8..b3d8a16b 100644 --- a/contracts/vesting-lp-pcl/schema/vesting-lp.json +++ b/contracts/vesting-lp-pcl/schema/vesting-lp-pcl.json @@ -1,5 +1,5 @@ { - "contract_name": "vesting-lp", + "contract_name": "vesting-lp-pcl", "contract_version": "1.1.0", "idl_version": "1.0.0", "instantiate": { @@ -10,7 +10,9 @@ "required": [ "owner", "token_info_manager", - "vesting_managers" + "vesting_managers", + "vesting_token", + "xyk_vesting_lp_contract" ], "properties": { "owner": { @@ -27,51 +29,88 @@ "items": { "type": "string" } + }, + "vesting_token": { + "$ref": "#/definitions/AssetInfo" + }, + "xyk_vesting_lp_contract": { + "type": "string" } }, - "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" + }, + "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 + } + ] + } + } }, "execute": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "ExecuteMsg", - "description": "This structure describes the execute messages available in a vesting contract.", "oneOf": [ { - "description": "Claim claims vested tokens and sends them to a recipient", "type": "object", "required": [ - "claim" + "base" ], "properties": { - "claim": { - "type": "object", - "properties": { - "amount": { - "description": "The amount of tokens to claim", - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "recipient": { - "description": "The address that receives the vested tokens", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false + "base": { + "$ref": "#/definitions/ExecuteMsg" } }, "additionalProperties": false }, { - "description": "Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template", "type": "object", "required": [ "receive" @@ -82,202 +121,235 @@ } }, "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" }, - { - "description": "RegisterVestingAccounts registers vesting targets/accounts", - "type": "object", - "required": [ - "register_vesting_accounts" - ], - "properties": { - "register_vesting_accounts": { + "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": [ - "vesting_accounts" + "token" ], "properties": { - "vesting_accounts": { - "type": "array", - "items": { - "$ref": "#/definitions/VestingAccount" - } + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false } }, "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Creates a request to change contract ownership ## Executor Only the current owner can execute this", - "type": "object", - "required": [ - "propose_new_owner" - ], - "properties": { - "propose_new_owner": { + }, + { + "description": "Native token", "type": "object", "required": [ - "expires_in", - "owner" + "native_token" ], "properties": { - "expires_in": { - "description": "The validity period of the offer to change the owner", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "owner": { - "description": "The newly proposed owner", - "type": "string" + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false } }, "additionalProperties": false } - }, - "additionalProperties": false + ] }, - { - "description": "Removes a request to change contract ownership ## Executor Only the current owner can execute this", + "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" + }, + "Cw20ReceiveMsg": { + "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", "type": "object", "required": [ - "drop_ownership_proposal" + "amount", + "msg", + "sender" ], "properties": { - "drop_ownership_proposal": { - "type": "object", - "additionalProperties": false + "amount": { + "$ref": "#/definitions/Uint128" + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "sender": { + "type": "string" } }, "additionalProperties": false }, - { - "description": "Claims contract ownership ## Executor Only the newly proposed owner can execute this", - "type": "object", - "required": [ - "claim_ownership" - ], - "properties": { - "claim_ownership": { + "ExecuteMsg": { + "description": "This structure describes the execute messages available in a vesting contract.", + "oneOf": [ + { + "description": "Claim claims vested tokens and sends them to a recipient", "type": "object", + "required": [ + "claim" + ], + "properties": { + "claim": { + "type": "object", + "properties": { + "amount": { + "description": "The amount of tokens to claim", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "recipient": { + "description": "The address that receives the vested tokens", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + } + }, "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Sets vesting token ## Executor Only the current owner or token info manager can execute this", - "type": "object", - "required": [ - "set_vesting_token" - ], - "properties": { - "set_vesting_token": { + }, + { + "description": "Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template", "type": "object", "required": [ - "vesting_token" + "receive" ], "properties": { - "vesting_token": { - "$ref": "#/definitions/AssetInfo" + "receive": { + "$ref": "#/definitions/Cw20ReceiveMsg" } }, "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Contains messages associated with the managed extension for vesting contracts.", - "type": "object", - "required": [ - "managed_extension" - ], - "properties": { - "managed_extension": { + }, + { + "description": "RegisterVestingAccounts registers vesting targets/accounts", "type": "object", "required": [ - "msg" + "register_vesting_accounts" ], "properties": { - "msg": { - "$ref": "#/definitions/ExecuteMsgManaged" + "register_vesting_accounts": { + "type": "object", + "required": [ + "vesting_accounts" + ], + "properties": { + "vesting_accounts": { + "type": "array", + "items": { + "$ref": "#/definitions/VestingAccount" + } + } + }, + "additionalProperties": false } }, "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Contains messages associated with the with_managers extension for vesting contracts.", - "type": "object", - "required": [ - "with_managers_extension" - ], - "properties": { - "with_managers_extension": { + }, + { + "description": "Creates a request to change contract ownership ## Executor Only the current owner can execute this", "type": "object", "required": [ - "msg" + "propose_new_owner" ], "properties": { - "msg": { - "$ref": "#/definitions/ExecuteMsgWithManagers" + "propose_new_owner": { + "type": "object", + "required": [ + "expires_in", + "owner" + ], + "properties": { + "expires_in": { + "description": "The validity period of the offer to change the owner", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "owner": { + "description": "The newly proposed owner", + "type": "string" + } + }, + "additionalProperties": false } }, "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Contains messages associated with the historical extension for vesting contracts.", - "type": "object", - "required": [ - "historical_extension" - ], - "properties": { - "historical_extension": { + }, + { + "description": "Removes a request to change contract ownership ## Executor Only the current owner can execute this", "type": "object", "required": [ - "msg" + "drop_ownership_proposal" ], "properties": { - "msg": { - "$ref": "#/definitions/ExecuteMsgHistorical" + "drop_ownership_proposal": { + "type": "object", + "additionalProperties": false } }, "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" - }, - "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", + "description": "Claims contract ownership ## Executor Only the newly proposed owner can execute this", "type": "object", "required": [ - "token" + "claim_ownership" ], "properties": { - "token": { + "claim_ownership": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Sets vesting token ## Executor Only the current owner or token info manager can execute this", + "type": "object", + "required": [ + "set_vesting_token" + ], + "properties": { + "set_vesting_token": { "type": "object", "required": [ - "contract_addr" + "vesting_token" ], "properties": { - "contract_addr": { - "$ref": "#/definitions/Addr" + "vesting_token": { + "$ref": "#/definitions/AssetInfo" } }, "additionalProperties": false @@ -286,53 +358,72 @@ "additionalProperties": false }, { - "description": "Native token", + "description": "Contains messages associated with the managed extension for vesting contracts.", "type": "object", "required": [ - "native_token" + "managed_extension" ], "properties": { - "native_token": { + "managed_extension": { "type": "object", "required": [ - "denom" + "msg" ], "properties": { - "denom": { - "type": "string" + "msg": { + "$ref": "#/definitions/ExecuteMsgManaged" } }, "additionalProperties": false } }, "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" - }, - "Cw20ReceiveMsg": { - "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", - "type": "object", - "required": [ - "amount", - "msg", - "sender" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" }, - "msg": { - "$ref": "#/definitions/Binary" + { + "description": "Contains messages associated with the with_managers extension for vesting contracts.", + "type": "object", + "required": [ + "with_managers_extension" + ], + "properties": { + "with_managers_extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/ExecuteMsgWithManagers" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false }, - "sender": { - "type": "string" + { + "description": "Contains messages associated with the historical extension for vesting contracts.", + "type": "object", + "required": [ + "historical_extension" + ], + "properties": { + "historical_extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/ExecuteMsgHistorical" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } - }, - "additionalProperties": false + ] }, "ExecuteMsgHistorical": { "description": "This structure describes the execute messages available in a historical vesting contract.", diff --git a/contracts/vesting-lp-pcl/src/lib.rs b/contracts/vesting-lp-pcl/src/lib.rs index b88f5887..4934c19d 100644 --- a/contracts/vesting-lp-pcl/src/lib.rs +++ b/contracts/vesting-lp-pcl/src/lib.rs @@ -1,6 +1,3 @@ pub mod contract; pub mod msg; pub mod state; - -#[cfg(test)] -mod tests; diff --git a/contracts/vesting-lp-pcl/src/msg.rs b/contracts/vesting-lp-pcl/src/msg.rs index c6e3a529..77df5e01 100644 --- a/contracts/vesting-lp-pcl/src/msg.rs +++ b/contracts/vesting-lp-pcl/src/msg.rs @@ -18,6 +18,7 @@ pub struct InstantiateMsg { pub vesting_token: AssetInfo, } +#[cw_serde] pub enum ExecuteMsg { Base(BaseExecute), Receive(Cw20ReceiveMsg), diff --git a/contracts/vesting-lp-pcl/src/tests/integration.rs b/contracts/vesting-lp-pcl/src/tests/integration.rs deleted file mode 100644 index 70e164ac..00000000 --- a/contracts/vesting-lp-pcl/src/tests/integration.rs +++ /dev/null @@ -1,1265 +0,0 @@ -use crate::msg::InstantiateMsg; -use astroport::asset::{native_asset_info, token_asset_info}; -use astroport::querier::query_balance; -use astroport::token::InstantiateMsg as TokenInstantiateMsg; -use cosmwasm_std::{coin, coins, to_binary, Addr, StdResult, Timestamp, Uint128}; -use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg, MinterResponse}; -use cw_multi_test::{App, ContractWrapper, Executor}; -use cw_utils::PaymentError; -use vesting_base_pcl::error::ContractError; -use vesting_base_pcl::msg::{ - Cw20HookMsg, ExecuteMsg, ExecuteMsgWithManagers, QueryMsg, QueryMsgHistorical, - QueryMsgWithManagers, -}; -use vesting_base_pcl::types::{ - Config, VestingAccount, VestingAccountResponse, VestingSchedule, VestingSchedulePoint, -}; - -const OWNER1: &str = "owner1"; -const TOKEN_MANAGER: &str = "token_manager"; -const USER1: &str = "user1"; -const USER2: &str = "user2"; -const TOKEN_INITIAL_AMOUNT: u128 = 1_000_000_000_000_000; -const VESTING_TOKEN: &str = "vesting_token"; -const BLOCK_TIME: u64 = 5; - -#[test] -fn claim() { - let user1 = Addr::unchecked(USER1); - let owner = Addr::unchecked(OWNER1); - - let mut app = mock_app(&owner); - - let token_code_id = store_token_code(&mut app); - - let cw20_token_instance = - instantiate_token(&mut app, token_code_id, "NTRN", Some(1_000_000_000_000_000)); - - let vesting_instance = instantiate_vesting(&mut app, &cw20_token_instance); - - let msg = Cw20ExecuteMsg::Send { - contract: vesting_instance.to_string(), - msg: to_binary(&Cw20HookMsg::RegisterVestingAccounts { - vesting_accounts: vec![VestingAccount { - address: user1.to_string(), - schedules: vec![ - VestingSchedule { - start_point: VestingSchedulePoint { - time: Timestamp::from_seconds(100).seconds(), - amount: Uint128::zero(), - }, - end_point: Some(VestingSchedulePoint { - time: Timestamp::from_seconds(101).seconds(), - amount: Uint128::new(200), - }), - }, - VestingSchedule { - start_point: VestingSchedulePoint { - time: Timestamp::from_seconds(100).seconds(), - amount: Uint128::zero(), - }, - end_point: Some(VestingSchedulePoint { - time: Timestamp::from_seconds(110).seconds(), - amount: Uint128::new(100), - }), - }, - VestingSchedule { - start_point: VestingSchedulePoint { - time: Timestamp::from_seconds(100).seconds(), - amount: Uint128::zero(), - }, - end_point: Some(VestingSchedulePoint { - time: Timestamp::from_seconds(200).seconds(), - amount: Uint128::new(100), - }), - }, - ], - }], - }) - .unwrap(), - amount: Uint128::from(300u128), - }; - - let res = app - .execute_contract(owner.clone(), cw20_token_instance.clone(), &msg, &[]) - .unwrap_err(); - assert_eq!(res.root_cause().to_string(), "Vesting schedule amount error. The total amount should be equal to the CW20 receive amount."); - - let msg = Cw20ExecuteMsg::Send { - contract: vesting_instance.to_string(), - msg: to_binary(&Cw20HookMsg::RegisterVestingAccounts { - vesting_accounts: vec![VestingAccount { - address: user1.to_string(), - schedules: vec![ - VestingSchedule { - start_point: VestingSchedulePoint { - time: Timestamp::from_seconds(100).seconds(), - amount: Uint128::zero(), - }, - end_point: Some(VestingSchedulePoint { - time: Timestamp::from_seconds(101).seconds(), - amount: Uint128::new(100), - }), - }, - VestingSchedule { - start_point: VestingSchedulePoint { - time: Timestamp::from_seconds(100).seconds(), - amount: Uint128::zero(), - }, - end_point: Some(VestingSchedulePoint { - time: Timestamp::from_seconds(110).seconds(), - amount: Uint128::new(100), - }), - }, - VestingSchedule { - start_point: VestingSchedulePoint { - time: Timestamp::from_seconds(100).seconds(), - amount: Uint128::zero(), - }, - end_point: Some(VestingSchedulePoint { - time: Timestamp::from_seconds(200).seconds(), - amount: Uint128::new(100), - }), - }, - ], - }], - }) - .unwrap(), - amount: Uint128::from(300u128), - }; - - app.execute_contract(owner.clone(), cw20_token_instance.clone(), &msg, &[]) - .unwrap(); - - let msg = QueryMsg::AvailableAmount { - address: user1.to_string(), - }; - - let user1_vesting_amount: Uint128 = app - .wrap() - .query_wasm_smart(vesting_instance.clone(), &msg) - .unwrap(); - assert_eq!(user1_vesting_amount.clone(), Uint128::new(300u128)); - - // Check owner balance - check_token_balance( - &mut app, - &cw20_token_instance, - &owner, - TOKEN_INITIAL_AMOUNT - 300u128, - ); - - // Check vesting balance - check_token_balance(&mut app, &cw20_token_instance, &vesting_instance, 300u128); - - let msg = ExecuteMsg::Claim { - recipient: None, - amount: None, - }; - let _res = app - .execute_contract(user1.clone(), vesting_instance.clone(), &msg, &[]) - .unwrap(); - - let msg = QueryMsg::VestingAccount { - address: user1.to_string(), - }; - - let vesting_res: VestingAccountResponse = app - .wrap() - .query_wasm_smart(vesting_instance.clone(), &msg) - .unwrap(); - assert_eq!(vesting_res.info.released_amount, Uint128::from(300u128)); - - // Check vesting balance - check_token_balance(&mut app, &cw20_token_instance, &vesting_instance, 0u128); - - // Check user balance - check_token_balance(&mut app, &cw20_token_instance, &user1, 300u128); - - // Owner balance mustn't change after claim - check_token_balance( - &mut app, - &cw20_token_instance, - &owner.clone(), - TOKEN_INITIAL_AMOUNT - 300u128, - ); - - let msg = QueryMsg::AvailableAmount { - address: user1.to_string(), - }; - - // Check user balance after claim - let user1_vesting_amount: Uint128 = app - .wrap() - .query_wasm_smart(vesting_instance.clone(), &msg) - .unwrap(); - - assert_eq!(user1_vesting_amount.clone(), Uint128::new(0u128)); -} - -#[test] -fn claim_native() { - let user1 = Addr::unchecked(USER1); - let owner = Addr::unchecked(OWNER1); - - let mut app = mock_app(&owner); - - let token_code_id = store_token_code(&mut app); - - let random_token_instance = - instantiate_token(&mut app, token_code_id, "RND", Some(1_000_000_000)); - - mint_tokens(&mut app, &random_token_instance, &owner, 1_000_000_000); - - let vesting_instance = instantiate_vesting_remote_chain(&mut app); - - let msg = Cw20ExecuteMsg::Send { - contract: vesting_instance.to_string(), - msg: to_binary(&Cw20HookMsg::RegisterVestingAccounts { - vesting_accounts: vec![VestingAccount { - address: user1.to_string(), - schedules: vec![VestingSchedule { - start_point: VestingSchedulePoint { - time: Timestamp::from_seconds(100).seconds(), - amount: Uint128::zero(), - }, - end_point: Some(VestingSchedulePoint { - time: Timestamp::from_seconds(101).seconds(), - amount: Uint128::new(200), - }), - }], - }], - }) - .unwrap(), - amount: Uint128::from(300u128), - }; - - let err = app - .execute_contract(owner.clone(), random_token_instance.clone(), &msg, &[]) - .unwrap_err(); - assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()); - - let msg = ExecuteMsg::RegisterVestingAccounts { - vesting_accounts: vec![VestingAccount { - address: user1.to_string(), - schedules: vec![ - VestingSchedule { - start_point: VestingSchedulePoint { - time: Timestamp::from_seconds(100).seconds(), - amount: Uint128::zero(), - }, - end_point: Some(VestingSchedulePoint { - time: Timestamp::from_seconds(101).seconds(), - amount: Uint128::new(100), - }), - }, - VestingSchedule { - start_point: VestingSchedulePoint { - time: Timestamp::from_seconds(100).seconds(), - amount: Uint128::zero(), - }, - end_point: Some(VestingSchedulePoint { - time: Timestamp::from_seconds(110).seconds(), - amount: Uint128::new(100), - }), - }, - VestingSchedule { - start_point: VestingSchedulePoint { - time: Timestamp::from_seconds(100).seconds(), - amount: Uint128::zero(), - }, - end_point: Some(VestingSchedulePoint { - time: Timestamp::from_seconds(200).seconds(), - amount: Uint128::new(100), - }), - }, - ], - }], - }; - - app.execute_contract( - owner.clone(), - vesting_instance.clone(), - &msg, - &coins(300, VESTING_TOKEN), - ) - .unwrap(); - - let msg = QueryMsg::AvailableAmount { - address: user1.to_string(), - }; - - let user1_vesting_amount: Uint128 = app - .wrap() - .query_wasm_smart(vesting_instance.clone(), &msg) - .unwrap(); - assert_eq!(user1_vesting_amount.clone(), Uint128::new(300u128)); - - // Check owner balance - let bal = query_balance(&app.wrap(), &owner, VESTING_TOKEN) - .unwrap() - .u128(); - assert_eq!(bal, TOKEN_INITIAL_AMOUNT - 300u128); - - // Check vesting balance - let bal = query_balance(&app.wrap(), &vesting_instance, VESTING_TOKEN) - .unwrap() - .u128(); - assert_eq!(bal, 300u128); - - let msg = ExecuteMsg::Claim { - recipient: None, - amount: None, - }; - app.execute_contract(user1.clone(), vesting_instance.clone(), &msg, &[]) - .unwrap(); - - let vesting_res: VestingAccountResponse = app - .wrap() - .query_wasm_smart( - vesting_instance.clone(), - &QueryMsg::VestingAccount { - address: user1.to_string(), - }, - ) - .unwrap(); - assert_eq!(vesting_res.info.released_amount, Uint128::from(300u128)); - - // Check vesting balance - let bal = query_balance(&app.wrap(), &vesting_instance, VESTING_TOKEN) - .unwrap() - .u128(); - assert_eq!(bal, 0); - - // Check user balance - let bal = query_balance(&app.wrap(), &user1, VESTING_TOKEN) - .unwrap() - .u128(); - assert_eq!(bal, 300); - - // Owner balance mustn't change after claim - let bal = query_balance(&app.wrap(), &owner, VESTING_TOKEN) - .unwrap() - .u128(); - assert_eq!(bal, TOKEN_INITIAL_AMOUNT - 300u128); - - let msg = QueryMsg::AvailableAmount { - address: user1.to_string(), - }; - - // Check user balance after claim - let user1_vesting_amount: Uint128 = - app.wrap().query_wasm_smart(vesting_instance, &msg).unwrap(); - - assert_eq!(user1_vesting_amount.clone(), Uint128::new(0u128)); -} - -#[test] -fn register_vesting_accounts() { - let user1 = Addr::unchecked(USER1); - let user2 = Addr::unchecked(USER2); - let owner = Addr::unchecked(OWNER1); - - let mut app = mock_app(&owner); - - let token_code_id = store_token_code(&mut app); - - let cw20_token_instance = - instantiate_token(&mut app, token_code_id, "NTRN", Some(1_000_000_000_000_000)); - - let noname_token_instance = instantiate_token( - &mut app, - token_code_id, - "NONAME", - Some(1_000_000_000_000_000), - ); - - mint_tokens( - &mut app, - &noname_token_instance, - &owner, - TOKEN_INITIAL_AMOUNT, - ); - - let vesting_instance = instantiate_vesting(&mut app, &cw20_token_instance); - - let msg = Cw20ExecuteMsg::Send { - contract: vesting_instance.to_string(), - msg: to_binary(&Cw20HookMsg::RegisterVestingAccounts { - vesting_accounts: vec![VestingAccount { - address: user1.to_string(), - schedules: vec![VestingSchedule { - start_point: VestingSchedulePoint { - time: Timestamp::from_seconds(150).seconds(), - amount: Uint128::zero(), - }, - end_point: Some(VestingSchedulePoint { - time: Timestamp::from_seconds(100).seconds(), - amount: Uint128::new(100), - }), - }], - }], - }) - .unwrap(), - amount: Uint128::from(100u128), - }; - - let res = app - .execute_contract(owner.clone(), cw20_token_instance.clone(), &msg, &[]) - .unwrap_err(); - assert_eq!(res.root_cause().to_string(), "Vesting schedule error on addr: user1. Should satisfy: (start < end and at_start < total) or (start = end and at_start = total)"); - - let msg = Cw20ExecuteMsg::Send { - contract: vesting_instance.to_string(), - msg: to_binary(&Cw20HookMsg::RegisterVestingAccounts { - vesting_accounts: vec![VestingAccount { - address: user1.to_string(), - schedules: vec![VestingSchedule { - start_point: VestingSchedulePoint { - time: Timestamp::from_seconds(100).seconds(), - amount: Uint128::zero(), - }, - end_point: Some(VestingSchedulePoint { - time: Timestamp::from_seconds(150).seconds(), - amount: Uint128::new(100), - }), - }], - }], - }) - .unwrap(), - amount: Uint128::from(100u128), - }; - - let res = app - .execute_contract( - user1.clone(), - cw20_token_instance.clone(), - &msg.clone(), - &[], - ) - .unwrap_err(); - assert_eq!(res.root_cause().to_string(), "Cannot Sub with 0 and 100"); - - let res = app - .execute_contract(owner.clone(), noname_token_instance.clone(), &msg, &[]) - .unwrap_err(); - assert_eq!(res.root_cause().to_string(), "Unauthorized"); - - // Checking that execute endpoint with native coin is unreachable if the asset is a cw20 token - let native_msg = ExecuteMsg::RegisterVestingAccounts { - vesting_accounts: vec![VestingAccount { - address: user1.to_string(), - schedules: vec![VestingSchedule { - start_point: VestingSchedulePoint { - time: Timestamp::from_seconds(100).seconds(), - amount: Uint128::zero(), - }, - end_point: Some(VestingSchedulePoint { - time: Timestamp::from_seconds(150).seconds(), - amount: Uint128::new(100), - }), - }], - }], - }; - - let err = app - .execute_contract( - owner.clone(), - vesting_instance.clone(), - &native_msg, - &coins(100u128, "random_coin"), - ) - .unwrap_err(); - assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()); - - let _res = app - .execute_contract(owner.clone(), cw20_token_instance.clone(), &msg, &[]) - .unwrap(); - - let msg = QueryMsg::AvailableAmount { - address: user1.to_string(), - }; - - let user1_vesting_amount: Uint128 = app - .wrap() - .query_wasm_smart(vesting_instance.clone(), &msg) - .unwrap(); - - assert_eq!(user1_vesting_amount.clone(), Uint128::new(100u128)); - check_token_balance( - &mut app, - &cw20_token_instance, - &owner.clone(), - TOKEN_INITIAL_AMOUNT - 100u128, - ); - check_token_balance(&mut app, &cw20_token_instance, &vesting_instance, 100u128); - - // Let's check user1's final vesting amount after add schedule for a new one - let msg = Cw20ExecuteMsg::Send { - contract: vesting_instance.to_string(), - msg: to_binary(&Cw20HookMsg::RegisterVestingAccounts { - vesting_accounts: vec![VestingAccount { - address: user2.to_string(), - schedules: vec![VestingSchedule { - start_point: VestingSchedulePoint { - time: Timestamp::from_seconds(100).seconds(), - amount: Uint128::zero(), - }, - end_point: Some(VestingSchedulePoint { - time: Timestamp::from_seconds(150).seconds(), - amount: Uint128::new(200), - }), - }], - }], - }) - .unwrap(), - amount: Uint128::from(200u128), - }; - - let _res = app - .execute_contract(owner.clone(), cw20_token_instance.clone(), &msg, &[]) - .unwrap(); - - let msg = QueryMsg::AvailableAmount { - address: user2.to_string(), - }; - - let user2_vesting_amount: Uint128 = app - .wrap() - .query_wasm_smart(vesting_instance.clone(), &msg) - .unwrap(); - - check_token_balance( - &mut app, - &cw20_token_instance, - &owner.clone(), - TOKEN_INITIAL_AMOUNT - 300u128, - ); - check_token_balance(&mut app, &cw20_token_instance, &vesting_instance, 300u128); - // A new schedule has been added successfully and an old one hasn't changed. - // The new schedule doesn't have the same value as the old one. - assert_eq!(user2_vesting_amount, Uint128::new(200u128)); - assert_eq!(user1_vesting_amount, Uint128::from(100u128)); - - // Add one more vesting schedule; final amount to vest must increase - let msg = Cw20ExecuteMsg::Send { - contract: vesting_instance.to_string(), - msg: to_binary(&Cw20HookMsg::RegisterVestingAccounts { - vesting_accounts: vec![VestingAccount { - address: user1.to_string(), - schedules: vec![VestingSchedule { - start_point: VestingSchedulePoint { - time: Timestamp::from_seconds(100).seconds(), - amount: Uint128::zero(), - }, - end_point: Some(VestingSchedulePoint { - time: Timestamp::from_seconds(200).seconds(), - amount: Uint128::new(10), - }), - }], - }], - }) - .unwrap(), - amount: Uint128::from(10u128), - }; - - let _res = app - .execute_contract(owner.clone(), cw20_token_instance.clone(), &msg, &[]) - .unwrap(); - - let msg = QueryMsg::AvailableAmount { - address: user1.to_string(), - }; - - let vesting_res: Uint128 = app - .wrap() - .query_wasm_smart(vesting_instance.clone(), &msg) - .unwrap(); - - assert_eq!(vesting_res, Uint128::new(110u128)); - check_token_balance( - &mut app, - &cw20_token_instance, - &owner.clone(), - TOKEN_INITIAL_AMOUNT - 310u128, - ); - check_token_balance(&mut app, &cw20_token_instance, &vesting_instance, 310u128); - - let msg = ExecuteMsg::Claim { - recipient: None, - amount: None, - }; - let _res = app - .execute_contract(user1.clone(), vesting_instance.clone(), &msg, &[]) - .unwrap(); - - let msg = QueryMsg::VestingAccount { - address: user1.to_string(), - }; - - let vesting_res: VestingAccountResponse = app - .wrap() - .query_wasm_smart(vesting_instance.clone(), &msg) - .unwrap(); - assert_eq!(vesting_res.info.released_amount, Uint128::from(110u128)); - check_token_balance(&mut app, &cw20_token_instance, &vesting_instance, 200u128); - check_token_balance(&mut app, &cw20_token_instance, &user1, 110u128); - - // Owner balance mustn't change after claim - check_token_balance( - &mut app, - &cw20_token_instance, - &owner.clone(), - TOKEN_INITIAL_AMOUNT - 310u128, - ); -} - -#[test] -fn register_vesting_accounts_native() { - let user1 = Addr::unchecked(USER1); - let user2 = Addr::unchecked(USER2); - let owner = Addr::unchecked(OWNER1); - - let mut app = mock_app(&owner); - - let token_code_id = store_token_code(&mut app); - - let random_token_instance = - instantiate_token(&mut app, token_code_id, "RND", Some(1_000_000_000_000_000)); - - mint_tokens( - &mut app, - &random_token_instance, - &owner, - TOKEN_INITIAL_AMOUNT, - ); - - let vesting_instance = instantiate_vesting_remote_chain(&mut app); - - let msg = Cw20ExecuteMsg::Send { - contract: vesting_instance.to_string(), - msg: to_binary(&Cw20HookMsg::RegisterVestingAccounts { - vesting_accounts: vec![VestingAccount { - address: user1.to_string(), - schedules: vec![VestingSchedule { - start_point: VestingSchedulePoint { - time: Timestamp::from_seconds(100).seconds(), - amount: Uint128::zero(), - }, - end_point: Some(VestingSchedulePoint { - time: Timestamp::from_seconds(150).seconds(), - amount: Uint128::new(100), - }), - }], - }], - }) - .unwrap(), - amount: Uint128::from(100u128), - }; - - let err = app - .execute_contract(owner.clone(), random_token_instance.clone(), &msg, &[]) - .unwrap_err(); - assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()); - - // Checking that execute endpoint with random native coin is unreachable - let native_msg = ExecuteMsg::RegisterVestingAccounts { - vesting_accounts: vec![VestingAccount { - address: user1.to_string(), - schedules: vec![VestingSchedule { - start_point: VestingSchedulePoint { - time: Timestamp::from_seconds(100).seconds(), - amount: Uint128::zero(), - }, - end_point: Some(VestingSchedulePoint { - time: Timestamp::from_seconds(150).seconds(), - amount: Uint128::new(100), - }), - }], - }], - }; - - let err = app - .execute_contract( - owner.clone(), - vesting_instance.clone(), - &native_msg, - &coins(100u128, "random_coin"), - ) - .unwrap_err(); - assert_eq!( - ContractError::PaymentError(PaymentError::MissingDenom("vesting_token".to_string())), - err.downcast().unwrap() - ); - - app.execute_contract( - owner.clone(), - vesting_instance.clone(), - &native_msg, - &coins(100u128, VESTING_TOKEN), - ) - .unwrap(); - - let msg = QueryMsg::AvailableAmount { - address: user1.to_string(), - }; - - let user1_vesting_amount: Uint128 = app - .wrap() - .query_wasm_smart(&vesting_instance, &msg) - .unwrap(); - assert_eq!(user1_vesting_amount.u128(), 100u128); - - let bal = query_balance(&app.wrap(), &owner, VESTING_TOKEN) - .unwrap() - .u128(); - assert_eq!(bal, TOKEN_INITIAL_AMOUNT - 100u128); - - let bal = query_balance(&app.wrap(), &vesting_instance, VESTING_TOKEN) - .unwrap() - .u128(); - assert_eq!(bal, 100); - - // Let's check user1's final vesting amount after add schedule for a new one - let msg = ExecuteMsg::RegisterVestingAccounts { - vesting_accounts: vec![VestingAccount { - address: user2.to_string(), - schedules: vec![VestingSchedule { - start_point: VestingSchedulePoint { - time: Timestamp::from_seconds(100).seconds(), - amount: Uint128::zero(), - }, - end_point: Some(VestingSchedulePoint { - time: Timestamp::from_seconds(150).seconds(), - amount: Uint128::new(200), - }), - }], - }], - }; - - app.execute_contract( - owner.clone(), - vesting_instance.clone(), - &msg, - &coins(200, VESTING_TOKEN), - ) - .unwrap(); - - let msg = QueryMsg::AvailableAmount { - address: user2.to_string(), - }; - - let user2_vesting_amount: Uint128 = app - .wrap() - .query_wasm_smart(vesting_instance.clone(), &msg) - .unwrap(); - - let bal = query_balance(&app.wrap(), &owner, VESTING_TOKEN) - .unwrap() - .u128(); - assert_eq!(bal, TOKEN_INITIAL_AMOUNT - 300u128); - let bal = query_balance(&app.wrap(), &vesting_instance, VESTING_TOKEN) - .unwrap() - .u128(); - assert_eq!(bal, 300u128); - - // A new schedule has been added successfully and an old one hasn't changed. - // The new schedule doesn't have the same value as the old one. - assert_eq!(user2_vesting_amount, Uint128::new(200u128)); - assert_eq!(user1_vesting_amount, Uint128::from(100u128)); - - // Add one more vesting schedule; final amount to vest must increase - let msg = ExecuteMsg::RegisterVestingAccounts { - vesting_accounts: vec![VestingAccount { - address: user1.to_string(), - schedules: vec![VestingSchedule { - start_point: VestingSchedulePoint { - time: Timestamp::from_seconds(100).seconds(), - amount: Uint128::zero(), - }, - end_point: Some(VestingSchedulePoint { - time: Timestamp::from_seconds(200).seconds(), - amount: Uint128::new(10), - }), - }], - }], - }; - - app.execute_contract( - owner.clone(), - vesting_instance.clone(), - &msg, - &coins(10, VESTING_TOKEN), - ) - .unwrap(); - - let msg = QueryMsg::AvailableAmount { - address: user1.to_string(), - }; - - let vesting_res: Uint128 = app - .wrap() - .query_wasm_smart(vesting_instance.clone(), &msg) - .unwrap(); - assert_eq!(vesting_res, Uint128::new(110u128)); - - let bal = query_balance(&app.wrap(), &owner, VESTING_TOKEN) - .unwrap() - .u128(); - assert_eq!(bal, TOKEN_INITIAL_AMOUNT - 310u128); - let bal = query_balance(&app.wrap(), &vesting_instance, VESTING_TOKEN) - .unwrap() - .u128(); - assert_eq!(bal, 310u128); - - let msg = ExecuteMsg::Claim { - recipient: None, - amount: None, - }; - let _res = app - .execute_contract(user1.clone(), vesting_instance.clone(), &msg, &[]) - .unwrap(); - - let msg = QueryMsg::VestingAccount { - address: user1.to_string(), - }; - - let vesting_res: VestingAccountResponse = app - .wrap() - .query_wasm_smart(vesting_instance.clone(), &msg) - .unwrap(); - assert_eq!(vesting_res.info.released_amount, Uint128::from(110u128)); - - let bal = query_balance(&app.wrap(), &vesting_instance, VESTING_TOKEN) - .unwrap() - .u128(); - assert_eq!(bal, 200); - let bal = query_balance(&app.wrap(), &user1, VESTING_TOKEN) - .unwrap() - .u128(); - assert_eq!(bal, 110u128); - - let bal = query_balance(&app.wrap(), &owner, VESTING_TOKEN) - .unwrap() - .u128(); - assert_eq!(bal, TOKEN_INITIAL_AMOUNT - 310u128); -} - -#[test] -fn query_at_height() { - let user1 = Addr::unchecked(USER1); - let user2 = Addr::unchecked(USER2); - let owner = Addr::unchecked(OWNER1); - - let mut app = mock_app(&owner); - let start_block_height = app.block_info().height; - - let vesting_instance = instantiate_vesting_remote_chain(&mut app); - - let native_msg = ExecuteMsg::RegisterVestingAccounts { - vesting_accounts: vec![ - VestingAccount { - address: user1.to_string(), - schedules: vec![ - VestingSchedule { - start_point: VestingSchedulePoint { - time: app.block_info().time.seconds(), - amount: Uint128::zero(), - }, - end_point: Some(VestingSchedulePoint { - time: app - .block_info() - .time - .plus_seconds(100 * BLOCK_TIME) - .seconds(), - amount: Uint128::new(50), - }), - }, - VestingSchedule { - start_point: VestingSchedulePoint { - time: app.block_info().time.seconds(), - amount: Uint128::zero(), - }, - end_point: Some(VestingSchedulePoint { - time: app - .block_info() - .time - .plus_seconds(100 * BLOCK_TIME) - .seconds(), - amount: Uint128::new(150), - }), - }, - ], - }, - VestingAccount { - address: user2.to_string(), - schedules: vec![VestingSchedule { - start_point: VestingSchedulePoint { - time: app.block_info().time.seconds(), - amount: Uint128::zero(), - }, - end_point: Some(VestingSchedulePoint { - time: app - .block_info() - .time - .plus_seconds(100 * BLOCK_TIME) - .seconds(), - amount: Uint128::new(1000), - }), - }], - }, - ], - }; - - app.execute_contract( - owner, - vesting_instance.clone(), - &native_msg, - &coins(1200, VESTING_TOKEN), - ) - .unwrap(); - - let query = QueryMsg::AvailableAmount { - address: user1.to_string(), - }; - - for _ in 1..=10 { - let vesting_res: Uint128 = app - .wrap() - .query_wasm_smart(vesting_instance.clone(), &query) - .unwrap(); - assert_eq!(vesting_res, Uint128::new(0u128)); - - app.update_block(|b| { - b.height += 10; - b.time = b.time.plus_seconds(10 * BLOCK_TIME) - }); - - let vesting_res: Uint128 = app - .wrap() - .query_wasm_smart(vesting_instance.clone(), &query) - .unwrap(); - assert_eq!(vesting_res, Uint128::new(20u128)); - - let msg = ExecuteMsg::Claim { - recipient: None, - amount: None, - }; - let _res = app - .execute_contract(user1.clone(), vesting_instance.clone(), &msg, &[]) - .unwrap(); - - let vesting_res: Uint128 = app - .wrap() - .query_wasm_smart(vesting_instance.clone(), &query) - .unwrap(); - assert_eq!(vesting_res, Uint128::new(0u128)); - } - app.update_block(|b| { - b.height += 100; - b.time = b.time.plus_seconds(100 * BLOCK_TIME) - }); - let vesting_res: Uint128 = app - .wrap() - .query_wasm_smart(vesting_instance.clone(), &query) - .unwrap(); - assert_eq!(vesting_res, Uint128::new(0u128)); - - let query_user_unclamed = QueryMsg::HistoricalExtension { - msg: QueryMsgHistorical::UnclaimedAmountAtHeight { - address: user1.to_string(), - height: start_block_height - 1, - }, - }; - let vesting_res: Uint128 = app - .wrap() - .query_wasm_smart(vesting_instance.clone(), &query_user_unclamed) - .unwrap(); - assert_eq!(vesting_res, Uint128::new(0u128)); - - let query_total_unclamed = QueryMsg::HistoricalExtension { - msg: QueryMsgHistorical::UnclaimedTotalAmountAtHeight { - height: start_block_height - 1, - }, - }; - let vesting_res: Uint128 = app - .wrap() - .query_wasm_smart(vesting_instance.clone(), &query_total_unclamed) - .unwrap(); - assert_eq!(vesting_res, Uint128::new(0u128)); - let max_unclaimed_user1: u128 = 200; - let max_unclaimed_total: u128 = 1200; - for i in 0..=10 { - let query = QueryMsg::HistoricalExtension { - msg: QueryMsgHistorical::UnclaimedAmountAtHeight { - address: user1.to_string(), - height: start_block_height + 1 + i * 10, - }, - }; - let vesting_res: Uint128 = app - .wrap() - .query_wasm_smart(vesting_instance.clone(), &query) - .unwrap(); - assert_eq!( - vesting_res, - Uint128::new(max_unclaimed_user1 - (i as u128) * 20) - ); - - let query_total_unclamed = QueryMsg::HistoricalExtension { - msg: QueryMsgHistorical::UnclaimedTotalAmountAtHeight { - height: start_block_height + 1 + i * 10, - }, - }; - let vesting_res: Uint128 = app - .wrap() - .query_wasm_smart(vesting_instance.clone(), &query_total_unclamed) - .unwrap(); - assert_eq!( - vesting_res, - Uint128::new(max_unclaimed_total - (i as u128) * 20) - ); - } -} - -#[test] -fn vesting_managers() { - let user1 = Addr::unchecked(USER1); - let user2 = Addr::unchecked(USER2); - let owner = Addr::unchecked(OWNER1); - - let mut app = mock_app(&owner); - let vesting_instance = instantiate_vesting_remote_chain(&mut app); - - let query = QueryMsg::WithManagersExtension { - msg: QueryMsgWithManagers::VestingManagers {}, - }; - let vesting_res: Vec = app - .wrap() - .query_wasm_smart(vesting_instance.clone(), &query) - .unwrap(); - assert_eq!(vesting_res.len(), 0,); - - let native_msg = ExecuteMsg::RegisterVestingAccounts { - vesting_accounts: vec![VestingAccount { - address: user1.to_string(), - schedules: vec![VestingSchedule { - start_point: VestingSchedulePoint { - time: app.block_info().time.seconds(), - amount: Uint128::zero(), - }, - end_point: Some(VestingSchedulePoint { - time: app - .block_info() - .time - .plus_seconds(100 * BLOCK_TIME) - .seconds(), - amount: Uint128::new(50), - }), - }], - }], - }; - let err = app - .execute_contract(user1.clone(), vesting_instance.clone(), &native_msg, &[]) - .unwrap_err(); - assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()); - - let add_manager_msg = ExecuteMsg::WithManagersExtension { - msg: ExecuteMsgWithManagers::AddVestingManagers { - managers: vec![user1.to_string()], - }, - }; - - let err = app - .execute_contract( - user1.clone(), - vesting_instance.clone(), - &add_manager_msg, - &[], - ) - .unwrap_err(); - assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()); - - let _res = app - .execute_contract( - owner.clone(), - vesting_instance.clone(), - &add_manager_msg, - &[], - ) - .unwrap(); - - let vesting_res: Vec = app - .wrap() - .query_wasm_smart(vesting_instance.clone(), &query) - .unwrap(); - assert_eq!(vesting_res, vec![Addr::unchecked(user1.clone())]); - - app.send_tokens(owner.clone(), user1.clone(), &coins(50, VESTING_TOKEN)) - .unwrap(); - - let _res = app - .execute_contract( - user1.clone(), - vesting_instance.clone(), - &native_msg, - &coins(50, VESTING_TOKEN), - ) - .unwrap(); - let err = app - .execute_contract(user2, vesting_instance.clone(), &native_msg, &[]) - .unwrap_err(); - assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()); - - let remove_manager_msg = ExecuteMsg::WithManagersExtension { - msg: ExecuteMsgWithManagers::RemoveVestingManagers { - managers: vec![user1.to_string()], - }, - }; - let err = app - .execute_contract(user1, vesting_instance.clone(), &remove_manager_msg, &[]) - .unwrap_err(); - assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()); - - let _res = app - .execute_contract(owner, vesting_instance.clone(), &remove_manager_msg, &[]) - .unwrap(); - - let vesting_res: Vec = app - .wrap() - .query_wasm_smart(vesting_instance, &query) - .unwrap(); - assert_eq!(vesting_res.len(), 0); -} - -fn mock_app(owner: &Addr) -> App { - App::new(|app, _, storage| { - app.bank - .init_balance( - storage, - owner, - vec![ - coin(TOKEN_INITIAL_AMOUNT, VESTING_TOKEN), - coin(10_000_000_000u128, "random_coin"), - ], - ) - .unwrap() - }) -} - -fn store_token_code(app: &mut App) -> u64 { - let cw20_token_contract = Box::new(ContractWrapper::new_with_empty( - astroport_token::contract::execute, - astroport_token::contract::instantiate, - astroport_token::contract::query, - )); - - app.store_code(cw20_token_contract) -} - -fn instantiate_token(app: &mut App, token_code_id: u64, name: &str, cap: Option) -> Addr { - let name = String::from(name); - - let msg = TokenInstantiateMsg { - name: name.clone(), - symbol: name.clone(), - decimals: 6, - initial_balances: vec![], - mint: Some(MinterResponse { - minter: String::from(OWNER1), - cap: cap.map(Uint128::from), - }), - marketing: None, - }; - - app.instantiate_contract( - token_code_id, - Addr::unchecked(OWNER1), - &msg, - &[], - name, - None, - ) - .unwrap() -} - -fn instantiate_vesting(app: &mut App, cw20_token_instance: &Addr) -> Addr { - let vesting_contract = Box::new(ContractWrapper::new_with_empty( - crate::contract::execute, - crate::contract::instantiate, - crate::contract::query, - )); - let owner = Addr::unchecked(OWNER1); - let vesting_code_id = app.store_code(vesting_contract); - - let init_msg = InstantiateMsg { - owner: OWNER1.to_string(), - token_info_manager: TOKEN_MANAGER.to_string(), - xyk_vesting_lp_contract: "test".to_string(), - vesting_managers: vec![], - vesting_token: token_asset_info(cw20_token_instance.clone()), - }; - - let vesting_instance = app - .instantiate_contract( - vesting_code_id, - owner.clone(), - &init_msg, - &[], - "Vesting", - None, - ) - .unwrap(); - let res: Config = app - .wrap() - .query_wasm_smart(vesting_instance.clone(), &QueryMsg::Config {}) - .unwrap(); - assert_eq!( - cw20_token_instance.to_string(), - res.vesting_token.unwrap().to_string() - ); - - mint_tokens(app, cw20_token_instance, &owner, TOKEN_INITIAL_AMOUNT); - - check_token_balance(app, cw20_token_instance, &owner, TOKEN_INITIAL_AMOUNT); - - vesting_instance -} - -fn instantiate_vesting_remote_chain(app: &mut App) -> Addr { - let vesting_contract = Box::new(ContractWrapper::new_with_empty( - crate::contract::execute, - crate::contract::instantiate, - crate::contract::query, - )); - let owner = Addr::unchecked(OWNER1); - let vesting_code_id = app.store_code(vesting_contract); - - let init_msg = InstantiateMsg { - owner: OWNER1.to_string(), - token_info_manager: TOKEN_MANAGER.to_string(), - vesting_managers: vec![], - vesting_token: native_asset_info(VESTING_TOKEN.to_string()), - xyk_vesting_lp_contract: "test".to_string(), - }; - - app.instantiate_contract(vesting_code_id, owner, &init_msg, &[], "Vesting", None) - .unwrap() -} - -fn mint_tokens(app: &mut App, token: &Addr, recipient: &Addr, amount: u128) { - let msg = Cw20ExecuteMsg::Mint { - recipient: recipient.to_string(), - amount: Uint128::from(amount), - }; - - app.execute_contract(Addr::unchecked(OWNER1), token.to_owned(), &msg, &[]) - .unwrap(); -} - -fn check_token_balance(app: &mut App, token: &Addr, address: &Addr, expected: u128) { - let msg = Cw20QueryMsg::Balance { - address: address.to_string(), - }; - let res: StdResult = app.wrap().query_wasm_smart(token, &msg); - assert_eq!(res.unwrap().balance, Uint128::from(expected)); -} diff --git a/contracts/vesting-lp-pcl/src/tests/mod.rs b/contracts/vesting-lp-pcl/src/tests/mod.rs deleted file mode 100644 index 6d3bbe60..00000000 --- a/contracts/vesting-lp-pcl/src/tests/mod.rs +++ /dev/null @@ -1 +0,0 @@ -mod integration; diff --git a/contracts/vesting-lp/examples/vesting-lp_schema.rs b/contracts/vesting-lp/examples/vesting-lp_schema.rs index 90284134..9f654a21 100644 --- a/contracts/vesting-lp/examples/vesting-lp_schema.rs +++ b/contracts/vesting-lp/examples/vesting-lp_schema.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::write_api; -use vesting_base::msg::{ExecuteMsg, MigrateMsg, QueryMsg}; -use vesting_lp::msg::InstantiateMsg; +use vesting_lp::msg::{ExecuteMsg, MigrateMsg, InstantiateMsg}; +use vesting_base::msg:: QueryMsg; fn main() { write_api! { diff --git a/contracts/vesting-lp/examples/vesting_schema.rs b/contracts/vesting-lp/examples/vesting_schema.rs index 90284134..9be67c43 100644 --- a/contracts/vesting-lp/examples/vesting_schema.rs +++ b/contracts/vesting-lp/examples/vesting_schema.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::write_api; -use vesting_base::msg::{ExecuteMsg, MigrateMsg, QueryMsg}; -use vesting_lp::msg::InstantiateMsg; +use vesting_lp::msg::{ExecuteMsg, MigrateMsg, InstantiateMsg}; +use vesting_base::msg::QueryMsg; fn main() { write_api! { diff --git a/contracts/vesting-lp/schema/raw/execute.json b/contracts/vesting-lp/schema/raw/execute.json index e5aa732d..c981b115 100644 --- a/contracts/vesting-lp/schema/raw/execute.json +++ b/contracts/vesting-lp/schema/raw/execute.json @@ -1,31 +1,29 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "ExecuteMsg", - "description": "This structure describes the execute messages available in a vesting contract.", "oneOf": [ { - "description": "Claim claims vested tokens and sends them to a recipient", "type": "object", "required": [ - "claim" + "base" ], "properties": { - "claim": { + "base": { + "$ref": "#/definitions/ExecuteMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "migrate_liquidity_to_pcl_pool" + ], + "properties": { + "migrate_liquidity_to_pcl_pool": { "type": "object", "properties": { - "amount": { - "description": "The amount of tokens to claim", - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "recipient": { - "description": "The address that receives the vested tokens", + "user": { "type": [ "string", "null" @@ -38,213 +36,369 @@ "additionalProperties": false }, { - "description": "Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template", + "description": "Callbacks; only callable by the contract itself.", "type": "object", "required": [ - "receive" + "callback" ], "properties": { - "receive": { - "$ref": "#/definitions/Cw20ReceiveMsg" + "callback": { + "$ref": "#/definitions/CallbackMsg" } }, "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" }, - { - "description": "RegisterVestingAccounts registers vesting targets/accounts", - "type": "object", - "required": [ - "register_vesting_accounts" - ], - "properties": { - "register_vesting_accounts": { + "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": [ - "vesting_accounts" + "token" ], "properties": { - "vesting_accounts": { - "type": "array", - "items": { - "$ref": "#/definitions/VestingAccount" - } + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false } }, "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Creates a request to change contract ownership ## Executor Only the current owner can execute this", - "type": "object", - "required": [ - "propose_new_owner" - ], - "properties": { - "propose_new_owner": { + }, + { + "description": "Native token", "type": "object", "required": [ - "expires_in", - "owner" + "native_token" ], "properties": { - "expires_in": { - "description": "The validity period of the offer to change the owner", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "owner": { - "description": "The newly proposed owner", - "type": "string" + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false } }, "additionalProperties": false } - }, - "additionalProperties": false + ] }, - { - "description": "Removes a request to change contract ownership ## Executor Only the current owner can execute this", - "type": "object", - "required": [ - "drop_ownership_proposal" - ], - "properties": { - "drop_ownership_proposal": { + "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" + }, + "CallbackMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "migrate_liquidity_to_cl_pair" + ], + "properties": { + "migrate_liquidity_to_cl_pair": { + "type": "object", + "required": [ + "amount", + "cl_pair", + "ntrn_denom", + "paired_asset_denom", + "slippage_tolerance", + "user", + "xyk_lp_token", + "xyk_pair" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "cl_pair": { + "$ref": "#/definitions/Addr" + }, + "ntrn_denom": { + "type": "string" + }, + "paired_asset_denom": { + "type": "string" + }, + "slippage_tolerance": { + "$ref": "#/definitions/Decimal" + }, + "user": { + "$ref": "#/definitions/VestingAccountResponse" + }, + "xyk_lp_token": { + "$ref": "#/definitions/Addr" + }, + "xyk_pair": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { "type": "object", + "required": [ + "provide_liquidity_to_cl_pair_after_withdrawal" + ], + "properties": { + "provide_liquidity_to_cl_pair_after_withdrawal": { + "type": "object", + "required": [ + "cl_pair", + "ntrn_denom", + "ntrn_init_balance", + "paired_asset_denom", + "paired_asset_init_balance", + "slippage_tolerance", + "user" + ], + "properties": { + "cl_pair": { + "$ref": "#/definitions/Addr" + }, + "ntrn_denom": { + "type": "string" + }, + "ntrn_init_balance": { + "$ref": "#/definitions/Uint128" + }, + "paired_asset_denom": { + "type": "string" + }, + "paired_asset_init_balance": { + "$ref": "#/definitions/Uint128" + }, + "slippage_tolerance": { + "$ref": "#/definitions/Decimal" + }, + "user": { + "$ref": "#/definitions/VestingAccountResponse" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "post_migration_vesting_reschedule" + ], + "properties": { + "post_migration_vesting_reschedule": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "user": { + "$ref": "#/definitions/VestingAccountResponse" + } + }, + "additionalProperties": false + } + }, "additionalProperties": false } - }, - "additionalProperties": false + ] }, - { - "description": "Claims contract ownership ## Executor Only the newly proposed owner can execute this", + "Cw20ReceiveMsg": { + "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", "type": "object", "required": [ - "claim_ownership" + "amount", + "msg", + "sender" ], "properties": { - "claim_ownership": { - "type": "object", - "additionalProperties": false + "amount": { + "$ref": "#/definitions/Uint128" + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "sender": { + "type": "string" } }, "additionalProperties": false }, - { - "description": "Sets vesting token ## Executor Only the current owner or token info manager can execute this", - "type": "object", - "required": [ - "set_vesting_token" - ], - "properties": { - "set_vesting_token": { + "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" + }, + "ExecuteMsg": { + "description": "This structure describes the execute messages available in a vesting contract.", + "oneOf": [ + { + "description": "Claim claims vested tokens and sends them to a recipient", "type": "object", "required": [ - "vesting_token" + "claim" ], "properties": { - "vesting_token": { - "$ref": "#/definitions/AssetInfo" + "claim": { + "type": "object", + "properties": { + "amount": { + "description": "The amount of tokens to claim", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "recipient": { + "description": "The address that receives the vested tokens", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false } }, "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Contains messages associated with the managed extension for vesting contracts.", - "type": "object", - "required": [ - "managed_extension" - ], - "properties": { - "managed_extension": { + }, + { + "description": "Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template", "type": "object", "required": [ - "msg" + "receive" ], "properties": { - "msg": { - "$ref": "#/definitions/ExecuteMsgManaged" + "receive": { + "$ref": "#/definitions/Cw20ReceiveMsg" } }, "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Contains messages associated with the with_managers extension for vesting contracts.", - "type": "object", - "required": [ - "with_managers_extension" - ], - "properties": { - "with_managers_extension": { + }, + { + "description": "RegisterVestingAccounts registers vesting targets/accounts", "type": "object", "required": [ - "msg" + "register_vesting_accounts" ], "properties": { - "msg": { - "$ref": "#/definitions/ExecuteMsgWithManagers" + "register_vesting_accounts": { + "type": "object", + "required": [ + "vesting_accounts" + ], + "properties": { + "vesting_accounts": { + "type": "array", + "items": { + "$ref": "#/definitions/VestingAccount" + } + } + }, + "additionalProperties": false } }, "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Contains messages associated with the historical extension for vesting contracts.", - "type": "object", - "required": [ - "historical_extension" - ], - "properties": { - "historical_extension": { + }, + { + "description": "Creates a request to change contract ownership ## Executor Only the current owner can execute this", "type": "object", "required": [ - "msg" + "propose_new_owner" ], "properties": { - "msg": { - "$ref": "#/definitions/ExecuteMsgHistorical" + "propose_new_owner": { + "type": "object", + "required": [ + "expires_in", + "owner" + ], + "properties": { + "expires_in": { + "description": "The validity period of the offer to change the owner", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "owner": { + "description": "The newly proposed owner", + "type": "string" + } + }, + "additionalProperties": false } }, "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" - }, - "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", + "description": "Removes a request to change contract ownership ## Executor Only the current owner can execute this", "type": "object", "required": [ - "token" + "drop_ownership_proposal" ], "properties": { - "token": { + "drop_ownership_proposal": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Claims contract ownership ## Executor Only the newly proposed owner can execute this", + "type": "object", + "required": [ + "claim_ownership" + ], + "properties": { + "claim_ownership": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Sets vesting token ## Executor Only the current owner or token info manager can execute this", + "type": "object", + "required": [ + "set_vesting_token" + ], + "properties": { + "set_vesting_token": { "type": "object", "required": [ - "contract_addr" + "vesting_token" ], "properties": { - "contract_addr": { - "$ref": "#/definitions/Addr" + "vesting_token": { + "$ref": "#/definitions/AssetInfo" } }, "additionalProperties": false @@ -253,53 +407,72 @@ "additionalProperties": false }, { - "description": "Native token", + "description": "Contains messages associated with the managed extension for vesting contracts.", "type": "object", "required": [ - "native_token" + "managed_extension" ], "properties": { - "native_token": { + "managed_extension": { "type": "object", "required": [ - "denom" + "msg" ], "properties": { - "denom": { - "type": "string" + "msg": { + "$ref": "#/definitions/ExecuteMsgManaged" } }, "additionalProperties": false } }, "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" - }, - "Cw20ReceiveMsg": { - "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", - "type": "object", - "required": [ - "amount", - "msg", - "sender" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" }, - "msg": { - "$ref": "#/definitions/Binary" + { + "description": "Contains messages associated with the with_managers extension for vesting contracts.", + "type": "object", + "required": [ + "with_managers_extension" + ], + "properties": { + "with_managers_extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/ExecuteMsgWithManagers" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false }, - "sender": { - "type": "string" + { + "description": "Contains messages associated with the historical extension for vesting contracts.", + "type": "object", + "required": [ + "historical_extension" + ], + "properties": { + "historical_extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/ExecuteMsgHistorical" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } - }, - "additionalProperties": false + ] }, "ExecuteMsgHistorical": { "description": "This structure describes the execute messages available in a historical vesting contract.", @@ -422,6 +595,59 @@ }, "additionalProperties": false }, + "VestingAccountResponse": { + "description": "This structure describes a custom struct used to return vesting data about a specific vesting target.", + "type": "object", + "required": [ + "address", + "info" + ], + "properties": { + "address": { + "description": "The address that's vesting tokens", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "info": { + "description": "Vesting information", + "allOf": [ + { + "$ref": "#/definitions/VestingInfo" + } + ] + } + }, + "additionalProperties": false + }, + "VestingInfo": { + "description": "This structure stores parameters for a batch of vesting schedules.", + "type": "object", + "required": [ + "released_amount", + "schedules" + ], + "properties": { + "released_amount": { + "description": "The total amount of vested tokens already claimed", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "schedules": { + "description": "The vesting schedules", + "type": "array", + "items": { + "$ref": "#/definitions/VestingSchedule" + } + } + }, + "additionalProperties": false + }, "VestingSchedule": { "description": "This structure stores parameters for a specific vesting schedule", "type": "object", diff --git a/contracts/vesting-lp/schema/raw/migrate.json b/contracts/vesting-lp/schema/raw/migrate.json index 1b9dcecf..c8134b6c 100644 --- a/contracts/vesting-lp/schema/raw/migrate.json +++ b/contracts/vesting-lp/schema/raw/migrate.json @@ -3,5 +3,43 @@ "title": "MigrateMsg", "description": "This structure describes a migration message. We currently take no arguments for migrations.", "type": "object", - "additionalProperties": false + "required": [ + "cl_pair", + "max_slippage", + "new_lp_token", + "ntrn_denom", + "paired_denom", + "pcl_vesting", + "xyk_pair" + ], + "properties": { + "cl_pair": { + "type": "string" + }, + "max_slippage": { + "$ref": "#/definitions/Decimal" + }, + "new_lp_token": { + "type": "string" + }, + "ntrn_denom": { + "type": "string" + }, + "paired_denom": { + "type": "string" + }, + "pcl_vesting": { + "type": "string" + }, + "xyk_pair": { + "type": "string" + } + }, + "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" + } + } } diff --git a/contracts/vesting-lp/schema/vesting-lp.json b/contracts/vesting-lp/schema/vesting-lp.json index 395775f8..8a8f1873 100644 --- a/contracts/vesting-lp/schema/vesting-lp.json +++ b/contracts/vesting-lp/schema/vesting-lp.json @@ -34,31 +34,29 @@ "execute": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "ExecuteMsg", - "description": "This structure describes the execute messages available in a vesting contract.", "oneOf": [ { - "description": "Claim claims vested tokens and sends them to a recipient", "type": "object", "required": [ - "claim" + "base" ], "properties": { - "claim": { + "base": { + "$ref": "#/definitions/ExecuteMsg" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "migrate_liquidity_to_pcl_pool" + ], + "properties": { + "migrate_liquidity_to_pcl_pool": { "type": "object", "properties": { - "amount": { - "description": "The amount of tokens to claim", - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "recipient": { - "description": "The address that receives the vested tokens", + "user": { "type": [ "string", "null" @@ -71,213 +69,369 @@ "additionalProperties": false }, { - "description": "Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template", + "description": "Callbacks; only callable by the contract itself.", "type": "object", "required": [ - "receive" + "callback" ], "properties": { - "receive": { - "$ref": "#/definitions/Cw20ReceiveMsg" + "callback": { + "$ref": "#/definitions/CallbackMsg" } }, "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" }, - { - "description": "RegisterVestingAccounts registers vesting targets/accounts", - "type": "object", - "required": [ - "register_vesting_accounts" - ], - "properties": { - "register_vesting_accounts": { + "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": [ - "vesting_accounts" + "token" ], "properties": { - "vesting_accounts": { - "type": "array", - "items": { - "$ref": "#/definitions/VestingAccount" - } + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false } }, "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Creates a request to change contract ownership ## Executor Only the current owner can execute this", - "type": "object", - "required": [ - "propose_new_owner" - ], - "properties": { - "propose_new_owner": { + }, + { + "description": "Native token", "type": "object", "required": [ - "expires_in", - "owner" + "native_token" ], "properties": { - "expires_in": { - "description": "The validity period of the offer to change the owner", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "owner": { - "description": "The newly proposed owner", - "type": "string" + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false } }, "additionalProperties": false } - }, - "additionalProperties": false + ] }, - { - "description": "Removes a request to change contract ownership ## Executor Only the current owner can execute this", - "type": "object", - "required": [ - "drop_ownership_proposal" - ], - "properties": { - "drop_ownership_proposal": { + "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" + }, + "CallbackMsg": { + "oneOf": [ + { "type": "object", + "required": [ + "migrate_liquidity_to_cl_pair" + ], + "properties": { + "migrate_liquidity_to_cl_pair": { + "type": "object", + "required": [ + "amount", + "cl_pair", + "ntrn_denom", + "paired_asset_denom", + "slippage_tolerance", + "user", + "xyk_lp_token", + "xyk_pair" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + }, + "cl_pair": { + "$ref": "#/definitions/Addr" + }, + "ntrn_denom": { + "type": "string" + }, + "paired_asset_denom": { + "type": "string" + }, + "slippage_tolerance": { + "$ref": "#/definitions/Decimal" + }, + "user": { + "$ref": "#/definitions/VestingAccountResponse" + }, + "xyk_lp_token": { + "$ref": "#/definitions/Addr" + }, + "xyk_pair": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "provide_liquidity_to_cl_pair_after_withdrawal" + ], + "properties": { + "provide_liquidity_to_cl_pair_after_withdrawal": { + "type": "object", + "required": [ + "cl_pair", + "ntrn_denom", + "ntrn_init_balance", + "paired_asset_denom", + "paired_asset_init_balance", + "slippage_tolerance", + "user" + ], + "properties": { + "cl_pair": { + "$ref": "#/definitions/Addr" + }, + "ntrn_denom": { + "type": "string" + }, + "ntrn_init_balance": { + "$ref": "#/definitions/Uint128" + }, + "paired_asset_denom": { + "type": "string" + }, + "paired_asset_init_balance": { + "$ref": "#/definitions/Uint128" + }, + "slippage_tolerance": { + "$ref": "#/definitions/Decimal" + }, + "user": { + "$ref": "#/definitions/VestingAccountResponse" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "post_migration_vesting_reschedule" + ], + "properties": { + "post_migration_vesting_reschedule": { + "type": "object", + "required": [ + "user" + ], + "properties": { + "user": { + "$ref": "#/definitions/VestingAccountResponse" + } + }, + "additionalProperties": false + } + }, "additionalProperties": false } - }, - "additionalProperties": false + ] }, - { - "description": "Claims contract ownership ## Executor Only the newly proposed owner can execute this", + "Cw20ReceiveMsg": { + "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", "type": "object", "required": [ - "claim_ownership" + "amount", + "msg", + "sender" ], "properties": { - "claim_ownership": { - "type": "object", - "additionalProperties": false + "amount": { + "$ref": "#/definitions/Uint128" + }, + "msg": { + "$ref": "#/definitions/Binary" + }, + "sender": { + "type": "string" } }, "additionalProperties": false }, - { - "description": "Sets vesting token ## Executor Only the current owner or token info manager can execute this", - "type": "object", - "required": [ - "set_vesting_token" - ], - "properties": { - "set_vesting_token": { + "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" + }, + "ExecuteMsg": { + "description": "This structure describes the execute messages available in a vesting contract.", + "oneOf": [ + { + "description": "Claim claims vested tokens and sends them to a recipient", "type": "object", "required": [ - "vesting_token" + "claim" ], "properties": { - "vesting_token": { - "$ref": "#/definitions/AssetInfo" + "claim": { + "type": "object", + "properties": { + "amount": { + "description": "The amount of tokens to claim", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "recipient": { + "description": "The address that receives the vested tokens", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false } }, "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Contains messages associated with the managed extension for vesting contracts.", - "type": "object", - "required": [ - "managed_extension" - ], - "properties": { - "managed_extension": { + }, + { + "description": "Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template", "type": "object", "required": [ - "msg" + "receive" ], "properties": { - "msg": { - "$ref": "#/definitions/ExecuteMsgManaged" + "receive": { + "$ref": "#/definitions/Cw20ReceiveMsg" } }, "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Contains messages associated with the with_managers extension for vesting contracts.", - "type": "object", - "required": [ - "with_managers_extension" - ], - "properties": { - "with_managers_extension": { + }, + { + "description": "RegisterVestingAccounts registers vesting targets/accounts", "type": "object", "required": [ - "msg" + "register_vesting_accounts" ], "properties": { - "msg": { - "$ref": "#/definitions/ExecuteMsgWithManagers" + "register_vesting_accounts": { + "type": "object", + "required": [ + "vesting_accounts" + ], + "properties": { + "vesting_accounts": { + "type": "array", + "items": { + "$ref": "#/definitions/VestingAccount" + } + } + }, + "additionalProperties": false } }, "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Contains messages associated with the historical extension for vesting contracts.", - "type": "object", - "required": [ - "historical_extension" - ], - "properties": { - "historical_extension": { + }, + { + "description": "Creates a request to change contract ownership ## Executor Only the current owner can execute this", "type": "object", "required": [ - "msg" + "propose_new_owner" ], "properties": { - "msg": { - "$ref": "#/definitions/ExecuteMsgHistorical" + "propose_new_owner": { + "type": "object", + "required": [ + "expires_in", + "owner" + ], + "properties": { + "expires_in": { + "description": "The validity period of the offer to change the owner", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "owner": { + "description": "The newly proposed owner", + "type": "string" + } + }, + "additionalProperties": false } }, "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" - }, - "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", + "description": "Removes a request to change contract ownership ## Executor Only the current owner can execute this", "type": "object", "required": [ - "token" + "drop_ownership_proposal" ], "properties": { - "token": { + "drop_ownership_proposal": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Claims contract ownership ## Executor Only the newly proposed owner can execute this", + "type": "object", + "required": [ + "claim_ownership" + ], + "properties": { + "claim_ownership": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Sets vesting token ## Executor Only the current owner or token info manager can execute this", + "type": "object", + "required": [ + "set_vesting_token" + ], + "properties": { + "set_vesting_token": { "type": "object", "required": [ - "contract_addr" + "vesting_token" ], "properties": { - "contract_addr": { - "$ref": "#/definitions/Addr" + "vesting_token": { + "$ref": "#/definitions/AssetInfo" } }, "additionalProperties": false @@ -286,53 +440,72 @@ "additionalProperties": false }, { - "description": "Native token", + "description": "Contains messages associated with the managed extension for vesting contracts.", "type": "object", "required": [ - "native_token" + "managed_extension" ], "properties": { - "native_token": { + "managed_extension": { "type": "object", "required": [ - "denom" + "msg" ], "properties": { - "denom": { - "type": "string" + "msg": { + "$ref": "#/definitions/ExecuteMsgManaged" } }, "additionalProperties": false } }, "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" - }, - "Cw20ReceiveMsg": { - "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", - "type": "object", - "required": [ - "amount", - "msg", - "sender" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" }, - "msg": { - "$ref": "#/definitions/Binary" + { + "description": "Contains messages associated with the with_managers extension for vesting contracts.", + "type": "object", + "required": [ + "with_managers_extension" + ], + "properties": { + "with_managers_extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/ExecuteMsgWithManagers" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false }, - "sender": { - "type": "string" + { + "description": "Contains messages associated with the historical extension for vesting contracts.", + "type": "object", + "required": [ + "historical_extension" + ], + "properties": { + "historical_extension": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/ExecuteMsgHistorical" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } - }, - "additionalProperties": false + ] }, "ExecuteMsgHistorical": { "description": "This structure describes the execute messages available in a historical vesting contract.", @@ -455,6 +628,59 @@ }, "additionalProperties": false }, + "VestingAccountResponse": { + "description": "This structure describes a custom struct used to return vesting data about a specific vesting target.", + "type": "object", + "required": [ + "address", + "info" + ], + "properties": { + "address": { + "description": "The address that's vesting tokens", + "allOf": [ + { + "$ref": "#/definitions/Addr" + } + ] + }, + "info": { + "description": "Vesting information", + "allOf": [ + { + "$ref": "#/definitions/VestingInfo" + } + ] + } + }, + "additionalProperties": false + }, + "VestingInfo": { + "description": "This structure stores parameters for a batch of vesting schedules.", + "type": "object", + "required": [ + "released_amount", + "schedules" + ], + "properties": { + "released_amount": { + "description": "The total amount of vested tokens already claimed", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "schedules": { + "description": "The vesting schedules", + "type": "array", + "items": { + "$ref": "#/definitions/VestingSchedule" + } + } + }, + "additionalProperties": false + }, "VestingSchedule": { "description": "This structure stores parameters for a specific vesting schedule", "type": "object", @@ -806,7 +1032,45 @@ "title": "MigrateMsg", "description": "This structure describes a migration message. We currently take no arguments for migrations.", "type": "object", - "additionalProperties": false + "required": [ + "cl_pair", + "max_slippage", + "new_lp_token", + "ntrn_denom", + "paired_denom", + "pcl_vesting", + "xyk_pair" + ], + "properties": { + "cl_pair": { + "type": "string" + }, + "max_slippage": { + "$ref": "#/definitions/Decimal" + }, + "new_lp_token": { + "type": "string" + }, + "ntrn_denom": { + "type": "string" + }, + "paired_denom": { + "type": "string" + }, + "pcl_vesting": { + "type": "string" + }, + "xyk_pair": { + "type": "string" + } + }, + "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" + } + } }, "sudo": null, "responses": { From 7b67a1d4b392e8fd0e01e88a16027c198b65777e Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Wed, 14 Feb 2024 07:35:55 -0300 Subject: [PATCH 10/30] fmt --- contracts/vesting-lp-pcl/examples/vesting-lp_schema.rs | 2 +- contracts/vesting-lp/examples/vesting-lp_schema.rs | 4 ++-- contracts/vesting-lp/examples/vesting_schema.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/vesting-lp-pcl/examples/vesting-lp_schema.rs b/contracts/vesting-lp-pcl/examples/vesting-lp_schema.rs index 6f61959b..186bbaf1 100644 --- a/contracts/vesting-lp-pcl/examples/vesting-lp_schema.rs +++ b/contracts/vesting-lp-pcl/examples/vesting-lp_schema.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::write_api; use vesting_base::msg::{MigrateMsg, QueryMsg}; -use vesting_lp_pcl::msg::{InstantiateMsg,ExecuteMsg}; +use vesting_lp_pcl::msg::{ExecuteMsg, InstantiateMsg}; fn main() { write_api! { diff --git a/contracts/vesting-lp/examples/vesting-lp_schema.rs b/contracts/vesting-lp/examples/vesting-lp_schema.rs index 9f654a21..821d12fc 100644 --- a/contracts/vesting-lp/examples/vesting-lp_schema.rs +++ b/contracts/vesting-lp/examples/vesting-lp_schema.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::write_api; -use vesting_lp::msg::{ExecuteMsg, MigrateMsg, InstantiateMsg}; -use vesting_base::msg:: QueryMsg; +use vesting_base::msg::QueryMsg; +use vesting_lp::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg}; fn main() { write_api! { diff --git a/contracts/vesting-lp/examples/vesting_schema.rs b/contracts/vesting-lp/examples/vesting_schema.rs index 9be67c43..821d12fc 100644 --- a/contracts/vesting-lp/examples/vesting_schema.rs +++ b/contracts/vesting-lp/examples/vesting_schema.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::write_api; -use vesting_lp::msg::{ExecuteMsg, MigrateMsg, InstantiateMsg}; use vesting_base::msg::QueryMsg; +use vesting_lp::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg}; fn main() { write_api! { From 221bbe3e52d60ea59b7348886f69eb48d3db2d15 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Thu, 15 Feb 2024 07:21:03 -0300 Subject: [PATCH 11/30] some minor fixes --- contracts/vesting-lp-pcl/src/contract.rs | 10 -------- contracts/vesting-lp/src/contract.rs | 31 +++++++++++++++--------- contracts/vesting-lp/src/msg.rs | 1 + 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/contracts/vesting-lp-pcl/src/contract.rs b/contracts/vesting-lp-pcl/src/contract.rs index d4612947..5ab97874 100644 --- a/contracts/vesting-lp-pcl/src/contract.rs +++ b/contracts/vesting-lp-pcl/src/contract.rs @@ -150,16 +150,6 @@ fn handle_migrate_xyk_liquidity( vesting_info.save(deps.storage, account_address, &user_vesting_info, height)?; - let mut to_deposit = Uint128::zero(); - for sch in &user_vesting_info.schedules { - let amount = if let Some(end_point) = &sch.end_point { - end_point.amount - } else { - sch.start_point.amount - }; - to_deposit = to_deposit.checked_add(amount)?; - } - vesting_state(config.extensions.historical).update::<_, ContractError>( deps.storage, height, diff --git a/contracts/vesting-lp/src/contract.rs b/contracts/vesting-lp/src/contract.rs index b06d12f6..c9780c61 100644 --- a/contracts/vesting-lp/src/contract.rs +++ b/contracts/vesting-lp/src/contract.rs @@ -201,8 +201,8 @@ fn _handle_callback( slippage_tolerance, user, ), - CallbackMsg::PostMigrationVestingReschedule { user } => { - post_migration_vesting_reschedule_callback(deps, env, &user) + CallbackMsg::PostMigrationVestingReschedule { user, init_balance_pcl_lp } => { + post_migration_vesting_reschedule_callback(deps, env, &user, init_balance_pcl_lp) } } } @@ -298,6 +298,16 @@ fn provide_liquidity_to_cl_pair_after_withdrawal_callback( let mut msgs: Vec = vec![]; + let migration_config: XykToClMigrationConfig = XYK_TO_CL_MIGRATION_CONFIG.load(deps.storage)?; + + let balance_response: BalanceResponse = deps.querier.query_wasm_smart( + &migration_config.new_lp_token, + &Cw20QueryMsg::Balance { + address: env.contract.address.to_string(), + }, + )?; + let current_balance = balance_response.balance; + if !withdrawn_ntrn_amount.is_zero() && !withdrawn_paired_asset_amount.is_zero() { msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: cl_pair_address.to_string(), @@ -317,7 +327,7 @@ fn provide_liquidity_to_cl_pair_after_withdrawal_callback( })) } - msgs.push(CallbackMsg::PostMigrationVestingReschedule { user }.to_cosmos_msg(&env)?); + msgs.push(CallbackMsg::PostMigrationVestingReschedule { user, init_balance_pcl_lp:current_balance }.to_cosmos_msg(&env)?); Ok(Response::default().add_messages(msgs)) } @@ -326,6 +336,7 @@ fn post_migration_vesting_reschedule_callback( deps: DepsMut, env: Env, user: &VestingAccountResponse, + init_balance_pcl_lp: Uint128 ) -> Result { let config = CONFIG.load(deps.storage)?; let migration_config: XykToClMigrationConfig = XYK_TO_CL_MIGRATION_CONFIG.load(deps.storage)?; @@ -335,19 +346,17 @@ fn post_migration_vesting_reschedule_callback( address: env.contract.address.to_string(), }, )?; - let current_balance = balance_response.balance; + let current_balance = balance_response.balance.checked_sub(init_balance_pcl_lp)?; let schedule = user.info.schedules.last().unwrap(); - let new_end_point; - if let Some(end_point) = &schedule.end_point { - new_end_point = Option::from(VestingSchedulePoint { + let new_end_point = match &schedule.end_point { + Some(end_point) => Option::from(VestingSchedulePoint { time: end_point.time, amount: current_balance, - }) - } else { - new_end_point = None - } + }), + None => None, + }; let new_schedule = VestingSchedule { start_point: VestingSchedulePoint { diff --git a/contracts/vesting-lp/src/msg.rs b/contracts/vesting-lp/src/msg.rs index 717b9985..5aa6bcdc 100644 --- a/contracts/vesting-lp/src/msg.rs +++ b/contracts/vesting-lp/src/msg.rs @@ -48,6 +48,7 @@ pub enum CallbackMsg { }, PostMigrationVestingReschedule { user: VestingAccountResponse, + init_balance_pcl_lp: Uint128, }, } From a5b500e18f3ef4fe6018c498dcfbb4da3c5c00e4 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Thu, 15 Feb 2024 07:27:21 -0300 Subject: [PATCH 12/30] satisfy clippy --- contracts/vesting-lp/src/contract.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/contracts/vesting-lp/src/contract.rs b/contracts/vesting-lp/src/contract.rs index c9780c61..d92c653c 100644 --- a/contracts/vesting-lp/src/contract.rs +++ b/contracts/vesting-lp/src/contract.rs @@ -201,9 +201,10 @@ fn _handle_callback( slippage_tolerance, user, ), - CallbackMsg::PostMigrationVestingReschedule { user, init_balance_pcl_lp } => { - post_migration_vesting_reschedule_callback(deps, env, &user, init_balance_pcl_lp) - } + CallbackMsg::PostMigrationVestingReschedule { + user, + init_balance_pcl_lp, + } => post_migration_vesting_reschedule_callback(deps, env, &user, init_balance_pcl_lp), } } @@ -301,7 +302,7 @@ fn provide_liquidity_to_cl_pair_after_withdrawal_callback( let migration_config: XykToClMigrationConfig = XYK_TO_CL_MIGRATION_CONFIG.load(deps.storage)?; let balance_response: BalanceResponse = deps.querier.query_wasm_smart( - &migration_config.new_lp_token, + migration_config.new_lp_token, &Cw20QueryMsg::Balance { address: env.contract.address.to_string(), }, @@ -327,7 +328,13 @@ fn provide_liquidity_to_cl_pair_after_withdrawal_callback( })) } - msgs.push(CallbackMsg::PostMigrationVestingReschedule { user, init_balance_pcl_lp:current_balance }.to_cosmos_msg(&env)?); + msgs.push( + CallbackMsg::PostMigrationVestingReschedule { + user, + init_balance_pcl_lp: current_balance, + } + .to_cosmos_msg(&env)?, + ); Ok(Response::default().add_messages(msgs)) } @@ -336,7 +343,7 @@ fn post_migration_vesting_reschedule_callback( deps: DepsMut, env: Env, user: &VestingAccountResponse, - init_balance_pcl_lp: Uint128 + init_balance_pcl_lp: Uint128, ) -> Result { let config = CONFIG.load(deps.storage)?; let migration_config: XykToClMigrationConfig = XYK_TO_CL_MIGRATION_CONFIG.load(deps.storage)?; From d701c7072dcbfbbe0506527b9d310c7d735527d0 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Thu, 15 Feb 2024 08:28:51 -0300 Subject: [PATCH 13/30] rewrite to from trait --- contracts/vesting-lp-pcl/Cargo.toml | 4 +- contracts/vesting-lp/Cargo.toml | 2 +- contracts/vesting-lp/src/contract.rs | 10 ++-- contracts/vesting-lp/src/lib.rs | 2 + contracts/vesting-lp/src/msg.rs | 77 +++++++++++++++++++++++++--- 5 files changed, 81 insertions(+), 14 deletions(-) diff --git a/contracts/vesting-lp-pcl/Cargo.toml b/contracts/vesting-lp-pcl/Cargo.toml index 55ec4334..2be7868f 100644 --- a/contracts/vesting-lp-pcl/Cargo.toml +++ b/contracts/vesting-lp-pcl/Cargo.toml @@ -16,8 +16,8 @@ library = [] [dependencies] cw2 = { workspace = true } -cw20 = { workspace = true } -astroport = { workspace = true } +cw20 = { version = "0.15" } +astroport = { path= "../../packages/astroport"} vesting-base = {path = "../../packages/vesting-base"} cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } diff --git a/contracts/vesting-lp/Cargo.toml b/contracts/vesting-lp/Cargo.toml index 1153e1a0..c1f74ca9 100644 --- a/contracts/vesting-lp/Cargo.toml +++ b/contracts/vesting-lp/Cargo.toml @@ -16,7 +16,7 @@ library = [] cw2 = { version = "0.15" } vesting-base = {path = "../../packages/vesting-base"} vesting-lp-pcl = {path = "../vesting-lp-pcl"} -astroport = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v2.8.0" } +astroport = { path = "../../packages/astroport" } cosmwasm-schema = { version = "1.1", default-features = false } cosmwasm-std = { version = "1.1" } cw-storage-plus = "0.15" diff --git a/contracts/vesting-lp/src/contract.rs b/contracts/vesting-lp/src/contract.rs index d92c653c..e7fc48ba 100644 --- a/contracts/vesting-lp/src/contract.rs +++ b/contracts/vesting-lp/src/contract.rs @@ -14,7 +14,7 @@ use vesting_base::builder::VestingBaseBuilder; use vesting_base::error::ContractError; use vesting_base::handlers::execute as base_execute; use vesting_base::handlers::query as base_query; -use vesting_base::msg::QueryMsg; +use vesting_base::msg::{ExecuteMsg as BaseExecute, QueryMsg}; use vesting_base::state::{vesting_info, vesting_state, CONFIG}; use vesting_base::types::{ VestingAccountResponse, VestingInfo, VestingSchedule, VestingSchedulePoint, @@ -51,14 +51,14 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { - ExecuteMsg::Base(base_msg) => { - // Delegate handling of the original message types to the base execute function - base_execute(deps, env, info, base_msg) - } ExecuteMsg::MigrateLiquidityToPCLPool { user } => { execute_migrate_liquidity(deps, info, env, None, user) } ExecuteMsg::Callback(msg) => _handle_callback(deps, env, info, msg), + _ => { + let base_msg: BaseExecute = msg.into(); + base_execute(deps, env, info, base_msg) + } } } diff --git a/contracts/vesting-lp/src/lib.rs b/contracts/vesting-lp/src/lib.rs index 4934c19d..172c45a6 100644 --- a/contracts/vesting-lp/src/lib.rs +++ b/contracts/vesting-lp/src/lib.rs @@ -1,3 +1,5 @@ +extern crate core; + pub mod contract; pub mod msg; pub mod state; diff --git a/contracts/vesting-lp/src/msg.rs b/contracts/vesting-lp/src/msg.rs index 5aa6bcdc..7876a6f8 100644 --- a/contracts/vesting-lp/src/msg.rs +++ b/contracts/vesting-lp/src/msg.rs @@ -1,7 +1,11 @@ +use astroport::asset::AssetInfo; use cosmwasm_schema::cw_serde; use cosmwasm_std::{to_binary, Addr, CosmosMsg, Decimal, Env, StdResult, Uint128, WasmMsg}; -use vesting_base::msg::ExecuteMsg as BaseExecute; -use vesting_base::types::VestingAccountResponse; +use cw20::Cw20ReceiveMsg; +use vesting_base::msg::{ + ExecuteMsg as BaseExecute, ExecuteMsgHistorical, ExecuteMsgManaged, ExecuteMsgWithManagers, +}; +use vesting_base::types::{VestingAccount, VestingAccountResponse}; /// This structure describes the parameters used for creating a contract. #[cw_serde] @@ -16,11 +20,48 @@ pub struct InstantiateMsg { #[cw_serde] pub enum ExecuteMsg { - Base(BaseExecute), - #[serde(rename = "migrate_liquidity_to_pcl_pool")] - MigrateLiquidityToPCLPool { - user: Option, + /// Claim claims vested tokens and sends them to a recipient + Claim { + /// The address that receives the vested tokens + recipient: Option, + /// The amount of tokens to claim + amount: Option, + }, + /// Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template + Receive(Cw20ReceiveMsg), + /// RegisterVestingAccounts registers vesting targets/accounts + RegisterVestingAccounts { + vesting_accounts: Vec, }, + /// Creates a request to change contract ownership + /// ## Executor + /// Only the current owner can execute this + ProposeNewOwner { + /// The newly proposed owner + owner: String, + /// The validity period of the offer to change the owner + expires_in: u64, + }, + /// Removes a request to change contract ownership + /// ## Executor + /// Only the current owner can execute this + DropOwnershipProposal {}, + /// Claims contract ownership + /// ## Executor + /// Only the newly proposed owner can execute this + ClaimOwnership {}, + /// Sets vesting token + /// ## Executor + /// Only the current owner or token info manager can execute this + SetVestingToken { vesting_token: AssetInfo }, + /// Contains messages associated with the managed extension for vesting contracts. + ManagedExtension { msg: ExecuteMsgManaged }, + /// Contains messages associated with the with_managers extension for vesting contracts. + WithManagersExtension { msg: ExecuteMsgWithManagers }, + /// Contains messages associated with the historical extension for vesting contracts. + HistoricalExtension { msg: ExecuteMsgHistorical }, + #[serde(rename = "migrate_liquidity_to_pcl_pool")] + MigrateLiquidityToPCLPool { user: Option }, /// Callbacks; only callable by the contract itself. Callback(CallbackMsg), } @@ -77,3 +118,27 @@ pub struct MigrateMsg { pub new_lp_token: String, pub pcl_vesting: String, } + +impl From for BaseExecute { + fn from(item: ExecuteMsg) -> Self { + match item { + ExecuteMsg::Claim { recipient, amount } => BaseExecute::Claim { recipient, amount }, + ExecuteMsg::Receive(msg) => BaseExecute::Receive(msg), + ExecuteMsg::RegisterVestingAccounts { vesting_accounts } => { + BaseExecute::RegisterVestingAccounts { vesting_accounts } + } + ExecuteMsg::ProposeNewOwner { owner, expires_in } => { + BaseExecute::ProposeNewOwner { owner, expires_in } + } + ExecuteMsg::DropOwnershipProposal {} => BaseExecute::DropOwnershipProposal {}, + ExecuteMsg::ClaimOwnership {} => BaseExecute::ClaimOwnership {}, + ExecuteMsg::SetVestingToken { vesting_token } => { + BaseExecute::SetVestingToken { vesting_token } + } + ExecuteMsg::ManagedExtension { msg } => BaseExecute::ManagedExtension { msg }, + ExecuteMsg::WithManagersExtension { msg } => BaseExecute::WithManagersExtension { msg }, + ExecuteMsg::HistoricalExtension { msg } => BaseExecute::HistoricalExtension { msg }, + _ => panic!("Unhandled ExecuteMsg variant"), + } + } +} From ebe9df6e91ed7f3ace5d54a765266552ad114871 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Thu, 15 Feb 2024 08:31:23 -0300 Subject: [PATCH 14/30] sync deps --- Cargo.lock | 20 +++----------------- packages/vesting-base/Cargo.toml | 4 ++-- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d3d8d135..345579ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,20 +59,6 @@ dependencies = [ "uint", ] -[[package]] -name = "astroport" -version = "2.8.0" -source = "git+https://github.com/astroport-fi/astroport-core.git?tag=v2.8.0#3b44a4044b823a145730f66ffaf7ae4205b2cd35" -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-factory" version = "1.5.0" @@ -1506,7 +1492,7 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus 1.1.0", "cw-utils 0.15.1", - "cw20 1.1.1", + "cw20 0.15.1", "thiserror", ] @@ -1529,7 +1515,7 @@ dependencies = [ name = "vesting-lp" version = "1.1.0" dependencies = [ - "astroport 2.8.0", + "astroport 2.0.0", "astroport-token", "cosmwasm-schema", "cosmwasm-std", @@ -1554,7 +1540,7 @@ dependencies = [ "cw-storage-plus 0.15.1", "cw-utils 0.15.1", "cw2 1.1.1", - "cw20 1.1.1", + "cw20 0.15.1", "vesting-base", ] diff --git a/packages/vesting-base/Cargo.toml b/packages/vesting-base/Cargo.toml index ffe8e644..f533e362 100644 --- a/packages/vesting-base/Cargo.toml +++ b/packages/vesting-base/Cargo.toml @@ -13,10 +13,10 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -cw20 = { workspace = true } +cw20 = { version = "0.15" } cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } -astroport = { workspace = true } +astroport = { path = "../../packages/astroport" } thiserror = { workspace = true } # we keep it at 0.15 instead of latest version just for vesting investors contract cw-utils = "0.15" From b4309bac82f3a044970857b4d2f03f4a1aa5a175 Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Thu, 15 Feb 2024 15:49:57 +0300 Subject: [PATCH 15/30] add missing migrate entry point for vesting lp --- contracts/vesting-lp/src/contract.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/vesting-lp/src/contract.rs b/contracts/vesting-lp/src/contract.rs index e7fc48ba..3bb86f82 100644 --- a/contracts/vesting-lp/src/contract.rs +++ b/contracts/vesting-lp/src/contract.rs @@ -69,6 +69,7 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { } /// Manages contract migration. +#[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result { XYK_TO_CL_MIGRATION_CONFIG.save( deps.storage, From c75163bd7875f727f038367c325e325413f502e5 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Thu, 15 Feb 2024 09:57:51 -0300 Subject: [PATCH 16/30] refactor pcl vesting --- contracts/vesting-lp-pcl/src/contract.rs | 8 +-- contracts/vesting-lp-pcl/src/msg.rs | 80 +++++++++++++++++++++++- 2 files changed, 81 insertions(+), 7 deletions(-) diff --git a/contracts/vesting-lp-pcl/src/contract.rs b/contracts/vesting-lp-pcl/src/contract.rs index 5ab97874..7c4b3f1b 100644 --- a/contracts/vesting-lp-pcl/src/contract.rs +++ b/contracts/vesting-lp-pcl/src/contract.rs @@ -12,7 +12,7 @@ use vesting_base::handlers::{ assert_vesting_schedules, get_vesting_token, register_vesting_accounts, }; use vesting_base::handlers::{execute as base_execute, query as base_query}; -use vesting_base::msg::QueryMsg; +use vesting_base::msg::{ExecuteMsg as BaseExecute, QueryMsg}; use vesting_base::state::{vesting_info, vesting_state, CONFIG, VESTING_MANAGERS}; use vesting_base::types::{Config, Extensions, VestingInfo}; @@ -67,11 +67,11 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { - ExecuteMsg::Base(base_msg) => { - // Delegate handling of the original message types to the base execute function + ExecuteMsg::Receive(msg) => receive_cw20(deps, env, info, msg), + _ => { + let base_msg: BaseExecute = msg.into(); base_execute(deps, env, info, base_msg) } - ExecuteMsg::Receive(msg) => receive_cw20(deps, env, info, msg), } } diff --git a/contracts/vesting-lp-pcl/src/msg.rs b/contracts/vesting-lp-pcl/src/msg.rs index 77df5e01..e20c443e 100644 --- a/contracts/vesting-lp-pcl/src/msg.rs +++ b/contracts/vesting-lp-pcl/src/msg.rs @@ -1,8 +1,10 @@ use astroport::asset::AssetInfo; use cosmwasm_schema::cw_serde; -use cosmwasm_std::Addr; +use cosmwasm_std::{Addr, Uint128}; use cw20::Cw20ReceiveMsg; -use vesting_base::msg::ExecuteMsg as BaseExecute; +use vesting_base::msg::{ + ExecuteMsg as BaseExecute, ExecuteMsgHistorical, ExecuteMsgManaged, ExecuteMsgWithManagers, +}; use vesting_base::types::{VestingAccount, VestingInfo}; /// This structure describes the parameters used for creating a contract. @@ -20,7 +22,56 @@ pub struct InstantiateMsg { #[cw_serde] pub enum ExecuteMsg { - Base(BaseExecute), + // Claim claims vested tokens and sends them to a recipient + Claim { + /// The address that receives the vested tokens + recipient: Option, + /// The amount of tokens to claim + amount: Option, + }, + /// RegisterVestingAccounts registers vesting targets/accounts + RegisterVestingAccounts { + vesting_accounts: Vec, + }, + /// Creates a request to change contract ownership + /// ## Executor + /// Only the current owner can execute this + ProposeNewOwner { + /// The newly proposed owner + owner: String, + /// The validity period of the offer to change the owner + expires_in: u64, + }, + /// Removes a request to change contract ownership + /// ## Executor + /// Only the current owner can execute this + DropOwnershipProposal {}, + /// Claims contract ownership + /// ## Executor + /// Only the newly proposed owner can execute this + ClaimOwnership {}, + /// Sets vesting token + /// ## Executor + /// Only the current owner or token info manager can execute this + SetVestingToken { + vesting_token: AssetInfo, + }, + /// Contains messages associated with the managed extension for vesting contracts. + ManagedExtension { + msg: ExecuteMsgManaged, + }, + /// Contains messages associated with the with_managers extension for vesting contracts. + WithManagersExtension { + msg: ExecuteMsgWithManagers, + }, + /// Contains messages associated with the historical extension for vesting contracts. + HistoricalExtension { + msg: ExecuteMsgHistorical, + }, + #[serde(rename = "migrate_liquidity_to_pcl_pool")] + MigrateLiquidityToPCLPool { + user: Option, + }, Receive(Cw20ReceiveMsg), } @@ -38,3 +89,26 @@ pub enum Cw20HookMsg { user_vesting_info: VestingInfo, }, } + +impl From for BaseExecute { + fn from(item: ExecuteMsg) -> Self { + match item { + ExecuteMsg::Claim { recipient, amount } => BaseExecute::Claim { recipient, amount }, + ExecuteMsg::RegisterVestingAccounts { vesting_accounts } => { + BaseExecute::RegisterVestingAccounts { vesting_accounts } + } + ExecuteMsg::ProposeNewOwner { owner, expires_in } => { + BaseExecute::ProposeNewOwner { owner, expires_in } + } + ExecuteMsg::DropOwnershipProposal {} => BaseExecute::DropOwnershipProposal {}, + ExecuteMsg::ClaimOwnership {} => BaseExecute::ClaimOwnership {}, + ExecuteMsg::SetVestingToken { vesting_token } => { + BaseExecute::SetVestingToken { vesting_token } + } + ExecuteMsg::ManagedExtension { msg } => BaseExecute::ManagedExtension { msg }, + ExecuteMsg::WithManagersExtension { msg } => BaseExecute::WithManagersExtension { msg }, + ExecuteMsg::HistoricalExtension { msg } => BaseExecute::HistoricalExtension { msg }, + _ => panic!("Unhandled ExecuteMsg variant"), + } + } +} From 06bc70b447e604d79ca6af70a8e4b45992ebe5eb Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Mon, 19 Feb 2024 07:29:14 -0300 Subject: [PATCH 17/30] return dust threshold --- contracts/vesting-lp/src/contract.rs | 19 ++++++++++++++++++- contracts/vesting-lp/src/msg.rs | 1 + contracts/vesting-lp/src/state.rs | 3 ++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/contracts/vesting-lp/src/contract.rs b/contracts/vesting-lp/src/contract.rs index 3bb86f82..e51db0ea 100644 --- a/contracts/vesting-lp/src/contract.rs +++ b/contracts/vesting-lp/src/contract.rs @@ -81,6 +81,7 @@ pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result for BaseExecute { diff --git a/contracts/vesting-lp/src/state.rs b/contracts/vesting-lp/src/state.rs index 9cf45b80..4036cd9c 100644 --- a/contracts/vesting-lp/src/state.rs +++ b/contracts/vesting-lp/src/state.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Decimal}; +use cosmwasm_std::{Addr, Decimal, Uint128}; use cw_storage_plus::Item; /// Config for xyk->CL liquidity migration. @@ -13,6 +13,7 @@ pub struct XykToClMigrationConfig { pub cl_pair: Addr, pub new_lp_token: Addr, pub pcl_vesting: Addr, + pub dust_threshold: Uint128, } pub const XYK_TO_CL_MIGRATION_CONFIG: Item = From efb80c1138fe9118314fd1670e0c49ab080c9c00 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Tue, 20 Feb 2024 09:39:19 -0300 Subject: [PATCH 18/30] cover fully payed acc migration --- contracts/vesting-lp/src/contract.rs | 34 ++++++++++++++++------------ 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/contracts/vesting-lp/src/contract.rs b/contracts/vesting-lp/src/contract.rs index e51db0ea..646434d2 100644 --- a/contracts/vesting-lp/src/contract.rs +++ b/contracts/vesting-lp/src/contract.rs @@ -402,23 +402,27 @@ fn post_migration_vesting_reschedule_callback( }, env.block.height, )?; - let msgs = vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: migration_config.new_lp_token.to_string(), - funds: vec![], - msg: to_binary(&Cw20ExecuteMsg::Send { - contract: migration_config.pcl_vesting.to_string(), - amount: current_balance, - msg: to_binary(&vesting_lp_pcl::msg::Cw20HookMsg::MigrateXYKLiquidity { - user_address_raw: user.address.clone(), - user_vesting_info: VestingInfo { - schedules: vec![new_schedule], - released_amount: Uint128::zero(), - }, + if !current_balance.is_zero() { + let msgs = vec![CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: migration_config.new_lp_token.to_string(), + funds: vec![], + msg: to_binary(&Cw20ExecuteMsg::Send { + contract: migration_config.pcl_vesting.to_string(), + amount: current_balance, + msg: to_binary(&vesting_lp_pcl::msg::Cw20HookMsg::MigrateXYKLiquidity { + user_address_raw: user.address.clone(), + user_vesting_info: VestingInfo { + schedules: vec![new_schedule], + released_amount: Uint128::zero(), + }, + })?, })?, - })?, - })]; + })]; - Ok(Response::new().add_messages(msgs)) + Ok(Response::new().add_messages(msgs)) + } else { + Ok(Response::new()) + } } fn compute_share(vesting_info: &VestingInfo) -> StdResult { From 674dc26d909390632c8f7babee6951837d466754 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Tue, 20 Feb 2024 09:57:23 -0300 Subject: [PATCH 19/30] optimize to save some gas --- contracts/vesting-lp/src/contract.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/contracts/vesting-lp/src/contract.rs b/contracts/vesting-lp/src/contract.rs index 646434d2..e5dff841 100644 --- a/contracts/vesting-lp/src/contract.rs +++ b/contracts/vesting-lp/src/contract.rs @@ -101,7 +101,8 @@ fn execute_migrate_liquidity( Some(val) => deps.api.addr_validate(&val)?, None => info.sender, }; - let info = vesting_info(config.extensions.historical).load(deps.storage, address.clone())?; + let vesting_info = vesting_info(config.extensions.historical); + let info = vesting_info.load(deps.storage, address.clone())?; let mut resp = Response::default(); let user = VestingAccountResponse { address, info }; @@ -143,6 +144,21 @@ fn execute_migrate_liquidity( user_share }; + // if there is nothing to migrate just update vi and quit. + // if there is only dust, than send it back to user, update vi and quit + if user_amount.is_zero() { + vesting_info.save( + deps.storage, + user.address.clone(), + &VestingInfo { + schedules: vec![], + released_amount: Uint128::zero(), + }, + env.block.height, + )?; + return Ok(resp); + } + if let Some(slippage_tolerance) = slippage_tolerance { if slippage_tolerance.gt(&migration_config.max_slippage) { return Err(ContractError::MigrationError {}); From 7af18fa2cc1dbae9249cd4a45bca4a07d4e304ec Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Thu, 22 Feb 2024 06:55:56 -0300 Subject: [PATCH 20/30] review cleaning --- contracts/auction/README.md | 2 +- .../vesting-lp-pcl/schema/raw/execute.json | 428 +++++++++--------- .../vesting-lp-pcl/schema/vesting-lp-pcl.json | 428 +++++++++--------- .../vesting-lp/examples/vesting_schema.rs | 12 - 4 files changed, 419 insertions(+), 451 deletions(-) delete mode 100644 contracts/vesting-lp/examples/vesting_schema.rs diff --git a/contracts/auction/README.md b/contracts/auction/README.md index b96204d5..20f115d3 100644 --- a/contracts/auction/README.md +++ b/contracts/auction/README.md @@ -5,7 +5,7 @@ The LP Bootstrap via auction contract facilitates cNTRN-NATIVE Neutron pool init **Phase 1 :: Bootstrapping cNTRN and NATIVE Side of the LP Pool** - Airdrop recipients and lockdrop participants can delegate part / all of their cNTRN rewards to the auction contract. -- Any _user can deposit UST directly to the auction contract to participate in the LP bootstrap auction. +- Any user can deposit UST directly to the auction contract to participate in the LP bootstrap auction. - Both UST deposited & cNTRN delegated (if any) balances are used to calculate _user's LP token shares and additional cNTRN incentives that he will receive for participating in the auction. **Phase 2 :: Post cNTRN-NATIVE Pool initialization** diff --git a/contracts/vesting-lp-pcl/schema/raw/execute.json b/contracts/vesting-lp-pcl/schema/raw/execute.json index 3a625610..51383681 100644 --- a/contracts/vesting-lp-pcl/schema/raw/execute.json +++ b/contracts/vesting-lp-pcl/schema/raw/execute.json @@ -5,255 +5,264 @@ { "type": "object", "required": [ - "base" + "claim" ], "properties": { - "base": { - "$ref": "#/definitions/ExecuteMsg" + "claim": { + "type": "object", + "properties": { + "amount": { + "description": "The amount of tokens to claim", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "recipient": { + "description": "The address that receives the vested tokens", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "RegisterVestingAccounts registers vesting targets/accounts", "type": "object", "required": [ - "receive" + "register_vesting_accounts" ], "properties": { - "receive": { - "$ref": "#/definitions/Cw20ReceiveMsg" - } - }, - "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" - }, - "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", + "register_vesting_accounts": { "type": "object", "required": [ - "token" + "vesting_accounts" ], "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "$ref": "#/definitions/Addr" - } - }, - "additionalProperties": false + "vesting_accounts": { + "type": "array", + "items": { + "$ref": "#/definitions/VestingAccount" + } } }, "additionalProperties": false - }, - { - "description": "Native token", + } + }, + "additionalProperties": false + }, + { + "description": "Creates a request to change contract ownership ## Executor Only the current owner can execute this", + "type": "object", + "required": [ + "propose_new_owner" + ], + "properties": { + "propose_new_owner": { "type": "object", "required": [ - "native_token" + "expires_in", + "owner" ], "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false + "expires_in": { + "description": "The validity period of the offer to change the owner", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "owner": { + "description": "The newly proposed owner", + "type": "string" } }, "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" + }, + "additionalProperties": false }, - "Cw20ReceiveMsg": { - "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", + { + "description": "Removes a request to change contract ownership ## Executor Only the current owner can execute this", "type": "object", "required": [ - "amount", - "msg", - "sender" + "drop_ownership_proposal" ], "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "msg": { - "$ref": "#/definitions/Binary" - }, - "sender": { - "type": "string" + "drop_ownership_proposal": { + "type": "object", + "additionalProperties": false } }, "additionalProperties": false }, - "ExecuteMsg": { - "description": "This structure describes the execute messages available in a vesting contract.", - "oneOf": [ - { - "description": "Claim claims vested tokens and sends them to a recipient", + { + "description": "Claims contract ownership ## Executor Only the newly proposed owner can execute this", + "type": "object", + "required": [ + "claim_ownership" + ], + "properties": { + "claim_ownership": { "type": "object", - "required": [ - "claim" - ], - "properties": { - "claim": { - "type": "object", - "properties": { - "amount": { - "description": "The amount of tokens to claim", - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "recipient": { - "description": "The address that receives the vested tokens", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, "additionalProperties": false - }, - { - "description": "Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template", + } + }, + "additionalProperties": false + }, + { + "description": "Sets vesting token ## Executor Only the current owner or token info manager can execute this", + "type": "object", + "required": [ + "set_vesting_token" + ], + "properties": { + "set_vesting_token": { "type": "object", "required": [ - "receive" + "vesting_token" ], "properties": { - "receive": { - "$ref": "#/definitions/Cw20ReceiveMsg" + "vesting_token": { + "$ref": "#/definitions/AssetInfo" } }, "additionalProperties": false - }, - { - "description": "RegisterVestingAccounts registers vesting targets/accounts", + } + }, + "additionalProperties": false + }, + { + "description": "Contains messages associated with the managed extension for vesting contracts.", + "type": "object", + "required": [ + "managed_extension" + ], + "properties": { + "managed_extension": { "type": "object", "required": [ - "register_vesting_accounts" + "msg" ], "properties": { - "register_vesting_accounts": { - "type": "object", - "required": [ - "vesting_accounts" - ], - "properties": { - "vesting_accounts": { - "type": "array", - "items": { - "$ref": "#/definitions/VestingAccount" - } - } - }, - "additionalProperties": false + "msg": { + "$ref": "#/definitions/ExecuteMsgManaged" } }, "additionalProperties": false - }, - { - "description": "Creates a request to change contract ownership ## Executor Only the current owner can execute this", + } + }, + "additionalProperties": false + }, + { + "description": "Contains messages associated with the with_managers extension for vesting contracts.", + "type": "object", + "required": [ + "with_managers_extension" + ], + "properties": { + "with_managers_extension": { "type": "object", "required": [ - "propose_new_owner" + "msg" ], "properties": { - "propose_new_owner": { - "type": "object", - "required": [ - "expires_in", - "owner" - ], - "properties": { - "expires_in": { - "description": "The validity period of the offer to change the owner", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "owner": { - "description": "The newly proposed owner", - "type": "string" - } - }, - "additionalProperties": false + "msg": { + "$ref": "#/definitions/ExecuteMsgWithManagers" } }, "additionalProperties": false - }, - { - "description": "Removes a request to change contract ownership ## Executor Only the current owner can execute this", + } + }, + "additionalProperties": false + }, + { + "description": "Contains messages associated with the historical extension for vesting contracts.", + "type": "object", + "required": [ + "historical_extension" + ], + "properties": { + "historical_extension": { "type": "object", "required": [ - "drop_ownership_proposal" + "msg" ], "properties": { - "drop_ownership_proposal": { - "type": "object", - "additionalProperties": false + "msg": { + "$ref": "#/definitions/ExecuteMsgHistorical" } }, "additionalProperties": false - }, - { - "description": "Claims contract ownership ## Executor Only the newly proposed owner can execute this", + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "migrate_liquidity_to_pcl_pool" + ], + "properties": { + "migrate_liquidity_to_pcl_pool": { "type": "object", - "required": [ - "claim_ownership" - ], "properties": { - "claim_ownership": { - "type": "object", - "additionalProperties": false + "user": { + "type": [ + "string", + "null" + ] } }, "additionalProperties": false - }, + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "receive" + ], + "properties": { + "receive": { + "$ref": "#/definitions/Cw20ReceiveMsg" + } + }, + "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" + }, + "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": "Sets vesting token ## Executor Only the current owner or token info manager can execute this", + "description": "Non-native Token", "type": "object", "required": [ - "set_vesting_token" + "token" ], "properties": { - "set_vesting_token": { + "token": { "type": "object", "required": [ - "vesting_token" + "contract_addr" ], "properties": { - "vesting_token": { - "$ref": "#/definitions/AssetInfo" + "contract_addr": { + "$ref": "#/definitions/Addr" } }, "additionalProperties": false @@ -262,72 +271,53 @@ "additionalProperties": false }, { - "description": "Contains messages associated with the managed extension for vesting contracts.", + "description": "Native token", "type": "object", "required": [ - "managed_extension" + "native_token" ], "properties": { - "managed_extension": { + "native_token": { "type": "object", "required": [ - "msg" + "denom" ], "properties": { - "msg": { - "$ref": "#/definitions/ExecuteMsgManaged" + "denom": { + "type": "string" } }, "additionalProperties": false } }, "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" + }, + "Cw20ReceiveMsg": { + "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", + "type": "object", + "required": [ + "amount", + "msg", + "sender" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" }, - { - "description": "Contains messages associated with the with_managers extension for vesting contracts.", - "type": "object", - "required": [ - "with_managers_extension" - ], - "properties": { - "with_managers_extension": { - "type": "object", - "required": [ - "msg" - ], - "properties": { - "msg": { - "$ref": "#/definitions/ExecuteMsgWithManagers" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false + "msg": { + "$ref": "#/definitions/Binary" }, - { - "description": "Contains messages associated with the historical extension for vesting contracts.", - "type": "object", - "required": [ - "historical_extension" - ], - "properties": { - "historical_extension": { - "type": "object", - "required": [ - "msg" - ], - "properties": { - "msg": { - "$ref": "#/definitions/ExecuteMsgHistorical" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false + "sender": { + "type": "string" } - ] + }, + "additionalProperties": false }, "ExecuteMsgHistorical": { "description": "This structure describes the execute messages available in a historical vesting contract.", diff --git a/contracts/vesting-lp-pcl/schema/vesting-lp-pcl.json b/contracts/vesting-lp-pcl/schema/vesting-lp-pcl.json index b3d8a16b..e9c4a3a0 100644 --- a/contracts/vesting-lp-pcl/schema/vesting-lp-pcl.json +++ b/contracts/vesting-lp-pcl/schema/vesting-lp-pcl.json @@ -101,255 +101,264 @@ { "type": "object", "required": [ - "base" + "claim" ], "properties": { - "base": { - "$ref": "#/definitions/ExecuteMsg" + "claim": { + "type": "object", + "properties": { + "amount": { + "description": "The amount of tokens to claim", + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, + "recipient": { + "description": "The address that receives the vested tokens", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false } }, "additionalProperties": false }, { + "description": "RegisterVestingAccounts registers vesting targets/accounts", "type": "object", "required": [ - "receive" + "register_vesting_accounts" ], "properties": { - "receive": { - "$ref": "#/definitions/Cw20ReceiveMsg" - } - }, - "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" - }, - "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", + "register_vesting_accounts": { "type": "object", "required": [ - "token" + "vesting_accounts" ], "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "$ref": "#/definitions/Addr" - } - }, - "additionalProperties": false + "vesting_accounts": { + "type": "array", + "items": { + "$ref": "#/definitions/VestingAccount" + } } }, "additionalProperties": false - }, - { - "description": "Native token", + } + }, + "additionalProperties": false + }, + { + "description": "Creates a request to change contract ownership ## Executor Only the current owner can execute this", + "type": "object", + "required": [ + "propose_new_owner" + ], + "properties": { + "propose_new_owner": { "type": "object", "required": [ - "native_token" + "expires_in", + "owner" ], "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false + "expires_in": { + "description": "The validity period of the offer to change the owner", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "owner": { + "description": "The newly proposed owner", + "type": "string" } }, "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" + }, + "additionalProperties": false }, - "Cw20ReceiveMsg": { - "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", + { + "description": "Removes a request to change contract ownership ## Executor Only the current owner can execute this", "type": "object", "required": [ - "amount", - "msg", - "sender" + "drop_ownership_proposal" ], "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "msg": { - "$ref": "#/definitions/Binary" - }, - "sender": { - "type": "string" + "drop_ownership_proposal": { + "type": "object", + "additionalProperties": false } }, "additionalProperties": false }, - "ExecuteMsg": { - "description": "This structure describes the execute messages available in a vesting contract.", - "oneOf": [ - { - "description": "Claim claims vested tokens and sends them to a recipient", + { + "description": "Claims contract ownership ## Executor Only the newly proposed owner can execute this", + "type": "object", + "required": [ + "claim_ownership" + ], + "properties": { + "claim_ownership": { "type": "object", - "required": [ - "claim" - ], - "properties": { - "claim": { - "type": "object", - "properties": { - "amount": { - "description": "The amount of tokens to claim", - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "recipient": { - "description": "The address that receives the vested tokens", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, "additionalProperties": false - }, - { - "description": "Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template", + } + }, + "additionalProperties": false + }, + { + "description": "Sets vesting token ## Executor Only the current owner or token info manager can execute this", + "type": "object", + "required": [ + "set_vesting_token" + ], + "properties": { + "set_vesting_token": { "type": "object", "required": [ - "receive" + "vesting_token" ], "properties": { - "receive": { - "$ref": "#/definitions/Cw20ReceiveMsg" + "vesting_token": { + "$ref": "#/definitions/AssetInfo" } }, "additionalProperties": false - }, - { - "description": "RegisterVestingAccounts registers vesting targets/accounts", + } + }, + "additionalProperties": false + }, + { + "description": "Contains messages associated with the managed extension for vesting contracts.", + "type": "object", + "required": [ + "managed_extension" + ], + "properties": { + "managed_extension": { "type": "object", "required": [ - "register_vesting_accounts" + "msg" ], "properties": { - "register_vesting_accounts": { - "type": "object", - "required": [ - "vesting_accounts" - ], - "properties": { - "vesting_accounts": { - "type": "array", - "items": { - "$ref": "#/definitions/VestingAccount" - } - } - }, - "additionalProperties": false + "msg": { + "$ref": "#/definitions/ExecuteMsgManaged" } }, "additionalProperties": false - }, - { - "description": "Creates a request to change contract ownership ## Executor Only the current owner can execute this", + } + }, + "additionalProperties": false + }, + { + "description": "Contains messages associated with the with_managers extension for vesting contracts.", + "type": "object", + "required": [ + "with_managers_extension" + ], + "properties": { + "with_managers_extension": { "type": "object", "required": [ - "propose_new_owner" + "msg" ], "properties": { - "propose_new_owner": { - "type": "object", - "required": [ - "expires_in", - "owner" - ], - "properties": { - "expires_in": { - "description": "The validity period of the offer to change the owner", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "owner": { - "description": "The newly proposed owner", - "type": "string" - } - }, - "additionalProperties": false + "msg": { + "$ref": "#/definitions/ExecuteMsgWithManagers" } }, "additionalProperties": false - }, - { - "description": "Removes a request to change contract ownership ## Executor Only the current owner can execute this", + } + }, + "additionalProperties": false + }, + { + "description": "Contains messages associated with the historical extension for vesting contracts.", + "type": "object", + "required": [ + "historical_extension" + ], + "properties": { + "historical_extension": { "type": "object", "required": [ - "drop_ownership_proposal" + "msg" ], "properties": { - "drop_ownership_proposal": { - "type": "object", - "additionalProperties": false + "msg": { + "$ref": "#/definitions/ExecuteMsgHistorical" } }, "additionalProperties": false - }, - { - "description": "Claims contract ownership ## Executor Only the newly proposed owner can execute this", + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "migrate_liquidity_to_pcl_pool" + ], + "properties": { + "migrate_liquidity_to_pcl_pool": { "type": "object", - "required": [ - "claim_ownership" - ], "properties": { - "claim_ownership": { - "type": "object", - "additionalProperties": false + "user": { + "type": [ + "string", + "null" + ] } }, "additionalProperties": false - }, + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "receive" + ], + "properties": { + "receive": { + "$ref": "#/definitions/Cw20ReceiveMsg" + } + }, + "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" + }, + "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": "Sets vesting token ## Executor Only the current owner or token info manager can execute this", + "description": "Non-native Token", "type": "object", "required": [ - "set_vesting_token" + "token" ], "properties": { - "set_vesting_token": { + "token": { "type": "object", "required": [ - "vesting_token" + "contract_addr" ], "properties": { - "vesting_token": { - "$ref": "#/definitions/AssetInfo" + "contract_addr": { + "$ref": "#/definitions/Addr" } }, "additionalProperties": false @@ -358,72 +367,53 @@ "additionalProperties": false }, { - "description": "Contains messages associated with the managed extension for vesting contracts.", + "description": "Native token", "type": "object", "required": [ - "managed_extension" + "native_token" ], "properties": { - "managed_extension": { + "native_token": { "type": "object", "required": [ - "msg" + "denom" ], "properties": { - "msg": { - "$ref": "#/definitions/ExecuteMsgManaged" + "denom": { + "type": "string" } }, "additionalProperties": false } }, "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" + }, + "Cw20ReceiveMsg": { + "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", + "type": "object", + "required": [ + "amount", + "msg", + "sender" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" }, - { - "description": "Contains messages associated with the with_managers extension for vesting contracts.", - "type": "object", - "required": [ - "with_managers_extension" - ], - "properties": { - "with_managers_extension": { - "type": "object", - "required": [ - "msg" - ], - "properties": { - "msg": { - "$ref": "#/definitions/ExecuteMsgWithManagers" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false + "msg": { + "$ref": "#/definitions/Binary" }, - { - "description": "Contains messages associated with the historical extension for vesting contracts.", - "type": "object", - "required": [ - "historical_extension" - ], - "properties": { - "historical_extension": { - "type": "object", - "required": [ - "msg" - ], - "properties": { - "msg": { - "$ref": "#/definitions/ExecuteMsgHistorical" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false + "sender": { + "type": "string" } - ] + }, + "additionalProperties": false }, "ExecuteMsgHistorical": { "description": "This structure describes the execute messages available in a historical vesting contract.", diff --git a/contracts/vesting-lp/examples/vesting_schema.rs b/contracts/vesting-lp/examples/vesting_schema.rs deleted file mode 100644 index 821d12fc..00000000 --- a/contracts/vesting-lp/examples/vesting_schema.rs +++ /dev/null @@ -1,12 +0,0 @@ -use cosmwasm_schema::write_api; -use vesting_base::msg::QueryMsg; -use vesting_lp::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg}; - -fn main() { - write_api! { - instantiate: InstantiateMsg, - query: QueryMsg, - execute: ExecuteMsg, - migrate: MigrateMsg - } -} From c9f907b0ed732ae661e54da41dd380f3a8176c14 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Fri, 23 Feb 2024 10:48:10 -0300 Subject: [PATCH 21/30] Update contracts/vesting-lp-pcl/src/contract.rs Co-authored-by: sotnikov-s <34917380+sotnikov-s@users.noreply.github.com> --- contracts/vesting-lp-pcl/src/contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/vesting-lp-pcl/src/contract.rs b/contracts/vesting-lp-pcl/src/contract.rs index 7c4b3f1b..7d6165b8 100644 --- a/contracts/vesting-lp-pcl/src/contract.rs +++ b/contracts/vesting-lp-pcl/src/contract.rs @@ -17,7 +17,7 @@ use vesting_base::state::{vesting_info, vesting_state, CONFIG, VESTING_MANAGERS} use vesting_base::types::{Config, Extensions, VestingInfo}; /// Contract name that is used for migration. -const CONTRACT_NAME: &str = "neutron-vesting-lp"; +const CONTRACT_NAME: &str = "neutron-vesting-lp-pcl"; /// Contract version that is used for migration. const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); From f9a6f804ad2483c2237b49237e3474952163d441 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Mon, 26 Feb 2024 10:20:02 -0300 Subject: [PATCH 22/30] post-review fixes --- contracts/lockdrop/README.md | 12 +-- contracts/vesting-investors/Cargo.toml | 2 +- contracts/vesting-lp-pcl/Cargo.toml | 2 +- contracts/vesting-lp-pcl/src/contract.rs | 49 +++++++------ contracts/vesting-lp-pcl/src/msg.rs | 4 - contracts/vesting-lp/src/contract.rs | 93 ++++++++++++------------ contracts/vesting-lp/src/msg.rs | 8 +- packages/vesting-base/src/error.rs | 3 + packages/vesting-base/src/types.rs | 9 +++ 9 files changed, 98 insertions(+), 84 deletions(-) diff --git a/contracts/lockdrop/README.md b/contracts/lockdrop/README.md index 2bea03c0..fdd3f939 100644 --- a/contracts/lockdrop/README.md +++ b/contracts/lockdrop/README.md @@ -16,14 +16,14 @@ Note - Users can open muliple lockup positions with different lockup duration fo | `ExecuteMsg::EnableClaims` | Executed by the Bootstrap auction contract when liquidity is added to the ASTRO-UST pool. Enables ASTRO withdrawals by the lockdrop recipients. | | `ExecuteMsg::InitializePool` | Admin function. Facilitates addition of new Pool (Terraswap Pools) whose LP tokens can then be locked in the lockdrop contract | | `ExecuteMsg::UpdatePool` | Admin function to update any configuraton parameter for a terraswap pool whose LP tokens are currently accepted for the lockdrop | -| `ExecuteMsg::IncreaseLockup` | Facilitates opening a new _user position or adding to an existing position | +| `ExecuteMsg::IncreaseLockup` | Facilitates opening a new user position or adding to an existing position | | `ExecuteMsg::IncreaseAstroIncentives` | Admin function to increase the ASTRO incentives that are to be distributed | -| `ExecuteMsg::WithdrawFromLockup` | Facilitates LP token withdrawals from lockup positions by users. 100% amount can be withdrawn during deposit window, which is then limited to 50% during 1st half of deposit window which then decreases linearly during 2nd half of deposit window. Only 1 withdrawal can be made by a _user during the withdrawal windows | +| `ExecuteMsg::WithdrawFromLockup` | Facilitates LP token withdrawals from lockup positions by users. 100% amount can be withdrawn during deposit window, which is then limited to 50% during 1st half of deposit window which then decreases linearly during 2nd half of deposit window. Only 1 withdrawal can be made by a user during the withdrawal windows | | `ExecuteMsg::MigrateLiquidity` | Admin function. Facilitates migration of liquidity (locked terraswap LP tokens) from Terraswap to Astroport (Astroport LP tokens) | | `ExecuteMsg::StakeLpTokens` | Admin function. Facilitates staking of Astroport LP tokens for a particular LP pool with the generator contract | -| `ExecuteMsg::DelegateAstroToAuction` | This function facilitates ASTRO tokens delegation to the Bootstrap auction contract during the bootstrap auction phase. Delegated ASTRO tokens are added to the _user's position in the bootstrap auction contract | +| `ExecuteMsg::DelegateAstroToAuction` | This function facilitates ASTRO tokens delegation to the Bootstrap auction contract during the bootstrap auction phase. Delegated ASTRO tokens are added to the user's position in the bootstrap auction contract | | `ExecuteMsg::ClaimRewardsAndOptionallyUnlock` | Facilitates rewards claim by users for a particular lockup position along with unlock when possible | -| `ExecuteMsg::ClaimAssetReward` | Collects assets reward from LP and distribute reward to _user if all requirements are met | +| `ExecuteMsg::ClaimAssetReward` | Collects assets reward from LP and distribute reward to user if all requirements are met | | `ExecuteMsg::TogglePoolRewards` | Admin function. Enables assets reward for specified LP | | `ExecuteMsg::ProposeNewOwner` | Admin function. Creates an offer to change the contract ownership. The validity period of the offer is set in the `expires_in` variable. After `expires_in` seconds pass, the proposal expires and cannot be accepted anymore. | | `ExecuteMsg::DropOwnershipProposal` | Admin function. Removes an existing offer to change the contract owner. | @@ -34,7 +34,7 @@ Note - Users can open muliple lockup positions with different lockup duration fo | Message | Description | |-------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------| | `CallbackMsg::UpdatePoolOnDualRewardsClaim` | Callback function to update contract state after pending dual staking rewards are claimed from the generator contract | -| `CallbackMsg::WithdrawUserLockupRewardsCallback` | Callback function to withdraw _user rewards for a particular lockcup position along with optional LP tokens withdrawal (upon lockup duration expiration) | +| `CallbackMsg::WithdrawUserLockupRewardsCallback` | Callback function to withdraw user rewards for a particular lockcup position along with optional LP tokens withdrawal (upon lockup duration expiration) | | `CallbackMsg::WithdrawLiquidityFromTerraswapCallback` | Callback function used during liquidity migration to update state after liquidity is removed from terraswap | | `CallbackMsg::DistributeAssetReward` | Callback function used for assets reward distribution after rewards claiming from LP | @@ -45,7 +45,7 @@ Note - Users can open muliple lockup positions with different lockup duration fo | `QueryMsg::Config` | Returns the config info | | `QueryMsg::State` | Returns the contract's global state | | `QueryMsg::Pool` | Returns info regarding a certain supported LP token pool | -| `QueryMsg::UserInfo` | Returns info regarding a _user (total ASTRO rewards, list of lockup positions) | +| `QueryMsg::UserInfo` | Returns info regarding a user (total ASTRO rewards, list of lockup positions) | | `QueryMsg::LockUpInfo` | Returns info regarding a particular lockup position with a given duration and identifer for the LP tokens locked | | `QueryMsg::PendingAssetReward` | Returns the amount of pending asset rewards for the specified recipient and for a specific lockup position | diff --git a/contracts/vesting-investors/Cargo.toml b/contracts/vesting-investors/Cargo.toml index b6f70eab..d05d5262 100644 --- a/contracts/vesting-investors/Cargo.toml +++ b/contracts/vesting-investors/Cargo.toml @@ -3,7 +3,7 @@ name = "vesting-investors" version = "1.1.1" authors = ["Neutron"] edition = "2021" -description = "Vesting contract which provides queries to get the amount of tokens that are being held by _user at certain height and allows to remove vesting accounts to the contract's owner." +description = "Vesting contract which provides queries to get the amount of tokens that are being held by user at certain height and allows to remove vesting accounts to the contract's owner." [lib] crate-type = ["cdylib", "rlib"] diff --git a/contracts/vesting-lp-pcl/Cargo.toml b/contracts/vesting-lp-pcl/Cargo.toml index 2be7868f..43b07451 100644 --- a/contracts/vesting-lp-pcl/Cargo.toml +++ b/contracts/vesting-lp-pcl/Cargo.toml @@ -3,7 +3,7 @@ name = "vesting-lp-pcl" version = "1.1.0" authors = ["Neutron"] edition = "2021" -description = "Vesting contract with a voting capabilities. Provides queries to get the amount of tokens are being held by _user at certain height." +description = "Vesting contract with a voting capabilities. Provides queries to get the amount of tokens are being held by user at certain height." [lib] crate-type = ["cdylib", "rlib"] diff --git a/contracts/vesting-lp-pcl/src/contract.rs b/contracts/vesting-lp-pcl/src/contract.rs index 7d6165b8..bc1de61d 100644 --- a/contracts/vesting-lp-pcl/src/contract.rs +++ b/contracts/vesting-lp-pcl/src/contract.rs @@ -8,10 +8,8 @@ use cosmwasm_std::{ use cw2::set_contract_version; use cw20::Cw20ReceiveMsg; use vesting_base::error::ContractError; -use vesting_base::handlers::{ - assert_vesting_schedules, get_vesting_token, register_vesting_accounts, -}; use vesting_base::handlers::{execute as base_execute, query as base_query}; +use vesting_base::handlers::{get_vesting_token, register_vesting_accounts}; use vesting_base::msg::{ExecuteMsg as BaseExecute, QueryMsg}; use vesting_base::state::{vesting_info, vesting_state, CONFIG, VESTING_MANAGERS}; use vesting_base::types::{Config, Extensions, VestingInfo}; @@ -40,7 +38,7 @@ pub fn instantiate( extensions: Extensions { historical: true, - managed: true, + managed: false, with_managers: true, }, }, @@ -86,25 +84,29 @@ fn receive_cw20( ) -> Result { let config = CONFIG.load(deps.storage)?; let vesting_token = get_vesting_token(&config)?; - - // Permission check - if !is_sender_whitelisted( - deps.storage, - &config, - &deps.api.addr_validate(&cw20_msg.sender)?, - ) || token_asset_info(info.sender) != vesting_token - { - return Err(ContractError::Unauthorized {}); - } + let sender = info.sender; match from_binary(&cw20_msg.msg)? { Cw20HookMsg::RegisterVestingAccounts { vesting_accounts } => { + if !is_sender_whitelisted( + deps.storage, + &config, + &deps.api.addr_validate(sender.as_str())?, + ) || token_asset_info(sender.clone()) != vesting_token + { + return Err(ContractError::Unauthorized {}); + } register_vesting_accounts(deps, vesting_accounts, cw20_msg.amount, env.block.height) } Cw20HookMsg::MigrateXYKLiquidity { user_address_raw, user_vesting_info, - } => handle_migrate_xyk_liquidity(deps, env, user_address_raw, user_vesting_info), + } => { + if !is_sender_xyk_vesting_lp(deps.storage, &deps.api.addr_validate(sender.as_str())?) { + return Err(ContractError::Unauthorized {}); + } + handle_migrate_xyk_liquidity(deps, env, user_address_raw, user_vesting_info) + } } } @@ -123,19 +125,24 @@ fn is_sender_whitelisted(store: &mut dyn Storage, config: &Config, sender: &Addr false } +fn is_sender_xyk_vesting_lp(store: &mut dyn Storage, sender: &Addr) -> bool { + let xyk_vesting_lp_contract = XYK_VESTING_LP_CONTRACT.load(store).unwrap(); + if *sender == xyk_vesting_lp_contract { + return true; + } + + false +} + fn handle_migrate_xyk_liquidity( deps: DepsMut, env: Env, - user_addr_raw: Addr, + user_addr: Addr, user_vesting_info: VestingInfo, ) -> Result { let height = env.block.height; let config = CONFIG.load(deps.storage)?; - let account_address = user_addr_raw; - - assert_vesting_schedules(&account_address, &user_vesting_info.schedules)?; - let mut to_deposit = Uint128::zero(); for sch in &user_vesting_info.schedules { let amount = if let Some(end_point) = &sch.end_point { @@ -148,7 +155,7 @@ fn handle_migrate_xyk_liquidity( let vesting_info = vesting_info(config.extensions.historical); - vesting_info.save(deps.storage, account_address, &user_vesting_info, height)?; + vesting_info.save(deps.storage, user_addr, &user_vesting_info, height)?; vesting_state(config.extensions.historical).update::<_, ContractError>( deps.storage, diff --git a/contracts/vesting-lp-pcl/src/msg.rs b/contracts/vesting-lp-pcl/src/msg.rs index e20c443e..2af63d0d 100644 --- a/contracts/vesting-lp-pcl/src/msg.rs +++ b/contracts/vesting-lp-pcl/src/msg.rs @@ -68,10 +68,6 @@ pub enum ExecuteMsg { HistoricalExtension { msg: ExecuteMsgHistorical, }, - #[serde(rename = "migrate_liquidity_to_pcl_pool")] - MigrateLiquidityToPCLPool { - user: Option, - }, Receive(Cw20ReceiveMsg), } diff --git a/contracts/vesting-lp/src/contract.rs b/contracts/vesting-lp/src/contract.rs index e5dff841..08b83678 100644 --- a/contracts/vesting-lp/src/contract.rs +++ b/contracts/vesting-lp/src/contract.rs @@ -17,7 +17,7 @@ use vesting_base::handlers::query as base_query; use vesting_base::msg::{ExecuteMsg as BaseExecute, QueryMsg}; use vesting_base::state::{vesting_info, vesting_state, CONFIG}; use vesting_base::types::{ - VestingAccountResponse, VestingInfo, VestingSchedule, VestingSchedulePoint, + VestingAccountFullInfo, VestingInfo, VestingSchedule, VestingSchedulePoint, }; /// Contract name that is used for migration. @@ -104,7 +104,7 @@ fn execute_migrate_liquidity( let vesting_info = vesting_info(config.extensions.historical); let info = vesting_info.load(deps.storage, address.clone())?; let mut resp = Response::default(); - let user = VestingAccountResponse { address, info }; + let user = VestingAccountFullInfo { address, info }; // get pairs LP token addresses let pair_info: PairInfo = deps @@ -127,6 +127,9 @@ fn execute_migrate_liquidity( } let user_share = compute_share(&user.info)?; + + // if there is nothing to migrate just update vi and quit. + // if there is only dust, than send it back to user, update vi and quit let user_amount = if user_share < migration_config.dust_threshold { if !user_share.is_zero() { resp = resp.add_message(CosmosMsg::Wasm(WasmMsg::Execute { @@ -139,14 +142,6 @@ fn execute_migrate_liquidity( })); } - Uint128::zero() - } else { - user_share - }; - - // if there is nothing to migrate just update vi and quit. - // if there is only dust, than send it back to user, update vi and quit - if user_amount.is_zero() { vesting_info.save( deps.storage, user.address.clone(), @@ -157,7 +152,9 @@ fn execute_migrate_liquidity( env.block.height, )?; return Ok(resp); - } + } else { + user_share + }; if let Some(slippage_tolerance) = slippage_tolerance { if slippage_tolerance.gt(&migration_config.max_slippage) { @@ -253,7 +250,7 @@ fn migrate_liquidity_to_cl_pair_callback( cl_pair: Addr, ntrn_denom: String, paired_asset_denom: String, - user: VestingAccountResponse, + user: VestingAccountFullInfo, ) -> Result { let ntrn_init_balance = deps .querier @@ -267,17 +264,15 @@ fn migrate_liquidity_to_cl_pair_callback( let mut msgs: Vec = vec![]; // push message to withdraw liquidity from the xyk pair - if !amount.is_zero() { - msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: xyk_lp_token.to_string(), - msg: to_binary(&Cw20ExecuteMsg::Send { - contract: xyk_pair.to_string(), - amount, - msg: to_binary(&PairCw20HookMsg::WithdrawLiquidity { assets: vec![] })?, - })?, - funds: vec![], - })) - } + msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: xyk_lp_token.to_string(), + msg: to_binary(&Cw20ExecuteMsg::Send { + contract: xyk_pair.to_string(), + amount, + msg: to_binary(&PairCw20HookMsg::WithdrawLiquidity { assets: vec![] })?, + })?, + funds: vec![], + })); let config = CONFIG.load(deps.storage)?; vesting_state(config.extensions.historical).update::<_, ContractError>( deps.storage, @@ -315,7 +310,7 @@ fn provide_liquidity_to_cl_pair_after_withdrawal_callback( paired_asset_init_balance: Uint128, cl_pair_address: Addr, slippage_tolerance: Decimal, - user: VestingAccountResponse, + user: VestingAccountFullInfo, ) -> Result { let ntrn_balance_after_withdrawal = deps .querier @@ -343,24 +338,22 @@ fn provide_liquidity_to_cl_pair_after_withdrawal_callback( )?; let current_balance = balance_response.balance; - if !withdrawn_ntrn_amount.is_zero() && !withdrawn_paired_asset_amount.is_zero() { - msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: cl_pair_address.to_string(), - msg: to_binary(&PairExecuteMsg::ProvideLiquidity { - assets: vec![ - native_asset(ntrn_denom.clone(), withdrawn_ntrn_amount), - native_asset(paired_asset_denom.clone(), withdrawn_paired_asset_amount), - ], - slippage_tolerance: Some(slippage_tolerance), - auto_stake: None, - receiver: None, - })?, - funds: vec![ - Coin::new(withdrawn_ntrn_amount.into(), ntrn_denom), - Coin::new(withdrawn_paired_asset_amount.into(), paired_asset_denom), + msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: cl_pair_address.to_string(), + msg: to_binary(&PairExecuteMsg::ProvideLiquidity { + assets: vec![ + native_asset(ntrn_denom.clone(), withdrawn_ntrn_amount), + native_asset(paired_asset_denom.clone(), withdrawn_paired_asset_amount), ], - })) - } + slippage_tolerance: Some(slippage_tolerance), + auto_stake: None, + receiver: None, + })?, + funds: vec![ + Coin::new(withdrawn_ntrn_amount.into(), ntrn_denom), + Coin::new(withdrawn_paired_asset_amount.into(), paired_asset_denom), + ], + })); msgs.push( CallbackMsg::PostMigrationVestingReschedule { @@ -376,7 +369,7 @@ fn provide_liquidity_to_cl_pair_after_withdrawal_callback( fn post_migration_vesting_reschedule_callback( deps: DepsMut, env: Env, - user: &VestingAccountResponse, + user: &VestingAccountFullInfo, init_balance_pcl_lp: Uint128, ) -> Result { let config = CONFIG.load(deps.storage)?; @@ -387,14 +380,20 @@ fn post_migration_vesting_reschedule_callback( address: env.contract.address.to_string(), }, )?; - let current_balance = balance_response.balance.checked_sub(init_balance_pcl_lp)?; + let balance_diff = balance_response.balance.checked_sub(init_balance_pcl_lp)?; - let schedule = user.info.schedules.last().unwrap(); + let schedule = user + .info + .schedules + .last() + .ok_or(ContractError::VestingScheduleExtractError( + user.address.to_string(), + ))?; let new_end_point = match &schedule.end_point { Some(end_point) => Option::from(VestingSchedulePoint { time: end_point.time, - amount: current_balance, + amount: balance_diff, }), None => None, }; @@ -418,13 +417,13 @@ fn post_migration_vesting_reschedule_callback( }, env.block.height, )?; - if !current_balance.is_zero() { + if !balance_diff.is_zero() { let msgs = vec![CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: migration_config.new_lp_token.to_string(), funds: vec![], msg: to_binary(&Cw20ExecuteMsg::Send { contract: migration_config.pcl_vesting.to_string(), - amount: current_balance, + amount: balance_diff, msg: to_binary(&vesting_lp_pcl::msg::Cw20HookMsg::MigrateXYKLiquidity { user_address_raw: user.address.clone(), user_vesting_info: VestingInfo { diff --git a/contracts/vesting-lp/src/msg.rs b/contracts/vesting-lp/src/msg.rs index 428feb8c..aba536d7 100644 --- a/contracts/vesting-lp/src/msg.rs +++ b/contracts/vesting-lp/src/msg.rs @@ -5,7 +5,7 @@ use cw20::Cw20ReceiveMsg; use vesting_base::msg::{ ExecuteMsg as BaseExecute, ExecuteMsgHistorical, ExecuteMsgManaged, ExecuteMsgWithManagers, }; -use vesting_base::types::{VestingAccount, VestingAccountResponse}; +use vesting_base::types::{VestingAccount, VestingAccountFullInfo}; /// This structure describes the parameters used for creating a contract. #[cw_serde] @@ -76,7 +76,7 @@ pub enum CallbackMsg { cl_pair: Addr, ntrn_denom: String, paired_asset_denom: String, - user: VestingAccountResponse, + user: VestingAccountFullInfo, }, ProvideLiquidityToClPairAfterWithdrawal { ntrn_denom: String, @@ -85,10 +85,10 @@ pub enum CallbackMsg { paired_asset_init_balance: Uint128, cl_pair: Addr, slippage_tolerance: Decimal, - user: VestingAccountResponse, + user: VestingAccountFullInfo, }, PostMigrationVestingReschedule { - user: VestingAccountResponse, + user: VestingAccountFullInfo, init_balance_pcl_lp: Uint128, }, } diff --git a/packages/vesting-base/src/error.rs b/packages/vesting-base/src/error.rs index a1859ac0..efee45d3 100644 --- a/packages/vesting-base/src/error.rs +++ b/packages/vesting-base/src/error.rs @@ -20,6 +20,9 @@ pub enum ContractError { #[error("Vesting schedule error on addr: {0}. Should satisfy: (start < end and at_start < total) or (start = end and at_start = total)")] VestingScheduleError(String), + #[error("Vesting schedule error on addr: {0}. No schedule found")] + VestingScheduleExtractError(String), + #[error("Vesting schedule amount error. The total amount should be equal to the CW20 receive amount.")] VestingScheduleAmountError {}, diff --git a/packages/vesting-base/src/types.rs b/packages/vesting-base/src/types.rs index f6081025..6a1378b4 100644 --- a/packages/vesting-base/src/types.rs +++ b/packages/vesting-base/src/types.rs @@ -81,6 +81,15 @@ pub struct VestingAccountResponse { pub info: VestingInfo, } +/// This structure describes a custom struct used to pass the vesting data about a specific vesting target during migration. +#[cw_serde] +pub struct VestingAccountFullInfo { + /// The address that's vesting tokens + pub address: Addr, + /// Vesting information + pub info: VestingInfo, +} + /// This structure describes a custom struct used to return vesting data for multiple vesting targets. #[cw_serde] pub struct VestingAccountsResponse { From c61cd52b2b66ae117125101216aacd39530e9980 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Mon, 26 Feb 2024 10:22:53 -0300 Subject: [PATCH 23/30] rm max available amount --- contracts/vesting-lp/src/contract.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/contracts/vesting-lp/src/contract.rs b/contracts/vesting-lp/src/contract.rs index 08b83678..202b282a 100644 --- a/contracts/vesting-lp/src/contract.rs +++ b/contracts/vesting-lp/src/contract.rs @@ -111,21 +111,6 @@ fn execute_migrate_liquidity( .querier .query_wasm_smart(migration_config.xyk_pair.clone(), &PairQueryMsg::Pair {})?; - // query max available amounts to be withdrawn from pool - let max_available_amount = { - let resp: BalanceResponse = deps.querier.query_wasm_smart( - pair_info.liquidity_token.clone(), - &Cw20QueryMsg::Balance { - address: env.contract.address.to_string(), - }, - )?; - resp.balance - }; - - if max_available_amount.is_zero() { - return Ok(resp); - } - let user_share = compute_share(&user.info)?; // if there is nothing to migrate just update vi and quit. From 747dc526685cf621e0e38071c7bf47a20143e578 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Mon, 26 Feb 2024 10:24:46 -0300 Subject: [PATCH 24/30] simplfy addr pass --- contracts/vesting-lp-pcl/src/contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/vesting-lp-pcl/src/contract.rs b/contracts/vesting-lp-pcl/src/contract.rs index bc1de61d..e2117472 100644 --- a/contracts/vesting-lp-pcl/src/contract.rs +++ b/contracts/vesting-lp-pcl/src/contract.rs @@ -102,7 +102,7 @@ fn receive_cw20( user_address_raw, user_vesting_info, } => { - if !is_sender_xyk_vesting_lp(deps.storage, &deps.api.addr_validate(sender.as_str())?) { + if !is_sender_xyk_vesting_lp(deps.storage, &sender){ return Err(ContractError::Unauthorized {}); } handle_migrate_xyk_liquidity(deps, env, user_address_raw, user_vesting_info) From 07adbf0f70c14b199f5d55187f6d062e51dc6d49 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Mon, 26 Feb 2024 10:25:16 -0300 Subject: [PATCH 25/30] simplify addr pass --- contracts/vesting-lp-pcl/src/contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/vesting-lp-pcl/src/contract.rs b/contracts/vesting-lp-pcl/src/contract.rs index e2117472..7ad49404 100644 --- a/contracts/vesting-lp-pcl/src/contract.rs +++ b/contracts/vesting-lp-pcl/src/contract.rs @@ -102,7 +102,7 @@ fn receive_cw20( user_address_raw, user_vesting_info, } => { - if !is_sender_xyk_vesting_lp(deps.storage, &sender){ + if !is_sender_xyk_vesting_lp(deps.storage, &sender) { return Err(ContractError::Unauthorized {}); } handle_migrate_xyk_liquidity(deps, env, user_address_raw, user_vesting_info) From f9b7749f8c13d35d09d8967b1644b11014c92732 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Mon, 26 Feb 2024 10:49:11 -0300 Subject: [PATCH 26/30] regen schema & fix readmes --- contracts/auction/README.md | 22 ++++++------ contracts/lockdrop/README.md | 34 +++++++++---------- ...-lp_schema.rs => vesting-lp-pcl_schema.rs} | 0 .../vesting-lp-pcl/schema/raw/execute.json | 21 ------------ .../vesting-lp-pcl/schema/vesting-lp-pcl.json | 21 ------------ 5 files changed, 28 insertions(+), 70 deletions(-) rename contracts/vesting-lp-pcl/examples/{vesting-lp_schema.rs => vesting-lp-pcl_schema.rs} (100%) diff --git a/contracts/auction/README.md b/contracts/auction/README.md index 20f115d3..ef047be5 100644 --- a/contracts/auction/README.md +++ b/contracts/auction/README.md @@ -6,7 +6,7 @@ The LP Bootstrap via auction contract facilitates cNTRN-NATIVE Neutron pool init - Airdrop recipients and lockdrop participants can delegate part / all of their cNTRN rewards to the auction contract. - Any user can deposit UST directly to the auction contract to participate in the LP bootstrap auction. -- Both UST deposited & cNTRN delegated (if any) balances are used to calculate _user's LP token shares and additional cNTRN incentives that he will receive for participating in the auction. +- Both UST deposited & cNTRN delegated (if any) balances are used to calculate user's LP token shares and additional cNTRN incentives that he will receive for participating in the auction. **Phase 2 :: Post cNTRN-NATIVE Pool initialization** @@ -19,15 +19,15 @@ The LP Bootstrap via auction contract facilitates cNTRN-NATIVE Neutron pool init ### Handle Messages -| Message | Description | -| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ExecuteMsg::Receive` | ReceiveCW20 Hook which facilitates cNTRN tokens delegation by lockdrop participants / airdrop recipients | -| `ExecuteMsg::UpdateConfig` | Admin function to update any of the configuration parameters. | -| `ExecuteMsg::DepositUst` | Facilitates UST deposits by users | -| `ExecuteMsg::WithdrawUst` | Facilitates UST withdrawals by users. 100% amount can be withdrawn during deposit window, which is then limited to 50% during 1st half of deposit window which then decreases linearly during 2nd half of deposit window. Only 1 withdrawal can be made by a _user during the withdrawal window | -| `ExecuteMsg::InitPool` | Admin function which facilitates Liquidity addtion to the Astroport cNTRN-UST Pool. Uses CallbackMsg to update state post liquidity addition to the pool | -| `ExecuteMsg::StakeLpTokens` | Admin function to stake cNTRN-UST LP tokens with the generator contract | -| `ExecuteMsg::ClaimRewards` | Facilitates cNTRN rewards claim (staking incentives from generator) for users and the withdrawal of LP shares which have been unlocked for the _user. | +| Message | Description | +| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ExecuteMsg::Receive` | ReceiveCW20 Hook which facilitates cNTRN tokens delegation by lockdrop participants / airdrop recipients | +| `ExecuteMsg::UpdateConfig` | Admin function to update any of the configuration parameters. | +| `ExecuteMsg::DepositUst` | Facilitates UST deposits by users | +| `ExecuteMsg::WithdrawUst` | Facilitates UST withdrawals by users. 100% amount can be withdrawn during deposit window, which is then limited to 50% during 1st half of deposit window which then decreases linearly during 2nd half of deposit window. Only 1 withdrawal can be made by a user during the withdrawal window | +| `ExecuteMsg::InitPool` | Admin function which facilitates Liquidity addtion to the Astroport cNTRN-UST Pool. Uses CallbackMsg to update state post liquidity addition to the pool | +| `ExecuteMsg::StakeLpTokens` | Admin function to stake cNTRN-UST LP tokens with the generator contract | +| `ExecuteMsg::ClaimRewards` | Facilitates cNTRN rewards claim (staking incentives from generator) for users and the withdrawal of LP shares which have been unlocked for the user. | ### Handle Messages :: Callback @@ -35,7 +35,7 @@ The LP Bootstrap via auction contract facilitates cNTRN-NATIVE Neutron pool init | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | `CallbackMsg::UpdateStateOnLiquidityAdditionToPool` | Callback function to update state after liquidity is added to the cNTRN-UST Pool | | `CallbackMsg::UpdateStateOnRewardClaim` | Callback function to update state after cNTRN rewards are claimed from the generator | -| `CallbackMsg::WithdrawUserRewardsCallback` | Callback function to facilitate cNTRN reward claiming and unlocked LP tokens withdrawal for the _user | +| `CallbackMsg::WithdrawUserRewardsCallback` | Callback function to facilitate cNTRN reward claiming and unlocked LP tokens withdrawal for the user | ### Query Messages diff --git a/contracts/lockdrop/README.md b/contracts/lockdrop/README.md index fdd3f939..ae990b11 100644 --- a/contracts/lockdrop/README.md +++ b/contracts/lockdrop/README.md @@ -10,24 +10,24 @@ Note - Users can open muliple lockup positions with different lockup duration fo ### Handle Messages -| Message | Description | -|-----------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `ExecuteMsg::UpdateConfig` | Can only be called by the admin. Facilitates updating configuration parameters | -| `ExecuteMsg::EnableClaims` | Executed by the Bootstrap auction contract when liquidity is added to the ASTRO-UST pool. Enables ASTRO withdrawals by the lockdrop recipients. | -| `ExecuteMsg::InitializePool` | Admin function. Facilitates addition of new Pool (Terraswap Pools) whose LP tokens can then be locked in the lockdrop contract | -| `ExecuteMsg::UpdatePool` | Admin function to update any configuraton parameter for a terraswap pool whose LP tokens are currently accepted for the lockdrop | -| `ExecuteMsg::IncreaseLockup` | Facilitates opening a new user position or adding to an existing position | -| `ExecuteMsg::IncreaseAstroIncentives` | Admin function to increase the ASTRO incentives that are to be distributed | +| Message | Description | +|-----------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `ExecuteMsg::UpdateConfig` | Can only be called by the admin. Facilitates updating configuration parameters | +| `ExecuteMsg::EnableClaims` | Executed by the Bootstrap auction contract when liquidity is added to the ASTRO-UST pool. Enables ASTRO withdrawals by the lockdrop recipients. | +| `ExecuteMsg::InitializePool` | Admin function. Facilitates addition of new Pool (Terraswap Pools) whose LP tokens can then be locked in the lockdrop contract | +| `ExecuteMsg::UpdatePool` | Admin function to update any configuraton parameter for a terraswap pool whose LP tokens are currently accepted for the lockdrop | +| `ExecuteMsg::IncreaseLockup` | Facilitates opening a new user position or adding to an existing position | +| `ExecuteMsg::IncreaseAstroIncentives` | Admin function to increase the ASTRO incentives that are to be distributed | | `ExecuteMsg::WithdrawFromLockup` | Facilitates LP token withdrawals from lockup positions by users. 100% amount can be withdrawn during deposit window, which is then limited to 50% during 1st half of deposit window which then decreases linearly during 2nd half of deposit window. Only 1 withdrawal can be made by a user during the withdrawal windows | -| `ExecuteMsg::MigrateLiquidity` | Admin function. Facilitates migration of liquidity (locked terraswap LP tokens) from Terraswap to Astroport (Astroport LP tokens) | -| `ExecuteMsg::StakeLpTokens` | Admin function. Facilitates staking of Astroport LP tokens for a particular LP pool with the generator contract | -| `ExecuteMsg::DelegateAstroToAuction` | This function facilitates ASTRO tokens delegation to the Bootstrap auction contract during the bootstrap auction phase. Delegated ASTRO tokens are added to the user's position in the bootstrap auction contract | -| `ExecuteMsg::ClaimRewardsAndOptionallyUnlock` | Facilitates rewards claim by users for a particular lockup position along with unlock when possible | -| `ExecuteMsg::ClaimAssetReward` | Collects assets reward from LP and distribute reward to user if all requirements are met | -| `ExecuteMsg::TogglePoolRewards` | Admin function. Enables assets reward for specified LP | -| `ExecuteMsg::ProposeNewOwner` | Admin function. Creates an offer to change the contract ownership. The validity period of the offer is set in the `expires_in` variable. After `expires_in` seconds pass, the proposal expires and cannot be accepted anymore. | -| `ExecuteMsg::DropOwnershipProposal` | Admin function. Removes an existing offer to change the contract owner. | -| `ExecuteMsg::ClaimOwnership` | Admin function. Used to claim contract ownership. | +| `ExecuteMsg::MigrateLiquidity` | Admin function. Facilitates migration of liquidity (locked terraswap LP tokens) from Terraswap to Astroport (Astroport LP tokens) | +| `ExecuteMsg::StakeLpTokens` | Admin function. Facilitates staking of Astroport LP tokens for a particular LP pool with the generator contract | +| `ExecuteMsg::DelegateAstroToAuction` | This function facilitates ASTRO tokens delegation to the Bootstrap auction contract during the bootstrap auction phase. Delegated ASTRO tokens are added to the user's position in the bootstrap auction contract | +| `ExecuteMsg::ClaimRewardsAndOptionallyUnlock` | Facilitates rewards claim by users for a particular lockup position along with unlock when possible | +| `ExecuteMsg::ClaimAssetReward` | Collects assets reward from LP and distribute reward to user if all requirements are met | +| `ExecuteMsg::TogglePoolRewards` | Admin function. Enables assets reward for specified LP | +| `ExecuteMsg::ProposeNewOwner` | Admin function. Creates an offer to change the contract ownership. The validity period of the offer is set in the `expires_in` variable. After `expires_in` seconds pass, the proposal expires and cannot be accepted anymore. | +| `ExecuteMsg::DropOwnershipProposal` | Admin function. Removes an existing offer to change the contract owner. | +| `ExecuteMsg::ClaimOwnership` | Admin function. Used to claim contract ownership. | ### Handle Messages :: Callback diff --git a/contracts/vesting-lp-pcl/examples/vesting-lp_schema.rs b/contracts/vesting-lp-pcl/examples/vesting-lp-pcl_schema.rs similarity index 100% rename from contracts/vesting-lp-pcl/examples/vesting-lp_schema.rs rename to contracts/vesting-lp-pcl/examples/vesting-lp-pcl_schema.rs diff --git a/contracts/vesting-lp-pcl/schema/raw/execute.json b/contracts/vesting-lp-pcl/schema/raw/execute.json index 51383681..c70872f2 100644 --- a/contracts/vesting-lp-pcl/schema/raw/execute.json +++ b/contracts/vesting-lp-pcl/schema/raw/execute.json @@ -206,27 +206,6 @@ }, "additionalProperties": false }, - { - "type": "object", - "required": [ - "migrate_liquidity_to_pcl_pool" - ], - "properties": { - "migrate_liquidity_to_pcl_pool": { - "type": "object", - "properties": { - "user": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, { "type": "object", "required": [ diff --git a/contracts/vesting-lp-pcl/schema/vesting-lp-pcl.json b/contracts/vesting-lp-pcl/schema/vesting-lp-pcl.json index e9c4a3a0..d05a001f 100644 --- a/contracts/vesting-lp-pcl/schema/vesting-lp-pcl.json +++ b/contracts/vesting-lp-pcl/schema/vesting-lp-pcl.json @@ -302,27 +302,6 @@ }, "additionalProperties": false }, - { - "type": "object", - "required": [ - "migrate_liquidity_to_pcl_pool" - ], - "properties": { - "migrate_liquidity_to_pcl_pool": { - "type": "object", - "properties": { - "user": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, { "type": "object", "required": [ From 222e651c0465884e684ed880037dd019589896bb Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Mon, 26 Feb 2024 11:02:14 -0300 Subject: [PATCH 27/30] simplify exit branch --- contracts/vesting-lp/src/contract.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/contracts/vesting-lp/src/contract.rs b/contracts/vesting-lp/src/contract.rs index 202b282a..a8c3cbf9 100644 --- a/contracts/vesting-lp/src/contract.rs +++ b/contracts/vesting-lp/src/contract.rs @@ -115,7 +115,7 @@ fn execute_migrate_liquidity( // if there is nothing to migrate just update vi and quit. // if there is only dust, than send it back to user, update vi and quit - let user_amount = if user_share < migration_config.dust_threshold { + if user_share < migration_config.dust_threshold { if !user_share.is_zero() { resp = resp.add_message(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: pair_info.liquidity_token.to_string(), @@ -137,8 +137,6 @@ fn execute_migrate_liquidity( env.block.height, )?; return Ok(resp); - } else { - user_share }; if let Some(slippage_tolerance) = slippage_tolerance { @@ -153,7 +151,7 @@ fn execute_migrate_liquidity( CallbackMsg::MigrateLiquidityToClPair { xyk_pair: migration_config.xyk_pair.clone(), xyk_lp_token: pair_info.liquidity_token.clone(), - amount: user_amount, + amount: user_share, slippage_tolerance, cl_pair: migration_config.cl_pair.clone(), ntrn_denom: migration_config.ntrn_denom.clone(), From 4fe3bdc191a13163f21abd44d8ffd79c0098769b Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Mon, 26 Feb 2024 11:05:43 -0300 Subject: [PATCH 28/30] decrease total granted instead of increasing total released --- contracts/vesting-lp/src/contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/vesting-lp/src/contract.rs b/contracts/vesting-lp/src/contract.rs index a8c3cbf9..367c19eb 100644 --- a/contracts/vesting-lp/src/contract.rs +++ b/contracts/vesting-lp/src/contract.rs @@ -262,7 +262,7 @@ fn migrate_liquidity_to_cl_pair_callback( env.block.height, |s| { let mut state = s.unwrap_or_default(); - state.total_released = state.total_released.checked_add(amount)?; + state.total_granted = state.total_granted.checked_sub(amount)?; Ok(state) }, )?; From 026f8654a80f52813bd76c753d2febfe4eb17e55 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Mon, 26 Feb 2024 11:08:31 -0300 Subject: [PATCH 29/30] fmt --- contracts/vesting-lp/src/contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/vesting-lp/src/contract.rs b/contracts/vesting-lp/src/contract.rs index 367c19eb..2756b247 100644 --- a/contracts/vesting-lp/src/contract.rs +++ b/contracts/vesting-lp/src/contract.rs @@ -115,7 +115,7 @@ fn execute_migrate_liquidity( // if there is nothing to migrate just update vi and quit. // if there is only dust, than send it back to user, update vi and quit - if user_share < migration_config.dust_threshold { + if user_share < migration_config.dust_threshold { if !user_share.is_zero() { resp = resp.add_message(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: pair_info.liquidity_token.to_string(), From 2747526e47c25e32abd648dea8debc11d3a1ae19 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Mon, 26 Feb 2024 11:21:03 -0300 Subject: [PATCH 30/30] rm user autoreplace --- contracts/auction/README.md | 22 +++++++++++----------- contracts/vesting-lp/Cargo.toml | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/contracts/auction/README.md b/contracts/auction/README.md index ef047be5..a4706ea5 100644 --- a/contracts/auction/README.md +++ b/contracts/auction/README.md @@ -19,20 +19,20 @@ The LP Bootstrap via auction contract facilitates cNTRN-NATIVE Neutron pool init ### Handle Messages -| Message | Description | -| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ExecuteMsg::Receive` | ReceiveCW20 Hook which facilitates cNTRN tokens delegation by lockdrop participants / airdrop recipients | -| `ExecuteMsg::UpdateConfig` | Admin function to update any of the configuration parameters. | -| `ExecuteMsg::DepositUst` | Facilitates UST deposits by users | +| Message | Description | +|-----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `ExecuteMsg::Receive` | ReceiveCW20 Hook which facilitates cNTRN tokens delegation by lockdrop participants / airdrop recipients | +| `ExecuteMsg::UpdateConfig` | Admin function to update any of the configuration parameters. | +| `ExecuteMsg::DepositUst` | Facilitates UST deposits by users | | `ExecuteMsg::WithdrawUst` | Facilitates UST withdrawals by users. 100% amount can be withdrawn during deposit window, which is then limited to 50% during 1st half of deposit window which then decreases linearly during 2nd half of deposit window. Only 1 withdrawal can be made by a user during the withdrawal window | -| `ExecuteMsg::InitPool` | Admin function which facilitates Liquidity addtion to the Astroport cNTRN-UST Pool. Uses CallbackMsg to update state post liquidity addition to the pool | -| `ExecuteMsg::StakeLpTokens` | Admin function to stake cNTRN-UST LP tokens with the generator contract | -| `ExecuteMsg::ClaimRewards` | Facilitates cNTRN rewards claim (staking incentives from generator) for users and the withdrawal of LP shares which have been unlocked for the user. | +| `ExecuteMsg::InitPool` | Admin function which facilitates Liquidity addtion to the Astroport cNTRN-UST Pool. Uses CallbackMsg to update state post liquidity addition to the pool | +| `ExecuteMsg::StakeLpTokens` | Admin function to stake cNTRN-UST LP tokens with the generator contract | +| `ExecuteMsg::ClaimRewards` | Facilitates cNTRN rewards claim (staking incentives from generator) for users and the withdrawal of LP shares which have been unlocked for the user. | ### Handle Messages :: Callback | Message | Description | -| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +|-----------------------------------------------------|------------------------------------------------------------------------------------------------------| | `CallbackMsg::UpdateStateOnLiquidityAdditionToPool` | Callback function to update state after liquidity is added to the cNTRN-UST Pool | | `CallbackMsg::UpdateStateOnRewardClaim` | Callback function to update state after cNTRN rewards are claimed from the generator | | `CallbackMsg::WithdrawUserRewardsCallback` | Callback function to facilitate cNTRN reward claiming and unlocked LP tokens withdrawal for the user | @@ -40,10 +40,10 @@ The LP Bootstrap via auction contract facilitates cNTRN-NATIVE Neutron pool init ### Query Messages | Message | Description | -| -------------------- | ----------------------------- | +|----------------------|-------------------------------| | `QueryMsg::Config` | Returns the config info | | `QueryMsg::State` | Returns state of the contract | -| `QueryMsg::UserInfo` | Returns _user position details | +| `QueryMsg::UserInfo` | Returns user position details | ## Build schema and run unit-tests diff --git a/contracts/vesting-lp/Cargo.toml b/contracts/vesting-lp/Cargo.toml index c1f74ca9..0dadfa86 100644 --- a/contracts/vesting-lp/Cargo.toml +++ b/contracts/vesting-lp/Cargo.toml @@ -3,7 +3,7 @@ name = "vesting-lp" version = "1.1.0" authors = ["Neutron"] edition = "2021" -description = "Vesting contract with a voting capabilities. Provides queries to get the amount of tokens are being held by _user at certain height." +description = "Vesting contract with a voting capabilities. Provides queries to get the amount of tokens are being held by user at certain height." [lib] crate-type = ["cdylib", "rlib"]