From e673b6395d576c161e5d6454a2b3b77a60b9b873 Mon Sep 17 00:00:00 2001 From: pr0n00gler Date: Wed, 18 Oct 2023 23:29:48 +0300 Subject: [PATCH 01/14] PCL pools migration --- Cargo.lock | 77 +- contracts/auction/Cargo.toml | 4 +- contracts/auction/schema/neutron-auction.json | 2 +- contracts/lockdrop/Cargo.toml | 4 +- .../lockdrop/schema/neutron-lockdrop.json | 185 ++- contracts/lockdrop/schema/raw/execute.json | 159 ++ contracts/lockdrop/schema/raw/migrate.json | 24 +- contracts/lockdrop/src/contract.rs | 531 ++++++- contracts/lockdrop/src/state.rs | 15 + contracts/lockdrop/src/testing.rs | 11 +- contracts/vesting-lp/Cargo.toml | 7 +- contracts/vesting-lp/schema/vesting-lp.json | 2 +- contracts/vesting-lp/src/contract.rs | 15 +- contracts/vesting-lp/src/lib.rs | 3 - contracts/vesting-lp/src/tests/integration.rs | 1281 ----------------- contracts/vesting-lp/src/tests/mod.rs | 1 - contracts/vesting-lti/src/contract.rs | 9 +- packages/astroport/src/mock_querier.rs | 2 +- packages/astroport_periphery/Cargo.toml | 2 +- packages/astroport_periphery/src/lockdrop.rs | 54 +- packages/vesting-base-lp/Cargo.toml | 25 + 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 | 68 + .../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 | 818 +++++++++++ packages/vesting-base-lp/src/lib.rs | 10 + packages/vesting-base-lp/src/msg.rs | 209 +++ packages/vesting-base-lp/src/state.rs | 166 +++ packages/vesting-base-lp/src/types.rs | 131 ++ 33 files changed, 3123 insertions(+), 1344 deletions(-) delete mode 100644 contracts/vesting-lp/src/tests/integration.rs delete mode 100644 contracts/vesting-lp/src/tests/mod.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 diff --git a/Cargo.lock b/Cargo.lock index 419b2625..fa3c1433 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,7 +34,7 @@ dependencies = [ [[package]] name = "astroport" version = "2.5.0" -source = "git+https://github.com/astroport-fi/astroport-core.git?tag=v2.5.0#65ce7d1879cc5d95b09fa14202f0423bba52ae0e" +source = "git+https://github.com/astroport-fi/astroport-core.git?rev=65ce7d1879cc5d95b09fa14202f0423bba52ae0e#65ce7d1879cc5d95b09fa14202f0423bba52ae0e" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -47,24 +47,37 @@ dependencies = [ [[package]] name = "astroport" -version = "2.5.0" -source = "git+https://github.com/astroport-fi/astroport-core.git?rev=65ce7d1879cc5d95b09fa14202f0423bba52ae0e#65ce7d1879cc5d95b09fa14202f0423bba52ae0e" +version = "3.6.0" +source = "git+https://github.com/astroport-fi/astroport-core.git?tag=v3.6.0#98550a04b98a593762908eb7d668cd9f2503f9c5" dependencies = [ + "astroport-circular-buffer", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 0.15.1", - "cw-utils 0.15.1", + "cw-utils 1.0.1", "cw20 0.15.1", + "cw3", "itertools", "uint", ] +[[package]] +name = "astroport-circular-buffer" +version = "0.1.0" +source = "git+https://github.com/astroport-fi/astroport-core.git?tag=v3.6.0#98550a04b98a593762908eb7d668cd9f2503f9c5" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 0.15.1", + "thiserror", +] + [[package]] name = "astroport-factory" version = "1.5.0" source = "git+https://github.com/astroport-fi/astroport-core.git?rev=65ce7d1879cc5d95b09fa14202f0423bba52ae0e#65ce7d1879cc5d95b09fa14202f0423bba52ae0e" dependencies = [ - "astroport 2.5.0 (git+https://github.com/astroport-fi/astroport-core.git?rev=65ce7d1879cc5d95b09fa14202f0423bba52ae0e)", + "astroport 2.5.0", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 0.15.1", @@ -79,7 +92,7 @@ name = "astroport-native-coin-registry" version = "1.0.1" source = "git+https://github.com/astroport-fi/astroport-core.git?rev=65ce7d1879cc5d95b09fa14202f0423bba52ae0e#65ce7d1879cc5d95b09fa14202f0423bba52ae0e" dependencies = [ - "astroport 2.5.0 (git+https://github.com/astroport-fi/astroport-core.git?rev=65ce7d1879cc5d95b09fa14202f0423bba52ae0e)", + "astroport 2.5.0", "cosmwasm-schema", "cosmwasm-std", "cosmwasm-storage", @@ -114,7 +127,7 @@ name = "astroport-pair" version = "1.2.0" source = "git+https://github.com/astroport-fi/astroport-core.git?rev=65ce7d1879cc5d95b09fa14202f0423bba52ae0e#65ce7d1879cc5d95b09fa14202f0423bba52ae0e" dependencies = [ - "astroport 2.5.0 (git+https://github.com/astroport-fi/astroport-core.git?rev=65ce7d1879cc5d95b09fa14202f0423bba52ae0e)", + "astroport 2.5.0", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 0.15.1", @@ -130,7 +143,7 @@ name = "astroport-pair-stable" version = "2.1.1" source = "git+https://github.com/astroport-fi/astroport-core.git?rev=65ce7d1879cc5d95b09fa14202f0423bba52ae0e#65ce7d1879cc5d95b09fa14202f0423bba52ae0e" dependencies = [ - "astroport 2.5.0 (git+https://github.com/astroport-fi/astroport-core.git?rev=65ce7d1879cc5d95b09fa14202f0423bba52ae0e)", + "astroport 2.5.0", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 0.15.1", @@ -145,7 +158,7 @@ dependencies = [ name = "astroport-periphery" version = "1.1.0" dependencies = [ - "astroport 2.5.0 (git+https://github.com/astroport-fi/astroport-core.git?tag=v2.5.0)", + "astroport 3.6.0", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 0.15.1", @@ -160,7 +173,7 @@ name = "astroport-token" version = "1.1.1" source = "git+https://github.com/astroport-fi/astroport-core.git?rev=65ce7d1879cc5d95b09fa14202f0423bba52ae0e#65ce7d1879cc5d95b09fa14202f0423bba52ae0e" dependencies = [ - "astroport 2.5.0 (git+https://github.com/astroport-fi/astroport-core.git?rev=65ce7d1879cc5d95b09fa14202f0423bba52ae0e)", + "astroport 2.5.0", "cosmwasm-schema", "cosmwasm-std", "cw2 0.15.1", @@ -747,6 +760,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw3" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fe0b587008aa221cd2a2579a21990a28c4347dc53ad43167c68ad765f5b6efa" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils 1.0.1", + "cw20 1.0.1", + "schemars", + "serde", + "thiserror", +] + [[package]] name = "der" version = "0.6.1" @@ -984,9 +1012,9 @@ checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" [[package]] name = "neutron-auction" -version = "1.0.0" +version = "1.0.1" dependencies = [ - "astroport 2.5.0 (git+https://github.com/astroport-fi/astroport-core.git?tag=v2.5.0)", + "astroport 3.6.0", "astroport-periphery", "cosmwasm-schema", "cosmwasm-std", @@ -1001,9 +1029,9 @@ dependencies = [ [[package]] name = "neutron-lockdrop" -version = "1.2.1" +version = "2.0.0" dependencies = [ - "astroport 2.5.0 (git+https://github.com/astroport-fi/astroport-core.git?tag=v2.5.0)", + "astroport 3.6.0", "astroport-periphery", "cosmwasm-schema", "cosmwasm-std", @@ -1739,6 +1767,22 @@ dependencies = [ "thiserror", ] +[[package]] +name = "vesting-base-lp" +version = "1.1.0" +dependencies = [ + "astroport 3.6.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", +] + [[package]] name = "vesting-investors" version = "1.1.1" @@ -1757,9 +1801,9 @@ dependencies = [ [[package]] name = "vesting-lp" -version = "1.1.0" +version = "2.0.0" dependencies = [ - "astroport 2.0.0", + "astroport 3.6.0", "astroport-token", "cosmwasm-schema", "cosmwasm-std", @@ -1769,6 +1813,7 @@ dependencies = [ "cw2 0.15.1", "cw20 0.15.1", "vesting-base", + "vesting-base-lp", ] [[package]] diff --git a/contracts/auction/Cargo.toml b/contracts/auction/Cargo.toml index a102f6b0..ce5bb295 100644 --- a/contracts/auction/Cargo.toml +++ b/contracts/auction/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "neutron-auction" -version = "1.0.0" +version = "1.0.1" authors = ["Sergey Ratiashvili "] edition = "2021" description = "Contract to facilitate cNTRN-NATIVE LP Pool bootstrapping via auction" @@ -23,7 +23,7 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -astroport = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v2.5.0" } +astroport = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v3.6.0" } astroport-periphery = { path = "../../packages/astroport_periphery" } cw20 = { version = "1.0.1" } diff --git a/contracts/auction/schema/neutron-auction.json b/contracts/auction/schema/neutron-auction.json index d24e56c2..36b6b524 100644 --- a/contracts/auction/schema/neutron-auction.json +++ b/contracts/auction/schema/neutron-auction.json @@ -1,6 +1,6 @@ { "contract_name": "neutron-auction", - "contract_version": "1.0.0", + "contract_version": "1.0.1", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/contracts/lockdrop/Cargo.toml b/contracts/lockdrop/Cargo.toml index dc095df4..921d6941 100644 --- a/contracts/lockdrop/Cargo.toml +++ b/contracts/lockdrop/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "neutron-lockdrop" -version = "1.2.1" +version = "2.0.0" authors = ["_astromartian"] edition = "2021" @@ -24,7 +24,7 @@ library = [] [dependencies] -astroport = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v2.5.0" } +astroport = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v3.6.0" } credits = {path = "../credits"} astroport-periphery = { path = "../../packages/astroport_periphery" } terraswap = "2.6" diff --git a/contracts/lockdrop/schema/neutron-lockdrop.json b/contracts/lockdrop/schema/neutron-lockdrop.json index cb8446ed..2d894777 100644 --- a/contracts/lockdrop/schema/neutron-lockdrop.json +++ b/contracts/lockdrop/schema/neutron-lockdrop.json @@ -1,6 +1,6 @@ { "contract_name": "neutron-lockdrop", - "contract_version": "1.2.1", + "contract_version": "2.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -280,6 +280,19 @@ }, "additionalProperties": false }, + { + "description": "Migrations", + "type": "object", + "required": [ + "migrate_from_xyk_to_cl" + ], + "properties": { + "migrate_from_xyk_to_cl": { + "$ref": "#/definitions/MigrateExecuteMsg" + } + }, + "additionalProperties": false + }, { "description": "Callbacks; only callable by the contract itself.", "type": "object", @@ -500,6 +513,98 @@ } }, "additionalProperties": false + }, + { + "type": "object", + "required": [ + "migrate_pair_step1" + ], + "properties": { + "migrate_pair_step1": { + "type": "object", + "required": [ + "pool_type" + ], + "properties": { + "pool_type": { + "$ref": "#/definitions/PoolType" + }, + "slippage_tolerance": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "migrate_pair_step2" + ], + "properties": { + "migrate_pair_step2": { + "type": "object", + "required": [ + "pool_type", + "prev_ntrn_balance", + "prev_reward_amount", + "prev_token_balance" + ], + "properties": { + "pool_type": { + "$ref": "#/definitions/PoolType" + }, + "prev_ntrn_balance": { + "$ref": "#/definitions/Uint128" + }, + "prev_reward_amount": { + "$ref": "#/definitions/Uint128" + }, + "prev_token_balance": { + "$ref": "#/definitions/Uint128" + }, + "slippage_tolerance": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "migrate_pair_step3" + ], + "properties": { + "migrate_pair_step3": { + "type": "object", + "required": [ + "pool_type" + ], + "properties": { + "pool_type": { + "$ref": "#/definitions/PoolType" + } + } + } + }, + "additionalProperties": false } ] }, @@ -523,6 +628,60 @@ } } }, + "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" + }, + "MigrateExecuteMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "migrate_liquidity" + ], + "properties": { + "migrate_liquidity": { + "type": "object", + "properties": { + "slippage_tolerance": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "migrate_users" + ], + "properties": { + "migrate_users": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + } + ] + }, "PoolType": { "type": "string", "enum": [ @@ -751,7 +910,29 @@ "migrate": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "MigrateMsg", - "type": "object" + "type": "object", + "required": [ + "max_slippage", + "new_atom_token", + "new_usdc_token" + ], + "properties": { + "max_slippage": { + "$ref": "#/definitions/Decimal" + }, + "new_atom_token": { + "type": "string" + }, + "new_usdc_token": { + "type": "string" + } + }, + "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": { diff --git a/contracts/lockdrop/schema/raw/execute.json b/contracts/lockdrop/schema/raw/execute.json index 9d5d587d..870405ee 100644 --- a/contracts/lockdrop/schema/raw/execute.json +++ b/contracts/lockdrop/schema/raw/execute.json @@ -172,6 +172,19 @@ }, "additionalProperties": false }, + { + "description": "Migrations", + "type": "object", + "required": [ + "migrate_from_xyk_to_cl" + ], + "properties": { + "migrate_from_xyk_to_cl": { + "$ref": "#/definitions/MigrateExecuteMsg" + } + }, + "additionalProperties": false + }, { "description": "Callbacks; only callable by the contract itself.", "type": "object", @@ -392,6 +405,98 @@ } }, "additionalProperties": false + }, + { + "type": "object", + "required": [ + "migrate_pair_step1" + ], + "properties": { + "migrate_pair_step1": { + "type": "object", + "required": [ + "pool_type" + ], + "properties": { + "pool_type": { + "$ref": "#/definitions/PoolType" + }, + "slippage_tolerance": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "migrate_pair_step2" + ], + "properties": { + "migrate_pair_step2": { + "type": "object", + "required": [ + "pool_type", + "prev_ntrn_balance", + "prev_reward_amount", + "prev_token_balance" + ], + "properties": { + "pool_type": { + "$ref": "#/definitions/PoolType" + }, + "prev_ntrn_balance": { + "$ref": "#/definitions/Uint128" + }, + "prev_reward_amount": { + "$ref": "#/definitions/Uint128" + }, + "prev_token_balance": { + "$ref": "#/definitions/Uint128" + }, + "slippage_tolerance": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "migrate_pair_step3" + ], + "properties": { + "migrate_pair_step3": { + "type": "object", + "required": [ + "pool_type" + ], + "properties": { + "pool_type": { + "$ref": "#/definitions/PoolType" + } + } + } + }, + "additionalProperties": false } ] }, @@ -415,6 +520,60 @@ } } }, + "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" + }, + "MigrateExecuteMsg": { + "oneOf": [ + { + "type": "object", + "required": [ + "migrate_liquidity" + ], + "properties": { + "migrate_liquidity": { + "type": "object", + "properties": { + "slippage_tolerance": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "migrate_users" + ], + "properties": { + "migrate_users": { + "type": "object", + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + } + ] + }, "PoolType": { "type": "string", "enum": [ diff --git a/contracts/lockdrop/schema/raw/migrate.json b/contracts/lockdrop/schema/raw/migrate.json index 87b18ea7..e8d4f59c 100644 --- a/contracts/lockdrop/schema/raw/migrate.json +++ b/contracts/lockdrop/schema/raw/migrate.json @@ -1,5 +1,27 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "MigrateMsg", - "type": "object" + "type": "object", + "required": [ + "max_slippage", + "new_atom_token", + "new_usdc_token" + ], + "properties": { + "max_slippage": { + "$ref": "#/definitions/Decimal" + }, + "new_atom_token": { + "type": "string" + }, + "new_usdc_token": { + "type": "string" + } + }, + "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/lockdrop/src/contract.rs b/contracts/lockdrop/src/contract.rs index 50aee229..fa89a7c5 100644 --- a/contracts/lockdrop/src/contract.rs +++ b/contracts/lockdrop/src/contract.rs @@ -1,4 +1,5 @@ use std::cmp::min; +use std::collections::HashMap; use std::convert::TryInto; use std::str::FromStr; @@ -22,13 +23,15 @@ 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, + LockUpInfoSummary, LockupInfoV2, MigrateExecuteMsg, MigrateMsg, MigrationState, PoolInfo, + PoolInfoV2, 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, + CompatibleLoader, ASSET_POOLS, ASSET_POOLS_V2, CONFIG, LOCKUP_INFO, MIGRATION_MAX_SLIPPAGE, + MIGRATION_STATUS, MIGRATION_USERS_COUNTER, MIGRATION_USERS_DEFAULT_LIMIT, OWNERSHIP_PROPOSAL, + STATE, TOTAL_USER_LOCKUP_AMOUNT, USER_INFO, }; const AIRDROP_REWARDS_MULTIPLIER: &str = "1.0"; @@ -151,6 +154,18 @@ pub fn instantiate( /// * **ExecuteMsg::ClaimOwnership {}** Claims contract ownership. #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { + let migration_state = MIGRATION_STATUS.may_load(deps.storage)?; + if migration_state.unwrap_or(MigrationState::Completed) != MigrationState::Completed { + match msg { + ExecuteMsg::MigrateFromXykToCl(..) => {} + ExecuteMsg::Callback(..) => {} + _ => { + return Err(StdError::generic_err( + "Contract is in migration state. Please wait for migration to complete.", + )) + } + } + } match msg { ExecuteMsg::Receive(msg) => receive_cw20(deps, env, info, msg), ExecuteMsg::ClaimRewardsAndOptionallyUnlock { @@ -166,6 +181,7 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S withdraw_lp_stake, ), ExecuteMsg::Callback(msg) => _handle_callback(deps, env, info, msg), + ExecuteMsg::MigrateFromXykToCl(msg) => _handle_migrate(deps, env, info, msg), ExecuteMsg::ProposeNewOwner { owner, expires_in } => { let config: Config = CONFIG.load(deps.storage)?; propose_new_owner( @@ -215,6 +231,20 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> S } } +fn _handle_migrate( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: MigrateExecuteMsg, +) -> StdResult { + match msg { + MigrateExecuteMsg::MigrateLiquidity { slippage_tolerance } => { + migrate_liquidity(deps, env, info, slippage_tolerance) + } + MigrateExecuteMsg::MigrateUsers { limit } => migrate_users(deps, env, info, limit), + } +} + /// 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. @@ -303,9 +333,42 @@ fn _handle_callback( duration, withdraw_lp_stake, ), + CallbackMsg::MigratePairStep1 { + pool_type, + slippage_tolerance, + } => migrate_pair_step_1(deps, info, env, pool_type, slippage_tolerance), + CallbackMsg::MigratePairStep2 { + pool_type, + prev_ntrn_balance, + prev_token_balance, + slippage_tolerance, + prev_reward_amount, + } => migrate_pair_step_2( + deps, + info, + env, + MigratePairStep2Data { + pool_type, + slippage_tolerance, + prev_ntrn_balance, + prev_token_balance, + prev_reward_amount, + }, + ), + CallbackMsg::MigratePairStep3 { pool_type } => { + migrate_pair_step_3(deps, info, env, pool_type) + } } } +struct MigratePairStep2Data { + pub pool_type: PoolType, + pub prev_ntrn_balance: Uint128, + pub prev_token_balance: Uint128, + pub slippage_tolerance: Option, + pub prev_reward_amount: Uint128, +} + /// Exposes all the queries available in the contract. /// ## Params /// * **deps** is an object of type [`Deps`]. @@ -349,6 +412,18 @@ fn _handle_callback( /// }** 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 { + let migration_state = MIGRATION_STATUS.may_load(deps.storage)?; + if migration_state.unwrap_or(MigrationState::Completed) != MigrationState::Completed { + match msg { + QueryMsg::QueryUserLockupTotalAtHeight { .. } + | QueryMsg::QueryLockupTotalAtHeight { .. } => { + return Err(StdError::generic_err( + "Contract is in migration state. Please wait for migration to complete.", + )) + } + _ => {} + } + } match msg { QueryMsg::Config {} => to_binary(&CONFIG.load(deps.storage)?), QueryMsg::State {} => to_binary(&query_state(deps)?), @@ -392,8 +467,448 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { /// /// * **_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()) +pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> StdResult { + let mut attrs = vec![attr("action", "migrate")]; + + ASSET_POOLS_V2.save( + deps.storage, + PoolType::ATOM, + &PoolInfoV2 { + lp_token: deps.api.addr_validate(&msg.new_atom_token)?, + amount_in_lockups: Uint128::zero(), + }, + )?; + ASSET_POOLS_V2.save( + deps.storage, + PoolType::USDC, + &PoolInfoV2 { + lp_token: deps.api.addr_validate(&msg.new_usdc_token)?, + amount_in_lockups: Uint128::zero(), + }, + )?; + MIGRATION_MAX_SLIPPAGE.save(deps.storage, &msg.max_slippage)?; + + attrs.push(attr("new_atom_token", msg.new_atom_token)); + attrs.push(attr("new_usdc_token", msg.new_usdc_token)); + + MIGRATION_STATUS.save(deps.storage, &MigrationState::MigrateLiquidity)?; + + Ok(Response::default().add_attributes(attrs)) +} + +fn migrate_liquidity( + deps: DepsMut, + env: Env, + _info: MessageInfo, + slippage_tolerance: Option, +) -> StdResult { + let migration_state = MIGRATION_STATUS.load(deps.storage)?; + + if migration_state != MigrationState::MigrateLiquidity { + return Err(StdError::generic_err( + "Migration is not in the correct state", + )); + } + + let max_slippage = MIGRATION_MAX_SLIPPAGE.load(deps.storage)?; + if slippage_tolerance.unwrap_or_default() > max_slippage { + return Err(StdError::generic_err("Slippage tolerance is too high")); + } + + let attrs = vec![attr("action", "migrate_liquidity")]; + let msgs = vec![ + CallbackMsg::MigratePairStep1 { + pool_type: PoolType::ATOM, + slippage_tolerance, + } + .to_cosmos_msg(&env)?, + CallbackMsg::MigratePairStep1 { + pool_type: PoolType::USDC, + slippage_tolerance, + } + .to_cosmos_msg(&env)?, + ]; + Ok(Response::new().add_messages(msgs).add_attributes(attrs)) +} + +fn get_lp_token_pool_addr(deps: Deps, lp_token_addr: &Addr) -> StdResult { + let minter_response: cw20::MinterResponse = deps + .querier + .query_wasm_smart(lp_token_addr.to_string(), &cw20::Cw20QueryMsg::Minter {})?; + Ok(minter_response.minter) +} + +fn get_reward_amount( + deps: Deps, + env: &Env, + astroport_lp_token: &String, + generator: &String, +) -> StdResult { + 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; + + Ok(reward_token_balance) +} + +fn migrate_pair_step_1( + deps: DepsMut, + _info: MessageInfo, + env: Env, + pool_type: PoolType, + slippage_tolerance: Option, +) -> StdResult { + let config: Config = CONFIG.load(deps.storage)?; + let generator = config + .generator + .as_ref() + .ok_or_else(|| StdError::generic_err("Generator address hasn't set yet!"))?; + + // get current ntrn contract balance + let current_ntrn_balance = deps + .querier + .query_balance(&env.contract.address, UNTRN_DENOM)? + .amount; + + // calculate current amount of token (ATOM|USDC) + let current_token_balance = { + let new_pool_info: PoolInfoV2 = ASSET_POOLS_V2.load(deps.storage, pool_type)?; + let new_pool_addr = get_lp_token_pool_addr(deps.as_ref(), &new_pool_info.lp_token)?; + let new_pool_info: astroport::pair::PoolResponse = deps + .querier + .query_wasm_smart(new_pool_addr, &astroport::pair::QueryMsg::Pool {})?; + let token_denom = new_pool_info + .assets + .iter() + .find_map(|x| match &x.info { + AssetInfo::NativeToken { denom } if denom != UNTRN_DENOM => Some(denom.clone()), + _ => None, + }) + .ok_or_else(|| StdError::generic_err("No second leg of pair found"))?; + deps.querier + .query_balance(&env.contract.address, token_denom.as_str())? + .amount + }; + + let mut attrs = vec![ + attr("action", "migrate_pair_step_1"), + attr("pool_type", pool_type), + ]; + let mut msgs = vec![]; + let pool: PoolInfo = ASSET_POOLS.load(deps.storage, pool_type)?; + let pool_addr = get_lp_token_pool_addr(deps.as_ref(), &pool.lp_token)?; + + //get reward amount so we can calculate difference + let reward_amount = get_reward_amount( + deps.as_ref(), + &env, + &pool.lp_token.to_string(), + &generator.to_string(), + )?; + + //unstake from generator + attrs.push(attr( + "unstake_from_generator", + pool.amount_in_lockups.to_string(), + )); + msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: generator.to_string(), + funds: vec![], + msg: to_binary(&astroport::generator::ExecuteMsg::Withdraw { + lp_token: pool.lp_token.to_string(), + amount: pool.amount_in_lockups, + })?, + })); + + //withdraw lp tokens from pool + msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: pool.lp_token.to_string(), + funds: vec![], + msg: to_binary(&Cw20ExecuteMsg::Send { + contract: pool_addr, + amount: pool.amount_in_lockups, + msg: to_binary(&astroport::pair::Cw20HookMsg::WithdrawLiquidity { assets: vec![] })?, + })?, + })); + attrs.push(attr( + "withdraw_from_pool_amount", + pool.amount_in_lockups.to_string(), + )); + + //next step + msgs.push( + CallbackMsg::MigratePairStep2 { + pool_type, + prev_ntrn_balance: current_ntrn_balance, + prev_token_balance: current_token_balance, + prev_reward_amount: reward_amount, + slippage_tolerance, + } + .to_cosmos_msg(&env)?, + ); + + Ok(Response::new().add_messages(msgs).add_attributes(attrs)) +} + +fn migrate_pair_step_2( + deps: DepsMut, + _info: MessageInfo, + env: Env, + data: MigratePairStep2Data, +) -> StdResult { + let config: Config = CONFIG.load(deps.storage)?; + let generator = config + .generator + .as_ref() + .ok_or_else(|| StdError::generic_err("Generator address hasn't set yet!"))?; + let mut attrs = vec![ + attr("action", "migrate_pair_step_2"), + attr("pool_type", data.pool_type), + ]; + let mut pool_info: PoolInfo = ASSET_POOLS.load(deps.storage, data.pool_type)?; + let new_pool_info: PoolInfoV2 = ASSET_POOLS_V2.load(deps.storage, data.pool_type)?; + let new_pool_addr = get_lp_token_pool_addr(deps.as_ref(), &new_pool_info.lp_token)?; + let mut msgs = vec![]; + let current_ntrn_balance = deps + .querier + .query_balance(&env.contract.address, UNTRN_DENOM)? + .amount; + attrs.push(attr( + "ntrn_balance_change", + (current_ntrn_balance - data.prev_ntrn_balance).to_string(), + )); + // update reward info so users can claim after the migration + pool_info.generator_ntrn_per_share = pool_info.generator_ntrn_per_share.checked_add({ + let reward_token_balance = get_reward_amount( + deps.as_ref(), + &env, + &pool_info.lp_token.to_string(), + &generator.to_string(), + )?; + attrs.push(attr( + "reward_token_balance", + reward_token_balance.to_string(), + )); + let base_reward_received = reward_token_balance.checked_sub(data.prev_reward_amount)?; + Decimal::from_ratio(base_reward_received, pool_info.amount_in_lockups) + })?; + attrs.push(attr( + "generator_ntrn_per_share", + pool_info.generator_ntrn_per_share.to_string(), + )); + ASSET_POOLS.save(deps.storage, data.pool_type, &pool_info, env.block.height)?; + // calculate amount of NTRN claimed from pool + let ntrn_to_new_pool = current_ntrn_balance - data.prev_ntrn_balance; + let new_pool_info: astroport::pair::PoolResponse = deps + .querier + .query_wasm_smart(&new_pool_addr, &astroport::pair::QueryMsg::Pool {})?; + let token_denom = new_pool_info + .assets + .iter() + .find_map(|x| match &x.info { + AssetInfo::NativeToken { denom } if denom != UNTRN_DENOM => Some(denom.clone()), + _ => None, + }) + .ok_or_else(|| StdError::generic_err("No second leg of pair found"))?; + attrs.push(attr("token_denom", token_denom.clone())); + // calculate current amount of token (ATOM|USDC) + let current_token_balance = deps + .querier + .query_balance(&env.contract.address, token_denom.as_str())? + .amount; + // calculate amount of token claimed from the pool + let token_to_new_pool = current_token_balance - data.prev_token_balance; + attrs.push(attr("token_balance_change", token_to_new_pool.to_string())); + + //construct message to send NTRN and (ATOM|USDC) to new pool + let base = Asset { + amount: ntrn_to_new_pool, + info: AssetInfo::NativeToken { + denom: UNTRN_DENOM.to_string(), + }, + }; + let other = Asset { + amount: token_to_new_pool, + info: AssetInfo::NativeToken { + denom: token_denom.to_string(), + }, + }; + let mut funds = vec![ + Coin { + denom: UNTRN_DENOM.to_string(), + amount: ntrn_to_new_pool, + }, + Coin { + denom: token_denom, + amount: token_to_new_pool, + }, + ]; + funds.sort_by(|a, b| a.denom.cmp(&b.denom)); + msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: new_pool_addr, + funds, + msg: to_binary(&astroport::pair::ExecuteMsg::ProvideLiquidity { + assets: vec![base, other], + slippage_tolerance: data.slippage_tolerance, + auto_stake: None, + receiver: None, + })?, + })); + + attrs.push(attr("provide_liquidity", "true")); + + msgs.push( + CallbackMsg::MigratePairStep3 { + pool_type: data.pool_type, + } + .to_cosmos_msg(&env)?, + ); + + Ok(Response::new().add_messages(msgs).add_attributes(attrs)) +} + +fn migrate_pair_step_3( + deps: DepsMut, + _info: MessageInfo, + env: Env, + pool_type: PoolType, +) -> StdResult { + let config: Config = CONFIG.load(deps.storage)?; + let mut attrs = vec![ + attr("action", "migrate_pair_step_3"), + attr("pool_type", pool_type), + ]; + let mut new_pool_info: PoolInfoV2 = ASSET_POOLS_V2.load(deps.storage, pool_type)?; + // get current balance of new LP token + let balance_response: cw20::BalanceResponse = deps.querier.query_wasm_smart( + &new_pool_info.lp_token, + &Cw20QueryMsg::Balance { + address: env.contract.address.to_string(), + }, + )?; + let current_balance = balance_response.balance; + attrs.push(attr("current_balance", current_balance.to_string())); + + //update pool info to reflect new LP token balance + new_pool_info.amount_in_lockups = current_balance; + ASSET_POOLS_V2.save(deps.storage, pool_type, &new_pool_info)?; + + // send stake message to generator + let stake_msgs = stake_messages( + config, + env.block.height + 1u64, + new_pool_info.lp_token, + current_balance, + )?; + + MIGRATION_STATUS.save(deps.storage, &MigrationState::MigrateUsers)?; + + Ok(Response::new() + .add_messages(stake_msgs) + .add_attributes(attrs)) +} + +fn migrate_users( + deps: DepsMut, + env: Env, + _info: MessageInfo, + limit: Option, +) -> StdResult { + let migrate_state: MigrationState = MIGRATION_STATUS.load(deps.storage)?; + if migrate_state != MigrationState::MigrateUsers { + return Err(StdError::generic_err( + "Migration is not in MigrateUsers state", + )); + } + let mut attrs = vec![attr("action", "migrate_users")]; + let limit = limit.unwrap_or(MIGRATION_USERS_DEFAULT_LIMIT); + if limit == 0 { + return Err(StdError::generic_err("Limit cannot be zero")); + } + let current_skip = MIGRATION_USERS_COUNTER + .may_load(deps.storage)? + .unwrap_or(0u32); + + let pool_types: Vec = ASSET_POOLS + .keys(deps.storage, None, None, Order::Ascending) + .collect::, StdError>>()?; + + let users: Vec = USER_INFO + .keys(deps.storage, None, None, Order::Ascending) + .skip(current_skip as usize) + .take(limit as usize) + .collect::, _>>()?; + + //calculate kfs of pools. Kf is a ratio of new pool amount in lockups to old pool amount in lockups + let mut kfs: HashMap = HashMap::new(); + for pool_type in &pool_types { + let pool_info: PoolInfo = ASSET_POOLS.load(deps.storage, *pool_type)?; + let new_pool_info: PoolInfoV2 = ASSET_POOLS_V2.load(deps.storage, *pool_type)?; + kfs.insert( + (*pool_type).into(), + Decimal::from_ratio(new_pool_info.amount_in_lockups, pool_info.amount_in_lockups), + ); + } + + if users.is_empty() { + //if no users left, finish migration and update pool numbers and token addresses + for pool_type in &pool_types { + let mut pool_info: PoolInfo = ASSET_POOLS.load(deps.storage, *pool_type)?; + let new_pool_info: PoolInfoV2 = ASSET_POOLS_V2.load(deps.storage, *pool_type)?; + pool_info.amount_in_lockups = new_pool_info.amount_in_lockups; + pool_info.lp_token = new_pool_info.lp_token; + let p: String = (*pool_type).into(); + let kf = kfs + .get(&p) + .ok_or_else(|| StdError::generic_err("Can't get kf"))?; + pool_info.generator_ntrn_per_share *= kf; + ASSET_POOLS.save(deps.storage, *pool_type, &pool_info, env.block.height)?; + } + MIGRATION_STATUS.save(deps.storage, &MigrationState::Completed)?; + attrs.push(attr("migration_completed", "true")); + } else { + attrs.push(attr("users_count", users.len().to_string())); + for user in users { + for pool_type in &pool_types { + let mut total_lokups = Uint128::zero(); + let lookup_infos: Vec<(u64, LockupInfoV2)> = LOCKUP_INFO + .prefix((*pool_type, &user)) + .range(deps.storage, None, None, Order::Ascending) + .collect::, StdError>>()?; + for (duration, mut lockup_info) in lookup_infos { + let p: String = (*pool_type).into(); + let kf = kfs + .get(&p) + .ok_or_else(|| StdError::generic_err("Can't get kf"))?; + // update user's lockup positions + lockup_info.lp_units_locked = + (*kf).checked_mul_uint128(lockup_info.lp_units_locked)?; + LOCKUP_INFO.save(deps.storage, (*pool_type, &user, duration), &lockup_info)?; + total_lokups += lockup_info.lp_units_locked; + } + // update user's total lockup amount + TOTAL_USER_LOCKUP_AMOUNT.update( + deps.storage, + (*pool_type, &user), + env.block.height, + |_lockup_amount| -> StdResult { Ok(total_lokups) }, + )?; + } + } + MIGRATION_USERS_COUNTER.save(deps.storage, &(current_skip + limit))?; + } + Ok(Response::default().add_attributes(attrs)) } /// Admin function to update Configuration parameters. Returns a default object of type [`Response`]. @@ -1295,7 +1810,7 @@ pub fn callback_withdraw_user_rewards_for_lockup_optional_withdraw( }, )?; - // Calculate claimable staking rewards for this lockup + // Calculate claimable staking rewards for this lockup (ASTRO incentives) let total_lockup_astro_rewards = pool_info .generator_ntrn_per_share .checked_mul(astroport_lp_amount.to_decimal())? @@ -1355,7 +1870,7 @@ pub fn callback_withdraw_user_rewards_for_lockup_optional_withdraw( // 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())?); + cosmos_msgs.push(pending_proxy_reward.into_msg(user_address.clone())?); } // COSMOSMSG :: If LP Tokens are staked, we unstake the amount which needs to be returned to the user diff --git a/contracts/lockdrop/src/state.rs b/contracts/lockdrop/src/state.rs index 5b817721..eeeb0f0c 100644 --- a/contracts/lockdrop/src/state.rs +++ b/contracts/lockdrop/src/state.rs @@ -2,11 +2,14 @@ use astroport::common::OwnershipProposal; use astroport::generator::PoolInfoResponse; use astroport::generator::QueryMsg as GenQueryMsg; use astroport::restricted_vector::RestrictedVector; +use astroport_periphery::lockdrop::MigrationState; +use astroport_periphery::lockdrop::PoolInfoV2; use astroport_periphery::lockdrop::PoolType; use astroport_periphery::lockdrop::{ Config, LockupInfoV1, LockupInfoV2, PoolInfo, State, UserInfo, }; use astroport_periphery::U64Key; +use cosmwasm_std::Decimal; use cosmwasm_std::{Addr, Deps, StdError, StdResult, Uint128}; use cw_storage_plus::{Item, Map, SnapshotMap, Strategy}; @@ -22,6 +25,18 @@ pub const ASSET_POOLS: SnapshotMap = SnapshotMap::new( "LiquidityPools_changelog", Strategy::EveryBlock, ); + +/// Pools V2 +pub const ASSET_POOLS_V2: Map = Map::new("liquidity_pools_v2"); +/// Migration status +pub const MIGRATION_STATUS: Item = Item::new("migration_status"); +/// Migration users current counter +pub const MIGRATION_USERS_COUNTER: Item = Item::new("migration_users_counter"); +/// Migration users default limit +pub const MIGRATION_USERS_DEFAULT_LIMIT: u32 = 30; +/// Migration max slippage for providing liquidity +pub const MIGRATION_MAX_SLIPPAGE: Item = Item::new("migration_max_slippage"); + /// 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 diff --git a/contracts/lockdrop/src/testing.rs b/contracts/lockdrop/src/testing.rs index 526c0362..9b727fa7 100644 --- a/contracts/lockdrop/src/testing.rs +++ b/contracts/lockdrop/src/testing.rs @@ -1,6 +1,7 @@ use crate::contract::{execute, instantiate, query, UNTRN_DENOM}; +use crate::state::MIGRATION_STATUS; use astroport_periphery::lockdrop::{ - Config, ExecuteMsg, InstantiateMsg, LockupRewardsInfo, QueryMsg, + Config, ExecuteMsg, InstantiateMsg, LockupRewardsInfo, MigrationState, QueryMsg, }; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; use cosmwasm_std::{coin, from_binary, Addr, Decimal256, StdError, Uint128}; @@ -9,7 +10,9 @@ use cosmwasm_std::{coin, from_binary, Addr, Decimal256, StdError, Uint128}; fn update_owner() { let mut deps = mock_dependencies(); let info = mock_info("addr0000", &[]); - + MIGRATION_STATUS + .save(&mut deps.storage, &MigrationState::Completed) + .unwrap(); let owner = Addr::unchecked("owner"); let token_info_manager = Addr::unchecked("token_info_manager"); @@ -100,7 +103,9 @@ fn increase_ntrn_incentives() { let owner = Addr::unchecked("owner"); let token_info_manager = Addr::unchecked("token_info_manager"); - + MIGRATION_STATUS + .save(&mut deps.storage, &MigrationState::Completed) + .unwrap(); let env = mock_env(); let msg = InstantiateMsg { diff --git a/contracts/vesting-lp/Cargo.toml b/contracts/vesting-lp/Cargo.toml index cc24f36c..776d6b83 100644 --- a/contracts/vesting-lp/Cargo.toml +++ b/contracts/vesting-lp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vesting-lp" -version = "1.1.0" +version = "2.0.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." @@ -15,7 +15,8 @@ library = [] [dependencies] cw2 = { version = "0.15" } vesting-base = {path = "../../packages/vesting-base"} -astroport = { path = "../../packages/astroport", default-features = false } +vesting-base-lp = {path = "../../packages/vesting-base-lp"} +astroport = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v3.6.0" } cosmwasm-schema = { version = "1.1", default-features = false } cosmwasm-std = { version = "1.1" } cw-storage-plus = "0.15" @@ -24,4 +25,4 @@ cw-storage-plus = "0.15" 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" \ No newline at end of file +cw-utils = "0.15" diff --git a/contracts/vesting-lp/schema/vesting-lp.json b/contracts/vesting-lp/schema/vesting-lp.json index 395775f8..1a16ca88 100644 --- a/contracts/vesting-lp/schema/vesting-lp.json +++ b/contracts/vesting-lp/schema/vesting-lp.json @@ -1,6 +1,6 @@ { "contract_name": "vesting-lp", - "contract_version": "1.1.0", + "contract_version": "2.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", 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/contracts/vesting-lp/src/tests/integration.rs b/contracts/vesting-lp/src/tests/integration.rs deleted file mode 100644 index 5a150e97..00000000 --- a/contracts/vesting-lp/src/tests/integration.rs +++ /dev/null @@ -1,1281 +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::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/src/tests/mod.rs b/contracts/vesting-lp/src/tests/mod.rs deleted file mode 100644 index 6d3bbe60..00000000 --- a/contracts/vesting-lp/src/tests/mod.rs +++ /dev/null @@ -1 +0,0 @@ -mod integration; diff --git a/contracts/vesting-lti/src/contract.rs b/contracts/vesting-lti/src/contract.rs index 2ba9d135..6c037613 100644 --- a/contracts/vesting-lti/src/contract.rs +++ b/contracts/vesting-lti/src/contract.rs @@ -1,11 +1,10 @@ use crate::msg::InstantiateMsg; use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; use cw2::set_contract_version; -use vesting_base::msg::MigrateMsg; use vesting_base::{ builder::VestingBaseBuilder, error::ContractError, - handlers::{execute as base_execute, migrate as base_migrate, query as base_query}, + handlers::{execute as base_execute, query as base_query}, msg::{ExecuteMsg, QueryMsg}, }; @@ -55,9 +54,3 @@ pub fn execute( pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { base_query(deps, env, msg) } - -// Exposes migrate function. -#[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/packages/astroport/src/mock_querier.rs b/packages/astroport/src/mock_querier.rs index 528fc737..2db60349 100644 --- a/packages/astroport/src/mock_querier.rs +++ b/packages/astroport/src/mock_querier.rs @@ -10,7 +10,7 @@ use crate::asset::PairInfo; use crate::factory::QueryMsg as FactoryQueryMsg; use cw20::{BalanceResponse, Cw20QueryMsg, TokenInfoResponse}; -/// mock_dependencies is a drop-in replacement for cosmwasm_std::tests::mock_dependencies +/// mock_dependencies is a drop-in replacement for cosmwasm_std::testing::mock_dependencies /// This uses the Astroport CustomQuerier. pub fn mock_dependencies( contract_balance: &[Coin], diff --git a/packages/astroport_periphery/Cargo.toml b/packages/astroport_periphery/Cargo.toml index bacba541..15e0798b 100644 --- a/packages/astroport_periphery/Cargo.toml +++ b/packages/astroport_periphery/Cargo.toml @@ -17,7 +17,7 @@ cw-storage-plus = "0.15.1" cosmwasm-std = { version = "1.0" } cosmwasm-schema = "1.2.1" terraswap = "2.6" -astroport = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v2.5.0" } +astroport = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v3.6.0" } schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } diff --git a/packages/astroport_periphery/src/lockdrop.rs b/packages/astroport_periphery/src/lockdrop.rs index 6dcbd342..c60e830f 100644 --- a/packages/astroport_periphery/src/lockdrop.rs +++ b/packages/astroport_periphery/src/lockdrop.rs @@ -149,6 +149,8 @@ pub enum ExecuteMsg { duration: u64, withdraw_lp_stake: bool, }, + /// Migrations + MigrateFromXykToCl(MigrateExecuteMsg), /// Callbacks; only callable by the contract itself. Callback(CallbackMsg), /// ProposeNewOwner creates a proposal to change contract ownership. @@ -165,6 +167,13 @@ pub enum ExecuteMsg { ClaimOwnership {}, } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum MigrateExecuteMsg { + MigrateLiquidity { slippage_tolerance: Option }, + MigrateUsers { limit: Option }, +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum Cw20HookMsg { @@ -192,12 +201,20 @@ pub enum CallbackMsg { duration: u64, withdraw_lp_stake: bool, }, - // WithdrawLiquidityFromTerraswapCallback { - // terraswap_lp_token: Addr, - // astroport_pool: Addr, - // prev_assets: [terraswap::asset::Asset; 2], - // slippage_tolerance: Option, - // }, + MigratePairStep1 { + pool_type: PoolType, + slippage_tolerance: Option, + }, + MigratePairStep2 { + prev_ntrn_balance: Uint128, + prev_token_balance: Uint128, + prev_reward_amount: Uint128, + pool_type: PoolType, + slippage_tolerance: Option, + }, + MigratePairStep3 { + pool_type: PoolType, + }, } // Modified from @@ -242,7 +259,11 @@ pub enum QueryMsg { } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct MigrateMsg {} +pub struct MigrateMsg { + pub new_atom_token: String, + pub new_usdc_token: String, + pub max_slippage: Decimal, +} #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema)] pub struct MigrationInfo { @@ -292,6 +313,19 @@ pub struct State { pub total_incentives_share: Uint128, } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, Default)] +pub enum MigrationState { + #[default] + /// Migration is started + Started, + /// + MigrateLiquidity, + /// Liquidity is migrated, can migrate users with pagination + MigrateUsers, + /// Migration is completed + Completed, +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct PoolInfo { pub lp_token: Addr, @@ -309,6 +343,12 @@ pub struct PoolInfo { pub is_staked: bool, } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct PoolInfoV2 { + pub lp_token: Addr, + pub amount_in_lockups: Uint128, +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, Default)] pub struct UserInfo { /// Total NTRN tokens user received as rewards for participation in the lockdrop diff --git a/packages/vesting-base-lp/Cargo.toml b/packages/vesting-base-lp/Cargo.toml new file mode 100644 index 00000000..687c024f --- /dev/null +++ b/packages/vesting-base-lp/Cargo.toml @@ -0,0 +1,25 @@ +[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"} +astroport = { git = "https://github.com/astroport-fi/astroport-core.git", tag = "v3.6.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..6f55e8b3 --- /dev/null +++ b/packages/vesting-base-lp/src/error.rs @@ -0,0 +1,68 @@ +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("Vesting token is already set!")] + VestingTokenAlreadySet {}, + + #[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..961d834b --- /dev/null +++ b/packages/vesting-base-lp/src/handlers.rs @@ -0,0 +1,818 @@ +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 = MIGRATION_STATUS.may_load(deps.storage)?; + if migration_state.unwrap_or(MigrationState::Completed) != MigrationState::Completed { + match msg { + ExecuteMsg::MigrateLiquidity { + slippage_tolerance: _, + } => {} + 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::MigrateLiquidity { slippage_tolerance } => { + execute_migrate_liquidity(deps, env, slippage_tolerance) + } + 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 {}); + } + 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 {}) +} + +fn execute_migrate_liquidity( + deps: DepsMut, + 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 vesting_infos = read_vesting_infos( + deps.as_ref(), + migration_config.last_processed_user, + Some(migration_config.batch_size), + None, + )?; + + let vesting_accounts: Vec<_> = vesting_infos + .into_iter() + .map(|(address, info)| VestingAccountResponse { address, info }) + .collect(); + + if vesting_accounts.is_empty() { + MIGRATION_STATUS.save(deps.storage, &MigrationState::Completed)?; + } + let mut resp = Response::default(); + + // get pairs LP token addresses + let pair_info: PairInfo = deps + .querier + .query_wasm_smart(migration_config.xyk_pair.clone(), &PairQueryMsg::Pair {})?; + + for user in vesting_accounts.into_iter() { + 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(VestingSchedulePoint { + time: end_point.time, + amount: balance_diff, + }) + } 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![new_schedule], + 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)?; + + Ok(Response::default()) +} + +/// Exposes all the queries available in the contract. +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { + let migration_state = MIGRATION_STATUS.may_load(deps.storage)?; + if migration_state.unwrap_or(MigrationState::Completed) != 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())?, + batch_size: msg.batch_size, + last_processed_user: None, + new_lp_token: deps.api.addr_validate(msg.new_lp_token.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(); + state.total_released = 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..30cd9d1f --- /dev/null +++ b/packages/vesting-base-lp/src/msg.rs @@ -0,0 +1,209 @@ +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 }, + /// + MigrateLiquidity { slippage_tolerance: 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 batch_size: u32, +} +/// 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..40147ed9 --- /dev/null +++ b/packages/vesting-base-lp/src/types.rs @@ -0,0 +1,131 @@ +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 last_processed_user: Option, + pub batch_size: u32, +} + +#[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 + } + } +} From c8184c689a8ea58cf5e3d58f6d0737c044592aca Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Fri, 20 Oct 2023 15:54:47 +0300 Subject: [PATCH 02/14] add missing set_contract_version in migrate --- contracts/lockdrop/src/contract.rs | 1 + contracts/vesting-investors/src/contract.rs | 1 + contracts/vesting-lp/src/contract.rs | 1 + 3 files changed, 3 insertions(+) diff --git a/contracts/lockdrop/src/contract.rs b/contracts/lockdrop/src/contract.rs index fa89a7c5..65b1bfef 100644 --- a/contracts/lockdrop/src/contract.rs +++ b/contracts/lockdrop/src/contract.rs @@ -493,6 +493,7 @@ pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> StdResult MIGRATION_STATUS.save(deps.storage, &MigrationState::MigrateLiquidity)?; + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; Ok(Response::default().add_attributes(attrs)) } diff --git a/contracts/vesting-investors/src/contract.rs b/contracts/vesting-investors/src/contract.rs index d9a3dde7..e8e88ad2 100644 --- a/contracts/vesting-investors/src/contract.rs +++ b/contracts/vesting-investors/src/contract.rs @@ -51,5 +51,6 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { /// Exposes migrate function. #[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; base_migrate(deps, env, msg) } diff --git a/contracts/vesting-lp/src/contract.rs b/contracts/vesting-lp/src/contract.rs index bf18ed13..9afbf988 100644 --- a/contracts/vesting-lp/src/contract.rs +++ b/contracts/vesting-lp/src/contract.rs @@ -51,5 +51,6 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { /// Exposes migrate functions available in the contract. #[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; base_migrate(deps, env, msg) } From 8cb28493571b95a88762c07210778af784533fb2 Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Fri, 20 Oct 2023 15:55:49 +0300 Subject: [PATCH 03/14] fix type: total_lokups -> total_lockups --- contracts/lockdrop/src/contract.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/lockdrop/src/contract.rs b/contracts/lockdrop/src/contract.rs index 65b1bfef..613a1873 100644 --- a/contracts/lockdrop/src/contract.rs +++ b/contracts/lockdrop/src/contract.rs @@ -882,7 +882,7 @@ fn migrate_users( attrs.push(attr("users_count", users.len().to_string())); for user in users { for pool_type in &pool_types { - let mut total_lokups = Uint128::zero(); + let mut total_lockups = Uint128::zero(); let lookup_infos: Vec<(u64, LockupInfoV2)> = LOCKUP_INFO .prefix((*pool_type, &user)) .range(deps.storage, None, None, Order::Ascending) @@ -896,14 +896,14 @@ fn migrate_users( lockup_info.lp_units_locked = (*kf).checked_mul_uint128(lockup_info.lp_units_locked)?; LOCKUP_INFO.save(deps.storage, (*pool_type, &user, duration), &lockup_info)?; - total_lokups += lockup_info.lp_units_locked; + total_lockups += lockup_info.lp_units_locked; } // update user's total lockup amount TOTAL_USER_LOCKUP_AMOUNT.update( deps.storage, (*pool_type, &user), env.block.height, - |_lockup_amount| -> StdResult { Ok(total_lokups) }, + |_lockup_amount| -> StdResult { Ok(total_lockups) }, )?; } } From 06ea3418e8ff87f1183e4f7b80b5ab0096cbbbbb Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Fri, 20 Oct 2023 16:30:48 +0300 Subject: [PATCH 04/14] replace fire and forget submsg creation with msg creation --- packages/vesting-base-lp/src/ext_managed.rs | 6 ++---- packages/vesting-base-lp/src/handlers.rs | 4 ++-- packages/vesting-base/src/ext_managed.rs | 6 ++---- packages/vesting-base/src/handlers.rs | 4 ++-- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/packages/vesting-base-lp/src/ext_managed.rs b/packages/vesting-base-lp/src/ext_managed.rs index b11cc1b0..9d9c114d 100644 --- a/packages/vesting-base-lp/src/ext_managed.rs +++ b/packages/vesting-base-lp/src/ext_managed.rs @@ -3,9 +3,7 @@ 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, -}; +use cosmwasm_std::{attr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Uint128}; /// Contains the managed extension check and routing of the message. pub(crate) fn handle_execute_managed_msg( @@ -86,7 +84,7 @@ fn remove_vesting_accounts( let transfer_msg = vesting_token .with_balance(amount_to_claw_back) .into_msg(clawback_address.clone())?; - response = response.add_submessage(SubMsg::new(transfer_msg)); + response = response.add_message(transfer_msg); vesting_state(config.extensions.historical).update::<_, ContractError>( deps.storage, diff --git a/packages/vesting-base-lp/src/handlers.rs b/packages/vesting-base-lp/src/handlers.rs index 961d834b..2efe6a98 100644 --- a/packages/vesting-base-lp/src/handlers.rs +++ b/packages/vesting-base-lp/src/handlers.rs @@ -21,7 +21,7 @@ use astroport::pair::{ }; use cosmwasm_std::{ attr, from_binary, to_binary, Addr, Binary, Coin, CosmosMsg, Decimal, Deps, DepsMut, Env, - MessageInfo, Response, StdError, StdResult, Storage, SubMsg, Uint128, WasmMsg, + MessageInfo, Response, StdError, StdResult, Storage, Uint128, WasmMsg, }; use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg, Cw20ReceiveMsg}; use cw_utils::must_pay; @@ -241,7 +241,7 @@ fn claim( 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)); + response = response.add_message(transfer_msg); sender_vesting_info.released_amount = sender_vesting_info .released_amount diff --git a/packages/vesting-base/src/ext_managed.rs b/packages/vesting-base/src/ext_managed.rs index 94b68d20..e8522ce3 100644 --- a/packages/vesting-base/src/ext_managed.rs +++ b/packages/vesting-base/src/ext_managed.rs @@ -3,9 +3,7 @@ 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, -}; +use cosmwasm_std::{attr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Uint128}; /// Contains the managed extension check and routing of the message. pub(crate) fn handle_execute_managed_msg( @@ -86,7 +84,7 @@ fn remove_vesting_accounts( 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)); + response = response.add_message(transfer_msg); vesting_state(config.extensions.historical).update::<_, ContractError>( deps.storage, diff --git a/packages/vesting-base/src/handlers.rs b/packages/vesting-base/src/handlers.rs index 7797db28..6872016e 100644 --- a/packages/vesting-base/src/handlers.rs +++ b/packages/vesting-base/src/handlers.rs @@ -13,7 +13,7 @@ use astroport::asset::{addr_opt_validate, token_asset_info, AssetInfo, AssetInfo 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, + StdError, StdResult, Storage, Uint128, }; use cw20::Cw20ReceiveMsg; use cw_utils::must_pay; @@ -220,7 +220,7 @@ fn claim( &deps.querier, recipient.unwrap_or_else(|| info.sender.to_string()), )?; - response = response.add_submessage(SubMsg::new(transfer_msg)); + response = response.add_message(transfer_msg); sender_vesting_info.released_amount = sender_vesting_info .released_amount From 8a1cd7a1b5c85bb9d3b07ae946bd8d870ca98639 Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Fri, 20 Oct 2023 16:31:09 +0300 Subject: [PATCH 05/14] remove redundant MigrationState::Started --- packages/astroport_periphery/src/lockdrop.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/astroport_periphery/src/lockdrop.rs b/packages/astroport_periphery/src/lockdrop.rs index c60e830f..d82709cf 100644 --- a/packages/astroport_periphery/src/lockdrop.rs +++ b/packages/astroport_periphery/src/lockdrop.rs @@ -313,12 +313,9 @@ pub struct State { pub total_incentives_share: Uint128, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, Default)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] pub enum MigrationState { - #[default] - /// Migration is started - Started, - /// + /// Migration is started, the first step is liquidity migration MigrateLiquidity, /// Liquidity is migrated, can migrate users with pagination MigrateUsers, From 44c32d31824ae7e1d7c6cd49fc05b8f8c018d0d2 Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Fri, 20 Oct 2023 17:01:03 +0300 Subject: [PATCH 06/14] unify description of execute and query entry points across the repo --- contracts/astroport/oracle/src/contract.rs | 18 ++++-- contracts/auction/src/contract.rs | 12 ---- contracts/credits/src/contract.rs | 17 ++++++ contracts/cw20-merkle-airdrop/src/contract.rs | 17 ++++++ contracts/lockdrop/src/contract.rs | 57 ------------------- contracts/price-feed/src/contract.rs | 17 ++++++ contracts/vesting-investors/src/contract.rs | 17 +++++- contracts/vesting-lp/src/contract.rs | 17 +++++- contracts/vesting-lti/src/contract.rs | 17 +++++- 9 files changed, 112 insertions(+), 77 deletions(-) diff --git a/contracts/astroport/oracle/src/contract.rs b/contracts/astroport/oracle/src/contract.rs index 756eae51..85fa9dc8 100644 --- a/contracts/astroport/oracle/src/contract.rs +++ b/contracts/astroport/oracle/src/contract.rs @@ -42,10 +42,16 @@ pub fn instantiate( Ok(Response::default()) } +/// ## Description /// Exposes all the execute functions available in the contract. +/// ## Params +/// * **deps** is an object of type [`DepsMut`]. /// -/// ## Variants -/// * **ExecuteMsg::Update {}** Updates the local TWAP values for the assets in the Astroport pool. +/// * **env** is an object of type [`Env`]. +/// +/// * **info** is an object of type [`MessageInfo`]. +/// +/// * **msg** is an object of type [`ExecuteMsg`]. #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, @@ -174,10 +180,12 @@ pub fn update(deps: DepsMut, env: Env) -> Result { } /// Exposes all the queries available in the contract. +/// ## Params +/// * **deps** is an object of type [`Deps`]. +/// +/// * **_env** is an object of type [`Env`]. /// -/// ## Queries -/// * **QueryMsg::Consult { token, amount }** Validates assets and calculates a new average -/// amount with updated precision +/// * **msg** is an object of type [`QueryMsg`]. #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result { match msg { diff --git a/contracts/auction/src/contract.rs b/contracts/auction/src/contract.rs index 3e714122..ec865503 100644 --- a/contracts/auction/src/contract.rs +++ b/contracts/auction/src/contract.rs @@ -113,11 +113,6 @@ pub fn instantiate( /// * **info** is an object of type [`MessageInfo`]. /// /// * **msg** is an object of type [`ExecuteMsg`]. -/// -/// ## Execute messages -/// -/// * **ExecuteMsg::Receive(msg)** Parse incoming messages from the NTRN token. -/// #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, @@ -229,13 +224,6 @@ pub fn execute_deposit(deps: DepsMut, env: Env, info: MessageInfo) -> Result StdResult { match msg { diff --git a/contracts/credits/src/contract.rs b/contracts/credits/src/contract.rs index 10f36e6b..038fb61e 100644 --- a/contracts/credits/src/contract.rs +++ b/contracts/credits/src/contract.rs @@ -74,6 +74,16 @@ pub fn instantiate( Ok(Response::new()) } +/// ## 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`]. #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, @@ -388,6 +398,13 @@ pub fn execute_mint(deps: DepsMut, env: Env, info: MessageInfo) -> Result StdResult { match msg { diff --git a/contracts/cw20-merkle-airdrop/src/contract.rs b/contracts/cw20-merkle-airdrop/src/contract.rs index 8fe98f28..9ff8a020 100644 --- a/contracts/cw20-merkle-airdrop/src/contract.rs +++ b/contracts/cw20-merkle-airdrop/src/contract.rs @@ -80,6 +80,16 @@ pub fn instantiate( ])) } +/// ## 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`]. #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, @@ -332,6 +342,13 @@ pub fn execute_resume( Ok(Response::new().add_attributes(vec![attr("action", "resume"), attr("paused", "false")])) } +/// 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`]. #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { diff --git a/contracts/lockdrop/src/contract.rs b/contracts/lockdrop/src/contract.rs index 613a1873..709dfbbe 100644 --- a/contracts/lockdrop/src/contract.rs +++ b/contracts/lockdrop/src/contract.rs @@ -129,29 +129,6 @@ pub fn instantiate( /// * **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 { let migration_state = MIGRATION_STATUS.may_load(deps.storage)?; @@ -376,40 +353,6 @@ struct MigratePairStep2Data { /// * **_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 { let migration_state = MIGRATION_STATUS.may_load(deps.storage)?; diff --git a/contracts/price-feed/src/contract.rs b/contracts/price-feed/src/contract.rs index 9e3630b6..af73b29f 100644 --- a/contracts/price-feed/src/contract.rs +++ b/contracts/price-feed/src/contract.rs @@ -60,6 +60,16 @@ pub fn instantiate( Ok(Response::new().add_attribute("method", "instantiate")) } +/// ## 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`]. #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, @@ -177,6 +187,13 @@ pub fn migrate(_deps: DepsMut, _env: Env, _msg: Empty) -> StdResult { Ok(Response::default()) } +/// 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`]. #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { diff --git a/contracts/vesting-investors/src/contract.rs b/contracts/vesting-investors/src/contract.rs index e8e88ad2..5e3395c2 100644 --- a/contracts/vesting-investors/src/contract.rs +++ b/contracts/vesting-investors/src/contract.rs @@ -31,7 +31,16 @@ pub fn instantiate( Ok(Response::default()) } -/// Exposes execute functions available in the contract. +/// ## 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`]. #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, @@ -43,6 +52,12 @@ pub fn execute( } /// 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`]. #[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/src/contract.rs b/contracts/vesting-lp/src/contract.rs index 9afbf988..4763b030 100644 --- a/contracts/vesting-lp/src/contract.rs +++ b/contracts/vesting-lp/src/contract.rs @@ -31,7 +31,16 @@ pub fn instantiate( Ok(Response::default()) } -/// Exposes execute functions available in the contract. +/// ## 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`]. #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, @@ -43,6 +52,12 @@ pub fn execute( } /// 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`]. #[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-lti/src/contract.rs b/contracts/vesting-lti/src/contract.rs index 6c037613..d82b2b63 100644 --- a/contracts/vesting-lti/src/contract.rs +++ b/contracts/vesting-lti/src/contract.rs @@ -38,7 +38,16 @@ pub fn instantiate( Ok(Response::default()) } -/// Exposes execute functions available in the contract. +/// ## 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`]. #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, @@ -50,6 +59,12 @@ pub fn execute( } /// 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`]. #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { base_query(deps, env, msg) From 9b9a12d8552ff53e84d508d9e3aa6f71041cbf7b Mon Sep 17 00:00:00 2001 From: pr0n00gler Date: Fri, 3 Nov 2023 13:40:53 +0200 Subject: [PATCH 07/14] fmt --- contracts/astroport/oracle/src/contract.rs | 4 ++-- contracts/astroport/oracle/src/mock_querier.rs | 4 ++-- contracts/astroport/oracle/tests/integration.rs | 4 ++-- contracts/credits/src/contract.rs | 4 +++- contracts/cw20-merkle-airdrop/src/contract.rs | 4 ++-- contracts/lockdrop/src/contract.rs | 4 +++- packages/astroport/src/asset.rs | 4 ++-- packages/astroport/src/generator.rs | 4 +++- packages/astroport/src/mock_querier.rs | 8 +++++--- packages/astroport_periphery/src/helpers.rs | 4 ++-- packages/astroport_periphery/src/lockdrop.rs | 4 ++-- packages/vesting-base-lp/src/ext_with_managers.rs | 4 ++-- packages/vesting-base-lp/src/handlers.rs | 6 +++--- packages/vesting-base-lp/src/msg.rs | 4 +++- packages/vesting-base/src/ext_with_managers.rs | 4 ++-- packages/vesting-base/src/handlers.rs | 6 +++--- 16 files changed, 41 insertions(+), 31 deletions(-) diff --git a/contracts/astroport/oracle/src/contract.rs b/contracts/astroport/oracle/src/contract.rs index 1b45e411..f2e63d98 100644 --- a/contracts/astroport/oracle/src/contract.rs +++ b/contracts/astroport/oracle/src/contract.rs @@ -7,8 +7,8 @@ use astroport::oracle::{Config, ExecuteMsg, InstantiateMsg, QueryMsg}; use astroport::pair::TWAP_PRECISION; use astroport::querier::query_pair_info; use cosmwasm_std::{ - entry_point, to_json_binary, Binary, Decimal256, Deps, DepsMut, Env, MessageInfo, Response, Uint128, - Uint256, Uint64, + entry_point, to_json_binary, Binary, Decimal256, Deps, DepsMut, Env, MessageInfo, Response, + Uint128, Uint256, Uint64, }; use cw2::set_contract_version; use std::ops::Div; diff --git a/contracts/astroport/oracle/src/mock_querier.rs b/contracts/astroport/oracle/src/mock_querier.rs index 90a6a2b6..2ddbe4d4 100644 --- a/contracts/astroport/oracle/src/mock_querier.rs +++ b/contracts/astroport/oracle/src/mock_querier.rs @@ -5,8 +5,8 @@ use astroport::pair::QueryMsg::{self, CumulativePrices}; use astroport::pair::{CumulativePricesResponse, SimulationResponse}; use cosmwasm_std::testing::{MockApi, MockQuerier, MockStorage, MOCK_CONTRACT_ADDR}; use cosmwasm_std::{ - from_json, to_json_binary, Addr, Coin, Empty, OwnedDeps, Querier, QuerierResult, - QueryRequest, SystemError, SystemResult, Uint128, WasmQuery, + from_json, to_json_binary, Addr, Coin, Empty, OwnedDeps, Querier, QuerierResult, QueryRequest, + SystemError, SystemResult, Uint128, WasmQuery, }; use cw20::{BalanceResponse, Cw20QueryMsg, TokenInfoResponse}; use std::collections::HashMap; diff --git a/contracts/astroport/oracle/tests/integration.rs b/contracts/astroport/oracle/tests/integration.rs index ba5c544b..5e91d8ac 100644 --- a/contracts/astroport/oracle/tests/integration.rs +++ b/contracts/astroport/oracle/tests/integration.rs @@ -1,7 +1,7 @@ use anyhow::Result; use cosmwasm_std::{ - attr, to_json_binary, Addr, BlockInfo, Coin, Decimal, Decimal256, QueryRequest, StdResult, Uint128, - Uint64, WasmQuery, + attr, to_json_binary, Addr, BlockInfo, Coin, Decimal, Decimal256, QueryRequest, StdResult, + Uint128, Uint64, WasmQuery, }; use cw20::{BalanceResponse, Cw20QueryMsg, MinterResponse}; use cw_multi_test::{App, AppResponse, ContractWrapper, Executor}; diff --git a/contracts/credits/src/contract.rs b/contracts/credits/src/contract.rs index 3abe76f5..d4928cde 100644 --- a/contracts/credits/src/contract.rs +++ b/contracts/credits/src/contract.rs @@ -411,7 +411,9 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { QueryMsg::WithdrawableAmount { address } => { to_json_binary(&query_withdrawable_amount(deps, env, address)?) } - QueryMsg::VestedAmount { address } => to_json_binary(&query_vested_amount(deps, env, address)?), + QueryMsg::VestedAmount { address } => { + to_json_binary(&query_vested_amount(deps, env, address)?) + } QueryMsg::Allocation { address } => to_json_binary(&query_allocation(deps, address)?), QueryMsg::Balance { address } => { to_json_binary(&::cw20_base::contract::query_balance(deps, address)?) diff --git a/contracts/cw20-merkle-airdrop/src/contract.rs b/contracts/cw20-merkle-airdrop/src/contract.rs index 74d93cb1..d371003c 100644 --- a/contracts/cw20-merkle-airdrop/src/contract.rs +++ b/contracts/cw20-merkle-airdrop/src/contract.rs @@ -2,8 +2,8 @@ use crate::enumerable::query_all_address_map; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - attr, coin, to_json_binary, BankMsg, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, - StdResult, Uint128, WasmMsg, + attr, coin, to_json_binary, BankMsg, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, + Response, StdResult, Uint128, WasmMsg, }; use cw2::set_contract_version; use cw20::{BalanceResponse, Cw20Contract, Cw20ExecuteMsg, Cw20QueryMsg}; diff --git a/contracts/lockdrop/src/contract.rs b/contracts/lockdrop/src/contract.rs index 385086ca..ff033e2a 100644 --- a/contracts/lockdrop/src/contract.rs +++ b/contracts/lockdrop/src/contract.rs @@ -582,7 +582,9 @@ fn migrate_pair_step_1( msg: to_json_binary(&Cw20ExecuteMsg::Send { contract: pool_addr, amount: pool.amount_in_lockups, - msg: to_json_binary(&astroport::pair::Cw20HookMsg::WithdrawLiquidity { assets: vec![] })?, + msg: to_json_binary(&astroport::pair::Cw20HookMsg::WithdrawLiquidity { + assets: vec![], + })?, })?, })); attrs.push(attr( diff --git a/packages/astroport/src/asset.rs b/packages/astroport/src/asset.rs index c0bba3c5..cff5ca2a 100644 --- a/packages/astroport/src/asset.rs +++ b/packages/astroport/src/asset.rs @@ -7,8 +7,8 @@ use crate::querier::{ query_balance, query_token_balance, query_token_precision, query_token_symbol, }; use cosmwasm_std::{ - to_json_binary, Addr, Api, BankMsg, Coin, ConversionOverflowError, CosmosMsg, Decimal256, Fraction, - MessageInfo, QuerierWrapper, StdError, StdResult, Uint128, Uint256, WasmMsg, + to_json_binary, Addr, Api, BankMsg, Coin, ConversionOverflowError, CosmosMsg, Decimal256, + Fraction, MessageInfo, QuerierWrapper, StdError, StdResult, Uint128, Uint256, WasmMsg, }; use cw20::{Cw20ExecuteMsg, Cw20QueryMsg, MinterResponse}; use itertools::Itertools; diff --git a/packages/astroport/src/generator.rs b/packages/astroport/src/generator.rs index 4872012d..bf52fc92 100644 --- a/packages/astroport/src/generator.rs +++ b/packages/astroport/src/generator.rs @@ -2,7 +2,9 @@ use crate::asset::{Asset, AssetInfo}; use crate::factory::PairType; use crate::restricted_vector::RestrictedVector; use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{to_json_binary, Addr, Decimal, Env, StdResult, SubMsg, Uint128, Uint64, WasmMsg}; +use cosmwasm_std::{ + to_json_binary, Addr, Decimal, Env, StdResult, SubMsg, Uint128, Uint64, WasmMsg, +}; use cw20::Cw20ReceiveMsg; /// This structure describes the parameters used for creating a contract. diff --git a/packages/astroport/src/mock_querier.rs b/packages/astroport/src/mock_querier.rs index 39a7799a..6a9c0c76 100644 --- a/packages/astroport/src/mock_querier.rs +++ b/packages/astroport/src/mock_querier.rs @@ -1,7 +1,7 @@ use cosmwasm_std::testing::{MockApi, MockQuerier, MockStorage, MOCK_CONTRACT_ADDR}; use cosmwasm_std::{ - from_json, to_json_binary, Coin, Empty, OwnedDeps, Querier, QuerierResult, - QueryRequest, SystemError, SystemResult, Uint128, WasmQuery, + from_json, to_json_binary, Coin, Empty, OwnedDeps, Querier, QuerierResult, QueryRequest, + SystemError, SystemResult, Uint128, WasmQuery, }; use std::collections::HashMap; @@ -161,7 +161,9 @@ impl CW20QueryHandler { } }; - SystemResult::Ok(to_json_binary(&BalanceResponse { balance: *balance }).into()) + SystemResult::Ok( + to_json_binary(&BalanceResponse { balance: *balance }).into(), + ) } _ => panic!("DO NOT ENTER HERE"), } diff --git a/packages/astroport_periphery/src/helpers.rs b/packages/astroport_periphery/src/helpers.rs index c811a8cf..36ef76a7 100644 --- a/packages/astroport_periphery/src/helpers.rs +++ b/packages/astroport_periphery/src/helpers.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{ - to_json_binary, Addr, Binary, CosmosMsg, QuerierWrapper, QueryRequest, StdResult, Uint128, WasmMsg, - WasmQuery, + to_json_binary, Addr, Binary, CosmosMsg, QuerierWrapper, QueryRequest, StdResult, Uint128, + WasmMsg, WasmQuery, }; use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg}; diff --git a/packages/astroport_periphery/src/lockdrop.rs b/packages/astroport_periphery/src/lockdrop.rs index 5e503ca5..42dcc1fa 100644 --- a/packages/astroport_periphery/src/lockdrop.rs +++ b/packages/astroport_periphery/src/lockdrop.rs @@ -2,8 +2,8 @@ use astroport::asset::{Asset, AssetInfo}; use astroport::restricted_vector::RestrictedVector; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{ - to_json_binary, Addr, CosmosMsg, Decimal, Decimal256, Env, StdError, StdResult, Uint128, Uint256, - WasmMsg, + to_json_binary, Addr, CosmosMsg, Decimal, Decimal256, Env, StdError, StdResult, Uint128, + Uint256, WasmMsg, }; use cw20::Cw20ReceiveMsg; use cw_storage_plus::{Key, KeyDeserialize, Prefixer, PrimaryKey}; diff --git a/packages/vesting-base-lp/src/ext_with_managers.rs b/packages/vesting-base-lp/src/ext_with_managers.rs index bbbdaae9..6d0f4feb 100644 --- a/packages/vesting-base-lp/src/ext_with_managers.rs +++ b/packages/vesting-base-lp/src/ext_with_managers.rs @@ -2,8 +2,8 @@ use crate::error::{ext_unsupported_err, ContractError}; use crate::msg::{ExecuteMsgWithManagers, QueryMsgWithManagers}; use crate::state::{CONFIG, VESTING_MANAGERS}; use cosmwasm_std::{ - attr, to_json_binary, Addr, Attribute, Binary, Deps, DepsMut, Env, MessageInfo, Order, Response, - StdError, StdResult, + attr, to_json_binary, Addr, Attribute, Binary, Deps, DepsMut, Env, MessageInfo, Order, + Response, StdError, StdResult, }; /// Contains the with_managers extension check and routing of the message. diff --git a/packages/vesting-base-lp/src/handlers.rs b/packages/vesting-base-lp/src/handlers.rs index 9990a4d6..60e3a436 100644 --- a/packages/vesting-base-lp/src/handlers.rs +++ b/packages/vesting-base-lp/src/handlers.rs @@ -623,9 +623,9 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { limit, order_by, )?)?), - QueryMsg::AvailableAmount { address } => Ok(to_json_binary(&query_vesting_available_amount( - deps, env, address, - )?)?), + QueryMsg::AvailableAmount { address } => Ok(to_json_binary( + &query_vesting_available_amount(deps, env, address)?, + )?), QueryMsg::VestingState {} => Ok(to_json_binary(&query_vesting_state(deps)?)?), QueryMsg::Timestamp {} => Ok(to_json_binary(&query_timestamp(env)?)?), QueryMsg::ManagedExtension { msg } => handle_query_managed_msg(deps, env, msg), diff --git a/packages/vesting-base-lp/src/msg.rs b/packages/vesting-base-lp/src/msg.rs index e7ac7660..898507b9 100644 --- a/packages/vesting-base-lp/src/msg.rs +++ b/packages/vesting-base-lp/src/msg.rs @@ -3,7 +3,9 @@ use crate::types::{ }; use astroport::asset::AssetInfo; use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{to_json_binary, Addr, Binary, CosmosMsg, Decimal, Env, StdResult, Uint128, WasmMsg}; +use cosmwasm_std::{ + to_json_binary, Addr, Binary, CosmosMsg, Decimal, Env, StdResult, Uint128, WasmMsg, +}; use cw20::Cw20ReceiveMsg; /// This structure describes the execute messages available in a vesting contract. diff --git a/packages/vesting-base/src/ext_with_managers.rs b/packages/vesting-base/src/ext_with_managers.rs index bbbdaae9..6d0f4feb 100644 --- a/packages/vesting-base/src/ext_with_managers.rs +++ b/packages/vesting-base/src/ext_with_managers.rs @@ -2,8 +2,8 @@ use crate::error::{ext_unsupported_err, ContractError}; use crate::msg::{ExecuteMsgWithManagers, QueryMsgWithManagers}; use crate::state::{CONFIG, VESTING_MANAGERS}; use cosmwasm_std::{ - attr, to_json_binary, Addr, Attribute, Binary, Deps, DepsMut, Env, MessageInfo, Order, Response, - StdError, StdResult, + attr, to_json_binary, Addr, Attribute, Binary, Deps, DepsMut, Env, MessageInfo, Order, + Response, StdError, StdResult, }; /// Contains the with_managers extension check and routing of the message. diff --git a/packages/vesting-base/src/handlers.rs b/packages/vesting-base/src/handlers.rs index 36b2a57a..b748c39f 100644 --- a/packages/vesting-base/src/handlers.rs +++ b/packages/vesting-base/src/handlers.rs @@ -299,9 +299,9 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { limit, order_by, )?)?), - QueryMsg::AvailableAmount { address } => Ok(to_json_binary(&query_vesting_available_amount( - deps, env, address, - )?)?), + QueryMsg::AvailableAmount { address } => Ok(to_json_binary( + &query_vesting_available_amount(deps, env, address)?, + )?), QueryMsg::VestingState {} => Ok(to_json_binary(&query_vesting_state(deps)?)?), QueryMsg::Timestamp {} => Ok(to_json_binary(&query_timestamp(env)?)?), QueryMsg::ManagedExtension { msg } => handle_query_managed_msg(deps, env, msg), From 9d56ed52b283d01b0d4ad50768241e76961b5a33 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Fri, 10 Nov 2023 12:30:27 +0400 Subject: [PATCH 08/14] add batch & debug --- packages/vesting-base-lp/src/handlers.rs | 10 ++++++++-- packages/vesting-base-lp/src/msg.rs | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/vesting-base-lp/src/handlers.rs b/packages/vesting-base-lp/src/handlers.rs index 4f5b5061..f054bfd9 100644 --- a/packages/vesting-base-lp/src/handlers.rs +++ b/packages/vesting-base-lp/src/handlers.rs @@ -101,7 +101,7 @@ pub fn execute( ExecuteMsg::HistoricalExtension { msg } => { handle_execute_historical_msg(deps, env, info, msg) } - ExecuteMsg::MigrateLiquidity { slippage_tolerance } => { + ExecuteMsg::MigrateLiquidity { slippage_tolerance, batch_size } => { execute_migrate_liquidity(deps, env, slippage_tolerance) } ExecuteMsg::Callback(msg) => _handle_callback(deps, env, info, msg), @@ -299,6 +299,7 @@ fn execute_migrate_liquidity( deps: DepsMut, env: Env, slippage_tolerance: Option, + batch_size: Option, ) -> Result { let migration_state: MigrationState = MIGRATION_STATUS.load(deps.storage)?; if migration_state == MigrationState::Completed { @@ -306,10 +307,15 @@ fn execute_migrate_liquidity( } let migration_config: XykToClMigrationConfig = XYK_TO_CL_MIGRATION_CONFIG.load(deps.storage)?; + batch = if Some(batch_size) { + batch_size + } else { + migration_config.batch_size + }; let vesting_infos = read_vesting_infos( deps.as_ref(), migration_config.last_processed_user, - Some(migration_config.batch_size), + Some(batch), None, )?; diff --git a/packages/vesting-base-lp/src/msg.rs b/packages/vesting-base-lp/src/msg.rs index 30cd9d1f..2c219c17 100644 --- a/packages/vesting-base-lp/src/msg.rs +++ b/packages/vesting-base-lp/src/msg.rs @@ -50,7 +50,7 @@ pub enum ExecuteMsg { /// Contains messages associated with the historical extension for vesting contracts. HistoricalExtension { msg: ExecuteMsgHistorical }, /// - MigrateLiquidity { slippage_tolerance: Option }, + MigrateLiquidity { slippage_tolerance: Option, batch_size: Option }, /// Callbacks; only callable by the contract itself. Callback(CallbackMsg), } From eb8fd43a39a67e26259337171c777fccd216ef42 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Fri, 10 Nov 2023 13:03:14 +0400 Subject: [PATCH 09/14] add debug & batch) --- packages/vesting-base-lp/src/handlers.rs | 26 ++++++++++++++++-------- packages/vesting-base-lp/src/msg.rs | 5 ++++- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/vesting-base-lp/src/handlers.rs b/packages/vesting-base-lp/src/handlers.rs index f054bfd9..732d9a72 100644 --- a/packages/vesting-base-lp/src/handlers.rs +++ b/packages/vesting-base-lp/src/handlers.rs @@ -38,6 +38,7 @@ pub fn execute( match msg { ExecuteMsg::MigrateLiquidity { slippage_tolerance: _, + batch_size: _, } => {} ExecuteMsg::Callback(..) => {} _ => return Err(ContractError::MigrationIncomplete {}), @@ -101,9 +102,10 @@ pub fn execute( ExecuteMsg::HistoricalExtension { msg } => { handle_execute_historical_msg(deps, env, info, msg) } - ExecuteMsg::MigrateLiquidity { slippage_tolerance, batch_size } => { - execute_migrate_liquidity(deps, env, slippage_tolerance) - } + ExecuteMsg::MigrateLiquidity { + slippage_tolerance, + batch_size, + } => execute_migrate_liquidity(deps, env, slippage_tolerance, batch_size), ExecuteMsg::Callback(msg) => _handle_callback(deps, env, info, msg), } } @@ -307,15 +309,15 @@ fn execute_migrate_liquidity( } let migration_config: XykToClMigrationConfig = XYK_TO_CL_MIGRATION_CONFIG.load(deps.storage)?; - batch = if Some(batch_size) { + let batch = if Some(batch_size).is_some() { batch_size } else { - migration_config.batch_size + Some(migration_config.batch_size) }; let vesting_infos = read_vesting_infos( deps.as_ref(), migration_config.last_processed_user, - Some(batch), + batch, None, )?; @@ -336,6 +338,12 @@ fn execute_migrate_liquidity( for user in vesting_accounts.into_iter() { let user_amount = compute_share(&user.info)?; + let debug_msg = format!( + "DEBUG: execute_migrate_liquidity: user={}, user_amount={}", + user.address.to_string(), + user_amount.to_string() + ); + deps.api.debug(&debug_msg); if let Some(slippage_tolerance) = slippage_tolerance { if slippage_tolerance.gt(&migration_config.max_slippage) { @@ -446,7 +454,8 @@ fn migrate_liquidity_to_cl_pair_callback( .amount; let mut msgs: Vec = vec![]; - + let debug_msg = format!("DEBUG: migrate_liquidity_to_cl_pair_callback: ntrn_init_balance={}, paired_asset_init_balance={}, amount={}, ntrn_denom={}, paired_denom={}", ntrn_init_balance.to_string(), paired_asset_init_balance.to_string(), amount.to_string(), ntrn_denom, paired_asset_denom); + deps.api.debug(&debug_msg); // push message to withdraw liquidity from the xyk pair if !amount.is_zero() { msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { @@ -501,7 +510,8 @@ fn provide_liquidity_to_cl_pair_after_withdrawal_callback( 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 debug_msg = format!("DEBUG: provide_liquidity_to_cl_pair_after_withdrawal_callback: ntrn_init_balance={}, paired_asset_init_balance={}, ntrn_balance_after_withdrawal={}, paired_asset_balance_after_withdrawal={}, withdrawn_ntrn_amount={}, withdrawn_paired_asset_amount={}, ntrn_denom={}, paired_asset_denom={}", ntrn_init_balance.to_string(), paired_asset_init_balance.to_string(), ntrn_balance_after_withdrawal.to_string(), paired_asset_balance_after_withdrawal.to_string(), withdrawn_ntrn_amount.to_string(), withdrawn_paired_asset_amount.to_string(), ntrn_denom, paired_asset_denom); + deps.api.debug(&debug_msg); let mut msgs: Vec = vec![]; if !withdrawn_ntrn_amount.is_zero() && !withdrawn_paired_asset_amount.is_zero() { diff --git a/packages/vesting-base-lp/src/msg.rs b/packages/vesting-base-lp/src/msg.rs index 2c219c17..0ecdcab8 100644 --- a/packages/vesting-base-lp/src/msg.rs +++ b/packages/vesting-base-lp/src/msg.rs @@ -50,7 +50,10 @@ pub enum ExecuteMsg { /// Contains messages associated with the historical extension for vesting contracts. HistoricalExtension { msg: ExecuteMsgHistorical }, /// - MigrateLiquidity { slippage_tolerance: Option, batch_size: Option }, + MigrateLiquidity { + slippage_tolerance: Option, + batch_size: Option, + }, /// Callbacks; only callable by the contract itself. Callback(CallbackMsg), } From 21f2552a15a2ea33c89a542073333b5a999eca01 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Fri, 10 Nov 2023 13:14:56 +0400 Subject: [PATCH 10/14] clippy + fmt --- packages/vesting-base-lp/src/handlers.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/vesting-base-lp/src/handlers.rs b/packages/vesting-base-lp/src/handlers.rs index 732d9a72..c4f307b5 100644 --- a/packages/vesting-base-lp/src/handlers.rs +++ b/packages/vesting-base-lp/src/handlers.rs @@ -340,8 +340,7 @@ fn execute_migrate_liquidity( let user_amount = compute_share(&user.info)?; let debug_msg = format!( "DEBUG: execute_migrate_liquidity: user={}, user_amount={}", - user.address.to_string(), - user_amount.to_string() + user.address, user_amount ); deps.api.debug(&debug_msg); @@ -454,7 +453,7 @@ fn migrate_liquidity_to_cl_pair_callback( .amount; let mut msgs: Vec = vec![]; - let debug_msg = format!("DEBUG: migrate_liquidity_to_cl_pair_callback: ntrn_init_balance={}, paired_asset_init_balance={}, amount={}, ntrn_denom={}, paired_denom={}", ntrn_init_balance.to_string(), paired_asset_init_balance.to_string(), amount.to_string(), ntrn_denom, paired_asset_denom); + let debug_msg = format!("DEBUG: migrate_liquidity_to_cl_pair_callback: ntrn_init_balance={}, paired_asset_init_balance={}, amount={}, ntrn_denom={}, paired_denom={}", ntrn_init_balance, paired_asset_init_balance, amount, ntrn_denom, paired_asset_denom); deps.api.debug(&debug_msg); // push message to withdraw liquidity from the xyk pair if !amount.is_zero() { @@ -510,7 +509,7 @@ fn provide_liquidity_to_cl_pair_after_withdrawal_callback( 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 debug_msg = format!("DEBUG: provide_liquidity_to_cl_pair_after_withdrawal_callback: ntrn_init_balance={}, paired_asset_init_balance={}, ntrn_balance_after_withdrawal={}, paired_asset_balance_after_withdrawal={}, withdrawn_ntrn_amount={}, withdrawn_paired_asset_amount={}, ntrn_denom={}, paired_asset_denom={}", ntrn_init_balance.to_string(), paired_asset_init_balance.to_string(), ntrn_balance_after_withdrawal.to_string(), paired_asset_balance_after_withdrawal.to_string(), withdrawn_ntrn_amount.to_string(), withdrawn_paired_asset_amount.to_string(), ntrn_denom, paired_asset_denom); + let debug_msg = format!("DEBUG: provide_liquidity_to_cl_pair_after_withdrawal_callback: ntrn_init_balance={}, paired_asset_init_balance={}, ntrn_balance_after_withdrawal={}, paired_asset_balance_after_withdrawal={}, withdrawn_ntrn_amount={}, withdrawn_paired_asset_amount={}, ntrn_denom={}, paired_asset_denom={}", ntrn_init_balance, paired_asset_init_balance, ntrn_balance_after_withdrawal, paired_asset_balance_after_withdrawal, withdrawn_ntrn_amount, withdrawn_paired_asset_amount, ntrn_denom, paired_asset_denom); deps.api.debug(&debug_msg); let mut msgs: Vec = vec![]; From 2bc5233c90dd92d077a02438223380233882651d Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Fri, 10 Nov 2023 17:40:17 +0400 Subject: [PATCH 11/14] poc of dust threshold --- packages/vesting-base-lp/src/handlers.rs | 9 ++++++++- packages/vesting-base-lp/src/msg.rs | 1 + packages/vesting-base-lp/src/types.rs | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/vesting-base-lp/src/handlers.rs b/packages/vesting-base-lp/src/handlers.rs index c4f307b5..295bbe23 100644 --- a/packages/vesting-base-lp/src/handlers.rs +++ b/packages/vesting-base-lp/src/handlers.rs @@ -337,7 +337,13 @@ fn execute_migrate_liquidity( .query_wasm_smart(migration_config.xyk_pair.clone(), &PairQueryMsg::Pair {})?; for user in vesting_accounts.into_iter() { - let user_amount = compute_share(&user.info)?; + let user_share = compute_share(&user.info)?; + let user_amount = if user_share < migration_config.dust_threshold { + Uint128::zero() + } else { + user_share + }; + let debug_msg = format!( "DEBUG: execute_migrate_liquidity: user={}, user_amount={}", user.address, user_amount @@ -723,6 +729,7 @@ pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> Result, pub batch_size: u32, + pub dust_threshold: Uint128, } #[cw_serde] From fef26e665f4e6c8300ab311b91ed186b35aab47a Mon Sep 17 00:00:00 2001 From: pr0n00gler Date: Sun, 12 Nov 2023 22:46:29 +0200 Subject: [PATCH 12/14] send LP dust to a user instead of withdraw --- packages/vesting-base-lp/src/handlers.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/vesting-base-lp/src/handlers.rs b/packages/vesting-base-lp/src/handlers.rs index 295bbe23..08db22db 100644 --- a/packages/vesting-base-lp/src/handlers.rs +++ b/packages/vesting-base-lp/src/handlers.rs @@ -19,6 +19,7 @@ use astroport::common::{claim_ownership, drop_ownership_proposal, propose_new_ow 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, @@ -339,6 +340,15 @@ fn execute_migrate_liquidity( for user in vesting_accounts.into_iter() { let user_share = compute_share(&user.info)?; let user_amount = if user_share < migration_config.dust_threshold { + resp = resp.add_message(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: pair_info.liquidity_token.to_string(), + msg: to_binary(&Cw20ExecuteMsg::Transfer { + recipient: user.address.to_string(), + amount: user_share, + })?, + funds: vec![], + })); + Uint128::zero() } else { user_share From 3fcb25005d12d16e5be5bff31945ea7329dc2307 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Mon, 13 Nov 2023 13:31:15 +0400 Subject: [PATCH 13/14] rm debug msgs --- packages/vesting-base-lp/src/handlers.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packages/vesting-base-lp/src/handlers.rs b/packages/vesting-base-lp/src/handlers.rs index 08db22db..a1fc0d6a 100644 --- a/packages/vesting-base-lp/src/handlers.rs +++ b/packages/vesting-base-lp/src/handlers.rs @@ -354,12 +354,6 @@ fn execute_migrate_liquidity( user_share }; - let debug_msg = format!( - "DEBUG: execute_migrate_liquidity: user={}, user_amount={}", - user.address, user_amount - ); - deps.api.debug(&debug_msg); - if let Some(slippage_tolerance) = slippage_tolerance { if slippage_tolerance.gt(&migration_config.max_slippage) { return Err(ContractError::MigrationSlippageToBig { @@ -469,8 +463,6 @@ fn migrate_liquidity_to_cl_pair_callback( .amount; let mut msgs: Vec = vec![]; - let debug_msg = format!("DEBUG: migrate_liquidity_to_cl_pair_callback: ntrn_init_balance={}, paired_asset_init_balance={}, amount={}, ntrn_denom={}, paired_denom={}", ntrn_init_balance, paired_asset_init_balance, amount, ntrn_denom, paired_asset_denom); - deps.api.debug(&debug_msg); // push message to withdraw liquidity from the xyk pair if !amount.is_zero() { msgs.push(CosmosMsg::Wasm(WasmMsg::Execute { @@ -525,8 +517,6 @@ fn provide_liquidity_to_cl_pair_after_withdrawal_callback( 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 debug_msg = format!("DEBUG: provide_liquidity_to_cl_pair_after_withdrawal_callback: ntrn_init_balance={}, paired_asset_init_balance={}, ntrn_balance_after_withdrawal={}, paired_asset_balance_after_withdrawal={}, withdrawn_ntrn_amount={}, withdrawn_paired_asset_amount={}, ntrn_denom={}, paired_asset_denom={}", ntrn_init_balance, paired_asset_init_balance, ntrn_balance_after_withdrawal, paired_asset_balance_after_withdrawal, withdrawn_ntrn_amount, withdrawn_paired_asset_amount, ntrn_denom, paired_asset_denom); - deps.api.debug(&debug_msg); let mut msgs: Vec = vec![]; if !withdrawn_ntrn_amount.is_zero() && !withdrawn_paired_asset_amount.is_zero() { From 5b65a9ed5f56e55e6e7ef0e19e5eb7fa18c84e09 Mon Sep 17 00:00:00 2001 From: quasisamurai Date: Mon, 13 Nov 2023 13:34:49 +0400 Subject: [PATCH 14/14] do not send zero amount --- packages/vesting-base-lp/src/handlers.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/vesting-base-lp/src/handlers.rs b/packages/vesting-base-lp/src/handlers.rs index a1fc0d6a..97ef7a55 100644 --- a/packages/vesting-base-lp/src/handlers.rs +++ b/packages/vesting-base-lp/src/handlers.rs @@ -340,14 +340,16 @@ fn execute_migrate_liquidity( for user in vesting_accounts.into_iter() { let user_share = compute_share(&user.info)?; let user_amount = if user_share < migration_config.dust_threshold { - resp = resp.add_message(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: pair_info.liquidity_token.to_string(), - msg: to_binary(&Cw20ExecuteMsg::Transfer { - recipient: user.address.to_string(), - amount: user_share, - })?, - funds: vec![], - })); + if !user_share.is_zero() { + resp = resp.add_message(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: pair_info.liquidity_token.to_string(), + msg: to_binary(&Cw20ExecuteMsg::Transfer { + recipient: user.address.to_string(), + amount: user_share, + })?, + funds: vec![], + })); + } Uint128::zero() } else {